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