Skip to content

Commit

Permalink
add types to Accordion
Browse files Browse the repository at this point in the history
  • Loading branch information
Johan Reitan authored and joharei committed Apr 15, 2020
1 parent 9b54fb9 commit 6261733
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 170 deletions.
62 changes: 35 additions & 27 deletions libraries/core-react/src/Accordion/Accordion.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,50 @@ import React, { forwardRef, useMemo, useState } from 'react'
import createId from 'lodash.uniqueid'
import { commonPropTypes, commonDefaultProps } from './Accordion.propTypes'

const Accordion = forwardRef(function Accordion(
{ headerLevel, chevronPosition, children, ...props },
ref,
) {
const accordionId = useMemo(() => createId('accordion-'), [])

const [focusVisible, setFocusVisible] = useState(true)

const handleFocusVisible = (isFocusVisible) => {
setFocusVisible(isFocusVisible)
}

const AccordionItems = React.Children.map(children, (child, index) => {
return React.cloneElement(child, {
accordionId,
index,
headerLevel,
chevronPosition,
focusVisible,
handleFocusVisible,
const Accordion = forwardRef(
/**
* @param {import('./Accordion.propTypes').CommonProps & React.HTMLAttributes<HTMLDivElement>} props
* @param ref
*/
function Accordion({ headerLevel, chevronPosition, children, ...rest }, ref) {
const accordionId = useMemo(() => createId('accordion-'), [])

const [focusVisible, setFocusVisible] = useState(true)

const handleFocusVisible = (isFocusVisible) => {
setFocusVisible(isFocusVisible)
}

const AccordionItems = React.Children.map(children, (child, index) => {
return React.cloneElement(
/** @type {React.ReactElement<any>} */ (child),
{
accordionId,
index,
headerLevel,
chevronPosition,
focusVisible,
handleFocusVisible,
},
)
})
})

return (
<div {...props} ref={ref}>
{AccordionItems}
</div>
)
})
return (
<div {...rest} ref={ref}>
{AccordionItems}
</div>
)
},
)

Accordion.displayName = 'eds-accordion'

// @ts-ignore
Accordion.propTypes = {
...commonPropTypes,
}

// @ts-ignore
Accordion.defaultProps = {
...commonDefaultProps,
}
Expand Down
6 changes: 6 additions & 0 deletions libraries/core-react/src/Accordion/Accordion.propTypes.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import PropTypes from 'prop-types'

/**
* @typedef CommonProps
* @prop {'left' | 'right'} [chevronPosition] Which side the chevron should be on
* @prop {'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'} [headerLevel] The header level, i.e. h1, h2, h3 etc.
*/

export const commonPropTypes = {
/** Which side the chevron should be on */
chevronPosition: PropTypes.oneOf(['left', 'right']),
Expand Down
182 changes: 103 additions & 79 deletions libraries/core-react/src/Accordion/AccordionHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,92 +70,113 @@ const AccordionHeaderTitle = styled.span`

AccordionHeaderTitle.displayName = 'eds-accordion-headertitle'

const AccordionHeader = forwardRef(function AccordionHeader(
{
parentIndex,
headerLevel,
chevronPosition,
panelId,
id,
isExpanded,
children,
toggleExpanded,
disabled,
focusVisible,
handleFocusVisible,
...props
},
ref,
) {
const handleClick = () => {
handleFocusVisible(false)
if (!disabled) {
toggleExpanded()
/**
* @typedef Props
* @prop {string} [id] The id of the button that toggles expansion
* @prop {boolean} [isExpanded] Is AccordionItem expanded
* @prop {string} [panelId] The panel that is controlled by the HeaderButton
* @prop {React.ReactNode} children
* @prop {number} [parentIndex] The index of the parent AccordionItem
* @prop {boolean} [disabled] accordion item is disabled
* @prop {boolean} [focusVisible]
* @prop {(isFocusVisible: boolean) => void} [handleFocusVisible]
* @prop {() => void} [toggleExpanded]
*/

const AccordionHeader = forwardRef(
/**
* @param {Props & import('./Accordion.propTypes').CommonProps & React.HTMLAttributes<HTMLDivElement>} props
* @param ref
*/
function AccordionHeader(
{
parentIndex,
headerLevel,
chevronPosition,
panelId,
id,
isExpanded,
children,
toggleExpanded,
disabled,
focusVisible,
handleFocusVisible,
...rest
},
ref,
) {
const handleClick = () => {
handleFocusVisible(false)
if (!disabled) {
toggleExpanded()
}
}
}

const handleMouseDown = () => {
handleFocusVisible(false)
}

const handleKeyDown = (event) => {
const { key } = event
handleFocusVisible(true)
if (key === 'Enter' || key === ' ') {
toggleExpanded()
event.preventDefault()

const handleMouseDown = () => {
handleFocusVisible(false)
}

const handleKeyDown = (event) => {
const { key } = event
handleFocusVisible(true)
if (key === 'Enter' || key === ' ') {
toggleExpanded()
event.preventDefault()
}
}
}

const chevron = (
<StyledIcon
key={`${id}-icon`}
name={isExpanded ? 'chevron_up' : 'chevron_down'}
size={16}
chevronPosition={chevronPosition}
color={disabled ? chevronDisabledColor : chevronDefaultColor}
/>
)

const headerChildren = React.Children.map(children, (child) => {

const chevron = (
<StyledIcon
key={`${id}-icon`}
name={isExpanded ? 'chevron_up' : 'chevron_down'}
size={16}
chevronPosition={chevronPosition}
color={disabled ? chevronDisabledColor : chevronDefaultColor}
/>
)

const headerChildren = React.Children.map(children, (child) => {
return (
(typeof child === 'string' && (
<AccordionHeaderTitle isExpanded={isExpanded} disabled={disabled}>
{child}
</AccordionHeaderTitle>
)) ||
// @ts-ignore
(child.type.displayName === 'eds-accordion-headertitle' &&
React.cloneElement(/** @type {React.ReactElement<any>} */ (child), {
isExpanded,
disabled,
})) ||
child
)
})

const newChildren = [chevron, headerChildren]

return (
(typeof child === 'string' && (
<AccordionHeaderTitle isExpanded={isExpanded} disabled={disabled}>
{child}
</AccordionHeaderTitle>
)) ||
(child.type.displayName === 'eds-accordion-headertitle' &&
React.cloneElement(child, {
isExpanded,
disabled,
})) ||
child
<StyledAccordionHeader
isExpanded={isExpanded}
parentIndex={parentIndex}
as={headerLevel}
disabled={disabled}
{...rest}
panelId={panelId}
onClick={handleClick}
onMouseDown={handleMouseDown}
onKeyDown={handleKeyDown}
focusVisible={focusVisible}
ref={ref}
>
{chevronPosition === 'left' ? newChildren : newChildren.reverse()}
</StyledAccordionHeader>
)
})

const newChildren = [chevron, headerChildren]

return (
<StyledAccordionHeader
isExpanded={isExpanded}
parentIndex={parentIndex}
as={headerLevel}
disabled={disabled}
{...props}
panelId={panelId}
onClick={handleClick}
onMouseDown={handleMouseDown}
onKeyDown={handleKeyDown}
focusVisible={focusVisible}
ref={ref}
>
{chevronPosition === 'left' ? newChildren : newChildren.reverse()}
</StyledAccordionHeader>
)
})
},
)

AccordionHeader.displayName = 'eds-accordion-header'

// @ts-ignore
AccordionHeader.propTypes = {
...commonPropTypes,
/** The id of the button that toggles expansion */
Expand All @@ -174,8 +195,10 @@ AccordionHeader.propTypes = {
focusVisible: PropTypes.bool,
/** @ignore */
handleFocusVisible: PropTypes.func,
toggleExpanded: PropTypes.func,
}

// @ts-ignore
AccordionHeader.defaultProps = {
...commonDefaultProps,
id: '',
Expand All @@ -185,6 +208,7 @@ AccordionHeader.defaultProps = {
disabled: false,
focusVisible: true,
handleFocusVisible: () => {},
toggleExpanded: () => {},
}

export { AccordionHeader, AccordionHeaderTitle }
Loading

0 comments on commit 6261733

Please sign in to comment.