import React, { FC, useState } from 'react' import { css } from 'styled-components' import * as A from 'fp-ts/lib/Array' import { eqString } from 'fp-ts/lib/Eq' import * as M from 'fp-ts/lib/Map' import * as O from 'fp-ts/lib/Option' import { ordString } from 'fp-ts/lib/Ord' import { pipe } from 'fp-ts/lib/pipeable' import { getFirstSemigroup } from 'fp-ts/lib/Semigroup' import { isNotNil } from '@monorail/sharedHelpers/typeGuards' import { Collapsible, CollapsibleProps, } from '@monorail/visualComponents/collapsible/Collapsible' import { ScrollAnimation } from '@monorail/visualComponents/layout/ScrollAnimation' type AccordionContent = Array type Props = { content: AccordionContent multiExpand?: boolean isContentScrollable?: boolean } /** * Accordion * * @param content - an array of collapsible props with keys to use for the internal state * @param multiExpand? - whether or not multiple collapsible sections can be expanded at the same time or not * @param cssOverrides? - any css overrides */ export const Accordion: FC = (props: Props) => { const { content, multiExpand = false, isContentScrollable = false, ...domProps } = props /** * Keep a local map of expanded states. These will track changes to expanded so the caller * doesn't have to manage that state, unless they want to. */ const [localExpandedStates, setLocalExpandedStates] = useState< Map >(() => pipe( content, A.reduce(new Map(), (localExpandedMap, collapsible) => { const expanded = isNotNil(collapsible.expanded) ? collapsible.expanded : false return M.insertAt(eqString)(collapsible.key, expanded)(localExpandedMap) }), ), ) /** * Sets a local expanded state. This varies by whether multi-expand behavior is enabled. */ const setLocalExpandedState = (key: string, expanded: boolean) => { const newLocalExpandedStates = multiExpand ? // If it's multi-expand, just set the selected one and leave everything else alone M.insertAt(eqString)(key, expanded)(localExpandedStates) : // If it's single-expand, set the selected one and make sure everything else is collapsed pipe( localExpandedStates, M.toArray(ordString), A.map(([localKey, _]): [string, boolean] => { const newExpanded = localKey === key ? expanded : false return [localKey, newExpanded] }), M.fromFoldable(eqString, getFirstSemigroup(), A.array), ) setLocalExpandedStates(newLocalExpandedStates) } /** * Checks if an item is expanded by first checking if the caller specified via props, then falling back to the local state */ const getExpanded = ( collapsible: CollapsibleProps & { key: string }, ): boolean => pipe( // If the caller specified expanded, use that O.fromNullable(collapsible.expanded), // If caller didn't specify, check the local state O.alt(() => M.lookup(eqString)(collapsible.key, localExpandedStates)), // Default to false O.getOrElse(() => false), ) return (
{content.map(collapsible => { const { key, onClick: onClickOpt, ...rest } = collapsible return ( pipe( O.fromNullable(onClickOpt), O.fold( () => setLocalExpandedState(key, isExpanded), onClick => { onClick(event, isExpanded) setLocalExpandedState(collapsible.key, isExpanded) }, ), ) } {...rest} /> ) })}
) }