Skip to content
File

Blob: src/client/components/ui/card.tsx

typescript51 lines
1import { type HTMLAttributes, type ReactNode } from "react";
2 
3type Variant = "default" | "accent";
4 
5type CardProps = HTMLAttributes<HTMLDivElement> & {
6 variant?: Variant;
7 interactive?: boolean;
8 as?: "div" | "section" | "article";
9 children?: ReactNode;
10};
11 
12const base = "rounded-2xl p-5 sm:p-6";
13const variants: Record<Variant, string> = {
14 default: "border border-zinc-800/60 bg-zinc-900/50",
15 accent: "border border-accent-500/20 bg-gradient-to-br from-zinc-900/80 to-zinc-900/40",
16};
17 
18export function Card({ variant = "default", interactive, className = "", as = "div", children, ...rest }: CardProps) {
19 const Tag = as;
20 const interactiveCls = interactive ? "hover:-translate-y-0.5 transition-transform cursor-pointer" : "";
21 return (
22 <Tag className={`${base} ${variants[variant]} ${interactiveCls} ${className}`} {...rest}>
23 {children}
24 </Tag>
25 );
26}
27 
28export function CardHeader({ className = "", children, ...rest }: HTMLAttributes<HTMLDivElement>) {
29 return (
30 <div className={`mb-4 flex items-start justify-between gap-3 ${className}`} {...rest}>
31 {children}
32 </div>
33 );
34}
35 
36export function CardTitle({ className = "", children, ...rest }: HTMLAttributes<HTMLHeadingElement>) {
37 return (
38 <h2 className={`text-base font-semibold text-zinc-100 ${className}`} {...rest}>
39 {children}
40 </h2>
41 );
42}
43 
44export function CardDescription({ className = "", children, ...rest }: HTMLAttributes<HTMLParagraphElement>) {
45 return (
46 <p className={`text-sm text-zinc-400 ${className}`} {...rest}>
47 {children}
48 </p>
49 );
50}