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
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
- Diferencie navegação, filtro e ação.
- Ação destrutiva não executa direto pela paleta.
- Atalhos exibidos precisam funcionar.
Escfecha a paleta sem executar ação.- Command é aceleração, não superfície primária para estado crítico.
Last updated on