'use client' import * as React from 'react' import { cva, type VariantProps } from 'class-variance-authority' import { AnimatePresence, LazyMotion, domAnimation, m } from 'motion/react' import { cn } from '../../internal/utils' import { useReducedMotion } from '../../hooks/use-reduced-motion' import { useDismissible } from '../../hooks/use-dismissible' const alertVariants = cva( cn( 'group/alert relative rounded-xl border p-5 text-foreground backdrop-blur-xl', 'grid w-full items-start', "[grid-template-columns:auto_1fr_auto] [grid-template-areas:'icon_title_close'_'._description_.'_'._actions_actions']", '@min-[460px]:data-[layout=inline]:items-center', '@min-[460px]:data-[layout=inline]:[grid-template-columns:auto_auto_1fr_auto_auto]', "@min-[460px]:data-[layout=inline]:[grid-template-areas:'icon_title_description_actions_close']", ), { variants: { variant: { default: 'bg-background border-border', primary: 'bg-primary-subtle border-primary-soft', secondary: 'bg-secondary-subtle border-secondary-soft', error: 'bg-error-subtle border-error-soft', success: 'bg-success-subtle border-success-soft', warning: 'bg-warning-subtle border-warning-soft', info: 'bg-info-subtle border-info-soft', }, }, defaultVariants: { variant: 'default' }, }, ) type AlertVariant = NonNullable['variant']> const alertIconColor: Record = { default: 'text-foreground-intense', primary: 'text-primary', secondary: 'text-secondary-emphasis', error: 'text-error-emphasis', success: 'text-success-emphasis', warning: 'text-warning-emphasis', info: 'text-info-emphasis', } const AlertVariantContext = React.createContext('default') const exitDefault = { opacity: 0, scale: 0.88, filter: 'blur(12px)', height: 0, } as const const exitReduced = { opacity: 0 } as const const transition = { default: { duration: 0.32, ease: [0.4, 0, 0.2, 1] as const }, height: { duration: 0.3, ease: [0.4, 0, 0.2, 1] as const, delay: 0.18 }, } as const type AlertLayout = 'block' | 'inline' interface AlertProps extends React.HTMLAttributes, Omit, 'variant'> { /** * Color scheme; also drives the `AlertIcon` accent. * @default 'default' */ variant?: VariantProps['variant'] /** * Stack the parts (`block`) or flow them on one row when wide enough (`inline`). * @default 'block' */ layout?: AlertLayout /** * Render a close button that hides the alert (with an exit animation). * @default false */ dismissible?: boolean /** Controlled visibility. Pair with `onOpenChange`. */ open?: boolean /** Called when the alert is dismissed (with `false`). */ onOpenChange?: (open: boolean) => void /** Remember the dismissal under this key so the alert stays hidden on return (uncontrolled). */ persistKey?: string /** * Which Web Storage backs `persistKey`. * @default 'local' */ persistStorage?: 'local' | 'session' /** * Accessible label for the close button. * @default 'Dismiss' */ closeLabel?: string } function Alert({ variant, layout = 'block', dismissible = false, open, onOpenChange, persistKey, persistStorage = 'local', closeLabel = 'Dismiss', role = 'alert', className, children, style, ...props }: AlertProps) { const reduced = useReducedMotion() const [internalOpen, setInternalOpen] = React.useState(true) const persisted = useDismissible(persistKey ?? '', { storage: persistStorage }) const isControlled = open !== undefined const usingPersisted = !isControlled && persistKey != null const actualOpen = isControlled ? open : usingPersisted ? persisted.open : internalOpen const shouldRender = actualOpen const handleDismiss = React.useCallback(() => { if (isControlled) { onOpenChange?.(false) return } if (usingPersisted) { persisted.dismiss() } else { setInternalOpen(false) } onOpenChange?.(false) }, [isControlled, usingPersisted, persisted, onOpenChange]) return ( {shouldRender && (
{children} {dismissible && ( )}
)}
) } function CloseIcon() { return ( ) } interface AlertIconProps extends React.HTMLAttributes {} function AlertIcon({ className, ...props }: AlertIconProps) { const variant = React.useContext(AlertVariantContext) return ( ) } interface AlertTitleProps extends React.HTMLAttributes { /** * Heading level, so the alert fits the page's heading outline. * @default 'h5' */ as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div' } function AlertTitle({ className, as: As = 'h5', ...props }: AlertTitleProps) { return ( ) } interface AlertDescriptionProps extends React.HTMLAttributes {} function AlertDescription({ className, ...props }: AlertDescriptionProps) { return (

) } interface AlertActionProps extends React.HTMLAttributes {} function AlertAction({ className, ...props }: AlertActionProps) { return (

) } export { Alert, AlertIcon, AlertTitle, AlertDescription, AlertAction } export type { AlertProps, AlertIconProps, AlertTitleProps, AlertDescriptionProps, AlertActionProps }