"use client"; import { useEffect, useId, useRef, useState } from "react"; import { Check, ChevronDown } from "lucide-react"; import type { Board } from "@/lib/api"; type BoardPickerProps = { boards: Board[]; value: number; onChange: (id: number) => void; disabled?: boolean; loading?: boolean; /** 提交校验失败后再为 true,用于红色错误态 */ invalid?: boolean; id?: string; }; function boardColorIndex(b: Board, fallbackIndex: number) { return Math.max(0, b.color_index ?? fallbackIndex) % 8; } export default function BoardPicker({ boards, value, onChange, disabled, loading, invalid = false, id, }: BoardPickerProps) { const listId = useId(); const rootRef = useRef(null); const [open, setOpen] = useState(false); const [activeIndex, setActiveIndex] = useState(0); const selectedIdx = boards.findIndex((b) => b.id === value); const selected = selectedIdx >= 0 ? boards[selectedIdx] : null; useEffect(() => { if (!open) return; setActiveIndex(selectedIdx >= 0 ? selectedIdx : 0); }, [open, selectedIdx]); useEffect(() => { if (!open) return; const onDoc = (e: MouseEvent) => { if (!rootRef.current?.contains(e.target as Node)) setOpen(false); }; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") { e.preventDefault(); setOpen(false); } }; document.addEventListener("mousedown", onDoc); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); }; }, [open]); const pick = (id: number) => { onChange(id); setOpen(false); }; const onTriggerKeyDown = (e: React.KeyboardEvent) => { if (disabled || loading) return; if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") { e.preventDefault(); setOpen(true); } }; const onMenuKeyDown = (e: React.KeyboardEvent) => { if (!boards.length) return; if (e.key === "ArrowDown") { e.preventDefault(); setActiveIndex((i) => (i + 1) % boards.length); } else if (e.key === "ArrowUp") { e.preventDefault(); setActiveIndex((i) => (i - 1 + boards.length) % boards.length); } else if (e.key === "Enter" || e.key === " ") { e.preventDefault(); const b = boards[activeIndex]; if (b) pick(b.id); } else if (e.key === "Home") { e.preventDefault(); setActiveIndex(0); } else if (e.key === "End") { e.preventDefault(); setActiveIndex(boards.length - 1); } }; const empty = !loading && !selected; const showError = empty && invalid; const label = loading ? "加载板块…" : selected ? selected.name : boards.length === 0 ? "暂无可用板块" : "请选择板块"; const colorIdx = selected ? boardColorIndex(selected, selectedIdx) : 0; return (
{open && ( )}
); } /** 编辑态只读板块徽章(与触发器同高) */ export function BoardBadge({ name, colorIndex = 0, }: { name: string; colorIndex?: number; }) { const c = Math.max(0, colorIndex) % 8; return (
{name.slice(0, 1)} {name}
); }