{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropdown-menu",
  "type": "registry:ui",
  "title": "Dropdown Menu",
  "description": "Click-triggered action menu with items, submenus, checkboxes, radio groups, and shortcuts.",
  "dependencies": [
    "@radix-ui/react-dropdown-menu",
    "@saasflare/ui",
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/dropdown-menu.tsx",
      "type": "registry:ui",
      "content": "// @toreview\n\"use client\"\n\n/**\n * @fileoverview DropdownMenu — premium animated dropdown menu with Motion spring transitions, sub-menus, checkbox/radio items, and keyboard navigation.\n * @module packages/ui/components/ui/dropdown-menu\n * @layer core\n *\n * @component\n * @example\n * import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@saasflare/ui';\n *\n * <DropdownMenu>\n *   <DropdownMenuTrigger>Open</DropdownMenuTrigger>\n *   <DropdownMenuContent>\n *     <DropdownMenuItem>Profile</DropdownMenuItem>\n *     <DropdownMenuItem>Settings</DropdownMenuItem>\n *   </DropdownMenuContent>\n * </DropdownMenu>\n */\n\nimport * as React from \"react\"\nimport { m } from \"motion/react\"\nimport { CheckIcon, CaretRightIcon, CircleIcon } from \"@saasflare/ui\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { cn } from \"@saasflare/ui\"\nimport { useSaasflareProps, type SaasflareComponentProps } from \"@saasflare/ui\"\nimport { useSaasflareMotion, springBouncy } from \"@saasflare/ui\"\n\n/**\n * Dropdown menu root. Manages open state and wires {@link DropdownMenuTrigger}\n * and {@link DropdownMenuContent} together, with spring-animated content,\n * nested sub-menus, checkbox/radio items, and full keyboard navigation.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n  return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n/**\n * Portals menu content into the document body. Rarely needed directly —\n * {@link DropdownMenuContent} renders its own portal.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {\n  return <DropdownMenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\n}\n/**\n * Button that toggles the dropdown menu open and closed.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuTrigger({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n  return <DropdownMenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" className={cn(\"cursor-pointer\", className)} {...props} />\n}\n/**\n * Groups related menu items for semantics and accessible structure.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n  return <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n}\n/**\n * Root for a nested sub-menu. Wraps {@link DropdownMenuSubTrigger} and\n * {@link DropdownMenuSubContent}.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n  return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />\n}\n/**\n * Single-select group for {@link DropdownMenuRadioItem} children, controlled\n * via `value` / `onValueChange`.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n  return <DropdownMenuPrimitive.RadioGroup data-slot=\"dropdown-menu-radio-group\" {...props} />\n}\n\n/**\n * Props for {@link DropdownMenuContent}.\n *\n * Extends the Radix Content props with {@link SaasflareComponentProps}, so\n * `surface`, `radius`, `animated`, and `iconWeight` can be supplied\n * per-instance or inherited from <SaasflareProvider>.\n */\ninterface DropdownMenuContentProps\n  extends Omit<React.ComponentProps<typeof DropdownMenuPrimitive.Content>, keyof SaasflareComponentProps>,\n    SaasflareComponentProps {}\n\n/**\n * Floating menu panel with a spring scale/fade entrance. Portals itself —\n * no {@link DropdownMenuPortal} wrapper required.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuContent({\n  className,\n  sideOffset = 4,\n  surface,\n  radius,\n  animated,\n  iconWeight,\n  ...props\n}: DropdownMenuContentProps) {\n  const sf = useSaasflareProps({ surface, radius, animated, iconWeight })\n  const motion = useSaasflareMotion(sf.animated, springBouncy)\n\n  return (\n    <DropdownMenuPrimitive.Portal>\n      <DropdownMenuPrimitive.Content\n        {...props}\n        data-slot=\"dropdown-menu-content\"\n        sideOffset={sideOffset}\n        asChild\n      >\n        <m.div\n          data-surface={sf.surface}\n          data-radius={sf.radius}\n          data-animated={String(sf.animated)}\n          initial={motion.disabled ? false : { opacity: 0, scale: 0.95, y: -4 }}\n          animate={{ opacity: 1, scale: 1, y: 0 }}\n          exit={motion.disabled ? undefined : { opacity: 0, scale: 0.95, y: -4 }}\n          transition={motion.transition}\n          className={cn(\n            \"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n            className\n          )}\n        />\n      </DropdownMenuPrimitive.Content>\n    </DropdownMenuPrimitive.Portal>\n  )\n}\n\ninterface DropdownMenuItemExtraProps {\n  /** Indent the item to align with sibling items that have a leading icon or indicator. */\n  inset?: boolean\n  /** Visual intent of the item. `\"destructive\"` tints the label and focus state with the destructive token. */\n  variant?: \"default\" | \"destructive\"\n}\n\n/**\n * Selectable menu entry. `inset` aligns it with icon-bearing siblings;\n * `variant=\"destructive\"` tints label and focus state with the destructive token.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuItem({ className, inset, variant = \"default\", ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & DropdownMenuItemExtraProps) {\n  return (\n    <DropdownMenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Menu item with a toggleable checked state, rendering a check indicator in\n * the leading slot.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuCheckboxItem({ className, children, checked, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n  const sf = useSaasflareProps()\n  return (\n    <DropdownMenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CheckIcon weight={sf.iconWeight} className=\"size-4\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.CheckboxItem>\n  )\n}\n\n/**\n * Single-select menu item for use inside {@link DropdownMenuRadioGroup},\n * rendering a dot indicator when active.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuRadioItem({ className, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n  const sf = useSaasflareProps()\n  return (\n    <DropdownMenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-accent focus:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <DropdownMenuPrimitive.ItemIndicator>\n          <CircleIcon weight={sf.iconWeight} className=\"size-2 fill-current\" />\n        </DropdownMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </DropdownMenuPrimitive.RadioItem>\n  )\n}\n\n/**\n * Non-interactive heading for a group of menu items.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuLabel({ className, inset, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }) {\n  return (\n    <DropdownMenuPrimitive.Label\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset}\n      className={cn(\"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8\", className)}\n      {...props}\n    />\n  )\n}\n\n/**\n * Thin horizontal rule between menu groups.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n  return (\n    <DropdownMenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn(\"bg-border -mx-1 my-1 h-px\", className)}\n      {...props}\n    />\n  )\n}\n\n/**\n * Right-aligned keyboard-shortcut hint inside a menu item. Purely visual —\n * does not register the key binding.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\n/**\n * Menu item that opens a nested sub-menu, with a trailing caret indicator.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuSubTrigger({ className, inset, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {\n  const sf = useSaasflareProps()\n  return (\n    <DropdownMenuPrimitive.SubTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <CaretRightIcon weight={sf.iconWeight} className=\"ml-auto size-4\" />\n    </DropdownMenuPrimitive.SubTrigger>\n  )\n}\n\n/**\n * Floating panel for a nested sub-menu, animated via CSS data-state transitions.\n *\n * @component\n * @layer core\n */\nfunction DropdownMenuSubContent({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n  return (\n    <DropdownMenuPrimitive.SubContent\n      data-slot=\"dropdown-menu-sub-content\"\n      className={cn(\n        \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuPortal,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n  type DropdownMenuContentProps,\n}\n",
      "target": "components/ui/dropdown-menu.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/dropdown-menu.json"
  }
}
