{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "command-palette",
  "title": "Command Palette",
  "type": "registry:block",
  "description": "Header-grade search/command palette. Bundles the slim trigger button (with platform-aware ⌘K/Ctrl-K kbd hint) AND the modal CommandDialog into one block; consumers drop it once and the global keyboard shortcut wires itself. Takes a `groups` array of `{ heading, items: [{ label, hint, icon, onSelect }] }` and calls `onSelect`. `showTrigger={false}` hides the inline button so the consumer can control open state from elsewhere.",
  "categories": [
    "layout",
    "dashboard",
    "overlay",
    "navigation"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/command-palette/command-palette.ts",
      "content": "import { LitElement, css, html, isServer, nothing } from 'lit'\nimport { FileText, Inbox, LayoutDashboard, Search, Settings, SquareKanban, Users, type IconNode } from 'lucide'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect import: registers <uip-command-dialog> for standalone use\n// (consumers who install only this block still get a working dialog).\nimport '../../components/command/command'\n\nexport interface CommandPaletteItem {\n  label: string\n  hint?: string\n  icon?: IconNode\n  iconName?: string\n  onSelect?: () => void\n}\n\nexport interface CommandPaletteGroup {\n  heading: string\n  items: CommandPaletteItem[]\n}\n\nconst defaultGroups: CommandPaletteGroup[] = [\n  {\n    heading: 'Navigate',\n    items: [\n      { label: 'Dashboard', hint: '/dashboard', icon: LayoutDashboard, iconName: 'layout-dashboard' },\n      { label: 'Inbox', hint: '/inbox', icon: Inbox, iconName: 'inbox' },\n      { label: 'Kanban', hint: '/kanban', icon: SquareKanban, iconName: 'square-kanban' },\n      { label: 'Team', hint: '/team', icon: Users, iconName: 'users' },\n    ],\n  },\n  {\n    heading: 'Settings',\n    items: [\n      { label: 'Profile', hint: '/profile', icon: FileText, iconName: 'file-text' },\n      { label: 'Settings', hint: '/settings', icon: Settings, iconName: 'settings' },\n    ],\n  },\n]\n\n/**\n * <uip-command-palette> — the registry CommandPalette block as a web component.\n *\n * Class strings are React's CommandPalette verbatim. React state (open,\n * platform shortcut) becomes @state fields; the ⌘K / Ctrl-K global shortcut\n * is wired in connectedCallback. `onSelect` becomes a `select` CustomEvent\n * (bubbles, composed, detail { label, hint }); a per-item `onSelect` in a\n * programmatically-set `groups` array is still called. `groups` is a property\n * (no attribute — structured data).\n *\n * Deviation: React's `KanbanSquare` icon is the `SquareKanban` IconNode here\n * (the installed `lucide` package only exports `SquareKanban`; same glyph).\n */\nexport class UipCommandPalette extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    groups: { attribute: false },\n    placeholder: {},\n    triggerLabel: { attribute: 'trigger-label' },\n    showTrigger: {\n      type: Boolean,\n      attribute: 'show-trigger',\n      converter: { fromAttribute: (v: string | null) => v !== null && v !== 'false' },\n    },\n    open: { state: true },\n    triggerShortcut: { state: true },\n  }\n\n  groups: CommandPaletteGroup[] = defaultGroups\n  placeholder = 'Search pages, commands…'\n  triggerLabel = 'Search pages, commands…'\n  showTrigger = true\n  private open = false\n  private triggerShortcut = '⌘K'\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'command-palette')\n    if (!isServer && typeof navigator !== 'undefined') {\n      this.triggerShortcut = /Mac|iPhone|iPad/i.test(navigator.platform) ? '⌘K' : 'Ctrl K'\n      window.addEventListener('keydown', this.onKeydown)\n    }\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    if (!isServer && typeof window !== 'undefined') window.removeEventListener('keydown', this.onKeydown)\n  }\n\n  private onKeydown = (e: KeyboardEvent) => {\n    if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {\n      e.preventDefault()\n      this.open = !this.open\n    }\n  }\n\n  private pick(item: CommandPaletteItem) {\n    this.open = false\n    item.onSelect?.()\n    this.dispatchEvent(\n      new CustomEvent('select', { detail: { label: item.label, hint: item.hint }, bubbles: true, composed: true }),\n    )\n  }\n\n  render() {\n    return html`\n      ${this.showTrigger\n        ? html`<button\n            type=\"button\"\n            class=\"bg-secondary/50 hover:bg-secondary text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 relative hidden h-8 w-full items-center gap-2 rounded-lg border border-transparent px-2.5 text-sm shadow-none transition-colors outline-none focus-visible:ring-[3px] sm:flex md:w-[220px] lg:w-[300px]\"\n            aria-label=\"Open command palette\"\n            @click=${() => {\n              this.open = true\n            }}\n          >\n            ${icon(Search, 'search', 'size-3.5 shrink-0')}\n            <span class=\"flex-1 truncate text-left\">${this.triggerLabel}</span>\n            <kbd\n              class=\"bg-muted/80 text-muted-foreground pointer-events-none flex h-5 items-center justify-center rounded-md border px-1.5 font-mono text-xs font-medium\"\n            >\n              <span>${this.triggerShortcut}</span>\n            </kbd>\n          </button>`\n        : nothing}\n\n      <uip-command-dialog\n        ?open=${this.open}\n        placeholder=${this.placeholder}\n        heading=\"Command palette\"\n        description=\"Search pages and run commands\"\n        @open-change=${(e: CustomEvent<{ open: boolean }>) => {\n          this.open = e.detail.open\n        }}\n      >\n        <uip-command-empty>No matches.</uip-command-empty>\n        ${this.groups.map(\n          (group, gi) => html`\n            <uip-command-group heading=${group.heading}>\n              ${group.items.map(\n                (item) => html`\n                  <uip-command-item\n                    data-slot=\"command-palette\"\n                    value=${`${group.heading} ${item.label} ${item.hint ?? ''}`}\n                    @select=${() => this.pick(item)}\n                  >\n                    ${item.icon ? icon(item.icon, item.iconName ?? 'icon', 'size-4') : nothing}\n                    <span>${item.label}</span>\n                    ${item.hint ? html`<uip-command-shortcut>${item.hint}</uip-command-shortcut>` : nothing}\n                  </uip-command-item>\n                `,\n              )}\n            </uip-command-group>\n            ${gi < this.groups.length - 1 ? html`<uip-command-separator></uip-command-separator>` : nothing}\n          `,\n        )}\n      </uip-command-dialog>\n    `\n  }\n}\n\ncustomElements.get('uip-command-palette') || customElements.define('uip-command-palette', UipCommandPalette)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-command-palette': UipCommandPalette\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/command-palette/command-palette.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/command.json",
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
