@acronis-platform/ui-react 5.5.9 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/components/ui/chart/chart.js +14 -11
  2. package/dist/components/ui/chart/chart.js.map +1 -1
  3. package/dist/components/ui/chart-state/chart-state.js +47 -37
  4. package/dist/components/ui/chart-state/chart-state.js.map +1 -1
  5. package/dist/components/ui/chart-widget/chart-widget.js +21 -23
  6. package/dist/components/ui/chart-widget/chart-widget.js.map +1 -1
  7. package/dist/components/ui/data-table/data-table.js +174 -173
  8. package/dist/components/ui/data-table/data-table.js.map +1 -1
  9. package/dist/components/ui/dialog/dialog.js +52 -38
  10. package/dist/components/ui/dialog/dialog.js.map +1 -1
  11. package/dist/components/ui/dropdown-menu/dropdown-menu.js +8 -2
  12. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  13. package/dist/components/ui/page-header/page-header.js +4 -4
  14. package/dist/components/ui/page-header/page-header.js.map +1 -1
  15. package/dist/components/ui/pie-chart/pie-chart.js +88 -84
  16. package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
  17. package/dist/index.js +45 -46
  18. package/dist/llms/chart-state.md +81 -164
  19. package/dist/llms/chart-widget.md +27 -25
  20. package/dist/llms/data-table.md +24 -14
  21. package/dist/llms/dialog-root.md +40 -13
  22. package/dist/llms/dialog.md +109 -14
  23. package/dist/llms/dropdown-menu.md +68 -3
  24. package/dist/llms/page-header.md +68 -11
  25. package/dist/llms/stepper-item.md +10 -7
  26. package/dist/llms.txt +4 -4
  27. package/dist/react.js +45 -46
  28. package/dist/src/components/ui/chart/chart.d.ts +3 -3
  29. package/dist/src/components/ui/chart-state/chart-state.d.ts +5 -14
  30. package/dist/src/components/ui/chart-state/index.d.ts +0 -1
  31. package/dist/src/components/ui/chart-widget/chart-widget.d.ts +10 -15
  32. package/dist/src/components/ui/data-table/data-table.d.ts +11 -5
  33. package/dist/src/components/ui/data-table/data-table.docs.d.ts +10 -4
  34. package/dist/src/components/ui/dialog/dialog.d.ts +10 -0
  35. package/dist/src/components/ui/dialog/dialog.docs.d.ts +7 -1
  36. package/dist/src/components/ui/dropdown-menu/dropdown-menu.d.ts +8 -1
  37. package/dist/src/components/ui/dropdown-menu/index.d.ts +1 -1
  38. package/dist/src/components/ui/page-header/page-header.d.ts +3 -1
  39. package/dist/src/components/ui/pie-chart/pie-chart.d.ts +1 -1
  40. package/dist/ui-react.css +1 -1
  41. package/package.json +13 -13
  42. package/dist/components/ui/chart-state/chart-state-illustrations.js +0 -412
  43. package/dist/components/ui/chart-state/chart-state-illustrations.js.map +0 -1
  44. package/dist/src/components/ui/chart-state/chart-state-illustrations.d.ts +0 -6
@@ -1,6 +1,6 @@
1
1
  # ChartState
2
2
 
3
- > A shared loading / empty / error placeholder for the chart types, rendered in place of the chart inside the same sized slot. A compact status block — a leading glyph over a centered label, with an optional retry action for the error state. The `empty` state draws the silhouette the design defines for each chart type (`variant`), plus an optional "Widget description" line; `loading` and `error` share one treatment. Distinct from `WidgetPlaceholder`, which is the composable skeleton for a widget that isn't a chart — see behavior.md.
3
+ > Shared loading / empty / warning / error / number visuals for a chart slot. Empty, warning, and error use the Breakpoints visual treatment: a 64px status circle with a 24px glyph and centered message. Number shows a centered KPI value; loading uses the shared spinner. Consumers select the state and may localize its content through `description`. Distinct from `WidgetPlaceholder`, which is the composable skeleton for a non-chart widget.
4
4
 
5
5
  **Status:** draft · **Category:** data-display · **Since:** v0.58.0
6
6
 
@@ -14,190 +14,107 @@ import { ChartState } from '@acronis-platform/ui-react'
14
14
 
15
15
  ```tsx
16
16
  <ChartState state="loading" />
17
- <ChartState state="empty" variant="bar" description="No results for the selected range" />
18
- <ChartState
19
- state="error"
20
- action={<Button variant="ghost" onClick={refetch}>Try again</Button>}
21
- />
17
+ <ChartState state="empty" description={t('chart.noData')} />
18
+ <ChartState state="warning" description={t('chart.partialData')} />
19
+ <ChartState state="error" description={t('chart.loadError')} />
20
+ <ChartState state="number" description="0 GB" />
22
21
  ```
23
22
 
24
- A shared loading / empty / error placeholder for the chart types. Render it in
25
- place of a chart — inside the same sized slot — while data is loading, when
26
- there is nothing to plot, or when the fetch failed.
23
+ A shared visual state for one chart slot. Use it while chart data is loading,
24
+ missing, incomplete, or has failed, or when a KPI should show its numeric empty
25
+ value. Empty, warning, and error share the Breakpoints state circles; number
26
+ uses the centered disabled-style KPI value; loading uses the shared spinner.
27
27
 
