@eaze/accordion
Version:
React components: AccordionElement
190 lines (168 loc) • 4.39 kB
JavaScript
import React, { PureComponent } from 'react'
import { bool, node, string, object, number } from 'prop-types'
import styled from '@emotion/styled'
const defaultFontColor = '#000'
export default class AccordionElement extends PureComponent {
static propTypes = {
buttonClass: string,
buttonContent: node,
children: node,
icon: object,
iconColor: string,
iconWidth: number,
iconHeight: number,
openByDefault: bool,
reverseDirection: bool,
rotate: string
}
static defaultProps = {
buttonClass: 'button',
fontColor: defaultFontColor,
reverseDirection: false
}
state = {
open: false,
contentHeight: 0
}
toggleContent = (event) => {
// adding preventDefault so we can have this component in
// forms without worrying about toggling triggering a submit
event.preventDefault()
const { open } = this.state
this.setState({ open: !open })
}
componentDidMount () {
this.setState({ contentHeight: this.accordionContent.clientHeight })
this.props.openByDefault && this.setState({ open: true })
}
componentDidUpdate (prevProps, prevState) {
if (this.accordionContent.clientHeight !== prevState.contentHeight) {
this.setState({ contentHeight: this.accordionContent.clientHeight })
}
}
render () {
const { open, contentHeight } = this.state
const {
children,
buttonContent,
buttonClass,
icon,
iconColor,
iconHeight,
iconWidth,
rotate,
reverseDirection
} = this.props
return (
<AccordionWrapper reverseDirection={reverseDirection}>
<Button
className={buttonClass}
onClick={this.toggleContent}
>
<IconContainer
open={open}
iconWidth={iconWidth}
iconHeight={iconHeight}
rotate={rotate}
>
{icon || <Arrow iconColor={iconColor}/>}
</IconContainer>
{buttonContent}
</Button>
<ContentWrapper open={open} height={contentHeight}>
<div
style={{ position: 'relative' }}
ref={(accordionContent) => { this.accordionContent = accordionContent }}
>
{children}
</div>
</ContentWrapper>
</AccordionWrapper>
)
}
}
// export some standardized variants
export const ChevronAccordion = ({ iconColor, buttonContent, children }) => (
<AccordionElement
icon={<Chevron iconColor={iconColor} />}
iconWidth={1.5}
iconHeight={1.5}
buttonContent={buttonContent}
>
{children}
</AccordionElement>
)
ChevronAccordion.propTypes = {
buttonContent: string,
children: node,
iconColor: string
}
const AccordionWrapper = styled.div`
display: flex;
flex-direction: ${({ reverseDirection }) => reverseDirection ? 'column-reverse' : 'column'};
`
const ContentWrapper = styled.div`
height: ${({ open, height }) => open ? height + 'px' : 0};
overflow: hidden;
transition: height 0.25s ease-out;
`
const Button = styled.div`
padding: 0;
width: 100%;
border: none;
background-color: transparent;
color: ${({ fontColor }) => fontColor};
cursor: pointer;
display: flex;
align-items: center;
user-select: none;
`
const IconContainer = styled.div`
display: flex;
transform: ${({ open, rotate }) => open ? (rotate || 'rotate(90deg)') : 'rotate(0deg)'};
transition: transform .25s ease-out;
& svg {
width: ${({ iconWidth }) => iconWidth + 'rem' || '1rem'};
height: ${({ iconHeight }) => iconHeight + 'rem' || '1rem'};
}
`
const Chevron = ({ iconColor }) => (
<svg
width='12px'
height='20px'
viewBox='0 0 12 20'
version='1.1'
xmlnsXlink='http://www.w3.org/1999/xlink'
>
<g
stroke={iconColor || '#000'}
strokeWidth='1.5px'
fill='none'
fillRule='evenodd'
strokeLinecap='round'
>
<path d='M0 0l9 9m0 0l-9 8.485' />
</g>
</svg>
)
Chevron.propTypes = {
iconColor: string
}
const Arrow = ({ iconColor }) => (
<svg
width='7px'
height='13px'
viewBox='0 0 7 13'
version='1.1'
xmlnsXlink='http://www.w3.org/1999/xlink'
>
<polygon
fill={iconColor || '#000'}
points='0.77578125 12.6097656 0.77578125 0.122265625 7.01953125 6.36601562'
fillRule='evenodd'
/>
</svg>
)
Arrow.propTypes = {
iconColor: string
}