prime-ui-kit 0.21.2 → 0.21.4

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 (171) hide show
  1. package/README.md +2 -2
  2. package/THIRD_PARTY_NOTICES +13 -0
  3. package/dist/chunks/{chunk-T7IEJSTH.js → chunk-HMLQCKRL.js} +36 -0
  4. package/dist/chunks/{chunk-7JYPFRZ3.js → chunk-PFASPQSJ.js} +393 -354
  5. package/dist/chunks/{chunk-SQGPQOUO.js → chunk-SUGCIPD7.js} +1 -1
  6. package/dist/chunks/{chunk-ME4VEAQ5.js → chunk-UNNQITFE.js} +2132 -1619
  7. package/dist/chunks/{chunk-VQ3UCVDV.js → chunk-WGXXPJDR.js} +5 -2
  8. package/dist/color-picker.js +3 -3
  9. package/dist/components/chat-list/ChatList.d.ts +4 -3
  10. package/dist/components/command-menu/CommandMenu.d.ts +1 -4
  11. package/dist/components/composer/Composer.d.ts +1 -1
  12. package/dist/components/data-table/DataTable.d.ts +9 -7
  13. package/dist/components/data-table/Footer.d.ts +7 -3
  14. package/dist/components/datepicker/DatepickerPanel.d.ts +2 -2
  15. package/dist/components/dnd/labels.d.ts +2 -0
  16. package/dist/components/field-list/FieldList.d.ts +2 -4
  17. package/dist/components/file-upload/FileUpload.d.ts +1 -9
  18. package/dist/components/index.d.ts +2 -2
  19. package/dist/components/input/Input.d.ts +1 -1
  20. package/dist/components/kanban/Kanban.d.ts +0 -2
  21. package/dist/components/list/List.d.ts +7 -1
  22. package/dist/components/login-form/LoginForm.d.ts +5 -1
  23. package/dist/components/message/Message.d.ts +5 -1
  24. package/dist/components/message/MessageParts.d.ts +7 -2
  25. package/dist/components/rich-text/RichText.d.ts +12 -4
  26. package/dist/components/select/selectContext.d.ts +0 -2
  27. package/dist/components/smart-filter/SmartFilter.d.ts +1 -5
  28. package/dist/components/sparkline/Sparkline.d.ts +50 -8
  29. package/dist/components/tag-select/TagSelect.d.ts +0 -2
  30. package/dist/components/textarea/Textarea.d.ts +2 -2
  31. package/dist/icon-set.d.ts +5 -1
  32. package/dist/icon-set.js +8 -2
  33. package/dist/icons/glyph.d.ts +12 -1
  34. package/dist/icons/glyphs/telegram.d.ts +2 -0
  35. package/dist/icons/glyphs/vk.d.ts +2 -0
  36. package/dist/icons/glyphs/whatsapp.d.ts +2 -0
  37. package/dist/icons/registry.d.ts +8 -1
  38. package/dist/index.css +1 -1
  39. package/dist/index.js +7 -3
  40. package/dist/internal/ToolRow.d.ts +3 -1
  41. package/dist/internal/autofill.d.ts +30 -0
  42. package/dist/internal/barPlan.d.ts +7 -1
  43. package/dist/internal/blockStack.d.ts +7 -0
  44. package/dist/internal/overlay/DialogParts.d.ts +8 -6
  45. package/dist/internal/richText.d.ts +32 -1
  46. package/dist/internal/textFormatting.d.ts +2 -2
  47. package/dist/layout/page/Page.d.ts +2 -1
  48. package/dist/skill/SKILL.md +3 -2
  49. package/dist/skill/api-contract.md +2 -1
  50. package/dist/skill/cheatsheet.md +1 -1
  51. package/dist/skill/checklist.md +8 -1
  52. package/dist/skill/choosing.md +76 -5
  53. package/dist/skill/components.md +2 -2
  54. package/dist/skill/composition.md +108 -20
  55. package/dist/skill/foundations.md +1 -1
  56. package/dist/skill/layouts.md +4 -4
  57. package/dist/skill/reference/components/button/examples/in-form.tsx +0 -1
  58. package/dist/skill/reference/components/card/COMPONENT.md +1 -1
  59. package/dist/skill/reference/components/card/examples/screen.tsx +9 -11
  60. package/dist/skill/reference/components/chat-list/COMPONENT.md +6 -5
  61. package/dist/skill/reference/components/chat-list/examples/structure.tsx +4 -4
  62. package/dist/skill/reference/components/command-menu/COMPONENT.md +1 -2
  63. package/dist/skill/reference/components/command-menu/examples/states.tsx +1 -1
  64. package/dist/skill/reference/components/composer/COMPONENT.md +5 -4
  65. package/dist/skill/reference/components/composer/examples/formatting.tsx +1 -1
  66. package/dist/skill/reference/components/data-table/COMPONENT.md +17 -16
  67. package/dist/skill/reference/components/data-table/examples/server.tsx +2 -1
  68. package/dist/skill/reference/components/datepicker/COMPONENT.md +2 -2
  69. package/dist/skill/reference/components/dnd/COMPONENT.md +2 -1
  70. package/dist/skill/reference/components/drawer/COMPONENT.md +9 -6
  71. package/dist/skill/reference/components/drawer/examples/screen.tsx +1 -5
  72. package/dist/skill/reference/components/drawer/examples/tab-switcher.tsx +158 -0
  73. package/dist/skill/reference/components/field-list/COMPONENT.md +7 -7
  74. package/dist/skill/reference/components/field-list/examples/limits.tsx +2 -2
  75. package/dist/skill/reference/components/file-upload/COMPONENT.md +8 -12
  76. package/dist/skill/reference/components/file-upload/examples/gallery.tsx +2 -2
  77. package/dist/skill/reference/components/file-upload/examples/in-form.tsx +2 -4
  78. package/dist/skill/reference/components/file-upload/examples/narrow.tsx +1 -0
  79. package/dist/skill/reference/components/file-upload/examples/overview.tsx +1 -5
  80. package/dist/skill/reference/components/file-upload/examples/sizes.tsx +1 -1
  81. package/dist/skill/reference/components/file-upload/examples/states.tsx +4 -5
  82. package/dist/skill/reference/components/file-upload/examples/validation.tsx +2 -7
  83. package/dist/skill/reference/components/input/COMPONENT.md +3 -0
  84. package/dist/skill/reference/components/input/examples/in-form.tsx +0 -1
  85. package/dist/skill/reference/components/input/examples/validation.tsx +1 -1
  86. package/dist/skill/reference/components/kanban/COMPONENT.md +1 -2
  87. package/dist/skill/reference/components/list/COMPONENT.md +11 -6
  88. package/dist/skill/reference/components/list/examples/reorder.tsx +30 -3
  89. package/dist/skill/reference/components/login-form/COMPONENT.md +2 -2
  90. package/dist/skill/reference/components/login-form/examples/sizes.tsx +6 -2
  91. package/dist/skill/reference/components/message/COMPONENT.md +12 -7
  92. package/dist/skill/reference/components/message/examples/status.tsx +2 -1
  93. package/dist/skill/reference/components/message/examples/text-formatting.tsx +13 -3
  94. package/dist/skill/reference/components/modal/COMPONENT.md +5 -4
  95. package/dist/skill/reference/components/rich-text/COMPONENT.md +45 -7
  96. package/dist/skill/reference/components/rich-text/examples/entities.tsx +20 -0
  97. package/dist/skill/reference/components/select/COMPONENT.md +1 -2
  98. package/dist/skill/reference/components/sheet/COMPONENT.md +5 -4
  99. package/dist/skill/reference/components/smart-filter/COMPONENT.md +3 -5
  100. package/dist/skill/reference/components/sparkline/COMPONENT.md +40 -12
  101. package/dist/skill/reference/components/sparkline/examples/lower-is-better.tsx +46 -0
  102. package/dist/skill/reference/components/sparkline/examples/own-value.tsx +41 -0
  103. package/dist/skill/reference/components/sparkline/examples/reference.tsx +2 -1
  104. package/dist/skill/reference/components/tabs/COMPONENT.md +1 -1
  105. package/dist/skill/reference/components/tabs/examples/orientation.tsx +1 -1
  106. package/dist/skill/reference/components/tag-select/COMPONENT.md +0 -2
  107. package/dist/skill/reference/components/tag-select/examples/overview.tsx +0 -1
  108. package/dist/skill/reference/components/tag-select/examples/sizes.tsx +0 -1
  109. package/dist/skill/reference/components/textarea/COMPONENT.md +6 -3
  110. package/dist/skill/reference/icon-set.ts +5 -1
  111. package/dist/skill/reference/layout/page/COMPONENT.md +13 -12
  112. package/dist/skill/reference/layout/page/examples/collapse.tsx +45 -31
  113. package/dist/skill/reference/layout/page/examples/router.tsx +47 -14
  114. package/dist/skill/ux.md +98 -4
  115. package/package.json +1 -1
  116. package/src/components/button/examples/in-form.tsx +0 -1
  117. package/src/components/card/COMPONENT.md +1 -1
  118. package/src/components/card/examples/screen.tsx +9 -11
  119. package/src/components/chat-list/COMPONENT.md +6 -5
  120. package/src/components/chat-list/examples/structure.tsx +4 -4
  121. package/src/components/command-menu/COMPONENT.md +1 -2
  122. package/src/components/command-menu/examples/states.tsx +1 -1
  123. package/src/components/composer/COMPONENT.md +5 -4
  124. package/src/components/composer/examples/formatting.tsx +1 -1
  125. package/src/components/data-table/COMPONENT.md +17 -16
  126. package/src/components/data-table/examples/server.tsx +2 -1
  127. package/src/components/datepicker/COMPONENT.md +2 -2
  128. package/src/components/dnd/COMPONENT.md +2 -1
  129. package/src/components/drawer/COMPONENT.md +9 -6
  130. package/src/components/drawer/examples/screen.tsx +1 -5
  131. package/src/components/drawer/examples/tab-switcher.tsx +158 -0
  132. package/src/components/field-list/COMPONENT.md +7 -7
  133. package/src/components/field-list/examples/limits.tsx +2 -2
  134. package/src/components/file-upload/COMPONENT.md +8 -12
  135. package/src/components/file-upload/examples/gallery.tsx +2 -2
  136. package/src/components/file-upload/examples/in-form.tsx +2 -4
  137. package/src/components/file-upload/examples/narrow.tsx +1 -0
  138. package/src/components/file-upload/examples/overview.tsx +1 -5
  139. package/src/components/file-upload/examples/sizes.tsx +1 -1
  140. package/src/components/file-upload/examples/states.tsx +4 -5
  141. package/src/components/file-upload/examples/validation.tsx +2 -7
  142. package/src/components/input/COMPONENT.md +3 -0
  143. package/src/components/input/examples/in-form.tsx +0 -1
  144. package/src/components/input/examples/validation.tsx +1 -1
  145. package/src/components/kanban/COMPONENT.md +1 -2
  146. package/src/components/list/COMPONENT.md +11 -6
  147. package/src/components/list/examples/reorder.tsx +30 -3
  148. package/src/components/login-form/COMPONENT.md +2 -2
  149. package/src/components/login-form/examples/sizes.tsx +6 -2
  150. package/src/components/message/COMPONENT.md +12 -7
  151. package/src/components/message/examples/status.tsx +2 -1
  152. package/src/components/message/examples/text-formatting.tsx +13 -3
  153. package/src/components/modal/COMPONENT.md +5 -4
  154. package/src/components/rich-text/COMPONENT.md +45 -7
  155. package/src/components/rich-text/examples/entities.tsx +20 -0
  156. package/src/components/select/COMPONENT.md +1 -2
  157. package/src/components/sheet/COMPONENT.md +5 -4
  158. package/src/components/smart-filter/COMPONENT.md +3 -5
  159. package/src/components/sparkline/COMPONENT.md +40 -12
  160. package/src/components/sparkline/examples/lower-is-better.tsx +46 -0
  161. package/src/components/sparkline/examples/own-value.tsx +41 -0
  162. package/src/components/sparkline/examples/reference.tsx +2 -1
  163. package/src/components/tabs/COMPONENT.md +1 -1
  164. package/src/components/tabs/examples/orientation.tsx +1 -1
  165. package/src/components/tag-select/COMPONENT.md +0 -2
  166. package/src/components/tag-select/examples/overview.tsx +0 -1
  167. package/src/components/tag-select/examples/sizes.tsx +0 -1
  168. package/src/components/textarea/COMPONENT.md +6 -3
  169. package/src/layout/page/COMPONENT.md +13 -12
  170. package/src/layout/page/examples/collapse.tsx +45 -31
  171. package/src/layout/page/examples/router.tsx +47 -14
