{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dropdown-menu-tone",
  "title": "Menu-tone dropdown",
  "description": "Styled dropdown: the Menu-tone set. 5 decorative dropdown variations (InfoMenu, SuccessMenu, WarningMenu, DangerMenu, MutedMenu) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/dropdown-menu-tone.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  Archive,\n  Ban,\n  Bell,\n  CheckCheck,\n  ChevronDown,\n  CircleAlert,\n  CircleCheck,\n  Clock,\n  Download,\n  EyeOff,\n  Flag,\n  Info,\n  RefreshCw,\n  Send,\n  ShieldAlert,\n  Trash2,\n  TriangleAlert,\n  Undo2,\n} from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Tone dropdown family: 5 semantically toned menus. Each variant drives its own\n   token family (info/success/warning/danger + -foreground/-soft/-soft-foreground;\n   the neutral one uses the muted tone). The panel gets a soft background, the\n   trigger a toned border, and items are marked with the tone itself on hover; the\n   root carries data-tone. Each export is a complete dropdown: a relative wrapper +\n   a trigger + a panel absolutely positioned below the trigger. NO radix or portal.\n   It closes on an outside click, on Escape (focus returns to the trigger) and on an\n   item selection. The arrow keys move between items. Color comes ONLY from tokens,\n   via alpha color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nexport type StyledTone = \"info\" | \"success\" | \"warning\" | \"danger\" | \"muted\"\n\nconst panelWidth: Record<StyledSize, string> = {\n  sm: \"w-48\",\n  md: \"w-56\",\n  lg: \"w-64\",\n  xl: \"w-72\",\n}\n\nconst itemHeight: Record<StyledSize, string> = {\n  sm: \"min-h-8 py-1.5\",\n  md: \"min-h-9 py-2\",\n  lg: \"min-h-10 py-2.5\",\n  xl: \"min-h-11 py-3\",\n}\n\nexport interface StyledMenuItem {\n  label: React.ReactNode\n  icon?: React.ReactNode\n  description?: React.ReactNode\n  onSelect?: () => void\n}\n\ninterface StyledMenuProps {\n  className?: string\n  size?: StyledSize\n  label?: React.ReactNode\n  items?: StyledMenuItem[]\n}\n\ninterface ToneStyle {\n  trigger: string\n  panel: string\n  item: string\n  heading: string\n}\n\n/* Tone -> class map. Every tone is written out explicitly because Tailwind needs to see literal classes; color-mix is used where alpha is needed. */\nconst toneStyles: Record<StyledTone, ToneStyle> = {\n  info: {\n    trigger:\n      \"border-[color-mix(in_oklab,var(--color-info)_35%,transparent)] bg-info-soft text-info-soft-foreground hover:bg-[color-mix(in_oklab,var(--color-info)_22%,transparent)]\",\n    panel:\n      \"border-[color-mix(in_oklab,var(--color-info)_28%,transparent)] bg-info-soft text-info-soft-foreground\",\n    item: \"text-info-soft-foreground hover:bg-info hover:text-info-foreground focus-visible:bg-info focus-visible:text-info-foreground\",\n    heading: \"text-[color-mix(in_oklab,var(--color-info-soft-foreground)_75%,transparent)]\",\n  },\n  success: {\n    trigger:\n      \"border-[color-mix(in_oklab,var(--color-success)_35%,transparent)] bg-success-soft text-success-soft-foreground hover:bg-[color-mix(in_oklab,var(--color-success)_22%,transparent)]\",\n    panel:\n      \"border-[color-mix(in_oklab,var(--color-success)_28%,transparent)] bg-success-soft text-success-soft-foreground\",\n    item: \"text-success-soft-foreground hover:bg-success hover:text-success-foreground focus-visible:bg-success focus-visible:text-success-foreground\",\n    heading: \"text-[color-mix(in_oklab,var(--color-success-soft-foreground)_75%,transparent)]\",\n  },\n  warning: {\n    trigger:\n      \"border-[color-mix(in_oklab,var(--color-warning)_35%,transparent)] bg-warning-soft text-warning-soft-foreground hover:bg-[color-mix(in_oklab,var(--color-warning)_22%,transparent)]\",\n    panel:\n      \"border-[color-mix(in_oklab,var(--color-warning)_28%,transparent)] bg-warning-soft text-warning-soft-foreground\",\n    item: \"text-warning-soft-foreground hover:bg-warning hover:text-warning-foreground focus-visible:bg-warning focus-visible:text-warning-foreground\",\n    heading: \"text-[color-mix(in_oklab,var(--color-warning-soft-foreground)_75%,transparent)]\",\n  },\n  danger: {\n    trigger:\n      \"border-[color-mix(in_oklab,var(--color-danger)_35%,transparent)] bg-danger-soft text-danger-soft-foreground hover:bg-[color-mix(in_oklab,var(--color-danger)_22%,transparent)]\",\n    panel:\n      \"border-[color-mix(in_oklab,var(--color-danger)_28%,transparent)] bg-danger-soft text-danger-soft-foreground\",\n    item: \"text-danger-soft-foreground hover:bg-danger hover:text-danger-foreground focus-visible:bg-danger focus-visible:text-danger-foreground\",\n    heading: \"text-[color-mix(in_oklab,var(--color-danger-soft-foreground)_75%,transparent)]\",\n  },\n  muted: {\n    trigger:\n      \"border-border bg-muted text-muted-foreground hover:bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)]\",\n    panel: \"border-border bg-muted text-foreground\",\n    item: \"text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground\",\n    heading: \"text-muted-foreground\",\n  },\n}\n\nconst triggerBase =\n  \"inline-flex h-9 shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border px-4 text-sm font-medium outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&>svg]:size-4 [&>svg]:shrink-0 [&>i]:text-base [&>i]:leading-none\"\n\nconst panelBase =\n  \"origin-top rounded-xl border p-1.5 text-sm shadow-lg outline-none\"\n\nconst itemBase =\n  \"flex w-full cursor-default select-none items-center gap-2.5 rounded-md px-3 text-left text-sm outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst menuMotion = {\n  initial: { opacity: 0, scale: 0.96, y: -6 },\n  animate: { opacity: 1, scale: 1, y: 0 },\n  exit: { opacity: 0, scale: 0.96, y: -6 },\n  transition: { type: \"spring\" as const, stiffness: 340, damping: 26 },\n}\n\nconst fade = { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n\nfunction useMenuState() {\n  const [open, setOpen] = React.useState(false)\n  const wrapperRef = React.useRef<HTMLDivElement>(null)\n  const triggerRef = React.useRef<HTMLButtonElement>(null)\n\n  React.useEffect(() => {\n    if (!open) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") {\n        setOpen(false)\n        triggerRef.current?.focus()\n      }\n    }\n    const onPointer = (e: PointerEvent) => {\n      const node = wrapperRef.current\n      if (node && e.target instanceof Node && !node.contains(e.target)) {\n        setOpen(false)\n      }\n    }\n    window.addEventListener(\"keydown\", onKey)\n    window.addEventListener(\"pointerdown\", onPointer)\n    return () => {\n      window.removeEventListener(\"keydown\", onKey)\n      window.removeEventListener(\"pointerdown\", onPointer)\n    }\n  }, [open])\n\n  return { open, setOpen, wrapperRef, triggerRef }\n}\n\nfunction useArrowNav(panelRef: React.RefObject<HTMLDivElement | null>) {\n  return React.useCallback(\n    (e: React.KeyboardEvent<HTMLDivElement>) => {\n      if (e.key !== \"ArrowDown\" && e.key !== \"ArrowUp\") return\n      const panel = panelRef.current\n      if (!panel) return\n      const nodes = Array.from(\n        panel.querySelectorAll<HTMLButtonElement>(\n          '[role=\"menuitem\"],[role=\"menuitemcheckbox\"]'\n        )\n      )\n      if (nodes.length === 0) return\n      e.preventDefault()\n      const current = nodes.indexOf(document.activeElement as HTMLButtonElement)\n      const next =\n        e.key === \"ArrowDown\"\n          ? (current + 1) % nodes.length\n          : current <= 0\n            ? nodes.length - 1\n            : current - 1\n      nodes[next]?.focus()\n    },\n    [panelRef]\n  )\n}\n\n/* Shared toned shell: trigger plus an absolutely positioned panel plus the item list. */\nfunction ToneMenu({\n  tone,\n  size = \"md\",\n  label,\n  className,\n  items,\n  heading,\n}: {\n  tone: StyledTone\n  size?: StyledSize\n  label: React.ReactNode\n  className?: string\n  items: StyledMenuItem[]\n  heading?: React.ReactNode\n}) {\n  const { open, setOpen, wrapperRef, triggerRef } = useMenuState()\n  const panelRef = React.useRef<HTMLDivElement>(null)\n  const onArrow = useArrowNav(panelRef)\n  const reduce = useReducedMotion()\n  const id = React.useId()\n  const panelId = `${id}-menu`\n  const t = toneStyles[tone]\n\n  const active = reduce ? fade : menuMotion\n\n  return (\n    <div\n      ref={wrapperRef}\n      data-slot=\"styled-dropdown-menu\"\n      data-tone={tone}\n      className={cn(\"relative inline-flex\", className)}\n    >\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        data-slot=\"styled-dropdown-menu-trigger\"\n        aria-haspopup=\"menu\"\n        aria-expanded={open}\n        aria-controls={open ? panelId : undefined}\n        className={cn(triggerBase, t.trigger)}\n        onClick={() => setOpen(!open)}\n      >\n        {label}\n        <ChevronDown\n          className={cn(\"transition-transform duration-200\", open && \"rotate-180\")}\n        />\n      </button>\n\n      <AnimatePresence>\n        {open ? (\n          <div className={cn(\"absolute left-0 top-full z-50 mt-1\", panelWidth[size])}>\n            <motion.div\n              ref={panelRef}\n              id={panelId}\n              role=\"menu\"\n              data-slot=\"styled-dropdown-menu-content\"\n              data-tone={tone}\n              className={cn(panelBase, t.panel)}\n              initial={active.initial}\n              animate={active.animate}\n              exit={active.exit}\n              transition={reduce ? { duration: 0.16 } : menuMotion.transition}\n              onKeyDown={onArrow}\n            >\n              {heading ? (\n                <div className={cn(\"px-3 py-1.5 text-xs font-medium\", t.heading)}>\n                  {heading}\n                </div>\n              ) : null}\n              <div className=\"flex flex-col\">\n                {items.map((item, i) => (\n                  <button\n                    key={`${i}`}\n                    type=\"button\"\n                    role=\"menuitem\"\n                    tabIndex={0}\n                    className={cn(itemBase, itemHeight[size], t.item)}\n                    onClick={() => {\n                      item.onSelect?.()\n                      setOpen(false)\n                    }}\n                  >\n                    {item.icon ? (\n                      <span className=\"flex shrink-0 items-center\">{item.icon}</span>\n                    ) : null}\n                    {item.label}\n                  </button>\n                ))}\n              </div>\n            </motion.div>\n          </div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\nconst infoItems: StyledMenuItem[] = [\n  { label: \"What is new\", icon: <Info /> },\n  { label: \"Release notes\", icon: <Flag /> },\n  { label: \"Status page\", icon: <Bell /> },\n  { label: \"Download report\", icon: <Download /> },\n]\n\n/* Info: bilgilendirici eylemler, info token ailesi. */\nexport function InfoMenu({ className, size = \"md\", label = \"Info\", items }: StyledMenuProps) {\n  return (\n    <ToneMenu\n      tone=\"info\"\n      size={size}\n      label={label}\n      className={className}\n      heading=\"Information\"\n      items={items ?? infoItems}\n    />\n  )\n}\n\nconst successItems: StyledMenuItem[] = [\n  { label: \"Approve\", icon: <CircleCheck /> },\n  { label: \"Mark all read\", icon: <CheckCheck /> },\n  { label: \"Publish\", icon: <Send /> },\n  { label: \"Archive\", icon: <Archive /> },\n]\n\n/* Success: onaylayici eylemler, success token ailesi. */\nexport function SuccessMenu({ className, size = \"md\", label = \"Success\", items }: StyledMenuProps) {\n  return (\n    <ToneMenu\n      tone=\"success\"\n      size={size}\n      label={label}\n      className={className}\n      heading=\"Confirm\"\n      items={items ?? successItems}\n    />\n  )\n}\n\nconst warningItems: StyledMenuItem[] = [\n  { label: \"Review flags\", icon: <TriangleAlert /> },\n  { label: \"Pause sync\", icon: <Clock /> },\n  { label: \"Retry failed\", icon: <RefreshCw /> },\n  { label: \"Check limits\", icon: <ShieldAlert /> },\n]\n\n/* Warning: dikkat isteyen eylemler, warning token ailesi. */\nexport function WarningMenu({ className, size = \"md\", label = \"Warning\", items }: StyledMenuProps) {\n  return (\n    <ToneMenu\n      tone=\"warning\"\n      size={size}\n      label={label}\n      className={className}\n      heading=\"Needs attention\"\n      items={items ?? warningItems}\n    />\n  )\n}\n\nconst dangerItems: StyledMenuItem[] = [\n  { label: \"Revoke access\", icon: <Ban /> },\n  { label: \"Report issue\", icon: <CircleAlert /> },\n  { label: \"Reset project\", icon: <Undo2 /> },\n  { label: \"Delete forever\", icon: <Trash2 /> },\n]\n\n/* Danger: yikici eylemler, danger token ailesi. */\nexport function DangerMenu({ className, size = \"md\", label = \"Danger\", items }: StyledMenuProps) {\n  return (\n    <ToneMenu\n      tone=\"danger\"\n      size={size}\n      label={label}\n      className={className}\n      heading=\"Destructive\"\n      items={items ?? dangerItems}\n    />\n  )\n}\n\nconst mutedItems: StyledMenuItem[] = [\n  { label: \"Hide from feed\", icon: <EyeOff /> },\n  { label: \"Snooze\", icon: <Clock /> },\n  { label: \"Archive\", icon: <Archive /> },\n  { label: \"Mute alerts\", icon: <Bell /> },\n]\n\n/* Muted: notr/ikincil eylemler, muted token ailesi (soft karsiligi yok). */\nexport function MutedMenu({ className, size = \"md\", label = \"Muted\", items }: StyledMenuProps) {\n  return (\n    <ToneMenu\n      tone=\"muted\"\n      size={size}\n      label={label}\n      className={className}\n      heading=\"Quiet actions\"\n      items={items ?? mutedItems}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/dropdown-menu-tone.tsx"
    }
  ],
  "categories": [
    "styled",
    "dropdown"
  ],
  "type": "registry:component"
}