28
- > **Design-pending v1.** Ported from the Figma InputSelect dropdown states (the
29
- > reference node for this task) and kept visually in step with the shipped
30
- > `InputSelectStatus`. There is no `--ui-chart-*` token tier yet, so it themes
31
- > from existing semantic tokens directly.
32
-
33
- ## When to use
34
-
35
- - A chart cannot render yet (data in flight), has no data to plot, or failed to
36
- load — swap the chart for the matching `ChartState`.
37
- - You want the same placeholder across every chart type, rather than each chart
38
- hand-rolling its own.
39
-
40
- ## When not to use
41
-
42
- - A full-page empty state (illustration, title, description, primary action) —
43
- use the larger `Empty` component instead.
44
- - A bare loading indicator with no surrounding message — use `Spinner` directly.
45
-
46
- ## Example
28
+ Consumers select the semantic state and pass localized or context-specific
29
+ content through `description`. `ChartState` does not infer the state from data.
47
30
 
48
31
  ```tsx
49
- import { ChartState, ChartContainer, Button } from '@acronis-platform/ui-react';
50
-
51
- function Sales({ status, data, config, refetch }) {
52
- if (status === 'loading') return <ChartState state="loading" />;
53
- if (status === 'error')
54
- return (
55
- <ChartState
56
- state="error"
57
- action={
58
- <Button variant="ghost" onClick={refetch}>
59
- Try again
60
- </Button>
61
- }
62
- />
63
- );
64
- if (!data.length) return <ChartState state="empty" />;
65
-
66
- return (
67
- <ChartContainer config={config} className="h-[300px] w-[500px]">
68
- {/* …the chart… */}
69
- </ChartContainer>
70
- );
71
- }
32
+ import { ChartState } from '@acronis-platform/ui-react';
33
+
34
+ if (status === 'loading') return <ChartState state="loading" />;
35
+ if (status === 'warning')
36
+ return <ChartState state="warning" description={t('chart.partialData')} />;
37
+ if (status === 'error')
38
+ return <ChartState state="error" description={t('chart.loadError')} />;
39
+ if (isEmptyKpi) return <ChartState state="number" description="0 GB" />;
40
+ if (!data.length)
41
+ return <ChartState state="empty" description={t('chart.noData')} />;
72
42
  ```
73
43
 
74
- The three states share one compact layout: a leading glyph (spinner / inbox /
75
- warning) over a centered label. Pass `description` to override a state's default
76
- label, and `action` to add a retry affordance to the error state. Long unbroken
77
- error descriptions (CTI paths, API URLs) wrap at word boundaries and scroll
78
- vertically when they exceed the slot height.
44
+ `ChartState` fills the chart's slot. It does not own the containing widget card
45
+ or dashboard composition; see `ChartWidget` for a single chart card.
79
46
 
80
47
  ## Props
81
48
 
82
49
  | Prop | Type | Default | Required | Description |
83
50
  |------|------|---------|----------|-------------|
84
- | `state` | `'loading' | 'empty' | 'error'` | — | yes | Which placeholder to show. Drives the leading artwork and the default label (loading → spinner + "Data is loading…"; empty → variant silhouette or no artwork + "No data found"; error → warning glyph + "Something went wrong"). |
85
- | `variant` | `'area' | 'bar' | 'line' | 'donut' | 'radial' | 'funnel' | 'radar' | 'sankey' | 'scatter' | 'treemap' | 'table' | 'text'` | — | no | Which chart the slot holds. The `empty` state then draws that type's silhouette instead of the generic inbox glyph — the design defines one per chart type. `donut` and `radial` share a ring. Ignored by `loading` and `error`, which share one treatment. |
86
- | `description` | `ReactNode` | — | no | Text shown below the illustration or glyph. Defaults to `"Data is loading…"` / `"No data found"` / `"Something went wrong"` based on `state`. Pass a value to override for any state. |
87
- | `action` | `ReactNode` | — | no | Trailing action rendered for the `error` state only (e.g. a "Try again" button). |
51
+ | `state` | `'loading' | 'empty' | 'warning' | 'error' | 'number'` | — | yes | Which state to show. Loading uses the shared spinner; empty, warning, and error use the shared circular status icon; number shows a centered disabled-style KPI value. Error is an assertive alert; other states are polite status messages. |
52
+ | `description` | `ReactNode` | — | no | Localized or context-specific content. For `number`, this is the numeric readout. Defaults to "Data is loading…", "No data found", "Data may be incomplete", "Something went wrong", or "0". |
88
53
 
89
54
  ## Behavior
90
55
 
91
56
  ### Rendering per state
92
57
 
93
58
  - **Given** `state="loading"`, **when** rendered, **then** it shows the shared
