diff --git a/.specs/empty-state.md b/.specs/empty-state.md index 15df28fd9..165f4bc60 100644 --- a/.specs/empty-state.md +++ b/.specs/empty-state.md @@ -3,20 +3,20 @@ name: empty-state category: feedback structure: monolithic status: approved -spec_version: 1 +spec_version: 2 figma: url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=446-864 node_id: 446:864 -checksum: 29f1b8b4fe22c0f15d54fbc3611a02685f3d3cf8133ec27d7be66ce12549d023 +checksum: 4ee0f68c74c6046d625e69c006af16abf34e63dfac4b3d569eb229ac0207cd20 created: 2026-06-25 -last_updated: 2026-06-25 +last_updated: 2026-07-22 --- # Empty State — Component Spec ## Purpose -Centered placeholder shown when a resource list or section has no content yet. Stacks a decorative illustration, a title, an optional description, and a consumer-composed actions area (buttons + documentation link) to guide the user toward a first action. Unlike `Message`, which is an inline severity banner, `EmptyState` is a full-region zero-data state and renders either inside a bordered surface card or on a plain transparent background. +Centered placeholder shown when a resource list or section has no content yet. Stacks an optional standardized adornment, a title, an optional description, and a consumer-composed actions area (buttons + documentation link) to guide the user toward a first action. The adornment is a standardized, size-scaled featured-icon tile driven by the `icon` prop; the consumer may instead supply custom content via the `icon` slot, or omit both for no adornment. Unlike `Message`, which is an inline severity banner, `EmptyState` is a full-region zero-data state and renders either inside a bordered surface card or on a plain transparent background. ## Usage @@ -50,8 +50,9 @@ import MiniButton from '@aziontech/webkit/mini-button' |---|---|---|---|---| | `title` | `string` | `—` | yes | Primary heading announcing the empty resource. | | `description` | `string` | `''` | no | Supporting body copy below the title. | -| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | no | Size token; affects the illustration size, the surrounding padding, and the gaps between illustration, text, and actions. | -| `bordered` | `boolean` | `false` | no | When true, wraps the content in a bordered surface card; otherwise renders on a transparent background. | +| `icon` | `string` | `''` | no | PrimeIcons/Azion icon class for the standardized adornment (e.g. `pi pi-inbox`). When set, renders a size-scaled featured-icon tile; when empty and the `icon` slot is unused, no adornment renders. | +| `size` | `'small' \| 'medium'` | `'medium'` | no | Size token (subset — only `small` and `medium`; no `large`). Scales the whole block on one harmonic ramp — the adornment size, the title and description typography, the surrounding padding, and the gaps between adornment, text, and actions all step up together. | +| `bordered` | `boolean` | `false` | no | When true, wraps the content in a dashed-bordered surface card; otherwise renders on a transparent background. | ## Events @@ -61,7 +62,7 @@ import MiniButton from '@aziontech/webkit/mini-button' | Slot | Scope | Notes | |---|---|---| -| `icon` | — | Decorative illustration centered above the title; rendered inside an `aria-hidden` container. Defaults to the bundled `EmptyStateIllustration` sub-component when not provided. | +| `icon` | — | Custom adornment centered above the title, rendered inside an `aria-hidden` container. Overrides the `icon` prop's featured-icon tile. When neither this slot nor the `icon` prop is provided, no adornment renders. | | `actions` | — | Action area below the description; consumer composes buttons and the documentation link. | ## States @@ -78,23 +79,36 @@ _none_ | Region | Token (DESIGN.md) | |---|---| -| title typography | `.text-body-sm` | -| description typography | `.text-body-xs` | +| title typography | per-`size` — see harmonic scale below | +| description typography | per-`size` — see harmonic scale below | | surface (bordered) | `var(--bg-surface)` | -| border (bordered) | `var(--border-default)` | +| border (bordered) | `var(--border-default)` (dashed, `var(--border-width-default)`) | | title text | `var(--text-default)` | | description text | `var(--text-muted)` | +| adornment tile surface | `var(--bg-surface)` | +| adornment tile border | `var(--border-default)` | +| adornment decorative layers | `var(--bg-canvas)` · `var(--border-strong)` | +| adornment shape | `var(--shape-card)` · `var(--shape-elements)` | +| adornment icon | `var(--text-default)` | | shape (bordered) | `var(--shape-button)` | -| spacing (padding, medium) | `var(--spacing-8)` (x) · `var(--spacing-12)` (y) | -| spacing (stack gap, medium) | `var(--spacing-6)` | | spacing (title gap) | `var(--spacing-xs)` | | spacing (actions gap) | `var(--spacing-xs)` | -## Theme gaps +### Harmonic scale (per `size`) + +Every axis steps up one rung per size on a single harmonic ramp. The title climbs the **heading** scale (`heading` tokens only — never `body`); the description climbs the **body** scale one rung below it, so the pair stays in proportion at every size. -| Figma variable | Temporary primitive | Follow-up | +| Axis | `small` | `medium` | |---|---|---| -| `Typography/Heading/xss` (title, 14px regular) | `.text-body-sm` (14px regular — size/weight match) | `TODO: catalogar` — naming-only gap; no heading-named token exists at 14px. | +| title typography | `.text-heading-xxs` (14px) | `.text-heading-sm` (14 → 18px) | +| description typography | `.text-body-xs` (12px) | `.text-body-sm` (14px) | +| adornment tile | `size-8` (32px) | `size-10` (40px) | +| padding (x · y) | `var(--spacing-6)` · `var(--spacing-8)` | `var(--spacing-8)` · `var(--spacing-12)` | +| stack gap | `var(--spacing-4)` | `var(--spacing-6)` | + +## Theme gaps + +_none_ — the title now uses `heading` tokens at every size (`.text-heading-xxs` is 14px regular, matching the Figma `Typography/Heading/xss` title), so the previous body-token workaround is retired. ## Accessibility (WCAG 2.1 AA) @@ -107,8 +121,10 @@ _none_ ## Stories (Storybook) -- Default -- Sizes — composite story rendering every `size` value side by side. +- Default — renders the standardized featured-icon tile via the `icon` prop. +- Sizes — composite story rendering every `size` value side by side (adornment scales with `size`). +- Icon — the standardized featured-icon tile adornment driven by the `icon` prop; justified because the tile is the primary adornment path and must be documented distinctly from custom slot content. +- NoAdornment — omits both the `icon` prop and the `icon` slot; justified because "no adornment" is a distinct rendered state (the adornment container does not render) that the default illustration previously masked. - Bordered — state story (args delta `bordered: true`); justified because the bordered surface card is a distinct top-level Figma frame (446:864) that materially changes the rendered container. ## Constraints — DO NOT diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--dark-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--dark-mobile.png index dd0990a61..0c961a253 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--dark-mobile.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--dark-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--dark-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--dark-tablet.png index 426f94cd6..a9cd0ff1b 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--dark-tablet.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--dark-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-desktop.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-desktop.png index 74aa87534..d165fb7a6 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-desktop.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-desktop.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-mobile.png index 4fb65d2e7..d84cf89d0 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-mobile.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-tablet.png index b18f26f32..0bf32dabd 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-tablet.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty--light-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty.png index 6c6f0153b..fa136f203 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-data-table--empty.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--dark-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--dark-mobile.png index 2d0751c8d..11c9e660c 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--dark-mobile.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--dark-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--dark-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--dark-tablet.png index bb0769b44..b17c4d6ed 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--dark-tablet.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--dark-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-desktop.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-desktop.png index 43ef766c9..e412b7d98 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-desktop.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-desktop.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-mobile.png index d5fbee8b7..995a3624a 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-mobile.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-tablet.png index 2b4a04a83..049ef416d 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-tablet.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered--light-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered.png index 0e0244da5..d63a628cb 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--bordered.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--dark-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--dark-mobile.png index ef11d26a0..063a1b029 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--dark-mobile.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--dark-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--dark-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--dark-tablet.png index 35d4fe410..2a8f4c773 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--dark-tablet.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--dark-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-desktop.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-desktop.png index beaabe9a8..ecdd42974 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-desktop.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-desktop.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-mobile.png index 02a004900..52d38f352 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-mobile.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-tablet.png index 588405f94..42bd81309 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-tablet.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default--light-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default.png index d855d6723..140beb148 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--default.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--dark-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--dark-mobile.png new file mode 100644 index 000000000..063a1b029 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--dark-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--dark-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--dark-tablet.png new file mode 100644 index 000000000..2a8f4c773 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--dark-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-desktop.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-desktop.png new file mode 100644 index 000000000..ecdd42974 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-desktop.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-mobile.png new file mode 100644 index 000000000..52d38f352 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-tablet.png new file mode 100644 index 000000000..42bd81309 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon--light-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon.png new file mode 100644 index 000000000..140beb148 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--icon.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--dark-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--dark-mobile.png new file mode 100644 index 000000000..1c4230a10 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--dark-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--dark-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--dark-tablet.png new file mode 100644 index 000000000..20824803e Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--dark-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-desktop.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-desktop.png new file mode 100644 index 000000000..3c43ce53e Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-desktop.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-mobile.png new file mode 100644 index 000000000..e7b649158 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-tablet.png new file mode 100644 index 000000000..6291c2476 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment--light-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment.png new file mode 100644 index 000000000..fdd4f1473 Binary files /dev/null and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--no-adornment.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--dark-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--dark-mobile.png index b2e6460f9..e0aeffb58 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--dark-mobile.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--dark-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--dark-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--dark-tablet.png index ea7951730..0ef14e9d7 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--dark-tablet.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--dark-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-desktop.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-desktop.png index 64170b30b..c671f7e23 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-desktop.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-desktop.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-mobile.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-mobile.png index 4463c26f5..9d9ae8f94 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-mobile.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-mobile.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-tablet.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-tablet.png index 26fa0a282..dfe2a1990 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-tablet.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes--light-tablet.png differ diff --git a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes.png b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes.png index 340120d61..1e3fbaea1 100644 Binary files a/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes.png and b/apps/storybook/.storybook/test-visual/__image_snapshots__/linux/components-feedback-emptystate--sizes.png differ diff --git a/apps/storybook/src/stories/components/feedback/empty-state/EmptyState.stories.js b/apps/storybook/src/stories/components/feedback/empty-state/EmptyState.stories.js index 59873f0dd..b8c1340be 100644 --- a/apps/storybook/src/stories/components/feedback/empty-state/EmptyState.stories.js +++ b/apps/storybook/src/stories/components/feedback/empty-state/EmptyState.stories.js @@ -39,7 +39,7 @@ const meta = { docs: { description: { component: - 'A full-region placeholder shown when a list or section has no content yet. It stacks a decorative illustration (the bundled `EmptyStateIllustration` by default), a title, an optional description, and a consumer-composed actions area, and renders on a plain background or inside a bordered surface card (`bordered`).' + 'A full-region placeholder shown when a list or section has no content yet. It stacks an optional standardized featured-icon tile (driven by the `icon` prop), a title, an optional description, and a consumer-composed actions area, and renders on a plain background or inside a bordered surface card (`bordered`). Supply custom adornment content via the `icon` slot, or omit both `icon` prop and slot for no adornment.' }, canvas: { sourceState: 'shown' } } @@ -57,27 +57,27 @@ const meta = { }, size: { control: 'select', - options: ['small', 'medium', 'large'], + options: ['small', 'medium'], description: - 'Size token; affects the illustration size, the surrounding padding, and the gaps between illustration, text, and actions.', + 'Size token (small and medium only); scales the adornment, the title and description typography, the surrounding padding, and the gaps on one harmonic ramp.', table: { category: 'props', - type: { summary: "'small' | 'medium' | 'large'" }, + type: { summary: "'small' | 'medium'" }, defaultValue: { summary: "'medium'" } } }, + icon: { + control: 'text', + description: + 'PrimeIcons/Azion icon class for the standardized adornment; renders a size-scaled featured-icon tile. The `icon` slot overrides it; omit both for no adornment.', + table: { category: 'props', type: { summary: 'string' }, defaultValue: { summary: "''" } } + }, bordered: { control: 'boolean', description: 'When true, wraps the content in a bordered surface card; otherwise renders on a transparent background.', table: { category: 'props', type: { summary: 'boolean' }, defaultValue: { summary: 'false' } } }, - icon: { - control: false, - description: - 'Decorative illustration centered above the title; rendered inside an aria-hidden container. Defaults to the bundled EmptyStateIllustration.', - table: { category: 'slots', type: { summary: 'slot' } } - }, actions: { control: false, description: @@ -88,6 +88,7 @@ const meta = { args: { title: 'No resource yet', description: 'Get started by creating your first resource.', + icon: 'pi pi-inbox', size: 'medium', bordered: false } @@ -108,6 +109,7 @@ ${ACTIONS_MARKUP} const DEFAULT_MARKUP = ` ${ACTIONS_MARKUP} ` @@ -118,7 +120,7 @@ export const Default = { parameters: { docs: { description: { - story: 'Default empty state: the illustration tile, title, description, and actions.' + story: 'Default empty state: the standardized featured-icon tile, title, description, and actions.' }, source: { code: toSfc(IMPORTS, DEFAULT_MARKUP) } } @@ -127,11 +129,12 @@ export const Default = { const SIZES_TEMPLATE = `
${ACTIONS_MARKUP} @@ -143,15 +146,59 @@ export const Sizes = { parameters: { docs: { controls: { disable: true }, - description: { story: 'The three size variants — small, medium, large — stacked.' }, + description: { story: 'The size variants — small and medium — stacked.' }, source: { code: toSfc(IMPORTS, SIZES_TEMPLATE) } } } } +const ICON_MARKUP = ` +${ACTIONS_MARKUP} +` + +/** @type {import('@storybook/vue3').StoryObj} */ +export const Icon = { + args: { icon: 'pi pi-inbox' }, + render: Template, + parameters: { + docs: { + description: { + story: 'The standardized featured-icon tile adornment, driven by the `icon` prop.' + }, + source: { code: toSfc(IMPORTS, ICON_MARKUP) } + } + } +} + +const NO_ADORNMENT_MARKUP = ` +${ACTIONS_MARKUP} +` + +/** @type {import('@storybook/vue3').StoryObj} */ +export const NoAdornment = { + args: { icon: '' }, + render: Template, + parameters: { + docs: { + description: { + story: 'No adornment: neither the `icon` prop nor the `icon` slot is provided, so the adornment container does not render.' + }, + source: { code: toSfc(IMPORTS, NO_ADORNMENT_MARKUP) } + } + } +} + const BORDERED_MARKUP = ` ${ACTIONS_MARKUP} diff --git a/packages/webkit/catalog.json b/packages/webkit/catalog.json index 599757332..3ffcfafa9 100644 --- a/packages/webkit/catalog.json +++ b/packages/webkit/catalog.json @@ -4242,26 +4242,33 @@ "required": "no", "doc": "Supporting body copy below the title." }, + { + "name": "icon", + "type": "string", + "default": "''", + "required": "no", + "doc": "PrimeIcons/Azion icon class for the standardized adornment (e.g. `pi pi-inbox`). When set, renders a size-scaled featured-icon tile; when empty and the `icon` slot is unused, no adornment renders." + }, { "name": "size", - "type": "'small' | 'medium' | 'large'", + "type": "'small' | 'medium'", "default": "'medium'", "required": "no", - "doc": "Size token; affects the illustration size, the surrounding padding, and the gaps between illustration, text, and actions." + "doc": "Size token (subset — only `small` and `medium`; no `large`). Scales the whole block on one harmonic ramp — the adornment size, the title and description typography, the surrounding padding, and the gaps between adornment, text, and actions all step up together." }, { "name": "bordered", "type": "boolean", "default": "false", "required": "no", - "doc": "When true, wraps the content in a bordered surface card; otherwise renders on a transparent background." + "doc": "When true, wraps the content in a dashed-bordered surface card; otherwise renders on a transparent background." } ], "slots": [ { "name": "icon", "scope": "—", - "notes": "Decorative illustration centered above the title; rendered inside an `aria-hidden` container. Defaults to the bundled `EmptyStateIllustration` sub-component when not provided." + "notes": "Custom adornment centered above the title, rendered inside an `aria-hidden` container. Overrides the `icon` prop's featured-icon tile. When neither this slot nor the `icon` prop is provided, no adornment renders." }, { "name": "actions", @@ -4269,7 +4276,7 @@ "notes": "Action area below the description; consumer composes buttons and the documentation link." } ], - "purpose": "Centered placeholder shown when a resource list or section has no content yet. Stacks a decorative illustration, a title, an optional description, and a consumer-composed actions area (buttons + documentation link) to guide the user toward a first action. Unlike `Message`, which is an inline severity banner, `EmptyState` is a full-region zero-data state and renders either inside a bordered surface card or on a plain transparent background." + "purpose": "Centered placeholder shown when a resource list or section has no content yet. Stacks an optional standardized adornment, a title, an optional description, and a consumer-composed actions area (buttons + documentation link) to guide the user toward a first action. The adornment is a standardized, size-scaled featured-icon tile driven by the `icon` prop; the consumer may instead supply custom content via the `icon` slot, or omit both for no adornment. Unlike `Message`, which is an inline severity banner, `EmptyState` is a full-region zero-data state and renders either inside a bordered surface card or on a plain transparent background." }, "status-indicator": { "import": "@aziontech/webkit/status-indicator", diff --git a/packages/webkit/src/components/data/table/table.vue b/packages/webkit/src/components/data/table/table.vue index 993085a2a..e7ad26f7d 100644 --- a/packages/webkit/src/components/data/table/table.vue +++ b/packages/webkit/src/components/data/table/table.vue @@ -19,8 +19,8 @@ import { useDebounceFn } from '@vueuse/core' import { computed, getCurrentInstance, provide, ref, useAttrs, useSlots, watch } from 'vue' - import EmptyIllustration from '../../../svg/illustration-layers/illustration-layers.vue' import { downloadCsv, toCsv } from '../../../utils/csv' + import EmptyState from '../../feedback/empty-state/empty-state.vue' import Skeleton from '../../feedback/skeleton/skeleton.vue' import Checkbox from '../../inputs/checkbox/checkbox.vue' import ScrollArea from '../../layout/scroll-area/scroll-area.vue' @@ -1128,16 +1128,15 @@ v-if="!loading && rows.length === 0" role="presentation" :data-testid="`${testId}__empty`" - class="flex w-full flex-col items-center justify-center gap-[var(--spacing-6)] px-[var(--spacing-8)] py-[var(--spacing-12)] text-center" + class="w-full" > - -
-

No results yet

-

- Get started by creating your first resource. -

-
+
diff --git a/packages/webkit/src/components/feedback/empty-state/empty-state.test.ts b/packages/webkit/src/components/feedback/empty-state/empty-state.test.ts index afcf8d793..7aa062811 100644 --- a/packages/webkit/src/components/feedback/empty-state/empty-state.test.ts +++ b/packages/webkit/src/components/feedback/empty-state/empty-state.test.ts @@ -28,7 +28,7 @@ describe('EmptyState', () => { it('honours a consumer-supplied data-testid across the whole subtree', () => { const { getByTestId } = within( render(EmptyState, { - props: { title: 'Empty' }, + props: { title: 'Empty', icon: 'pi pi-inbox' }, attrs: { 'data-testid': 'custom-empty' } }).container ) @@ -51,19 +51,27 @@ describe('EmptyState', () => { expect(withoutDesc.queryByTestId('feedback-empty-state__description')).toBeNull() }) - it('marks the icon container aria-hidden and renders the default illustration', () => { - const { getByTestId } = within(render(EmptyState, { props: { title: 'Empty' } }).container) + it('renders no adornment when neither the icon prop nor the icon slot is provided', () => { + const { queryByTestId } = within(render(EmptyState, { props: { title: 'Empty' } }).container) + + expect(queryByTestId('feedback-empty-state__icon')).toBeNull() + }) + + it('renders the standardized featured-icon tile when the icon prop is set', () => { + const { getByTestId } = within( + render(EmptyState, { props: { title: 'Empty', icon: 'pi pi-inbox' } }).container + ) const icon = getByTestId('feedback-empty-state__icon') expect(icon.getAttribute('aria-hidden')).toBe('true') - // Default slot renders the bundled illustration (an svg/element inside the tile). - expect(icon.children.length).toBeGreaterThan(0) + // The tile carries the consumer-supplied icon glyph. + expect(icon.querySelector('i.pi.pi-inbox')).toBeTruthy() }) - it('renders custom icon slot content in place of the default illustration', () => { + it('renders custom icon slot content, overriding the icon prop tile', () => { const { getByTestId } = within( render(EmptyState, { - props: { title: 'Empty' }, + props: { title: 'Empty', icon: 'pi pi-inbox' }, slots: { icon: 'glyph' } }).container ) @@ -71,6 +79,8 @@ describe('EmptyState', () => { const icon = getByTestId('feedback-empty-state__icon') expect(icon.getAttribute('aria-hidden')).toBe('true') expect(getByTestId('my-icon').textContent).toBe('glyph') + // Slot wins: the prop's default tile glyph is not rendered. + expect(icon.querySelector('i.pi.pi-inbox')).toBeNull() }) it('renders the actions region only when the actions slot is supplied', () => { @@ -101,15 +111,15 @@ describe('EmptyState', () => { const def = within(render(EmptyState, { props: { title: 'Empty' } }).container) expect(def.getByTestId('feedback-empty-state').getAttribute('data-size')).toBe('medium') - const large = within(render(EmptyState, { props: { title: 'Empty', size: 'large' } }).container) - expect(large.getByTestId('feedback-empty-state').getAttribute('data-size')).toBe('large') + const small = within(render(EmptyState, { props: { title: 'Empty', size: 'small' } }).container) + expect(small.getByTestId('feedback-empty-state').getAttribute('data-size')).toBe('small') }) - it.each(['small', 'medium', 'large'] as const)( + it.each(['small', 'medium'] as const)( 'mirrors size=%s onto data-size of the root and the icon tile', (size) => { const { getByTestId } = within( - render(EmptyState, { props: { title: 'Empty', size } }).container + render(EmptyState, { props: { title: 'Empty', size, icon: 'pi pi-inbox' } }).container ) expect(getByTestId('feedback-empty-state').getAttribute('data-size')).toBe(size) expect(getByTestId('feedback-empty-state__icon').getAttribute('data-size')).toBe(size) @@ -150,10 +160,10 @@ describe('EmptyState', () => { expect(getByTestId('feedback-empty-state').getAttribute('data-bordered')).toBe('true') }) - it('renders the Sizes story with all three size variants', () => { + it('renders the Sizes story with all size variants', () => { const { getAllByTestId } = within(render(Sizes).container) const roots = getAllByTestId('feedback-empty-state') - expect(roots.map((el) => el.getAttribute('data-size'))).toEqual(['small', 'medium', 'large']) + expect(roots.map((el) => el.getAttribute('data-size'))).toEqual(['small', 'medium']) }) }) }) diff --git a/packages/webkit/src/components/feedback/empty-state/empty-state.vue b/packages/webkit/src/components/feedback/empty-state/empty-state.vue index 21f9bcb53..0bce77fd4 100644 --- a/packages/webkit/src/components/feedback/empty-state/empty-state.vue +++ b/packages/webkit/src/components/feedback/empty-state/empty-state.vue @@ -1,9 +1,7 @@