SPB Git

Repository Public

command-palette.tsx 4.6 KB 81 lines tsx

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
Files main
Menu

Theme