import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react"; import { XIcon } from "lucide-react"; import { cn } from "~/lib/utils"; import { Alert, AlertAction, AlertDescription, AlertTitle } from "../ui/alert"; import { Button } from "../ui/button"; const DISMISS_TRANSITION_MS = 220; const frontExitStyle = { opacity: 0, transform: "translate3d(0, 4rem, 0)", } satisfies CSSProperties; const stackedExitStyle = { opacity: 0, transform: "translate3d(0, 7rem, 0)", } satisfies CSSProperties; const restingStyle = { opacity: 1, transform: "none", } satisfies CSSProperties; const exitTransitionStyle = { transition: `transform ${DISMISS_TRANSITION_MS}ms ease-in, opacity ${DISMISS_TRANSITION_MS}ms ease-in`, } satisfies CSSProperties; export interface ComposerBannerStackItem { readonly id: string; readonly variant: "error" | "info" | "success" | "warning"; readonly icon: ReactNode; readonly title: ReactNode; readonly description?: ReactNode; readonly actions?: ReactNode; readonly className?: string; readonly actionClassName?: string; readonly dismissLabel?: string; readonly onDismiss?: () => void; } interface ComposerBannerStackProps { readonly className?: string; readonly items: ReadonlyArray; } export function ComposerBannerStack({ className, items }: ComposerBannerStackProps) { const [requestedExitingItemId, setExitingItemId] = useState(null); const dismissTimeoutRef = useRef | null>(null); const exitingItemId = requestedExitingItemId !== null && items.some((item) => item.id === requestedExitingItemId) ? requestedExitingItemId : null; useEffect(() => { return () => { if (dismissTimeoutRef.current) { clearTimeout(dismissTimeoutRef.current); } }; }, []); if (items.length === 0) { return null; } const frontItem = items[0]; if (!frontItem) { return null; } const stackedItems = items.slice(1); const hasStack = stackedItems.length > 0; const showCollapsedStackCap = hasStack && exitingItemId !== frontItem.id; const requestDismiss = (item: ComposerBannerStackItem) => { if (!item.onDismiss || exitingItemId) { return; } setExitingItemId(item.id); if (dismissTimeoutRef.current) { clearTimeout(dismissTimeoutRef.current); } dismissTimeoutRef.current = setTimeout(() => { dismissTimeoutRef.current = null; item.onDismiss?.(); }, DISMISS_TRANSITION_MS); }; return (
{showCollapsedStackCap ? (
); } function ComposerBannerStackAlert({ item, exiting, onDismissRequest, }: { readonly item: ComposerBannerStackItem; readonly exiting: boolean; readonly onDismissRequest: () => void; }) { const dismissOnly = item.onDismiss && !item.actions; return ( {item.icon} {item.title} {item.description ? {item.description} : null} {item.actions || item.onDismiss ? ( {item.actions} {item.onDismiss ? ( ) : null} ) : null} ); }