import { useState, useEffect, useCallback } from "react"; import { FileText, LayoutDashboard, Plus, Moon, Settings, Search, } from "lucide-react"; import { CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, } from "@/components/ui/command"; import { useAppStore } from "@/stores/app-store"; import { useBoardStore } from "@/stores/board-store"; import { searchAllBoards, type SearchResult } from "@/lib/storage"; interface CommandPaletteProps { onOpenSettings: () => void; } export function CommandPalette({ onOpenSettings }: CommandPaletteProps) { const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const [crossBoardResults, setCrossBoardResults] = useState( [] ); const boards = useAppStore((s) => s.boards); const setView = useAppStore((s) => s.setView); const setTheme = useAppStore((s) => s.setTheme); const theme = useAppStore((s) => s.settings.theme); const addRecentBoard = useAppStore((s) => s.addRecentBoard); const board = useBoardStore((s) => s.board); const openBoard = useBoardStore((s) => s.openBoard); // Listen for Ctrl+K and custom event to open useEffect(() => { function handleKeyDown(e: KeyboardEvent) { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((prev) => !prev); } } function handleOpenPalette() { setOpen(true); } document.addEventListener("keydown", handleKeyDown); document.addEventListener("open-command-palette", handleOpenPalette); return () => { document.removeEventListener("keydown", handleKeyDown); document.removeEventListener("open-command-palette", handleOpenPalette); }; }, []); // Cross-board search when query has 2+ characters useEffect(() => { if (query.length < 2) { setCrossBoardResults([]); return; } let cancelled = false; searchAllBoards(query).then((results) => { if (!cancelled) { setCrossBoardResults(results); } }); return () => { cancelled = true; }; }, [query]); // Reset state when closing useEffect(() => { if (!open) { setQuery(""); setCrossBoardResults([]); } }, [open]); const handleSelectBoard = useCallback( async (boardId: string) => { setOpen(false); await openBoard(boardId); setView({ type: "board", boardId }); addRecentBoard(boardId); }, [openBoard, setView, addRecentBoard] ); const handleSelectCard = useCallback( (cardId: string, boardId: string) => { setOpen(false); // If the card is from the current board, dispatch a custom event to open it if (board && board.id === boardId) { document.dispatchEvent( new CustomEvent("open-card-detail", { detail: { cardId } }) ); } else { // Navigate to the board first, then open the card openBoard(boardId).then(() => { setView({ type: "board", boardId }); addRecentBoard(boardId); // Small delay to allow BoardView to mount setTimeout(() => { document.dispatchEvent( new CustomEvent("open-card-detail", { detail: { cardId } }) ); }, 100); }); } }, [board, openBoard, setView, addRecentBoard] ); const handleToggleTheme = useCallback(() => { setOpen(false); const next = theme === "dark" ? "light" : "dark"; setTheme(next); }, [theme, setTheme]); const handleNewBoard = useCallback(() => { setOpen(false); // Go to board list and trigger new board dialog setView({ type: "board-list" }); setTimeout(() => { document.dispatchEvent(new CustomEvent("open-new-board-dialog")); }, 100); }, [setView]); const handleOpenSettings = useCallback(() => { setOpen(false); onOpenSettings(); }, [onOpenSettings]); // Current board cards for search const currentBoardCards = board ? Object.values(board.cards) : []; return ( No results found. {/* Current board cards */} {board && currentBoardCards.length > 0 && ( {currentBoardCards.map((card) => ( handleSelectCard(card.id, board.id)} > {card.title} ))} )} {/* Cross-board search results */} {crossBoardResults.length > 0 && ( <> {crossBoardResults .filter( (r) => !board || r.boardId !== board.id ) .slice(0, 10) .map((result) => ( handleSelectCard(result.cardId, result.boardId) } > {result.cardTitle} {result.boardTitle} ))} )} {/* Boards */} {boards.map((b) => ( handleSelectBoard(b.id)} > {b.title} {b.columnCount} cols, {b.cardCount} cards ))} {/* Actions */} New Board Toggle Dark Mode Settings ); }