{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command",
  "title": "Command",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A filterable command list on cmdk, inline or in a Cmd/Ctrl+K dialog, with groups, shortcuts, descriptions, loading state and page-safe scrolling.",
  "dependencies": [
    "cmdk@^1",
    "radix-ui",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/command.tsx",
      "content": "// Ballmac UI: Command. https://ui.ballmac.com/components/command\n// Based on shadcn/ui Command (MIT, Copyright (c) 2023 shadcn) on cmdk (MIT, Copyright (c) 2022 Dip), adding a page-safe scroll fix, an item description, a loading row, and a hotkey dialog.\n\"use client\";\n\nimport * as React from \"react\";\nimport { Command as Primitive } from \"cmdk\";\nimport { Check, Search } from \"lucide-react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport { cn } from \"@/lib/utils\";\nimport { useMessages } from \"@/lib/ballmac/i18n\";\n\n/**\n * cmdk scrolls the highlighted row into view with scrollIntoView, which also scrolls the page when\n * the command menu is off-screen (an inline demo below the fold). Route it to the list only.\n */\nfunction scrollWithinList(this: HTMLElement) {\n  const list = this.closest<HTMLElement>(\"[cmdk-list]\");\n  if (!list) return;\n  const bounds = list.getBoundingClientRect();\n  const rect = this.getBoundingClientRect();\n  if (rect.top < bounds.top) list.scrollTop -= bounds.top - rect.top + 8;\n  else if (rect.bottom > bounds.bottom)\n    list.scrollTop += rect.bottom - bounds.bottom + 8;\n}\nfunction keepScrollInList(el: HTMLElement | null) {\n  if (el) el.scrollIntoView = scrollWithinList;\n}\nfunction setRef<T>(ref: React.Ref<T> | undefined, value: T | null) {\n  if (typeof ref === \"function\") ref(value);\n  else if (ref) (ref as React.RefObject<T | null>).current = value;\n}\n\ntype CommandProps = React.ComponentProps<typeof Primitive>;\n/** Root. Accepts cmdk props such as `filter`, `shouldFilter`, `value`, `onValueChange` and `loop`. */\nfunction Command({ className, loop = true, label, ...props }: CommandProps) {\n  const msg = useMessages()\n  label ??= msg(\"command.label\", \"Command menu\")\n  return (\n    <Primitive\n      data-slot=\"command\"\n      loop={loop}\n      label={label}\n      className={cn(\n        \"flex h-full w-full flex-col overflow-hidden rounded-xl bg-popover text-popover-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype CommandDialogProps = Omit<CommandProps, \"title\"> & {\n  /** Controlled open state. */\n  open?: boolean;\n  /** Initial open state when uncontrolled. */\n  defaultOpen?: boolean;\n  /** Called when the dialog opens or closes. */\n  onOpenChange?: (open: boolean) => void;\n  /** Key that toggles the dialog together with ⌘ (Ctrl on Windows and Linux). `false` turns the shortcut off. */\n  hotkey?: string | false;\n  /** Accessible title of the dialog (visually hidden). */\n  title?: string;\n  /** Accessible description of the dialog (visually hidden). */\n  description?: string;\n};\n/** The command menu in a modal dialog near the top of the screen. Toggled with ⌘K by default; Escape closes it. */\nfunction CommandDialog({\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  hotkey = \"k\",\n  title,\n  description,\n  className,\n  children,\n  ...props\n}: CommandDialogProps) {\n  const msg = useMessages()\n  title ??= msg(\"command.title\", \"Command menu\")\n  description ??= msg(\"command.description\", \"Type a command or search. Use the arrow keys to move and Enter to run.\")\n  const [uncontrolled, setUncontrolled] = React.useState(defaultOpen);\n  const open = openProp ?? uncontrolled;\n  const setOpen = React.useCallback(\n    (next: boolean) => {\n      if (openProp === undefined) setUncontrolled(next);\n      onOpenChange?.(next);\n    },\n    [openProp, onOpenChange],\n  );\n  React.useEffect(() => {\n    if (!hotkey) return;\n    const onKeyDown = (event: KeyboardEvent) => {\n      if (\n        (event.metaKey || event.ctrlKey) &&\n        !event.altKey &&\n        event.key.toLowerCase() === hotkey.toLowerCase()\n      ) {\n        event.preventDefault();\n        setOpen(!open);\n      }\n    };\n    document.addEventListener(\"keydown\", onKeyDown);\n    return () => document.removeEventListener(\"keydown\", onKeyDown);\n  }, [hotkey, open, setOpen]);\n  return (\n    <DialogPrimitive.Root open={open} onOpenChange={setOpen}>\n      <DialogPrimitive.Portal>\n        <DialogPrimitive.Overlay\n          data-slot=\"command-overlay\"\n          className=\"fixed inset-0 z-50 bg-black/40 backdrop-blur-[2px] data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 motion-reduce:animate-none\"\n        />\n        <DialogPrimitive.Content\n          data-slot=\"command-dialog\"\n          className=\"fixed top-[14dvh] left-1/2 z-50 w-[calc(100%-2rem)] max-w-xl -translate-x-1/2 overflow-hidden rounded-xl border bg-popover shadow-[0_24px_60px_-12px_rgb(0_0_0/0.4)] outline-none duration-150 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 motion-reduce:animate-none\"\n        >\n          <DialogPrimitive.Title className=\"sr-only\">{title}</DialogPrimitive.Title>\n          <DialogPrimitive.Description className=\"sr-only\">\n            {description}\n          </DialogPrimitive.Description>\n          <Command className={className} {...props}>\n            {children}\n          </Command>\n        </DialogPrimitive.Content>\n      </DialogPrimitive.Portal>\n    </DialogPrimitive.Root>\n  );\n}\n\ntype CommandInputProps = React.ComponentProps<typeof Primitive.Input>;\nfunction CommandInput({ className, ...props }: CommandInputProps) {\n  return (\n    <div\n      data-slot=\"command-input-wrapper\"\n      className=\"flex h-12 shrink-0 items-center gap-2 border-b px-3.5\"\n    >\n      <Search aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n      <Primitive.Input\n        data-slot=\"command-input\"\n        className={cn(\n          \"h-full w-full min-w-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n          className,\n        )}\n        {...props}\n      />\n    </div>\n  );\n}\n\ntype CommandListProps = React.ComponentProps<typeof Primitive.List>;\nfunction CommandList({ className, ...props }: CommandListProps) {\n  return (\n    <Primitive.List\n      data-slot=\"command-list\"\n      className={cn(\n        \"max-h-[min(20rem,55dvh)] scroll-py-1 overflow-x-hidden overflow-y-auto overscroll-contain p-1\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype CommandEmptyProps = React.ComponentProps<typeof Primitive.Empty>;\nfunction CommandEmpty({ className, ...props }: CommandEmptyProps) {\n  return (\n    <Primitive.Empty\n      data-slot=\"command-empty\"\n      className={cn(\"py-8 text-center text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\ntype CommandLoadingProps = React.ComponentProps<typeof Primitive.Loading>;\n/** Shown while results load. Renders `role=\"progressbar\"` with the given `progress` (0 to 100). */\nfunction CommandLoading({ className, ...props }: CommandLoadingProps) {\n  return (\n    <Primitive.Loading\n      data-slot=\"command-loading\"\n      className={cn(\"py-6 text-center text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\ntype CommandGroupProps = React.ComponentProps<typeof Primitive.Group>;\nfunction CommandGroup({ ref, className, ...props }: CommandGroupProps) {\n  return (\n    <Primitive.Group\n      ref={(el: HTMLDivElement | null) => {\n        keepScrollInList(el?.querySelector<HTMLElement>(\"[cmdk-group-heading]\") ?? null);\n        setRef(ref, el);\n      }}\n      data-slot=\"command-group\"\n      className={cn(\n        \"overflow-hidden text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype CommandSeparatorProps = React.ComponentProps<typeof Primitive.Separator>;\nfunction CommandSeparator({ className, ...props }: CommandSeparatorProps) {\n  return (\n    <Primitive.Separator\n      data-slot=\"command-separator\"\n      aria-hidden=\"true\"\n      className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  );\n}\n\ntype CommandItemProps = React.ComponentProps<typeof Primitive.Item> & {\n  /** Leading icon. Use this instead of putting the icon in `children` when the item has a `description`. */\n  icon?: React.ReactNode;\n  /** Second line of muted text under the title. */\n  description?: React.ReactNode;\n  /** Show a check at the end, for the currently chosen option in a picker. */\n  selected?: boolean;\n};\nfunction CommandItem({\n  ref,\n  className,\n  icon,\n  description,\n  selected,\n  children,\n  ...props\n}: CommandItemProps) {\n  return (\n    <Primitive.Item\n      ref={(el: HTMLDivElement | null) => {\n        keepScrollInList(el);\n        setRef(ref, el);\n      }}\n      data-slot=\"command-item\"\n      data-checked={selected ? \"true\" : undefined}\n      className={cn(\n        \"relative flex min-h-9 cursor-default select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      {icon}\n      {description ? (\n        <span className=\"grid min-w-0 flex-1 gap-0.5\">\n          <span className=\"truncate\">{children}</span>\n          <span className=\"truncate text-xs text-muted-foreground\">{description}</span>\n        </span>\n      ) : (\n        children\n      )}\n      {selected && <Check aria-hidden=\"true\" className=\"ms-auto size-4 !text-foreground\" />}\n    </Primitive.Item>\n  );\n}\n\ntype CommandShortcutProps = React.ComponentProps<\"span\">;\nfunction CommandShortcut({ className, ...props }: CommandShortcutProps) {\n  return (\n    <span\n      data-slot=\"command-shortcut\"\n      aria-hidden=\"true\"\n      className={cn(\n        \"ms-auto font-mono text-xs tracking-wide text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandLoading,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n  type CommandProps,\n  type CommandDialogProps,\n  type CommandInputProps,\n  type CommandListProps,\n  type CommandEmptyProps,\n  type CommandLoadingProps,\n  type CommandGroupProps,\n  type CommandItemProps,\n  type CommandShortcutProps,\n  type CommandSeparatorProps,\n};\n",
      "type": "registry:ui",
      "target": "@components/ballmac/command.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "command palette",
      "search",
      "cmdk",
      "keyboard"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "Compose Command, CommandInput, CommandList, CommandGroup and CommandItem. CommandDialog adds the modal and the ⌘K hotkey.",
      "whenToUse": [
        "Command palettes and quick switchers",
        "Searchable action lists",
        "The list inside a combobox"
      ],
      "whenNotToUse": [
        "macOS-style launcher visuals; use spotlight-search",
        "A simple select; use select"
      ],
      "composesWith": [
        "popover",
        "dialog",
        "kbd"
      ],
      "a11y": [
        {
          "keys": "Type",
          "action": "Filters the list"
        },
        {
          "keys": "ArrowUp / ArrowDown",
          "action": "Moves the highlighted item"
        },
        {
          "keys": "Enter",
          "action": "Runs the highlighted item"
        },
        {
          "keys": "Cmd/Ctrl+K",
          "action": "Toggles CommandDialog"
        },
        {
          "keys": "Escape",
          "action": "Closes the dialog"
        }
      ],
      "customization": [
        "hotkey key or false",
        "filter and shouldFilter from cmdk",
        "item description and selected check",
        "CommandLoading for async results"
      ]
    },
    "source": {
      "name": "shadcn/ui Command",
      "url": "https://github.com/shadcn-ui/ui",
      "license": "MIT",
      "copyright": "Copyright (c) 2023 shadcn",
      "modified": true
    },
    "examples": [
      "command-demo",
      "command-dialog"
    ],
    "url": "https://ui.ballmac.com/components/command"
  },
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}