import * as React from "react" import { mergeProps } from "@base-ui/react/merge-props" import { useRender } from "@base-ui/react/use-render" import { cva, type VariantProps } from "class-variance-authority" import { XIcon } from "lucide-react" import { cn } from "@/lib/utils" const badgeVariants = cva( "group/badge inline-flex w-fit shrink-0 items-center justify-center whitespace-nowrap rounded-full border font-medium outline-none transition-[background-color,border-color,color,box-shadow,opacity] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 [&>svg]:pointer-events-none [&>svg]:size-3.5 [&_[data-slot=badge-avatar]]:size-4 [&_[data-slot=badge-dot]]:size-1.5 [&_[data-slot=badge-dot]]:shrink-0 [&_[data-slot=badge-dot]]:rounded-full [&_[data-slot=badge-dot]]:bg-current [&_[data-slot=badge-dot][data-pulse=true]]:animate-pulse [&_[data-slot=badge-label]]:min-w-0 [&_[data-slot=badge-label]]:truncate [&_[data-slot=badge-remove]]:-mr-1 [&_[data-slot=badge-remove]]:inline-flex [&_[data-slot=badge-remove]]:size-4 [&_[data-slot=badge-remove]]:shrink-0 [&_[data-slot=badge-remove]]:items-center [&_[data-slot=badge-remove]]:justify-center [&_[data-slot=badge-remove]]:rounded-full [&_[data-slot=badge-remove]]:opacity-70 [&_[data-slot=badge-remove]]:transition-opacity hover:[&_[data-slot=badge-remove]]:opacity-100 focus-visible:[&_[data-slot=badge-remove]]:ring-2 focus-visible:[&_[data-slot=badge-remove]]:ring-ring [&_[data-icon=remove]]:size-3", { variants: { variant: { default: "border-transparent shadow-sm", secondary: "border-transparent bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", soft: "border-transparent", outline: "bg-background shadow-sm", ghost: "border-transparent bg-transparent", link: "h-auto border-transparent bg-transparent p-0 text-primary underline-offset-4 hover:underline focus-visible:ring-0 focus-visible:ring-offset-0", }, tone: { neutral: "", info: "", success: "", warning: "", danger: "", muted: "", }, size: { sm: "h-5 gap-1 px-1.5 text-[10px] leading-none [&>svg]:size-3 [&_[data-slot=badge-avatar]]:size-3.5 [&_[data-slot=badge-dot]]:size-1.5", default: "h-6 gap-1.5 px-2.5 text-xs leading-none", lg: "h-7 gap-1.5 px-3 text-sm leading-none [&>svg]:size-4 [&_[data-slot=badge-avatar]]:size-5 [&_[data-slot=badge-dot]]:size-2", }, dot: { true: "", false: "", }, }, compoundVariants: [ { variant: "default", tone: "neutral", className: "bg-primary text-primary-foreground hover:bg-primary/90", }, { variant: "default", tone: "info", className: "bg-blue-600 text-white hover:bg-blue-700", }, { variant: "default", tone: "success", className: "bg-emerald-600 text-white hover:bg-emerald-700", }, { variant: "default", tone: "warning", className: "bg-amber-500 text-white hover:bg-amber-600", }, { variant: "default", tone: "danger", className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", }, { variant: "default", tone: "muted", className: "bg-muted text-muted-foreground hover:bg-muted/80", }, { variant: "soft", tone: "neutral", className: "bg-secondary text-secondary-foreground hover:bg-secondary/80", }, { variant: "soft", tone: "info", className: "bg-blue-50 text-blue-700 hover:bg-blue-100 dark:bg-blue-950/60 dark:text-blue-300 dark:hover:bg-blue-950", }, { variant: "soft", tone: "success", className: "bg-emerald-50 text-emerald-700 hover:bg-emerald-100 dark:bg-emerald-950/60 dark:text-emerald-300 dark:hover:bg-emerald-950", }, { variant: "soft", tone: "warning", className: "bg-amber-50 text-amber-700 hover:bg-amber-100 dark:bg-amber-950/60 dark:text-amber-300 dark:hover:bg-amber-950", }, { variant: "soft", tone: "danger", className: "bg-red-50 text-red-700 hover:bg-red-100 dark:bg-red-950/60 dark:text-red-300 dark:hover:bg-red-950", }, { variant: "soft", tone: "muted", className: "bg-muted text-muted-foreground hover:bg-muted/80", }, { variant: "outline", tone: "neutral", className: "border-border text-foreground hover:bg-accent hover:text-accent-foreground", }, { variant: "outline", tone: "info", className: "border-blue-200 text-blue-700 hover:bg-blue-50 dark:border-blue-800 dark:text-blue-300 dark:hover:bg-blue-950/50", }, { variant: "outline", tone: "success", className: "border-emerald-200 text-emerald-700 hover:bg-emerald-50 dark:border-emerald-800 dark:text-emerald-300 dark:hover:bg-emerald-950/50", }, { variant: "outline", tone: "warning", className: "border-amber-200 text-amber-700 hover:bg-amber-50 dark:border-amber-800 dark:text-amber-300 dark:hover:bg-amber-950/50", }, { variant: "outline", tone: "danger", className: "border-red-200 text-red-700 hover:bg-red-50 dark:border-red-800 dark:text-red-300 dark:hover:bg-red-950/50", }, { variant: "outline", tone: "muted", className: "border-border text-muted-foreground hover:bg-muted", }, { variant: "ghost", tone: "neutral", className: "text-foreground hover:bg-accent hover:text-accent-foreground", }, { variant: "ghost", tone: "info", className: "text-blue-700 hover:bg-blue-50 dark:text-blue-300 dark:hover:bg-blue-950/50", }, { variant: "ghost", tone: "success", className: "text-emerald-700 hover:bg-emerald-50 dark:text-emerald-300 dark:hover:bg-emerald-950/50", }, { variant: "ghost", tone: "warning", className: "text-amber-700 hover:bg-amber-50 dark:text-amber-300 dark:hover:bg-amber-950/50", }, { variant: "ghost", tone: "danger", className: "text-red-700 hover:bg-red-50 dark:text-red-300 dark:hover:bg-red-950/50", }, { variant: "ghost", tone: "muted", className: "text-muted-foreground hover:bg-muted", }, ], defaultVariants: { variant: "default", tone: "neutral", size: "default", dot: false, }, } ) type BadgeProps = useRender.ComponentProps<"span"> & Omit, "dot"> & { label?: React.ReactNode count?: React.ReactNode status?: "neutral" | "info" | "success" | "warning" | "danger" | "muted" removable?: boolean selected?: boolean onRemove?: () => void removeLabel?: string leftIcon?: React.ReactNode rightIcon?: React.ReactNode avatar?: React.ReactNode dot?: boolean | React.ReactNode showDot?: boolean dotPosition?: "start" | "end" pulse?: boolean interactive?: boolean } function Badge({ className, variant = "default", tone = "neutral", size = "default", dot = false, label, count, status, removable = false, selected = false, onRemove, removeLabel = "Remove badge", leftIcon, rightIcon, avatar, showDot, dotPosition = "start", pulse = false, interactive = false, children, render, onKeyDown, ...props }: BadgeProps) { const resolvedTone = status ?? tone const resolvedLabel = label ?? children const resolvedShowDot = showDot ?? Boolean(dot || status === "success" || status === "warning" || status === "danger" || status === "info") const dotNode = resolvedShowDot ? ( {React.isValidElement(dot) ? dot : null} ) : null return useRender({ defaultTagName: "span", props: mergeProps<"span">( { className: cn( badgeVariants({ variant, tone: resolvedTone, size, dot: resolvedShowDot }), selected && "ring-2 ring-ring/45 ring-offset-1", interactive && "cursor-pointer", className ), tabIndex: removable || interactive ? 0 : undefined, onKeyDown: (event: React.KeyboardEvent) => { if (removable && (event.key === "Backspace" || event.key === "Delete")) { event.preventDefault() onRemove?.() } onKeyDown?.(event) }, children: ( <> {dotPosition === "start" ? dotNode : null} {avatar ? {avatar} : null} {leftIcon ? {leftIcon} : null} {resolvedLabel ? {resolvedLabel} : null} {count != null ? ( {count} ) : null} {rightIcon ? {rightIcon} : null} {dotPosition === "end" ? dotNode : null} {removable ? ( ) : null} ), } as React.HTMLAttributes, { "data-variant": variant ?? "default", "data-tone": resolvedTone ?? "neutral", "data-size": size ?? "default", "data-removable": removable || undefined, "data-selected": selected || undefined, "data-interactive": interactive || undefined, "data-pulse": pulse || undefined, } as React.HTMLAttributes, props ), render, state: { slot: "badge", variant, tone: resolvedTone, size, dot: resolvedShowDot, }, }) } export { Badge, badgeVariants, type BadgeProps }