@acronis-platform/ui-react 6.0.0 → 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.
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  # Dialog
2
2
 
3
- > A higher-level "recipe" dialog built on top of the DialogRoot primitive parts. A single `variant` prop selects one of seven canned use-cases (default, rename, save changes, reset password, discard changes, accept, read-only) — each with its own title, body copy and footer buttons — plus a legacy `wide` escape hatch (no Figma preset, free-form footer via the `footer` prop) kept for backward compatibility with pre-Figma call sites. `children` overrides the body slot and `hasLoading` shows a `Loading` overlay. Colors, typography, and container/header/footer geometry resolve to the `--ui-dialog-*`/`--ui-footer-*` token tier (see tokens.yaml). In Figma the matching component set is named "DialogDefault"; the code-facing name is `Dialog`.
3
+ > A higher-level "recipe" dialog built on top of the DialogRoot primitive parts. A single `variant` prop selects one of seven canned use-cases (default, rename, save changes, reset password, discard changes, accept, read-only) — each with its own title, body copy and footer buttons — plus a legacy `wide` escape hatch (no Figma preset, free-form footer via the `footer` prop) kept for backward compatibility with pre-Figma call sites. `children` overrides the body slot, `bodyClassName` styles its wrapper, and `hasLoading` shows a `Loading` overlay. The popup is top-aligned 48px from the viewport edge by default; `verticalAlign="center"` opts into vertical centering. Colors, typography, and container/header/footer geometry resolve to the `--ui-dialog-*`/`--ui-footer-*` token tier (see tokens.yaml). In Figma the matching component set is named "DialogDefault"; the code-facing name is `Dialog`.
4
4
 
5
5
  **Status:** draft · **Category:** overlays · **Since:** v0.58.0
6
6
 
@@ -25,6 +25,28 @@ import { Dialog } from '@acronis-platform/ui-react'
25
25
  <p>Any custom content.</p>
26
26
  </Dialog>
27
27
 
28
+ {/* Full-height body with independent scroll panes */}
29
+ <Dialog
30
+ variant="wide"
31
+ size="large"
32
+ title="Add a widget"
33
+ className="h-[90vh] max-w-[min(71rem,calc(100dvw-96px))]"
34
+ bodyClassName="flex-1 min-h-0 p-0 justify-start overflow-hidden"
35
+ defaultOpen
36
+ >
37
+ <div className="flex min-h-0 min-w-0 flex-1">
38
+ <aside
39
+ tabIndex={0}
40
+ className="w-28 shrink-0 overflow-y-auto border-e border-border sm:w-60"
41
+ >
42
+ Categories
43
+ </aside>
44
+ <section tabIndex={0} className="min-h-0 min-w-0 flex-1 overflow-y-auto">
45
+ Widget cards
46
+ </section>
47
+ </div>
48
+ </Dialog>
49
+
28
50
  {/* Interpolate the real object name into the canned title/body */}
29
51
  <Dialog variant="rename" objectName="Q3 Report.xlsx" open onOpenChange={setOpen} />
30
52
 
@@ -49,6 +71,10 @@ same `DialogRoot` + `DialogHeader` + `DialogFooter` boilerplate each time. In
49
71
  Figma the matching component set is named "DialogDefault"; the code-facing
50
72
  name is `Dialog`.
51
73
 
74
+ The popup is horizontally centered and top-aligned 48px from the viewport edge
75
+ by default, matching the full-screen product mockups. Set
76
+ `verticalAlign="center"` for flows that need vertical centering.
77
+
52
78
  > **Status: draft.** Container/header/footer geometry and color resolve to the
53
79
  > `--ui-dialog-*`/`--ui-footer-*` token tier (reconciled against the Figma
54
80
  > "DialogDefault" node). `size="large"` (832px) is the one exception — a legacy
@@ -63,16 +89,19 @@ name is `Dialog`.
63
89
  - You have a legacy call site that needs a wider popup and fully custom footer
64
90
  buttons — use `variant="wide"` (see below). This is a backward-compatibility
65
91
  escape hatch, not a recommended pattern for new work.
92
+ - You need a full-bleed or fixed-height body while keeping the recipe's header
93
+ and footer — use `bodyClassName` to adjust the body wrapper and compose the
94
+ panes inside `children`.
66
95
  - You need a body-only dialog with no title bar and/or no action bar — set
