Repository Public
1'use client';2import { useRouter } from 'next/navigation';3import { Command } from 'cmdk';4import { FolderGit2, Monitor, Moon, Plus, Search, Sun } from 'lucide-react';5import { useMissions, useProjects } from '@/lib/queries';6import { useUi } from '@/stores/ui';7import { Dot } from '@/components/ui/badge';8import { RUN_STATUS } from '@/lib/status';9import { NAV } from './nav';10import { useSetTheme } from './theme-toggle';1112const item = 'flex min-h-9 cursor-default select-none items-center gap-2.5 rounded-sm px-2.5 text-[13px] text-text-2 data-[selected=true]:bg-hover data-[selected=true]:text-text pointer-coarse:min-h-11';13const group = '[&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-[11px] [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wide [&_[cmdk-group-heading]]:text-muted';1415export function CommandPalette() {16 const open = useUi((s) => s.paletteOpen);17 const setUi = useUi((s) => s.set);18 const router = useRouter();19 const missions = useMissions().data ?? [];20 const projects = useProjects().data ?? [];21 const setTheme = useSetTheme();22 const run = (fn: () => void) => {23 setUi({ paletteOpen: false });24 fn();25 };26 return (27 <Command.Dialog28 open={open}29 onOpenChange={(o) => setUi({ paletteOpen: o })}30 label="Command palette"31 overlayClassName="anim-fade fixed inset-0 z-[60] bg-scrim"32 contentClassName="anim-pop fixed left-1/2 top-[12dvh] z-[60] w-[min(600px,calc(100vw-24px))] -translate-x-1/2 overflow-hidden rounded-lg border border-line-2 bg-surface shadow-pop"33 >34 <div className="flex items-center gap-2.5 border-b border-line px-3.5">35 <Search className="size-4 shrink-0 text-muted" aria-hidden />36 <Command.Input placeholder="Search missions, projects, pages…" data-testid="palette-input" className="h-12 w-full bg-transparent text-sm text-text placeholder:text-muted focus:outline-none" />37 </div>38 <Command.List className="max-h-[min(60dvh,440px)] overflow-y-auto p-1.5">39 <Command.Empty className="px-3 py-8 text-center text-xs text-muted">No results</Command.Empty>40 <Command.Group heading="Actions" className={group}>41 <Command.Item className={item} onSelect={() => run(() => useUi.getState().openNewMission())}>42 <Plus className="size-4 text-muted" /> New mission43 </Command.Item>44 </Command.Group>45 {missions.length > 0 && (46 <Command.Group heading="Missions" className={group}>47 {missions.slice(0, 30).map((m) => (48 <Command.Item key={m.id} value={`mission ${m.title} ${m.projectName ?? ''} ${m.id}`} className={item} onSelect={() => run(() => router.push(`/mission/?id=${m.id}`))}>49 <Dot tone={RUN_STATUS[m.status].tone} /> <span className="truncate">{m.title}</span>50 <span className="ml-auto shrink-0 text-[11px] text-muted">{RUN_STATUS[m.status].label}</span>51 </Command.Item>52 ))}53 </Command.Group>54 )}55 {projects.length > 0 && (56 <Command.Group heading="New mission in" className={group}>57 {projects.map((p) => (58 <Command.Item key={p.id} value={`project ${p.name} ${p.path}`} className={item} onSelect={() => run(() => useUi.getState().openNewMission(p.id))}>59 <FolderGit2 className="size-4 text-muted" /> <span className="truncate">{p.name}</span>60 <span className="ml-auto shrink-0 font-mono text-[11px] text-muted">{p.path}</span>61 </Command.Item>62 ))}63 </Command.Group>64 )}65 <Command.Group heading="Go to" className={group}>66 {NAV.map((n) => (67 <Command.Item key={n.href} value={`go to ${n.label}`} className={item} onSelect={() => run(() => router.push(n.href))}>68 <n.icon className="size-4 text-muted" /> {n.label}69 </Command.Item>70 ))}71 </Command.Group>72 <Command.Group heading="Theme" className={group}>73 <Command.Item value="theme light" className={item} onSelect={() => run(() => setTheme('light'))}><Sun className="size-4 text-muted" /> Light</Command.Item>74 <Command.Item value="theme dark" className={item} onSelect={() => run(() => setTheme('dark'))}><Moon className="size-4 text-muted" /> Dark</Command.Item>75 <Command.Item value="theme system" className={item} onSelect={() => run(() => setTheme('system'))}><Monitor className="size-4 text-muted" /> System</Command.Item>76 </Command.Group>77 </Command.List>78 </Command.Dialog>79 );80}81