94
- `Spinner` (lg) above the label "Data is loading…" and announces the label via
95
- its `role="status"` live region (no `aria-busy`, which could defer that
96
- announcement).
97
- - **Given** `state="empty"`, **when** rendered, **then** it shows the label
98
- "No data found" (no artwork unless a `variant` is passed).
99
- - **Given** `state="error"`, **when** rendered, **then** it shows the warning
100
- glyph above the label "Something went wrong" and announces as an alert.
101
-
102
- ### Label override
103
-
104
- - **Given** a `description` prop, **when** rendered in any state, **then** the
105
- supplied text replaces that state's default label.
106
-
107
- ### Action
108
-
109
- - **Given** an `action` node, **when** `state="error"`, **then** the action is
110
- rendered after the label (e.g. a "Try again" button).
111
- - **Given** an `action` node, **when** `state` is `loading` or `empty`, **then**
112
- the action is **not** rendered.
113
-
114
- ### Layout
115
-
116
- - **Given** any state, **when** placed in a sized slot (the same box a chart
117
- would occupy), **then** the block fills it (`size-full`) with a `min-h-32`
118
- floor so it stays legible in an unsized parent.
119
- - **Given** `state="loading"` or `state="empty"`, **when** rendered, **then**
120
- the content is vertically centered in the slot (`justify-center`).
121
- - **Given** `state="error"`, **when** the description is short enough to fit,
122
- **then** the content is vertically centered via auto margins (`my-auto`).
123
- - **Given** `state="error"` with a long unbroken `description` (e.g. CTI error
124
- paths, API URLs), **when** the text overflows the slot height, **then** the
125
- auto margins collapse and the icon stays anchored at the top, with vertical
126
- scroll (`overflow-y: auto`) so the full diagnostic text is accessible. Long
127
- unbroken strings wrap at word boundaries (`overflow-wrap: break-word`),
128
- breaking mid-word only when a single token cannot fit on one line.
129
- `overflow-x: hidden` prevents any horizontal scrollbar.
130
-
131
- ### Per-type empty states
132
-
133
- `empty` is the one state the design draws per chart type: an area silhouette for
134
- an area chart, a ring for a donut, a funnel for a funnel. An empty widget then
135
- still says what it _would_ have shown, which a single generic glyph can't.
136
-
137
- `variant` selects it. `donut` and `radial` share one ring — a radial-bar widget
138
- with no data has nothing to tell it apart. Without a `variant` the empty state
139
- shows no artwork — just the text.
140
-
141
- The geometry is exported from the Figma instances rather than redrawn, and every
142
- path is `currentColor`: the tone is set once on the container, so brand and theme
143
- overrides reach the artwork. Two of the eleven silhouettes use a second, fainter
144
- tone (the table and metric-list ones tint their header row darker than the body
145
- rows); that hierarchy is kept as `fill-opacity` on the same colour rather than a
146
- second token.
147
-
148
- The caption is **one line, in one style**. Figma styles it `body/default` on
149
- `text/onSurface/primary` — 14/24, centred, _not_ muted — which is what the root
150
- already applies, so the description and the fallback status label read
151
- identically. For a per-type empty state that line is the `description`: the mockups draw the silhouette over "Widget description" with no
152
- status label, because the artwork already says "no data" and the sentence worth
153
- the space is what the widget _would_ show. With no `description` it falls back to
154
- the status label, so a caller that only sets `variant` still reads sensibly.
155
-
156
- `loading` and `error` deliberately keep one shared treatment — the design only
157
- redesigned `empty`.
158
-
159
- ### Not `WidgetPlaceholder`
160
-
161
- Two components, two jobs, and they are not interchangeable:
162
-
163
- - **`ChartState`** is the placeholder _inside_ a chart's slot. It fills whatever
164
- gives the slot its height, renders one of three states, and is what a chart
165
- widget swaps in for its plot.
166
- - **`WidgetPlaceholder`** is a composable skeleton with its own header, footer
167
- and action parts, plus an `interactive` affordance — for a dashboard widget
168
- that isn't a chart, or a whole tile that is still being set up.
169
-
170
- `WidgetPlaceholder` predates the per-type empty states and is used by consuming
171
- MFEs, so it stays.
59
+ large `Spinner` and the default message "Data is loading…".
60
+ - **Given** `state="empty"`, **when** rendered, **then** it shows the neutral
61
+ circle with the `CircleMinusGray` glyph and the default message "No data
62
+ found".
63
+ - **Given** `state="warning"`, **when** rendered, **then** it shows the warning
64
+ circle with the `TriangleWarningYellow` glyph and the default message "Data
65
+ may be incomplete".
66
+ - **Given** `state="error"`, **when** rendered, **then** it shows the critical
67
+ circle with the `DiamondWarningRed` glyph and the default message "Something
68
+ went wrong".
69
+ - **Given** `state="number"`, **when** rendered, **then** it shows the centered
70
+ disabled-style numeric value "0" without a state icon.
71
+ - **Given** empty, warning, or error, **when** rendered, **then** the group is
72
+ centered and uses a 64px state circle, a 24px glyph, 12px separation, and
73
+ centered 14px / 24px text.
74
+
75
+ ### Copy
76
+
77
+ - **Given** a `description`, **when** rendered in any state, **then** the supplied
78
+ content replaces that state's fallback message. Consumers provide localized
79
+ or context-specific copy.
80
+
81
+ ### Layout and overflow
82
+
83
+ - **Given** any state, **when** placed in a chart slot, **then** the block fills
84
+ the slot without forcing a minimum height beyond its content.
85
+ - **Given** loading, empty, or warning, **when** rendered, **then** the content
86
+ is centered in the slot.
87
+ - **Given** `state="error"` with a short message, **when** rendered, **then**
88
+ the icon and message are vertically centered.
89
+ - **Given** an error with a long message, **when** it exceeds the slot height,
90
+ **then** the icon remains fully visible, the state scrolls vertically, and
91
+ unbroken strings wrap within the available width.
92
+
93
+ ### Composition boundary
94
+
95
+ `ChartState` controls the state visuals inside one chart slot. It does not set
96
+ the chart-widget card border or arrange a group of widgets; those belong to
97
+ their respective component or consuming application.
172
98
 
173
99
  ## Accessibility
174
100
 
175
- - The root is a **live region** so a screen reader announces the state change
176
- when a chart swaps its content in:
177
- - `loading` / `empty` → `role="status"` with `aria-live="polite"`.
178
- - `error` → `role="alert"` with `aria-live="assertive"`.
179
- - On `loading` the shared `Spinner` is rendered `aria-hidden` so the root's own
180
- live region (with the label) announces once, rather than the Spinner's
181
- `role="status"` + sr-only "Loading…" nesting inside it and double-announcing.
182
- - The root does **not** carry `aria-busy`. It is itself the live region, and some
183
- screen readers defer announcing a busy region's content until `aria-busy`
184
- clears; because this placeholder is unmounted (swapped for the chart) rather
185
- than un-busied, a busy flag would risk swallowing the "Data is loading…"
186
- announcement. Put `aria-busy` on the chart-slot **container** instead if a busy
187
- signal is needed (same guidance as `Skeleton`).
188
- - The live-region attributes (`role` / `aria-live`) are applied after the prop
189
- spread, so the state's a11y contract is not clobbered by a consumer-passed
190
- attribute.
191
- - The label text is a real `<p>`, so the state message is always in the
192
- accessible tree — the glyph is decorative and does not need its own label.
193
- - **Do not rely on the glyph color alone** to convey the state — the label
194
- carries the meaning. Icon colors (`--ui-glyph-on-status-info` /
195
- `--ui-glyph-on-status-warning`) and the label (`--ui-text-on-surface-primary`)
196
- resolve to semantic tokens that meet contrast in both themes.
197
- - If the error state offers a retry, pass an actual interactive control as
198
- `action` (a `Button`) so it is focusable and keyboard-operable.
101
+ - The root is the live region: loading, empty, warning, and number use
102
+ `role="status"` with `aria-live="polite"`; error uses `role="alert"` with
103
+ `aria-live="assertive"`.
104
+ - The root live-region attributes are applied after the prop spread, so the
105
+ selected state's announcement semantics cannot be overwritten by consumer
106
+ props.
107
+ - The loading Spinner and state glyphs are decorative. The displayed content
108
+ supplies the state meaning independently of icon color.
109
+ - The root does not carry `aria-busy`. This placeholder is unmounted when the
110
+ chart replaces it, and some screen readers defer announcements inside a busy
111
+ region. Put `aria-busy` on the chart-slot container if a busy signal is needed.
112
+ - Long error text can scroll vertically, and long unbroken strings wrap within
113
+ the available width.
199
114
 
200
115
  ## Contrast
201
116
 
202
- Label and glyph colors resolve to semantic `--ui-*` tokens that meet contrast on
203
- the surface in both light and dark themes.
117
+ Message text uses `--ui-text-on-surface-primary`. The state glyphs are the
118
+ multi-color design assets, placed on semantic neutral, warning, and critical
119
+ background tokens. The textual message communicates meaning without relying on
120
+ color.
@@ -1,6 +1,6 @@
1
1
  # ChartWidget
2
2
 
3
- > The dashboard card a chart sits in: a `Card` with the standard widget header (title, optional filter chip and the ⋯ actions menu) and an optional metric readout above the plot. Adds the one thing a `Card` doesn't know about — the body's loading / empty / error placeholder — and re-implements nothing else: its `header` is `CardHeaderProps`, spread. It declares no size: the grid sizes the card and the widget passes that height down, so a `size-full` chart fills whatever the header leaves. The per-type chart components stay card-less, so a chart is still usable outside a widget.
3
+ > The dashboard card a chart sits in: a `Card` with the standard widget header (title, optional filter chip and the ⋯ actions menu) and an optional metric readout above the plot. Adds the one thing a `Card` doesn't know about — the body's loading / empty / warning / error / number state — and re-implements nothing else. The consumer selects the state explicitly; the widget does not infer it from chart data. its `header` is `CardHeaderProps`, spread. It declares no size: the grid sizes the card and the widget passes that height down, so a `size-full` chart fills whatever the header leaves. The per-type chart components stay card-less, so a chart is still usable outside a widget.
4
4
 
5
5
  **Status:** draft · **Category:** data-display · **Since:** v2.1.0
6
6
 
@@ -35,10 +35,11 @@ readout, then the plot. `ChartWidget` is that composition — and only that.
35
35
 
36
36
  ## What it adds
37
37
 
38
- One thing a `Card` legitimately doesn't know about: **what the body shows while
39
- there is no plot.** `state="loading" | "empty" | "error"` renders `ChartState` in
40
- place of the chart, and `error` also gives the Card its error border — one prop,
41
- not two.
38
+ One thing a `Card` legitimately doesn't know about: **what the body shows for
39
+ an explicitly selected state.** `state="loading" | "empty" | "warning" |
40
+ "error" | "number"` renders `ChartState` in place of the metric and chart. The
41
+ consumer chooses the state; the widget does not infer it from data. Card chrome
42
+ remains consistent for each state.
42
43
 
43
44
  ## What it doesn't add
44
45
 
@@ -63,7 +64,7 @@ the rest of the card. See `behavior.md`.
63
64
  | ------------------ | ------------------------------------------------------------------ |
64
65
  | `index.yaml` | Identity, category, status, `since`. |
65
66
  | `api.yaml` | The prop contract and the per-framework adapters. |
66
- | `anatomy.yaml` | Parts, the schematic, and the four body states. |
67
+ | `anatomy.yaml` | Parts, the schematic, and the five widget states. |
67
68
  | `behavior.md` | Composition, how the height reaches the plot, state, localization. |
68
69
  | `accessibility.md` | Roles, the placeholder's live region, header controls. |
69
70
  | `tokens.yaml` | The `--ui-*` tokens the widget references. |
