{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command-palette",
  "type": "registry:ui",
  "title": "CommandPalette",
  "description": "کامند پالت",
  "dependencies": [
    "@kashi-ui/persian@^1.8.0"
  ],
  "registryDependencies": [
    "https://kashi.sepantapouya.com/r/combobox.json",
    "https://kashi.sepantapouya.com/r/cx.json",
    "https://kashi.sepantapouya.com/r/kashi-provider.json"
  ],
  "files": [
    {
      "path": "registry/kashi/CommandPalette.tsx",
      "type": "registry:ui",
      "target": "components/kashi/CommandPalette.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport { toFaDigits } from \"@kashi-ui/persian\";\nimport { defaultFilter, highlight, type ComboboxOption } from \"./Combobox\";\nimport { cx } from \"./cx\";\nimport { useKashi } from \"./KashiProvider\";\n\nexport interface CommandItem {\n  id: string;\n  label: string;\n  group?: string;\n  icon?: ReactNode;\n  /** میان‌بر نمایشی، مثل \"Ctrl+N\" */\n  shortcut?: string;\n  /** کلمه‌های اضافه برای جستجو */\n  keywords?: string[];\n  description?: string;\n  onSelect?: () => void;\n  /** به‌جای onSelect، رفتن به یک آدرس */\n  href?: string;\n}\n\nexport interface CommandPaletteProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  items: CommandItem[];\n  /** باز کردن با Ctrl+K یا ⌘K @default true */\n  shortcut?: boolean;\n  placeholder?: string;\n  emptyMessage?: ReactNode;\n  labels?: { title?: string; navigate?: string; select?: string; close?: string; results?: (n: number) => string };\n  className?: string;\n}\n\n/**\n * پالت فرمان: جستجوی سریع در عمل‌ها و صفحه‌ها با Ctrl+K.\n * پنجره روی <dialog> بومی است (تلهٔ فوکوس و Esc) و فهرست با الگوی combobox/listbox پیمایش می‌شود.\n */\nexport function CommandPalette({\n  open, onOpenChange, items, shortcut = true, placeholder, emptyMessage,\n  labels = {}, className,\n}: CommandPaletteProps) {\n  const { messages: M, n: N } = useKashi();\n  placeholder ??= M.command.placeholder;\n  emptyMessage ??= M.command.empty;\n  const id = useId();\n  const listId = `${id}-list`;\n  const ref = useRef<HTMLDialogElement>(null);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const opener = useRef<Element | null>(null);\n  const [query, setQuery] = useState(\"\");\n  const [active, setActive] = useState(0);\n  const { title = M.command.title, navigate = M.command.navigate, select = M.command.select, close = M.common.close, results = (x: number) => M.common.results(N(x)) } = labels;\n\n  // میان‌بر سراسری\n  useEffect(() => {\n    if (!shortcut) return;\n    const onKey = (e: globalThis.KeyboardEvent) => {\n      if ((e.ctrlKey || e.metaKey) && (e.key === \"k\" || e.key === \"K\" || e.code === \"KeyK\")) {\n        e.preventDefault();\n        onOpenChange(!open);\n      }\n    };\n    document.addEventListener(\"keydown\", onKey);\n    return () => document.removeEventListener(\"keydown\", onKey);\n  }, [shortcut, open, onOpenChange]);\n\n  useEffect(() => {\n    const d = ref.current;\n    if (!d) return;\n    if (open && !d.open) {\n      opener.current = document.activeElement;\n      setQuery(\"\");\n      setActive(0);\n      d.showModal();\n      inputRef.current?.focus();\n    } else if (!open && d.open) d.close();\n  }, [open]);\n\n  const filtered = useMemo(\n    () => items.filter((it) => defaultFilter({ value: it.id, label: it.label, description: it.description, keywords: it.keywords } as ComboboxOption, query)),\n    [items, query],\n  );\n\n  useEffect(() => {\n    document.getElementById(`${listId}-${active}`)?.scrollIntoView({ block: \"nearest\" });\n  }, [active, listId]);\n\n  const run = (it: CommandItem | undefined) => {\n    if (!it) return;\n    onOpenChange(false);\n    if (it.href) window.location.href = it.href;\n    else it.onSelect?.();\n  };\n\n  const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n    const n = filtered.length;\n    if (!n) return;\n    if (e.key === \"ArrowDown\") setActive((a) => (a + 1) % n);\n    else if (e.key === \"ArrowUp\") setActive((a) => (a - 1 + n) % n);\n    else if (e.key === \"Home\") setActive(0);\n    else if (e.key === \"End\") setActive(n - 1);\n    else if (e.key === \"Enter\") run(filtered[active]);\n    else return;\n    e.preventDefault();\n  };\n\n  let lastGroup: string | undefined;\n\n  return (\n    <dialog\n      ref={ref}\n      className={cx(\"k-dialog\", \"k-command\", className)}\n      aria-label={title}\n      onClose={() => {\n        onOpenChange(false);\n        (opener.current as HTMLElement | null)?.focus?.();\n      }}\n      onCancel={(e) => {\n        e.preventDefault();\n        onOpenChange(false);\n      }}\n      onClick={(e) => e.target === ref.current && onOpenChange(false)}\n    >\n      <div className=\"k-input\">\n        <span className=\"k-input__addon\">\n          <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.7} strokeLinecap=\"round\" aria-hidden=\"true\"><circle cx=\"9\" cy=\"9\" r=\"5.5\" /><path d=\"m13.5 13.5 3 3\" /></svg>\n        </span>\n        <input\n          ref={inputRef}\n          className=\"k-input__control\"\n          type=\"text\"\n          role=\"combobox\"\n          aria-expanded=\"true\"\n          aria-controls={listId}\n          aria-autocomplete=\"list\"\n          aria-activedescendant={filtered.length ? `${listId}-${active}` : undefined}\n          aria-label={title}\n          placeholder={placeholder}\n          autoComplete=\"off\"\n          value={query}\n          onChange={(e) => {\n            setQuery(e.target.value);\n            setActive(0);\n          }}\n          onKeyDown={onKeyDown}\n        />\n      </div>\n      <ul id={listId} role=\"listbox\" aria-label={title} className=\"k-listbox k-listbox--inline k-command__list\">\n        {filtered.length === 0 && <li role=\"presentation\" className=\"k-listbox__empty\">{emptyMessage}</li>}\n        {filtered.map((it, i) => {\n          const header = it.group && it.group !== lastGroup ? it.group : null;\n          lastGroup = it.group;\n          return [\n            header && <li key={`g-${header}`} role=\"presentation\" className=\"k-listbox__group\">{header}</li>,\n            <li\n              key={it.id}\n              id={`${listId}-${i}`}\n              role=\"option\"\n              aria-selected={i === active}\n              data-active={i === active ? \"\" : undefined}\n              className=\"k-option\"\n              onPointerDown={(e) => e.preventDefault()}\n              onPointerMove={() => active !== i && setActive(i)}\n              onClick={() => run(it)}\n            >\n              {it.icon}\n              <span className=\"k-option__body\">\n                <span>{highlight(it.label, query)}</span>\n                {it.description && <span className=\"k-option__desc\">{it.description}</span>}\n              </span>\n              {it.shortcut && <span className=\"k-menu__shortcut\" aria-hidden=\"true\">{it.shortcut}</span>}\n            </li>,\n          ];\n        })}\n      </ul>\n      <div className=\"k-command__footer\" aria-hidden=\"true\">\n        <span><kbd className=\"k-kbd\">↑</kbd><kbd className=\"k-kbd\">↓</kbd>{navigate}</span>\n        <span><kbd className=\"k-kbd\">Enter</kbd>{select}</span>\n        <span><kbd className=\"k-kbd\">Esc</kbd>{close}</span>\n      </div>\n      <span className=\"k-sr-only\" aria-live=\"polite\">{query ? results(filtered.length) : \"\"}</span>\n    </dialog>\n  );\n}\n"
    }
  ]
}