Skip to Content

Command

Organism

Command acelera navegação e ações frequentes, mas não substitui confirmação quando há consequência operacional.

Use Command para abrir páginas, buscar casos, acionar comandos seguros e reduzir fricção para operadores experientes. A paleta deve deixar claro se o item navega, filtra, cria ou executa uma ação.

Exemplos copiáveis

React

import * as React from "react" type CommandItem = { id: string group: string label: string description?: string shortcut?: string disabled?: boolean onSelect: () => void } type PulsoCommandProps = { open: boolean items: CommandItem[] onOpenChange: (open: boolean) => void } export function PulsoCommand({ open, items, onOpenChange }: PulsoCommandProps) { const [query, setQuery] = React.useState("") const inputRef = React.useRef<HTMLInputElement>(null) const normalizedQuery = query.trim().toLowerCase() const filteredItems = items.filter((item) => { const haystack = `${item.group} ${item.label} ${item.description ?? ""}`.toLowerCase() return haystack.includes(normalizedQuery) }) const groups = Array.from(new Set(filteredItems.map((item) => item.group))) React.useEffect(() => { function onKeyDown(event: KeyboardEvent) { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { event.preventDefault() onOpenChange(!open) } if (event.key === "Escape") onOpenChange(false) } document.addEventListener("keydown", onKeyDown) return () => document.removeEventListener("keydown", onKeyDown) }, [onOpenChange, open]) React.useEffect(() => { if (open) inputRef.current?.focus() }, [open]) if (!open) return null return ( <div className="fixed inset-0 z-50 grid place-items-start bg-black/40 p-4 pt-[12vh]"> <div role="dialog" aria-modal="true" aria-label="Paleta de comandos" className="mx-auto w-full max-w-2xl overflow-hidden rounded-[var(--radius-xl)] border border-[var(--border)] bg-[var(--popover)] shadow-[var(--shadow-lg)]"> <div className="border-b border-[var(--border)] p-3"> <label htmlFor="command-search" className="sr-only">Buscar comando</label> <input ref={inputRef} id="command-search" value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Buscar caso, página ou ação segura…" className="h-11 w-full bg-transparent px-2 text-base text-[var(--fg)] outline-none placeholder:text-[var(--fg-subtle)]" /> </div> <div className="max-h-[420px] overflow-y-auto p-2" role="listbox" aria-label="Resultados"> {filteredItems.length === 0 ? <p className="px-3 py-8 text-center text-sm text-[var(--fg-muted)]">Nenhum comando encontrado.</p> : null} {groups.map((group) => ( <section key={group} className="not-first:mt-2"> <h2 className="px-3 py-2 font-mono text-[10.5px] uppercase tracking-[var(--tracking-kicker)] text-[var(--fg-subtle)]">{group}</h2> <div className="grid gap-1"> {filteredItems.filter((item) => item.group === group).map((item) => ( <button key={item.id} type="button" role="option" disabled={item.disabled} onClick={() => { item.onSelect(); onOpenChange(false) }} className="grid grid-cols-[1fr_auto] gap-3 rounded-[var(--radius-md)] px-3 py-2 text-left hover:bg-[var(--muted)] focus-visible:outline focus-visible:outline-[var(--ring-width)] focus-visible:outline-offset-2 focus-visible:outline-[var(--ring)] disabled:cursor-not-allowed disabled:opacity-50"> <span> <span className="block text-sm font-medium text-[var(--fg)]">{item.label}</span> {item.description ? <span className="mt-0.5 block text-xs text-[var(--fg-muted)]">{item.description}</span> : null} </span> {item.shortcut ? <kbd className="self-center font-mono text-[11px] text-[var(--fg-muted)]">{item.shortcut}</kbd> : null} </button> ))} </div> </section> ))} </div> </div> </div> ) }
<PulsoCommand open={open} onOpenChange={setOpen} items={[ { id: "cases", group: "Navegação", label: "Abrir casos ativos", shortcut: "G C", onSelect: () => {} }, { id: "new", group: "Ações seguras", label: "Criar monitoramento", description: "Abre formulário; não cria sem revisão humana.", onSelect: () => {} }, ]} />

Regras

  1. Diferencie navegação, filtro e ação.
  2. Ação destrutiva não executa direto pela paleta.
  3. Atalhos exibidos precisam funcionar.
  4. Esc fecha a paleta sem executar ação.
  5. Command é aceleração, não superfície primária para estado crítico.
Last updated on