222 lines
4.6 KiB
TypeScript
222 lines
4.6 KiB
TypeScript
import { ReactNode } from "react";
|
|
import clsx from "clsx";
|
|
import Button from "../button/Button";
|
|
|
|
type CardVariant = "surface" | "panel" | "frosted" | "borderless" | "gradient";
|
|
type CardPadding = "none" | "xs" | "sm" | "md" | "lg";
|
|
type CardShadow = "none" | "sm" | "md";
|
|
|
|
interface CardProps {
|
|
children: ReactNode;
|
|
className?: string;
|
|
onClick?: () => void;
|
|
variant?: CardVariant;
|
|
padding?: CardPadding;
|
|
shadow?: CardShadow;
|
|
}
|
|
|
|
export const Card: React.FC<CardProps> = ({
|
|
children,
|
|
className = "",
|
|
onClick,
|
|
variant = "surface",
|
|
padding = "md",
|
|
shadow = "sm",
|
|
}) => {
|
|
const variantClasses: Record<CardVariant, string> = {
|
|
surface:
|
|
"bg-white border border-gray-200 dark:bg-white/[0.03] dark:border-white/10",
|
|
panel:
|
|
"bg-gray-50 border border-gray-100 dark:bg-white/[0.04] dark:border-white/[0.04]",
|
|
frosted:
|
|
"bg-white/80 backdrop-blur-xl border border-white/60 dark:bg-white/[0.08] dark:border-white/20",
|
|
borderless: "bg-transparent border border-transparent",
|
|
gradient:
|
|
"bg-[linear-gradient(180deg,var(--color-panel),var(--color-panel-alt))] border border-white/20 dark:border-white/10",
|
|
};
|
|
|
|
const paddingClasses: Record<CardPadding, string> = {
|
|
none: "p-0",
|
|
xs: "p-3",
|
|
sm: "p-4 sm:p-5",
|
|
md: "p-5 sm:p-6",
|
|
lg: "p-6 sm:p-8",
|
|
};
|
|
|
|
const shadowClasses: Record<CardShadow, string> = {
|
|
none: "",
|
|
sm: "shadow-theme-sm",
|
|
md: "shadow-theme-lg",
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
"rounded-2xl transition-shadow duration-200",
|
|
variantClasses[variant],
|
|
paddingClasses[padding],
|
|
shadowClasses[shadow],
|
|
className,
|
|
)}
|
|
onClick={onClick}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface CardImageProps {
|
|
src: string;
|
|
alt?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export const CardImage: React.FC<CardImageProps> = ({
|
|
src,
|
|
alt = "card",
|
|
className = "",
|
|
}) => {
|
|
return (
|
|
<div className="mb-5 overflow-hidden rounded-lg">
|
|
<img
|
|
alt={alt}
|
|
className={`overflow-hidden rounded-lg ${className}`}
|
|
src={src}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface CardTitleProps {
|
|
children: ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export const CardTitle: React.FC<CardTitleProps> = ({
|
|
children,
|
|
className = "",
|
|
}) => {
|
|
return (
|
|
<h4
|
|
className={`mb-1 font-medium text-gray-800 text-theme-xl dark:text-white/90 ${className}`}
|
|
>
|
|
{children}
|
|
</h4>
|
|
);
|
|
};
|
|
|
|
interface CardContentProps {
|
|
children: ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export const CardContent: React.FC<CardContentProps> = ({
|
|
children,
|
|
className = "",
|
|
}) => {
|
|
return (
|
|
<div className={className}>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface CardDescriptionProps {
|
|
children: ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export const CardDescription: React.FC<CardDescriptionProps> = ({
|
|
children,
|
|
className = "",
|
|
}) => {
|
|
return (
|
|
<p className={`text-sm text-gray-500 dark:text-gray-400 ${className}`}>
|
|
{children}
|
|
</p>
|
|
);
|
|
};
|
|
|
|
interface CardActionProps {
|
|
children: ReactNode;
|
|
href?: string;
|
|
onClick?: () => void;
|
|
variant?: "button" | "link";
|
|
className?: string;
|
|
}
|
|
|
|
export const CardAction: React.FC<CardActionProps> = ({
|
|
children,
|
|
href,
|
|
onClick,
|
|
variant = "button",
|
|
className = "",
|
|
}) => {
|
|
if (variant === "button") {
|
|
return (
|
|
<div className={clsx("mt-4 inline-flex", className)}>
|
|
<Button
|
|
size="sm"
|
|
variant="solid"
|
|
tone="brand"
|
|
as={href ? "a" : "button"}
|
|
href={href}
|
|
onClick={onClick}
|
|
>
|
|
{children}
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<a
|
|
href={href}
|
|
onClick={onClick}
|
|
className={clsx(
|
|
"mt-4 inline-flex items-center gap-1 text-sm font-semibold text-brand-600 hover:text-brand-700 dark:text-brand-300",
|
|
className,
|
|
)}
|
|
>
|
|
{children}
|
|
</a>
|
|
);
|
|
};
|
|
|
|
interface CardIconProps {
|
|
children: ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export const CardIcon: React.FC<CardIconProps> = ({
|
|
children,
|
|
className = "",
|
|
}) => {
|
|
return (
|
|
<div
|
|
className={`mb-5 flex h-14 max-w-14 items-center justify-center rounded-[10.5px] bg-brand-50 text-brand-500 dark:bg-brand-500/10 ${className}`}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface HorizontalCardProps {
|
|
children: ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export const HorizontalCard: React.FC<HorizontalCardProps> = ({
|
|
children,
|
|
className = "",
|
|
}) => {
|
|
return (
|
|
<div
|
|
className={`flex flex-col gap-5 rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03] sm:flex-row sm:items-center sm:gap-6 ${className}`}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
};
|
|
|