{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"toaster","title":"Toaster","description":"Headless toast notifications powered by Sonner, with variant-aware styling and custom action/cancel controls","dependencies":["@hugeicons/core-free-icons","@hugeicons/react","next-themes","sonner"],"registryDependencies":["button"],"files":[{"path":"components/nexus-ui/toaster.tsx","content":"\"use client\";\n\nimport {\n  Alert02Icon,\n  Cancel01Icon,\n  CheckmarkCircle01Icon,\n  InformationCircleIcon,\n  Loading03Icon,\n  OctagonXIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport type * as React from \"react\";\nimport { useTheme } from \"next-themes\";\nimport {\n  Toaster as Sonner,\n  toast as sonnerToast,\n  type ExternalToast,\n  type ToasterProps,\n} from \"sonner\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\n\ntype ToastVariant =\n  | \"default\"\n  | \"success\"\n  | \"info\"\n  | \"warning\"\n  | \"error\"\n  | \"loading\";\n\ntype ToastAction = {\n  label: React.ReactNode;\n  onClick?: () => void;\n};\n\ntype ToastContent = {\n  title: React.ReactNode;\n  description?: React.ReactNode;\n  variant?: ToastVariant;\n  icon?: React.ReactNode | null;\n  action?: ToastAction;\n  cancel?: ToastAction;\n} & Omit<\n  ExternalToast,\n  \"icon\" | \"classNames\" | \"unstyled\" | \"action\" | \"cancel\"\n>;\n\nconst variantIconMap: Partial<Record<ToastVariant, React.ReactNode>> = {\n  success: (\n    <HugeiconsIcon\n      icon={CheckmarkCircle01Icon}\n      strokeWidth={2}\n      className=\"size-4.5\"\n    />\n  ),\n  info: (\n    <HugeiconsIcon\n      icon={InformationCircleIcon}\n      strokeWidth={2}\n      className=\"size-4.5\"\n    />\n  ),\n  warning: (\n    <HugeiconsIcon icon={Alert02Icon} strokeWidth={2} className=\"size-4.5\" />\n  ),\n  error: (\n    <HugeiconsIcon icon={OctagonXIcon} strokeWidth={2} className=\"size-4.5\" />\n  ),\n  loading: (\n    <HugeiconsIcon\n      icon={Loading03Icon}\n      strokeWidth={2}\n      className=\"size-4.5 animate-spin\"\n    />\n  ),\n};\n\nconst toast = {\n  custom: (content: ToastContent) => {\n    const {\n      title,\n      description,\n      variant,\n      icon,\n      action,\n      cancel,\n      dismissible,\n      closeButton,\n      ...sonnerOptions\n    } = content;\n    return sonnerToast.custom(\n      (id) => (\n        <ToastCard\n          id={id}\n          title={title}\n          description={description}\n          variant={variant}\n          icon={icon}\n          action={action}\n          cancel={cancel}\n          dismissible={dismissible}\n          closeButton={closeButton}\n        />\n      ),\n      {\n        ...sonnerOptions,\n        dismissible,\n        closeButton,\n      },\n    );\n  },\n  default: (\n    title: React.ReactNode,\n    options?: Omit<ToastContent, \"title\" | \"variant\">,\n  ) => toast.custom({ title, variant: \"default\", ...options }),\n  success: (\n    title: React.ReactNode,\n    options?: Omit<ToastContent, \"title\" | \"variant\">,\n  ) => toast.custom({ title, variant: \"success\", ...options }),\n  info: (\n    title: React.ReactNode,\n    options?: Omit<ToastContent, \"title\" | \"variant\">,\n  ) => toast.custom({ title, variant: \"info\", ...options }),\n  warning: (\n    title: React.ReactNode,\n    options?: Omit<ToastContent, \"title\" | \"variant\">,\n  ) => toast.custom({ title, variant: \"warning\", ...options }),\n  error: (\n    title: React.ReactNode,\n    options?: Omit<ToastContent, \"title\" | \"variant\">,\n  ) => toast.custom({ title, variant: \"error\", ...options }),\n  loading: (\n    title: React.ReactNode,\n    options?: Omit<ToastContent, \"title\" | \"variant\">,\n  ) => toast.custom({ title, variant: \"loading\", ...options }),\n  dismiss: sonnerToast.dismiss,\n};\n\nfunction ToastCard({\n  id,\n  title,\n  description,\n  icon,\n  action,\n  cancel,\n  dismissible = true,\n  closeButton = true,\n  variant = \"default\",\n}: ToastContent & { id: string | number }) {\n  const resolvedIcon = icon === null ? null : (icon ?? variantIconMap[variant]);\n  const canDismiss = dismissible !== false;\n\n  return (\n    <div\n      className={cn(\n        \"relative flex w-full items-start justify-between gap-2 rounded-lg px-4 py-3 shadow-[0_8px_10px_rgb(0,0,0,0.02)] transition-colors lg:w-90 xl:w-120\",\n        \"border border-(--toast-color)/5 bg-(--toast-bg) text-(--toast-color)\",\n        \"dark:bg-(--toast-bg)\",\n        \"[--toast-bg:var(--popover)] [--toast-color:var(--popover-foreground)]\",\n        \"data-[variant=default]:[--toast-bg:var(--popover)] data-[variant=default]:[--toast-color:var(--primary)]\",\n        \"data-[variant=success]:[--toast-bg:#F0FDF4] data-[variant=success]:[--toast-color:#16A34A] data-[variant=success]:dark:[--toast-bg:#17221C] data-[variant=success]:dark:[--toast-color:#15803D]\",\n        \"data-[variant=info]:[--toast-bg:#EFF6FF] data-[variant=info]:[--toast-color:#2563EB] data-[variant=info]:dark:[--toast-bg:#181D28] data-[variant=info]:dark:[--toast-color:#1D4ED8]\",\n        \"data-[variant=warning]:[--toast-bg:#FEFCE8] data-[variant=warning]:[--toast-color:#CA8A04] data-[variant=warning]:dark:[--toast-bg:#252015] data-[variant=warning]:dark:[--toast-color:#CA8A04]\",\n        \"data-[variant=error]:[--toast-bg:#FEF2F2] data-[variant=error]:[--toast-color:#DC2626] data-[variant=error]:dark:[--toast-bg:#271818] data-[variant=error]:dark:[--toast-color:#B91C1C]\",\n      )}\n      data-variant={variant}\n    >\n      {resolvedIcon ? (\n        <div className=\"flex size-6 shrink-0 items-center justify-center text-(--toast-color)\">\n          {resolvedIcon}\n        </div>\n      ) : null}\n\n      <div className=\"flex w-full flex-col gap-0.25\">\n        <span className=\"text-sm leading-6 font-medium text-(--toast-color)\">\n          {title}\n        </span>\n        {description ? (\n          <div\n            className=\"mt-0 text-sm leading-5.5 font-[350] text-(--toast-color) data-[variant=default]:text-muted-foreground\"\n            data-variant={variant}\n          >\n            {description}\n          </div>\n        ) : null}\n\n        <div className=\"flex items-center gap-1.5\">\n          {action ? (\n            <Button\n              type=\"button\"\n              variant=\"default\"\n              size=\"sm\"\n              className={cn(\n                \"mt-2 inline-flex w-fit cursor-pointer items-center justify-center rounded-full text-[13px] font-[450] transition-colors\",\n                \"bg-(--toast-color) text-(--toast-bg) hover:bg-(--toast-color)/90 hover:text-(--toast-bg)\",\n              )}\n              onClick={() => {\n                action.onClick?.();\n                if (canDismiss) sonnerToast.dismiss(id);\n              }}\n            >\n              {action.label}\n            </Button>\n          ) : null}\n\n          {cancel ? (\n            <Button\n              type=\"button\"\n              variant=\"outline\"\n              size=\"sm\"\n              className={cn(\n                \"mt-2 inline-flex w-fit cursor-pointer items-center justify-center rounded-full text-[13px] font-[450] transition-colors\",\n                \"border border-(--toast-color)/10 text-(--toast-color) hover:bg-(--toast-color)/5 dark:hover:bg-(--toast-color)/10\",\n              )}\n              onClick={() => {\n                cancel.onClick?.();\n                if (canDismiss) sonnerToast.dismiss(id);\n              }}\n            >\n              {cancel.label}\n            </Button>\n          ) : null}\n        </div>\n      </div>\n\n      {canDismiss && closeButton !== false ? (\n        <Button\n          type=\"button\"\n          variant=\"ghost\"\n          size=\"icon-sm\"\n          aria-label=\"Close notification\"\n          className={cn(\n            \"inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full text-(--toast-color)/70 transition-colors\",\n            \"hover:bg-(--toast-color)/10 hover:text-(--toast-color) focus-visible:ring-2 focus-visible:ring-(--toast-color)/35 dark:hover:bg-(--toast-color)/10 dark:hover:text-(--toast-color)\",\n          )}\n          onClick={() => sonnerToast.dismiss(id)}\n        >\n          <HugeiconsIcon\n            icon={Cancel01Icon}\n            strokeWidth={2}\n            className=\"size-4\"\n          />\n        </Button>\n      ) : null}\n    </div>\n  );\n}\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme();\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      toastOptions={{ unstyled: true }}\n      {...props}\n    />\n  );\n};\n\nexport { toast, Toaster };\n","type":"registry:file","target":"~/components/nexus-ui/toaster.tsx"}],"type":"registry:ui"}