Last active
June 10, 2026 18:26
-
-
Save mg901/9b54778289db3547211235abd182d290 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useId, useRef, useState, useCallback } from "react"; | |
| export default function Accordion({ sections }) { | |
| const baseId = useId(); | |
| const buttonsRef = useRef([]); | |
| const [openSections, setOpenSections] = useState(new Set()); | |
| const n = sections.length; | |
| const handleKeyDown = useCallback((e, index) => { | |
| const { key } = e; | |
| if (key === "ArrowUp") { | |
| const prevIndex = (index - 1 + n) % n; | |
| buttonsRef.current[prevIndex].focus(); | |
| e.preventDefault(); | |
| } else if (key === "ArrowDown") { | |
| const nextIndex = (index + 1) % n; | |
| buttonsRef.current[nextIndex].focus(); | |
| e.preventDefault(); | |
| } else if (key === "Home") { | |
| buttonsRef.current[0].focus(); | |
| e.preventDefault(); | |
| } else if (key === "End") { | |
| buttonsRef.current[n - 1].focus(); | |
| e.preventDefault(); | |
| } | |
| }, []); | |
| const setActive = useCallback((index) => { | |
| setOpenSections((prev) => { | |
| const next = new Set(prev); | |
| if (next.has(index)) { | |
| next.delete(index); | |
| } else { | |
| next.add(index); | |
| } | |
| return next; | |
| }); | |
| }, []); | |
| return ( | |
| <div className="accordion"> | |
| {sections.map(({ value, title, contents }, index) => { | |
| const isExpanded = openSections.has(index); | |
| const headerId = `${baseId}-header-${value}`; | |
| const panelId = `${baseId}-panel-${value}`; | |
| return ( | |
| <div className="accordion-item" key={value}> | |
| <button | |
| id={headerId} | |
| aria-controls={panelId} | |
| aria-expanded={isExpanded} | |
| className="accordion-item-title" | |
| onClick={() => setActive(index)} | |
| onKeyDown={(e) => handleKeyDown(e, index)} | |
| ref={(node) => { | |
| if (node) { | |
| buttonsRef.current[index] = node; | |
| } | |
| }} | |
| > | |
| {title}{" "} | |
| <span | |
| aria-hidden={true} | |
| data-active={isExpanded} | |
| className="accordion-icon" | |
| /> | |
| </button> | |
| <div | |
| id={panelId} | |
| aria-labelledby={headerId} | |
| role="region" | |
| hidden={!isExpanded} | |
| > | |
| {contents} | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment