Ir al contenido
Superposiciones

CommandPalette

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

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.

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…».

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

Generadas del TypeScript del paquete. Las propias del componente, más las heredadas del primitivo que tienen algo que explicar —marcadas «heredada de Base UI»—. El resto está en la línea «hereda de».

CommandPalette

PropTipoPor defectoDescripción
onOpenChange*(open: boolean) => void—Avisa que se pidió abrirla o cerrarla.
open*boolean—Si la paleta está abierta.
closeOnSelectbooleantrueCierra la paleta al elegir un ítem. Por defecto, true: «copiar el número» puede querer false.
groupsreadonly CommandPaletteGroup[][]Los grupos, en orden: { heading?, items }. Un grupo sin ítems no se dibuja.
labelsPartial<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.
placeholderstring—El texto del campo vacío y su nombre. Por defecto, el de Command.
shouldFilterboolean—Con false no filtra: es para cuando la app ya filtró o ordenó. Ver Command.
classNamestring—Clases del panel.

Teclado

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