{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sonner-styled",
  "title": "Sonner essentials",
  "description": "Styled sonner: the essentials set. 5 decorative sonner variations (SimpleToaster, ToneToaster, GlassToaster, ActionToaster, PromiseToaster) 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/sonner-styled.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AlertTriangle, CheckCircle, Info, Loader2, X } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Styled sonner family: 5 self-contained toasters. NO sonner/portal - each export\n   renders a demo trigger button; clicking it pushes a toast onto a LOCAL stack (a\n   useState array), it appears in the bottom-right corner (fixed bottom-right, z-50)\n   and disappears on a timeout or via the close button. Multiple toasts stack on top\n   of each other. AnimatePresence handles the enter/exit slide+fade; instant under\n   reduced-motion. Color comes ONLY from tokens, via alpha color-mix.\n   Deterministic: the ids come from a ref counter (NO Date.now/Math.random). */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nexport type StyledTone = \"success\" | \"info\" | \"warning\" | \"danger\"\n\n/* Toast genisligi size'a bagli. */\nconst toastWidth: Record<StyledSize, string> = {\n  sm: \"w-64\",\n  md: \"w-72\",\n  lg: \"w-80\",\n  xl: \"w-96\",\n}\n\n/* Tone -> ikon rengi (semantic token). */\nconst toneIconColor: Record<StyledTone, string> = {\n  success: \"text-success\",\n  info: \"text-info\",\n  warning: \"text-warning-soft-foreground\",\n  danger: \"text-danger\",\n}\n\n/* Tone -> ikon bileseni. */\nconst toneIcon: Record<StyledTone, React.ComponentType<{ className?: string }>> = {\n  success: CheckCircle,\n  info: Info,\n  warning: AlertTriangle,\n  danger: X,\n}\n\nconst triggerBtn =\n  \"inline-flex h-9 shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border border-border bg-secondary px-4 text-sm font-medium text-secondary-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst stackClass =\n  \"pointer-events-none fixed bottom-0 right-0 z-50 flex flex-col items-end gap-2 p-4\"\n\nconst closeBtn =\n  \"inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_8%,transparent)] hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst toastBase =\n  \"pointer-events-auto relative flex items-start gap-3 overflow-hidden rounded-xl border border-border p-4 text-sm shadow-lg [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\n/* A slide+fade enter/exit; instant under reduced-motion (opacity only). */\nfunction toastMotion(reduce: boolean | null) {\n  if (reduce) {\n    return {\n      initial: { opacity: 0 },\n      animate: { opacity: 1 },\n      exit: { opacity: 0 },\n      transition: { duration: 0.12 },\n    }\n  }\n  return {\n    initial: { opacity: 0, x: 32, scale: 0.96 },\n    animate: { opacity: 1, x: 0, scale: 1 },\n    exit: { opacity: 0, x: 32, scale: 0.96 },\n    transition: { type: \"spring\" as const, stiffness: 320, damping: 28 },\n  }\n}\n\n/* ---------------------------------------------------------------- SimpleToaster A plain message toast. */\ninterface SimpleItem {\n  id: number\n  message: React.ReactNode\n}\n\nexport function SimpleToaster({\n  className,\n  size = \"md\",\n  label = \"Show toast\",\n}: {\n  className?: string\n  size?: StyledSize\n  label?: React.ReactNode\n}) {\n  const reduce = useReducedMotion()\n  const [items, setItems] = React.useState<SimpleItem[]>([])\n  const counter = React.useRef(0)\n  const timers = React.useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map())\n\n  const dismiss = React.useCallback((id: number) => {\n    setItems((prev) => prev.filter((t) => t.id !== id))\n    const timer = timers.current.get(id)\n    if (timer) {\n      clearTimeout(timer)\n      timers.current.delete(id)\n    }\n  }, [])\n\n  const push = React.useCallback(() => {\n    const id = counter.current++\n    setItems((prev) => [...prev, { id, message: \"Your changes have been saved.\" }])\n    const timer = setTimeout(() => dismiss(id), 4000)\n    timers.current.set(id, timer)\n  }, [dismiss])\n\n  React.useEffect(() => {\n    const map = timers.current\n    return () => {\n      map.forEach((t) => clearTimeout(t))\n      map.clear()\n    }\n  }, [])\n\n  const m = toastMotion(reduce)\n\n  return (\n    <div data-slot=\"styled-sonner\" className={cn(\"inline-flex\", className)}>\n      <button type=\"button\" className={triggerBtn} onClick={push}>\n        {label}\n      </button>\n      <div className={stackClass}>\n        <AnimatePresence initial={false}>\n          {items.map((t) => (\n            <motion.div\n              key={t.id}\n              role=\"status\"\n              className={cn(toastBase, \"bg-popover text-popover-foreground\", toastWidth[size])}\n              initial={m.initial}\n              animate={m.animate}\n              exit={m.exit}\n              transition={m.transition}\n              layout={!reduce}\n            >\n              <div className=\"min-w-0 flex-1 pt-0.5\">{t.message}</div>\n              <button\n                type=\"button\"\n                aria-label=\"Dismiss\"\n                className={closeBtn}\n                onClick={() => dismiss(t.id)}\n              >\n                <X />\n              </button>\n            </motion.div>\n          ))}\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n\n/* ------------------------------------------------------------------ ToneToaster\n   success/info/warning/danger tonlarini eslesen token ikonu ile dolasir. */\ninterface ToneItem {\n  id: number\n  tone: StyledTone\n  message: React.ReactNode\n}\n\nconst toneCycle: { tone: StyledTone; message: string }[] = [\n  { tone: \"success\", message: \"Payment received.\" },\n  { tone: \"info\", message: \"A new update is available.\" },\n  { tone: \"warning\", message: \"Your storage is almost full.\" },\n  { tone: \"danger\", message: \"Could not reach the server.\" },\n]\n\nexport function ToneToaster({\n  className,\n  size = \"md\",\n  label = \"Show toast\",\n}: {\n  className?: string\n  size?: StyledSize\n  label?: React.ReactNode\n}) {\n  const reduce = useReducedMotion()\n  const [items, setItems] = React.useState<ToneItem[]>([])\n  const counter = React.useRef(0)\n  const cursor = React.useRef(0)\n  const timers = React.useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map())\n\n  const dismiss = React.useCallback((id: number) => {\n    setItems((prev) => prev.filter((t) => t.id !== id))\n    const timer = timers.current.get(id)\n    if (timer) {\n      clearTimeout(timer)\n      timers.current.delete(id)\n    }\n  }, [])\n\n  const push = React.useCallback(() => {\n    const id = counter.current++\n    const next = toneCycle[cursor.current % toneCycle.length]\n    cursor.current++\n    setItems((prev) => [...prev, { id, tone: next.tone, message: next.message }])\n    const timer = setTimeout(() => dismiss(id), 4000)\n    timers.current.set(id, timer)\n  }, [dismiss])\n\n  React.useEffect(() => {\n    const map = timers.current\n    return () => {\n      map.forEach((t) => clearTimeout(t))\n      map.clear()\n    }\n  }, [])\n\n  const m = toastMotion(reduce)\n\n  return (\n    <div data-slot=\"styled-sonner\" className={cn(\"inline-flex\", className)}>\n      <button type=\"button\" className={triggerBtn} onClick={push}>\n        {label}\n      </button>\n      <div className={stackClass}>\n        <AnimatePresence initial={false}>\n          {items.map((t) => {\n            const Icon = toneIcon[t.tone]\n            return (\n              <motion.div\n                key={t.id}\n                role=\"status\"\n                data-tone={t.tone}\n                className={cn(\n                  toastBase,\n                  \"bg-popover text-popover-foreground\",\n                  toastWidth[size]\n                )}\n                initial={m.initial}\n                animate={m.animate}\n                exit={m.exit}\n                transition={m.transition}\n                layout={!reduce}\n              >\n                <Icon className={cn(\"mt-0.5\", toneIconColor[t.tone])} />\n                <div className=\"min-w-0 flex-1 pt-0.5\">{t.message}</div>\n                <button\n                  type=\"button\"\n                  aria-label=\"Dismiss\"\n                  className={closeBtn}\n                  onClick={() => dismiss(t.id)}\n                >\n                  <X />\n                </button>\n              </motion.div>\n            )\n          })}\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n\n/* ----------------------------------------------------------------- GlassToaster\n   Buzlu cam toast + backdrop-blur. */\nexport function GlassToaster({\n  className,\n  size = \"md\",\n  label = \"Show toast\",\n}: {\n  className?: string\n  size?: StyledSize\n  label?: React.ReactNode\n}) {\n  const reduce = useReducedMotion()\n  const [items, setItems] = React.useState<SimpleItem[]>([])\n  const counter = React.useRef(0)\n  const timers = React.useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map())\n\n  const dismiss = React.useCallback((id: number) => {\n    setItems((prev) => prev.filter((t) => t.id !== id))\n    const timer = timers.current.get(id)\n    if (timer) {\n      clearTimeout(timer)\n      timers.current.delete(id)\n    }\n  }, [])\n\n  const push = React.useCallback(() => {\n    const id = counter.current++\n    setItems((prev) => [...prev, { id, message: \"Frosted and floating.\" }])\n    const timer = setTimeout(() => dismiss(id), 4000)\n    timers.current.set(id, timer)\n  }, [dismiss])\n\n  React.useEffect(() => {\n    const map = timers.current\n    return () => {\n      map.forEach((t) => clearTimeout(t))\n      map.clear()\n    }\n  }, [])\n\n  const m = toastMotion(reduce)\n\n  return (\n    <div data-slot=\"styled-sonner\" className={cn(\"inline-flex\", className)}>\n      <button type=\"button\" className={triggerBtn} onClick={push}>\n        {label}\n      </button>\n      <div className={stackClass}>\n        <AnimatePresence initial={false}>\n          {items.map((t) => (\n            <motion.div\n              key={t.id}\n              role=\"status\"\n              className={cn(\n                toastBase,\n                \"border-[color-mix(in_oklab,var(--color-foreground)_12%,transparent)] bg-[color-mix(in_oklab,var(--color-popover)_70%,transparent)] text-popover-foreground supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-popover)_45%,transparent)] supports-[backdrop-filter]:backdrop-blur-xl\",\n                toastWidth[size]\n              )}\n              initial={m.initial}\n              animate={m.animate}\n              exit={m.exit}\n              transition={m.transition}\n              layout={!reduce}\n            >\n              <div className=\"min-w-0 flex-1 pt-0.5\">{t.message}</div>\n              <button\n                type=\"button\"\n                aria-label=\"Dismiss\"\n                className={closeBtn}\n                onClick={() => dismiss(t.id)}\n              >\n                <X />\n              </button>\n            </motion.div>\n          ))}\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n\n/* ---------------------------------------------------------------- ActionToaster\n   Baslik + aciklama + token aksiyon butonu + kapat. */\ninterface ActionItem {\n  id: number\n  title: React.ReactNode\n  description: React.ReactNode\n}\n\nexport function ActionToaster({\n  className,\n  size = \"md\",\n  label = \"Show toast\",\n}: {\n  className?: string\n  size?: StyledSize\n  label?: React.ReactNode\n}) {\n  const reduce = useReducedMotion()\n  const [items, setItems] = React.useState<ActionItem[]>([])\n  const counter = React.useRef(0)\n  const timers = React.useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map())\n\n  const dismiss = React.useCallback((id: number) => {\n    setItems((prev) => prev.filter((t) => t.id !== id))\n    const timer = timers.current.get(id)\n    if (timer) {\n      clearTimeout(timer)\n      timers.current.delete(id)\n    }\n  }, [])\n\n  const push = React.useCallback(() => {\n    const id = counter.current++\n    setItems((prev) => [\n      ...prev,\n      { id, title: \"File deleted\", description: \"report.pdf was moved to trash.\" },\n    ])\n    const timer = setTimeout(() => dismiss(id), 6000)\n    timers.current.set(id, timer)\n  }, [dismiss])\n\n  React.useEffect(() => {\n    const map = timers.current\n    return () => {\n      map.forEach((t) => clearTimeout(t))\n      map.clear()\n    }\n  }, [])\n\n  const m = toastMotion(reduce)\n\n  return (\n    <div data-slot=\"styled-sonner\" className={cn(\"inline-flex\", className)}>\n      <button type=\"button\" className={triggerBtn} onClick={push}>\n        {label}\n      </button>\n      <div className={stackClass}>\n        <AnimatePresence initial={false}>\n          {items.map((t) => (\n            <motion.div\n              key={t.id}\n              role=\"status\"\n              className={cn(\n                toastBase,\n                \"flex-col items-stretch bg-popover text-popover-foreground\",\n                toastWidth[size]\n              )}\n              initial={m.initial}\n              animate={m.animate}\n              exit={m.exit}\n              transition={m.transition}\n              layout={!reduce}\n            >\n              <button\n                type=\"button\"\n                aria-label=\"Dismiss\"\n                className={cn(closeBtn, \"absolute right-2 top-2\")}\n                onClick={() => dismiss(t.id)}\n              >\n                <X />\n              </button>\n              <div className=\"pr-6\">\n                <div className=\"font-medium tracking-tight text-foreground\">{t.title}</div>\n                <div className=\"mt-0.5 text-muted-foreground\">{t.description}</div>\n              </div>\n              <div className=\"mt-3 flex justify-end\">\n                <button\n                  type=\"button\"\n                  className=\"inline-flex h-8 select-none items-center justify-center rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-primary)_88%,var(--color-foreground))] focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n                  onClick={() => dismiss(t.id)}\n                >\n                  Undo\n                </button>\n              </div>\n            </motion.div>\n          ))}\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n\n/* --------------------------------------------------------------- PromiseToaster A loading toast that turns into a success toast after a short delay. */\ninterface PromiseItem {\n  id: number\n  state: \"loading\" | \"success\"\n}\n\nexport function PromiseToaster({\n  className,\n  size = \"md\",\n  label = \"Show toast\",\n}: {\n  className?: string\n  size?: StyledSize\n  label?: React.ReactNode\n}) {\n  const reduce = useReducedMotion()\n  const [items, setItems] = React.useState<PromiseItem[]>([])\n  const counter = React.useRef(0)\n  const timers = React.useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map())\n\n  const clearTimer = React.useCallback((id: number) => {\n    const timer = timers.current.get(id)\n    if (timer) {\n      clearTimeout(timer)\n      timers.current.delete(id)\n    }\n  }, [])\n\n  const dismiss = React.useCallback(\n    (id: number) => {\n      setItems((prev) => prev.filter((t) => t.id !== id))\n      clearTimer(id)\n    },\n    [clearTimer]\n  )\n\n  const push = React.useCallback(() => {\n    const id = counter.current++\n    setItems((prev) => [...prev, { id, state: \"loading\" }])\n    const resolveTimer = setTimeout(() => {\n      setItems((prev) => prev.map((t) => (t.id === id ? { ...t, state: \"success\" } : t)))\n      const dismissTimer = setTimeout(() => dismiss(id), 3000)\n      timers.current.set(id, dismissTimer)\n    }, 2000)\n    timers.current.set(id, resolveTimer)\n  }, [dismiss])\n\n  React.useEffect(() => {\n    const map = timers.current\n    return () => {\n      map.forEach((t) => clearTimeout(t))\n      map.clear()\n    }\n  }, [])\n\n  const m = toastMotion(reduce)\n\n  return (\n    <div data-slot=\"styled-sonner\" className={cn(\"inline-flex\", className)}>\n      <button type=\"button\" className={triggerBtn} onClick={push}>\n        {label}\n      </button>\n      <div className={stackClass}>\n        <AnimatePresence initial={false}>\n          {items.map((t) => (\n            <motion.div\n              key={t.id}\n              role=\"status\"\n              data-state={t.state}\n              className={cn(toastBase, \"bg-popover text-popover-foreground\", toastWidth[size])}\n              initial={m.initial}\n              animate={m.animate}\n              exit={m.exit}\n              transition={m.transition}\n              layout={!reduce}\n            >\n              {t.state === \"loading\" ? (\n                <Loader2 className=\"mt-0.5 animate-spin text-muted-foreground\" />\n              ) : (\n                <CheckCircle className={cn(\"mt-0.5\", toneIconColor.success)} />\n              )}\n              <div className=\"min-w-0 flex-1 pt-0.5\">\n                {t.state === \"loading\" ? \"Uploading your file...\" : \"Upload complete.\"}\n              </div>\n              <button\n                type=\"button\"\n                aria-label=\"Dismiss\"\n                className={closeBtn}\n                onClick={() => dismiss(t.id)}\n              >\n                <X />\n              </button>\n            </motion.div>\n          ))}\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/sonner-styled.tsx"
    }
  ],
  "categories": [
    "styled",
    "sonner"
  ],
  "type": "registry:component"
}