import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { InfoIcon, WarningIcon, WarningCircleIcon, CheckCircleIcon, XIcon, type Icon, } from "@phosphor-icons/react"; import { cn } from "../../utils.js"; import "./banner.css"; export type BannerVariant = "info" | "warning" | "danger" | "success"; export interface BannerProps extends React.HTMLAttributes { variant?: BannerVariant; open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; onDismiss?: () => void; dismissible?: boolean; asChild?: boolean; } const variantIcons: Record = { info: InfoIcon, warning: WarningIcon, danger: WarningCircleIcon, success: CheckCircleIcon, }; const BannerContext = React.createContext<{ variant: BannerVariant; onDismiss?: () => void; }>({ variant: "info", }); export const Banner = React.forwardRef( ( { className, variant = "info", open: controlledOpen, defaultOpen = true, onOpenChange, onDismiss, dismissible, asChild = false, children, ...props }, ref ) => { const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen); const isOpen = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen; const handleDismiss = React.useCallback(() => { if (controlledOpen === undefined) { setUncontrolledOpen(false); } onOpenChange?.(false); onDismiss?.(); }, [controlledOpen, onOpenChange, onDismiss]); if (!isOpen) { return null; } const Comp = asChild ? Slot : "div"; return ( {children} {(dismissible || onDismiss) && !React.Children.toArray(children).some( (child) => React.isValidElement(child) && (child.type === BannerDismiss || child.type === BannerClose) ) && } ); } ); Banner.displayName = "Banner"; export interface BannerIconProps extends React.HTMLAttributes { asChild?: boolean; } export const BannerIcon = React.forwardRef( ({ className, asChild = false, children, ...props }, ref) => { const { variant } = React.useContext(BannerContext); const Comp = asChild ? Slot : "span"; const DefaultIcon = variantIcons[variant]; return ( {children ?? } ); } ); BannerIcon.displayName = "BannerIcon"; export interface BannerTitleProps extends React.HTMLAttributes { asChild?: boolean; } export const BannerTitle = React.forwardRef( ({ className, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "h5"; return ( ); } ); BannerTitle.displayName = "BannerTitle"; export interface BannerDescriptionProps extends React.HTMLAttributes { asChild?: boolean; } export const BannerDescription = React.forwardRef< HTMLParagraphElement, BannerDescriptionProps >(({ className, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "div"; return ( ); }); BannerDescription.displayName = "BannerDescription"; export interface BannerActionProps extends React.ButtonHTMLAttributes { asChild?: boolean; } export const BannerAction = React.forwardRef( ({ className, asChild = false, type = "button", ...props }, ref) => { const Comp = asChild ? Slot : "button"; return ( ); } ); BannerAction.displayName = "BannerAction"; export interface BannerDismissProps extends React.ButtonHTMLAttributes { asChild?: boolean; } export const BannerDismiss = React.forwardRef< HTMLButtonElement, BannerDismissProps >(({ className, asChild = false, type = "button", onClick, children, ...props }, ref) => { const { onDismiss } = React.useContext(BannerContext); const Comp = asChild ? Slot : "button"; const handleClick = (e: React.MouseEvent) => { onClick?.(e); if (!e.defaultPrevented) { onDismiss?.(); } }; return ( {children ?? } ); }); BannerDismiss.displayName = "BannerDismiss"; export const BannerClose = BannerDismiss;