{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sonner",
  "title": "Sonner",
  "author": "AlgorizeTech",
  "description": "A stacked toast notifier driven by a module-level function rather than a React context. Use it when you want to fire notifications from outside the component tree.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/SonnerBlock.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { AlertCircle, CheckCircle2, Info, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { type ToastProps, toast, toastState } from \"./sonner-store\";\n\n// ==========================================\n// 3. TOASTER COMPONENT (Mounts at App Root)\n// ==========================================\nexport interface ToasterProps {\n  position?:\n    | \"bottom-right\"\n    | \"bottom-center\"\n    | \"bottom-left\"\n    | \"top-right\"\n    | \"top-center\"\n    | \"top-left\";\n  expand?: boolean;\n}\n\nexport function Toaster({\n  position = \"bottom-right\",\n  expand = false,\n}: ToasterProps) {\n  const [toasts, setToasts] = useState<ToastProps[]>([]);\n  const [isHovered, setIsHovered] = useState(false);\n\n  useEffect(() => {\n    return toastState.subscribe(setToasts);\n  }, []);\n  \n\n  if (toasts.length === 0) return null;\n\n  const positionClasses = {\n    \"bottom-right\": \"bottom-3 right-3 sm:bottom-4 sm:right-4 flex-col-reverse items-end\",\n    \"bottom-center\": \"bottom-3 left-1/2 -translate-x-1/2 sm:bottom-4 flex-col-reverse items-center\",\n    \"bottom-left\": \"bottom-3 left-3 sm:bottom-4 sm:left-4 flex-col-reverse items-start\",\n    \"top-right\": \"top-3 right-3 sm:top-4 sm:right-4 flex-col items-end\",\n    \"top-center\": \"top-3 left-1/2 -translate-x-1/2 sm:top-4 flex-col items-center\",\n    \"top-left\": \"top-3 left-3 sm:top-4 sm:left-4 flex-col items-start\",\n  };\n\n  const isTop = position.startsWith(\"top\");\n\n  return (\n    <div\n      className={cn(\n        \"fixed z-[9999] p-2.5 sm:p-4 flex outline-none pointer-events-none w-[calc(100vw-20px)] max-w-[260px] xs:max-w-[280px] sm:max-w-none sm:w-[356px]\",\n        positionClasses[position],\n      )}\n      onMouseEnter={() => setIsHovered(true)}\n      onMouseLeave={() => setIsHovered(false)}\n    >\n      <div className=\"relative flex w-full flex-col items-center\">\n        {toasts.map((t, index) => {\n          // Math for the stacking effect\n          const isFront = index === 0;\n          const isExpanded = expand || isHovered;\n\n          // Calculate transforms based on position in stack\n          const offset = isExpanded ? index * 10 + index * 56 : index * 12;\n          const scale = isExpanded ? 1 : 1 - index * 0.05;\n          const translateY = isTop ? offset : -offset;\n\n          return (\n            <div\n              key={t.id}\n              className={cn(\n                \"absolute w-full transition-all duration-300 ease-out pointer-events-auto\",\n                isTop ? \"top-0\" : \"bottom-0\",\n                isFront || isExpanded\n                  ? \"opacity-100\"\n                  : \"opacity-0 pointer-events-none\",\n              )}\n              style={{\n                zIndex: 100 - index,\n                transform: `translateY(${translateY}px) scale(${scale})`,\n                transformOrigin: isTop ? \"top center\" : \"bottom center\",\n                opacity: index > 2 && !isExpanded ? 0 : undefined,\n              }}\n            >\n              <ToastCard toast={t} onDismiss={() => toast.dismiss(t.id)} />\n            </div>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\n// ==========================================\n// 4. INDIVIDUAL TOAST CARD\n// ==========================================\nfunction ToastCard({\n  toast,\n  onDismiss,\n}: {\n  toast: ToastProps;\n  isTop?: boolean;\n  onDismiss: () => void;\n}) {\n  const { title, description, type = \"default\", action } = toast;\n\n  const typeStyles = {\n    default:\n      \"bg-popover text-popover-foreground border-primary/20 dark:border-primary/30 shadow-lg\",\n    success:\n      \"bg-emerald-50 dark:bg-emerald-500/10 text-emerald-900 dark:text-emerald-100 border-emerald-200 dark:border-emerald-500/20\",\n    error:\n      \"bg-red-50 dark:bg-red-500/10 text-red-900 dark:text-red-100 border-red-200 dark:border-red-500/20\",\n    info: \"bg-blue-50 dark:bg-blue-500/10 text-blue-900 dark:text-blue-100 border-blue-200 dark:border-blue-500/20\",\n  };\n\n  const IconMap = {\n    default: null,\n    success: (\n      <CheckCircle2 className=\"w-4 h-4 sm:w-5 sm:h-5 text-emerald-600 dark:text-emerald-500 mt-0.5 shrink-0\" />\n    ),\n    error: (\n      <AlertCircle className=\"w-4 h-4 sm:w-5 sm:h-5 text-red-600 dark:text-red-500 mt-0.5 shrink-0\" />\n    ),\n    info: (\n      <Info className=\"w-4 h-4 sm:w-5 sm:h-5 text-blue-600 dark:text-blue-500 mt-0.5 shrink-0\" />\n    ),\n  };\n\n  return (\n    <div\n      className={cn(\n        \"group flex items-start gap-2 sm:gap-3 w-full p-2.5 sm:p-4 rounded-xl border shadow-lg backdrop-blur-md\",\n        typeStyles[type],\n      )}\n    >\n      {IconMap[type]}\n\n      <div className=\"flex flex-col gap-0.5 sm:gap-1 w-full min-w-0 pr-3 sm:pr-0\">\n        <div className=\"font-semibold text-[11px] xs:text-xs sm:text-sm leading-tight\">{title}</div>\n        {description && (\n          <div className=\"text-[10px] xs:text-xs sm:text-sm opacity-80 line-clamp-2\">{description}</div>\n        )}\n\n        {action && (\n          <button\n            onClick={() => {\n              action.onClick();\n              onDismiss();\n            }}\n            className=\"mt-1 sm:mt-2 w-max px-2.5 py-1 text-[10px] sm:text-xs font-semibold rounded-md bg-primary text-primary-foreground hover:opacity-90 transition-opacity\"\n          >\n            {action.label}\n          </button>\n        )}\n      </div>\n\n      <button\n        onClick={onDismiss}\n        className=\"shrink-0 p-1 rounded-md opacity-0 group-hover:opacity-100 hover:bg-foreground/10 transition-all absolute right-2 top-2\"\n        aria-label=\"Close\"\n      >\n        <X className=\"w-4 h-4 opacity-70\" />\n      </button>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/custom/blocks/SonnerBlock.tsx"
    },
    {
      "path": "src/components/custom/blocks/sonner-store.ts",
      "content": "\"use client\";\n\nexport type ToastType = \"default\" | \"success\" | \"error\" | \"info\";\n\nexport interface ToastAction {\n  label: string;\n  onClick: () => void;\n}\n\nexport interface ToastProps {\n  id: string;\n  title: string;\n  description?: string;\n  type?: ToastType;\n  duration?: number;\n  action?: ToastAction;\n}\n\nexport type ToastOptions = Omit<ToastProps, \"id\" | \"title\">;\n\nclass ToastObserver {\n  private toasts: ToastProps[] = [];\n  private listeners: Set<(toasts: ToastProps[]) => void> = new Set();\n\n  subscribe(listener: (toasts: ToastProps[]) => void) {\n    this.listeners.add(listener);\n    return () => {\n      this.listeners.delete(listener);\n    };\n  }\n\n  private notify() {\n    this.listeners.forEach((listener) => listener([...this.toasts]));\n  }\n\n  add(title: string, options?: ToastOptions) {\n    const id = Math.random().toString(36).substring(2, 9);\n    const newToast: ToastProps = { id, title, duration: 4000, ...options };\n\n    // Add to front of array\n    this.toasts = [newToast, ...this.toasts].slice(0, 5); // Max 5 in stack\n    this.notify();\n\n    if (newToast.duration !== Infinity) {\n      setTimeout(() => this.dismiss(id), newToast.duration);\n    }\n    return id;\n  }\n\n  dismiss(id: string) {\n    this.toasts = this.toasts.filter((t) => t.id !== id);\n    this.notify();\n  }\n\n  clear() {\n    this.toasts = [];\n    this.notify();\n  }\n}\n\nexport const toastState = new ToastObserver();\n\nexport const toast = Object.assign(\n  (title: string, options?: ToastOptions) => toastState.add(title, options),\n  {\n    success: (title: string, options?: ToastOptions) =>\n      toastState.add(title, { ...options, type: \"success\" as const }),\n    error: (title: string, options?: ToastOptions) =>\n      toastState.add(title, { ...options, type: \"error\" as const }),\n    info: (title: string, options?: ToastOptions) =>\n      toastState.add(title, { ...options, type: \"info\" as const }),\n    dismiss: (id: string) => toastState.dismiss(id),\n    clear: () => toastState.clear(),\n  },\n);\n",
      "type": "registry:component",
      "target": "components/custom/blocks/sonner-store.ts"
    }
  ],
  "docs": "Named exports: Toaster, toast, toastState. Mount Toaster once, then call `toast()` from anywhere — including non-React code — since state lives in a module singleton. Set `expand` to fan the stack apart on hover. For a context-based API, use `toast`.",
  "categories": [
    "feedback"
  ],
  "type": "registry:component"
}
