Accordion
Description for an accordion.
styled-components
import React, { useState } from 'react'; import rightArrow from '../../images/right-arrow.svg'; import styled from 'styled-components'; const StyledAccordion = styled.dl` display: flex; flex-direction: column; gap: 1.5rem; dt { display: flex; justify-content: space-between; cursor: pointer; } dt[aria-expanded='true'] img { transform: rotateZ(90deg); } .list-group { display: flex; flex-direction: column; gap: 1.5rem; border-top: 1px solid black; padding: 1.5rem 0 3.5rem 0; } img { transition: all 0.35s ease; } dd { overflow: hidden; max-height: 50rem; transition: max-height 1.5s ease-in-out; } dd[aria-expanded='true'] { max-height: 0px; transition: max-height 1s cubic-bezier(0, 1, 0, 1); } `; const Accordion = ({ accordionList }) => { const [open, setOpen] = useState(accordionList.map(accordion => false)); const [focused, setFocused] = useState(accordionList.map(accordion => false)); const toggleAccordion = idx => { setOpen({ ...open, [idx]: !open[idx] }); }; const handleKeyboardListener = (e, idx) => { if (e.key === ' ' && focused[idx]) { e.preventDefault(); let newOpen = open; newOpen[idx] = !open[idx]; setOpen(newOpen); } }; const handleFocus = (e, idx) => { let newFocused = focused; newFocused[idx] = !focused[idx]; setFocused(newFocused); }; return ( <StyledAccordion> {accordionList.map((acc, idx) => ( <React.Fragment key={idx}> <div className="list-group"> <dt onClick={() => toggleAccordion(idx)} onKeyDown={e => handleKeyboardListener(e, idx)} onFocus={e => handleFocus(e, idx)} onBlur={e => handleFocus(e, idx)} aria-expanded={open[idx]} tabIndex={'0'} role="button" > {acc.title} <img src={rightArrow} alt="collapse accordion" /> </dt> <dd aria-expanded={!open[idx]}>{acc.content}</dd> </div> </React.Fragment> ))} </StyledAccordion> ); }; export default Accordion;
TailwindCSS
import React, { useState } from 'react'; import rightArrow from '../../images/right-arrow.svg'; const Accordion = ({ accordionList }) => { const [open, setOpen] = useState(accordionList.map(accordion => false)); const [focused, setFocused] = useState(accordionList.map(accordion => false)); const toggleAccordion = idx => { setOpen(prevOpen => ({ ...prevOpen, [idx]: !prevOpen[idx] })); }; const handleKeyboardListener = (e, idx) => { if (e.key === ' ' && focused[idx]) { e.preventDefault(); setOpen(prevOpen => ({ ...prevOpen, [idx]: !prevOpen[idx] })); } }; const handleFocus = (e, idx) => { setFocused(prevFocused => ({ ...prevFocused, [idx]: !prevFocused[idx] })); }; return ( <dl className="flex flex-col gap-1.5rem"> {accordionList.map((acc, idx) => ( <React.Fragment key={idx}> <div className="list-group flex flex-col gap-1.5rem border-t border-black pt-1.5 pb-3.5"> <dt onClick={() => toggleAccordion(idx)} onKeyDown={e => handleKeyboardListener(e, idx)} onFocus={e => handleFocus(e, idx)} onBlur={e => handleFocus(e, idx)} aria-expanded={open[idx]} tabIndex={'0'} role="button" className="flex justify-between cursor-pointer" > {acc.title} <img src={rightArrow} alt="collapse accordion" className={`transition-transform duration-350 ease-transform ${ open[idx] && 'transform rotate-90' }`} /> </dt> <dd aria-expanded={!open[idx]} className="overflow-hidden max-h-50rem transition-max-height duration-1500 ease-in-out" > {acc.content} </dd> </div> </React.Fragment> ))} </dl> ); }; export default Accordion;