fixup! (frontend) add keyboard navigation for subdocs with focus activation

This commit is contained in:
Cyril
2025-08-08 14:14:44 +02:00
parent 922d05d7e9
commit 2f4cd67af0
3 changed files with 67 additions and 49 deletions

View File

@@ -1,6 +1,9 @@
// src/features/docs/doc-tree/hooks/useDropdownKeyboardNav.ts
import { RefObject, useEffect } from 'react';
import { DropdownMenuOption } from '../DropdownMenu';
import { DropdownMenuOption } from '@/components/DropdownMenu';
import { useKeyboardActivation } from './useKeyboardActivation';
type UseDropdownKeyboardNavProps = {
isOpen: boolean;
@@ -19,6 +22,22 @@ export const useDropdownKeyboardNav = ({
setFocusedIndex,
onOpenChange,
}: UseDropdownKeyboardNavProps) => {
useKeyboardActivation(['Enter', ' '], isOpen, () => {
if (focusedIndex === -1) {
return;
}
const enabledIndices = options
.map((opt, i) => (opt.show !== false && !opt.disabled ? i : -1))
.filter((i) => i !== -1);
const selectedOpt = options[enabledIndices[focusedIndex]];
if (selectedOpt?.callback) {
onOpenChange(false);
void selectedOpt.callback();
}
});
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (!isOpen) {
@@ -26,57 +45,42 @@ export const useDropdownKeyboardNav = ({
}
const enabledIndices = options
.map((option, index) =>
option.show !== false && !option.disabled ? index : -1,
)
.filter((index) => index !== -1);
.map((opt, i) => (opt.show !== false && !opt.disabled ? i : -1))
.filter((i) => i !== -1);
switch (event.key) {
case 'ArrowDown':
case 'ArrowDown': {
event.preventDefault();
const nextIndex =
focusedIndex < enabledIndices.length - 1 ? focusedIndex + 1 : 0;
const nextEnabledIndex = enabledIndices[nextIndex];
const nextEnabled = enabledIndices[nextIndex];
setFocusedIndex(nextIndex);
menuItemRefs.current[nextEnabledIndex]?.focus();
menuItemRefs.current[nextEnabled]?.focus();
break;
}
case 'ArrowUp':
case 'ArrowUp': {
event.preventDefault();
const prevIndex =
focusedIndex > 0 ? focusedIndex - 1 : enabledIndices.length - 1;
const prevEnabledIndex = enabledIndices[prevIndex];
const prevEnabled = enabledIndices[prevIndex];
setFocusedIndex(prevIndex);
menuItemRefs.current[prevEnabledIndex]?.focus();
menuItemRefs.current[prevEnabled]?.focus();
break;
}
case 'Enter':
case ' ':
event.preventDefault();
if (focusedIndex >= 0 && focusedIndex < enabledIndices.length) {
const selectedOptionIndex = enabledIndices[focusedIndex];
const selectedOption = options[selectedOptionIndex];
if (selectedOption && selectedOption.callback) {
onOpenChange(false);
void selectedOption.callback();
}
}
break;
case 'Escape':
case 'Escape': {
event.preventDefault();
onOpenChange(false);
break;
}
}
};
if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
}
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
return () => document.removeEventListener('keydown', handleKeyDown);
}, [
isOpen,
focusedIndex,

View File

@@ -0,0 +1,32 @@
import { useEffect } from 'react';
export const useKeyboardActivation = (
keys: string[],
enabled: boolean,
action: () => void,
capture = false,
) => {
useEffect(() => {
if (!enabled) {
return;
}
const onKeyDown = (e: KeyboardEvent) => {
const modal = document.querySelector('.c__modal__scroller');
if (modal) {
e.stopPropagation();
e.stopImmediatePropagation();
return;
}
if (keys.includes(e.key)) {
e.preventDefault();
action();
}
};
document.addEventListener('keydown', onKeyDown, capture);
return () => document.removeEventListener('keydown', onKeyDown, capture);
}, [keys, enabled, action, capture]);
};

View File

@@ -1,26 +1,8 @@
import { useEffect } from 'react';
import { useKeyboardActivation } from './useKeyboardActivation';
/**
* While the node has keyboard focus, run `activate()` on Enter / Space.
* Gives tree-items the same "open on Enter" behaviour that clicks already have.
*/
export const useTreeItemKeyboardActivate = (
focused: boolean,
activate: () => void,
) => {
useEffect(() => {
if (!focused) {
return;
}
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
activate();
}
};
document.addEventListener('keydown', onKeyDown, true);
return () => document.removeEventListener('keydown', onKeyDown, true);
}, [focused, activate]);
useKeyboardActivation(['Enter', ' '], focused, activate, true);
};