Added Aria compliance to the selectablelist.

This commit is contained in:
Colin Dawson 2026-02-16 16:20:34 +00:00
parent 9ae4b54155
commit 5188570f26

View File

@ -12,16 +12,13 @@ export const SelectableList = <T,>(
): JSX.Element => {
const { items, selectedValue, renderLabel, onSelect } = props;
// Track focus state of the list itself
const listRef = useRef<HTMLUListElement | null>(null);
const isFocusedRef = useRef(false);
// One ref per item so we can focus + scroll it
const itemRefs = useRef<(HTMLLIElement | null)[]>([]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLUListElement>) => {
// Ignore keyboard input unless the list itself is focused
if (!isFocusedRef.current) return;
if (!items.length) return;
@ -44,9 +41,8 @@ export const SelectableList = <T,>(
[items, selectedValue, onSelect],
);
// Only focus the selected item if the list itself already has focus
useEffect(() => {
if (!isFocusedRef.current) return; // Do NOT steal focus
if (!isFocusedRef.current) return;
if (!selectedValue) return;
const index = items.indexOf(selectedValue);
@ -64,6 +60,10 @@ export const SelectableList = <T,>(
ref={listRef}
className="selectable-list"
tabIndex={0}
role="listbox"
aria-activedescendant={
selectedValue ? `option-${items.indexOf(selectedValue)}` : undefined
}
onFocus={() => (isFocusedRef.current = true)}
onBlur={() => (isFocusedRef.current = false)}
onKeyDown={handleKeyDown}
@ -75,8 +75,11 @@ export const SelectableList = <T,>(
return (
<li
key={index}
id={`option-${index}`}
ref={(el) => (itemRefs.current[index] = el)}
tabIndex={isSelected ? 0 : -1}
role="option"
aria-selected={isSelected}
onClick={() => onSelect(item)}
className={className}
>