@@ -87,12 +88,10 @@ chart.
87
88
  | `header` | `CardHeaderProps` | — | no | The card header, forwarded verbatim to `CardHeader` — `title`, `description`, `extras` (a filter chip), `actions` (the ⋯ menu), `isDraggable`, `isSwitchable`, `hasAvatar`, `hasRename`, `isCollapsible`. Omit for a header-less widget. |
88
89
  | `orientation` | `'vertical' | 'horizontal'` | `vertical` | no | How the `metric` and plot are arranged inside the card body. `vertical` (default) — metric above the plot, full width; the metric takes its natural height and the plot fills the rest. `horizontal` — metric and plot side by side, each `flex-1`; use for compact (sm/288px) widgets where vertical space is at a premium. |
89
90
  | `metric` | `ReactNode` | — | no | The readout beside or above the plot (depending on `orientation`), typically a `Metric`. |
90
- | `variant` | `'area' | 'bar' | 'line' | 'donut' | 'radial' | 'funnel' | 'radar' | 'sankey' | 'scatter' | 'treemap' | 'table' | 'text'` | — | no | Which chart the widget holds. Only used while `state="empty"`, to pick the silhouette for that chart type. Forwarded to `ChartState`. |
91
- | `state` | `'loading' | 'empty' | 'error'` | — | no | Renders `ChartState` in place of the plot, keeping the header. `error` additionally gives the Card its error border. |
92
- | `stateDescription` | `ReactNode` | — | no | Text below the placeholder illustration or glyph. Forwarded to `ChartState` as `description`. Defaults to the per-state built-in ("Data is loading…" / "No data found" / "Something went wrong"). |
93
- | `stateAction` | `ReactNode` | — | no | Trailing action on the `error` placeholder — e.g. a retry button. |
91
+ | `state` | `'loading' | 'empty' | 'warning' | 'error' | 'number'` | — | no | Renders `ChartState` in place of the metric and plot, keeping the header. The consumer selects the state; no state is inferred from the data. Card chrome remains consistent for all states. |
92
+ | `stateDescription` | `ReactNode` | — | no | Localized or context-specific content shown by `ChartState`. For `number`, pass the numeric value and unit. Defaults to per-state fallback content, including `0` for `number`. |
94
93
  | `bodyClassName` | `string` | — | no | Class for the card body. Rarely needed — the body already fills the card. For the case where neither the grid nor the content gives a height, e.g. a placeholder-only widget outside a sized cell. |
95
- | `children` | `ReactNode` | — | no | The chart. Replaced by the placeholder while `state` is set. |
94
+ | `children` | `ReactNode` | — | no | The selected state replaces this chart and the metric while `state` is set. |
96
95
  | `render` | `useRender.RenderProp` | — | no | Replace the rendered `<div>` with another element or component (Base UI composition), forwarded to `Card`. How a dashboard gives each widget a landmark role — `render={<section aria-label="Sessions" />}` — which is the one thing the widget can't infer for itself. |
97
96
 
98
97
  ## Behavior
@@ -142,21 +141,23 @@ design constraint, and hard-coding them would have fought the grid.
142
141
 
143
142
  ### State
144
143
 
145
- `state` renders `ChartState` **instead of** `children`. The header stays.
144
+ The consumer selects `state` explicitly; the widget does not infer it from chart
145
+ data. When set, `ChartState` replaces both the `metric` and `children`. The
146
+ header stays. The `number` state shows a centered numeric KPI value.
146
147
 
147
- `state="error"` also maps to `Card`'s `hasError`, so the error border and the
148
- error placeholder come from one prop. A caller can't get one without the other,
149
- which is what the mockups draw.
148
+ The card surface and border remain the same for loading, empty, warning, error,
149
+ and number. State-specific visuals are contained within `ChartState` in the
150
+ body.
150
151
 
151
- `ChartState` owns the live-region contract (`role="status"` for loading/empty,
152
- `role="alert"` for error), so the announcement follows the placeholder rather
153
- than being re-declared on the widget.
152
+ `ChartState` owns the live-region contract (`role="status"` for loading, empty,
153
+ warning, and number; `role="alert"` for error), so the announcement follows
154
+ the state rather than being re-declared on the widget.
154
155
 
155
156
  ### Localization
156
157
 
157
- Every string is the caller's: the header's `title` / `description`, the metric's
158
- own labels, `stateDescription`, and the label inside `stateAction`. The only
159
- literals are `ChartState`'s per-state defaults, which `stateDescription` overrides.
158
+ The header's `title` / `description`, the metric's labels, and
159
+ `stateDescription` can be supplied by the consumer. The `stateDescription`
160
+ prop lets applications localize or contextualize the state content.
160
161
 
161
162
  ## Accessibility
162
163
 
@@ -187,7 +188,7 @@ name, so a chart inside should not repeat it in its own accessible name.
187
188
  ## The placeholder is the live region
188
189
 
189
190
  While `state` is set, `ChartState` is the live region — `role="status"`
190
- (`aria-live="polite"`) for loading and empty, `role="alert"`
191
+ (`aria-live="polite"`) for loading, empty, warning, and number; `role="alert"`
191
192
  (`aria-live="assertive"`) for error. The widget deliberately does **not** add
192
193
  `aria-busy` around it: the placeholder is unmounted and replaced by the plot
193
194
  rather than un-busied, and some assistive tech defers announcing a busy region's
@@ -203,7 +204,8 @@ switch, rename button and collapse trigger each take a label prop with a
203
204
  default, and the `actions` slot is the caller's own control (an icon button
204
205
  needs its own `aria-label` — the mockups' ⋯ menu has no visible text).
205
206
 
