# CommandPalette

> La paleta ⌘K declarativa: grupos de ítems (`{ heading, items }`) sobre `CommandDialog`, con carga diferida opcional de los datos.

```tsx
import { CommandPalette } from "sebs7n-ui/command-palette"
```

## Ejemplos

### Grupos declarativos

Un grupo por tipo de cosa, con ícono, detalle y palabras clave. Se abre con ⌘K (registrado acá, en la demo) o con el botón; elegir un ítem cierra la paleta.

```tsx
import { Button } from "sebs7n-ui/button"
import { CommandPalette, type CommandPaletteGroup } from "sebs7n-ui/command-palette"
import { FileTextIcon, HomeIcon, UsersIcon } from "lucide-react"
import { Kbd } from "sebs7n-ui/kbd"
import { useState } from "react"

function Groups() {
  const [open, setOpen] = useState(false)
  const [chosen, setChosen] = useState("nada todavía")
  useCommandShortcut(() => setOpen(true))
  const groups: CommandPaletteGroup[] = [
    {
      heading: "Secciones",
      items: [
        { value: "home", label: "Inicio", icon: <HomeIcon />, onSelect: () => setChosen("Inicio") },
        { value: "invoices", label: "Facturas", icon: <FileTextIcon />, keywords: ["cobros"], onSelect: () => setChosen("Facturas") },
        { value: "customers", label: "Clientes", icon: <UsersIcon />, onSelect: () => setChosen("Clientes") },
      ],
    },
    {
      heading: "Facturas",
      items: [
        { value: "F-0042", label: "F-0042", description: "Acme S.A. · Servicio mensual", icon: <FileTextIcon />, keywords: ["Acme"], onSelect: () => setChosen("F-0042") },
        { value: "F-0039", label: "F-0039", description: "Globex SRL · Licencias", icon: <FileTextIcon />, keywords: ["Globex"], onSelect: () => setChosen("F-0039") },
      ],
    },
  ]
  return (
    <div className="flex flex-col items-start gap-3">
      <Button onClick={() => setOpen(true)} variant="secondary">
        Buscar <Kbd size="sm">⌘K</Kbd>
      </Button>
      <p aria-live="polite" className="text-callout text-label-secondary">
        Elegiste: {chosen}
      </p>
      <CommandPalette groups={groups} onOpenChange={setOpen} open={open} placeholder="Secciones y facturas…" />
    </div>
  )
}
```

### Carga diferida

`loadGroups` trae los clientes la primera vez que se abre, no al montar la pantalla. Mientras llega dice «Cargando…».

```tsx
import { Button } from "sebs7n-ui/button"
import { CommandPalette } from "sebs7n-ui/command-palette"
import { UsersIcon } from "lucide-react"
import { useState } from "react"

function Deferred() {
  const [open, setOpen] = useState(false)
  return (
    <div className="flex flex-col items-start gap-3">
      <Button onClick={() => setOpen(true)} variant="secondary">
        Buscar un cliente
      </Button>
      <CommandPalette
        loadGroups={async () => {
          await new Promise((resolve) => setTimeout(resolve, 800))
          return [
            {
              heading: "Clientes",
              items: ["Acme S.A.", "Globex SRL", "Initech", "Umbrella Corp."].map((name) => ({ value: name, label: name, icon: <UsersIcon />, onSelect: () => {} })),
            },
          ]
        }}
        onOpenChange={setOpen}
        open={open}
        placeholder="Clientes…"
      />
    </div>
  )
}
```

## Props

### CommandPalette

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `onOpenChange` * | `(open: boolean) => void` | — | Avisa que se pidió abrirla o cerrarla. |
| `open` * | `boolean` | — | Si la paleta está abierta. |
| `closeOnSelect` | `boolean` | `true` | Cierra la paleta al elegir un ítem. Por defecto, `true`: «copiar el número» puede querer `false`. |
| `groups` | `readonly CommandPaletteGroup[]` | `[]` | Los grupos, en orden: `{ heading?, items }`. Un grupo sin ítems no se dibuja. |
| `labels` | `Partial<CommandPaletteLabels> & Partial<Labels["command"]>` | — | Textos: `loading` y `loadError`, más los de `Command` (`dialog`, `placeholder`, `empty`, `filters`) que se pasan tal cual. |
| `loadGroups` | `() => Promise<readonly CommandPaletteGroup[]>` | — | Trae más grupos la primera vez que se abre la paleta. Se suman a `groups`. |
| `placeholder` | `string` | — | El texto del campo vacío y su nombre. Por defecto, el de `Command`. |
| `shouldFilter` | `boolean` | — | Con `false` no filtra: es para cuando la app ya filtró o ordenó. Ver `Command`. |
| `className` | `string` | — | Clases del panel. |

`*` obligatoria.

## Teclado

| Tecla | Qué hace |
|---|---|
| Escribir | Filtra por título y `keywords`, sin distinguir mayúsculas ni tildes. |
| ↑ ↓ | Mueven el elegido. |
| Enter | Ejecuta el `onSelect` del elegido y cierra la paleta. |
| Escape | Cierra. |

## Accesibilidad

- Es `Command` en un diálogo: campo `combobox` y lista `listbox`, con el foco en el campo. Se nombra con `labels.dialog`.
- Mientras `loadGroups` trae los datos hay un `status` «Cargando…»; si falla, un `alert` con `labels.loadError`.

## Reglas de uso

- **No escucha el teclado**: ⌘K lo registra la app (un `keydown` en el layout) y le pasa `open` y `onOpenChange`.
- Cada ítem es `{ value, label, icon?, description?, keywords?, onSelect }`; el `value` es único en toda la paleta. Elegir cierra la paleta salvo con `closeOnSelect={false}`.
- `loadGroups` trae los grupos la primera vez que se abre (los de `groups` están desde el principio); si falla, se reintenta al abrir de nuevo.
- Para que el JS de la paleta no entre en el primer bundle, importala con `next/dynamic` o `React.lazy` y montala recién cuando se pidió por primera vez.
- Para filtros (`CommandFilters`), ítems propios o una paleta incrustada en la página, `Command`. Solo por subpath (`sebs7n-ui/command-palette`).

## Relacionados

[command](/docs/components/command.md) · [shortcuts-dialog](/docs/components/shortcuts-dialog.md) · [kbd](/docs/components/kbd.md)
