laif-ds
Version:
Design System di Laif con componenti React basati su principi di Atomic Design
309 lines (261 loc) • 13.7 kB
Markdown
# AppCard
## Overview
Enhanced card component built on top of the base Card component. Provides a simplified prop-based API with built-in header, body, and footer sections, 10 visual variants (including semantic states), size control, loading skeleton state, and sub-components for advanced composition.
---
## Props
### AppCardProps
| Prop | Type | Default | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------ | ----------- | ------------------------------------------------------------------- |
| `title` | `string \| React.ReactNode` | `undefined` | Card title displayed in the header. |
| `description` | `string \| React.ReactNode` | `undefined` | Card description displayed below the title. |
| `header` | `React.ReactNode` | `undefined` | Custom header content; overrides `title` and `description`. |
| `body` | `React.ReactNode` | `undefined` | Main content rendered inside CardContent. |
| `children` | `React.ReactNode` | `undefined` | Fallback body content when `body` is not provided. |
| `actions` | `React.ReactNode` | `undefined` | Right-aligned action elements inside the header. |
| `footer` | `React.ReactNode` | `undefined` | Footer content (typically action buttons). |
| `variant` | `AppCardVariant` | `"default"` | Visual variant of the card (see table below). |
| `size` | `"sm" \| "default" \| "lg" \| "none"` | `undefined` | Controls internal padding via the base Card component. |
| `isLoading` | `boolean` | `false` | When true, renders skeleton placeholders instead of content. |
| `showHeaderBorder` | `boolean` | `false` | Displays a border between the header and body sections. |
| `showFooterBorder` | `boolean` | `false` | Displays a border between the body and footer sections. |
| `className` | `string` | `undefined` | Additional classes for the card container. |
| `headerClassName` | `string` | `undefined` | Additional classes for the header section. |
| `bodyClassName` | `string` | `undefined` | Additional classes for the body section. |
| `footerClassName` | `string` | `undefined` | Additional classes for the footer section. |
### Variant Reference
| Variant | Description |
| ------------- | ------------------------------------------------------------------------------- |
| `default` | Standard card with background, border, and subtle shadow. |
| `elevated` | Stronger shadow for layered or floating layouts. |
| `outlined` | Transparent background with visible border; no shadow. |
| `ghost` | No border, no shadow — blends seamlessly into the background. |
| `flat` | Muted background, no border or shadow; ideal for nested card contexts. |
| `interactive` | Default styling with hover shadow and border accent; pointer cursor for clicks. |
| `success` | Green-tinted semantic card for positive states and confirmations. |
| `warning` | Amber-tinted semantic card for cautionary messages and alerts. |
| `destructive` | Red-tinted semantic card for errors, danger states, or critical notices. |
| `info` | Primary-tinted semantic card for informational context and updates. |
### Size Reference
| Size | Description |
| --------- | ---------------------------------------------------- |
| `sm` | Reduced padding — compact cards and nested contexts. |
| `default` | Standard padding (base Card default). |
| `lg` | Spacious padding for prominent content areas. |
| `none` | No default padding — apply custom padding manually. |
---
## Exports
- **AppCard**: Main card component.
- **AppCardHeader**: Sub-component for composing custom headers.
- **AppCardBody**: Sub-component for composing custom body sections.
- **AppCardFooter**: Sub-component for composing custom footer sections.
- **AppCardProps**: TypeScript interface for AppCard props.
- **AppCardHeaderProps**: TypeScript interface for AppCardHeader props.
- **AppCardVariant**: Union type of all 10 variant values.
- **AppCardSize**: Union type of all size values (`"sm" | "default" | "lg" | "none"`).
---
## Behavior
- **`body` vs `children`**: `body` takes precedence. When `body` is omitted, `children` is rendered inside `CardContent`. This allows both prop-based and JSX-composition patterns.
- **`isLoading`**: When true, all section content is replaced with a skeleton showing a title placeholder, description placeholder, and three text-line placeholders. The skeleton respects the `size` and `variant` of the card.
- **`size` passthrough**: The `size` prop is forwarded to the underlying `Card` component, controlling padding and gap via the base `paddingMap`.
- **Conditional sections**: Header, body, and footer sections are only rendered if their corresponding props have content. An empty card renders just the card container.
- **Header border**: Setting `showHeaderBorder` adds `border-b` to the `CardHeader`. When absent, the body section removes its top padding to maintain visual continuity.
- **Footer border**: Setting `showFooterBorder` adds `border-b` to the body `CardContent`. When absent, the footer section removes its top padding.
- **`interactive` variant**: Adds `cursor-pointer` and hover-triggered shadow/border transitions. The card is a `div`, so attach `onClick` directly to the `AppCard` element.
- **Focus management**: The card container uses `focusRingWithin` from `designTokens`, so focusable elements inside the card display the system focus ring.
---
## Examples
### Basic
```tsx
import { AppCard, Button } from "laif-ds";
export function BasicCard() {
return (
<AppCard
title="Card Title"
description="Optional description providing additional context."
body={<p>Main content of the card.</p>}
footer={<Button>Primary Action</Button>}
className="w-[380px]"
/>
);
}
```
### Semantic Variants
```tsx
import { AppCard } from "laif-ds";
export function SemanticCards() {
return (
<div className="grid grid-cols-2 gap-4">
<AppCard
title="Operation Successful"
description="All services are running normally."
body={<p>Deployment completed in 2m 34s.</p>}
variant="success"
className="w-[300px]"
/>
<AppCard
title="Warning"
description="Storage usage is high."
body={<p>Disk at 80% capacity. Consider cleanup.</p>}
variant="warning"
className="w-[300px]"
/>
<AppCard
title="Error Detected"
description="Service is not responding."
body={<p>Payment gateway returned a 503 error.</p>}
variant="destructive"
className="w-[300px]"
/>
<AppCard
title="Update Available"
description="Version 2.4.0 is ready to install."
body={<p>Includes security patches and new features.</p>}
variant="info"
className="w-[300px]"
/>
</div>
);
}
```
### Interactive Card with onClick
```tsx
import { useState } from "react";
import { AppCard } from "laif-ds";
export function ClickableCard() {
const [count, setCount] = useState(0);
return (
<AppCard
title="Clickable Card"
description="Click anywhere on the card"
body={<p className="text-sm">Clicked {count} times.</p>}
variant="interactive"
onClick={() => setCount((c) => c + 1)}
className="w-[320px]"
/>
);
}
```
### Loading State
```tsx
import { useState } from "react";
import { AppCard, Button } from "laif-ds";
export function LoadingCard() {
const [loading, setLoading] = useState(true);
return (
<div className="flex flex-col gap-4">
<Button onClick={() => setLoading((v) => !v)}>Toggle Loading</Button>
<AppCard
title="Remote Data Card"
description="Loaded from external source"
body={<p>This is the real content.</p>}
footer={<Button>Action</Button>}
isLoading={loading}
className="w-[380px]"
/>
</div>
);
}
```
### Nested Flat Cards
```tsx
import { AppCard } from "laif-ds";
export function NestedCards() {
return (
<AppCard
title="Parent Card"
description="Contains nested flat sub-cards"
body={
<div className="flex flex-col gap-3">
<AppCard
title="Nested Item A"
body={<p className="text-sm">Flat cards have no border or shadow.</p>}
variant="flat"
size="sm"
/>
<AppCard
title="Nested Item B"
body={<p className="text-sm">Good for secondary content regions.</p>}
variant="flat"
size="sm"
/>
</div>
}
className="w-[420px]"
/>
);
}
```
### Size Variants
```tsx
import { AppCard } from "laif-ds";
export function SizeVariants() {
return (
<div className="flex flex-col gap-6 w-[400px]">
<AppCard
size="sm"
title="Size: Small"
description="Reduced padding for compact layouts"
body={<p>This card uses size="sm".</p>}
/>
<AppCard
size="default"
title="Size: Default"
description="Standard padding"
body={<p>This card uses size="default".</p>}
/>
<AppCard
size="lg"
title="Size: Large"
description="Spacious padding for prominent content"
body={<p>This card uses size="lg".</p>}
/>
</div>
);
}
```
### Children as Fallback Body
```tsx
import { AppCard } from "laif-ds";
export function ChildrenCard() {
return (
<AppCard title="Card with Children" className="w-[380px]">
<p>This content is passed as children and rendered inside CardContent.</p>
</AppCard>
);
}
```
### Advanced Composition with Sub-components
```tsx
import {
AppCard,
AppCardHeader,
AppCardBody,
AppCardFooter,
Button,
Badge,
} from "laif-ds";
export function ComposedCard() {
return (
<AppCard className="w-[380px]">
<AppCardHeader
title="Advanced Composition"
description="Using sub-components directly"
actions={<Badge>New</Badge>}
/>
<AppCardBody>
<p>Full control over each section independently.</p>
</AppCardBody>
<AppCardFooter>
<Button className="w-full">Confirm</Button>
</AppCardFooter>
</AppCard>
);
}
```
---
## Notes
- **`body` vs `children` precedence**: If both are provided, `body` wins. Use `body` for the prop-based API and `children` when composing JSX naturally.
- **`interactive` variant and accessibility**: The `interactive` variant only adds visual affordances (hover states, pointer cursor). For keyboard accessibility on a clickable card, consider wrapping with an `<a>` or `<button>` using `asChild` on a child component, or ensure the card content includes a focusable element.
- **Sub-components**: `AppCardHeader`, `AppCardBody`, and `AppCardFooter` wrap the base `CardHeader`, `CardContent`, and `CardFooter` respectively. Use them when the prop API of `AppCard` is insufficient for your layout needs.
- **vs Card**: Use `AppCard` for most card use cases — it handles conditional rendering and spacing automatically. Use the base `Card` and its sub-components directly when you need maximum layout control with no abstraction overhead.
- **Semantic variants and theming**: The semantic variant colors (`success`, `warning`, `destructive`, `info`) use CSS variable tokens that respond to light/dark mode automatically.