Skip to content

Instantly share code, notes, and snippets.

@stevenpetryk
Last active July 29, 2019 06:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevenpetryk/67a2f7191bef5e424b9f07c06fd07f1b to your computer and use it in GitHub Desktop.
Save stevenpetryk/67a2f7191bef5e424b9f07c06fd07f1b to your computer and use it in GitHub Desktop.
export default function FocusGroup() {
const items = [1, 2, 3, 4, 5]
const [selectedIndex, setSelectedIndex] = useState(0)
const {
containerProps,
itemProps,
} = useFocusGroup<HTMLButtonElement>(items, index => selectedIndex === index)
return (
<ul {...containerProps}>
{items.map((item, index) => (
<li key={index}>
<button onClick={() => setSelectedIndex(index)} {...itemProps(index)}>
Button #{item}
</button>
</li>
))}
</ul>
)
}
function useFocusGroup<E extends HTMLElement>(
list: unknown[],
isSelected: (index: number) => boolean,
) {
const [focusedIndex, setFocusedIndex] = useState<number>(null)
const refList = useMemo(() => list.map(() => React.createRef<E>()), [list])
function tabIndex(index: number) {
return isSelected(index) ? 0 : -1
}
function addRef(index: number) {
return refList[index]!
}
function onContainerKeyDown(event: React.KeyboardEvent<any>) {
if (focusedIndex === null) return
if (event.key === 'ArrowUp') {
setFocusedIndex(Math.max(focusedIndex - 1, 0))
}
if (event.key === 'ArrowDown') {
setFocusedIndex(Math.min(focusedIndex + 1, list.length))
}
}
function onContainerFocus() {
if (focusedIndex === null) {
setFocusedIndex(0)
}
}
function activeElementIsOneOfOurs() {
return refList.map<any>(ref => ref.current).includes(document.activeElement)
}
useEffect(() => {
if (!activeElementIsOneOfOurs()) return
if (focusedIndex === null || !refList[focusedIndex]) return
const currentRef = refList[focusedIndex].current
if (!currentRef) return
currentRef.focus()
}, [refList, focusedIndex])
return {
containerProps: { onKeyDown: onContainerKeyDown, onFocus: onContainerFocus },
itemProps: (index: number) => ({ tabIndex: tabIndex(index), ref: addRef(index) }),
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment