Retro Bulletin Board Systems on atproto. Web app and TUI.
atbbs.xyz
python
tui
atproto
bbs
1/** Shared focus/blur and keyboard navigation for dropdown menus. */
2
3import { useRef, useState } from "react";
4
5export function useDropdown(
6 optionCount: number,
7 onSelect: (index: number) => void,
8) {
9 const [focused, setFocused] = useState(false);
10 const [activeIndex, setActiveIndex] = useState(-1);
11 const blurTimeout = useRef<ReturnType<typeof setTimeout>>(undefined);
12 const onSelectRef = useRef(onSelect);
13 onSelectRef.current = onSelect;
14
15 function onFocus() {
16 clearTimeout(blurTimeout.current);
17 setFocused(true);
18 }
19
20 function onBlur() {
21 blurTimeout.current = setTimeout(() => {
22 setFocused(false);
23 setActiveIndex(-1);
24 }, 150);
25 }
26
27 function onKeyDown(event: React.KeyboardEvent) {
28 if (optionCount === 0 || !focused) return;
29
30 if (event.key === "ArrowDown") {
31 event.preventDefault();
32 setActiveIndex((prev) => (prev < optionCount - 1 ? prev + 1 : 0));
33 } else if (event.key === "ArrowUp") {
34 event.preventDefault();
35 setActiveIndex((prev) => (prev > 0 ? prev - 1 : optionCount - 1));
36 } else if (event.key === "Enter" && activeIndex >= 0) {
37 event.preventDefault();
38 onSelectRef.current(activeIndex);
39 } else if (event.key === "Escape") {
40 setFocused(false);
41 setActiveIndex(-1);
42 }
43 }
44
45 function close() {
46 setFocused(false);
47 setActiveIndex(-1);
48 }
49
50 return { focused, activeIndex, onFocus, onBlur, onKeyDown, close };
51}