{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"toast","type":"registry:ui","title":"Toast","description":"Toast from react-fancy","package":"react-fancy","dependencies":["lucide-react"],"registryDependencies":["portal"],"files":[{"path":"components\/fancy\/toast\/Toast.context.ts","content":"import { createContext, useContext } from \"react\";\r\nimport type { ToastContextValue } from \".\/Toast.types\";\r\n\r\nexport const ToastContext = createContext<ToastContextValue | null>(null);\r\n\r\nexport function useToast(): ToastContextValue {\r\n  const ctx = useContext(ToastContext);\r\n  if (!ctx) {\r\n    throw new Error(\"useToast must be used within a <Toast.Provider>\");\r\n  }\r\n  return ctx;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/toast\/Toast.context.ts"},{"path":"components\/fancy\/toast\/Toast.tsx","content":"import { useCallback, useMemo, useRef, useState } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { Portal } from \"..\/Portal\";\r\nimport { ToastContext } from \".\/Toast.context\";\r\nimport { ToastItem } from \".\/ToastItem\";\r\nimport type { ToastData, ToastProviderProps } from \".\/Toast.types\";\r\n\r\nconst POSITION_STYLES = {\r\n  \"top-right\": \"top-4 right-4\",\r\n  \"top-left\": \"top-4 left-4\",\r\n  \"bottom-right\": \"bottom-4 right-4\",\r\n  \"bottom-left\": \"bottom-4 left-4\",\r\n} as const;\r\n\r\nfunction ToastProvider({\r\n  children,\r\n  position = \"bottom-right\",\r\n  maxToasts = 5,\r\n}: ToastProviderProps) {\r\n  const [toasts, setToasts] = useState<ToastData[]>([]);\r\n  const idCounter = useRef(0);\r\n\r\n  const toast = useCallback(\r\n    (data: Omit<ToastData, \"id\">) => {\r\n      const id = `toast-${++idCounter.current}`;\r\n      setToasts((prev) => [...prev.slice(-(maxToasts - 1)), { ...data, id }]);\r\n      return id;\r\n    },\r\n    [maxToasts],\r\n  );\r\n\r\n  const dismiss = useCallback((id: string) => {\r\n    setToasts((prev) => prev.filter((t) => t.id !== id));\r\n  }, []);\r\n\r\n  const ctx = useMemo(() => ({ toasts, toast, dismiss }), [toasts, toast, dismiss]);\r\n\r\n  return (\r\n    <ToastContext.Provider value={ctx}>\r\n      {children}\r\n      <Portal>\r\n        <div\r\n          data-react-fancy-toast=\"\"\r\n          className={cn(\r\n            \"fixed z-[100] flex flex-col gap-2 pointer-events-none\",\r\n            POSITION_STYLES[position],\r\n          )}\r\n        >\r\n          {toasts.map((t) => (\r\n            <ToastItem key={t.id} data={t} onDismiss={dismiss} \/>\r\n          ))}\r\n        <\/div>\r\n      <\/Portal>\r\n    <\/ToastContext.Provider>\r\n  );\r\n}\r\n\r\nexport const Toast = {\r\n  Provider: ToastProvider,\r\n};\r\n","type":"registry:ui","target":"components\/fancy\/toast\/Toast.tsx"},{"path":"components\/fancy\/toast\/Toast.types.ts","content":"import type { ReactNode } from \"react\";\r\n\r\nexport type ToastVariant = \"default\" | \"success\" | \"error\" | \"warning\" | \"info\";\r\nexport type ToastPosition =\r\n  | \"top-right\"\r\n  | \"top-left\"\r\n  | \"bottom-right\"\r\n  | \"bottom-left\";\r\n\r\nexport interface ToastData {\r\n  id: string;\r\n  title: string;\r\n  description?: string;\r\n  variant?: ToastVariant;\r\n  duration?: number;\r\n}\r\n\r\nexport interface ToastContextValue {\r\n  toasts: ToastData[];\r\n  toast: (data: Omit<ToastData, \"id\">) => string;\r\n  dismiss: (id: string) => void;\r\n}\r\n\r\nexport interface ToastProviderProps {\r\n  children: ReactNode;\r\n  position?: ToastPosition;\r\n  maxToasts?: number;\r\n}\r\n\r\nexport interface ToastItemProps {\r\n  data: ToastData;\r\n  onDismiss: (id: string) => void;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/toast\/Toast.types.ts"},{"path":"components\/fancy\/toast\/ToastItem.tsx","content":"import { useEffect } from \"react\";\r\nimport { AlertTriangle, Check, Info, X, XCircle } from \"lucide-react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport type { ToastItemProps } from \".\/Toast.types\";\r\n\r\nconst VARIANT_STYLES = {\r\n  default:\r\n    \"border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900\",\r\n  success:\r\n    \"border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-950\",\r\n  error:\r\n    \"border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950\",\r\n  warning:\r\n    \"border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950\",\r\n  info: \"border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950\",\r\n} as const;\r\n\r\nconst VARIANT_ICON_COLORS = {\r\n  default: \"text-zinc-400\",\r\n  success: \"text-emerald-500\",\r\n  error: \"text-red-500\",\r\n  warning: \"text-amber-500\",\r\n  info: \"text-blue-500\",\r\n} as const;\r\n\r\nexport function ToastItem({ data, onDismiss }: ToastItemProps) {\r\n  const { id, title, description, variant = \"default\", duration = 5000 } = data;\r\n\r\n  useEffect(() => {\r\n    if (duration <= 0) return;\r\n    const timer = setTimeout(() => onDismiss(id), duration);\r\n    return () => clearTimeout(timer);\r\n  }, [id, duration, onDismiss]);\r\n\r\n  return (\r\n    <div\r\n      data-react-fancy-toast-item=\"\"\r\n      role=\"alert\"\r\n      className={cn(\r\n        \"fancy-toast-in pointer-events-auto w-80 rounded-xl border p-4 shadow-lg\",\r\n        VARIANT_STYLES[variant],\r\n      )}\r\n    >\r\n      <div className=\"flex items-start gap-3\">\r\n        <div className={cn(\"mt-0.5 shrink-0\", VARIANT_ICON_COLORS[variant])}>\r\n          {variant === \"success\" && <Check size={16} \/>}\r\n          {variant === \"error\" && <XCircle size={16} \/>}\r\n          {variant === \"warning\" && <AlertTriangle size={16} \/>}\r\n          {variant === \"info\" && <Info size={16} \/>}\r\n        <\/div>\r\n        <div className=\"flex-1 min-w-0\">\r\n          <p className=\"text-sm font-medium text-zinc-900 dark:text-zinc-100\">{title}<\/p>\r\n          {description && (\r\n            <p className=\"mt-1 text-xs text-zinc-500 dark:text-zinc-400\">\r\n              {description}\r\n            <\/p>\r\n          )}\r\n        <\/div>\r\n        <button\r\n          type=\"button\"\r\n          onClick={() => onDismiss(id)}\r\n          className=\"shrink-0 rounded p-0.5 text-zinc-400 transition-colors hover:text-zinc-600 dark:hover:text-zinc-300\"\r\n          aria-label=\"Dismiss\"\r\n        >\r\n          <X size={14} \/>\r\n        <\/button>\r\n      <\/div>\r\n    <\/div>\r\n  );\r\n}\r\n\r\nToastItem.displayName = \"ToastItem\";\r\n","type":"registry:ui","target":"components\/fancy\/toast\/ToastItem.tsx"},{"path":"components\/fancy\/toast\/index.ts","content":"export { Toast } from \".\/Toast\";\r\nexport { useToast } from \".\/Toast.context\";\r\nexport type {\r\n  ToastData,\r\n  ToastVariant,\r\n  ToastPosition,\r\n  ToastProviderProps,\r\n  ToastItemProps,\r\n  ToastContextValue,\r\n} from \".\/Toast.types\";\r\n","type":"registry:ui","target":"components\/fancy\/toast\/index.ts"}]}