/** * Alert — a status message with an optional leading status icon. * * The indicator derives its icon and colour from the variant, so a success * alert cannot end up with a warning icon. Pass `icon` on the root to override * it everywhere, or give `Alert.Indicator` children to override one. */ import { createContext, forwardRef, useContext, type ReactNode } from 'react'; import { View, type Text as RNText, type ViewProps } from 'react-native'; import { tv, type VariantProps } from 'tailwind-variants'; import { useCSSVariable } from 'uniwind'; import { cn } from '../../utils/cn'; import { Text, type TextProps, textChildren } from '../../primitives/text'; import { AlertTriangleIcon, CheckCircleIcon, InfoIcon } from '../../icons'; const alertVariants = tv({ slots: { root: 'w-full flex-row gap-3 rounded-xl border px-3.5 py-3', indicator: 'pt-px', content: 'flex-1 gap-0.5', title: 'text-sm font-medium', description: 'text-sm text-muted-foreground', }, variants: { variant: { default: { root: 'border-border bg-surface', title: 'text-card-foreground', }, info: { root: 'border-info/32 bg-info-soft', title: 'text-info-foreground', }, success: { root: 'border-success/32 bg-success-soft', title: 'text-success-foreground', }, warning: { root: 'border-warning/32 bg-warning-soft', title: 'text-warning-foreground', }, destructive: { root: 'border-destructive/32 bg-destructive-soft', title: 'text-destructive-foreground', }, }, }, defaultVariants: { variant: 'default', }, }); type AlertVariant = NonNullable['variant']>; const AlertContext = createContext('default'); /** CSS variable each variant's indicator icon takes its colour from. */ const INDICATOR_COLOR_VAR: Record = { default: '--color-muted-foreground', info: '--color-info-foreground', success: '--color-success-foreground', warning: '--color-warning-foreground', destructive: '--color-destructive-foreground', }; const INDICATOR_ICON: Record = { default: InfoIcon, info: InfoIcon, success: CheckCircleIcon, warning: AlertTriangleIcon, destructive: AlertTriangleIcon, }; export interface AlertProps extends ViewProps, VariantProps { className?: string; /** * Leading element rendered before the content. * * @deprecated Prefer ``, which picks a status icon for you. * Still honoured so v0.2 call sites keep rendering. */ icon?: ReactNode; children?: ReactNode; } const AlertRoot = forwardRef( ({ className, variant = 'default', icon, children, ...props }, ref) => { const { root, indicator, content } = alertVariants({ variant }); return ( {icon ? ( <> {icon} {textChildren(children)} ) : ( children )} ); } ); AlertRoot.displayName = 'Alert'; export interface AlertIndicatorProps extends ViewProps { className?: string; /** Overrides the size/colour of the default status icon. */ iconProps?: { size?: number; color?: string }; /** Replaces the default status icon entirely. */ children?: ReactNode; } /** * Leading status icon. Resolves both the icon and its colour from the parent * Alert's variant, so it follows the active theme. */ const AlertIndicator = forwardRef( ({ className, iconProps, children, ...props }, ref) => { const variant = useContext(AlertContext); const { indicator } = alertVariants({ variant }); const themeColor = useCSSVariable(INDICATOR_COLOR_VAR[variant]); const Icon = INDICATOR_ICON[variant]; const color = iconProps?.color ?? (typeof themeColor === 'string' ? themeColor : undefined); return ( {children ?? } ); } ); AlertIndicator.displayName = 'Alert.Indicator'; /** Flex-1 wrapper for Alert.Title and Alert.Description. */ const AlertContent = forwardRef( ({ className, ...props }, ref) => { const variant = useContext(AlertContext); const { content } = alertVariants({ variant }); return ; } ); AlertContent.displayName = 'Alert.Content'; const AlertTitle = forwardRef( ({ className, ...props }, ref) => { const variant = useContext(AlertContext); const { title } = alertVariants({ variant }); return ; } ); AlertTitle.displayName = 'Alert.Title'; const AlertDescription = forwardRef( ({ className, ...props }, ref) => { const variant = useContext(AlertContext); const { description } = alertVariants({ variant }); return ; } ); AlertDescription.displayName = 'Alert.Description'; export const Alert = Object.assign(AlertRoot, { /** @optional Status icon rendered as the leading visual element. */ Indicator: AlertIndicator, /** @optional Wrapper for title and description. */ Content: AlertContent, Title: AlertTitle, Description: AlertDescription, });