{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropdown-menu",
  "title": "Dropdown Menu",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "An action menu on a trigger with checkbox and radio items, submenus, shortcut hints and destructive rows, built on Radix for full keyboard and typeahead support.",
  "dependencies": [
    "radix-ui",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/direction.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/dropdown-menu.tsx",
      "content": "// Ballmac UI: Dropdown Menu. https://ui.ballmac.com/components/dropdown-menu\n// Based on shadcn/ui Dropdown Menu (MIT, Copyright (c) 2023 shadcn), restyled with tokens, destructive and inset items, shortcut and description slots.\n\"use client\";\n\nimport * as React from \"react\";\nimport { Check, ChevronRight, Circle } from \"lucide-react\";\nimport { DropdownMenu as Primitive } from \"radix-ui\";\nimport { useDirection } from \"@/lib/ballmac/direction\";\nimport { cn } from \"@/lib/utils\";\n\nconst itemBase =\n  \"relative flex min-h-8 cursor-default select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\";\n\nconst surface =\n  \"z-50 min-w-[12rem] overflow-hidden rounded-xl border bg-popover p-1 text-popover-foreground shadow-[0_12px_36px_-10px_rgb(0_0_0/0.25)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 motion-reduce:animate-none\";\n\ntype DropdownMenuProps = React.ComponentProps<typeof Primitive.Root>;\nfunction DropdownMenu(props: DropdownMenuProps) {\n  const dir = useDirection(props.dir);\n  return <Primitive.Root data-slot=\"dropdown-menu\" dir={dir} {...props} />;\n}\n\ntype DropdownMenuTriggerProps = React.ComponentProps<typeof Primitive.Trigger>;\nfunction DropdownMenuTrigger(props: DropdownMenuTriggerProps) {\n  return <Primitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\ntype DropdownMenuGroupProps = React.ComponentProps<typeof Primitive.Group>;\nfunction DropdownMenuGroup(props: DropdownMenuGroupProps) {\n  return <Primitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\ntype DropdownMenuSubProps = React.ComponentProps<typeof Primitive.Sub>;\nfunction DropdownMenuSub(props: DropdownMenuSubProps) {\n  return <Primitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\ntype DropdownMenuRadioGroupProps = React.ComponentProps<\n  typeof Primitive.RadioGroup\n>;\nfunction DropdownMenuRadioGroup(props: DropdownMenuRadioGroupProps) {\n  return (\n    <Primitive.RadioGroup data-slot=\"dropdown-menu-radio-group\" {...props} />\n  );\n}\n\ntype DropdownMenuContentProps = React.ComponentProps<typeof Primitive.Content>;\nfunction DropdownMenuContent({\n  className,\n  sideOffset = 6,\n  collisionPadding = 12,\n  ...props\n}: DropdownMenuContentProps) {\n  return (\n    <Primitive.Portal>\n      <Primitive.Content\n        data-slot=\"dropdown-menu-content\"\n        sideOffset={sideOffset}\n        collisionPadding={collisionPadding}\n        className={cn(\n          surface,\n          \"max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin) overflow-y-auto\",\n          className,\n        )}\n        {...props}\n      />\n    </Primitive.Portal>\n  );\n}\n\ntype DropdownMenuItemProps = React.ComponentProps<typeof Primitive.Item> & {\n  /** Indent the item so it lines up with items that show a check or icon. */\n  inset?: boolean;\n  /** Style the item as a destructive action (delete, sign out). */\n  destructive?: boolean;\n};\nfunction DropdownMenuItem({\n  className,\n  inset,\n  destructive,\n  ...props\n}: DropdownMenuItemProps) {\n  return (\n    <Primitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset ? \"\" : undefined}\n      data-variant={destructive ? \"destructive\" : \"default\"}\n      className={cn(\n        itemBase,\n        \"data-[inset]:ps-8\",\n        \"data-[variant=destructive]:text-destructive data-[variant=destructive]:data-[highlighted]:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:text-destructive data-[variant=destructive]:[&_svg]:!text-destructive\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype DropdownMenuCheckboxItemProps = React.ComponentProps<\n  typeof Primitive.CheckboxItem\n>;\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  ...props\n}: DropdownMenuCheckboxItemProps) {\n  return (\n    <Primitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      className={cn(itemBase, \"ps-8\", className)}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute start-2 flex size-4 items-center justify-center\">\n        <Primitive.ItemIndicator>\n          <Check aria-hidden=\"true\" className=\"size-4 !text-foreground\" />\n        </Primitive.ItemIndicator>\n      </span>\n      {children}\n    </Primitive.CheckboxItem>\n  );\n}\n\ntype DropdownMenuRadioItemProps = React.ComponentProps<\n  typeof Primitive.RadioItem\n>;\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  ...props\n}: DropdownMenuRadioItemProps) {\n  return (\n    <Primitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      className={cn(itemBase, \"ps-8\", className)}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute start-2 flex size-4 items-center justify-center\">\n        <Primitive.ItemIndicator>\n          <Circle\n            aria-hidden=\"true\"\n            className=\"size-2 fill-current !text-foreground\"\n          />\n        </Primitive.ItemIndicator>\n      </span>\n      {children}\n    </Primitive.RadioItem>\n  );\n}\n\ntype DropdownMenuLabelProps = React.ComponentProps<typeof Primitive.Label> & {\n  /** Indent the label to match inset items. */\n  inset?: boolean;\n};\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: DropdownMenuLabelProps) {\n  return (\n    <Primitive.Label\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset ? \"\" : undefined}\n      className={cn(\n        \"px-2 py-1.5 text-xs font-medium text-muted-foreground data-[inset]:ps-8\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype DropdownMenuSeparatorProps = React.ComponentProps<\n  typeof Primitive.Separator\n>;\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: DropdownMenuSeparatorProps) {\n  return (\n    <Primitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  );\n}\n\ntype DropdownMenuShortcutProps = React.ComponentProps<\"span\">;\n/** Right-aligned keyboard hint. Decorative: the accessible name stays the item text, so add `aria-keyshortcuts` on the item when the shortcut is real. */\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: DropdownMenuShortcutProps) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      aria-hidden=\"true\"\n      className={cn(\n        \"ms-auto ps-4 font-mono text-xs tracking-wide text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype DropdownMenuSubTriggerProps = React.ComponentProps<\n  typeof Primitive.SubTrigger\n> & {\n  /** Indent the trigger to match inset items. */\n  inset?: boolean;\n};\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: DropdownMenuSubTriggerProps) {\n  return (\n    <Primitive.SubTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset ? \"\" : undefined}\n      className={cn(\n        itemBase,\n        \"data-[inset]:ps-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRight aria-hidden=\"true\" className=\"ms-auto size-4 rtl:rotate-180\" />\n    </Primitive.SubTrigger>\n  );\n}\n\ntype DropdownMenuSubContentProps = React.ComponentProps<\n  typeof Primitive.SubContent\n>;\nfunction DropdownMenuSubContent({\n  className,\n  collisionPadding = 12,\n  ...props\n}: DropdownMenuSubContentProps) {\n  return (\n    <Primitive.Portal>\n      <Primitive.SubContent\n        data-slot=\"dropdown-menu-sub-content\"\n        collisionPadding={collisionPadding}\n        className={cn(\n          surface,\n          \"origin-(--radix-dropdown-menu-content-transform-origin)\",\n          className,\n        )}\n        {...props}\n      />\n    </Primitive.Portal>\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n  type DropdownMenuProps,\n  type DropdownMenuTriggerProps,\n  type DropdownMenuContentProps,\n  type DropdownMenuGroupProps,\n  type DropdownMenuItemProps,\n  type DropdownMenuCheckboxItemProps,\n  type DropdownMenuRadioGroupProps,\n  type DropdownMenuRadioItemProps,\n  type DropdownMenuLabelProps,\n  type DropdownMenuSeparatorProps,\n  type DropdownMenuShortcutProps,\n  type DropdownMenuSubProps,\n  type DropdownMenuSubTriggerProps,\n  type DropdownMenuSubContentProps,\n};\n",
      "type": "registry:ui",
      "target": "@components/ballmac/dropdown-menu.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "menu",
      "overlay",
      "actions",
      "radix"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "Opens a menu of actions from a button. Supports groups, labels, checkbox and radio items, nested submenus, shortcuts and a destructive style.",
      "whenToUse": [
        "Account, row and overflow (…) actions",
        "Sort and view preferences with radio items",
        "Menus with a submenu or checked options"
      ],
      "whenNotToUse": [
        "Choosing a form value; use select or combobox",
        "Right-click menus; use context-menu",
        "A persistent app menu bar; use menubar"
      ],
      "composesWith": [
        "button",
        "kbd"
      ],
      "a11y": [
        {
          "keys": "Enter / Space / ArrowDown",
          "action": "Opens the menu from the trigger"
        },
        {
          "keys": "ArrowUp / ArrowDown",
          "action": "Moves between items; Home and End jump"
        },
        {
          "keys": "Type a letter",
          "action": "Jumps to the next item starting with it"
        },
        {
          "keys": "ArrowRight / ArrowLeft",
          "action": "Opens and closes a submenu"
        },
        {
          "keys": "Escape",
          "action": "Closes and returns focus to the trigger"
        }
      ],
      "customization": [
        "destructive item style",
        "inset alignment for mixed icon rows",
        "alignment and side offset on the content",
        "controlled open state"
      ]
    },
    "source": {
      "name": "shadcn/ui Dropdown Menu",
      "url": "https://github.com/shadcn-ui/ui",
      "license": "MIT",
      "copyright": "Copyright (c) 2023 shadcn",
      "modified": true
    },
    "examples": [
      "dropdown-menu-demo",
      "dropdown-menu-states"
    ],
    "url": "https://ui.ballmac.com/components/dropdown-menu"
  },
  "categories": [
    "primitives"
  ],
  "type": "registry:ui"
}