{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select-tone",
  "title": "Tone select",
  "description": "Styled select: the Tone set. 5 decorative select variations (InfoSelect, SuccessSelect, WarningSelect, DangerSelect, MutedSelect) 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/select-tone.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AlertTriangle, Check, ChevronDown, CircleAlert, CircleCheck, Info, Minus } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Tone select family: 5 self-contained single-selects, each carrying a semantic\n   tone (info, success, warning, danger, muted). This is a STYLED component, so it\n   is not limited to the STATUS tone set of the base text controls\n   (neutral/success/danger); as a separate component it uses its own tone set. The\n   tone reaches the trigger frame, the focus ring, the panel edge and the selected\n   marker. The root element carries data-tone. NO radix, NO portal. Color comes ONLY\n   from tokens, via alpha color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\nexport type StyledTone = \"info\" | \"success\" | \"warning\" | \"danger\" | \"muted\"\n\nconst triggerHeight: Record<StyledSize, string> = {\n  sm: \"h-8 text-sm\",\n  md: \"h-9 text-sm\",\n  lg: \"h-10 text-base\",\n  xl: \"h-12 text-base\",\n}\n\nconst panelWidth: Record<StyledSize, string> = {\n  sm: \"w-48\",\n  md: \"w-56\",\n  lg: \"w-64\",\n  xl: \"w-72\",\n}\n\nexport interface StyledSelectOption {\n  value: string\n  label: string\n}\n\nexport interface StyledSelectProps {\n  className?: string\n  size?: StyledSize\n  placeholder?: string\n  options?: StyledSelectOption[]\n  value?: string\n  defaultValue?: string\n  onValueChange?: (v: string) => void\n}\n\nconst defaultOptions: StyledSelectOption[] = [\n  { value: \"queued\", label: \"Queued\" },\n  { value: \"running\", label: \"Running\" },\n  { value: \"passed\", label: \"Passed\" },\n  { value: \"failed\", label: \"Failed\" },\n]\n\nconst triggerBase =\n  \"inline-flex w-full shrink-0 select-none items-center justify-between gap-2 whitespace-nowrap rounded-lg border px-3 font-medium outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst panelBase =\n  \"absolute left-0 top-full z-50 mt-1 max-h-72 min-w-48 origin-top overflow-auto rounded-xl border bg-popover p-1.5 text-sm text-popover-foreground shadow-lg outline-none\"\n\nconst optionBase =\n  \"flex w-full cursor-default select-none items-center gap-2.5 rounded-md px-3 py-2 text-left text-sm text-popover-foreground 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 panelMotion = {\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\ninterface ToneStyle {\n  trigger: string\n  ring: string\n  panel: string\n  option: string\n  accent: string\n  icon: React.ReactNode\n}\n\nconst toneStyles: Record<StyledTone, ToneStyle> = {\n  info: {\n    trigger:\n      \"border-[color-mix(in_oklab,var(--color-info)_45%,transparent)] bg-[color-mix(in_oklab,var(--color-info)_10%,transparent)] text-foreground hover:bg-[color-mix(in_oklab,var(--color-info)_18%,transparent)]\",\n    ring: \"focus-visible:ring-info/40\",\n    panel: \"border-[color-mix(in_oklab,var(--color-info)_40%,transparent)]\",\n    option: \"hover:bg-[color-mix(in_oklab,var(--color-info)_14%,transparent)]\",\n    accent: \"text-info\",\n    icon: <Info />,\n  },\n  success: {\n    trigger:\n      \"border-[color-mix(in_oklab,var(--color-success)_45%,transparent)] bg-[color-mix(in_oklab,var(--color-success)_10%,transparent)] text-foreground hover:bg-[color-mix(in_oklab,var(--color-success)_18%,transparent)]\",\n    ring: \"focus-visible:ring-success/40\",\n    panel: \"border-[color-mix(in_oklab,var(--color-success)_40%,transparent)]\",\n    option: \"hover:bg-[color-mix(in_oklab,var(--color-success)_14%,transparent)]\",\n    accent: \"text-success\",\n    icon: <CircleCheck />,\n  },\n  warning: {\n    trigger:\n      \"border-[color-mix(in_oklab,var(--color-warning)_50%,transparent)] bg-[color-mix(in_oklab,var(--color-warning)_12%,transparent)] text-foreground hover:bg-[color-mix(in_oklab,var(--color-warning)_20%,transparent)]\",\n    ring: \"focus-visible:ring-warning/40\",\n    panel: \"border-[color-mix(in_oklab,var(--color-warning)_45%,transparent)]\",\n    option: \"hover:bg-[color-mix(in_oklab,var(--color-warning)_16%,transparent)]\",\n    accent: \"text-warning\",\n    icon: <AlertTriangle />,\n  },\n  danger: {\n    trigger:\n      \"border-[color-mix(in_oklab,var(--color-danger)_45%,transparent)] bg-[color-mix(in_oklab,var(--color-danger)_10%,transparent)] text-foreground hover:bg-[color-mix(in_oklab,var(--color-danger)_18%,transparent)]\",\n    ring: \"focus-visible:ring-danger/40\",\n    panel: \"border-[color-mix(in_oklab,var(--color-danger)_40%,transparent)]\",\n    option: \"hover:bg-[color-mix(in_oklab,var(--color-danger)_14%,transparent)]\",\n    accent: \"text-danger\",\n    icon: <CircleAlert />,\n  },\n  muted: {\n    trigger: \"border-field-border bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground\",\n    ring: \"focus-visible:ring-ring/50\",\n    panel: \"border-border\",\n    option: \"hover:bg-accent hover:text-accent-foreground\",\n    accent: \"text-muted-foreground\",\n    icon: <Minus />,\n  },\n}\n\nfunction BaseToneSelect({\n  className,\n  size = \"md\",\n  placeholder = \"Select...\",\n  options,\n  value,\n  defaultValue,\n  onValueChange,\n  tone,\n}: StyledSelectProps & { tone: StyledTone }) {\n  const list = options ?? defaultOptions\n  const style = toneStyles[tone]\n  const reduce = useReducedMotion()\n  const uid = React.useId()\n  const listId = `${uid}-listbox`\n  const optionId = (i: number) => `${uid}-option-${i}`\n\n  const [open, setOpen] = React.useState(false)\n  const [active, setActive] = React.useState(0)\n  const [internal, setInternal] = React.useState<string | undefined>(defaultValue)\n  const wrapperRef = React.useRef<HTMLDivElement>(null)\n  const triggerRef = React.useRef<HTMLButtonElement>(null)\n\n  const isControlled = value !== undefined\n  const current = isControlled ? value : internal\n  const selectedLabel = list.find((o) => o.value === current)?.label\n\n  const close = React.useCallback(() => {\n    setOpen(false)\n    triggerRef.current?.focus()\n  }, [])\n\n  const select = React.useCallback(\n    (next: string) => {\n      if (!isControlled) setInternal(next)\n      onValueChange?.(next)\n      setOpen(false)\n      triggerRef.current?.focus()\n    },\n    [isControlled, onValueChange],\n  )\n\n  React.useEffect(() => {\n    if (!open) return\n    const onPointer = (e: PointerEvent) => {\n      const node = wrapperRef.current\n      if (node && e.target instanceof Node && !node.contains(e.target)) setOpen(false)\n    }\n    window.addEventListener(\"pointerdown\", onPointer)\n    return () => window.removeEventListener(\"pointerdown\", onPointer)\n  }, [open])\n\n  const indexOfCurrent = () => {\n    const i = list.findIndex((o) => o.value === current)\n    return i < 0 ? 0 : i\n  }\n\n  const onKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === \"Escape\") {\n      if (!open) return\n      e.preventDefault()\n      close()\n      return\n    }\n    if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n      e.preventDefault()\n      if (!open) {\n        setOpen(true)\n        setActive(indexOfCurrent())\n        return\n      }\n      const dir = e.key === \"ArrowDown\" ? 1 : -1\n      setActive((i) => (i + dir + list.length) % list.length)\n      return\n    }\n    if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault()\n      if (!open) {\n        setOpen(true)\n        setActive(indexOfCurrent())\n        return\n      }\n      const opt = list[active]\n      if (opt) select(opt.value)\n    }\n  }\n\n  const fade = { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n  const anim = reduce ? fade : panelMotion\n\n  return (\n    <div\n      ref={wrapperRef}\n      data-slot=\"styled-select\"\n      data-tone={tone}\n      className={cn(\"relative inline-flex w-56 max-w-full\", panelWidth[size], className)}\n    >\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        data-slot=\"styled-select-trigger\"\n        role=\"combobox\"\n        aria-expanded={open}\n        aria-haspopup=\"listbox\"\n        aria-controls={listId}\n        aria-activedescendant={open ? optionId(active) : undefined}\n        className={cn(triggerBase, triggerHeight[size], style.trigger, style.ring)}\n        onClick={() => {\n          setActive(indexOfCurrent())\n          setOpen(!open)\n        }}\n        onKeyDown={onKeyDown}\n      >\n        <span className=\"flex min-w-0 items-center gap-2\">\n          <span className={cn(\"flex shrink-0 items-center\", style.accent)}>{style.icon}</span>\n          <span className={cn(\"truncate\", selectedLabel === undefined && \"text-muted-foreground\")}>\n            {selectedLabel ?? placeholder}\n          </span>\n        </span>\n        <ChevronDown\n          className={cn(\"shrink-0 opacity-70 transition-transform duration-200\", open && \"rotate-180\")}\n        />\n      </button>\n\n      <AnimatePresence>\n        {open ? (\n          <motion.div\n            id={listId}\n            role=\"listbox\"\n            data-slot=\"styled-select-content\"\n            className={cn(panelBase, panelWidth[size], style.panel)}\n            initial={anim.initial}\n            animate={anim.animate}\n            exit={anim.exit}\n            transition={reduce ? { duration: 0.14 } : panelMotion.transition}\n          >\n            <div className=\"flex flex-col\">\n              {list.map((option, i) => {\n                const isSelected = option.value === current\n                return (\n                  <button\n                    key={option.value}\n                    id={optionId(i)}\n                    type=\"button\"\n                    role=\"option\"\n                    tabIndex={-1}\n                    aria-selected={isSelected}\n                    className={cn(optionBase, style.option, style.ring, i === active && \"bg-accent text-accent-foreground\")}\n                    onPointerEnter={() => setActive(i)}\n                    onClick={() => select(option.value)}\n                  >\n                    <span className={cn(\"flex size-4 shrink-0 items-center justify-center\", style.accent)}>\n                      {isSelected ? <Check /> : null}\n                    </span>\n                    <span className=\"truncate\">{option.label}</span>\n                  </button>\n                )\n              })}\n            </div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\n/* Info: bilgilendirici mavi ton. */\nexport function InfoSelect(props: StyledSelectProps) {\n  return <BaseToneSelect {...props} tone=\"info\" />\n}\n\n/* Success: olumlu/onaylanmis ton. */\nexport function SuccessSelect(props: StyledSelectProps) {\n  return <BaseToneSelect {...props} tone=\"success\" />\n}\n\n/* Warning: dikkat isteyen ton. */\nexport function WarningSelect(props: StyledSelectProps) {\n  return <BaseToneSelect {...props} tone=\"warning\" />\n}\n\n/* Danger: yikici/riskli secim tonu. */\nexport function DangerSelect(props: StyledSelectProps) {\n  return <BaseToneSelect {...props} tone=\"danger\" />\n}\n\n/* Muted: sessiz, ikincil ton. */\nexport function MutedSelect(props: StyledSelectProps) {\n  return <BaseToneSelect {...props} tone=\"muted\" />\n}\n",
      "type": "registry:component",
      "target": "components/ui/select-tone.tsx"
    }
  ],
  "categories": [
    "styled",
    "select"
  ],
  "type": "registry:component"
}