UNPKG

semantic-ui-react

Version:
208 lines (172 loc) 5.83 kB
import _ from 'lodash' import cx from 'classnames' import React, { Children, cloneElement, PropTypes } from 'react' import { AutoControlledComponent as Component, customPropTypes, getElementType, META, useKeyOnly, } from '../../lib' import AccordionContent from './AccordionContent' import AccordionTitle from './AccordionTitle' /** * An accordion allows users to toggle the display of sections of content. */ export default class Accordion extends Component { static propTypes = { /** An element type to render as (string or function). */ as: customPropTypes.as, /** Index of the currently active panel. */ activeIndex: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.number), PropTypes.number, ]), /** Primary content. */ children: PropTypes.node, /** Additional classes. */ className: PropTypes.string, /** Initial activeIndex value. */ defaultActiveIndex: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.number), PropTypes.number, ]), /** Only allow one panel open at a time. */ exclusive: PropTypes.bool, /** Format to take up the width of it's container. */ fluid: PropTypes.bool, /** Format for dark backgrounds. */ inverted: PropTypes.bool, /** * Called when a panel title is clicked. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {number} index - The index of the clicked panel. */ onTitleClick: PropTypes.func, /** * Create simple accordion panels from an array of { text: <string>, content: <custom> } objects. * Object can optionally define an `active` key to open/close the panel. * Object can opitonally define a `key` key used for title and content nodes' keys. * Mutually exclusive with children. * TODO: AccordionPanel should be a sub-component */ panels: customPropTypes.every([ customPropTypes.disallow(['children']), PropTypes.arrayOf(PropTypes.shape({ key: PropTypes.string, active: PropTypes.bool, title: customPropTypes.contentShorthand, content: customPropTypes.contentShorthand, onClick: PropTypes.func, })), ]), /** Adds some basic styling to accordion panels. */ styled: PropTypes.bool, } static defaultProps = { exclusive: true, } static autoControlledProps = [ 'activeIndex', ] static _meta = { name: 'Accordion', type: META.TYPES.MODULE, } static Content = AccordionContent static Title = AccordionTitle state = {} constructor(...args) { super(...args) this.state = { activeIndex: this.props.exclusive ? -1 : [-1], } } handleTitleClick = (e, index) => { const { onTitleClick, exclusive } = this.props const { activeIndex } = this.state let newIndex if (exclusive) { newIndex = index === activeIndex ? -1 : index } else { // check to see if index is in array, and remove it, if not then add it newIndex = _.includes(activeIndex, index) ? _.without(activeIndex, index) : [...activeIndex, index] } this.trySetState({ activeIndex: newIndex }) if (onTitleClick) onTitleClick(e, index) } isIndexActive = (index) => { const { exclusive } = this.props const { activeIndex } = this.state return exclusive ? activeIndex === index : _.includes(activeIndex, index) } renderChildren = () => { const { children } = this.props let titleIndex = 0 let contentIndex = 0 return Children.map(children, (child) => { const isTitle = child.type === AccordionTitle const isContent = child.type === AccordionContent if (isTitle) { const currentIndex = titleIndex const isActive = _.has(child, 'props.active') ? child.props.active : this.isIndexActive(titleIndex) const onClick = (e) => { this.handleTitleClick(e, currentIndex) if (child.props.onClick) child.props.onClick(e, currentIndex) } titleIndex++ return cloneElement(child, { ...child.props, active: isActive, onClick }) } if (isContent) { const isActive = _.has(child, 'props.active') ? child.props.active : this.isIndexActive(contentIndex) contentIndex++ return cloneElement(child, { ...child.props, active: isActive }) } return child }) } renderPanels = () => { const { panels } = this.props const children = [] _.each(panels, (panel, i) => { const isActive = _.has(panel, 'active') ? panel.active : this.isIndexActive(i) const onClick = (e) => { this.handleTitleClick(e, i) if (panel.onClick) panel.onClick(e, i) } // implement all methods of creating a key that are supported in factories const key = panel.key || _.isFunction(panel.childKey) && panel.childKey(panel) || panel.childKey && panel.childKey || panel.title children.push(AccordionTitle.create({ active: isActive, onClick, key: `${key}-title`, content: panel.title })) children.push(AccordionContent.create({ active: isActive, key: `${key}-content`, content: panel.content })) }) return children } render() { const { className, fluid, inverted, panels, styled, } = this.props const classes = cx( 'ui', useKeyOnly(fluid, 'fluid'), useKeyOnly(inverted, 'inverted'), useKeyOnly(styled, 'styled'), 'accordion', className, ) const rest = _.omit(this.props, _.keys(Accordion.propTypes)) const ElementType = getElementType(Accordion, this.props) return ( <ElementType {...rest} className={classes}> {panels ? this.renderPanels() : this.renderChildren()} </ElementType> ) } }