@@ -50,7 +50,7 @@ DataTable card-fill block, radius 12, clips its conten
50
50
  │ (one per group) with getRowGroup: a <th scope="rowgroup"> header row, then its rows
51
51
  │ with onReorder: a Dnd.Sortable tbody; a Dnd.Handle ends each row (reorderHandle
52
52
  │ "end"), leads it ("start"), or the row itself is the grip ("row")
53
- └── footer range «Показано 1–5 из 23» · Pagination · infinite-scroll status
53
+ └── footer (when it has something) Pagination · range «Показано 1–5 из 23» (`showRange`) · «Догружаем строки…» while more rows load
54
54
  ```
55
55
  ```
56
56
  DataTableColumnsMenu the column chooser outside the table, for the page's tools
@@ -65,7 +65,7 @@ Columns are data (`DataTableColumn<Row>[]`), not child parts; the table has no p
65
65
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
66
66
 
67
67
  ### DataTable
68
- Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an optional toolbar, a scrolling `<table>` and a footer (range, Pagination, infinite-scroll status); passes its tier to the controls inside.
68
+ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an optional toolbar, a scrolling `<table>` and a footer when it has something to show (Pagination, the range with `showRange`, the status while more rows load); passes its tier to the controls inside.
69
69
 
70
70
  | Prop | Type | Default | Description |
71
71
  |---|---|---|---|
@@ -79,7 +79,7 @@ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an
79
79
  | `columnsMenu` | `boolean` | `false` | The column chooser in the head of the row-actions column (the last one, `hideHeader`); reorders with `columnOrder`. Without that column it renders nothing and warns: use `DataTableColumnsMenu` in the page's tools. |
80
80
  | `rows` | `Row[]` | — (required) | Data; sorted in memory when a sort is set. With `source="server"` — the page (or the loaded batches) as the server returned it. |
81
81
  | `source` | `"client" \| "server"` | `"client"` | Who sorts and pages the rows. `client`: the table sorts `rows` and slices pages. `server`: `rows` is one page, already sorted; `sort` / `page` changes are the request, `rowCount` drives Pagination. |
82
- | `rowCount` | `number` | `rows.length` | Total rows on the server (`source="server"`): the page count and «Показано {from}–{to} из {total}». |
82
+ | `rowCount` | `number` | `rows.length` | Total rows on the server (`source="server"`): the page count and the range line (`showRange`). |
83
83
  | `getRowKey` | `(row: Row, index: number) => Key` | — | Stable row id (position by default; sub-rows too, `index` among siblings); selection, expansion and the enter animation of new rows are keyed by it. Pass it whenever sorting, selection or expansion is used. |
84
84
  | `getRowLabel` | `(row: Row) => string` | — | Human row name for the checkbox and toggle names (`{label}` in `labels`). |