67
96
  `hasHeader`/`hasFooter` to `false`. Both default to `true`; this is beyond
68
97
  the strict Figma contract (all seven canned variants always show both).
69
98
 
70
99
  ## When not to use
71
100
 
72
- - You need a bespoke layout or extra parts none of the seven variants (or the
73
- `wide` escape hatch) cover — this is a gap to raise with the design system
74
- team (the underlying `DialogRoot` primitive is internal-only, not a public
75
- escape hatch).
101
+ - You need a completely bespoke dialog structure or extra parts the recipe
102
+ cannot express — this is a gap to raise with the design system team (the
103
+ underlying `DialogRoot` primitive is internal-only, not a public escape
104
+ hatch).
76
105
  - For transient, non-blocking feedback — use a toast.
77
106
  - For contextual hints anchored to an element — use a tooltip or popover.
78
107
 
@@ -95,15 +124,15 @@ name is `Dialog`.
95
124
 
96
125
  ## Parts
97
126
 
98
- | Part | Element (default) | Purpose |
99
- | ----------------- | ------------------ | ---------------------------------------------------- |
100
- | `container` | `div[role=dialog]` | The portaled popup card; centered (or top-aligned). |
101
- | `header` | `div` | Top bar; title + close button. |
102
- | `title` | `h2` | Accessible dialog name (`aria-labelledby`). |
103
- | `close-button` | `button` | Icon button that dismisses the dialog. |
104
- | `body` | `div` | Variant copy or the `children` override. |
105
- | `footer` | `div` | Right-aligned action bar; or the free-form `footer`. |
106
- | `loading-overlay` | `div` | Spinner scrim shown while `hasLoading`. |
127
+ | Part | Element (default) | Purpose |
128
+ | ----------------- | ------------------ | ------------------------------------------------------- |
129
+ | `container` | `div[role=dialog]` | The portaled popup card; centered (or top-aligned). |
130
+ | `header` | `div` | Top bar; title + close button. |
131
+ | `title` | `h2` | Accessible dialog name (`aria-labelledby`). |
132
+ | `close-button` | `button` | Icon button that dismisses the dialog. |
133
+ | `body` | `div` | Variant copy or `children`; styled via `bodyClassName`. |
134
+ | `footer` | `div` | Right-aligned action bar; or the free-form `footer`. |
135
+ | `loading-overlay` | `div` | Spinner scrim shown while `hasLoading`. |
107
136
 
108
137
  ## Example
109
138
 
@@ -118,6 +147,28 @@ import { Dialog } from '@acronis-platform/ui-react';
118
147
  <p>Any custom body content.</p>
119
148
  </Dialog>;
120
149
 
150
+ // Full-bleed, fixed-height body with independent scroll panes
151
+ <Dialog
152
+ variant="wide"
153
+ size="large"
154
+ title="Add a widget"
155
+ open
156
+ className="h-[90vh] max-w-[min(71rem,calc(100dvw-96px))]"
157
+ bodyClassName="flex-1 min-h-0 p-0 justify-start overflow-hidden"
158
+ >
159
+ <div className="flex min-h-0 min-w-0 flex-1">
160
+ <aside
161
+ tabIndex={0}
162
+ className="w-28 shrink-0 overflow-y-auto border-e border-border sm:w-60"
163
+ >
164
+ Categories
165
+ </aside>
166
+ <section tabIndex={0} className="min-h-0 min-w-0 flex-1 overflow-y-auto">
167
+ Widget cards
168
+ </section>
169
+ </div>
170
+ </Dialog>;
171
+
121
172
  // Busy state
122
173
  <Dialog variant="save changes" hasLoading open />;
123
174
 
@@ -146,8 +197,8 @@ import { Dialog } from '@acronis-platform/ui-react';
146
197
  <p>Body-only content — no title bar, no close button.</p>
147
198
  </Dialog>;
148
199
 
149
- // Pin the popup 48px from the top of the viewport instead of centering it
150
- <Dialog variant="default" verticalAlign="top" open>
200
+ // Opt into vertical centering when a flow calls for it
201
+ <Dialog variant="default" verticalAlign="center" open>
151
202
  <p>Any custom body content.</p>
