Skip to content

Instantly share code, notes, and snippets.

@nguyenmanh1507
Created April 26, 2023 11:30
Show Gist options
  • Save nguyenmanh1507/e1e80a53d2b5dc7bbab78983801e1942 to your computer and use it in GitHub Desktop.
Save nguyenmanh1507/e1e80a53d2b5dc7bbab78983801e1942 to your computer and use it in GitHub Desktop.
Simple accessibility React Accordion
import {
ButtonHTMLAttributes,
DetailedHTMLProps,
Dispatch,
HTMLAttributes,
SetStateAction,
createContext,
useContext,
useId,
useState,
} from 'react'
interface IAccordion {
id: string
open: boolean
toggle: Dispatch<SetStateAction<boolean>>
}
const AccordionContext = createContext<IAccordion>({
id: '',
open: false,
toggle: () => {},
})
interface AccordionProps
extends Omit<
DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>,
'children'
> {
defaultOpen?: boolean
children: React.ReactNode | ((props: { open: boolean }) => React.ReactNode)
}
export const Accordion = ({
children,
defaultOpen = false,
...rest
}: AccordionProps) => {
const [open, toggle] = useState(defaultOpen)
const id = useId()
return (
<AccordionContext.Provider value={{ id, open, toggle }}>
<section {...rest}>
{typeof children === 'function' ? children({ open }) : children}
</section>
</AccordionContext.Provider>
)
}
interface HeaderProps
extends DetailedHTMLProps<
ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> {}
const Header = ({ children, className, ...rest }: HeaderProps) => {
const { id, open, toggle } = useContext(AccordionContext)
return (
<header>
<h3>
<button
aria-expanded={open}
aria-controls={id}
onClick={() => toggle(!open)}
className={`w-full text-left ${className}`}
{...rest}
>
{children}
</button>
</h3>
</header>
)
}
interface BodyProps
extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {}
const Body = ({ children, className, ...rest }: BodyProps) => {
const { id, open } = useContext(AccordionContext)
return (
<div
id={id}
role="region"
aria-hidden={!open}
aria-labelledby={id}
tabIndex={open ? 0 : -1}
className={`${className} ${open ? 'block' : 'hidden'}`}
{...rest}
>
{children}
</div>
)
}
Accordion.Header = Header
Accordion.Body = Body
/**
* Usage
*
<Accordion>
<Accordion.Header>Header</Accordion.Header>
<Accordion.Body>Body</Accordion.Body>
</Accordion>
----------------------------
<Accordion>
{({ open }) => (
<>
<Accordion.Header>Header</Accordion.Header>
<Accordion.Body>Body</Accordion.Body>
</>
)}
</Accordion>
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment