Skip to content

Latest commit

 

History

History

Accordion

Accordion

Make parts of the UI collapsible using this component.

Basic Usage

import { AccordionSet, Accordion } from '@folio/stripes/components';

...
<AccordionSet>
  <Accordion label="Example Accordion" id="ex-1">
    <p>Accordion content!</p>
  </Accordion>
  <Accordion label="Acc #2" id="ex-2">
    <p>Accordion content!</p>
  </Accordion>
</AccordionSet>

label is the only required prop. label represents the visual heading for the modal - it can be a string, an html tag or a component.

AccordionSets

The <AccordionSet> sets up keyboard navigation and conveniently controls state of its wrapped collection of accordions if no accordionStatus prop is provided. <Accordion>s don't have to be direct children of an <AccordionSet> since they register themselves via context passed down from the set. Component hierarchies like this will work fine:

<AccordionSet>
  <IfInterface>
    <AccountAccordion /> // <Accordion> is rendered within.
  </IfInterface>
</AccordionSet>

Keyboard Navigation

Keyboard support comes packaged with use of the <AccordionSet>. The keys are active when an accordion header is in focus. Bindings are as follows:

key function
up Navigate to the previous accordion header. If the first accordion header is focused, the focus will jump to the last accordion.
down Navigate to the next accordion header. If the last accordion header is focused, the focus will jump to the first accordion.
home Navigate to the first accordion in the set.
end Navigate to the last accordion in the set.

AccordionStatus

The <AccordionStatus> component can be used as a high-level parent for an uncontrolled setup to relay status and toggle functionality to <AccordionSet> children.

<AccordionStatus>
  <ExpandAllButton />
  <AccordionSet>
    <Accordion label="AccordionStatus_one">
      <p>first content</p>
    </Accordion>
    <Accordion label="AccordionStatus_two">
      <p>second content</p>
    </Accordion>
  </AccordionSet>
</AccordionStatus>

Initializing AccordionStatus

The <AccordionStatus> can be given an intial state via the initialStatus prop of <AccordionSet>. Like accordionStatus props, keys of the initialStatus object should match the id's of the contained <Accordions>.

const initial = {
  first: true,
  second: false,
  third: false
};

<AccordionStatus>
  <ExpandAllButton />
  <AccordionSet initialStatus={initial}>
    <Accordion id="first" label="first Accordion">
    ...

onRegisterAccordion and onUnregisterAccordion props

The onRegisterAccordion and onUnregisterAccordion props are called when an <Accordion> is registered and unregistered. This is useful when accordions can be mounted or unmounted and you want to notify a consumer of <AccordionStatus> when that has happened. The props are called with a single argument - accordion's id.

Open render-prop

Accordions can pass a their open status to their children via a functional child:

<Accordion label="Lazy content">
  { open => (
    <List contentData={open ? data : []} />
  )}
</Accordion>

onClickToggle prop

This property is a callback function that is called when a user has opened accordion by interacting with it (clicking on header, pressing Enter). It's not called when accordion was toggled via "Expand all" button or any other way. Callback is called with the same argument as onToggle with one additional property - open

{
  id: 'accordion-id',
  label: 'accordion-label',
  open: true,
}

Controlled

Accordions can, of course, be controlled by state or local resource. Simply include an object with a list of keys for each accordion's id set to a boolean value that will be passed through to the corresponding accordion's open prop. This object should be passed to the <AccordionSet>'s accordionStatus prop. An onToggle handler will also need to be provided for proper state interaction. Passed to the <AccordionSet>'s onToggle prop, it will receive both the label and id of the target accordion, either of which could be used for additional interactions as needed.

import { AccordionSet, Accordion } from '@folio/stripes/components';

... // state or manifest/local resource.

this.state = {
  accordions: {
    'ex-1': true,
    'ex-2': false,
  }
}

... // Toggle handler

onToggleSection({label, id}) {
  this.setState((curState) =>{
    let newState = _.cloneDeep(curState); // remember to safely copy state! using lodash's cloneDeep() for example.
    newState.detailAccordions[id] = !curState.detailAccordions[id];
    return newState
  });
}

... // include in the jsx:

<AccordionSet accordionStatus={this.state.accordions} onToggle={this.onToggleSection}>
  <Accordion label="Example Accordion" id="ex-1">
    <p>Accordion content!</p>
  </Accordion>
  <Accordion label="Acc #2" id="ex-2">
    <p>Accordion content!</p>
  </Accordion>
</AccordionSet>

Rendering Summary Items and Actions in the Header.

<Accordion> provides two additional props: displayWhenOpen and displayWhenClosed that are used to place content in the accordion header at various states. An example of this would be summary information rendered in the displayWhenClosed prop, and <Button>'s rendered in the displayWhenOpen prop.

<AccordionSet>
  <Accordion
    label="Example Accordion"
    id="ex-1"
    displayWhenClosed={<em>3 items</em>}
    displayWhenOpen={<Button>Add item</Button>}
  >
    <ul>
      <li>All</li>
      <li>The</li>
      <li>Items!</li>
    </ul>
  </Accordion>
</AccordionSet>

Header

The <Accordion> comes with a default header component out of the box. The default header suits many cases, but if it is not adequate, a custom header can be provided via <Accordion>'s header prop.

A custom header component should take ContentId and labelId props in order to appropriately apply aria-attributes to the custom header. Any props passed to <Accordion> will also be passed to its header component.

You can pass down additional props for the header component by using the headerProps-prop. This can be useful for e.g. changing the header level of the default accordion header via. the headingLevel-prop (defaults to 3).

Example custom header:

const header = ({
    contentId,
    toggleRef,
    open,
    onToggle,
    label,
    labelId,
  }) => (
      <p style={{ border: '1px solid #ddd', padding: '0 4px' }}>
      <button ref={toggleRef} onClick={onToggle} aria-owns={contentId} id={labelId}>{`${label} Header Content (click to ${open ? 'close':'expand'})`}</button>
      </p>
    );

<Accordion header={header} label="Example">
Content
</Accordion>

Accordion Props

Name type description default required
label string, element Visible header label true
open bool Open or closed true
closedByDefault bool Use to collapse the Accordion by default. Ensure that you use it only when the Accordion is not being controlled by an open prop false
id string Unique ID to track accordion state
displayWhenOpen element Content to display in header when Accordion is in the open state
displayWhenClosed element Content to display in header when Accordion is in the closed state
onToggle func Callback for toggling the accordion open/closed
onClickToggle func Callback for letting the component consumer know when accordion has been toggled by clicking on header
header node, func Used to render a custom accordion header
headerProps object Passes additional props for the header component of the accordion
contentRef func Reference function for accessing the accordion content's DOM element.
children node, array of nodes Content of the accordion
autoFocus bool If this prop is true, Accordion toggle/label will automatically focus on mount

Expand or Collapse All

The <ExpandAllButton> component can be added to a stack of controlled accordions to provide a helper for performing collapse/expansions of the full stack all at once. The onToggle prop for <ExpandAllButton> takes a callback using the accordionStatus object that can be used to update the application's state.

// handler for ExpandAll...
  handleExpandAll(newAccordionStatus) {
    this.setState((curState) => {
      const newState = _.cloneDeep(curState);
      newState.accordions = newAccordionStatus; // substitute 'accordions' with your designated status object's name.
      return newState;
    });
  }

// in JSX
  <ExpandAllButton accordionStatus={this.state.accordions} onToggle={this.handleExpandAll} />