{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "notification-center-shadcnui", "type": "registry:component", "title": "Notification Center", "description": "Multi-variant notification stack with accessible announcements, actions, and motion states", "registryDependencies": [ "button" ], "dependencies": [ "framer-motion", "react" ], "files": [ { "path": "@uitripled/react-shadcn/src/components/components/notifications/notification-center.tsx", "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport {\n AlertCircle,\n AlertTriangle,\n CheckCircle,\n ChevronDown,\n Info,\n LucideIcon,\n X,\n} from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\n\ntype NotificationType = \"success\" | \"error\" | \"warning\" | \"info\";\n\ntype NotificationConfig = {\n title: string;\n message: string;\n description: string;\n action: {\n label: string;\n onClick: () => void;\n };\n icon: LucideIcon;\n toneClassName: string;\n};\n\ntype ActiveNotification = {\n id: string;\n type: NotificationType;\n};\n\nconst NOTIFICATION_CONFIGS: Record = {\n success: {\n title: \"Success\",\n message: \"Operation completed successfully\",\n description:\n \"Your changes have been saved to the database. All updates are now live.\",\n action: {\n label: \"View Details\",\n onClick: () => console.log(\"View details\"),\n },\n icon: CheckCircle,\n toneClassName: \"text-green-500\",\n },\n error: {\n title: \"Error Occurred\",\n message: \"Something went wrong\",\n description:\n \"Failed to process your request. Please try again or contact support if the issue persists.\",\n action: { label: \"Retry\", onClick: () => console.log(\"Retry\") },\n icon: AlertCircle,\n toneClassName: \"text-red-500\",\n },\n warning: {\n title: \"Warning\",\n message: \"Please review this action\",\n description:\n \"This action may have unintended consequences. Review the details before proceeding.\",\n action: { label: \"Learn More\", onClick: () => console.log(\"Learn more\") },\n icon: AlertTriangle,\n toneClassName: \"text-yellow-500\",\n },\n info: {\n title: \"Information\",\n message: \"New feature available\",\n description:\n \"Check out our new notification system with expandable details. Click to see more information.\",\n action: { label: \"Explore\", onClick: () => console.log(\"Explore\") },\n icon: Info,\n toneClassName: \"text-blue-500\",\n },\n};\n\nconst BUTTON_CONFIGS: Array<{ type: NotificationType; label: string }> = [\n { type: \"success\", label: \"Success\" },\n { type: \"error\", label: \"Error\" },\n { type: \"warning\", label: \"Warning\" },\n { type: \"info\", label: \"Info\" },\n];\n\nexport function NotificationCenter() {\n const [notifications, setNotifications] = useState([]);\n const prefersReducedMotion = useReducedMotion() ?? false;\n\n const addNotification = useCallback((type: NotificationType) => {\n const id = Math.random().toString(36).slice(2, 9);\n setNotifications((prev) => [...prev, { id, type }]);\n\n window.setTimeout(() => {\n setNotifications((prev) =>\n prev.filter((notification) => notification.id !== id)\n );\n }, 8000);\n }, []);\n\n const removeNotification = useCallback((id: string) => {\n setNotifications((prev) =>\n prev.filter((notification) => notification.id !== id)\n );\n }, []);\n\n return (\n
\n \n
\n \n {notifications.map((notification) => {\n const config = NOTIFICATION_CONFIGS[notification.type];\n\n return (\n removeNotification(notification.id)}\n prefersReducedMotion={prefersReducedMotion}\n />\n );\n })}\n \n
\n
\n\n
\n
\n {BUTTON_CONFIGS.map(({ type, label }) => (\n addNotification(type)}\n whileHover={{ scale: prefersReducedMotion ? 1 : 1.02 }}\n whileTap={{ scale: prefersReducedMotion ? 1 : 0.98 }}\n className=\"relative overflow-hidden rounded-2xl border border-border/50 bg-background/60 p-4 text-left transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background sm:p-5\"\n >\n
\n
\n \n \n {label}\n \n
\n \n ))}\n
\n
\n \n );\n}\n\ntype NotificationBarProps = {\n config: NotificationConfig;\n type: NotificationType;\n notificationId: string;\n onDismiss: () => void;\n prefersReducedMotion: boolean;\n};\n\nfunction NotificationBar({\n config,\n type,\n notificationId,\n onDismiss,\n prefersReducedMotion,\n}: NotificationBarProps) {\n const [isExpanded, setIsExpanded] = useState(false);\n const {\n action,\n description,\n icon: Icon,\n message,\n title,\n toneClassName,\n } = config;\n\n return (\n \n \n \n \n \n\n
\n
\n
\n

{title}

\n

{message}

\n
\n setIsExpanded((prev) => !prev)}\n aria-expanded={isExpanded}\n aria-controls={`notification-details-${notificationId}`}\n whileHover={{ scale: prefersReducedMotion ? 1 : 1.05 }}\n whileTap={{ scale: prefersReducedMotion ? 1 : 0.95 }}\n className=\"flex h-8 w-8 items-center justify-center rounded-full border border-border/60 bg-background/40 text-foreground/60 transition-colors hover:text-foreground\"\n >\n \n \n \n \n {isExpanded ? \"Hide details\" : \"Show details\"}\n \n \n
\n \n {isExpanded && (\n \n
\n

{description}

\n
\n \n {action.label}\n \n {\n console.log(\"Remind me later\");\n onDismiss();\n }}\n >\n Remind me later\n \n
\n
\n \n )}\n
\n
\n\n \n \n \n
\n \n );\n}\n\ntype ButtonIconProps = {\n type: NotificationType;\n};\n\nfunction ButtonIcon({ type }: ButtonIconProps) {\n const Icon = NOTIFICATION_CONFIGS[type].icon;\n const prefersReducedMotion = useReducedMotion() ?? false;\n\n return (\n \n \n \n );\n}\n", "type": "registry:component", "target": "components/uitripled/notification-center-shadcnui.tsx" } ] }