From 3d894bf54170b0ad0b643058118b615876250ea2 Mon Sep 17 00:00:00 2001 From: stereobooster Date: Thu, 30 May 2019 14:30:40 +0200 Subject: [PATCH] Fully accessible component --- src/components/Accordion.js | 89 ++++++++++++++++++++++++++---- src/components/AccordionSection.js | 18 +++++- 2 files changed, 96 insertions(+), 11 deletions(-) diff --git a/src/components/Accordion.js b/src/components/Accordion.js index 80b994d..a99a26b 100644 --- a/src/components/Accordion.js +++ b/src/components/Accordion.js @@ -1,6 +1,18 @@ -import React from "react"; +import React, { + useRef, + createContext, + useContext, + useState, + useMemo +} from "react"; import styles from "./Accordion.module.css"; +const AccordionContext = createContext({ + focusRef: {}, + selected: null +}); +export const useAccordionContext = () => useContext(AccordionContext); + /** * Accordion according to Accordion Design Pattern in WAI-ARIA Authoring Practices 1.1 * see https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html @@ -17,17 +29,74 @@ Shift + Tab 👍 Moves focus to the previous focusable element. 👍 All focusable elements in the accordion are included in the page Tab sequence. Down Arrow - 👎 When focus is on an accordion header, moves focus to the next accordion header. - 👎 When focus is on last accordion header, moves focus to first accordion header. + 👍 When focus is on an accordion header, moves focus to the next accordion header. + 👍 When focus is on last accordion header, moves focus to first accordion header. Up Arrow - 👎 When focus is on an accordion header, moves focus to the previous accordion header. - 👎 When focus is on first accordion header, moves focus to last accordion header. + 👍 When focus is on an accordion header, moves focus to the previous accordion header. + 👍 When focus is on first accordion header, moves focus to last accordion header. Home - 👎 When focus is on an accordion header, moves focus to the first accordion header. + 👍 When focus is on an accordion header, moves focus to the first accordion header. End - 👎 When focus is on an accordion header, moves focus to the last accordion header. + 👍 When focus is on an accordion header, moves focus to the last accordion header. ``` */ -export const Accordion = ({ children }) => ( -
{children}
-); +export const Accordion = ({ children }) => { + const focusRef = useRef(null); + const [selected, setSelected] = useState(null); + const context = useMemo( + () => ({ + focusRef, + selected + }), + [selected] + ); + + return ( +
{ + switch (e.key) { + case "ArrowDown": + { + const ids = React.Children.map(children, child => child.props.id); + const index = ids.findIndex(x => x === focusRef.current); + if (index >= ids.length - 1) { + setSelected(ids[0]); + } else { + setSelected(ids[index + 1]); + } + } + break; + case "ArrowUp": + { + const ids = React.Children.map(children, child => child.props.id); + const index = ids.findIndex(x => x === focusRef.current); + if (index <= 0) { + setSelected(ids[ids.length - 1]); + } else { + setSelected(ids[index - 1]); + } + } + break; + case "Home": + { + const ids = React.Children.map(children, child => child.props.id); + setSelected(ids[0]); + } + break; + case "End": + { + const ids = React.Children.map(children, child => child.props.id); + setSelected(ids[ids.length - 1]); + } + break; + default: + } + }} + > + + {children} + +
+ ); +}; diff --git a/src/components/AccordionSection.js b/src/components/AccordionSection.js index 1f47005..7c66ac6 100644 --- a/src/components/AccordionSection.js +++ b/src/components/AccordionSection.js @@ -1,5 +1,6 @@ -import React from "react"; +import React, { useRef, useEffect } from "react"; import styles from "./AccordionSection.module.css"; +import { useAccordionContext } from "./Accordion"; export const AccordionSection = ({ children, @@ -11,6 +12,14 @@ export const AccordionSection = ({ const sectionId = `section-${id}`; const labelId = `label-${id}`; + const { focusRef, selected } = useAccordionContext(); + const labelRef = useRef(); + useEffect(() => { + if (id === selected && labelRef.current) { + labelRef.current.focus(); + } + }, [id, selected]); + return ( <>
{ + focusRef.current = id; + }} + onBlur={() => { + focusRef.current = null; + }} + ref={labelRef} > {title} {expanded ? "▲" : "▼"}