85
85
  | `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | host tier, else `"m"` | Density and control tier: rows from 36 · 36 · 44 · 52 · 52 px (`--prime-table-row-height-<size>`); passed to checkboxes, toggles, Pagination and nested controls. Without it the tier of the host. |
@@ -88,6 +88,7 @@ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an
88
88
  | `page` | `number` | — | Current page (1-based), controlled. |
89
89
  | `defaultPage` | `number` | `1` | Initial page, uncontrolled. |
90
90
  | `onPageChange` | `(page: number) => void` | — | Page change from the pager; a sort change also returns to page 1. |
91
+ | `showRange` | `boolean` | `false` | A range line in the footer (`labels.range`) when the rows do not fit one page: paged with more than one page, or `paging="infinite"`. |
91
92
  | `sort` | `DataTableSortState` | — | Current sort, controlled: `{ columnId, order: "asc" \| "desc" }` or `null`. |
92
93
  | `defaultSort` | `DataTableSortState` | `null` | Initial sort, uncontrolled. |
93
94
  | `onSortChange` | `(sort: DataTableSortState) => void` | — | A head click cycles asc → desc → none. |
@@ -112,7 +113,7 @@ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an
112
113
  | `onExpandedChange` | `(expanded: Key[]) => void` | — | Expansion change. |
113
114
  | `infiniteBatchSize` | `number` | `20` | Rows revealed per scroll step. |
114
115
  | `hasMore` | `boolean` | `false` | More rows can be loaded from the server. |
115
- | `loadingMore` | `boolean` | `false` | A server batch is loading: footer status, `aria-busy`. |
116
+ | `loadingMore` | `boolean` | `false` | A server batch is loading: the footer shows `labels.loadingMore` with a Spinner, `aria-busy`. |
116
117
  | `onLoadMore` | `() => void \| Promise<void>` | — | Called at the end when every loaded row is shown and `hasMore` is set. A returned promise shows the loading status until it settles; a rejected one stops asking until the rows change. |
117
118
  | `scrollHeight` | `number \| string \| "fill"` | `360` with `paging="infinite"`, else none | Max height of the scroll viewport (number = px); `"fill"` — the table takes the height of its flex / grid parent (or a sized block), the rows scroll inside, infinite paging follows it. |
118
119
  | `stickyHeader` | `boolean` | `false` | The head sticks while the body scrolls (with `scrollHeight`); on screens lower than 480px it scrolls with the rows. |
@@ -172,7 +173,7 @@ One column of `columns`: data, not a part.
172
173
  | `description` | `string` | — | What the column means: an info glyph after the header opens it as a tooltip; the header cell is described by it (`aria-describedby`). |
173
174
  | `numeric` | `boolean` | — | Tabular figures, no wrapping, end alignment unless `align` is set. |
174
175
  | `truncate` | `boolean` | — | One line with an ellipsis; width from `maxWidth` (or `width`); string values get a `title`. |
175
- | `grow` | `boolean` | — | Takes the free width and wraps its text; the table then fills its container. With `minWidth` it never gets narrower: below that the table scrolls instead of cells overlapping. |
176
+ | `grow` | `boolean` | — | Takes the free width; the table then fills its container. Its cells fit the width they get: text wraps, `truncate` ellipsizes, a `Badge.Group` with `overflow="count"` shows «+N». It never gets narrower than its header or `minWidth`: below that the table scrolls instead of cells overlapping. |
176
177
  | `hideable` | `boolean` | `true` | The column can be hidden through `hiddenColumns` and `columnsMenu`; `false` for the key column that names the row and for the row-actions column. |
177
178
  | `reorderable` | `boolean` | `true` | The column moves when columns reorder (`columnOrder`); `false` holds its place: no column moves past it, the chooser shows a lock. A `hideHeader` column and a pinned first or last column (`stickyFirstColumn`, `stickyLastColumn`) hold theirs too. |
178
179
  | `scale` | `boolean \| { min?: number; max?: number }` | — | Heatmap: each cell is shaded by its numeric value on the five-step chart scale. `true` spans the column's minimum to maximum over all rows; `{ min, max }` fixes either end. |
@@ -193,13 +194,13 @@ Without `size` the table takes the tier of its host (a sized form, panel or popo
193
194
 
194
195
  Row height is a minimum: cells pad vertically by (row height − control one tier down) / 2, so one text line or a control one tier down keeps the tier height and taller content grows the row. Checkboxes use the table tier; expand toggles are square ghost Buttons of a fixed small tier (`xs` for xs–m, `s` for l–xl).
195
196
 
196
- Column widths come from the content and then hold still: once rows are laid out the widths are frozen, so searching, filtering, paging and opening sub-rows do not move the columns. A column widens when later content does not fit it, never narrows; widths are measured again when the column set or the container width changes. `grow` columns keep taking the free width.
197
+ Column widths come from the content and then hold still: once rows are laid out the widths are frozen, so searching, filtering, paging and opening sub-rows do not move the columns. A column widens when later content does not fit it, never narrows; widths are measured again when the column set or the container width changes. `grow` columns keep taking the free width; their body never widens them (only the header and `minWidth` set their narrowest), so a `truncate` value, a counted `Badge.Group` or wrapping text fits whatever width the column gets.
197
198
 
198
199
  ### paging
199
200
  | Value | Looks like | Use when | Default |
200
201
  |---|---|---|---|
201
- | `pages` | `pageSize` rows, footer range and Pagination (compact on narrow widths) | tables people return to and reference | yes |
202
- | `infinite` | rows revealed while scrolling in a `scrollHeight` window, footer status | logs and feeds | |
202
+ | `pages` | `pageSize` rows, Pagination in the footer (compact on narrow widths) | tables people return to and reference | yes |
203
+ | `infinite` | rows revealed while scrolling in a `scrollHeight` window; no footer at rest, only a Spinner and `labels.loadingMore` while a request runs | logs and feeds | |
203
204
  | `none` | every row, no footer | short lists, reports in a scroll window | |
204
205
 
205
206
  ### source
@@ -265,6 +266,7 @@ Heads keep `start` whatever the cell alignment, with the sort icon at the end ed
265
266
  | `columnOrder` · `defaultColumnOrder` · `onColumnOrderChange` | any of the three makes columns reorder: a press that moves on a header carries the header alone (a finger holds still) while the body stays, Alt + ← / → on the header, grips in the column chooser plus «Исходный порядок» | tables people arrange for themselves; `defaultColumnOrder={[]}` starts in the order of `columns` | off |
266
267
  | column `reorderable` | `false`: the column holds its place — its header carries nothing, no column moves past it, the chooser shows a lock. A `hideHeader` column and a pinned first / last column hold theirs too | the key column, a total that must stay last | `true` |
267
268
  | `showHeader` | head row shown | off only for self-explanatory key/value tables | `true` |
269
+ | `showRange` | «Показано 1–5 из 23» on the left of the footer, with more than one page or with infinite scroll | the count matters to the task (a register people page through, a server list with a total) | `false` |
268
270
  | `fullWidth` | spans the container; `false` = content width | `false` for short lookup tables in wide layouts | `true` |
269
271
  | column `numeric` | tabular figures, end-aligned, no wrap | numbers, money | — |
270
272
  | column `truncate` | one line with an ellipsis and a `title` | long names in narrow columns | — |
@@ -277,11 +279,11 @@ Heads keep `start` whatever the cell alignment, with the sort icon at the end ed
277
279
  | State | Driven by | DOM |
278
280
  |---|---|---|
279
281
  | loading (no rows) | `loading` | skeleton rows (`data-skeleton`), `role="status"` with `labels.loading`, `aria-busy` on the table, `data-loading` on the root |
280
- | loading more | `hasMore` with every loaded row shown calls `onLoadMore`; `loadingMore`, or its pending promise | `aria-busy`; footer `labels.loadingMore`; no new request until it settles (after a rejection, until the rows change) |
282
+ | loading more | `hasMore` with every loaded row shown calls `onLoadMore`; `loadingMore`, or its pending promise | `aria-busy`; the footer rises in with a Spinner and `labels.loadingMore` (announced by a live region that stays mounted) and closes up in place when it settles; no new request until it settles (after a rejection, until the rows change) |
281
283
  | refreshing | `loading` with rows on screen (a refetch, another server page or sort) | the rows, head, toolbar and footer stay; `aria-busy` on the table, `data-loading` on the root — no skeleton |
282
284
  | new server page | `source="server"` and `rows` replaced (not appended) | the body remounts with the swap fade (`swapMotion`), the viewport scrolls to its top; no per-row enter |
283
285
  | empty | no rows and not loading | compact EmptyPage with `empty` or `labels.empty` (`role="status"`), or the `empty` node; centred in the visible width, not across every column of a wide table |
284
- | error | `loadError` | body replaced by a `role="alert"` message in `danger-text`, centred in the visible width; range and infinite status hidden |
286
+ | error | `loadError` | body replaced by a `role="alert"` message in `danger-text`, centred in the visible width; range and loading status hidden |
285
287
  | sorted | `sort` / `defaultSort` | `aria-sort` and `data-sorted` on the head; the sort icon: unsorted `text-disabled`, hover `text-muted`, sorted `text-secondary` — never accent |
286
288
  | selected | `selectable` with `selected` / `defaultSelected` | `data-selectable` on the root, a leading checkbox column; `aria-selected` rows with `accent-soft` fill; polite `labels.selectedCount` |
287
289
  | expanded | `expanded` / `defaultExpanded` | `data-expanded` on the parent row (one step darker); toggle `aria-expanded`, chevron turns 90° |
@@ -311,7 +313,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
311
313
  - Pass the tools as direct children of `toolbar` or a fragment: the row lays out each element as a cell. A wrapper component that returns several controls is one cell — no gaps between them, no growing search.
312
314
  - The selection actions menu (`renderSelectionActions`): a soft neutral Button at the table tier — `labels.selectionActions`, a Badge with the count of selected rows, a chevron — named «Действия N» as read aloud. It enters last in the tool row (after the `toolbar`) once a row is selected and closes up in place when the selection empties; without a `toolbar` the tools row comes and goes with it. Its Dropdown opens below, at the end edge: `labels.clearSelection` first, a separator, then the items `renderSelectionActions(selected)` returns for the selected keys.
313
315
  - Toolbar padding `--prime-space-3` × cell padding, items `gap: --prime-space-2`, chips 12 under the row; below 30rem of table width the footer stacks.
314
- - The footer has a hairline on top: range on the left, Pagination on the right. The range shows only with more than one page or with infinite scroll.
316
+ - The footer has a hairline on top and exists only when it has something to show: Pagination on the right, the range on the left with `showRange` (only with more than one page or with infinite scroll), the loading status of infinite scroll while a request runs. An empty label draws nothing.
315
317
  - Columns scroll sideways inside the table, never the page; pin the identifying column with `stickyFirstColumn` and the row actions column with `stickyLastColumn` (it pins the last visible column, so hiding it pins the one before). A 320 px column holds the table without breaking it.
316
318
  - While columns are hidden past an edge, that edge shows a `--prime-space-4` shadow (`--prime-color-bg-edge-shadow`) over the content; it takes no space and follows RTL. With `stickyFirstColumn` the start shadow falls from the pinned column's end edge; with `stickyLastColumn` the end shadow falls from the pinned column's start edge. With `stickyHeader` the pinned head cells hold both corners.
317
319
  - Layers, bottom to top: every cell that scrolls · the pinned columns (lead cells, the first and the last column, the end grip cell) · sticky group headers · the sticky head · the corners (head cells pinned to a column edge). Each cell keeps what it holds inside its own layer, so a control in a scrolled cell — a Switch, an icon Button, a focus ring — always passes under the pinned columns and the head, whatever its own stacking. A head label truncates inside its cell and never runs under the next one.
@@ -370,9 +372,8 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
370
372
  |---|---|---|
371
373
  | `loading` | `"Загрузка данных…"` | Screen-reader status while skeleton rows are shown. |
372
374
  | `empty` | `"Нет данных для отображения."` | Default empty-state text. |
373
- | `range` | `"Показано {from}–{to} из {total}"` | Footer range line; `{from}`, `{to}`, `{total}` are replaced. |
374
- | `loadingMore` | `"Догружаем строки…"` | Footer status while `loadingMore`. |
375
- | `scrollForMore` | `"Прокрутите вниз для загрузки"` | Footer hint when more rows can appear by scrolling. |
375
+ | `range` | `"Показано {from}–{to} из {total}"` | Footer range line with `showRange`; `{from}`, `{to}`, `{total}` are replaced. |
376
+ | `loadingMore` | `"Догружаем строки…"` | Footer status while more rows load (`loadingMore` or a pending `onLoadMore`); announced politely. |
376
377
  | `selectAll` | `"Выбрать все строки"` | Name of the head «select all» checkbox. |
377
378
  | `selectRow` | `"Выбрать строку {label}"` | Name of a row checkbox; `{label}` is `getRowLabel(row)`, empty without it. |
378
379
  | `selectedCount` | `"Выбрано: {count}"` | Polite announcement after the selection changes. |
@@ -414,7 +415,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
414
415
  | [columns-visibility.tsx](examples/columns-visibility.tsx) | Orders without a row-actions column: the column chooser stands in the tools above the table with the count of shown columns; the choice outlives a reload in local storage, the order number always stays — `DataTableColumnsMenu`, `hiddenColumns`, `onHiddenColumnsChange`, `hideable`. |
415
416
  | [column-order.tsx](examples/column-order.tsx) | Orders whose columns a manager puts in place by dragging a header — it moves alone and the gap opens among the headers where it lands — or with Alt + ← / →, and shows, hides and reorders in the chooser over the row menu; the order number stays first and the row menu last, and the choice outlives a reload — `columnOrder`, `onColumnOrderChange`, `reorderable`, `columnsMenu`, `hiddenColumns`. |
416
417
  | [infinite-scroll.tsx](examples/infinite-scroll.tsx) | An audit log that reveals loaded rows in batches and then asks the server for more — `paging`, `infiniteBatchSize`, `hasMore`, `loadingMore`, `onLoadMore`. |
417
- | [server.tsx](examples/server.tsx) | A CRM deal list the server sorts, pages and searches, with its tools in its own row — search, filter and column chooser, the active filters under them; a new search returns to page 1 — `source`, `rowCount`, `sort`, `page`, `loading`, `toolbar`, `toolbarChips`. |
418
+ | [server.tsx](examples/server.tsx) | A CRM deal list the server sorts, pages and searches, with its tools in its own row — search, filter and column chooser, the active filters under them; a new search returns to page 1, and the footer counts the deals shown out of the total — `source`, `rowCount`, `showRange`, `sort`, `page`, `loading`, `toolbar`, `toolbarChips`. |
418
419
  | [fill-height.tsx](examples/fill-height.tsx) | An audit log that fills the height of its panel and loads older entries while scrolling; the status shows while the request is pending — `scrollHeight`, `paging`, `onLoadMore`. |
419
420
  | [states.tsx](examples/states.tsx) | Loading skeleton, an empty period and a load error with a retry: the head stays, only the body changes — `loading`, `loadingRows`, `empty`, `loadError`. |
420
421
  | [controlled.tsx](examples/controlled.tsx) | Sort, page and the column choice owned by the parent (a URL or a store): a header click goes asc → desc → none, a new sort returns to page 1 — `sort`, `onSortChange`, `page`, `onPageChange`, `hiddenColumns`, `onHiddenColumnsChange`. |
@@ -461,5 +462,5 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
461
462
  - A new search that keeps the current page → set `page` to 1 together with the search.
462
463
 
463
464
  ## Related
464
- - **Built from:** [Checkbox](../checkbox/COMPONENT.md), [Button](../button/COMPONENT.md), [Dropdown](../dropdown/COMPONENT.md) (the column chooser, the selection actions menu), [Pagination](../pagination/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [VisuallyHidden](../visually-hidden/COMPONENT.md) (loading status, selection announcement), [Skeleton](../skeleton/COMPONENT.md) (loading rows, in the table tier), [Dnd](../dnd/COMPONENT.md) (`onReorder`: `Dnd.Sortable as="tbody"`, `Dnd.Handle`; `columnOrder`: the head row as a `Dnd.Sortable as="tr"`, the chooser's `Dnd.Sortable` list, `Dnd.Handle`, the lock), [Badge](../badge/COMPONENT.md) (the count on `DataTableColumnsMenu` and on the selection actions menu), [Tooltip](../tooltip/COMPONENT.md) (handle hint, a column `description`, the column chooser), Icon (sort arrows, the info glyph of a column `description`, the expand chevron, the column chooser trigger); `useVirtualRows` (the window of a long body); `useFlipList` (the head cells glide to an order set from outside the head row); `enterMotion` (the moved column fades in); `ToolRow` (the `toolbar` row, the selection actions cell and the `toolbarChips` line) with [SmartFilter](../smart-filter/COMPONENT.md), [Input](../input/COMPONENT.md) and [Typography](../typography/COMPONENT.md) (what grows in the tool row), [SegmentedControl](../segmented-control/COMPONENT.md), [Datepicker](../datepicker/COMPONENT.md) and [Select](../select/COMPONENT.md) (what fills a short line; the first two get `fullWidth`); `partPresence` (the selection actions menu and the tools row it brings enter and close up)
465
+ - **Built from:** [Checkbox](../checkbox/COMPONENT.md), [Button](../button/COMPONENT.md), [Dropdown](../dropdown/COMPONENT.md) (the column chooser, the selection actions menu), [Pagination](../pagination/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [VisuallyHidden](../visually-hidden/COMPONENT.md) (loading status, selection announcement), [Skeleton](../skeleton/COMPONENT.md) (loading rows, in the table tier), [Spinner](../spinner/COMPONENT.md) (the footer status while more rows load), [Dnd](../dnd/COMPONENT.md) (`onReorder`: `Dnd.Sortable as="tbody"`, `Dnd.Handle`; `columnOrder`: the head row as a `Dnd.Sortable as="tr"`, the chooser's `Dnd.Sortable` list, `Dnd.Handle`, the lock), [Badge](../badge/COMPONENT.md) (the count on `DataTableColumnsMenu` and on the selection actions menu), [Tooltip](../tooltip/COMPONENT.md) (handle hint, a column `description`, the column chooser), Icon (sort arrows, the info glyph of a column `description`, the expand chevron, the column chooser trigger); `useVirtualRows` (the window of a long body); `useFlipList` (the head cells glide to an order set from outside the head row); `enterMotion` (the moved column fades in); `ToolRow` (the `toolbar` row, the selection actions cell and the `toolbarChips` line) with [SmartFilter](../smart-filter/COMPONENT.md), [Input](../input/COMPONENT.md) and [Typography](../typography/COMPONENT.md) (what grows in the tool row), [SegmentedControl](../segmented-control/COMPONENT.md), [Datepicker](../datepicker/COMPONENT.md) and [Select](../select/COMPONENT.md) (what fills a short line; the first two get `fullWidth`); `partPresence` (the selection actions menu and the tools row it brings enter and close up)
465
466
  - **See also:** [Sparkline](../sparkline/COMPONENT.md) (`layout="compact"` in a cell), [SmartFilter](../smart-filter/COMPONENT.md), [Page](../../layout/page/COMPONENT.md) (`Page.Tools`, `Page.Chips`), [Gantt](../gantt/COMPONENT.md), [Kanban](../kanban/COMPONENT.md), [Badge](../badge/COMPONENT.md), [Avatar](../avatar/COMPONENT.md), [Card](../card/COMPONENT.md)
@@ -1,4 +1,4 @@
1
- /** A CRM deal list the server sorts, pages and searches, with its tools in its own row — search, filter and column chooser, the active filters under them; a new search returns to page 1 — `source`, `rowCount`, `sort`, `page`, `loading`, `toolbar`, `toolbarChips`. */
1
+ /** A CRM deal list the server sorts, pages and searches, with its tools in its own row — search, filter and column chooser, the active filters under them; a new search returns to page 1, and the footer counts the deals shown out of the total — `source`, `rowCount`, `showRange`, `sort`, `page`, `loading`, `toolbar`, `toolbarChips`. */
2
2
  import {
3
3
  Badge,
4
4
  DataTable,
@@ -174,6 +174,7 @@ export default function DataTableServerExample() {
174
174
  hiddenColumns={hidden}
175
175
  rows={result.rows}
176
176
  rowCount={result.total}
177
+ showRange
177
178
  getRowKey={(row) => row.id}
178
179
  pageSize={PAGE_SIZE}
179
180
  page={page}
@@ -150,7 +150,7 @@ A date as a form submits it, in local time: `yyyy-MM-dd`, with `withTime` `yyyy-
150
150
  ### Flags