152
203
  </Dialog>;
153
204
 
@@ -179,6 +230,7 @@ import { Button, DialogClose } from '@acronis-platform/ui-react';
179
230
  | `loadingLabel` | `string` | `'Data is loading…'` | no | Overrides the loading overlay's accessible label. Ignored when `hasLoading` is `false`. |
180
231
  | `hasHeader` | `boolean` | `true` | no | Shows the header (title + close button). When `false`, the title is still rendered off-screen so the dialog keeps an accessible name — only the visible bar and close button are omitted. Beyond the strict Figma contract (all seven canned variants always show the header) — a ui-react-only extension, like `wide`/`size="large"`. |
181
232
  | `hasFooter` | `boolean` | `true` | no | Shows the footer (action buttons). Beyond the strict Figma contract (all seven canned variants always show the footer) — a ui-react-only extension, like `wide`/`size="large"`. When `false`, `footer` is not rendered either. |
233
+ | `bodyClassName` | `string` | — | no | Adds classes to the Dialog body wrapper (React: `bodyClassName`) so a caller can adjust its padding, alignment, flex sizing, and overflow for full-bleed or fixed-height layouts. Classes merge with the defaults; `className` continues to style the popup container. |
182
234
  | `title` | `string` | — | no | Overrides the variant's default title, for localization or copy changes. |
183
235
  | `objectName` | `string` | — | no | The real name of the object being acted on (e.g. a file or resource name). Interpolated into the `rename`/`discard changes`/`accept` variants' canned title/body in place of the generic placeholder text (e.g. `rename` → "Rename {objectName}" and the field's prefilled value); ignored by variants that don't reference an object name. |
184
236
  | `objectNameLabel` | `string` | `'Object name'` | no | Overrides the accessible name of the `rename` variant's text field. Ignored by every other variant. |
@@ -190,7 +242,7 @@ import { Button, DialogClose } from '@acronis-platform/ui-react';
190
242
  | `default-open` | `boolean` | `false` | no | Initial open state when uncontrolled (React `defaultOpen`). |
191
243
  | `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. |
192
244
  | `size` | `'sm' | 'large'` | `'sm'` | no | Popup max-width (forwarded to the underlying content). Defaults to `sm` (512px, the Figma-defined size), or to `large` (832px) when `variant` is `wide`. `large` predates the Figma node and has no design token — a legacy backward-compatibility size, not a design-system width scale. |
193
- | `verticalAlign` | `'center' | 'top'` | `'center'` | no | Vertical placement of the popup (forwarded to the underlying content). `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"`. |
245
+ | `verticalAlign` | `'center' | 'top'` | `'top'` | no | Vertical placement of the popup (forwarded to the underlying content). `top` (the default) pins it 48px from the top edge of the viewport; `center` centers it vertically, keeping its content height. A ui-react-only extension beyond the Figma contract, like `size="large"`. |
194
246
  | `portal` | `boolean` | `true` | no | Render the content inside a portal (forwarded to the underlying content). Required for correct stacking; disable only when supplying your own portal ancestor. |
