"use client"; import { ArrowUpRight, BellOff } from "lucide-react"; import { motion, useReducedMotion, type Transition } from "motion/react"; import { useCallback, useRef, useState, type FocusEvent, type KeyboardEvent, type ReactNode, } from "react"; import { ActionSwapText } from "@/components/motion/action-swap"; import { EASE_OUT, SPRING_LAYOUT } from "@/lib/ease"; import { useHoverCapable } from "@/lib/hooks/use-hover-capable"; import { cn } from "@/lib/utils"; export type NotificationStackItem = { id: string; title: ReactNode; description?: ReactNode; trailing?: ReactNode; }; export type NotificationStackClassNames = { stack?: string; card?: string; content?: string; title?: string; description?: string; trailing?: string; footer?: string; count?: string; }; export interface NotificationStackProps { items: NotificationStackItem[]; expanded?: boolean; defaultExpanded?: boolean; onExpandedChange?: (expanded: boolean) => void; onViewAll?: () => void; maxVisible?: number; collapsedLabel?: string; expandedLabel?: string; emptyLabel?: string; className?: string; classNames?: NotificationStackClassNames; } const STACK_PEEK = 8; const STACK_INSET = 12; function useControllableExpanded({ expanded, defaultExpanded, onExpandedChange, }: { expanded?: boolean; defaultExpanded: boolean; onExpandedChange?: (expanded: boolean) => void; }) { const [internalExpanded, setInternalExpanded] = useState(defaultExpanded); const isControlled = expanded !== undefined; const value = expanded ?? internalExpanded; const setValue = useCallback( (next: boolean) => { if (!isControlled) setInternalExpanded(next); onExpandedChange?.(next); }, [isControlled, onExpandedChange], ); return [value, setValue] as const; } function NotificationCardContent({ item, classNames, }: { item: NotificationStackItem; classNames?: NotificationStackClassNames; }) { return ( {item.title} {item.trailing ? ( {item.trailing} ) : null} {item.description ? ( {item.description} ) : null} ); } export function NotificationStack({ items, expanded, defaultExpanded = false, onExpandedChange, onViewAll, maxVisible = 3, collapsedLabel = "Notifications", expandedLabel = "View all", emptyLabel = "All caught up", className, classNames, }: NotificationStackProps) { const reduce = useReducedMotion(); const canHover = useHoverCapable(); const hasFocus = useRef(false); const [isExpanded, setIsExpanded] = useControllableExpanded({ expanded, defaultExpanded, onExpandedChange, }); const visibleItems = items.slice(0, Math.max(1, maxVisible)); const primaryItem = visibleItems[0]; const transition: Transition = reduce ? { duration: 0 } : SPRING_LAYOUT; const cardTransition: Transition = reduce ? { duration: 0 } : { duration: 0.32, ease: EASE_OUT }; const backgroundTransition: Transition = reduce ? { duration: 0 } : { duration: 0.26, ease: EASE_OUT }; if (!primaryItem) { return (