Skip to content

Commit

Permalink
Children type checking (#729)
Browse files Browse the repository at this point in the history
* Improved child checking

* Fixed & simplified popover children splitting

* brainfart 🤦🏻‍♂️

* Fixed menulist using displayName

* Remove unecessary type
  • Loading branch information
Michael Marszalek authored and vnys committed Nov 13, 2020
1 parent af1a6c5 commit 16bfef6
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 52 deletions.
25 changes: 14 additions & 11 deletions libraries/core-react/src/Accordion/AccordionHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { forwardRef, ReactElement } from 'react'
import React, { forwardRef, isValidElement, ReactElement } from 'react'
import styled from 'styled-components'
import type { CSSObject } from 'styled-components'
// eslint-disable-next-line camelcase
Expand Down Expand Up @@ -151,19 +151,22 @@ const AccordionHeader = forwardRef<
const headerChildren = React.Children.map(
children,
(child: AccordionChild) => {
return (
(typeof child === 'string' && (
if (typeof child === 'string') {
return (
<AccordionHeaderTitle isExpanded={isExpanded} disabled={disabled}>
{child}
</AccordionHeaderTitle>
)) ||
(child.type.displayName === 'eds-accordion-headertitle' &&
React.cloneElement(child, {
isExpanded,
disabled,
})) ||
child
)
)
}

if (isValidElement(child) && child.type === AccordionHeaderTitle) {
return React.cloneElement(child, {
isExpanded,
disabled,
})
}

return child
},
)

Expand Down
4 changes: 2 additions & 2 deletions libraries/core-react/src/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const Content = styled.div`
align-items: center;
`

export type MenuItemProps = {
export type Props = {
/** @ignore */
index?: number
/** Is active */
Expand All @@ -98,7 +98,7 @@ export type MenuItemProps = {
} & React.HTMLAttributes<HTMLLIElement>

export const MenuItem = React.memo(
React.forwardRef<HTMLLIElement, MenuItemProps>(function EdsMenuItem(
React.forwardRef<HTMLLIElement, Props>(function EdsMenuItem(
{ children, disabled, index = 0, onClick, ...rest },
ref,
) {
Expand Down
21 changes: 13 additions & 8 deletions libraries/core-react/src/Menu/MenuList.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React, { useEffect, ReactElement, ReactNode } from 'react'
import React, {
useEffect,
ReactElement,
ReactNode,
isValidElement,
} from 'react'
import styled from 'styled-components'
import { useMenu } from './Menu.context'
import type { FocusTarget } from './Menu.types'
import type { MenuItemProps } from './MenuItem'
import type { MenuSectionProps } from './MenuSection'
import { Props as MenuItemProps, MenuItem } from './MenuItem'
import { Props as MenuSectionProps, MenuSection } from './MenuSection'

const isFragment = (object: ReactNode): boolean => {
if ((object as ReactElement).type) {
Expand All @@ -27,10 +32,7 @@ type Props = {
children: ReactNode
}

type MenuChild = ReactElement<MenuItemProps> &
ReactElement<MenuSectionProps> & {
type: { displayName?: string }
}
type MenuChild = ReactElement<MenuItemProps> & ReactElement<MenuSectionProps>

type Direction = 'down' | 'up'

Expand All @@ -50,7 +52,10 @@ export const MenuList = React.forwardRef<HTMLUListElement, Props>(

const focusableIndexs: number[] = (updatedChildren || [])
.filter((x) => !x.props.disabled)
.filter((x) => x.type ?? x.type.displayName.includes('eds-menu'))
.filter(
(x) =>
isValidElement(x) && (x.type === MenuSection || x.type === MenuItem),
)
.map((x) => x.props.index)

const firstFocusIndex = focusableIndexs[0]
Expand Down
6 changes: 2 additions & 4 deletions libraries/core-react/src/Menu/MenuSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const ListItem = styled.li.attrs(() => ({
}
`

export type MenuSectionProps = {
export type Props = {
/** @ignore */
index?: number
/** @ignore */
Expand All @@ -31,9 +31,7 @@ export type MenuSectionProps = {
title?: string
}

export const MenuSection = React.memo(function EdsMenuSection(
props: MenuSectionProps,
) {
export const MenuSection = React.memo(function EdsMenuSection(props: Props) {
const { children, title, index } = props
return (
<>
Expand Down
54 changes: 27 additions & 27 deletions libraries/core-react/src/Popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import React, { forwardRef, useRef, HTMLAttributes, ReactNode } from 'react'
import React, {
forwardRef,
useRef,
HTMLAttributes,
ReactNode,
isValidElement,
} from 'react'
import styled from 'styled-components'
import { PopoverItem } from './PopoverItem'
import { PopoverAnchor } from './PopoverAnchor'

const Container = styled.div`
position: relative;
Expand All @@ -13,9 +20,10 @@ const Anchor = styled.div`
outline: none;
}
`
type PopoverChild = {
type?: { displayName?: string }
} & ReactNode
type PopoverSplit = {
anchorElement: ReactNode
childArray: ReactNode[]
}

export type Props = {
/* Popover placement relative to anchor */
Expand Down Expand Up @@ -52,30 +60,22 @@ export const Popover = forwardRef<HTMLDivElement, Props>(function Popover(
return <Container {...props} />
}
const anchorRef = useRef<HTMLDivElement>(null)
const popoverChildren: PopoverChild | PopoverChild[] = children
let anchorElement: PopoverChild
const childArray = []
if (Array.isArray(popoverChildren)) {
for (let i = 0; i < popoverChildren.length; i += 1) {
/*
Find anchor element in children to wrap the element together with <PopoverItem/>.
Children is required, but user has to wrap the actual anchor with <PopoverAnchor />
*/
const child = popoverChildren[i] as PopoverChild
if (child.type && child.type.displayName === 'eds-popover-anchor') {
anchorElement = child
} else {
// Add the remaining children to a new array to display inside <PopoverItem/>
childArray.push(child)

const { anchorElement, childArray } = React.Children.toArray(children).reduce(
(acc: PopoverSplit, child): PopoverSplit => {
if (isValidElement(child) && child.type === PopoverAnchor) {
return {
...acc,
anchorElement: child,
}
}
}
} else if (
!Array.isArray(popoverChildren) &&
popoverChildren.type &&
popoverChildren.type.displayName === 'eds-popover-anchor'
) {
anchorElement = popoverChildren
}
return {
...acc,
childArray: [...acc.childArray, child],
}
},
{ anchorElement: null, childArray: [] },
)

if (open && anchorRef.current) {
anchorRef.current.focus()
Expand Down

0 comments on commit 16bfef6

Please sign in to comment.