151
151
  | Value | Looks like | Use when | Default |
152
152
  |---|---|---|---|
153
- | `presets` | «Период» column on the left (a scrolling row above when narrow); the active preset is pressed | analytics periods | `false` |
153
+ | `presets` | a column of preset buttons on the left (a scrolling row above when narrow); the active preset is pressed | analytics periods | `false` |
154
154
  | `prompt` | muted step line under the grid | infrequent use of ranges | `false` |
155
155
  | `footer` | dates (dd.MM.yyyy, dd.MM with `yearless`) and «Сбросить» / «Применить» | the user confirms, or with time | `false` |
156
156
  | `withTime` | HH:mm inputs in the footer | timestamps (with `footer`) | `false` |
@@ -227,7 +227,7 @@ Committing: without `footer` a picked day (or the second day of a range) applies
227
227
  | `timeEnd` | `"Время конца"` | Name of the end time input. |
228
228
  | `time` | `"Время"` | Name of the time input of a single date. |
229
229
  | `until` | `"до"` | Prefix of a range without a start: «до 6 окт». |
230
- | `presetsTitle` | `"Период"` | Heading and group name of the presets column. |
230
+ | `presetsGroup` | `"Период"` | Accessible name of the presets group (no visible heading). |
231
231
  | `optional` | `"необязательно"` | Muted marker after the label when `optional`. |
