Retro Bulletin Board Systems on atproto. Web app and TUI. atbbs.xyz
python tui atproto bbs
at master 51 lines 1.4 kB view raw
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}