206
- ## Colour is not the only signal
207
+ ## State meaning
207
208
 
208
- `state="error"` changes the card's border colour, but the placeholder also
209
- carries an icon and a message, so the error is not conveyed by colour alone.
209
+ The widget card border stays consistent across states. `ChartState` supplies a
210
+ state icon and message (or the numeric KPI value), so meaning does not rely on
211
+ color alone.
@@ -72,10 +72,12 @@ from it too (see **Server-driven usage** below).
72
72
  - **Infinite scroll** — set `paginationMode="infinite"` and pass the full
73
73
  accumulated `data`, `hasNextPage`, `isLoadingMore`, and an `onLoadMore`
74
74
  callback. DataTable renders a sentinel row that calls `onLoadMore` once it
75
- scrolls into view, and a trailing loading row while `isLoadingMore` is true.
76
- The sentinel needs at least one row already rendered — it can't drive an
77
- empty table's very first fetch, so seed the first page yourself (e.g. on
78
- mount). Pass `loadMoreRootMargin` (e.g. `'400px'`) to fire `onLoadMore`
75
+ scrolls into view. While `isLoadingMore` is true, it hides the sentinel and
76
+ shows a centered status row with "Loading more data..."; override
77
+ `loadingMoreLabel` to localize it. Loaded rows stay visible. The `skeleton`
78
+ prop remains the initial-load treatment. The sentinel needs at least one row
79
+ already rendered — it can't drive an empty table's very first fetch, so seed
80
+ the first page yourself (e.g. on mount). Pass `loadMoreRootMargin` (e.g. `'400px'`) to fire `onLoadMore`
79
81
  before the sentinel is literally visible, so the fetch has a head start on
80
82
  the user's scroll — tune it relative to your page size, since a large
81
83
  margin with small pages can trigger several `onLoadMore` calls back-to-back
@@ -153,13 +155,13 @@ as `children` of the toolbar for per-column filtering.
153
155
  | `expand-label` | `string` | `'Expand row'` | no | On the expand-trigger part — accessible name while the row is collapsed. Override to localize. |
154
156
  | `collapse-label` | `string` | `'Collapse row'` | no | On the expand-trigger part — accessible name while the row is expanded. Override to localize. |
155
157
  | `sort-label` | `(title: string) => string` | `(title) => `Sort by ${title}`` | no | On the column-header part — builds the accessible label of the sort toggle from the column title. Override to localize. |
156
- | `table` | `Table<TData>` | — | no | Render from an externally-built TanStack `table` instance instead of DataTable's own — DataTable then owns no state. Makes the following no-ops (configure the equivalent on the external instance instead): columnVisibility, onColumnVisibilityChange, onColumnSizingChange, enableColumnResizing, getRowCanExpand, manualSorting, sorting, onSortingChange, paginationMode, onLoadMore, loadMoreRootMargin, hasNextPage, isLoadingMore. meta.pin-driven column pinning is also skipped — pin/unpin the external instance directly via TanStack's `column.pin()`. |
158
+ | `table` | `Table<TData>` | — | no | Render from an externally-built TanStack `table` instance instead of DataTable's own — DataTable then owns no state. Makes the following no-ops (configure the equivalent on the external instance instead): columnVisibility, onColumnVisibilityChange, onColumnSizingChange, enableColumnResizing, getRowCanExpand, manualSorting, sorting, onSortingChange, paginationMode, onLoadMore, loadMoreRootMargin, hasNextPage, isLoadingMore, loadingMoreLabel. meta.pin-driven column pinning is also skipped — pin/unpin the external instance directly via TanStack's `column.pin()`. |
157
159
  | `getRowCanExpand` | `(row: Row<TData>) => boolean` | — | no | Enables row expansion for rows that return true. Pair with renderExpandedRow. |
158
160
  | `renderExpandedRow` | `(row: Row<TData>) => ReactNode` | — | no | Renders the detail content for an expanded row. |
159
161
  | `striped` | `boolean` | `false` | no | Alternating row backgrounds (borrowed from the Vue AvTable). |
160
162
  | `bordered` | `boolean` | `false` | no | Vertical borders between columns (rows already have horizontal borders). |
161
163
  | `highlightCurrentRow` | `boolean` | `false` | no | Highlight the row the user last clicked (the "current" row). |
162
- | `skeleton` | `boolean` | `false` | no | Render placeholder skeleton rows instead of data (loading state). |
164
+ | `skeleton` | `boolean` | `false` | no | Render shared Skeleton placeholders for initial loading, replacing the data rows while keeping the header visible. |
163
165
  | `skeletonRows` | `number` | `5` | no | Number of skeleton rows when skeleton is set. |
