import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const cardVariants = cva( "group/card flex min-w-0 flex-col gap-(--card-spacing) overflow-hidden rounded-lg border text-card-foreground transition-[background-color,border-color,box-shadow,opacity] py-(--card-spacing) [--card-spacing:--spacing(5)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0", { variants: { variant: { default: "border-border bg-card shadow-sm", elevated: "border-border bg-card shadow-md", outline: "border-border bg-background shadow-none", soft: "border-transparent bg-muted text-foreground shadow-none", ghost: "border-transparent bg-transparent shadow-none", }, size: { sm: "rounded-md [--card-spacing:--spacing(4)]", default: "rounded-lg [--card-spacing:--spacing(5)]", lg: "rounded-xl [--card-spacing:--spacing(6)]", }, density: { compact: "[--card-spacing:--spacing(3)]", default: "", comfortable: "[--card-spacing:--spacing(6)]", }, tone: { neutral: "", info: "", success: "", warning: "", danger: "", }, interactive: { true: "cursor-pointer outline-none hover:border-ring/40 hover:shadow-md focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", false: "", }, selected: { true: "border-primary ring-2 ring-primary/25", false: "", }, disabled: { true: "pointer-events-none opacity-55", false: "", }, }, compoundVariants: [ { variant: ["default", "elevated", "outline"], tone: "info", className: "border-blue-200 dark:border-blue-900/80", }, { variant: ["default", "elevated", "outline"], tone: "success", className: "border-emerald-200 dark:border-emerald-900/80", }, { variant: ["default", "elevated", "outline"], tone: "warning", className: "border-amber-200 dark:border-amber-900/80", }, { variant: ["default", "elevated", "outline"], tone: "danger", className: "border-red-200 dark:border-red-900/80", }, { variant: "soft", tone: "info", className: "bg-blue-50 text-blue-950 dark:bg-blue-950/50 dark:text-blue-100", }, { variant: "soft", tone: "success", className: "bg-emerald-50 text-emerald-950 dark:bg-emerald-950/50 dark:text-emerald-100", }, { variant: "soft", tone: "warning", className: "bg-amber-50 text-amber-950 dark:bg-amber-950/50 dark:text-amber-100", }, { variant: "soft", tone: "danger", className: "bg-red-50 text-red-950 dark:bg-red-950/50 dark:text-red-100", }, ], defaultVariants: { variant: "default", size: "default", density: "default", tone: "neutral", interactive: false, selected: false, disabled: false, }, } ) type CardSurfaceSlots = { eyebrow?: React.ReactNode title?: React.ReactNode description?: React.ReactNode badge?: React.ReactNode action?: React.ReactNode media?: React.ReactNode content?: React.ReactNode footer?: React.ReactNode mediaClassName?: string bodyClassName?: string headerClassName?: string eyebrowClassName?: string titleClassName?: string descriptionClassName?: string contentClassName?: string footerClassName?: string } export type CardOrientation = "vertical" | "horizontal" export type CardMediaAspect = "auto" | "square" | "video" | "poster" | "wide" export type CardPadding = "none" | "sm" | "default" | "lg" export type CardProps = Omit, keyof CardSurfaceSlots> & VariantProps & CardSurfaceSlots & { orientation?: CardOrientation mediaPosition?: "start" | "end" mediaAspect?: CardMediaAspect padding?: CardPadding } function hasSurfaceContent(props: CardSurfaceSlots) { return ( props.eyebrow != null || props.title != null || props.description != null || props.badge != null || props.action != null || props.media != null || props.content != null || props.footer != null ) } function Card({ className, variant, size, density, tone, interactive, selected, disabled, orientation = "vertical", mediaPosition = "start", mediaAspect = "auto", padding = "default", tabIndex, eyebrow, title, description, badge, action, media, content, footer, mediaClassName, bodyClassName, headerClassName, eyebrowClassName, titleClassName, descriptionClassName, contentClassName, footerClassName, children, ...props }: CardProps) { const surfaceMode = hasSurfaceContent({ eyebrow, title, description, badge, action, media, content, footer, }) const resolvedContent = content ?? children const hasHeader = eyebrow != null || title != null || description != null || badge != null || action != null const hasFooter = footer != null const mediaNode = media ? (
{media}
) : null const surfacePaddingClassName = { none: "", sm: "px-4", default: "px-(--card-spacing)", lg: "px-6", } satisfies Record const bodyPaddingClassName = { none: "", sm: "pb-4", default: "", lg: "pb-6", } satisfies Record return (
{surfaceMode ? ( <> {mediaPosition === "start" ? mediaNode : null}
{hasHeader ? (
{eyebrow != null ? (
{eyebrow}
) : null} {title != null ? {title} : null} {description != null ? ( {description} ) : null}
{badge != null || action != null ? ( {badge} {action} ) : null}
) : null} {resolvedContent != null ? {resolvedContent} : null} {hasFooter ? {footer} : null}
{mediaPosition === "end" ? mediaNode : null} ) : ( children )}
) } function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardTitle({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardDescription({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardAction({ className, ...props }: React.ComponentProps<"div">) { return (
) } function CardContent({ className, ...props }: React.ComponentProps<"div">) { return
} function CardFooter({ className, ...props }: React.ComponentProps<"div">) { return (
) } export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, cardVariants, }