{
  "schemaVersion": 1,
  "name": "dropdown-menu",
  "useFor": "Use for actions on an item or a compact navigation menu. Set variant=\"glass\" for a menu that opens from the header's glass tray. Use onSelect for actions and DropdownMenuHoldItem for destructive actions that require a hold.",
  "constraints": [
    "Menus for navigation that should be visible",
    "More than one level of submenu",
    "A destructive row that fires on one click"
  ],
  "documentation": "",
  "source": "Extension from opencommerce-agentcard-web src/app/globals.css (.nav-tray, .nav-link)",
  "exports": [
    "DropdownMenu",
    "DropdownMenuCheckboxItem",
    "DropdownMenuContent",
    "DropdownMenuGroup",
    "DropdownMenuHoldItem",
    "DropdownMenuItem",
    "DropdownMenuLabel",
    "DropdownMenuRadioGroup",
    "DropdownMenuRadioItem",
    "DropdownMenuSeparator",
    "DropdownMenuShortcut",
    "DropdownMenuSub",
    "DropdownMenuSubContent",
    "DropdownMenuSubTrigger",
    "DropdownMenuTrigger"
  ],
  "client": true,
  "docsUrl": "https://opencommerce-ds.vercel.app/components/dropdown-menu",
  "markdownUrl": "https://opencommerce-ds.vercel.app/r/dropdown-menu.md",
  "examples": [
    {
      "path": "examples/dropdown-menu.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuCheckboxItem,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuHoldItem,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Field } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport { CopyButton } from \"@/components/ui/copy-button\";\nimport { Surface } from \"@/lib/surface\";\n\nfunction CardMenu({ defaultOpen = false }: { defaultOpen?: boolean }) {\n  const [open, setOpen] = React.useState(defaultOpen);\n  const [paused, setPaused] = React.useState(false);\n  const [closed, setClosed] = React.useState(false);\n  const [limit, setLimit] = React.useState(250);\n  const [balance, setBalance] = React.useState(500);\n  const [editing, setEditing] = React.useState<\"limit\" | \"funds\" | null>(null);\n  const [message, setMessage] = React.useState(\"\");\n  return (\n    <div className=\"grid justify-items-start gap-3\">\n    <DropdownMenu open={open} onOpenChange={setOpen} modal={false}>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"secondary\" disabled={closed}>Card actions</Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"start\" onOpenAutoFocus={defaultOpen ? (event) => event.preventDefault() : undefined} onCloseAutoFocus={defaultOpen ? (event) => event.preventDefault() : undefined}>\n        <DropdownMenuLabel>Travel agent</DropdownMenuLabel>\n        <DropdownMenuItem onSelect={() => setEditing(\"limit\")}>\n          Edit limits\n          <DropdownMenuShortcut>E</DropdownMenuShortcut>\n        </DropdownMenuItem>\n        <DropdownMenuItem onSelect={() => setEditing(\"funds\")}>\n          Add funds\n          <DropdownMenuShortcut>F</DropdownMenuShortcut>\n        </DropdownMenuItem>\n        <DropdownMenuItem onSelect={() => { setPaused(!paused); setMessage(paused ? \"Card resumed.\" : \"Card paused.\"); }}>{paused ? \"Resume card\" : \"Pause card\"}</DropdownMenuItem>\n        <DropdownMenuSeparator />\n        <DropdownMenuHoldItem onConfirm={() => { setClosed(true); setOpen(false); setEditing(null); setMessage(\"Card closed.\"); }}>Close card</DropdownMenuHoldItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n    {editing && <form key={editing} className=\"grid gap-3\" onSubmit={(event) => {\n      event.preventDefault();\n      const amount = Number(new FormData(event.currentTarget).get(\"amount\"));\n      if (editing === \"limit\") { setLimit(amount); setMessage(`Purchase limit updated to $${amount.toFixed(2)}.`); }\n      else { setBalance(balance + amount); setMessage(`Balance updated to $${(balance + amount).toFixed(2)}.`); }\n      setEditing(null);\n    }}>\n      <Field label={editing === \"limit\" ? \"Purchase limit (USD)\" : \"Amount to add (USD)\"} required>\n        <Input name=\"amount\" type=\"number\" min=\"0.01\" step=\"0.01\" defaultValue={editing === \"limit\" ? limit : 100} />\n      </Field>\n      <div className=\"flex gap-2\"><Button type=\"submit\">{editing === \"limit\" ? \"Save limit\" : \"Add funds\"}</Button><Button variant=\"secondary\" type=\"button\" onClick={() => setEditing(null)}>Cancel</Button></div>\n    </form>}\n    <p role=\"status\" className=\"text-body-sm text-fg-muted empty:hidden\">{message}</p>\n    {closed && <Button variant=\"secondary\" size=\"sm\" onClick={() => { setClosed(false); setPaused(false); setMessage(\"Card active.\"); }}>Reset demo</Button>}\n    </div>\n  );\n}\n\nexport function Site() {\n  return <CardMenu />;\n}\n\nexport function Open() {\n  return (\n    <div className=\"h-64\">\n      <CardMenu defaultOpen />\n    </div>\n  );\n}\n\n// The header's tray as a menu: frosted glass over whatever is behind it.\nexport function Glass() {\n  const [product, setProduct] = React.useState(\"\");\n  return (\n    <Surface ground=\"mist\" className=\"flex h-80 flex-col items-start gap-4 rounded-panel p-8\">\n      <DropdownMenu defaultOpen modal={false}>\n        <DropdownMenuTrigger asChild>\n          <Button variant=\"secondary\">Products</Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent variant=\"glass\" align=\"start\" className=\"w-64\" onOpenAutoFocus={(event) => event.preventDefault()} onCloseAutoFocus={(event) => event.preventDefault()}>\n          <DropdownMenuItem onSelect={() => setProduct(\"Search\")}>Search</DropdownMenuItem>\n          <DropdownMenuItem onSelect={() => setProduct(\"Checkout\")}>Checkout</DropdownMenuItem>\n          <DropdownMenuItem onSelect={() => setProduct(\"Tracking\")}>Tracking</DropdownMenuItem>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem onSelect={() => setProduct(\"Updates\")}>\n            Updates\n            <DropdownMenuShortcut>New</DropdownMenuShortcut>\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n      {product && <p role=\"status\" className=\"text-body-sm\">{product === \"Search\" ? \"Search any store's catalog conversationally.\" : product === \"Checkout\" ? \"Place an order at any online store with one call.\" : product === \"Tracking\" ? \"Follow every order to the door.\" : \"You’re viewing the latest product updates.\"}</p>}\n    </Surface>\n  );\n}\n\n// A working glass submenu and a persistent preference; the submenu inherits its parent panel's treatment.\nexport function Choices() {\n  const [exported, setExported] = React.useState(\"\");\n  const [notifications, setNotifications] = React.useState(true);\n  return (\n    <Surface ground=\"mist\" className=\"min-h-80 rounded-panel p-8\">\n      <DropdownMenu modal={false}>\n        <DropdownMenuTrigger asChild><Button variant=\"secondary\">Preferences</Button></DropdownMenuTrigger>\n        <DropdownMenuContent variant=\"glass\" align=\"start\">\n          <DropdownMenuCheckboxItem checked={notifications} onCheckedChange={setNotifications}>Notifications</DropdownMenuCheckboxItem>\n          <DropdownMenuSub>\n            <DropdownMenuSubTrigger>Exports</DropdownMenuSubTrigger>\n            <DropdownMenuSubContent>\n              <DropdownMenuItem onSelect={() => setExported(`notifications\\n${notifications}`)}>CSV</DropdownMenuItem>\n              <DropdownMenuItem onSelect={() => setExported(JSON.stringify({ notifications }, null, 2))}>JSON</DropdownMenuItem>\n              <DropdownMenuItem disabled>PDF (coming soon)</DropdownMenuItem>\n            </DropdownMenuSubContent>\n          </DropdownMenuSub>\n        </DropdownMenuContent>\n      </DropdownMenu>\n      {exported && <div className=\"mt-4 flex items-start gap-3\"><pre aria-label=\"Export preview\" className=\"text-body-sm\">{exported}</pre><CopyButton value={exported} label=\"Copy export\" /></div>}\n      <p className=\"mt-56 font-display text-caption\" role=\"status\">Notifications {notifications ? \"on\" : \"off\"}</p>\n    </Surface>\n  );\n}\n",
      "exports": [
        "Site",
        "Open",
        "Glass",
        "Choices"
      ],
      "requires": [
        "dropdown-menu",
        "button",
        "field",
        "input",
        "copy-button",
        "surface"
      ],
      "packages": [],
      "previews": [
        {
          "name": "Site",
          "url": "https://opencommerce-ds.vercel.app/preview/dropdown-menu/Site"
        },
        {
          "name": "Open",
          "url": "https://opencommerce-ds.vercel.app/preview/dropdown-menu/Open"
        },
        {
          "name": "Glass",
          "url": "https://opencommerce-ds.vercel.app/preview/dropdown-menu/Glass"
        },
        {
          "name": "Choices",
          "url": "https://opencommerce-ds.vercel.app/preview/dropdown-menu/Choices"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Dropdown menu",
  "description": "Action menu with white and frosted glass panels, submenus and selection controls.",
  "surface": "site",
  "kinds": [
    "menu-panel",
    "menu-item",
    "glass-menu",
    "glass-menu-item"
  ],
  "requires": [
    "theme",
    "use-hold",
    "surface",
    "utils"
  ],
  "packages": [
    "@radix-ui/react-dropdown-menu",
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/ui/dropdown-menu.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { Check, ChevronRight } from \"lucide-react\";\n\nimport { useHold } from \"@/hooks/use-hold\";\nimport { usePortalContainer } from \"@/lib/surface\";\nimport { cn } from \"@/lib/utils\";\n\n// The brand's menu: a floating white panel (a 9 percent ink edge, 8px corners, a soft drop) with 4px of padding, rows\n// in 14px Inter with 5px corners that take a faint wash when highlighted, with no transition, since menus are worked\n// from the keyboard. It grows from its trigger (scale 0.97 and a fade, 160ms ease-out). `glass` is the header's tray as\n// a menu: white at 72 percent under a light sheen and a blur, a 10 percent ink edge, the same rows.\n// Destructive rows can require a hold (DropdownMenuHoldItem) so a stray click can't delete anything.\nconst PANEL = {\n  default: \"border-line bg-surface text-fg shadow-floating\",\n  glass: \"border-line-glass bg-glass bg-(image:--ds-glass-sheen) text-fg shadow-floating [backdrop-filter:var(--ds-glass-blur)]\",\n} as const;\n\nconst MenuVariant = React.createContext<keyof typeof PANEL>(\"default\");\n\nconst ROW =\n  \"relative flex w-full cursor-default items-center gap-2 rounded-menu px-2.5 py-2 font-sans text-body-sm leading-snug text-fg-muted outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-40 data-[highlighted]:bg-wash-hover data-[highlighted]:text-fg [&_svg]:size-3.5 [&_svg]:shrink-0\";\nconst DANGER = \"text-danger data-[highlighted]:bg-danger/6 data-[highlighted]:text-danger\";\nconst QUIET = \"text-fg-faint\";\n\nfunction DropdownMenu(props: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n  return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuTrigger(props: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n  return <DropdownMenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({ className, sideOffset = 6, collisionPadding = 8, align = \"end\", variant = \"default\", ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Content> & { variant?: keyof typeof PANEL; onOpenAutoFocus?: (event: Event) => void }) {\n  return (\n    <DropdownMenuPrimitive.Portal container={usePortalContainer()}>\n      <MenuVariant.Provider value={variant}>\n        <DropdownMenuPrimitive.Content\n          data-slot=\"dropdown-menu-content\"\n          data-variant={variant}\n          sideOffset={sideOffset}\n          collisionPadding={collisionPadding}\n          align={align}\n          className={cn(\"group/menu z-50 flex min-w-[min(12rem,var(--radix-dropdown-menu-content-available-width))] max-w-(--radix-dropdown-menu-content-available-width) max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto overscroll-contain origin-(--radix-dropdown-menu-content-transform-origin) animate-pop-in motion-reduce:animate-none flex-col gap-0.5 rounded-control border p-1 outline-none\", PANEL[variant], className)}\n          {...props}\n        />\n      </MenuVariant.Provider>\n    </DropdownMenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup(props: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n  return <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuItem({ className, variant = \"default\", ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & { variant?: \"default\" | \"destructive\" }) {\n  return <DropdownMenuPrimitive.Item data-slot=\"dropdown-menu-item\" className={cn(ROW, variant === \"destructive\" && DANGER, className)} {...props} />;\n}\n\n/** A row that fires only after being held (destructive by default), its fill running across as the hint. The fill runs\n * at a constant rate while held and snaps back in 200ms ease-out on release. */\nfunction DropdownMenuHoldItem({\n  holdMs = 2000,\n  onConfirm,\n  variant = \"destructive\",\n  children,\n  className,\n  ...props\n}: Omit<React.ComponentProps<typeof DropdownMenuPrimitive.Item>, \"onSelect\"> & { holdMs?: number; onConfirm: () => void; variant?: \"default\" | \"destructive\" }) {\n  const { isHolding, handlers } = useHold(holdMs, onConfirm, props.disabled);\n  const danger = variant === \"destructive\";\n  return (\n    <DropdownMenuPrimitive.Item\n      data-slot=\"dropdown-menu-hold-item\"\n      onSelect={(event) => event.preventDefault()}\n      className={cn(ROW, \"overflow-hidden\", danger && DANGER, className)}\n      {...props}\n      {...handlers}\n      onPointerDown={(event) => { props.onPointerDown?.(event); handlers.onPointerDown(event); }}\n      onKeyDown={(event) => { props.onKeyDown?.(event); handlers.onKeyDown(event); }}\n      onPointerUp={(event) => { handlers.onPointerUp(); props.onPointerUp?.(event); }}\n      onPointerLeave={(event) => { handlers.onPointerLeave(); props.onPointerLeave?.(event); }}\n      onPointerCancel={(event) => { handlers.onPointerCancel(); props.onPointerCancel?.(event); }}\n      onKeyUp={(event) => { handlers.onKeyUp(event); props.onKeyUp?.(event); }}\n      onBlur={(event) => { handlers.onBlur(); props.onBlur?.(event); }}\n    >\n      <span\n        aria-hidden\n        className={cn(\"absolute inset-0 origin-left transition-transform motion-reduce:transition-none\", danger ? \"bg-danger/12\" : \"bg-wash-pill\", isHolding ? \"scale-x-100 ease-linear\" : \"scale-x-0 duration-200 ease-out\")}\n        style={isHolding ? { transitionDuration: `${holdMs}ms` } : undefined}\n      />\n      <span className=\"relative flex flex-1 items-center gap-2\">{isHolding ? \"Keep holding\" : children}</span>\n      <span className={cn(\"relative ml-4 text-caption\", QUIET)}>Hold {holdMs / 1000}s</span>\n    </DropdownMenuPrimitive.Item>\n  );\n}\n\nfunction DropdownMenuCheckboxItem({ className, children, checked, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n  return (\n    <DropdownMenuPrimitive.CheckboxItem data-slot=\"dropdown-menu-checkbox-item\" checked={checked} className={cn(ROW, \"pr-8\", className)} {...props}>\n      {children}\n      <DropdownMenuPrimitive.ItemIndicator className=\"absolute right-2.5 text-fg-brand\">\n        <Check strokeWidth={1.75} />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </DropdownMenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup(props: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n  return <DropdownMenuPrimitive.RadioGroup data-slot=\"dropdown-menu-radio-group\" {...props} />;\n}\n\nfunction DropdownMenuRadioItem({ className, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n  return (\n    <DropdownMenuPrimitive.RadioItem data-slot=\"dropdown-menu-radio-item\" className={cn(ROW, \"pr-8 data-[state=checked]:text-fg\", className)} {...props}>\n      {children}\n      <DropdownMenuPrimitive.ItemIndicator className=\"absolute right-2.5 text-fg-brand\">\n        <Check strokeWidth={1.75} />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </DropdownMenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Label>) {\n  return <DropdownMenuPrimitive.Label data-slot=\"dropdown-menu-label\" className={cn(\"px-2.5 pt-2 pb-1 font-sans text-caption\", QUIET, className)} {...props} />;\n}\n\nfunction DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n  return <DropdownMenuPrimitive.Separator data-slot=\"dropdown-menu-separator\" className={cn(\"-mx-1 my-1 h-px bg-line\", className)} {...props} />;\n}\n\nfunction DropdownMenuShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n  return <span data-slot=\"dropdown-menu-shortcut\" className={cn(\"ml-auto pl-4 text-caption\", QUIET, className)} {...props} />;\n}\n\nfunction DropdownMenuSub(props: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n  return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({ className, children, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger>) {\n  return (\n    <DropdownMenuPrimitive.SubTrigger data-slot=\"dropdown-menu-sub-trigger\" className={cn(ROW, \"data-[state=open]:bg-wash-hover\", className)} {...props}>\n      {children}\n      <ChevronRight className=\"ml-auto\" strokeWidth={1.5} />\n    </DropdownMenuPrimitive.SubTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({ className, variant: variantProp, collisionPadding = 8, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent> & { variant?: keyof typeof PANEL }) {\n  const inheritedVariant = React.useContext(MenuVariant);\n  const variant = variantProp ?? inheritedVariant;\n  return (\n    <DropdownMenuPrimitive.Portal container={usePortalContainer()}>\n      <MenuVariant.Provider value={variant}>\n        <DropdownMenuPrimitive.SubContent\n          data-slot=\"dropdown-menu-sub-content\"\n          collisionPadding={collisionPadding}\n          data-variant={variant}\n          className={cn(\"group/menu z-50 flex min-w-[min(10rem,var(--radix-dropdown-menu-content-available-width))] max-w-(--radix-dropdown-menu-content-available-width) max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto overscroll-contain origin-(--radix-dropdown-menu-content-transform-origin) animate-pop-in motion-reduce:animate-none flex-col gap-0.5 rounded-control border p-1\", PANEL[variant], className)}\n          {...props}\n        />\n      </MenuVariant.Provider>\n    </DropdownMenuPrimitive.Portal>\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuHoldItem,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n};\n"
    }
  ]
}