164
166
  | `enableColumnResizing` | `boolean` | `false` | no | Opt in to interactive column resizing — renders a drag handle at the trailing edge of each resizable header (TanStack's native `columnResizing`, `columnResizeMode: 'onChange'`). |
165
167
  | `enableColumnReordering` | `boolean` | `false` | no | Opt in to column reordering by dragging a header cell onto another one (native HTML5 drag-and-drop driving TanStack's `columnOrder`; the dragged header dims while the gesture is in flight). Pinned columns are excluded — they're anchored to a table edge by definition. Pointer-only: there is no keyboard equivalent yet. No-op when `table` is passed. |
@@ -188,9 +190,10 @@ as `children` of the toolbar for per-column filtering.
188
190
  | `renderRow` | `(row: Row<TData>, rowIndex: number) => ReactNode` | — | no | Renders a full row, bypassing DataTable's default per-cell `flexRender` path entirely. Use to swap in a custom, independently memoizable row component — the caller owns the row's markup and equality semantics. Reuse the exported `getCellStyle` / `getPinnedStyle` / `getColumnWidth` helpers to match DataTable's default cell styling if desired. Also bypasses `renderExpandedRow` — a row rendered via `renderRow` never gets an expanded-content row appended; read `row.getIsExpanded()` and render it yourself if needed. |
189
191
  | `renderEmptyState` | `(context: { hasFilters: boolean }) => ReactNode` | — | no | Renders a custom empty state instead of the default "No results." row. Receives `hasFilters` (whether any column filter is currently applied) so the caller can distinguish "no data at all" from "no matches" — the copy/wording/localization stays the caller's job. |
190
192
  | `paginationMode` | `'page' | 'infinite'` | `'page'` | no | `'page'` keeps today's client-paginated behavior. `'infinite'` omits the paginated row model — `data` is the full accumulated array the caller appends to on each `onLoadMore` — and renders a sentinel row that calls `onLoadMore` once it scrolls into view. The sentinel requires at least one row already rendered, so it cannot drive an empty table's very first fetch — seed the first page yourself (e.g. on mount) and use `onLoadMore` for subsequent pages. No-op when `table` is passed. Does not compose with virtualization; for a large accumulated list, virtualize the raw `Table` primitives yourself instead. |
191
- | `loadMoreRootMargin` | `string` | — | no | Expands the sentinel's `IntersectionObserver` root margin (native CSS margin syntax, e.g. "400px") so `onLoadMore` fires before the sentinel is literally visible, reducing how often the trailing loading row is seen. `paginationMode="infinite"` only; no-op when `table` is passed. How far this actually prefetches also depends on page size — a large margin with small pages can trigger several `onLoadMore` calls back-to-back as the user scrolls normally, which is expected; tune it relative to page size. |
193
+ | `loadMoreRootMargin` | `string` | — | no | Expands the sentinel's `IntersectionObserver` root margin (native CSS margin syntax, e.g. "400px") so `onLoadMore` fires before the sentinel is literally visible, reducing how often the trailing loading status row is seen. `paginationMode="infinite"` only; no-op when `table` is passed. How far this actually prefetches also depends on page size — a large margin with small pages can trigger several `onLoadMore` calls back-to-back as the user scrolls normally, which is expected; tune it relative to page size. |
192
194
  | `hasNextPage` | `boolean` | `false` | no | Whether more rows are available to load. `paginationMode="infinite"` only. |
193
- | `isLoadingMore` | `boolean` | `false` | no | Whether a load is in flight — suppresses further `onLoadMore` calls and renders a trailing loading row. `paginationMode="infinite"` only. |
195
+ | `isLoadingMore` | `boolean` | `false` | no | Whether a load is in flight — suppresses further `onLoadMore` calls and renders a centered status row with `loadingMoreLabel`. The observer sentinel is omitted while loading. `paginationMode="infinite"` only; no-op when `table` is passed. |
196
+ | `loadingMoreLabel` | `string` | `'Loading more data...'` | no | Visible text in the infinite-scroll loading status row. Override to localize. `paginationMode="infinite"` only; no-op when `table` is passed. |
194
197
 
195
198
  ## Events
196
199
 
@@ -264,12 +267,18 @@ Scenario: Custom row rendering
264
267
  Scenario: Infinite scroll
265
268
  Given paginationMode="infinite" and hasNextPage is true
266
269
  And at least one row is already rendered
267
- Then a sentinel row renders as the last row of the body
270
+ And isLoadingMore is false
271
+ Then an aria-hidden sentinel row renders after the loaded rows
268
272
  When the sentinel scrolls into view and isLoadingMore is false
269
273
  Then onLoadMore fires
270
274
  And no further onLoadMore calls fire while isLoadingMore is true
271
275
  When isLoadingMore is true
272
- Then a trailing loading row renders below the sentinel
276
+ Then the sentinel is omitted
277
+ And a centered status row renders after the loaded rows
278
+ And the status row shows loadingMoreLabel (default "Loading more data...")
279
+ And the status row carries role="status" and aria-live="polite"
280
+ When isLoadingMore becomes false and hasNextPage is still true
281
+ Then the sentinel renders again
273
282
  ```
274
283
 
275
284
  ```gherkin
@@ -509,10 +518,11 @@ Scenario: Selection counts under an active filter — two deliberate scopes
509
518
  renders no tooltip.
510
519
  - Per-column filter fields live in a keyboard-navigable `FilterSearchFilters`
511
520
  popover; each applied-filter chip below the toolbar removes its own filter.
512
- - The infinite-scroll loading row (`paginationMode="infinite"`, `isLoadingMore`)
513
- carries `role="status"`/`aria-live="polite"` with an `sr-only` "Loading more
514
- rows…" text, so a screen-reader user is told a fetch is in flight without
515
- having to notice the animated placeholder.
521
+ - The infinite-scroll loading status row (`paginationMode="infinite"`,
522
+ `isLoadingMore`) visibly shows `loadingMoreLabel` (default "Loading more
523
+ data...") and carries `role="status"`/`aria-live="polite"`. Override
524
+ `loadingMoreLabel` to localize the message. The observer sentinel is omitted
525
+ while this status row is shown.
516
526
 
517
527
  ## Known limitations
518
528
 
@@ -58,19 +58,19 @@ decision. Composable from parts; built on the Base UI Dialog primitive.
58
58
 
59
59
  ## Parts
60
60
 
61
- | Part | Element (default) | Purpose |
62
- | -------------------------------- | ------------------ | ------------------------------------------------- |
63
- | `DialogRoot` | — | Root; owns the open state (Base UI `Root`). |
64
- | `DialogTrigger` | `button` | Opens the dialog. |
65
- | `DialogContent` | `div[role=dialog]` | The portaled, centered popup (+ overlay). |
66
- | `DialogHeaderRoot` | `div` | Top bar; holds the title and close button. |
67
- | `DialogTitle` | `h2` | Accessible dialog name (`aria-labelledby`). |
68
- | `DialogCloseButton` | `button` | Icon button that dismisses the dialog. |
69
- | `DialogBodyRoot` | `div` | Scrollable content region. |
70
- | `DialogDescription` | `p` | Supporting copy (`aria-describedby`). |
71
- | `DialogFooterRoot` | `div` | Right-aligned action bar. |
72
- | `DialogClose` | `button` | Closes the dialog; wrap a Button via `render`. |
73
- | `DialogOverlay` / `DialogPortal` | — | Lower-level parts (`DialogContent` renders them). |
61
+ | Part | Element (default) | Purpose |
62
+ | -------------------------------- | ------------------ | ---------------------------------------------------------------------------------------- |
63
+ | `DialogRoot` | — | Root; owns the open state (Base UI `Root`). |
64
+ | `DialogTrigger` | `button` | Opens the dialog. |
65
+ | `DialogContent` | `div[role=dialog]` | The portaled popup (+ overlay); centered by default, or top-aligned via `verticalAlign`. |
66
+ | `DialogHeaderRoot` | `div` | Top bar; holds the title and close button. |
67
+ | `DialogTitle` | `h2` | Accessible dialog name (`aria-labelledby`). |
68
+ | `DialogCloseButton` | `button` | Icon button that dismisses the dialog. |
69
+ | `DialogBodyRoot` | `div` | Scrollable content region. |
70
+ | `DialogDescription` | `p` | Supporting copy (`aria-describedby`). |
71
+ | `DialogFooterRoot` | `div` | Right-aligned action bar. |
72
+ | `DialogClose` | `button` | Closes the dialog; wrap a Button via `render`. |
73
+ | `DialogOverlay` / `DialogPortal` | — | Lower-level parts (`DialogContent` renders them). |
74
74
 
75
75
  ## Sizes
76
76
 
@@ -79,6 +79,12 @@ decision. Composable from parts; built on the Base UI Dialog primitive.
79
79
  `large` (832px) is a legacy backward-compatibility size with no design token,
80
80
  kept for existing call sites (see the public `Dialog` recipe's `wide` variant).
81
81
 
82
+ ## Placement
83
+
84
+ `DialogContent` takes a `verticalAlign` prop: `center` (default) centers the
85
+ popup in the viewport; `top` pins it 48px from the top edge, horizontally
86
+ centered, keeping its content height (still capped at `100dvh - 96px`).
87
+
82
88
  ## Example (internal usage only)
83
89
 
84
90
  ```tsx
@@ -124,6 +130,7 @@ import { Button } from '../button';
124
130
  | `default-open` | `boolean` | `false` | no | Initial open state when uncontrolled. |
125
131
  | `modal` | `boolean | 'trap-focus'` | `true` | no | Modal behavior — traps focus and locks background scroll while open. `'trap-focus'` keeps the focus trap but allows background interaction. |
126
132
  | `size` | `'sm' | 'large'` | `'sm'` | no | Popup max-width (on the content part). `sm` (512px) has a Figma-defined token (`--ui-dialog-container-size-sm`); `large` (832px) is a legacy backward-compatibility size with no design token, kept for existing call sites (see the public `Dialog` recipe's `wide` variant). |
133
+ | `verticalAlign` | `'center' | 'top'` | `'center'` | no | Vertical placement of the popup (on the content part). `center` (the default) centers it in the viewport; `top` pins it 48px from the top edge of the viewport instead, keeping its content height. A ui-react-only extension beyond the Figma contract, like `size="large"`. |
127
134
  | `portal` | `boolean` | `true` | no | Render the content inside a portal (the content part). Required by the underlying primitive for correct stacking; disable only when supplying your own portal ancestor. |
128
135
  | `render` | `RenderProp` | `undefined` | no | Composition prop available on the trigger, close, title, description, and other primitive parts — replaces the rendered element with another component (e.g. render the trigger as a Button) while merging props. React-only; other frameworks use their own polymorphism. |
129
136
 
@@ -205,6 +212,26 @@ Scenario: Modal locks the background (default)
205
212
  inside the dialog until it closes
206
213
  ```
207
214
 
215
+ ### Placement
216
+
217
+ ```gherkin
218
+ Scenario: The popup is centered by default
219
+ Given a DialogRoot whose DialogContent has no verticalAlign (or
220
+ verticalAlign = "center")
221
+ When it opens
222
+ Then the popup is centered in the viewport, horizontally and vertically
223
+ ```
224
+
225
+ ```gherkin
226
+ Scenario: verticalAlign = "top" pins the popup near the top edge
227
+ Given a DialogRoot whose DialogContent has verticalAlign = "top"
228
+ When it opens
229
+ Then the popup's top edge sits 48px below the top of the viewport
230
+ And it stays horizontally centered
231
+ And it keeps its content height (still capped at 100dvh - 96px), leaving
232
+ the viewport below it empty
233
+ ```
234
+
208
235
  ## Accessibility
209
236
 
210
237
  DialogRoot leans on the Base UI Dialog primitive for its modal semantics,