UNPKG

@loke/design-system

Version:

A design system with individually importable components

303 lines (236 loc) 10.1 kB
--- name: display-components description: > Use static/presentational design system components. Card (CardHeader, CardContent, CardFooter, CardTitle, CardDescription), Text (responsive size/weight/color/decoration, truncate, clamped line-clamp), Heading (semantic h1-h6 + display/title/subtitle/section variants, decoupled as and variant props), Badge (default/secondary/destructive/outline), Avatar (AvatarImage, AvatarFallback, AvatarBadge, AvatarGroup), Separator, Spinner (sm/default/lg/xl), Skeleton. Activate when rendering display content, typography, or status indicators. type: core library: '@loke/design-system' library_version: '2.0.0-rc.6' requires: - getting-started sources: - 'LOKE/merchant-frontends:packages/design-system/src/components/card' - 'LOKE/merchant-frontends:packages/design-system/src/components/text' - 'LOKE/merchant-frontends:packages/design-system/src/components/heading' - 'LOKE/merchant-frontends:packages/design-system/src/components/badge' - 'LOKE/merchant-frontends:packages/design-system/src/components/avatar' - 'LOKE/merchant-frontends:packages/design-system/src/components/separator' - 'LOKE/merchant-frontends:packages/design-system/src/components/spinner' - 'LOKE/merchant-frontends:packages/design-system/src/components/skeleton' --- # Display Components This skill builds on **getting-started**. Read it first for setup and imports. ## Setup Each component is imported from its own subpath: ```tsx import { Card, CardHeader, CardContent, CardFooter, CardTitle, CardDescription } from "@loke/design-system/card"; import { Text } from "@loke/design-system/text"; import { Heading } from "@loke/design-system/heading"; import { Badge } from "@loke/design-system/badge"; import { Avatar, AvatarImage, AvatarFallback, AvatarBadge, AvatarGroup, AvatarGroupCount } from "@loke/design-system/avatar"; import { Separator } from "@loke/design-system/separator"; import { Spinner } from "@loke/design-system/spinner"; import { Skeleton } from "@loke/design-system/skeleton"; ``` Combined usage example: ```tsx <Card> <CardHeader> <CardTitle>Monthly Revenue</CardTitle> <CardDescription>Compared to last month</CardDescription> </CardHeader> <CardContent> <Heading as="h3" variant="display">$12,400</Heading> <Text size="sm" color="muted">Up 8% from October</Text> </CardContent> <CardFooter> <Badge variant="secondary">Verified</Badge> </CardFooter> </Card> ``` ## Core Patterns ### Card composition `Card` is a layout shell. Use `CardHeader` + `CardTitle` + `CardDescription` for the top section, `CardContent` for the body, and `CardFooter` for actions or summary info. Both `CardHeader` and `CardFooter` accept a `centered` prop. ```tsx <Card> <CardHeader centered> <CardTitle>Order #4821</CardTitle> <CardDescription>Placed on 1 April 2026</CardDescription> </CardHeader> <CardContent> <Text>2 items · $48.00</Text> </CardContent> <CardFooter centered> <Badge>Fulfilled</Badge> </CardFooter> </Card> ``` ### Typography with Text and Heading `Text` renders a `<span>` with a full variant system. All props are responsive (pass a plain value or `{ base, sm, md, lg }` breakpoint object). ```tsx // Size, weight, color <Text size="sm" weight="semibold" color="muted">Invoice #1042</Text> // Text transform and alignment <Text transform="uppercase" size="xs" weight="medium" color="primary"> Category </Text> // Truncate a single line <Text truncate>A very long product name that should be cut off with an ellipsis</Text> // Clamp to N lines <Text clamped={3}> A long description that should be clamped to exactly three lines before being hidden, regardless of how much content follows </Text> // Responsive size <Text size={{ base: "sm", md: "base", lg: "lg" }}>Responsive body copy</Text> ``` `Text` prop reference: - `size`: `xs` | `sm` | `base` | `lg` | `xl` | `2xl` | `3xl` | `4xl` - `weight`: `light` | `normal` | `medium` | `semibold` | `bold` - `color`: `foreground` | `muted` | `primary` | `secondary` | `accent` | `destructive` | `card` | `popover` | `white` - `align`: `left` | `center` | `right` | `justify` - `decoration`: `none` | `underline` | `line-through` | `italic` - `lineHeight`: `none` | `tight` | `snug` | `normal` | `relaxed` | `loose` - `transform`: `normal` | `uppercase` | `lowercase` | `capitalize` - `whiteSpace`: `normal` | `nowrap` | `pre` | `pre-line` | `pre-wrap` - `wordBreak`: `normal` | `words` | `all` | `keep` - `truncate`: `boolean` — single-line ellipsis - `clamped`: `number` — multi-line clamp via `-webkit-line-clamp` `Heading` renders a semantic heading element with visual styling decoupled from the HTML tag. Use `as` to control the DOM element and `variant` to control appearance independently. ```tsx // Defaults: as="h2", variant matches the as tag <Heading>Section Title</Heading> // Decouple visual appearance from semantic level <Heading as="h3" variant="display">Hero-sized text in an h3</Heading> <Heading as="h1" variant="subtitle">Large subtitle styling on the page h1</Heading> // Neutral color override <Heading as="h2" variant="h2" color="n-500">Muted heading</Heading> ``` `Heading` variant reference: - `h1` – `text-4xl font-extrabold` - `h2` – `text-3xl font-bold` - `h3` – `text-2xl font-semibold` - `h4` – `text-xl font-semibold` - `h5` – `text-lg font-medium` - `h6` – `text-base font-medium` - `display` – `text-5xl font-extrabold` (hero/marketing) - `title` – `text-4xl font-bold` - `subtitle` – `text-2xl font-medium text-foreground/80` - `section` – `text-xl font-semibold text-primary` `Heading` color reference: `foreground` | `accent` | `card` | `destructive` | `popover` | `primary` | `secondary` | `n-50` through `n-950` (zinc scale) ### Avatar with fallback `Avatar` accepts a `size` prop (`sm` | `default` | `lg`). Always pair `AvatarImage` with `AvatarFallback` — fallback shows automatically when the image fails or is absent. ```tsx // Single avatar with badge <Avatar size="lg"> <AvatarImage src="/users/jane.jpg" alt="Jane Smith" /> <AvatarFallback>JS</AvatarFallback> <AvatarBadge /> </Avatar> // Group of avatars with overflow count <AvatarGroup> <Avatar> <AvatarImage src="/users/alice.jpg" alt="Alice" /> <AvatarFallback>AL</AvatarFallback> </Avatar> <Avatar> <AvatarImage src="/users/bob.jpg" alt="Bob" /> <AvatarFallback>BO</AvatarFallback> </Avatar> <AvatarGroupCount>+4</AvatarGroupCount> </AvatarGroup> ``` `AvatarBadge` renders a small dot indicator at the bottom-right of the avatar. Pass icon children to `AvatarBadge` for a status icon — the icon is automatically hidden at `sm` size. ### Status indicators **Badge** — four variants, all `inline-flex` and pill-shaped. Pass `onRemove` to make the badge a removable button with an X icon. ```tsx <Badge>Active</Badge> <Badge variant="secondary">Draft</Badge> <Badge variant="destructive">Error</Badge> <Badge variant="outline">Beta</Badge> // Removable badge <Badge variant="secondary" onRemove={() => removeTag(id)}> electronics </Badge> ``` **Spinner** — loading indicator using an animated `LoaderCircle` icon. Renders an `<output>` element with `aria-label="Loading"`. ```tsx <Spinner /> {/* md size, primary color */} <Spinner size="sm" /> <Spinner size="lg" color="secondary" /> <Spinner size="xl" color="accent" /> ``` `Spinner` size reference: `sm` (16px) | `md` (24px, default) | `lg` (32px) | `xl` (48px) `Spinner` color reference: `primary` | `secondary` | `accent` **Skeleton** — animated pulse placeholder. Size it with `className` to match the content it replaces. ```tsx {/* Text line placeholders */} <Skeleton className="h-4 w-48" /> <Skeleton className="h-4 w-32" /> {/* Card layout placeholder */} <Card> <CardHeader> <Skeleton className="h-6 w-40" /> <Skeleton className="h-4 w-64" /> </CardHeader> <CardContent> <Skeleton className="h-32 w-full" /> </CardContent> </Card> {/* Avatar placeholder */} <Skeleton className="size-8 rounded-full" /> ``` **Separator** — horizontal by default, pass `orientation="vertical"` for inline use. ```tsx <Separator /> <Separator orientation="vertical" className="h-6" /> ``` ## Common Mistakes ### CRITICAL: Using native HTML elements instead of design system equivalents Wrong: ```tsx <h2 className="text-3xl font-bold">Section title</h2> <span className="text-sm text-gray-500">Supporting text</span> ``` Right: ```tsx <Heading as="h2">Section title</Heading> <Text size="sm" color="muted">Supporting text</Text> ``` Native elements bypass the variant system, semantic tokens, and responsive props. This applies everywhere display content appears — cross-skill with interactive-components for the same issue in form labels and button text. ### MEDIUM: Not decoupling Heading `as` and `variant` props Wrong: ```tsx {/* Reaching for raw className to get a large visual on a semantic h3 */} <h3 className="text-5xl font-extrabold">Hero Headline</h3> ``` Right: ```tsx <Heading as="h3" variant="display">Hero Headline</Heading> ``` The `as` prop controls the DOM element (and thus document outline / accessibility). The `variant` prop controls only visual appearance. They are fully independent — any combination is valid. ### MEDIUM: Building custom truncation instead of using Text props Wrong: ```tsx <span className="overflow-hidden text-ellipsis whitespace-nowrap block"> Long product name </span> <p className="line-clamp-3"> Long description text that needs clamping </p> ``` Right: ```tsx <Text truncate>Long product name</Text> <Text clamped={3}>Long description text that needs clamping</Text> ``` `truncate` handles single-line ellipsis. `clamped={n}` handles multi-line clamp with `-webkit-line-clamp`. Neither requires additional className overrides. ## See also - `theming/SKILL.md` — customizing component appearance with semantic tokens - `interactive-components/SKILL.md` — Button, Input, and other interactive elements