232
232
  | `required` | `"обязательное поле"` | Read after the field name of a `required` field: its trigger is a button, which takes no `aria-required`. |
233
233
 
@@ -226,7 +226,7 @@ An item's own `onPointerDown` / `onKeyDown` run before the kit's: `preventDefaul
226
226
  ### ARIA
227
227
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
228
228
 
229
- - A live region (`role="status"`, `aria-live="polite"`) announces grabbed, dropped, returned, cancelled, «position N of M», a move to another list by its name and a refusal with its reason, from `labels`.
229
+ - A live region (`role="status"`, `aria-live="polite"`) announces grabbed, dropped, returned, cancelled, «position N of M», a move to another list by its name, a refusal with its reason and a keyboard step a `locked` item stops, from `labels`.
230
230
  - The keyboard target of a sortable item has `aria-keyshortcuts` (with the across keys in linked lists) and `aria-roledescription` and is in the tab order: the item itself (`tabIndex={0}`; a named `<div>` item is a `button`, an `li` or a `tr` keeps its role), or with `handle` its grip. A role description stands only on an element with a role. The clone is `aria-hidden`.
231
231
  - While `onReorder`'s promise is pending, the item has `aria-busy` and holds still, and its `Dnd.Handle` is busy.
232
232
  - In lists sharing a `kind`, Alt + the across arrows move the item to the same row of the neighbouring list in document order; focus follows the item.
@@ -249,6 +249,7 @@ An item's own `onPointerDown` / `onKeyDown` run before the kit's: `preventDefaul
249
249
  | `cancelled` | `"{label}: перемещение отменено"` | Live region: cancelled (Escape, interruption). |
250
250
  | `moved` | `"{label}: позиция {position} из {total}"` | Live region: moved with the keyboard. |
251
251
  | `movedTo` | `"{label}: «{list}», позиция {position} из {total}"` | Live region: moved to a linked list with the keyboard; `{list}` is its accessible name. |
252
+ | `blocked` | `"{label}: «{locked}» закреплено, дальше не переместить"` | Live region: a keyboard step refused because a `locked` item holds that place; `{locked}` is its label. |
252
253
  | `refused` | `"{label}: «{list}» не принимает элемент"` | Live region: a linked list refused a keyboard move (`canDrop` answered `false`). |
253
254
  | `refusedReason` | `"{label}: {reason}"` | Live region: a target refused the item and said why (`canDrop` answered a string). |
254
255
 
@@ -33,7 +33,7 @@ Drawer.Root state and dismiss policy (no DOM)
33
33
  │ ├─ Drawer.Icon tone tile around an Icon, or the record's Avatar / Thumbnail, no tile, 40
34
34
  │ ├─ Drawer.Title line 1: <h2>; `loading` holds it with a skeleton
35
35
  │ ├─ Drawer.Description line 2: <p>; the record's status Badges lead it (tier s), then the text
36
- │ └─ Drawer.Actions buttons only (icon buttons, a ⋯ Dropdown), tier s, on the title's line
36
+ │ └─ Drawer.Actions one tab-wide switcher, then buttons (icon buttons, a ⋯ Dropdown), tier s, on the title's line
37
37
  ├─ Drawer.Body the only scrolling zone (ScrollContainer)
38
38
  │ └─ Drawer.Section <section>: <h3> label, description, Drawer.Actions at the heading's end
39
39
  └─ Drawer.Footer always: Close or Cancel + the main action, primary last
@@ -82,7 +82,7 @@ No DOM, no ref. State and dismiss policy.
82
82
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `className` and the other attributes of the `<header>`. |
83
83
 
84
84
  ### Drawer.Actions
85
- `ref` → `HTMLDivElement`. Header buttons only (icon buttons, a ⋯ Dropdown; a status goes in `Drawer.Description`), tier `s`, on the title's line; under it below 12rem; at the end of a `Drawer.Section` heading. In `Drawer.Footer`: the start group at the footer tier.
85
+ `ref` → `HTMLDivElement`. Header: a tab-wide switcher (`SegmentedControl`, `Datepicker`), then buttons (a status goes in `Drawer.Description`), tier `s`; under the title below 12rem; at the end of a `Drawer.Section` heading. In `Drawer.Footer`: the start group at the footer tier.
86
86
 
87
87
  | Prop | Type | Default | Description |
88
88
  |---|---|---|---|
@@ -208,13 +208,13 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
208
208
  ## Layout & spacing
209
209
  - The header has one anatomy, the same in every drawer, modal and sheet:
210
210
  ```
211
- [mark] Title [icon buttons] [⋯] [×]
211
+ [mark] Title [tab switcher] [icon buttons] [⋯] [×]
212
212
  [status badges] description text
213
213
  ```
214
214
  - mark — `Drawer.Icon`: a glyph on the tone tile, or the record's picture (`Avatar.Root`, `Thumbnail.Root`) with no tile; both are 40 px, so the header never changes its height between a person and a setting.
215
215
  - line 1 — `Drawer.Title`, the record's name.
