import { cva } from "class-variance-authority"; import React from "react"; import { Button, MiniButtonProps } from "@sparkle/components/Button"; import { LinkWrapperProps } from "@sparkle/components/LinkWrapper"; import { noHrefLink, SparkleContext, SparkleContextLinkType, } from "@sparkle/context"; import { XMarkIcon } from "@sparkle/icons/app"; import { cn } from "@sparkle/lib/utils"; export const CARD_VARIANTS = ["primary", "secondary", "tertiary"] as const; export type CardVariantType = (typeof CARD_VARIANTS)[number]; export const CARD_SIZES = ["xs", "sm", "md", "lg"] as const; export type CardSizeType = (typeof CARD_SIZES)[number]; const interactiveClasses = cn( "s-cursor-pointer", "s-transition s-duration-200", "hover:s-bg-primary-100 dark:hover:s-bg-primary-100-night", "active:s-bg-primary-150 dark:active:s-bg-primary-150-night", "disabled:s-text-primary-muted dark:disabled:s-text-primary-muted-night", "disabled:s-border-border dark:disabled:s-border-border-night", "disabled:s-pointer-events-none" ); const cardVariants = cva( cn( "s-flex s-text-left s-group", "s-border s-overflow-hidden", "s-text-foreground dark:s-text-foreground-night" ), { variants: { variant: { primary: cn( "s-bg-muted-background", "s-border-border/0", "dark:s-bg-muted-background-night", "dark:s-border-border-night/0" ), secondary: cn( "s-bg-background", "s-border-border", "dark:s-bg-background-night", "dark:s-border-border-night" ), tertiary: cn( "s-bg-background", "s-border-border/0", "dark:s-bg-background-night", "dark:s-border-border-night/0" ), }, size: { xs: "s-px-2 s-py-1.5 s-rounded-lg", sm: "s-p-3 s-rounded-xl", md: "s-p-4 s-rounded-2xl", lg: "s-p-5 s-rounded-3xl", }, selected: { true: cn( "s-border-highlight-300 dark:s-border-highlight-300-night", "s-ring-2 s-ring-highlight-200/70 dark:s-ring-highlight-300/60", "s-shadow-sm" ), false: "", }, }, defaultVariants: { variant: "primary", size: "md", selected: false, }, } ); interface CommonProps { variant?: CardVariantType; size?: CardSizeType; className?: string; selected?: boolean; } interface CardLinkProps extends CommonProps, LinkWrapperProps { onClick?: never; } interface CardButtonProps extends CommonProps, React.ButtonHTMLAttributes { href?: never; target?: never; rel?: never; replace?: never; shallow?: never; } type InnerCardProps = CardLinkProps | CardButtonProps; const InnerCard = React.forwardRef( ( { children, variant, size, className, onClick, href, target = "_blank", rel = "", replace, shallow, selected, ...props }, ref ) => { const { components } = React.useContext(SparkleContext); const Link: SparkleContextLinkType = href ? components.link : noHrefLink; // Determine if the card is interactive based on href or onClick const isInteractive = Boolean(href || onClick); const isSelected = Boolean(selected); const hasSelectionProp = typeof selected !== "undefined"; const cardButtonClassNames = cn( cardVariants({ variant, size, selected: isSelected }), // Apply interactive styles when either href or onClick is present isInteractive ? interactiveClasses : "", className ); if (href) { return ( {children} ); } return (
{children}
); } ); interface CardPropsBase { action?: React.ReactNode; containerClassName?: string; className?: string; variant?: CardVariantType; size?: CardSizeType; } interface CardPropsWithLink extends CardPropsBase, Omit { href: string; onClick?: never; } interface CardPropsWithButton extends CardPropsBase, Omit { href?: never; } export type CardProps = CardPropsWithLink | CardPropsWithButton; export const Card = React.forwardRef( ({ containerClassName, className, action, ...props }, ref) => { return (
{action && {action}}
); } ); Card.displayName = "Card"; const CardActions = React.forwardRef< HTMLDivElement, React.ComponentPropsWithoutRef<"div"> & { children: React.ReactNode; } >(({ children, ...props }, ref) => { return (
{children}
); }); CardActions.displayName = "CardActions"; export const CardActionButton = React.forwardRef< HTMLButtonElement, MiniButtonProps >(({ className, variant = "outline", icon = XMarkIcon, ...props }, ref) => { return (