UNPKG

laif-ds

Version:

Design System di Laif con componenti React basati su principi di Atomic Design

309 lines (261 loc) 13.7 kB
# 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.