216
216
  - line 2 — `Drawer.Description`: the record's statuses as `Badge.Root`s first, then its text (an email, a handle, «Заказ № 1042 · 3 коробки»). The kit sizes the badges (chip tier `s`, the line's own 20 px), keeps them first and one gap apart, and wraps the text under them on a narrow panel. A status belongs to the record, so it lives on its line, never next to the buttons.
217
- - end — `Drawer.Actions`: only buttons — ghost icon buttons, each named and with a Tooltip, and a ⋯ Dropdown for the rest — at the close button's tier, centred on the title's line.
217
+ - end — `Drawer.Actions`: first one switcher that applies to the whole open tab (a `SegmentedControl` — the text language RU / EN; a `Datepicker` — the period, the same one as on the page), then the buttons — ghost icon buttons, each named and with a Tooltip, and a ⋯ Dropdown for the rest — all at the close button's tier, centred on the title's line. A switcher that applies only to some tabs is rendered only on them: each control stands in a cell that rises in and closes up, so the buttons and the title glide instead of jumping.
218
218
  - A drawer has three zones: `Drawer.Header` (title, the × close button), `Drawer.Body` (the content, the only zone that scrolls) and `Drawer.Footer` (the actions). The footer is not optional decoration — it is where the drawer is finished:
219
219
  - read-only details: `Drawer.Close` around `<Button.Root variant="outline" tone="neutral">Закрыть</Button.Root>`, plus the record's main action (solid, last) when it has one;
220
220
  - a form: «Отмена» (`Drawer.Close`, outline neutral) and the submit button (solid, last; `type="submit" form={formId}`), `Drawer.Meta` for «Есть несохранённые изменения»;
@@ -225,7 +225,7 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
225
225
  - Full viewport height; only the body scrolls, with its own side padding.
226
226
  - Tabs in the header: a `Tabs.List` inside `Drawer.Header` (with `Tabs.Root` around the header and the body) spans the header's last row. The header becomes the tab strip, edge to edge and without the hairline; the active tab is the body rising into it, the panels sit in the body.
227
227
  - Fields inside get the surface field fill.
