{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "specials-animated-toast",
  "type": "registry:component",
  "title": "Animated Toast",
  "description": "Detailed toast notification with slide-in animation, icon, and auto-dismiss progress bar",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/new-york/animations/specials/animated-toast.tsx",
      "content": "\"use client\";\nimport * as React from \"react\";\nimport * as motion from \"motion/react-client\";\nimport type { Easing } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface AnimatedToastProps {\n    message: string;\n    description?: string;\n    type?: \"default\" | \"success\" | \"error\" | \"info\" | \"warning\";\n    duration?: number;\n    onClose?: () => void;\n    className?: string;\n}\n\nexport function AnimatedToast({\n    message,\n    description,\n    type = \"default\",\n    duration = 4000,\n    onClose,\n    className,\n}: AnimatedToastProps) {\n    const [isVisible, setIsVisible] = React.useState(true);\n    const [progress, setProgress] = React.useState(100);\n\n    React.useEffect(() => {\n        const startTime = Date.now();\n        const progressInterval = setInterval(() => {\n            const elapsed = Date.now() - startTime;\n            const remaining = Math.max(0, 100 - (elapsed / duration) * 100);\n            setProgress(remaining);\n        }, 16);\n\n        const timer = setTimeout(() => {\n            setIsVisible(false);\n            setTimeout(() => onClose?.(), 300);\n        }, duration);\n\n        return () => {\n            clearInterval(progressInterval);\n            clearTimeout(timer);\n        };\n    }, [duration, onClose]);\n\n    const typeConfig = {\n        default: {\n            iconBg: \"bg-zinc-100 dark:bg-zinc-800\",\n            iconColor: \"text-zinc-900 dark:text-zinc-100\",\n            accentColor: \"bg-zinc-400 dark:bg-zinc-600\",\n        },\n        success: {\n            iconBg: \"bg-emerald-100 dark:bg-emerald-950/50\",\n            iconColor: \"text-emerald-600 dark:text-emerald-400\",\n            accentColor: \"bg-emerald-500 dark:bg-emerald-500\",\n        },\n        error: {\n            iconBg: \"bg-red-100 dark:bg-red-950/50\",\n            iconColor: \"text-red-600 dark:text-red-400\",\n            accentColor: \"bg-red-500 dark:bg-red-500\",\n        },\n        info: {\n            iconBg: \"bg-blue-100 dark:bg-blue-950/50\",\n            iconColor: \"text-blue-600 dark:text-blue-400\",\n            accentColor: \"bg-blue-500 dark:bg-blue-500\",\n        },\n        warning: {\n            iconBg: \"bg-amber-100 dark:bg-amber-950/50\",\n            iconColor: \"text-amber-600 dark:text-amber-400\",\n            accentColor: \"bg-amber-500 dark:bg-amber-500\",\n        },\n    };\n\n    const config = typeConfig[type];\n\n    return (\n        <motion.div\n            className={cn(\n                \"pointer-events-auto relative w-[356px] overflow-hidden rounded-lg shadow-lg\",\n                \"border bg-white dark:bg-zinc-900\",\n                \"border-zinc-200 dark:border-zinc-800\",\n                className\n            )}\n            initial={{ opacity: 0, y: -50, scale: 0.95 }}\n            animate={\n                isVisible\n                    ? {\n                        opacity: 1,\n                        y: 0,\n                        scale: 1,\n                    }\n                    : {\n                        opacity: 0,\n                        y: -20,\n                        scale: 0.95,\n                    }\n            }\n            transition={{\n                type: \"spring\",\n                stiffness: 500,\n                damping: 30,\n            }}\n        >\n            <div className=\"flex items-start gap-3 p-4\">\n                {/* Animated Icon */}\n                {type !== \"default\" && (\n                    <motion.div\n                        className={cn(\n                            \"flex h-8 w-8 shrink-0 items-center justify-center rounded-full\",\n                            config.iconBg\n                        )}\n                        initial={{ scale: 0, rotate: -90 }}\n                        animate={{ scale: 1, rotate: 0 }}\n                        transition={{\n                            type: \"spring\",\n                            stiffness: 400,\n                            damping: 20,\n                            delay: 0.1,\n                        }}\n                    >\n                        <div className={config.iconColor}>\n                            {type === \"success\" && (\n                                <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n                                    <motion.path\n                                        d=\"M13.5 4L6 11.5L2.5 8\"\n                                        stroke=\"currentColor\"\n                                        strokeWidth=\"2\"\n                                        strokeLinecap=\"round\"\n                                        strokeLinejoin=\"round\"\n                                        initial={{ pathLength: 0 }}\n                                        animate={{ pathLength: 1 }}\n                                        transition={{\n                                            duration: 0.4,\n                                            delay: 0.2,\n                                            ease: [0.16, 1, 0.3, 1] as Easing,\n                                        }}\n                                    />\n                                </svg>\n                            )}\n                            {type === \"error\" && (\n                                <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n                                    <motion.path\n                                        d=\"M12 4L4 12M4 4L12 12\"\n                                        stroke=\"currentColor\"\n                                        strokeWidth=\"2\"\n                                        strokeLinecap=\"round\"\n                                        initial={{ pathLength: 0 }}\n                                        animate={{ pathLength: 1 }}\n                                        transition={{\n                                            duration: 0.3,\n                                            delay: 0.2,\n                                            ease: \"easeOut\" as Easing,\n                                        }}\n                                    />\n                                </svg>\n                            )}\n                            {type === \"info\" && (\n                                <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n                                    <circle\n                                        cx=\"8\"\n                                        cy=\"8\"\n                                        r=\"6\"\n                                        stroke=\"currentColor\"\n                                        strokeWidth=\"2\"\n                                    />\n                                    <motion.path\n                                        d=\"M8 7V11M8 5H8.01\"\n                                        stroke=\"currentColor\"\n                                        strokeWidth=\"2\"\n                                        strokeLinecap=\"round\"\n                                        initial={{ pathLength: 0 }}\n                                        animate={{ pathLength: 1 }}\n                                        transition={{\n                                            duration: 0.4,\n                                            delay: 0.2,\n                                            ease: [0.16, 1, 0.3, 1] as Easing,\n                                        }}\n                                    />\n                                </svg>\n                            )}\n                            {type === \"warning\" && (\n                                <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n                                    <path\n                                        d=\"M8 2L14 13H2L8 2Z\"\n                                        stroke=\"currentColor\"\n                                        strokeWidth=\"2\"\n                                        strokeLinecap=\"round\"\n                                        strokeLinejoin=\"round\"\n                                    />\n                                    <motion.path\n                                        d=\"M8 6V9M8 11H8.01\"\n                                        stroke=\"currentColor\"\n                                        strokeWidth=\"2\"\n                                        strokeLinecap=\"round\"\n                                        initial={{ pathLength: 0 }}\n                                        animate={{ pathLength: 1 }}\n                                        transition={{\n                                            duration: 0.4,\n                                            delay: 0.2,\n                                            ease: [0.16, 1, 0.3, 1] as Easing,\n                                        }}\n                                    />\n                                </svg>\n                            )}\n                        </div>\n                    </motion.div>\n                )}\n\n                {/* Content */}\n                <div className=\"flex-1 pt-0.5\">\n                    <motion.p\n                        className=\"text-sm font-medium text-zinc-900 dark:text-zinc-100\"\n                        initial={{ opacity: 0, y: 5 }}\n                        animate={{ opacity: 1, y: 0 }}\n                        transition={{\n                            duration: 0.3,\n                            delay: 0.1,\n                            ease: [0.16, 1, 0.3, 1] as Easing,\n                        }}\n                    >\n                        {message}\n                    </motion.p>\n                    {description && (\n                        <motion.p\n                            className=\"mt-1 text-sm text-zinc-600 dark:text-zinc-400\"\n                            initial={{ opacity: 0, y: 5 }}\n                            animate={{ opacity: 1, y: 0 }}\n                            transition={{\n                                duration: 0.3,\n                                delay: 0.2,\n                                ease: [0.16, 1, 0.3, 1] as Easing,\n                            }}\n                        >\n                            {description}\n                        </motion.p>\n                    )}\n                </div>\n\n                {/* Close button */}\n                <motion.button\n                    onClick={() => {\n                        setIsVisible(false);\n                        setTimeout(() => onClose?.(), 300);\n                    }}\n                    className=\"flex h-5 w-5 shrink-0 items-center justify-center rounded text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300\"\n                    whileHover={{ scale: 1.1 }}\n                    whileTap={{ scale: 0.9 }}\n                    initial={{ opacity: 0 }}\n                    animate={{ opacity: 1 }}\n                    transition={{ delay: 0.2 }}\n                >\n                    <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n                        <path\n                            d=\"M9 3L3 9M3 3L9 9\"\n                            stroke=\"currentColor\"\n                            strokeWidth=\"1.5\"\n                            strokeLinecap=\"round\"\n                        />\n                    </svg>\n                </motion.button>\n            </div>\n\n            {/* Progress Bar */}\n            <div className=\"h-0.5 w-full bg-zinc-100 dark:bg-zinc-800\">\n                <motion.div\n                    className={cn(\"h-full\", config.accentColor)}\n                    initial={{ width: \"100%\" }}\n                    animate={{ width: `${progress}%` }}\n                    transition={{ duration: 0.05, ease: \"linear\" as Easing }}\n                />\n            </div>\n        </motion.div>\n    );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "registry/new-york/animations/specials/toast-trigger.tsx",
      "content": "\"use client\";\nimport * as React from \"react\";\nimport * as motion from \"motion/react-client\";\nimport type { Easing } from \"motion/react\";\nimport { AnimatedToast } from \"./animated-toast\";\n\ninterface Toast {\n    id: number;\n    message: string;\n    description?: string;\n    type?: \"default\" | \"success\" | \"error\" | \"info\" | \"warning\";\n}\n\nconst ToastContext = React.createContext<{\n    addToast: (toast: Omit<Toast, \"id\">) => void;\n} | null>(null);\n\nexport function useToast() {\n    const context = React.useContext(ToastContext);\n    if (!context) {\n        throw new Error(\"useToast must be used within ToastProvider\");\n    }\n    return context;\n}\n\nexport function ToastProvider({\n    children,\n    maxToasts = 5\n}: {\n    children: React.ReactNode;\n    maxToasts?: number\n}) {\n    const [toasts, setToasts] = React.useState<Toast[]>([]);\n    const nextId = React.useRef(0);\n\n    const addToast = React.useCallback((toast: Omit<Toast, \"id\">) => {\n        const id = nextId.current++;\n        setToasts((prev) => {\n            const newToasts = [...prev, { ...toast, id }];\n            return newToasts.slice(-maxToasts);\n        });\n    }, [maxToasts]);\n\n    const removeToast = React.useCallback((id: number) => {\n        setToasts((prev) => prev.filter((t) => t.id !== id));\n    }, []);\n\n    return (\n        <ToastContext.Provider value={{ addToast }}>\n            {children}\n            <div className=\"pointer-events-none fixed top-0 right-0 z-[100] flex flex-col items-end p-4\">\n                <div className=\"relative\">\n                    {toasts.map((toast, index) => {\n                        // Bottom toast = index 0, top toast = last index\n                        // We want top toast fully visible, others slightly behind\n                        const fromTop = toasts.length - 1 - index;\n                        const isTopToast = fromTop === 0;\n\n                        return (\n                            <motion.div\n                                key={toast.id}\n                                layout\n                                className=\"absolute top-0 right-0\"\n                                initial={{ opacity: 0, y: -100, scale: 0.95 }}\n                                animate={{\n                                    y: fromTop * 4,\n                                    scale: 1 - fromTop * 0.05,\n                                    opacity: isTopToast ? 1 : 0.75,\n                                }}\n                                exit={{\n                                    opacity: 0,\n                                    y: -20,\n                                    scale: 0.9,\n                                    transition: { duration: 0.2 }\n                                }}\n                                transition={{\n                                    duration: 0.2,\n                                    ease: [0.16, 1, 0.3, 1] as Easing,\n                                }}\n                                style={{\n                                    zIndex: 100 + index,\n                                    pointerEvents: isTopToast ? \"auto\" : \"none\",\n                                }}\n                            >\n                                <AnimatedToast\n                                    message={toast.message}\n                                    description={toast.description}\n                                    type={toast.type}\n                                    duration={4000}\n                                    onClose={() => removeToast(toast.id)}\n                                />\n                            </motion.div>\n                        );\n                    })}\n                </div>\n            </div>\n        </ToastContext.Provider>\n    );\n}\n\nexport interface ToastTriggerProps {\n    buttonText?: string;\n    toastMessage?: string;\n    toastDescription?: string;\n    toastType?: \"default\" | \"success\" | \"error\" | \"info\" | \"warning\";\n    className?: string;\n}\n\nexport function ToastTrigger({\n    buttonText = \"Show Toast\",\n    toastMessage = \"Event has been created\",\n    toastDescription,\n    toastType = \"default\",\n    className,\n}: ToastTriggerProps) {\n    const context = React.useContext(ToastContext);\n    const [showToast, setShowToast] = React.useState(false);\n    const [toastKey, setToastKey] = React.useState(0);\n\n    const handleClick = () => {\n        if (context) {\n            context.addToast({\n                message: toastMessage,\n                description: toastDescription,\n                type: toastType,\n            });\n        } else {\n            setShowToast(true);\n            setToastKey((prev) => prev + 1);\n        }\n    };\n\n    const handleToastClose = () => {\n        setShowToast(false);\n    };\n\n    const buttonStyles = {\n        default: \"bg-white text-zinc-900 hover:bg-zinc-100 border border-zinc-300\",\n        success: \"bg-emerald-500 text-white hover:bg-emerald-600\",\n        error: \"bg-red-500 text-white hover:bg-red-600\",\n        info: \"bg-blue-500 text-white hover:bg-blue-600\",\n        warning: \"bg-amber-500 text-white hover:bg-amber-600\",\n    };\n\n    return (\n        <>\n            <motion.button\n                onClick={handleClick}\n                className={`rounded-md px-4 py-2 text-sm font-medium shadow-sm transition-colors ${buttonStyles[toastType]} ${className || \"\"}`}\n                whileHover={{ scale: 1.02 }}\n                whileTap={{ scale: 0.98 }}\n            >\n                {buttonText}\n            </motion.button>\n\n            {!context && showToast && (\n                <div className=\"pointer-events-none fixed top-4 right-4 z-50\">\n                    <AnimatedToast\n                        key={toastKey}\n                        message={toastMessage}\n                        description={toastDescription}\n                        type={toastType}\n                        duration={4000}\n                        onClose={handleToastClose}\n                    />\n                </div>\n            )}\n        </>\n    );\n}\n",
      "type": "registry:component"
    }
  ]
}