{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command-dialog",
  "title": "Dialog command",
  "description": "Styled command: the Dialog set. 5 decorative command variations (ModalCommand, OverlayCommand, CenterCommand, TopCommand, FullscreenCommand) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/command-dialog.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport {\n  Calculator,\n  Calendar,\n  CreditCard,\n  FileText,\n  Search,\n  Settings,\n  Smile,\n  User,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Dialog command family: 5 SELF-CONTAINED command palettes, all inside a modal\n   layer (the Cmd+K feel). NO cmdk, NO radix, NO portal - like dialog-styled it\n   builds its own fixed backdrop + panel. Each export renders a trigger button;\n   clicking it opens the palette, focus goes to the input, Escape and a backdrop\n   click close it, and clicking the panel does not (stopPropagation). The palette\n   is mounted only while open, so every opening starts with a clean query.\n   Color comes ONLY from tokens, via alpha color-mix. No transform under reduced\n   motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst paletteWidth: Record<StyledSize, string> = {\n  sm: \"max-w-sm\",\n  md: \"max-w-md\",\n  lg: \"max-w-lg\",\n  xl: \"max-w-2xl\",\n}\n\nexport interface CommandItem {\n  label: React.ReactNode\n  group?: string\n  icon?: React.ReactNode\n  onSelect?: () => void\n}\n\ninterface CommandDialogProps {\n  className?: string\n  size?: StyledSize\n  placeholder?: string\n  items?: CommandItem[]\n  trigger?: React.ReactNode\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (o: boolean) => void\n}\n\n/* The label can be a ReactNode; flatten it to plain text for filtering. */\nfunction nodeText(node: React.ReactNode): string {\n  if (node === null || node === undefined || typeof node === \"boolean\") return \"\"\n  if (typeof node === \"string\" || typeof node === \"number\") return String(node)\n  if (Array.isArray(node)) return node.map(nodeText).join(\"\")\n  if (React.isValidElement(node)) {\n    return nodeText((node.props as { children?: React.ReactNode }).children)\n  }\n  return \"\"\n}\n\n/* Varsayilan liste: prop'suz render eder. */\nconst defaultItems: CommandItem[] = [\n  { label: \"Calendar\", group: \"Suggestions\", icon: <Calendar /> },\n  { label: \"Search Emoji\", group: \"Suggestions\", icon: <Smile /> },\n  { label: \"Calculator\", group: \"Suggestions\", icon: <Calculator /> },\n  { label: \"Profile\", group: \"Settings\", icon: <User /> },\n  { label: \"Billing\", group: \"Settings\", icon: <CreditCard /> },\n  { label: \"Settings\", group: \"Settings\", icon: <Settings /> },\n  { label: \"New Document\", group: \"Actions\", icon: <FileText /> },\n]\n\n/* The shared state + the combobox/listbox ids. Escape does NOT clear the query here:\n   the event bubbles up to the window and the dialog closes. */\nfunction usePalette(source: CommandItem[]) {\n  const uid = React.useId()\n  const [query, setQuery] = React.useState(\"\")\n  const [active, setActive] = React.useState(0)\n\n  const filtered = React.useMemo(() => {\n    const q = query.trim().toLowerCase()\n    if (q.length === 0) return source\n    return source.filter((item) => nodeText(item.label).toLowerCase().includes(q))\n  }, [query, source])\n\n  React.useEffect(() => {\n    setActive((prev) => (prev >= filtered.length ? 0 : prev))\n  }, [filtered.length])\n\n  const select = React.useCallback(\n    (index: number) => {\n      const item = filtered[index]\n      if (item) item.onSelect?.()\n    },\n    [filtered]\n  )\n\n  const onKeyDown = React.useCallback(\n    (e: React.KeyboardEvent<HTMLInputElement>) => {\n      if (e.key === \"ArrowDown\") {\n        e.preventDefault()\n        setActive((prev) => (filtered.length === 0 ? 0 : (prev + 1) % filtered.length))\n      } else if (e.key === \"ArrowUp\") {\n        e.preventDefault()\n        setActive((prev) =>\n          filtered.length === 0 ? 0 : (prev - 1 + filtered.length) % filtered.length\n        )\n      } else if (e.key === \"Enter\") {\n        e.preventDefault()\n        select(active)\n      }\n    },\n    [filtered.length, active, select]\n  )\n\n  const listId = `${uid}-list`\n  const optionId = React.useCallback((i: number) => `${uid}-option-${i}`, [uid])\n\n  return { query, setQuery, active, setActive, filtered, select, onKeyDown, listId, optionId }\n}\n\nconst inputRowBase = \"flex items-center gap-2 border-b border-border px-3\"\n\nconst inputBase =\n  \"h-11 w-full rounded-md bg-transparent py-3 text-sm text-popover-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nconst optionBase =\n  \"flex cursor-pointer select-none items-center gap-2 rounded-lg px-2.5 py-2 text-sm outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground [&_i]:text-base [&_i]:leading-none [&_i]:text-muted-foreground\"\n\nconst emptyBase = \"py-6 text-center text-sm text-muted-foreground\"\n\nconst triggerBtn =\n  \"inline-flex h-9 shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border border-border bg-secondary px-4 text-sm font-medium text-secondary-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst backdropBase = \"fixed inset-0 z-50 flex p-4\"\n\n/* Palette body: focuses the input when it opens. */\nfunction PaletteBody({\n  items,\n  placeholder,\n  listClassName,\n}: {\n  items: CommandItem[]\n  placeholder: string\n  listClassName?: string\n}) {\n  const p = usePalette(items)\n  const reduce = useReducedMotion() ?? false\n  const inputRef = React.useRef<HTMLInputElement>(null)\n\n  React.useEffect(() => {\n    const id = window.requestAnimationFrame(() => inputRef.current?.focus())\n    return () => window.cancelAnimationFrame(id)\n  }, [])\n\n  return (\n    <>\n      <div className={inputRowBase}>\n        <Search className=\"size-4 shrink-0 text-muted-foreground\" />\n        <input\n          ref={inputRef}\n          role=\"combobox\"\n          aria-expanded={true}\n          aria-controls={p.listId}\n          aria-autocomplete=\"list\"\n          aria-activedescendant={p.filtered.length > 0 ? p.optionId(p.active) : undefined}\n          value={p.query}\n          placeholder={placeholder}\n          onChange={(e) => p.setQuery(e.target.value)}\n          onKeyDown={p.onKeyDown}\n          className={inputBase}\n        />\n      </div>\n      <div\n        id={p.listId}\n        role=\"listbox\"\n        aria-label=\"Command palette\"\n        className={cn(\"max-h-72 overflow-y-auto p-1.5\", listClassName)}\n      >\n        {p.filtered.length === 0 ? (\n          <p className={emptyBase}>No results found.</p>\n        ) : (\n          p.filtered.map((item, i) => (\n            <motion.div\n              key={nodeText(item.label) || String(i)}\n              id={p.optionId(i)}\n              role=\"option\"\n              aria-selected={i === p.active}\n              tabIndex={-1}\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              transition={reduce ? { duration: 0 } : { duration: 0.15, ease: \"easeOut\" }}\n              onMouseEnter={() => p.setActive(i)}\n              onClick={() => p.select(i)}\n              className={cn(optionBase, i === p.active && \"bg-accent text-accent-foreground\")}\n            >\n              {item.icon}\n              <span className=\"flex-1 truncate\">{item.label}</span>\n            </motion.div>\n          ))\n        )}\n      </div>\n    </>\n  )\n}\n\n/* Controlled/uncontrolled acik durum yonetimi. */\nfunction useDialogState(props: CommandDialogProps) {\n  const { open, defaultOpen, onOpenChange } = props\n  const isControlled = open !== undefined\n  const [internal, setInternal] = React.useState(defaultOpen ?? false)\n  const isOpen = isControlled ? open : internal\n\n  const setOpen = React.useCallback(\n    (next: boolean) => {\n      if (!isControlled) setInternal(next)\n      onOpenChange?.(next)\n    },\n    [isControlled, onOpenChange]\n  )\n\n  return { isOpen, setOpen }\n}\n\n/* Shared shell: trigger plus backdrop plus panel. The variants differ only in the backdrop class, the alignment and the panel motion. */\nfunction CommandDialogShell({\n  props,\n  backdropClassName,\n  alignClassName,\n  panelClassName,\n  panelMotion,\n  fullWidth,\n}: {\n  props: CommandDialogProps\n  backdropClassName: string\n  alignClassName: string\n  panelClassName?: string\n  panelMotion: {\n    initial: Record<string, number>\n    animate: Record<string, number>\n    exit: Record<string, number>\n  }\n  fullWidth?: boolean\n}) {\n  const {\n    className,\n    size = \"md\",\n    placeholder = \"Type a command...\",\n    items = defaultItems,\n    trigger,\n  } = props\n  const { isOpen, setOpen } = useDialogState(props)\n  const reduce = useReducedMotion() ?? false\n\n  React.useEffect(() => {\n    if (!isOpen) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") setOpen(false)\n    }\n    window.addEventListener(\"keydown\", onKey)\n    return () => window.removeEventListener(\"keydown\", onKey)\n  }, [isOpen, setOpen])\n\n  const fade = { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n  const m = reduce ? fade : panelMotion\n\n  return (\n    <>\n      <span\n        data-slot=\"styled-command-trigger\"\n        className=\"inline-flex\"\n        onClick={() => setOpen(true)}\n      >\n        {trigger ?? (\n          <button type=\"button\" className={triggerBtn}>\n            <Search />\n            Open command palette\n          </button>\n        )}\n      </span>\n\n      <AnimatePresence>\n        {isOpen ? (\n          <motion.div\n            className={cn(backdropBase, alignClassName, backdropClassName)}\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: 0.2, ease: \"easeOut\" }}\n            onClick={() => setOpen(false)}\n          >\n            <motion.div\n              data-slot=\"styled-command\"\n              role=\"dialog\"\n              aria-modal=\"true\"\n              aria-label=\"Command palette\"\n              className={cn(\n                \"flex w-full flex-col overflow-hidden border border-border bg-popover text-popover-foreground shadow-lg outline-none\",\n                fullWidth ? \"h-full rounded-none\" : \"rounded-xl\",\n                !fullWidth && paletteWidth[size],\n                panelClassName,\n                className\n              )}\n              initial={m.initial}\n              animate={m.animate}\n              exit={m.exit}\n              transition={reduce ? { duration: 0.12 } : { duration: 0.22, ease: \"easeOut\" }}\n              onClick={(e) => e.stopPropagation()}\n            >\n              <PaletteBody\n                items={items}\n                placeholder={placeholder}\n                listClassName={fullWidth ? \"max-h-none flex-1\" : undefined}\n              />\n            </motion.div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </>\n  )\n}\n\nconst scaleIn = {\n  initial: { opacity: 0, scale: 0.96 },\n  animate: { opacity: 1, scale: 1 },\n  exit: { opacity: 0, scale: 0.96 },\n}\n\n/* ModalCommand: klasik ortali modal, notr scrim. */\nexport function ModalCommand(props: CommandDialogProps) {\n  return (\n    <CommandDialogShell\n      props={props}\n      alignClassName=\"items-center justify-center\"\n      backdropClassName=\"bg-[color-mix(in_oklab,var(--color-foreground)_45%,transparent)]\"\n      panelMotion={scaleIn}\n    />\n  )\n}\n\n/* OverlayCommand: guclu backdrop-blur ile sayfayi tamamen dagitan katman. */\nexport function OverlayCommand(props: CommandDialogProps) {\n  return (\n    <CommandDialogShell\n      props={props}\n      alignClassName=\"items-center justify-center\"\n      backdropClassName=\"bg-[color-mix(in_oklab,var(--color-foreground)_30%,transparent)] supports-[backdrop-filter]:backdrop-blur-xl\"\n      panelMotion={scaleIn}\n    />\n  )\n}\n\n/* CenterCommand: primary tonlu scrim, ortadan asagi dogru buyuyen panel. */\nexport function CenterCommand(props: CommandDialogProps) {\n  return (\n    <CommandDialogShell\n      props={props}\n      alignClassName=\"items-center justify-center\"\n      backdropClassName=\"bg-[color-mix(in_oklab,var(--color-primary)_30%,transparent)] supports-[backdrop-filter]:backdrop-blur-sm\"\n      panelClassName=\"shadow-[0_0_0_1px_color-mix(in_oklab,var(--color-primary)_25%,transparent)]\"\n      panelMotion={{\n        initial: { opacity: 0, scale: 0.9 },\n        animate: { opacity: 1, scale: 1 },\n        exit: { opacity: 0, scale: 0.9 },\n      }}\n    />\n  )\n}\n\n/* TopCommand: the panel sits at the top and slides in downward. */\nexport function TopCommand(props: CommandDialogProps) {\n  return (\n    <CommandDialogShell\n      props={props}\n      alignClassName=\"items-start justify-center pt-[12vh]\"\n      backdropClassName=\"bg-[color-mix(in_oklab,var(--color-foreground)_40%,transparent)] supports-[backdrop-filter]:backdrop-blur-sm\"\n      panelMotion={{\n        initial: { opacity: 0, y: -16 },\n        animate: { opacity: 1, y: 0 },\n        exit: { opacity: 0, y: -16 },\n      }}\n    />\n  )\n}\n\n/* FullscreenCommand: panel tum ekrani kaplar, kenar yuvarlamasi yok. */\nexport function FullscreenCommand(props: CommandDialogProps) {\n  return (\n    <CommandDialogShell\n      props={props}\n      alignClassName=\"items-stretch justify-stretch p-0\"\n      backdropClassName=\"bg-[color-mix(in_oklab,var(--color-foreground)_20%,transparent)]\"\n      fullWidth\n      panelMotion={{\n        initial: { opacity: 0, scale: 1.02 },\n        animate: { opacity: 1, scale: 1 },\n        exit: { opacity: 0, scale: 1.02 },\n      }}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/command-dialog.tsx"
    }
  ],
  "categories": [
    "styled",
    "command"
  ],
  "type": "registry:component"
}