228
- - Titled groups of the body (a record's contacts, its deals, its history): `Drawer.Section label` — an `<h3>` under the drawer's `<h2>` title, an optional `description`, a `Drawer.Actions` at the end of its heading row; content 16 below, sections 32 apart — directly in the body, in a `Tabs.Panel` or in a form, with no spacing wrapper of your own. A group of fields is a `Fieldset`; a page section is `Page.Section`.
228
+ - Titled groups of the body (a record's contacts, its deals, its history): `Drawer.Section label` — an `<h3>` under the drawer's `<h2>` title, an optional `description`, a `Drawer.Actions` at the end of its heading row; content 16 below, sections 32 apart — directly in the body, in a `Tabs.Panel` or in a form, with no spacing wrapper of your own. A tab's blocks: a `Drawer.Section` with a label, or a self-titled block (a `ChartFrame`, a `Card` with its `Card.Title`) directly in the panel — a `Tabs.Panel` in the body stands its blocks 32 apart, the step between sections, by itself. A group of fields is a `Fieldset`; a page section is `Page.Section`.
229
229
  - With a `Drawer.Meta` the buttons stay one row: when the footer runs short the status goes above them first.
230
230
  - `Drawer.Actions` keep their place beside the title while it has 12rem; on a phone, where the buttons beside the × would fold the title into letters, they move under it by themselves.
231
231
  - Opening a record: the header and its tabs come at once, the body as a `Skeleton` in the data's geometry. A record opened from a list row comes with the data the row already has (its title, often its status) at once, and only the rest loads (`Drawer.Description loading`); a record opened by a link knows only its id, so `Drawer.Title loading` holds the title line too. Read-only data swaps under a `Crossfade`; a form gets no `Crossfade` around it — its fields render once the record is there, so typing and focus are never cross-faded. Never a skeleton of your own in the header.
@@ -272,6 +272,7 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
272
272
  | [header-actions.tsx](examples/header-actions.tsx) | The one header of a record: the avatar, the name, its statuses leading the line under it, and only buttons at the end — `Drawer.Icon`, `Drawer.Description`, `Drawer.Actions`. |
273
273
  | [footer-actions.tsx](examples/footer-actions.tsx) | A profile panel: Sign out stands apart at the start of the footer, the main pair keeps the end; on a phone the column puts it first and the primary last — `Drawer.Footer`, `Drawer.Actions`. |
274
274
  | [client-card.tsx](examples/client-card.tsx) | A client card from the list: the contact's avatar heads the panel in place of an icon tile, then the details in titled sections, one with its own action — `Drawer.Icon`, `Avatar.Root`, `Drawer.Section`, `Drawer.Actions`. |
275
+ | [tab-switcher.tsx](examples/tab-switcher.tsx) | A model card with tabs: one switcher for the whole open tab stands in the header before ⋯ — the text language on the description tab, the period on the analytics tab, whose two self-titled charts the panel spaces by itself — `Drawer.Actions`, `Tabs.Root`, `Tabs.Panel`. |
275
276
  | [loading.tsx](examples/loading.tsx) | A record card that opens before its data arrives: the title comes from the list row, the line under it and the body hold their place in skeletons, then the data fades in — `Drawer.Description`, `loading`. |
276
277
  | [link-loading.tsx](examples/link-loading.tsx) | A record opened by a link knows only its id: the title and the line under it hold their place in skeletons, the body keeps the data's geometry, then the name and the data fade in — `Drawer.Title`, `loading`. |
277
278
  | [guard.tsx](examples/guard.tsx) | A form that holds the drawer from inside: after an edit Escape, the scrim and Cancel ask before discarding, the footer says so, and saving locks every way out — `Drawer.Guard`, `Drawer.Meta`. |
@@ -286,12 +287,14 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
286
287
  - Save or Cancel buttons at the end of the body, or in the header → they go in `Drawer.Footer`, which stays in place while the body scrolls.
287
288
  - A delete confirmation in a Drawer → use ConfirmDialog.
288
289
  - A hand-made heading row (`Typography as="h3"` + buttons) over a group in the body → `Drawer.Section label` with `Drawer.Actions`.
289
- - A wrapper with its own gap around `Drawer.Section`s inside a `Tabs.Panel` or a form → not needed: sections keep 32 between them in any wrapper.
290
+ - A wrapper with its own gap around `Drawer.Section`s or self-titled blocks inside a `Tabs.Panel`, or around sections in a form → not needed: the panel in the body stands its blocks 32 apart, and sections keep 32 between them in any wrapper.
291
+ - A `Drawer.Section` only to space a block that already names itself (a chart, a titled card) → put it directly in the `Tabs.Panel`; the panel spaces it.
290
292
  - A `Skeleton` placed by hand in `Drawer.Title` or `Drawer.Description` while the record loads → their `loading`; a placeholder title that shifts the header when the name arrives → `Drawer.Title loading`.
291
293
  - A start-side button («Выйти») put inside `Drawer.Meta` → `Drawer.Actions` inside `Drawer.Footer`; `Meta` is status text only.
292
294
  - Busy or unsaved state lifted out of the form into Root props → put `Drawer.Guard` next to the form, inside Content.
293
295
  - Clearing the record together with `open` and rendering a skeleton for «no record» → not needed: the panel keeps its last content while it slides out.
294
296
  - A status `Badge` in the header's `Drawer.Actions`, beside the buttons → put it in `Drawer.Description`, before the text: the kit sizes it to the line and keeps it first.
297
+ - A control that applies to the whole tab (the text language, the period) repeated in section headings or put in one section → one switcher in the header's `Drawer.Actions`, before the buttons, rendered only on the tabs it applies to.
295
298
  - A menu or a button as a custom child of the header text column → put it in `Drawer.Actions`.
296
299
  - A Badge with its own `size` in `Drawer.Description` → leave `size` out: the line gives every header the same chip tier.
297
300
  - An avatar wrapped in its own box, or given its own `size`, in `Drawer.Icon` → pass the `Avatar.Root` (or your component that renders it) as it is: the slot drops the tile and sets 40 px.
@@ -180,11 +180,7 @@ export default function DrawerScreenExample() {
180
180
  </Radio.Group>
181
181
  <Input.Root label="Название" error={errors.name}>
182
182
  <Input.Wrapper>
183
- <Input.Field
184
- name="name"
185
- placeholder="ООО «Ромашка»"
186
- autoComplete="organization"
187
- />
183
+ <Input.Field name="name" placeholder="ООО «Ромашка»" />
188
184
  </Input.Wrapper>
189
185
  </Input.Root>
190
186
  <div className={styles.pair}>
@@ -0,0 +1,158 @@
1
+ /** A model card with tabs: one switcher for the whole open tab stands in the header before ⋯ — the text language on the description tab, the period on the analytics tab, whose two self-titled charts the panel spaces by itself — `Drawer.Actions`, `Tabs.Root`, `Tabs.Panel`. */
2
+ import {
3
+ Button,
4
+ Card,
5
+ Datepicker,
6
+ type DatepickerRange,
7
+ DEFAULT_DATEPICKER_PRESETS,
8
+ DescriptionList,
9
+ Drawer,
10
+ Dropdown,
11
+ Icon,
12
+ SegmentedControl,
13
+ Sparkline,
14
+ type SparklinePoint,
15
+ Tabs,
16
+ } from "prime-ui-kit";
17
+ import * as React from "react";
18
+
19
+ const MODEL = {
20
+ name: "Кресло «Орион»",
21
+ sku: "OR-240",
22
+ description: {
23
+ ru: "Офисное кресло с сетчатой спинкой и поддержкой поясницы. Подлокотники регулируются в четырёх направлениях, механизм качания фиксируется в пяти положениях.",
24
+ en: "An office chair with a mesh back and lumbar support. The armrests adjust in four directions, the tilt locks in five positions.",
25
+ },
26
+ title: { ru: "Кресло «Орион», графит", en: "Orion chair, graphite" },
27
+ };
28
+
29
+ const SOLD: SparklinePoint[] = [
30
+ { label: "май", value: 52 },
31
+ { label: "июн", value: 61 },
32
+ { label: "июл", value: 58 },
33
+ { label: "авг", value: 74 },
34
+ { label: "сен", value: 83 },
35
+ { label: "окт", value: 90 },
36
+ ];
37
+
38
+ const RETURNS: SparklinePoint[] = [
39
+ { label: "май", value: 2 },
40
+ { label: "июн", value: 1 },
41
+ { label: "июл", value: 0 },
42
+ { label: "авг", value: 1 },
43
+ { label: "сен", value: 1 },
44
+ { label: "окт", value: 1 },
45
+ ];
46
+
47
+ const formatPieces = (value: number) => `${value.toLocaleString("ru-RU")} шт.`;
48
+
49
+ const ALL_TIME: DatepickerRange = { from: null, to: null };
50
+
51
+ type Lang = "ru" | "en";
52
+
53
+ export default function DrawerTabSwitcherExample() {
54
+ const [tab, setTab] = React.useState("description");
55
+ const [lang, setLang] = React.useState<Lang>("ru");
56
+ const [period, setPeriod] = React.useState<DatepickerRange>(ALL_TIME);
57
+
58
+ return (
59
+ <Drawer.Root>
60
+ <Drawer.Trigger>
61
+ <Button.Root variant="soft" tone="neutral">
62
+ Открыть модель
63
+ </Button.Root>
64
+ </Drawer.Trigger>
65
+ <Drawer.Content>
66
+ <Tabs.Root value={tab} onValueChange={setTab}>
67
+ <Drawer.Header>
68
+ <Drawer.Title>{MODEL.name}</Drawer.Title>
69
+ <Drawer.Description>Артикул {MODEL.sku}</Drawer.Description>
70
+ <Drawer.Actions>
71
+ {tab === "description" ? (
72
+ <SegmentedControl.Root
73
+ aria-label="Язык текстов"
74
+ value={lang}
75
+ onValueChange={(next) => setLang(next as Lang)}
76
+ >
77
+ <SegmentedControl.Item value="ru">RU</SegmentedControl.Item>
78
+ <SegmentedControl.Item value="en">EN</SegmentedControl.Item>
79
+ </SegmentedControl.Root>
80
+ ) : (
81
+ <Datepicker.Root
82
+ mode="range"
83
+ aria-label="Период"
84
+ placeholder="Всё время"
85
+ value={period}
86
+ onValueChange={setPeriod}
87
+ resetValue={ALL_TIME}
88
+ presets={DEFAULT_DATEPICKER_PRESETS}
89
+ disableFuture
90
+ align="end"
91
+ />
92
+ )}
93
+ <Dropdown.Root>
94
+ <Dropdown.Trigger>
95
+ <Button.Root variant="ghost" tone="neutral" aria-label="Действия с моделью">
96
+ <Button.Icon>
97
+ <Icon name="action.more" />
98
+ </Button.Icon>
99
+ </Button.Root>
100
+ </Dropdown.Trigger>
101
+ <Dropdown.Content>
102
+ <Dropdown.Item>Дублировать</Dropdown.Item>
103
+ <Dropdown.Item>Снять с продажи</Dropdown.Item>
104
+ </Dropdown.Content>
105
+ </Dropdown.Root>
106
+ </Drawer.Actions>
107
+ <Tabs.List aria-label={MODEL.name}>
108
+ <Tabs.Item value="description">Описание</Tabs.Item>
109
+ <Tabs.Item value="analytics">Аналитика</Tabs.Item>
110
+ </Tabs.List>
111
+ </Drawer.Header>
112
+ <Drawer.Body>
113
+ <Tabs.Panel value="description">
114
+ <DescriptionList.Root>
115
+ <DescriptionList.Item label="Название на сайте">
116
+ {MODEL.title[lang]}
117
+ </DescriptionList.Item>
118
+ <DescriptionList.Item label="Описание">
119
+ {MODEL.description[lang]}
120
+ </DescriptionList.Item>
121
+ </DescriptionList.Root>
122
+ </Tabs.Panel>
123
+ <Tabs.Panel value="analytics">
124
+ <Card.Root variant="panel">
125
+ <Card.Header>
126
+ <Card.Title>Продажи</Card.Title>
127
+ <Card.Description>По месяцам, в штуках</Card.Description>
128
+ </Card.Header>
129
+ <Card.Body>
130
+ <Sparkline
131
+ data={SOLD}
132
+ label="Продано за период"
133
+ summary="sum"
134
+ formatValue={formatPieces}
135
+ />
136
+ </Card.Body>
137
+ </Card.Root>
138
+ <Card.Root variant="panel">
139
+ <Card.Header>
140
+ <Card.Title>Возвраты</Card.Title>
141
+ <Card.Description>По месяцам, в штуках</Card.Description>
142
+ </Card.Header>
143
+ <Card.Body>
144
+ <Sparkline
145
+ data={RETURNS}
146
+ label="Возвращено за период"
147
+ summary="sum"
148
+ formatValue={formatPieces}
149
+ />
150
+ </Card.Body>
151
+ </Card.Root>
152
+ </Tabs.Panel>
153
+ </Drawer.Body>
154
+ </Tabs.Root>
155
+ </Drawer.Content>
156
+ </Drawer.Root>
157
+ );
158
+ }
@@ -33,7 +33,7 @@ FieldList field frame: label · the list · hint / error
33
33
  │ │ └─ <li> row [a cell per column] [end: × | ⋯] [grip: handle | lock]
34
34
  │ │ └─ cell one kit field without `label`, named by its column heading
35
35
  │ └─ totals row `renderTotals`: a group «Итого», a total per column on the same tracks
36
- └─ add row Button «addLabel» (+ `labels.maxReached` at `max`)
36
+ └─ add row Button «addLabel» (leaves at `max`)
37
37
  ```
38
38
  A leaf: rows come from `value` through `renderItem`, which returns one element per column (a fragment). Every framed kit field (Input, Select, Datepicker, NativeSelect, ColorField…) in a cell takes its column heading as its label: read by screen readers, not shown while the heading row is on screen, shown above the field once the rows stack in a narrow container. `ColorPresets` takes it as the name of its trigger. Every row, a one-column row too, ends with the same slots: the remove «×» (or the «⋯» menu) and, with `reorderable` buttons, the grip after it — never a segment inside a field, where a «×» would read as «clear the text».
39
39
 
@@ -53,7 +53,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
53
53
  | `value` | `readonly T[]` | — | The rows (controlled). |
54
54
  | `defaultValue` | `readonly T[]` | `[]` | The first rows (uncontrolled). |
55
55
  | `onValueChange` | `(value: T[]) => void \| Promise<void>` | — | The new list after an edit (`row.update`), an add, a remove or a move. Return the save's promise: the new list shows while it is pending, the owner's again on a reject (say why with `error`). |
56
- | `createItem` | `() => T` | — | A new empty item; with it the add button shows under the rows and focus goes to the new row's first field. |
56
+ | `createItem` | `() => T` | — | A new empty item; with it the add button shows under the rows (until `max`) and focus goes to the new row's first field. |
57
57
  | `addLabel` | `string` | — | Text of the add button: what is added («Добавить позицию»). |
58
58
  | `getLabel` | `(item: T) => string` | — | The row's own text (its title field) for the remove button, the handle, the menu button and move announcements; else the row number. |
59
59
  | `getLocked` | `(item: T) => boolean` | — | Pinned rows: the remove stays in its place, off, and says why (`labels.lockedReason`); with `reorderable` a lock takes the handle's place and the row holds it. In the menu every item is off. |
@@ -61,7 +61,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
61
61
  | `rowActions` | `"buttons" \| "menu"` | `"buttons"` | How a row offers its actions: `buttons` — a remove button «×» and, with `reorderable`, the handle after it; `menu` — one «⋯» button whose menu moves the row up or down and removes it. |
62
62
  | `variant` | `"plain" \| "surface"` | `"plain"` | Look of the list: `plain` — the rows on the host surface; `surface` — the rows on a fill of their own, one step up the surface ladder, with side padding. |
63
63
  | `min` | `number` | `0` | The fewest rows: at this count the remove buttons are disabled. |
64
- | `max` | `number` | — | The most rows: at this count the add button is disabled with `labels.maxReached` beside it. |
64
+ | `max` | `number` | — | The most rows: at this count the add button closes up in place and comes back once a row is removed. The list says no limit text of its own; a limit the person should know goes in `hint`. |
65
65
  | `label` | `ReactNode` | — | Heading of the list; names the `<ol>`. Omit it inside a `Fieldset` whose legend names the list. |
66
66
  | `aria-label` | `string` | — | Name of the `<ol>` when no visible `label` names it (a section heading above the list). |
67
67
  | `hint` | `ReactNode` | — | Help text under the list; hidden while `error` shows. |
@@ -101,7 +101,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
101
101
  | `reorderable` | buttons: a grip at the very end of every row, after the remove button, Alt + ↑ / ↓ on it; menu: «Выше» and «Ниже» in the menu, Alt + ↑ / ↓ on «⋯» | the order of the records matters (a pricing page, a checklist, payment stages) | `false` |
102
102
  | `getLocked` | the remove button in its place, off, with a tooltip saying why (`labels.lockedReason`); with `reorderable` a lock where the grip is and the row holds its place; in the menu every item off, «Удалить» says why | a base value, system records a person cannot drop | — |
103
103
  | `createItem` | the add button under the rows, aligned with the first column | the person grows the list | — |
104
- | `min` · `max` | remove buttons off at `min`; the add button off with «Не больше {max}» at `max` | the record count is bounded | `0` · — |
104
+ | `min` · `max` | remove buttons off at `min`; the add button closes up at `max` and comes back when a row leaves — no limit text of its own, a limit the person should know goes in `hint` | the record count is bounded | `0` · — |
105
105
  | `renderTotals` | a row of totals under the rows over a faint line, each on its column, aligned as the column's field text; «Итого» in the first cell without a total | the rows add up (shares, amounts, hours) | — |
106
106
 
107
107
  **Combinations**
@@ -115,7 +115,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
115
115
  |---|---|---|
116
116
  | empty | `value` with no items | no heading row; only the add button (with `createItem`) |
117
117
  | at min | `value.length <= min` | remove buttons (or «Удалить» items) `disabled` |
118
- | at max | `value.length >= max` | add button `disabled`, `labels.maxReached` beside it |
118
+ | at max | `value.length >= max` | no add button: it closes up in place (`partPresence`) and rises in again when a row leaves |
119
119
  | pinned row | `getLocked(item)` | `data-locked="true"` on the `<li>`; the remove button stays in its slot, `disabled` behind a focusable Tooltip host, its tooltip `labels.lockedReason`; with `reorderable` Dnd's lock in the grip box; in the menu every item `disabled`, «Удалить» described by `labels.lockedReason` |
120
120
  | hover | the pointer over a row of a `surface` list (`(hover: hover)` only; a `plain` row has no fill of its own) | the whole row takes the field fill (`--prime-color-fill-muted`) and its fields the surface fill (`--prime-color-layer-current`) |
121
121
  | invalid | `error` (the list), a field's own `error` | the list's message under the add row; a field's under the field in its row |
@@ -172,7 +172,6 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
172
172
  | `row` | `"строка {index}"` | Name of a row without `getLabel` text; `{index}` counts from 1. |
173
173
  | `namedRow` | `"«{label}»"` | Name of a row with `getLabel` text; `{label}` is that text. |
174
174
  | `lockedReason` | `"Закреплённую строку нельзя удалить"` | Why a pinned row (`getLocked`) cannot be removed: the tooltip of its remove button, the description of its menu's remove item. |
175
- | `maxReached` | `"Не больше {max}"` | Text beside the disabled add button once the list holds `max` rows. |
176
175
  | `totals` | `"Итого"` | Name of the totals row (`renderTotals`), shown in its first cell when that has no total. |
177
176
 
178
177
  ## Examples
@@ -185,7 +184,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
185
184
  | [row-menu.tsx](examples/row-menu.tsx) | Payment stages of a project on a touch-first screen, where a drag is awkward: one more-button per row holds move up, move down and remove, the moves are off at the ends and next to the pinned advance, and Alt+arrows on the button move the row too — `rowActions`, `reorderable`, `getLocked`. |
186
185
  | [locked.tsx](examples/locked.tsx) | Specs of a plan on one line each: an icon picker and its color side by side in compact columns, the first three specs are pinned with a lock in the handle's place and a remove button that is off and says why, the rest reorder below them — `getLocked`, `width`, `reorderable`. |
187
186
  | [base-row.tsx](examples/base-row.tsx) | A plan price and its changes by month in one table: the base price is the first, pinned row whose remove button is off and says why, each change is a row with its month and its price, and the unit sits in the field, not in a heading — `getLocked`, `labels`, `width`. |
188
- | [limits.tsx](examples/limits.tsx) | Recipients of invoice reminders, from one to three: the last row cannot be removed and the add button says why it is off once the list is full — `min`, `max`, `hint`. |
187
+ | [limits.tsx](examples/limits.tsx) | Recipients of invoice reminders, from one to three: the last row cannot be removed, the add button leaves once the list is full and the hint names the limit — `min`, `max`, `hint`. |
189
188
  | [errors.tsx](examples/errors.tsx) | Contacts of a client checked on save: a field's problem shows under that field in its row, a problem of the whole list under the list, and focus goes to the first wrong field; a `*` after a heading marks a required column — `error`, `required`. |
190
189
  | [totals.tsx](examples/totals.tsx) | A deal bonus split between the team: the share takes a narrow column, and the totals row under the lines sums the shares and the amounts, each ending where its fields end their numbers — `renderTotals`, `width`. |
191
190
  | [saving.tsx](examples/saving.tsx) | Removing a recipient is saved on the server: the row leaves at once and comes back with the reason if the server refuses — `onValueChange`, `error`. |
@@ -206,6 +205,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
206
205
  - A «⋯» menu or a «−» drawn by hand next to the rows → `rowActions`; a Card drawn around the list to set it apart from the form → `variant="surface"`.
207
206
  - `rowActions="menu"` on a two-column list a person reorders all day → the buttons: the handle and «×» in sight are one gesture, the menu is three.
208
207
  - `aria-label` written on every field in a cell → none: the column heading names the field.
208
+ - `createItem` dropped by hand once nothing is left to add (every section picked) → `max`: the add button leaves at the limit by itself; a limit the person should know is a `hint` («Не больше трёх получателей»), never a count beside the button.
209
209
  - A grip, a remove button and an add button drawn by hand around a `.map()` of fields → FieldList.
210
210
  - A total under the list typed by hand in a line of text, or a DataTable of fields to get a totals row → `renderTotals`.
211
211
 
@@ -1,4 +1,4 @@
1
- /** Recipients of invoice reminders, from one to three: the last row cannot be removed and the add button says why it is off once the list is full — `min`, `max`, `hint`. */
1
+ /** Recipients of invoice reminders, from one to three: the last row cannot be removed, the add button leaves once the list is full and the hint names the limit — `min`, `max`, `hint`. */
2
2
  import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
3
  import * as React from "react";
4
4
 
@@ -18,7 +18,7 @@ export default function FieldListLimitsExample() {
18
18
  return (
19
19
  <FieldList
20
20
  label="Кому напоминать об оплате"
21
- hint="Письмо уходит за три дня до срока."
21
+ hint="Письмо уходит за три дня до срока, не больше трёх получателей."
22
22
  columns={COLUMNS}
23
23
  min={1}
24
24
  max={3}