UNPKG

lightswind

Version:

A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.

314 lines (311 loc) 17.2 kB
"use strict"; "use client"; Object.defineProperty(exports, "__esModule", { value: true }); const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const react_flip_toolkit_1 = require("react-flip-toolkit"); const lucide_react_1 = require("lucide-react"); const utils_1 = require("@/components/lib/utils"); const uuidv4 = () => Math.random().toString(36).substring(2, 15); const defaultMessages = [ "Just completed a task! ✅", "New feature deployed 🚀", "Check out our latest update 📱", "Server responded with 200 OK ✨", "Background job finished 🔄", "Data synced successfully! 💾", "User logged in successfully 👋", "Payment processed 💳", "Email sent successfully 📧", "Backup completed 🛡️", ]; const Avatar = ({ user, showAvatar }) => { if (!showAvatar) return null; return ((0, jsx_runtime_1.jsx)("div", { className: "flex-shrink-0 w-10 h-10 rounded-full overflow-hidden bg-gradient-to-br from-primary/20 to-primary/40 flex items-center justify-center transition-all duration-300 hover:scale-110 backdrop-blur-sm", style: { backgroundColor: user.color }, children: user.avatarUrl ? ((0, jsx_runtime_1.jsx)("img", { src: user.avatarUrl, alt: `${user.name} avatar`, className: "w-full h-full object-cover", loading: "lazy" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-xs font-bold text-white drop-shadow-sm", children: user.initials || user.name.split(" ").map((n) => n[0]).join("").slice(0, 2).toUpperCase() })) })); }; const Notification = ({ notification, showAvatars, showTimestamps, variant, onDismiss, onClick, allowDismiss }) => { const getVariantStyles = () => { switch (variant) { case "minimal": return "bg-background/95 border border-border/50 backdrop-blur-xl"; case "glass": return "bg-background/30 backdrop-blur-2xl border border-white/20 dark:border-gray-800/20 shadow-2xl"; case "bordered": return "bg-card/95 border-2 border-primary/30 backdrop-blur-lg shadow-xl"; default: return "bg-background/30 backdrop-blur-2xl border border-white/20 shadow-2xl"; } }; const getPriorityStyles = () => { switch (notification.priority) { case "high": return "border-l-4 border-l-red-500 shadow-red-500/20 dark:border-l-red-500 dark:shadow-red-500/20"; case "medium": return "border-l-4 border-l-yellow-500 shadow-yellow-500/20 dark:border-l-yellow-500 dark:shadow-yellow-500/20"; case "low": return "border-l-4 border-l-blue-500 shadow-[0_4px_15px_color-mix(in_srgb,var(--primarylw)_20%,transparent)] dark:border-l-blue-500 dark:shadow-[0_4px_15px_color-mix(in_srgb,var(--primarylw)_20%,transparent)]"; default: return "border-l-4 border-l-primary/50 shadow-primary/20 dark:border-l-primary/50 dark:shadow-primary/20"; } }; return ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)("group relative transition-all duration-500 ease-out transform hover:scale-[1.02] hover:-translate-y-1", "rounded-xl p-4 flex items-start gap-3 w-80 max-w-80 cursor-pointer", getVariantStyles(), getPriorityStyles(), notification.fadingOut && "animate-pulse"), onClick: onClick, children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-xl pointer-events-none" }), (0, jsx_runtime_1.jsx)(Avatar, { user: notification.user, showAvatar: showAvatars }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 min-w-0 space-y-1", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("h3", { className: "font-semibold text-sm text-foreground/90 truncate", children: notification.user.name }), showTimestamps && notification.timestamp && (0, jsx_runtime_1.jsx)("span", { className: "text-xs text-muted-foreground/70 ", children: notification.timestamp })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted-foreground/80 line-clamp-2 leading-relaxed", children: notification.message })] }), allowDismiss && ((0, jsx_runtime_1.jsx)("button", { onClick: (e) => { e.stopPropagation(); onDismiss?.(); }, className: "flex-shrink-0 w-5 h-5 text-muted-foreground/50 hover:text-muted-foreground transition-all duration-200 hover:scale-110 opacity-0 group-hover:opacity-100", "aria-label": "dismiss notification", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "w-4 h-4" }) }))] })); }; async function fetchRandomUser(apiEndpoint) { try { const endpoint = apiEndpoint || "https://randomuser.me/api/"; const res = await fetch(endpoint); const data = await res.json(); const user = data.results[0]; return { avatarUrl: user.picture?.large, name: `${user.name.first} ${user.name.last}`, color: `hsl(${Math.floor(Math.random() * 360)}, 70%, 80%)`, }; } catch (error) { const names = ["John Doe", "Jane Smith", "Alex Johnson", "Sarah Wilson", "Mike Brown"]; const randomName = names[Math.floor(Math.random() * names.length)]; return { name: randomName, color: `hsl(${Math.floor(Math.random() * 360)}, 70%, 80%)`, }; } } function getRandomMessage(customMessages) { const messages = customMessages || defaultMessages; return messages[Math.floor(Math.random() * messages.length)]; } async function generateNotification(customMessages, userApiEndpoint, fixedUser) { const user = fixedUser || await fetchRandomUser(userApiEndpoint); return { id: uuidv4(), user, message: getRandomMessage(customMessages), timestamp: new Date().toLocaleTimeString(), priority: ["low", "medium", "high"][Math.floor(Math.random() * 3)], }; } const AnimatedNotification = ({ maxNotifications = 3, autoInterval = 3500, autoGenerate = true, notifications = [], customMessages, animationDuration = 800, position = "center", width = 320, showAvatars = true, showTimestamps = true, className, onNotificationClick, onNotificationDismiss, allowDismiss = true, autoDismissTimeout = 3000, userApiEndpoint, variant = "glass", fixedUser, }) => { const [notes, setNotes] = (0, react_1.useState)(notifications); const intervalRef = (0, react_1.useRef)(null); const dismissTimeouts = (0, react_1.useRef)(new Map()); const isGenerating = (0, react_1.useRef)(false); // Helper: Clear a per-note timeout const clearNoteTimeout = (0, react_1.useCallback)((id) => { const t = dismissTimeouts.current.get(id); if (t) { window.clearTimeout(t); dismissTimeouts.current.delete(id); } }, []); // Dismiss with fade + removal after animationDuration const dismissNotification = (0, react_1.useCallback)((id, isAutoRemoval = false) => { setNotes((prev) => { const note = prev.find((n) => n.id === id); if (!note || note.fadingOut) return prev; // mark fading out const updated = prev.map((n) => (n.id === id ? { ...n, fadingOut: true } : n)); // clear any auto timeout clearNoteTimeout(id); // remove after animation window.setTimeout(() => { setNotes((current) => { const remaining = current.filter((n) => n.id !== id); // only call onNotificationDismiss if the note actually existed before removal if (note && onNotificationDismiss) { // isAutoRemoval true indicates it was auto-dismissed; user can still be notified if needed onNotificationDismiss(note); } return remaining; }); }, animationDuration); return updated; }); }, [animationDuration, clearNoteTimeout, onNotificationDismiss]); // Add a new note (from generator). Ensures we don't create concurrency issues. const addGeneratedNote = (0, react_1.useCallback)(async () => { if (!autoGenerate) return; if (isGenerating.current) return; isGenerating.current = true; try { const newNote = await generateNotification(customMessages, userApiEndpoint, fixedUser); setNotes((prev) => { // prune: if there are >= maxNotifications non-fading notes, dismiss the oldest non-fading const nonFading = prev.filter((n) => !n.fadingOut); if (nonFading.length >= maxNotifications) { const oldest = nonFading[0]; // gracefully mark it for fading and schedule removal using existing dismissNotification // call dismissNotification (which will clear its timeout and run onNotificationDismiss) // but to avoid calling setNotes nested while in this setNotes, we will mark it locally: const locallyUpdated = prev.map((n) => (n.id === oldest.id ? { ...n, fadingOut: true } : n)); // schedule actual removal outside this setNotes by using setTimeout window.setTimeout(() => { setNotes((current) => current.filter((n) => n.id !== oldest.id)); clearNoteTimeout(oldest.id); onNotificationDismiss?.(oldest); }, animationDuration); // Also clear any auto timeout for that oldest note const t = dismissTimeouts.current.get(oldest.id); if (t) { window.clearTimeout(t); dismissTimeouts.current.delete(oldest.id); } // then append the new note const appended = [...locallyUpdated, newNote]; // schedule auto-dismiss for newNote if (autoDismissTimeout > 0) { const timeoutId = window.setTimeout(() => dismissNotification(newNote.id, true), autoDismissTimeout); dismissTimeouts.current.set(newNote.id, timeoutId); } return appended; } else { const appended = [...prev, newNote]; if (autoDismissTimeout > 0) { const timeoutId = window.setTimeout(() => dismissNotification(newNote.id, true), autoDismissTimeout); dismissTimeouts.current.set(newNote.id, timeoutId); } return appended; } }); } finally { isGenerating.current = false; } }, [ autoGenerate, customMessages, userApiEndpoint, maxNotifications, animationDuration, autoDismissTimeout, dismissNotification, onNotificationDismiss, clearNoteTimeout, fixedUser, ]); // Start/stop the interval generator (0, react_1.useEffect)(() => { // If autoGenerate enabled, start interval. We'll call addGeneratedNote every autoInterval. if (autoGenerate) { // start with a small delay so UI can mount intervalRef.current = window.setInterval(() => { void addGeneratedNote(); }, autoInterval); // do a first immediate call after small delay (not instant) so initial experience is consistent const first = window.setTimeout(() => void addGeneratedNote(), 900); return () => { if (intervalRef.current) { window.clearInterval(intervalRef.current); intervalRef.current = null; } window.clearTimeout(first); }; } else { // autoGenerate disabled -> ensure no interval running if (intervalRef.current) { window.clearInterval(intervalRef.current); intervalRef.current = null; } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [autoGenerate, autoInterval, addGeneratedNote]); // Sync external notifications prop if the parent supplies them (0, react_1.useEffect)(() => { if (notifications && notifications.length > 0) { // clear existing timers dismissTimeouts.current.forEach((t) => window.clearTimeout(t)); dismissTimeouts.current.clear(); setNotes(notifications); // set up auto dismiss for those if needed if (autoDismissTimeout > 0) { notifications.forEach((n) => { const id = window.setTimeout(() => dismissNotification(n.id, true), autoDismissTimeout); dismissTimeouts.current.set(n.id, id); }); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [notifications]); // cleanup all timers on unmount (0, react_1.useEffect)(() => { return () => { if (intervalRef.current) window.clearInterval(intervalRef.current); dismissTimeouts.current.forEach((t) => window.clearTimeout(t)); dismissTimeouts.current.clear(); }; }, []); // Manual dismiss wrapper so the caller's onNotificationDismiss isn't called twice const handleManualDismiss = (0, react_1.useCallback)((note) => { // clear timeout and fade out clearNoteTimeout(note.id); // mark fading and remove after animationDuration setNotes((prev) => prev.map((n) => (n.id === note.id ? { ...n, fadingOut: true } : n))); window.setTimeout(() => { setNotes((current) => { const filtered = current.filter((n) => n.id !== note.id); onNotificationDismiss?.(note); return filtered; }); }, animationDuration); }, [animationDuration, clearNoteTimeout, onNotificationDismiss]); const getPositionStyles = () => { switch (position) { case "top-left": return "fixed top-6 left-6 z-50"; case "top-right": return "fixed top-6 right-6 z-50"; case "bottom-left": return "fixed bottom-6 left-6 z-50"; case "bottom-right": return "fixed bottom-6 right-6 z-50"; default: return "flex items-center justify-center min-h-auto p-6"; } }; return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("style", { dangerouslySetInnerHTML: { __html: ` @keyframes notification-enter { from { opacity: 0; transform: translateY(20px) scale(0.95); filter: blur(4px); } to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0px); } } @keyframes notification-exit { from { opacity: 1; transform: translateY(0) scale(1); filter: blur(0px); } to { opacity: 0; transform: translateY(-20px) scale(0.95); filter: blur(4px); } } .notification-enter { animation: notification-enter var(--animation-duration) cubic-bezier(0.4, 0, 0.2, 1) forwards; } .notification-exit { animation: notification-exit var(--animation-duration) cubic-bezier(0.4, 0, 0.2, 1) forwards; } `, } }), (0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)(getPositionStyles(), className), children: (0, jsx_runtime_1.jsx)(react_flip_toolkit_1.Flipper, { flipKey: notes.map((n) => n.id).join(""), children: (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-4 items-center", style: { width }, children: notes.map((note) => ((0, jsx_runtime_1.jsx)(react_flip_toolkit_1.Flipped, { flipId: note.id, children: (0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)("notification-item", note.fadingOut ? "notification-exit" : "notification-enter"), style: { ["--animation-duration"]: `${animationDuration}ms` }, children: (0, jsx_runtime_1.jsx)(Notification, { notification: note, showAvatars: showAvatars, showTimestamps: showTimestamps, variant: variant, allowDismiss: allowDismiss, onClick: () => onNotificationClick?.(note), onDismiss: () => handleManualDismiss(note) }) }) }, note.id))) }) }) })] })); }; exports.default = AnimatedNotification; //# sourceMappingURL=animated-notification.js.map