Skip to content

Commit

Permalink
Fully accessible component
Browse files Browse the repository at this point in the history
  • Loading branch information
stereobooster committed May 30, 2019
1 parent 8f01409 commit 3d894bf
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 11 deletions.
89 changes: 79 additions & 10 deletions src/components/Accordion.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 }) => (
<div className={styles.Accordion}>{children}</div>
);
export const Accordion = ({ children }) => {
const focusRef = useRef(null);
const [selected, setSelected] = useState(null);
const context = useMemo(
() => ({
focusRef,
selected
}),
[selected]
);

return (
<div
className={styles.Accordion}
onKeyDown={e => {
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:
}
}}
>
<AccordionContext.Provider value={context}>
{children}
</AccordionContext.Provider>
</div>
);
};
18 changes: 17 additions & 1 deletion src/components/AccordionSection.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 (
<>
<div
Expand All @@ -30,6 +39,13 @@ export const AccordionSection = ({
default:
}
}}
onFocus={() => {
focusRef.current = id;
}}
onBlur={() => {
focusRef.current = null;
}}
ref={labelRef}
>
{title}
<span aria-hidden={true}>{expanded ? "▲" : "▼"}</span>
Expand Down

0 comments on commit 3d894bf

Please sign in to comment.