UNPKG

@eaze/accordion

Version:

React components: AccordionElement

190 lines (168 loc) 4.39 kB
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 }