{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropdown-menu",
  "title": "Dropdown Menu",
  "description": "Compact menu of actions that opens from a button and closes on choice, with arrow-key navigation and a destructive variant.",
  "dependencies": [
    "radix-ui",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/dropdown-menu.tsx",
      "content": "import type * as React from \"react\"\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\nimport {\n  MENU_MATERIALIZE_CLASS,\n  MENU_SPRING_CONTENT_CLASS,\n  MENU_SPRING_ITEM_CLASS,\n  MenuHighlight,\n  MenuSpringScope,\n  useMenuItemHighlight,\n  type DropdownMenuEffect,\n} from \"@/registry/miseui/ui/dropdown-menu-effects\"\n\n/** Gap between the trigger and the menu, in px. */\nconst SIDE_OFFSET_PX = 4\n\nconst PLAIN_MOTION_CLASS = [\n  \"duration-(--duration-fast) ease-(--ease-mise)\",\n  \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n  \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n  \"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2\",\n  \"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n]\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\ntype DropdownMenuContentProps = React.ComponentProps<typeof DropdownMenuPrimitive.Content> & {\n  /** `materialize` grows the menu out of the trigger; `spring` slides one highlight between the items. */\n  effect?: DropdownMenuEffect\n}\n\nfunction DropdownMenuContent({ className, sideOffset = SIDE_OFFSET_PX, effect, ...props }: DropdownMenuContentProps) {\n  return (\n    <MenuSpringScope enabled={effect === \"spring\"}>\n      <DropdownMenuPrimitive.Portal>\n        <DropdownMenuPrimitive.Content\n          data-slot=\"dropdown-menu-content\"\n          data-effect={effect}\n          sideOffset={sideOffset}\n          className={cn(\n            \"z-50 min-w-32 overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n            \"max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin)\",\n            effect === \"materialize\" ? MENU_MATERIALIZE_CLASS : PLAIN_MOTION_CLASS,\n            effect === \"spring\" && MENU_SPRING_CONTENT_CLASS,\n            className,\n          )}\n          {...props}\n        />\n      </DropdownMenuPrimitive.Portal>\n    </MenuSpringScope>\n  )\n}\n\nfunction DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n  return <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n}\n\ntype DropdownMenuItemProps = React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n  /** Destructive items read in the destructive colour so the eye slows down before clicking. */\n  variant?: \"default\" | \"destructive\"\n}\n\nfunction DropdownMenuItem({\n  className,\n  variant = \"default\",\n  asChild,\n  children,\n  onFocus,\n  onBlur,\n  ...props\n}: DropdownMenuItemProps) {\n  const highlight = useMenuItemHighlight({ asChild, onFocus, onBlur })\n  return (\n    <DropdownMenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      asChild={asChild}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none\",\n        \"transition-[color,background-color] duration-(--duration-fast) ease-(--ease-mise)\",\n        \"focus:bg-accent focus:text-accent-foreground\",\n        \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n        \"data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive\",\n        \"dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive!\",\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        highlight.spring && MENU_SPRING_ITEM_CLASS,\n        className,\n      )}\n      onFocus={highlight.onFocus}\n      onBlur={highlight.onBlur}\n      {...props}\n    >\n      {highlight.spring ? (\n        <>\n          {highlight.highlighted ? <MenuHighlight variant={variant} reduceMotion={highlight.reduceMotion} /> : null}\n          {children}\n        </>\n      ) : (\n        children\n      )}\n    </DropdownMenuPrimitive.Item>\n  )\n}\n\nfunction DropdownMenuLabel({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Label>) {\n  return (\n    <DropdownMenuPrimitive.Label\n      data-slot=\"dropdown-menu-label\"\n      className={cn(\"px-2 py-1.5 text-sm font-medium\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n  return (\n    <DropdownMenuPrimitive.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\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\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuLabel,\n  DropdownMenuShortcut,\n  DropdownMenuGroup,\n  type DropdownMenuItemProps,\n  type DropdownMenuContentProps,\n  type DropdownMenuEffect,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/dropdown-menu-effects.tsx",
      "content": "import type * as React from \"react\"\nimport { createContext, useContext, useId, useMemo, useState } from \"react\"\nimport { LayoutGroup, motion, useReducedMotion } from \"motion/react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype DropdownMenuEffect = \"materialize\" | \"spring\"\ntype HighlightVariant = \"default\" | \"destructive\"\n\n/** The enter and exit values stay unqualified so the media overrides below win on source order. */\nconst MENU_MATERIALIZE_CLASS = [\n  \"duration-(--duration-base) ease-(--ease-mise-out) will-change-[transform,opacity,filter]\",\n  \"data-[state=open]:animate-in data-[state=closed]:animate-out\",\n  \"fade-in-0 zoom-in-90 blur-in-sm fade-out-0 zoom-out-90 blur-out-sm\",\n  \"motion-reduce:zoom-in-100 motion-reduce:zoom-out-100 motion-reduce:blur-in-0 motion-reduce:blur-out-0\",\n  \"[@media(prefers-reduced-transparency:reduce)]:blur-in-0 [@media(prefers-reduced-transparency:reduce)]:blur-out-0\",\n]\n\n/** One stacking context for the menu keeps a flying highlight behind every label and above the surface colour. */\nconst MENU_SPRING_CONTENT_CLASS = \"isolate\"\n\n/** Clears every item background so the sliding highlight is the only one drawn. */\nconst MENU_SPRING_ITEM_CLASS = [\n  \"focus:bg-transparent focus:text-foreground\",\n  \"data-[variant=destructive]:focus:bg-transparent data-[variant=destructive]:focus:text-destructive\",\n  \"dark:data-[variant=destructive]:focus:bg-transparent\",\n]\n\nconst HIGHLIGHT_COLOR_CLASS: Record<HighlightVariant, string> = {\n  default: \"bg-mise-soft\",\n  destructive: \"bg-destructive/10 dark:bg-destructive/20\",\n}\n\nconst MenuSpringContext = createContext<{ reduceMotion: boolean } | null>(null)\n\nfunction MenuSpringScope({ enabled, children }: { enabled: boolean; children: React.ReactNode }) {\n  // layoutId is global to the page; without a per-instance group two open menus would share one highlight.\n  const groupId = useId()\n  const reduceMotion = useReducedMotion() ?? false\n  const value = useMemo(() => ({ reduceMotion }), [reduceMotion])\n  if (!enabled) return children\n  return (\n    <MenuSpringContext.Provider value={value}>\n      <LayoutGroup id={groupId}>{children}</LayoutGroup>\n    </MenuSpringContext.Provider>\n  )\n}\n\ninterface ItemHighlightOptions {\n  asChild?: boolean\n  onFocus?: React.FocusEventHandler<HTMLDivElement>\n  onBlur?: React.FocusEventHandler<HTMLDivElement>\n}\n\n/** Radix highlights an item by focusing it, for the keyboard and the pointer alike, so focus is the one signal needed. */\nfunction useMenuItemHighlight({ asChild, onFocus, onBlur }: ItemHighlightOptions) {\n  const scope = useContext(MenuSpringContext)\n  const [focused, setFocused] = useState(false)\n  // A Slot takes exactly one child, so an asChild item keeps the plain focus background instead of a highlight.\n  if (!scope || asChild) return { spring: false, highlighted: false, reduceMotion: false, onFocus, onBlur }\n  return {\n    spring: true,\n    highlighted: focused,\n    reduceMotion: scope.reduceMotion,\n    onFocus: (event: React.FocusEvent<HTMLDivElement>) => {\n      onFocus?.(event)\n      setFocused(true)\n    },\n    onBlur: (event: React.FocusEvent<HTMLDivElement>) => {\n      onBlur?.(event)\n      setFocused(false)\n    },\n  }\n}\n\nfunction MenuHighlight({ variant, reduceMotion }: { variant: HighlightVariant; reduceMotion: boolean }) {\n  return (\n    <motion.span\n      aria-hidden=\"true\"\n      data-slot=\"dropdown-menu-highlight\"\n      data-reduced={reduceMotion ? \"true\" : \"false\"}\n      layoutId={reduceMotion ? undefined : \"dropdown-menu-highlight\"}\n      transition={SPRING_DEFAULT}\n      className={cn(\"absolute inset-0 -z-10 rounded-sm\", HIGHLIGHT_COLOR_CLASS[variant])}\n    />\n  )\n}\n\nexport {\n  MENU_MATERIALIZE_CLASS,\n  MENU_SPRING_CONTENT_CLASS,\n  MENU_SPRING_ITEM_CLASS,\n  MenuHighlight,\n  MenuSpringScope,\n  useMenuItemHighlight,\n  type DropdownMenuEffect,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}