|
| 1 | +import { useEffect, useCallback } from "react"; |
| 2 | +import { Label } from "@lib/types"; |
| 3 | + |
| 4 | +export function useLabelKeyboardNavigation( |
| 5 | + labels: Label[], |
| 6 | + selectedLabelId: string | null, |
| 7 | + onSelectLabel: (id: string) => void, |
| 8 | +) { |
| 9 | + const currentIndex = labels.findIndex((l) => l.id === selectedLabelId); |
| 10 | + |
| 11 | + const selectPrev = useCallback(() => { |
| 12 | + if (labels.length === 0) return; |
| 13 | + |
| 14 | + const nextIndex = currentIndex <= 0 ? labels.length - 1 : currentIndex - 1; |
| 15 | + |
| 16 | + onSelectLabel(labels[nextIndex].id); |
| 17 | + }, [labels, currentIndex, onSelectLabel]); |
| 18 | + |
| 19 | + const selectNext = useCallback(() => { |
| 20 | + if (labels.length === 0) return; |
| 21 | + |
| 22 | + const nextIndex = currentIndex >= labels.length - 1 ? 0 : currentIndex + 1; |
| 23 | + |
| 24 | + onSelectLabel(labels[nextIndex].id); |
| 25 | + }, [labels, currentIndex, onSelectLabel]); |
| 26 | + |
| 27 | + useEffect(() => { |
| 28 | + const handleKey = (e: KeyboardEvent) => { |
| 29 | + if (e.repeat) return; |
| 30 | + // block when typing in input/text areas |
| 31 | + const active = document.activeElement as HTMLElement | null; |
| 32 | + if ( |
| 33 | + active && |
| 34 | + (["INPUT", "TEXTAREA", "SELECT"].includes(active.tagName) || |
| 35 | + active.isContentEditable) |
| 36 | + ) { |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | + if (e.key === "ArrowUp") { |
| 41 | + e.preventDefault(); |
| 42 | + selectPrev(); |
| 43 | + } else if (e.key === "ArrowDown") { |
| 44 | + e.preventDefault(); |
| 45 | + selectNext(); |
| 46 | + } |
| 47 | + }; |
| 48 | + |
| 49 | + window.addEventListener("keydown", handleKey); |
| 50 | + return () => window.removeEventListener("keydown", handleKey); |
| 51 | + }, [selectPrev, selectNext]); |
| 52 | +} |
0 commit comments