109 lines
2.7 KiB
TypeScript
109 lines
2.7 KiB
TypeScript
// src/components/ui/Card.tsx
|
|
'use client';
|
|
|
|
import * as React from 'react';
|
|
import clsx from 'clsx';
|
|
|
|
export type CardVariant =
|
|
| 'default' // normale Card, gerundet, Schatten
|
|
| 'edgeToEdge' // Kante-zu-Kante auf Mobile, rounded ab sm:
|
|
| 'well' // "Well" auf weißem Hintergrund
|
|
| 'wellOnGray' // Well auf grauem Hintergrund
|
|
| 'wellEdgeToEdge'; // Well, edge-to-edge auf Mobile
|
|
|
|
export interface CardProps
|
|
extends React.HTMLAttributes<HTMLDivElement> {
|
|
variant?: CardVariant;
|
|
/** Trenne Header/Body/Footer mit divide-y */
|
|
divided?: boolean;
|
|
}
|
|
|
|
function CardRoot({
|
|
variant = 'default',
|
|
divided = false,
|
|
className,
|
|
...props
|
|
}: CardProps) {
|
|
const base = 'overflow-hidden';
|
|
|
|
const variantClasses = (() => {
|
|
switch (variant) {
|
|
case 'edgeToEdge':
|
|
return 'bg-white shadow-sm sm:rounded-lg dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10';
|
|
case 'well':
|
|
return 'rounded-lg bg-gray-50 dark:bg-gray-800/50';
|
|
case 'wellOnGray':
|
|
return 'rounded-lg bg-gray-200 dark:bg-gray-800/50';
|
|
case 'wellEdgeToEdge':
|
|
return 'bg-gray-50 sm:rounded-lg dark:bg-gray-800/50';
|
|
case 'default':
|
|
default:
|
|
return 'rounded-lg bg-white shadow-sm dark:bg-gray-800/50 dark:shadow-none dark:outline dark:-outline-offset-1 dark:outline-white/10';
|
|
}
|
|
})();
|
|
|
|
const divideClasses = divided
|
|
? 'divide-y divide-gray-200 dark:divide-white/10'
|
|
: '';
|
|
|
|
return (
|
|
<div
|
|
className={clsx(base, variantClasses, divideClasses, className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
type SectionProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
/** Grau hinterlegt (Body / Footer) wie in deinen Beispielen */
|
|
muted?: boolean;
|
|
};
|
|
|
|
function CardHeader({ className, muted, ...props }: SectionProps) {
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'px-4 py-5 sm:px-6',
|
|
muted && 'bg-gray-50 dark:bg-gray-800/50',
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function CardBody({ className, muted, ...props }: SectionProps) {
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'px-4 py-5 sm:p-6',
|
|
muted && 'bg-gray-50 dark:bg-gray-800/50',
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function CardFooter({ className, muted, ...props }: SectionProps) {
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'px-4 py-4 sm:px-6',
|
|
muted && 'bg-gray-50 dark:bg-gray-800/50',
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
// Default-Export mit statischen Subkomponenten: <Card>, <Card.Header> etc.
|
|
export const Card = Object.assign(CardRoot, {
|
|
Header: CardHeader,
|
|
Body: CardBody,
|
|
Footer: CardFooter,
|
|
});
|
|
|
|
export default Card;
|