Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
# Empty
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - empty.variants — `emptyVariants` is the cva config……
- [empty.variants](./empty.variants.md) — `emptyVariants` is the cva config that powers the `<Empty>` chassis component (with `emptyHeaderVariants`, `emptyMediaV…
- empty.variants — `emptyVariants` is the cva config……
- [empty.variants](./empty.variants.md) — `emptyVariants` is the cva config that powers the `<Empty>` chassis component (with `emptyHeaderVariants`, `emptyMediaV…
- empty.variants — `emptyVariants` is the cva config……
- [empty.variants](./empty.variants.md) — `emptyVariants` is the cva config that powers the `<Empty>` chassis component (with `emptyHeaderVariants`, `emptyMediaV…
- empty.variants — `emptyVariants` is the cva config…
- [empty.variants](./empty.variants.md) — `emptyVariants` is the cva config that powers the `<Empty>` chassis component (with `emptyHeaderVariants`, `emptyMediaV…
- [empty.variants](./empty.variants.md) — `emptyVariants` is the cva config that powers the `<Empty>` chassis component (with `emptyHeaderVariants`, `emptyMediaV…
## Purpose
`<Empty>` is the chassis empty-state primitive for `@suaveplan/react-ui`. It mirrors the upstream shadcn `empty` registry item (per Wave 3a's reconciliation finding #6) and ships as a family of six components — `<Empty>` (root), `<EmptyHeader>`, `<EmptyMedia>`, `<EmptyTitle>`, `<EmptyDescription>`, `<EmptyContent>` — that compose into the conventional empty-state layout. Per the shadcn skill rule, ALWAYS use `<Empty>` for empty states rather than building custom empty-state markup — hand-rolled empty states diverge across teams in spacing, iconography, and CTA layout.
The root `<Empty>` exposes a `size` axis with three rungs (`sm` / `default` / `lg`) that control vertical breathing room. The component renders `role="status"` with `aria-live="polite"` so assistive tech announces the empty state when it appears (e.g. when a search returns no results) without interrupting the user.
## Features
- Six composition primitives — `Empty`, `EmptyHeader`, `EmptyMedia`, `EmptyTitle`, `EmptyDescription`, `EmptyContent`.
- Three sizes via the `size` axis (`sm` / `default` / `lg`) on the root.
- `role="status"` + `aria-live="polite"` so AT announces the empty state when it appears.
- `<EmptyMedia>` is `aria-hidden="true"` because the title / description carries the accessible name.
- `<EmptyTitle>` renders as `<h3>`, `<EmptyDescription>` renders as `<p>` so the document outline stays clean.
- All sub-components forward refs.
## Usage: Basic Example
```tsx
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@suaveplan/react-ui";
import { FolderIcon } from "lucide-react";
export function NoProjects() {
return (
<Empty>
<EmptyHeader>
<EmptyMedia>
<FolderIcon className="size-6" />
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>
Get started by creating a new project.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<button type="button">Create project</button>
</EmptyContent>
</Empty>
);
}
```
## Usage: Advanced Example
```tsx
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@suaveplan/react-ui";
import { useTranslation } from "@suaveplan/react-i18n";
import { InboxIcon } from "lucide-react";
export function EmptyInbox() {
const { t } = useTranslation();
return (
<Empty size="lg">
<EmptyHeader>
<EmptyMedia className="bg-sky-500/10 text-sky-600">
<InboxIcon className="size-6" />
</EmptyMedia>
<EmptyTitle>{t("react-ui.empty.defaultTitle")}</EmptyTitle>
<EmptyDescription>
{t("react-ui.empty.defaultDescription")}
</EmptyDescription>
</EmptyHeader>
</Empty>
);
}
```
## API Reference
### `<Empty>`
`forwardRef<HTMLDivElement, EmptyProps>` — root container. Renders `role="status"` and `aria-live="polite"`.
| Prop | Type | Default | Description |
|---|---|---|---|
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Vertical breathing room. |
| _all_ `HTMLAttributes<HTMLDivElement>` | _native_ | _native_ | Forwarded as-is. |
### `<EmptyHeader>`
`forwardRef<HTMLDivElement, EmptyHeaderProps>` — flex column wrapping `<EmptyMedia>`, `<EmptyTitle>`, `<EmptyDescription>`.
### `<EmptyMedia>`
`forwardRef<HTMLDivElement, EmptyMediaProps>` — wrapper for the icon / illustration. Always `aria-hidden="true"`.
### `<EmptyTitle>`
`forwardRef<HTMLHeadingElement, EmptyTitleProps>` — heading rendered as `<h3>`.
### `<EmptyDescription>`
`forwardRef<HTMLParagraphElement, EmptyDescriptionProps>` — supplementary description rendered as `<p>`.
### `<EmptyContent>`
`forwardRef<HTMLDivElement, EmptyContentProps>` — flex row for action affordances.
#### Errors
The Empty components do NOT call `useTranslation()` so they do not interact with the i18n provider directly. The chassis ships translation keys (`react-ui.empty.defaultTitle`, `react-ui.empty.defaultDescription`, `react-ui.empty.noResults`) that consumers can use as fallbacks via `t('react-ui.empty.defaultTitle')` at the call site.
## Implementation Notes
- `role="status"` + `aria-live="polite"` is set unconditionally on the root so AT announces the empty state when it appears (e.g. when a search returns no results) without interrupting the user. Consumers needing a more aggressive announcement should wrap in their own `aria-live="assertive"` container; consumers wanting silence should pass `role="presentation"` to override.
- `<EmptyMedia>` is `aria-hidden="true"` because the icon is decorative — the accessible name comes from the title and description text.
- The chassis intentionally does NOT inject a default fallback title / description — the bundled translation keys (`react-ui.empty.defaultTitle`, `react-ui.empty.defaultDescription`) are convenience fallbacks consumers opt into via `t()`. Hardcoding fallback text would force every consumer to translate it even when their own copy already exists.
- The `size` axis affects only the root's `gap` and `p` — sub-component spacing remains constant. This keeps the visual rhythm consistent across all empty-state sizes and means consumers can swap `size` without rewriting their composition.
- `<EmptyContent>` uses `flex-wrap` so action buttons stack vertically on narrow viewports rather than overflow horizontally.