Skip to content

Commit 12bb25a

Browse files
committed
fixup! ✨(frontend) add keyboard navigation for subdocs with focus activation
1 parent 9348806 commit 12bb25a

File tree

3 files changed

+59
-49
lines changed

3 files changed

+59
-49
lines changed

src/frontend/apps/impress/src/features/docs/doc-tree/hooks/useDropdownKeyboardNav.ts

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
// src/features/docs/doc-tree/hooks/useDropdownKeyboardNav.ts
12
import { RefObject, useEffect } from 'react';
23

3-
import { DropdownMenuOption } from '../DropdownMenu';
4+
import { DropdownMenuOption } from '@/components/DropdownMenu';
5+
6+
import { useKeyboardActivation } from './useKeyboardActivation';
47

58
type UseDropdownKeyboardNavProps = {
69
isOpen: boolean;
@@ -19,64 +22,65 @@ export const useDropdownKeyboardNav = ({
1922
setFocusedIndex,
2023
onOpenChange,
2124
}: UseDropdownKeyboardNavProps) => {
25+
useKeyboardActivation(['Enter', ' '], isOpen, () => {
26+
if (focusedIndex === -1) {
27+
return;
28+
}
29+
30+
const enabledIndices = options
31+
.map((opt, i) => (opt.show !== false && !opt.disabled ? i : -1))
32+
.filter((i) => i !== -1);
33+
34+
const selectedOpt = options[enabledIndices[focusedIndex]];
35+
if (selectedOpt?.callback) {
36+
onOpenChange(false);
37+
void selectedOpt.callback();
38+
}
39+
});
40+
2241
useEffect(() => {
2342
const handleKeyDown = (event: KeyboardEvent) => {
2443
if (!isOpen) {
2544
return;
2645
}
2746

2847
const enabledIndices = options
29-
.map((option, index) =>
30-
option.show !== false && !option.disabled ? index : -1,
31-
)
32-
.filter((index) => index !== -1);
48+
.map((opt, i) => (opt.show !== false && !opt.disabled ? i : -1))
49+
.filter((i) => i !== -1);
3350

3451
switch (event.key) {
35-
case 'ArrowDown':
52+
case 'ArrowDown': {
3653
event.preventDefault();
3754
const nextIndex =
3855
focusedIndex < enabledIndices.length - 1 ? focusedIndex + 1 : 0;
39-
const nextEnabledIndex = enabledIndices[nextIndex];
56+
const nextEnabled = enabledIndices[nextIndex];
4057
setFocusedIndex(nextIndex);
41-
menuItemRefs.current[nextEnabledIndex]?.focus();
58+
menuItemRefs.current[nextEnabled]?.focus();
4259
break;
60+
}
4361

44-
case 'ArrowUp':
62+
case 'ArrowUp': {
4563
event.preventDefault();
4664
const prevIndex =
4765
focusedIndex > 0 ? focusedIndex - 1 : enabledIndices.length - 1;
48-
const prevEnabledIndex = enabledIndices[prevIndex];
66+
const prevEnabled = enabledIndices[prevIndex];
4967
setFocusedIndex(prevIndex);
50-
menuItemRefs.current[prevEnabledIndex]?.focus();
68+
menuItemRefs.current[prevEnabled]?.focus();
5169
break;
70+
}
5271

53-
case 'Enter':
54-
case ' ':
55-
event.preventDefault();
56-
if (focusedIndex >= 0 && focusedIndex < enabledIndices.length) {
57-
const selectedOptionIndex = enabledIndices[focusedIndex];
58-
const selectedOption = options[selectedOptionIndex];
59-
if (selectedOption && selectedOption.callback) {
60-
onOpenChange(false);
61-
void selectedOption.callback();
62-
}
63-
}
64-
break;
65-
66-
case 'Escape':
72+
case 'Escape': {
6773
event.preventDefault();
6874
onOpenChange(false);
6975
break;
76+
}
7077
}
7178
};
7279

7380
if (isOpen) {
7481
document.addEventListener('keydown', handleKeyDown);
7582
}
76-
77-
return () => {
78-
document.removeEventListener('keydown', handleKeyDown);
79-
};
83+
return () => document.removeEventListener('keydown', handleKeyDown);
8084
}, [
8185
isOpen,
8286
focusedIndex,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useEffect } from 'react';
2+
3+
export const useKeyboardActivation = (
4+
keys: string[],
5+
enabled: boolean,
6+
action: () => void,
7+
capture = false,
8+
) => {
9+
useEffect(() => {
10+
if (!enabled) {
11+
return;
12+
}
13+
14+
const onKeyDown = (e: KeyboardEvent) => {
15+
if (keys.includes(e.key)) {
16+
e.preventDefault();
17+
action();
18+
}
19+
};
20+
21+
document.addEventListener('keydown', onKeyDown, capture);
22+
return () => document.removeEventListener('keydown', onKeyDown, capture);
23+
}, [keys, enabled, action, capture]);
24+
};
Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,8 @@
1-
import { useEffect } from 'react';
1+
import { useKeyboardActivation } from './useKeyboardActivation';
22

3-
/**
4-
* While the node has keyboard focus, run `activate()` on Enter / Space.
5-
* Gives tree-items the same "open on Enter" behaviour that clicks already have.
6-
*/
73
export const useTreeItemKeyboardActivate = (
84
focused: boolean,
95
activate: () => void,
106
) => {
11-
useEffect(() => {
12-
if (!focused) {
13-
return;
14-
}
15-
16-
const onKeyDown = (e: KeyboardEvent) => {
17-
if (e.key === 'Enter' || e.key === ' ') {
18-
e.preventDefault();
19-
activate();
20-
}
21-
};
22-
23-
document.addEventListener('keydown', onKeyDown, true);
24-
return () => document.removeEventListener('keydown', onKeyDown, true);
25-
}, [focused, activate]);
7+
useKeyboardActivation(['Enter', ' '], focused, activate, true);
268
};

0 commit comments

Comments
 (0)