Card

The card was one of the more interesting cases when I was architecting this component. Originally, I was going to make a record and have defined styles for the card. After further analysis, there could be a case where thousands of cards could be used either for a eCommerece page or blog page or if a user wanted to create a full image card or partial image card. I concluded that component having flexible styling and content would be a better approach given those use cases.

Team Plan

Everything you need to collaborate with your team.

Code

// Card.tsx
import { cn } from "src/lib/utils"

interface CardCSSProperties extends React.CSSProperties {
  "--card-px": string
  "--card-py": string
}

interface CardProps extends React.ComponentPropsWithRef<"div"> {
  variant?: keyof typeof cardStyles.variant;
  behavior?: keyof typeof cardStyles.behavior;
  children: React.ReactNode
  size: "sm" | "md" | "lg" | "xl"
}

const styleMap: Record<CardProps["size"], CardCSSProperties> = {
  sm: {
        "--card-px": "1rem",
        "--card-py": "1rem",
      },
  md: {
      "--card-px": "1.5rem",
      "--card-py": "1.5rem",
      },
  lg: {
      "--card-px": "2rem",
      "--card-py": "2rem",
    },
  xl: {
    "--card-px": "2.5rem",
    "--card-py": "2.5rem",
    }
};

const cardStyles = {
  variant: {
    bordered: "rounded-md border border-border bg-card",
    flat: "rounded-md bg-muted/50 border border-transparent",
    elevated: "rounded-md bg-card border border-border/50 shadow-sm"
  },
  behavior: {
    static: "",
    interactive: "transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md cursor-pointer"
  }
};

const Card = ({
  children,
  ref,
  className,
  style,
  size = "md",
  variant = "bordered",
  behavior = "static",
  ...props} : CardProps) => {
  return (
    <div 
    className = {cn(cardStyles.variant[variant], cardStyles.behavior[behavior], className)}
    style = {{...styleMap[size], ...style}}
    ref = {ref}
    {...props}
    >
      {children}
    </div>
  )
}

export default Card

// CardHeader.tsx
import React from 'react'
import { cn } from 'src/lib/utils'

interface CardHeaderProps extends React.ComponentPropsWithRef<"div"> {
  children: React.ReactNode
}

const CardHeader = ({children, ref, className, ...props}: CardHeaderProps) => {
  return (
    <div 
    className = {cn("flex flex-col gap-1.5 pt-[var(--card-py)] px-[var(--card-px)]", className)}
    ref = {ref}
    {...props}
    >
      {children}
    </div>
  )
}

export default CardHeader

// CardTitle.tsx
import React from 'react'
import { cn } from 'src/lib/utils'

interface CardTitleProps extends React.ComponentPropsWithRef<"h3"> {
  children: React.ReactNode
}

const CardTitle = ({children, ref, className, ...props}: CardTitleProps) => {
  return (
    <h3 className = {cn('font-semibold leading-none tracking-tight', className)}
    ref = {ref}
    {...props}
    >
      {children}
    </h3>
  )
}

export default CardTitle

// CardDescription.tsx
import React from 'react'
import { cn } from 'src/lib/utils'

interface CardDescriptionProps extends React.ComponentPropsWithRef<"p"> {
  children: React.ReactNode
}

const CardDescription = ({children, ref, className, ...props}: CardDescriptionProps) => {
  return (
    <p className = {cn('text-sm text-gray-500', className)}
    ref = {ref}
    {...props}
    >
        {children}
    </p>
  )
}

export default CardDescription

// CardFooter.tsx
import { cn } from 'src/lib/utils'

interface CardFooterProps extends React.ComponentPropsWithRef<"div"> {
  children: React.ReactNode
}

const CardFooter = ({children, ref, className, ...props}: CardFooterProps) => {

  return (
    <div 
    className = {cn("pt-4 pb-[var(--card-py)] px-[var(--card-px)]", className)}
    ref = {ref}
    {...props}
    >
        {children}
    </div>
  )
}

export default CardFooter