195
247
  | `keepMounted` | `boolean` | — | no | Keep the content mounted while closed (forwarded to the underlying content's portal). |
196
248
 
@@ -250,20 +302,28 @@ Scenario: The wide variant is a free-form escape hatch
250
302
  ### Placement
251
303
 
252
304
  ```gherkin
253
- Scenario: The popup is centered by default
254
- Given a Dialog with no verticalAlign (or verticalAlign = "center")
305
+ Scenario: The popup is top-aligned by default
306
+ Given a Dialog with no verticalAlign (or verticalAlign = "top")
255
307
  When it opens
256
- Then the popup is centered in the viewport, horizontally and vertically
308
+ Then the popup's top edge sits 48px below the top of the viewport
309
+ And it stays horizontally centered
310
+ And it keeps its content height (still capped at 100dvh - 96px)
257
311
  ```
258
312
 
259
313
  ```gherkin
260
- Scenario: verticalAlign = "top" pins the popup near the top edge
261
- Given a Dialog with verticalAlign = "top"
314
+ Scenario: verticalAlign = "center" opts into vertical centering
315
+ Given a Dialog with verticalAlign = "center"
262
316
  When it opens
263
- Then the popup's top edge sits 48px below the top of the viewport
317
+ Then the popup is centered vertically in the viewport
264
318
  And it stays horizontally centered
265
- And it keeps its content height (still capped at 100dvh - 96px), leaving
266
- the viewport below it empty
319
+ ```
320
+
321
+ ```gherkin
322
+ Scenario: A narrow viewport keeps the popup's 48px edge margin
323
+ Given a viewport narrower than the Dialog's 256px minimum width plus 96px
324
+ When the Dialog opens
325
+ Then its minimum width shrinks to the available viewport width minus 96px
326
+ And the popup keeps at least 48px between each horizontal edge and viewport edge
267
327
  ```
268
328
 
269
329
  ### Body slot
@@ -276,6 +336,16 @@ Scenario: Children override the canned body
276
336
  And the header title and footer buttons are unchanged
277
337
  ```
278
338
 
339
+ ```gherkin
340
+ Scenario: bodyClassName styles the body wrapper independently of the popup
341
+ Given an open Dialog with a fixed popup height
342
+ And bodyClassName sets flex-1, min-h-0, zero padding, and start alignment
343
+ When it opens
344
+ Then those classes apply to the body wrapper while className stays on the popup
345
+ And the body fills the space between the header and footer
346
+ And the caller can give its body panes independent overflow behavior
347
+ ```
348
+
279
349
  ### Object name interpolation
280
350
 
281
351
  ```gherkin
@@ -35,6 +35,9 @@ token tier from `@acronis-platform/tokens-pd`.
35
35
 
36
36
  - A compact list of **actions** triggered from a `ButtonMenu` (row actions,
37
37
  account menu, "more" overflow).
38
+ - The menu is non-modal by default, so the rest of the page remains scrollable
39
+ and interactive. Set `modal` to `true` when the page should be blocked while
40
+ the menu is open.
38
41
 
39
42
  ## When not to use
40
43
 
@@ -83,12 +86,40 @@ import {
83
86
  </DropdownMenu>;
84
87
  ```
85
88
 
89
+ ## Explain a disabled action
90
+
91
+ Attach the tooltip trigger to the disabled menu item itself. The row stays the
92
+ menu's single keyboard focus target, while pointer hover and menu focus can open
93
+ the tooltip. Include the reason in the item's accessible name or description;
94
+ the visual tooltip is supplemental.
95
+
96
+ ```tsx
97
+ import {
98
+ DropdownMenuItem,
99
+ Tooltip,
100
+ TooltipContent,
101
+ TooltipTrigger,
102
+ } from '@acronis-platform/ui-react';
103
+
104
+ <Tooltip>
105
+ <TooltipTrigger
106
+ aria-label="Export report is unavailable. Requires administrator access."
107
+ render={<DropdownMenuItem disabled>Export report</DropdownMenuItem>}
108
+ />
109
+ <TooltipContent>
110
+ Export report is unavailable. Requires administrator access.
111
+ </TooltipContent>
112
+ </Tooltip>;
113
+ ```
114
+
86
115
  ## Props
87
116
 
88
117
  | Prop | Type | Default | Required | Description |
89
118
  |------|------|---------|----------|-------------|
90
119
  | `open` | `boolean` | — | no | Controlled open state. Pair with the change event. |
91
120
  | `default-open` | `boolean` | `false` | no | Initial open state when uncontrolled. |
121
+ | `modal` | `boolean` | `false` | no | Whether opening the menu blocks interaction outside it and locks page scrolling. Defaults to non-modal behavior. |
122
+ | `disabled` | `boolean` | `false` | no | On an item — prevents the action from running while preserving disabled menuitem semantics and keyboard focus. |
92
123
  | `side` | `'top' | 'bottom' | 'left' | 'right' | 'inline-start' | 'inline-end'` | `'bottom'` | no | On the content — which side of the trigger to render on. |
93
124
  | `align` | `'start' | 'center' | 'end'` | — | no | On the content — alignment along the chosen side. |
94
125
  | `side-offset` | `number` | `4` | no | On the content — distance in px from the trigger. |
@@ -113,6 +144,20 @@ state (or the consumer controls it via `open`).
113
144
 
114
145
  ### Opening & closing
115
146
 
147
+ ```gherkin
148
+ Scenario: The menu is non-modal by default
149
+ Given a menu without an explicit modal setting
150
+ When the menu opens
151
+ Then page scrolling and interaction outside the menu remain available
152
+ ```
153
+
154
+ ```gherkin
155
+ Scenario: Opting into modal behavior
156
+ Given a menu configured with modal=true
157
+ When the menu opens
158
+ Then page scrolling and interaction outside the menu are blocked
159
+ ```
160
+
116
161
  ```gherkin
117
162
  Scenario: Opening from the trigger
118
163
  Given a closed menu
@@ -141,7 +186,15 @@ Scenario: Roving navigation and typeahead
141
186
  Given an open menu
142
187
  When the user presses Arrow keys
143
188
  Then highlight moves between items (data-[highlighted])
144
- And typing characters jumps to the matching item
189
+ And typing characters jumps to a matching item
190
+ ```
191
+
192
+ ```gherkin
193
+ Scenario: Explaining a disabled action
194
+ Given an open menu with a disabled item and an explanatory tooltip
195
+ When the pointer hovers its tooltip trigger or keyboard focus reaches it
196
+ Then the tooltip opens
197
+ And the item remains disabled and its action does not run
145
198
  ```
146
199
 
147
200
  ### Submenus
@@ -165,6 +218,13 @@ roles, roving focus, typeahead, and dismissal.
165
218
  - Items are `role="menuitem"`. The shortcut hint is decorative text inside its
166
219
  item. Groups (`DropdownMenuGroup`) visually section items; non-first groups
167
220
  render a top-border separator.
221
+ - Disabled items expose `aria-disabled="true"`, remain in keyboard menu
222
+ navigation, and cannot be activated.
223
+ - To explain a disabled action, prefer attaching the tooltip trigger to the
224
+ disabled menu item itself. This keeps the menu item as the single keyboard
225
+ focus target instead of adding another interactive control inside a menu item.
226
+ - A tooltip is supplemental visual information. Keep the disabled reason in the
227
+ trigger's accessible name or description as well.
168
228
 
169
229
  ## Keyboard
170
230
 
@@ -172,16 +232,21 @@ roles, roving focus, typeahead, and dismissal.
172
232
  to the first item.
173
233
  - **Arrow Up / Down** move the highlight (roving); **Home / End** jump to
174
234
  first / last; **typeahead** jumps to a matching item.
175
- - **Arrow Right** opens a submenu from its trigger; **Arrow Left** / **Escape**
235
+ - **Arrow Right** opens a submenu from its trigger; **Arrow Left / Escape**
176
236
  closes it (Escape closes the whole menu from the root and returns focus to the
177
237
  trigger).
178
- - **Enter / Space** activates the highlighted item.
238
+ - **Enter / Space** activates the highlighted enabled item. A disabled item
239
+ stays focusable but its action does not run.
240
+ - If the tooltip trigger is the disabled menu item, keyboard navigation focuses
241
+ the trigger and opens the tooltip.
179
242
  - **Focus ring** (`--ui-focus-primary`, 3px inset) appears only on keyboard
180
243
  navigation (`focus-visible:not(:hover)`), not on pointer interaction.
181
244
 
182
245
  ## Screen reader
183
246
 
184
247
  - Opening announces the menu and its first item ("Profile, menu item 1 of 4").
248
+ - The disabled reason is available in the menu item's accessible name or
249
+ description; the visual tooltip supplements that text.
185
250
 
186
251
  ## Contrast
187
252
 
@@ -43,15 +43,15 @@ a tags slot, an actions slot) and an optional description row, capped at
43
43
 
44
44
  ## Parts
45
45
 
46
- | Export | Element | Purpose |
47
- | -------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
- | `PageHeader` | `div` | The banner region. |
49
- | `PageHeaderRow` | `div` | Title row: title, optional edit button, tags, actions. |
50
- | `PageHeaderTitle` | `h1` | The page title. |
51
- | `PageHeaderTags` | `div` | Optional tags slot (e.g. `Tag` chips). Collapses to the first tag + a "+#" tag (hover tooltip lists the hidden labels) when the full set doesn't fit. |
52
- | `PageHeaderActions` | `div` | Trailing action buttons. Secondary-variant buttons collapse under a "More" `ButtonIcon` dropdown when the full set doesn't fit; primary buttons never collapse. |
53
- | `PageHeaderDescriptionRow` | `div` | Optional description row (max 512px). |
54
- | `PageHeaderDescription` | `p` | Muted supporting text. |
46
+ | Export | Element | Purpose |
47
+ | -------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `PageHeader` | `div` | The `banner` landmark by default; accepts a native `role` override. |
49
+ | `PageHeaderRow` | `div` | Title row: title, optional edit button, tags, actions. |
50
+ | `PageHeaderTitle` | `h1` by default | The page title; use `as="h2"` for an embedded section under an existing page title. |
51
+ | `PageHeaderTags` | `div` | Optional tags slot (e.g. `Tag` chips). Collapses to the first tag + a "+#" tag (hover tooltip lists the hidden labels) when the full set doesn't fit. |
52
+ | `PageHeaderActions` | `div` | Trailing action buttons. Secondary-variant buttons collapse under a "More" `ButtonIcon` dropdown when the full set doesn't fit; primary buttons never collapse. |
53
+ | `PageHeaderDescriptionRow` | `div` | Optional description row (max 512px). |
54
+ | `PageHeaderDescription` | `p` | Muted supporting text. |
55
55
 
56
56
  ## Example
57
57
 
@@ -72,6 +72,33 @@ a tags slot, an actions slot) and an optional description row, capped at
72
72
  </PageHeader>
73
73
  ```
74
74
 
75
+ ## Page-level and embedded headers
76
+
77
+ Keep both defaults for a page-level header: the root is a `banner` landmark and
78
+ `PageHeaderTitle` renders as `<h1>`. For a header inside an existing page-level
79
+ banner, set `role="none"` on the embedded root to avoid adding a second banner,
80
+ and use `<h2>` for the embedded title. `role="none"` suppresses the extra
81
+ landmark while the `<h2>` remains a heading. Choose the heading level from the
82
+ surrounding document hierarchy; both levels keep the same visual style.
83
+
84
+ ```tsx
85
+ <>
86
+ <PageHeader>
87
+ <PageHeaderRow>
88
+ <PageHeaderTitle>Reports</PageHeaderTitle>
89
+ </PageHeaderRow>
90
+ </PageHeader>
91
+
92
+ <section>
93
+ <PageHeader role="none">
94
+ <PageHeaderRow>
95
+ <PageHeaderTitle as="h2">Recent reports</PageHeaderTitle>
96
+ </PageHeaderRow>
97
+ </PageHeader>
98
+ </section>
99
+ </>
100
+ ```
101
+
75
102
  ### Editable title/description (full-page wizards)
76
103
 
77
104
  ```tsx
@@ -92,6 +119,13 @@ a tags slot, an actions slot) and an optional description row, capped at
92
119
  </div>
93
120
  ```
94
121
 
122
+ ## Props
123
+
124
+ | Prop | Type | Default | Required | Description |
125
+ |------|------|---------|----------|-------------|
126
+ | `PageHeader.role` | `string` | `banner` | no | Native role override for the root div. PageHeader is a banner landmark by default. Set role="none" when composing an embedded PageHeader beneath an existing page-level banner to avoid adding a second banner landmark; this does not change the heading semantics of its contents. |
127
+ | `PageHeaderTitle.as` | `'h1' | 'h2'` | `h1` | no | The heading element used for PageHeaderTitle. Use h1 for a page's primary title and h2 when the title is nested under an existing page title, such as an embedded view. Both levels share the same visual style. |
128
+
95
129
  ## Behavior
96
130
 
97
131
  ```gherkin
@@ -160,8 +194,31 @@ Scenario: Actions fit
160
194
 
161
195
  ## Accessibility
162
196
 
163
- - The region is a `banner` landmark and the title is an `<h1>` — one page title per
164
- page; ensure it fits the document outline.
197
+ - By default, the root is a `banner` landmark and the title renders as `<h1>`.
198
+ Keep these defaults for a page-level header. When composing an embedded view
199
+ beneath an existing page-level banner, set `role="none"` on its PageHeader root
200
+ to suppress the extra landmark, and set the title to `<h2>` to reflect the
201
+ surrounding document hierarchy. The role override does not remove the `<h2>`
202
+ heading semantics; both heading levels share the same visual style.
203
+
204
+ ```tsx
205
+ <>
206
+ <PageHeader>
207
+ <PageHeaderRow>
208
+ <PageHeaderTitle>Reports</PageHeaderTitle>
209
+ </PageHeaderRow>
210
+ </PageHeader>
211
+
212
+ <section>
213
+ <PageHeader role="none">
214
+ <PageHeaderRow>
215
+ <PageHeaderTitle as="h2">Recent reports</PageHeaderTitle>
216
+ </PageHeaderRow>
217
+ </PageHeader>
218
+ </section>
219
+ </>
220
+ ```
221
+
165
222
  - If a breadcrumb is composed above PageHeader, it's a separate `Breadcrumb` with
166
223
  its own `<nav aria-label="breadcrumb">` — not a PageHeader part, so it carries
167
224
  its own accessibility contract (see the Breadcrumb spec).
@@ -16,13 +16,13 @@ import { StepperItem } from '@acronis-platform/ui-react'
16
16
  <StepperItem
17
17
  variant="completed"
18
18
  label="Create an account"
19
- avatar={<Avatar color="green"><CheckIcon size={16} /></Avatar>}
19
+ avatar={<Avatar color="green" className="[box-shadow:none]"><CheckIcon size={16} /></Avatar>}
20
20
  render={<button type="button" onClick={goToStep(1)} />}
21
21
  />
22
22
  <StepperItem
23
23
  variant="current"
24
24
  label="Choose a plan"
25
- avatar={<Avatar color="blue"><AvatarFallback>2</AvatarFallback></Avatar>}
25
+ avatar={<Avatar color="blue" className="[box-shadow:none] text-[var(--ui-stepper-item-current-label-color)]"><AvatarFallback>2</AvatarFallback></Avatar>}
26
26
  />
27
27
  ```
28
28
 
@@ -105,10 +105,13 @@ two caveats the caller applies via `className`, both shown above:
105
105
  as an unwanted halo on `current`'s blue fill and on `completed`'s
106
106
  hover/active fills. See "Design status" below.
107
107
 
108
- Only six `variant` x `state` combinations exist in the design, and the
109
- component reproduces exactly that: `current` is always highlighted with a
110
- border, `future` is always disabled, and only `completed` reads `state`
111
- (`idle`, `hover`, `active`, `focus`).
108
+ The label uses the Stepper typography treatment.
109
+
110
+ The Figma component set draws three top-level `type` variants: `current`,
111
+ `completed`, and `future`. The React API also has a four-value `state` prop, but
112
+ only `completed` responds to it. Figma's nested Completed instance exposes only
113
+ `idle`; the other completed states are token-backed implementation states, not
114
+ drawn variant symbols.
112
115
 
113
116
  `current` is the only variant that paints a visible border, but every variant
114
117
  reserves the same border box (transparent on `completed` and `future`), so the
@@ -149,7 +152,7 @@ above (the same pattern `Timeline`'s marker already used).
149
152
  | `variant` | `'current' | 'completed' | 'future'` | `current` | no | The step's structural role in the sequence. `current` is where the user is and always renders highlighted with a border; `completed` is already passed and is the only variant whose look responds to `state`; `future` is not reachable yet and always renders disabled and non-interactive. Defaults to `current` for prop ergonomics — in a real sequence every step declares its own. |
150
153
  | `state` | `'idle' | 'hover' | 'active' | 'focus'` | `idle` | no | The interaction look. Only observable when `variant` is `completed` (`idle`, `hover`, and `active` each paint their own fill token — `idle`'s resolves to `transparent` in the shipped brands, so it reads as no fill; `focus` draws a 3px ring — its token is byte-identical to `--ui-focus-primary`, i.e. the same look `focus-visible` already draws automatically on a composed control). Ignored for `current`, which is always highlighted, and for `future`, which is always disabled. It is a declarative prop, not a CSS pseudo-state, because the design models it as distinct token roles and a consumer may need to force it. |
151
154
  | `avatar` | `Element` | — | yes | The step's marker — an avatar element supplied and styled entirely by the caller (color scheme, initials / icon / image, size overrides). The step renders it verbatim and never restyles it. |
152
- | `label` | `ReactNode` | — | no | The step name. Truncates rather than wrapping. |
155
+ | `label` | `ReactNode` | — | no | The step name. Truncates rather than wrapping and uses the Stepper typography treatment. |
153
156
  | `render` | `RenderProp` | — | no | Base UI composition prop — replaces the rendered `<div>` with another element or component while merging props and classes. Use it to make a completed step a real `<button>` (or a router link) that jumps back. React-only; other frameworks use their own polymorphism. |
154
157
 
155
158
  ## Events
package/dist/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Acronis UI Kit for React — LLM Reference
2
2
 
3
- > `@acronis-platform/ui-react` v6.0.0
3
+ > `@acronis-platform/ui-react` v7.0.0
4
4
 
5
5
  Machine-readable component reference compiled from framework-agnostic specs.
6
6
  Each entry links to a self-contained markdown file with props, behavior,
@@ -137,7 +137,7 @@ accessibility notes, and usage examples.
137
137
 
138
138
  ## Overlays
139
139
 
140
- - [Dialog](./llms/dialog.md): A higher-level "recipe" dialog built on top of the DialogRoot primitive parts. A single `variant` prop selects one of seven canned use-cases (default, rename, save changes, reset password, discard changes, accept, read-only) — each with its own title, body copy and footer buttons — plus a legacy `wide` escape hatch (no Figma preset, free-form footer via the `footer` prop) kept for backward compatibility with pre-Figma call sites. `children` overrides the body slot and `hasLoading` shows a `Loading` overlay. Colors, typography, and container/header/footer geometry resolve to the `--ui-dialog-*`/`--ui-footer-*` token tier (see tokens.yaml). In Figma the matching component set is named "DialogDefault"; the code-facing name is `Dialog`.
140
+ - [Dialog](./llms/dialog.md): A higher-level "recipe" dialog built on top of the DialogRoot primitive parts. A single `variant` prop selects one of seven canned use-cases (default, rename, save changes, reset password, discard changes, accept, read-only) — each with its own title, body copy and footer buttons — plus a legacy `wide` escape hatch (no Figma preset, free-form footer via the `footer` prop) kept for backward compatibility with pre-Figma call sites. `children` overrides the body slot, `bodyClassName` styles its wrapper, and `hasLoading` shows a `Loading` overlay. The popup is top-aligned 48px from the viewport edge by default; `verticalAlign="center"` opts into vertical centering. Colors, typography, and container/header/footer geometry resolve to the `--ui-dialog-*`/`--ui-footer-*` token tier (see tokens.yaml). In Figma the matching component set is named "DialogDefault"; the code-facing name is `Dialog`.
141
141
  - [DialogRoot](./llms/dialog-root.md): A modal overlay that interrupts the user to show focused content or request a decision. A set of composable parts (trigger, overlay, content, header, title, close, body, description, footer) built on the Base UI Dialog primitive. Initial version ported from the legacy shadcn-uikit `Dialog`; design reconciliation against Figma is pending. Internal-only as of the Dialog (recipe) merge — not exported from the package root. `Dialog` (sourceDir: dialog, see the `dialog` spec) is the sole public entry point; it composes these parts internally.
142
142
  - [DropdownMenu](./llms/dropdown-menu.md): A menu of actions anchored to a trigger (typically a `ButtonMenu`), opened on demand. Composable parts — trigger, content, group (section), item, shortcut, submenu — built on the Base UI Menu primitive. Themed by the `--ui-button-menu-dropdown-*` token tier from @acronis-platform/tokens-pd.
143
143
  - [Popover](./llms/popover.md): A floating panel anchored to a trigger, shown on demand for secondary content or quick actions. Built on the Base UI Popover primitive (positioning, focus management, outside-press / Esc dismissal). Ported from the legacy shadcn-uikit `Popover`; container chrome themed by the `--ui-popover-*` tier, with optional `PopoverBody`/`PopoverFooter` parts for the body-rhythm and default action-row recipe shown in Figma.