wwnorton/design-system

Roving Focus Provider

sh0ji opened this issue · 1 comments

sh0ji commented

Now that we have a structure in place for exposing React context providers, I'd like to ship a roving focus provider. Wrapping a section of your code in this should cause that section to keep track of the current roving focus index, making it accessible via a hook so that you can do something like this:

const MyListbox = ({ items, onSelect, selected }) => {
	const [focusIndex, setFocusIndex] = useRovingFocus();

	return items.map((item, i) => (
		<li
			role="option"
			aria-selected={selected.includes(item)}
			tabIndex={(focusIndex === i) ? 0 : -1}
			onKeyDown={(e) => {
				if (e.key === 'Enter' && onSelect) {
					onSelect(item);
				}
			}}
			onClick={(e) => {
				e.target.focus();
				setFocusIndex(i);
			}}
			ref={(el) => {
				if (el && focusIndex === i) el.focus();
			}}
		>
			{ item }
		</li>
	));
};
sh0ji commented

This was implemented as a hook instead of a provider in #136: https://github.com/wwnorton/design-system/blob/main/packages/react/src/utilities/rovingTabindex.

While I did explore using a <RovingTabindexProvider>, all of the scenarios where it would be used also had the mapped children in context so it felt redundant and better designed with a hook.