prime-ui-kit 0.20.1 → 0.21.1

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 (154) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/{chunk-4KONMO7Z.js → chunk-6L7VWEY6.js} +1 -1
  3. package/dist/chunks/{chunk-3PY52JP3.js → chunk-S27VR4ON.js} +5828 -5596
  4. package/dist/chunks/{chunk-4374EBWV.js → chunk-S4H2ZKWJ.js} +1049 -644
  5. package/dist/color-picker.js +2 -2
  6. package/dist/components/badge/Badge.d.ts +19 -2
  7. package/dist/components/card/Card.d.ts +10 -3
  8. package/dist/components/composer/Composer.d.ts +49 -1
  9. package/dist/components/crossfade/Crossfade.d.ts +6 -4
  10. package/dist/components/data-table/DataTable.d.ts +10 -2
  11. package/dist/components/data-table/Row.d.ts +12 -1
  12. package/dist/components/data-table/SelectionMenu.d.ts +20 -0
  13. package/dist/components/data-table/useRowSelection.d.ts +2 -0
  14. package/dist/components/empty-page/EmptyPage.d.ts +5 -2
  15. package/dist/components/hint/Hint.d.ts +1 -1
  16. package/dist/components/index.d.ts +1 -1
  17. package/dist/components/input/Input.d.ts +15 -3
  18. package/dist/components/message/Message.d.ts +6 -3
  19. package/dist/components/segmented-control/SegmentedControl.d.ts +5 -1
  20. package/dist/components/smart-filter/SmartFilter.d.ts +4 -0
  21. package/dist/components/spinner/Spinner.d.ts +3 -2
  22. package/dist/components/tabs/Tabs.d.ts +10 -1
  23. package/dist/hooks/useModalLayer.d.ts +15 -8
  24. package/dist/index.css +1 -1
  25. package/dist/index.js +2 -2
  26. package/dist/internal/RegionSwap.d.ts +18 -10
  27. package/dist/internal/RollingNumber.d.ts +7 -5
  28. package/dist/internal/ToolRow.d.ts +6 -0
  29. package/dist/internal/mediaSlot.d.ts +29 -0
  30. package/dist/internal/overlay/DialogParts.d.ts +30 -6
  31. package/dist/internal/overlay/focus.d.ts +18 -3
  32. package/dist/internal/partPresence.d.ts +19 -2
  33. package/dist/layout/page/Page.d.ts +1 -1
  34. package/dist/layout/sidebar/Sidebar.d.ts +3 -2
  35. package/dist/skill/anti-slop.md +35 -37
  36. package/dist/skill/api-contract.md +50 -2
  37. package/dist/skill/cheatsheet.md +15 -10
  38. package/dist/skill/checklist.md +20 -6
  39. package/dist/skill/choosing.md +82 -8
  40. package/dist/skill/components.md +1 -1
  41. package/dist/skill/composition.md +268 -51
  42. package/dist/skill/foundations.md +17 -6
  43. package/dist/skill/layouts.md +65 -14
  44. package/dist/skill/motion.md +9 -3
  45. package/dist/skill/reference/components/avatar/COMPONENT.md +1 -0
  46. package/dist/skill/reference/components/badge/COMPONENT.md +29 -5
  47. package/dist/skill/reference/components/badge/examples/examples.module.css +14 -0
  48. package/dist/skill/reference/components/badge/examples/overflow-count.tsx +32 -0
  49. package/dist/skill/reference/components/banner/COMPONENT.md +1 -1
  50. package/dist/skill/reference/components/card/COMPONENT.md +8 -4
  51. package/dist/skill/reference/components/card/examples/person-header.tsx +38 -0
  52. package/dist/skill/reference/components/composer/COMPONENT.md +35 -5
  53. package/dist/skill/reference/components/composer/examples/bot-keyboard.tsx +43 -0
  54. package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
  55. package/dist/skill/reference/components/crossfade/COMPONENT.md +18 -10
  56. package/dist/skill/reference/components/data-table/COMPONENT.md +17 -11
  57. package/dist/skill/reference/components/data-table/examples/screen.tsx +6 -9
  58. package/dist/skill/reference/components/data-table/examples/selection.tsx +60 -18
  59. package/dist/skill/reference/components/drawer/COMPONENT.md +41 -19
  60. package/dist/skill/reference/components/drawer/examples/footer-actions.tsx +54 -0
  61. package/dist/skill/reference/components/drawer/examples/header-actions.tsx +46 -25
  62. package/dist/skill/reference/components/drawer/examples/link-loading.tsx +57 -0
  63. package/dist/skill/reference/components/drawer/examples/screen.tsx +3 -5
  64. package/dist/skill/reference/components/empty-page/COMPONENT.md +2 -2
  65. package/dist/skill/reference/components/field-list/COMPONENT.md +4 -4
  66. package/dist/skill/reference/components/field-list/examples/row-menu.tsx +1 -1
  67. package/dist/skill/reference/components/hint/COMPONENT.md +1 -1
  68. package/dist/skill/reference/components/input/COMPONENT.md +16 -3
  69. package/dist/skill/reference/components/input/examples/password-policy.tsx +65 -0
  70. package/dist/skill/reference/components/list/COMPONENT.md +1 -1
  71. package/dist/skill/reference/components/login-form/COMPONENT.md +13 -11
  72. package/dist/skill/reference/components/login-form/examples/overview.tsx +5 -5
  73. package/dist/skill/reference/components/login-form/examples/states.tsx +7 -6
  74. package/dist/skill/reference/components/message/COMPONENT.md +5 -5
  75. package/dist/skill/reference/components/message/examples/bot-actions.tsx +47 -28
  76. package/dist/skill/reference/components/modal/COMPONENT.md +31 -12
  77. package/dist/skill/reference/components/modal/examples/structure.tsx +2 -4
  78. package/dist/skill/reference/components/segmented-control/COMPONENT.md +11 -6
  79. package/dist/skill/reference/components/segmented-control/examples/router.tsx +22 -0
  80. package/dist/skill/reference/components/select/COMPONENT.md +4 -1
  81. package/dist/skill/reference/components/sheet/COMPONENT.md +31 -12
  82. package/dist/skill/reference/components/sheet/examples/header-actions.tsx +5 -3
  83. package/dist/skill/reference/components/skeleton/COMPONENT.md +3 -2
  84. package/dist/skill/reference/components/smart-filter/COMPONENT.md +2 -1
  85. package/dist/skill/reference/components/smart-filter/examples/ranges.tsx +8 -2
  86. package/dist/skill/reference/components/spinner/COMPONENT.md +1 -1
  87. package/dist/skill/reference/components/tabs/COMPONENT.md +7 -4
  88. package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +18 -2
  89. package/dist/skill/reference/components/thumbnail/COMPONENT.md +1 -0
  90. package/dist/skill/reference/components/tooltip/COMPONENT.md +3 -3
  91. package/dist/skill/reference/foundation.md +15 -9
  92. package/dist/skill/reference/layout/page/COMPONENT.md +10 -6
  93. package/dist/skill/reference/layout/page/examples/router.tsx +124 -0
  94. package/dist/skill/reference/layout/page/examples/tools.tsx +12 -22
  95. package/dist/skill/reference/layout/sidebar/COMPONENT.md +8 -3
  96. package/dist/skill/reference/layout/sidebar/examples/account-profile.tsx +66 -0
  97. package/dist/skill/responsive.md +28 -6
  98. package/dist/skill/ux.md +97 -2
  99. package/dist/tokens/semantic.d.ts +2 -1
  100. package/dist/tokens/themes/dark.d.ts +1 -0
  101. package/package.json +1 -1
  102. package/src/components/avatar/COMPONENT.md +1 -0
  103. package/src/components/badge/COMPONENT.md +29 -5
  104. package/src/components/badge/examples/examples.module.css +14 -0
  105. package/src/components/badge/examples/overflow-count.tsx +32 -0
  106. package/src/components/banner/COMPONENT.md +1 -1
  107. package/src/components/card/COMPONENT.md +8 -4
  108. package/src/components/card/examples/person-header.tsx +38 -0
  109. package/src/components/composer/COMPONENT.md +35 -5
  110. package/src/components/composer/examples/bot-keyboard.tsx +43 -0
  111. package/src/components/confirm-dialog/COMPONENT.md +1 -1
  112. package/src/components/crossfade/COMPONENT.md +18 -10
  113. package/src/components/data-table/COMPONENT.md +17 -11
  114. package/src/components/data-table/examples/screen.tsx +6 -9
  115. package/src/components/data-table/examples/selection.tsx +60 -18
  116. package/src/components/drawer/COMPONENT.md +41 -19
  117. package/src/components/drawer/examples/footer-actions.tsx +54 -0
  118. package/src/components/drawer/examples/header-actions.tsx +46 -25
  119. package/src/components/drawer/examples/link-loading.tsx +57 -0
  120. package/src/components/drawer/examples/screen.tsx +3 -5
  121. package/src/components/empty-page/COMPONENT.md +2 -2
  122. package/src/components/field-list/COMPONENT.md +4 -4
  123. package/src/components/field-list/examples/row-menu.tsx +1 -1
  124. package/src/components/hint/COMPONENT.md +1 -1
  125. package/src/components/input/COMPONENT.md +16 -3
  126. package/src/components/input/examples/password-policy.tsx +65 -0
  127. package/src/components/list/COMPONENT.md +1 -1
  128. package/src/components/login-form/COMPONENT.md +13 -11
  129. package/src/components/login-form/examples/overview.tsx +5 -5
  130. package/src/components/login-form/examples/states.tsx +7 -6
  131. package/src/components/message/COMPONENT.md +5 -5
  132. package/src/components/message/examples/bot-actions.tsx +47 -28
  133. package/src/components/modal/COMPONENT.md +31 -12
  134. package/src/components/modal/examples/structure.tsx +2 -4
  135. package/src/components/segmented-control/COMPONENT.md +11 -6
  136. package/src/components/segmented-control/examples/router.tsx +22 -0
  137. package/src/components/select/COMPONENT.md +4 -1
  138. package/src/components/sheet/COMPONENT.md +31 -12
  139. package/src/components/sheet/examples/header-actions.tsx +5 -3
  140. package/src/components/skeleton/COMPONENT.md +3 -2
  141. package/src/components/smart-filter/COMPONENT.md +2 -1
  142. package/src/components/smart-filter/examples/ranges.tsx +8 -2
  143. package/src/components/spinner/COMPONENT.md +1 -1
  144. package/src/components/tabs/COMPONENT.md +7 -4
  145. package/src/components/tabs/examples/in-drawer.tsx +18 -2
  146. package/src/components/thumbnail/COMPONENT.md +1 -0
  147. package/src/components/tooltip/COMPONENT.md +3 -3
  148. package/src/layout/page/COMPONENT.md +10 -6
  149. package/src/layout/page/examples/router.tsx +124 -0
  150. package/src/layout/page/examples/tools.tsx +12 -22
  151. package/src/layout/sidebar/COMPONENT.md +8 -3
  152. package/src/layout/sidebar/examples/account-profile.tsx +66 -0
  153. package/src/styles/theme-dark.css +1 -0
  154. package/src/styles/theme-light.css +1 -0
@@ -16,7 +16,7 @@
16
16
  - Hierarchical rows (`getRowChildren`) or rows with a detail panel (`renderExpanded`).
17
17
  - Wide numeric reports with a sticky head and first column.
18
18
  - Wide records on narrow screens: let people choose the columns (`columnsMenu` over the row actions, else `DataTableColumnsMenu` beside the table's other tools) instead of dropping them.
19
- - A table among other blocks (KPI cards, a chart): its search, filter, chips, column chooser and view stand in its own tools row (`toolbar` + `toolbarChips`) — a filter stands next to what it filters. The page's bar keeps the title, a period that drives several blocks and the primary action.
19
+ - Any table with its own search, filter, chips or column chooser — among other blocks or the whole page: they stand in its own tools row (`toolbar` + `toolbarChips`) — a filter stands next to what it filters. The page's bar keeps the title, sections or a view switch, a period that drives several blocks and the actions.
20
20
  - A log or a register that takes the rest of the page height (`scrollHeight="fill"`).
21
21
 
22
22
  ## When not to use
@@ -37,7 +37,8 @@ Exported types: `DataTableColumn<Row>`, `DataTableProps<Row>`, `DataTableSortSta
37
37
  ## Anatomy
38
38
  ```
39
39
  DataTable card-fill block, radius 12, clips its content
40
- ├── toolbar the table's own tools row (ToolRow, as Page.Tools): search, filter, chooser, view, bulk actions
40
+ ├── toolbar the table's own tools row (ToolRow, as Page.Tools): search, filter, chooser, view
41
+ │ ├── selection actions renderSelectionActions: «Действия · N» Button + Dropdown, last, while rows are selected
41
42
  │ └── toolbarChips the applied filters (SmartFilter.Chips) on a line under it, gone while empty
42
43
  ├── scroll viewport ScrollContainer, both axes, in a frame that holds the edge shadows
43
44
  │ └── table
@@ -57,7 +58,7 @@ DataTableColumnsMenu the column chooser outside the table, for th
57
58
  └── Dropdown a «Показывать» group of check items (with onColumnOrderChange a Dnd.Sortable list,
58
59
  a grip ends each item) · «Показать все» · «Исходный порядок»
59
60
  ```
60
- Columns are data (`DataTableColumn<Row>[]`), not child parts; the table has no parts. The column chooser has one place per table: the `columnsMenu` flag puts it in the head of the row-actions column; a table without one gets `DataTableColumnsMenu` beside the table's other tools — in its `toolbar` on a page that holds more than the table, in `Page.Tools` on a page that is the table — bound to the same `hiddenColumns` / `columnOrder` state; never alone in a `toolbar`.
61
+ Columns are data (`DataTableColumn<Row>[]`), not child parts; the table has no parts. The column chooser has one place per table: the `columnsMenu` flag puts it in the head of the row-actions column; a table without one gets `DataTableColumnsMenu` beside the table's other tools in its `toolbar`, bound to the same `hiddenColumns` / `columnOrder` state; never alone in a `toolbar`.
61
62
 
62
63
  ## API
63
64
 
@@ -94,7 +95,8 @@ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an
94
95
  | `loadingRows` | `number` | `min(pageSize, 5)` | Number of skeleton rows. |
95
96
  | `empty` | `ReactNode` | `labels.empty` | Empty-state content: a string goes into a compact EmptyPage, a node (e.g. a full `EmptyPage`) is rendered as is. |
96
97
  | `loadError` | `ReactNode` | — | Load error: replaces the body with the message in `role="alert"`. |
97
- | `toolbar` | `ReactNode` | — | The table's own tools row — search, filter, `DataTableColumnsMenu`, a view, bulk actions; two or more, unwrapped. Laid out as `Page.Tools`: the search takes the free room, the rest keep their width at the end. |
98
+ | `toolbar` | `ReactNode` | — | The table's own tools row — search, filter, `DataTableColumnsMenu`, a view; two or more, as direct children or a fragment (a wrapper component is one cell). Laid out as `Page.Tools`. |
99
+ | `renderSelectionActions` | `(selected: Key[]) => ReactNode` | — | `Dropdown.Item`s acting on the selected keys: while rows are selected, a menu button with the count enters last in the tool row, after the `toolbar`; its first item clears the selection. |
98
100
  | `toolbarChips` | `ReactNode` | — | The applied filters (`SmartFilter.Chips`) on a line under the `toolbar`, gone while empty; `SmartFilter.Root` wraps the DataTable. |
99
101
  | `selectable` | `boolean` | `false` | Leading checkbox column with «select all», Shift range, press-and-drag and Space. |
100
102
  | `selected` | `Key[]` | — | Selected row ids, controlled. |
@@ -303,15 +305,17 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
303
305
 
304
306
  ## Layout & spacing
305
307
  - The root is a layer of the surface ladder (`data-depth`, one above its host): it fills its container on `--prime-color-layer-current`, radius 12, and clips full-bleed head and rows; zebra rows and the sticky column are one ladder step off it, the head two. On a card or in a modal it is the next layer by itself — never wrap it in a Card.
306
- - A filter stands next to what it filters. `toolbar` is the table's own tools row whenever the page holds more than this table (KPI cards, a chart, another block): its search and filter (`SmartFilter.Toolbar`, chips in `toolbarChips`), `DataTableColumnsMenu`, a table / cards view, bulk actions on the selected rows. When the table is the whole page, «next to the table» is the page's bar: its search and filter stay in `Page.Tools` and `Page.Chips`. Either way the bar keeps what acts on the whole page — the title with its line of data, a period that drives several blocks, sections, the primary action (`Page.Actions`). One control never gets a row of its own.
308
+ - A filter stands next to what it filters. `toolbar` is the table's own tools row, always — on a page with other blocks (KPI cards, a chart) and on a page that is the table: its search and filter (`SmartFilter.Toolbar`, chips in `toolbarChips`), `DataTableColumnsMenu`, a table / cards view. Actions on the selected rows go to `renderSelectionActions`, never into `toolbar`: while rows are selected the table adds a «Действия · N» menu button last in the tool row — the search, filter and chooser stay, the selection never replaces the row. The page's bar keeps what acts on the whole page — the title with its line of data, sections or a view switch, a period that drives several blocks, the actions (`Page.Actions`). One control never gets a row of its own.
307
309
  - The tools row is the one tool row of the kit (`Page.Tools` lays out the same way): controls in the order written, a search or a line of text (`Typography`) takes the free room, switchers, pickers and buttons keep their content width at the end edge. Under 36rem of table width the search takes a line of its own and a `SegmentedControl` (`fullWidth` unless set) fills the line it shares with the buttons; a search never parts from its filter button (`SmartFilter.Toolbar` keeps both). Wrap the table in `SmartFilter.Root` so the toolbar and the chips share one state.
310
+ - 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.
311
+ - 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.
308
312
  - 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.
309
313
  - 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.
310
314
  - 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.
311
315
  - 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.
312
316
  - 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.
313
317
  - Fewer columns, less scrolling: offer the column chooser (the key column and the row-actions column `hideable: false`). The kit stores nothing: keep `onHiddenColumnsChange`'s list where the app keeps settings and pass it back as `hiddenColumns`. Never hide columns silently by width.
314
- - The column chooser has one place per table. Over the row actions (`columnsMenu`): an icon-only ghost neutral Button one tier below the table (the `view.columns` icon; `labels.columns` is its name and its Tooltip) in the head cell of the row-actions column (the last visible column, `hideHeader`), lined up with the ⋯ buttons under it, sticking with that column under `stickyLastColumn`. Without row actions: `DataTableColumnsMenu` in the page's tools (`Page.Tools`, or the host's own toolbar) — a soft neutral Button with the icon, `labels.columns` and a Badge with the count of shown columns, at the host tier — bound to the same `hiddenColumns` (and `columnOrder`) state the table reads. Both open the same Dropdown: a `labels.showColumns` group heading over one check item per column (the check at the end).
318
+ - The column chooser has one place per table. Over the row actions (`columnsMenu`): an icon-only ghost neutral Button one tier below the table (the `view.columns` icon; `labels.columns` is its name and its Tooltip) in the head cell of the row-actions column (the last visible column, `hideHeader`), lined up with the ⋯ buttons under it, sticking with that column under `stickyLastColumn`. Without row actions: `DataTableColumnsMenu` in the table's `toolbar` — a soft neutral Button with the icon, `labels.columns` and a Badge with the count of shown columns, at the host tier — bound to the same `hiddenColumns` (and `columnOrder`) state the table reads. Both open the same Dropdown: a `labels.showColumns` group heading over one check item per column (the check at the end).
315
319
  - Reorderable rows: the grip is the fixed `xs` `Dnd.Handle`. At the end it takes a trailing cell (the grip plus the end padding), after the row actions; at the start a leading cell before the checkboxes (the first column's padding is the gap to it). Rows that are their own grip (`row`) add no cell.
316
320
  - Reorderable columns move only between the columns that hold their place: the selection, toggle, grip and trailing cells never move, a pinned first or last column stays at its edge, a `hideHeader` (row actions) or `reorderable: false` column keeps its slot. A hidden column keeps its slot in `columnOrder` too, so showing it again puts it back where it was. Keep `columnOrder` with `hiddenColumns` where the app keeps settings. The chooser's Dropdown opens below (lined up with the trigger's end edge over the row actions), lists every column in table order and ends with `labels.showAllColumns` (and `labels.resetColumnOrder` with `columnOrder`); a `hideable: false` column stays checked and disabled, and the last visible column cannot be hidden. With `columnOrder` the items are a `Dnd.Sortable` list, a grip (or a lock) ending each item after its check mark.
317
321
  - Row height per `size` is public: `--prime-table-row-height-xs` · `-s` · `-m` · `-l` · `-xl` (36 · 36 · 44 · 52 · 52). A placeholder outside the table that stands in its geometry (a heatmap, a matrix skeleton) uses the same token.
@@ -371,6 +375,8 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
371
375
  | `selectAll` | `"Выбрать все строки"` | Name of the head «select all» checkbox. |
372
376
  | `selectRow` | `"Выбрать строку {label}"` | Name of a row checkbox; `{label}` is `getRowLabel(row)`, empty without it. |
373
377
  | `selectedCount` | `"Выбрано: {count}"` | Polite announcement after the selection changes. |
378
+ | `selectionActions` | `"Действия"` | Text of the menu button of the actions on the selected rows; its name adds the count. |
379
+ | `clearSelection` | `"Снять выбор"` | The first item of the selection actions menu: clears the selection. |
374
380
  | `expand` | `"Развернуть строку {label}"` | Name of the toggle of a collapsed row. |
375
381
  | `collapse` | `"Свернуть строку {label}"` | Name of the toggle of an expanded row. |
376
382
  | `columns` | `"Колонки"` | Name and tooltip of the icon trigger (`columnsMenu`), the text of `DataTableColumnsMenu`; `{shown}` and `{total}` are column counts. |
@@ -395,7 +401,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
395
401
  | [content-width.tsx](examples/content-width.tsx) | A short lookup table sized by its content with a centered column, and a `grow` column that takes the free width and wraps — `fullWidth`, `align`, `grow`. |
396
402
  | [appearance.tsx](examples/appearance.tsx) | Row styling: zebra rows without lines, a column wash under the pointer, a headless key/value list — `striped`, `rowDividers`, `highlightColumnOnHover`, `columnDividers`, `showHeader`. |
397
403
  | [toolbar.tsx](examples/toolbar.tsx) | A campaign table with its own tools row: the search takes the free room, the status switch keeps its width at the end, and on a narrow table they wrap; the empty text when nothing matches — `toolbar`, `empty`. |
398
- | [selection.tsx](examples/selection.tsx) | Team members with checkboxes: Shift+click range, press-and-drag, «select all» and bulk actions in the toolbar — `selectable`, `selected`, `onSelectedChange`, `getRowLabel`. |
404
+ | [selection.tsx](examples/selection.tsx) | Team members with checkboxes: Shift+click range, press-and-drag, «select all», and an actions menu after the search and the column chooser while rows are selected — `selectable`, `selected`, `onSelectedChange`, `getRowLabel`, `renderSelectionActions`. |
399
405
  | [nested-rows.tsx](examples/nested-rows.tsx) | Partners with their expense lines: sub-rows indented under the parent's name, a chevron toggle, together with selection and sorting — `getRowChildren`, `expanded`, `onExpandedChange`. |
400
406
  | [detail-panel.tsx](examples/detail-panel.tsx) | Order details in a full-width row under an expanded order, aligned with the first content column; a cancelled order has nothing to show and gets no toggle — `renderExpanded`, `defaultExpanded`, `isRowExpandable`. |
401
407
  | [sticky.tsx](examples/sticky.tsx) | Monthly sales by region in a 280 px window: the head and the region column stay while scrolling both ways — `stickyHeader`, `stickyFirstColumn`, `scrollHeight`. |
@@ -412,7 +418,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
412
418
  | [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`. |
413
419
  | [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`. |
414
420
  | [narrow.tsx](examples/narrow.tsx) | A 320 px support queue: columns scroll inside the table with the first one pinned, the column chooser stays in reach above it, the footer reflows and the pager turns compact — `stickyFirstColumn`, `DataTableColumnsMenu`, `size`. |
415
- | [screen.tsx](examples/screen.tsx) | A list page: the title, a line of figures, search, filters and the one primary action in its top bar, a DataTable of invoices with statuses, row actions, pages and a filtered empty state — `empty`, `pageSize`, `stickyFirstColumn`. |
421
+ | [screen.tsx](examples/screen.tsx) | A list page: the title, a line of figures and the one primary action in its top bar, search and filters in the table's own tools row, a DataTable of invoices with statuses, row actions, pages and a filtered empty state — `empty`, `pageSize`, `stickyFirstColumn`. |
416
422
 
417
423
  ## Mistakes
418
424
  - No `getRowKey` with `selectable`, expansion or sorting → selection follows positions, not records.
@@ -433,9 +439,9 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
433
439
  - `paging="none"` for hundreds of rows → use `pages` or `infinite`.
434
440
  - Dropping columns below a breakpoint → people lose data without knowing it; offer the column chooser.
435
441
  - A hand-built column chooser from Dropdown items → `columnsMenu` over the row actions, else `DataTableColumnsMenu` (key column, last visible column, «Показать все», reorder built in).
436
- - `columnsMenu` on a table without a row-actions column → nothing renders and the table warns; put `DataTableColumnsMenu` beside the table's other tools (its `toolbar`, or `Page.Tools` on a page that is the table), bound to the same `hiddenColumns`.
442
+ - `columnsMenu` on a table without a row-actions column → nothing renders and the table warns; put `DataTableColumnsMenu` beside the table's other tools in its `toolbar`, bound to the same `hiddenColumns`.
437
443
  - A toolbar with one button (the column chooser, «Экспорт», «Создать») → a whole row above the table for one control; the chooser stands over the row actions or beside the table's search, the page's actions go to `Page.Actions`.
438
- - The table's search, filter, chips or column chooser in the page's bar above KPI cards or a chart they do not filter → people read them as filters of the whole page; put them in the table's `toolbar` / `toolbarChips` and keep the bar for the title, the period that drives every block and the primary action.
444
+ - The table's search, filter, chips or column chooser in the page's bar (`Page.Tools`, `Page.Chips`) → they read as filters of the whole page and part from the table they filter; put them in the table's `toolbar` / `toolbarChips`, also when the table is the whole page, and keep the bar for the title, sections or a view, the period that drives every block and the actions.
439
445
  - A period that drives the cards and the table, the page's sections, figures or actions in `toolbar` → they act on the whole page: `Page.Tools`, `Page.Actions`, `Page.Description`.
440
446
  - A hand-made flex row or a `SmartFilter.Root` with its own column inside `toolbar` → pass the controls to `toolbar` as they are (it lays them out), the chips to `toolbarChips`, and wrap the DataTable in `SmartFilter.Root`.
441
447
  - Letting the key column be hidden → set `hideable: false` on it, so every row keeps its name.
@@ -454,5 +460,5 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
454
460
  - A new search that keeps the current page → set `page` to 1 together with the search.
455
461
 
456
462
  ## Related
457
- - **Built from:** [Checkbox](../checkbox/COMPONENT.md), [Button](../button/COMPONENT.md), [Dropdown](../dropdown/COMPONENT.md) (the column chooser), [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`), [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 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`)
463
+ - **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)
458
464
  - **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 list page: the title, a line of figures, search, filters and the one primary action in its top bar, a DataTable of invoices with statuses, row actions, pages and a filtered empty state — `empty`, `pageSize`, `stickyFirstColumn`. */
1
+ /** A list page: the title, a line of figures and the one primary action in its top bar, search and filters in the table's own tools row, a DataTable of invoices with statuses, row actions, pages and a filtered empty state — `empty`, `pageSize`, `stickyFirstColumn`. */
2
2
  import {
3
3
  Avatar,
4
4
  Badge,
@@ -293,16 +293,14 @@ export default function DataTableScreenExample() {
293
293
  onSearchChange={setSearch}
294
294
  >
295
295
  <Page.Root>
296
- {/* A work screen titles itself in its top bar: the only h1, a line of figures under it, the
297
- page's search and filters, then export and the one primary action. */}
296
+ {/* A work screen titles itself in its top bar: the only h1, a line of figures under it,
297
+ export and the one primary action. The table's search and filters stand in its own
298
+ tools row, next to what they filter. */}
298
299
  <Page.TopBar>
299
300
  <Page.Heading>
300
301
  <Page.Title>Счета</Page.Title>
301
302
  <Page.Description>{SUMMARY}</Page.Description>
302
303
  </Page.Heading>
303
- <Page.Tools aria-label="Счета">
304
- <SmartFilter.Toolbar />
305
- </Page.Tools>
306
304
  <Page.Actions>
307
305
  <Button.Root variant="soft" tone="neutral">
308
306
  <Button.Icon>
@@ -317,9 +315,6 @@ export default function DataTableScreenExample() {
317
315
  Новый счёт
318
316
  </Button.Root>
319
317
  </Page.Actions>
320
- <Page.Chips>
321
- <SmartFilter.Chips />
322
- </Page.Chips>
323
318
  </Page.TopBar>
324
319
  <Page.Body>
325
320
  <DataTable
@@ -331,6 +326,8 @@ export default function DataTableScreenExample() {
331
326
  stickyFirstColumn
332
327
  stickyLastColumn
333
328
  columnsMenu
329
+ toolbar={<SmartFilter.Toolbar />}
330
+ toolbarChips={<SmartFilter.Chips />}
334
331
  defaultSort={{ columnId: "due", order: "desc" }}
335
332
  empty={
336
333
  <EmptyPage.Root size="s" role="status" aria-labelledby="invoices-empty">
@@ -1,5 +1,12 @@
1
- /** Team members with checkboxes: Shift+click range, press-and-drag, «select all» and bulk actions in the toolbar — `selectable`, `selected`, `onSelectedChange`, `getRowLabel`. */
2
- import { Button, DataTable, type DataTableColumn, Typography } from "prime-ui-kit";
1
+ /** Team members with checkboxes: Shift+click range, press-and-drag, «select all», and an actions menu after the search and the column chooser while rows are selected — `selectable`, `selected`, `onSelectedChange`, `getRowLabel`, `renderSelectionActions`. */
2
+ import {
3
+ DataTable,
4
+ type DataTableColumn,
5
+ DataTableColumnsMenu,
6
+ Dropdown,
7
+ Icon,
8
+ Input,
9
+ } from "prime-ui-kit";
3
10
  import * as React from "react";
4
11
 
5
12
  type Member = { id: string; name: string; email: string; role: string };
@@ -14,18 +21,32 @@ const MEMBERS: Member[] = [
14
21
  ];
15
22
 
16
23
  const COLUMNS: DataTableColumn<Member>[] = [
17
- { id: "name", header: "Имя", accessor: "name", sortable: true },
24
+ { id: "name", header: "Имя", accessor: "name", sortable: true, hideable: false },
18
25
  { id: "email", header: "Почта", accessor: "email", truncate: true, maxWidth: "14rem" },
19
26
  { id: "role", header: "Роль", accessor: "role", sortable: true },
20
27
  ];
21
28
 
22
29
  export default function DataTableSelectionExample() {
30
+ const [members, setMembers] = React.useState(MEMBERS);
23
31
  const [selected, setSelected] = React.useState<React.Key[]>(["u2"]);
32
+ const [query, setQuery] = React.useState("");
33
+ const [hidden, setHidden] = React.useState<string[]>([]);
34
+
35
+ const needle = query.trim().toLowerCase();
36
+ const shown = needle
37
+ ? members.filter((member) => member.name.toLowerCase().includes(needle))
38
+ : members;
39
+
40
+ const removeSelected = (keys: React.Key[]) => {
41
+ setMembers((current) => current.filter((member) => !keys.includes(member.id)));
42
+ setSelected([]);
43
+ };
24
44
 
25
45
  return (
26
46
  <DataTable
27
47
  columns={COLUMNS}
28
- rows={MEMBERS}
48
+ hiddenColumns={hidden}
49
+ rows={shown}
29
50
  getRowKey={(row) => row.id}
30
51
  getRowLabel={(row) => row.name}
31
52
  selectable
@@ -34,22 +55,43 @@ export default function DataTableSelectionExample() {
34
55
  paging="none"
35
56
  toolbar={
36
57
  <>
37
- <Typography as="span" variant="body-s" tone="secondary">
38
- Выбрано: {selected.length} из {MEMBERS.length}
39
- </Typography>
40
- <Button.Root
41
- variant="outline"
42
- tone="neutral"
43
- disabled={selected.length === 0}
44
- onClick={() => setSelected([])}
45
- >
46
- Снять выбор
47
- </Button.Root>
48
- <Button.Root variant="soft" tone="danger" disabled={selected.length === 0}>
49
- Удалить
50
- </Button.Root>
58
+ <Input.Root>
59
+ <Input.Wrapper>
60
+ <Input.Icon side="start">
61
+ <Icon name="action.search" />
62
+ </Input.Icon>
63
+ <Input.Field
64
+ type="search"
65
+ placeholder="Поиск по имени"
66
+ aria-label="Поиск по участникам"
67
+ value={query}
68
+ onValueChange={setQuery}
69
+ />
70
+ </Input.Wrapper>
71
+ </Input.Root>
72
+ <DataTableColumnsMenu
73
+ columns={COLUMNS}
74
+ hiddenColumns={hidden}
75
+ onHiddenColumnsChange={setHidden}
76
+ />
51
77
  </>
52
78
  }
79
+ renderSelectionActions={(keys) => (
80
+ <>
81
+ <Dropdown.Item>
82
+ <Dropdown.ItemIcon>
83
+ <Icon name="action.download" />
84
+ </Dropdown.ItemIcon>
85
+ Выгрузить в CSV
86
+ </Dropdown.Item>
87
+ <Dropdown.Item tone="danger" onSelect={() => removeSelected(keys)}>
88
+ <Dropdown.ItemIcon>
89
+ <Icon name="action.delete" />
90
+ </Dropdown.ItemIcon>
91
+ Удалить из команды
92
+ </Dropdown.Item>
93
+ </>
94
+ )}
53
95
  />
54
96
  );
55
97
  }
@@ -8,7 +8,7 @@
8
8
  ## When to use
9
9
  - Editing settings or a record without leaving the page.
10
10
  - Filters or navigation on narrow screens (`side="left"`).
11
- - Read-only details of a list row (order, user, event), with its status and a ⋯ menu in the header (`Drawer.Actions`).
11
+ - Read-only details of a list row (order, user, event), with its status under the title (`Drawer.Description`) and a ⋯ menu in the header (`Drawer.Actions`).
12
12
  - Long content (history, logs) that needs a tall scrolling area.
13
13
 
14
14
  ## When not to use
@@ -30,13 +30,14 @@ Drawer.Root state and dismiss policy (no DOM)
30
30
  └─ Drawer.Content portal + scrim + role="dialog" panel at the side
31
31
  ├─ Drawer.Guard no DOM: holds the drawer (busy / dirty) from inside
32
32
  ├─ Drawer.Header grid: [Icon] [Title + Description] [Actions] [close Button]
33
- │ ├─ Drawer.Icon tone tile around an Icon, or an Avatar.Root / Thumbnail.Root with no tile
34
- │ ├─ Drawer.Title <h2>
35
- │ ├─ Drawer.Description <p>
36
- │ └─ Drawer.Actions row of tier-s controls before the close button
33
+ │ ├─ Drawer.Icon tone tile around an Icon, or the record's Avatar / Thumbnail, no tile, 40
34
+ │ ├─ Drawer.Title line 1: <h2>; `loading` holds it with a skeleton
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
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
40
+ ├─ Drawer.Actions the start group: an action apart from the main pair
40
41
  ├─ Drawer.Meta <p>, muted status at the start
41
42
  └─ Drawer.Close clones its child; closes on click
42
43
  ```
@@ -81,19 +82,19 @@ No DOM, no ref. State and dismiss policy.
81
82
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `className` and the other attributes of the `<header>`. |
82
83
 
83
84
  ### Drawer.Actions
84
- `ref` → `HTMLDivElement`. A row of controls at the end of the header, before the close button: a status Badge, a ⋯ Dropdown; under the title when it would get less than 12rem; or at the end of a `Drawer.Section` heading. Controls inside take tier `s`.
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
86
 
86
87
  | Prop | Type | Default | Description |
87
88
  |---|---|---|---|
88
89
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `className`, `children` and the other attributes of the row. |
89
90
 
90
91
  ### Drawer.Icon
91
- `ref` → `HTMLSpanElement`. An `aria-hidden` 40 px tile with a tone fill; around an `Avatar.Root` or a `Thumbnail.Root` (the record's picture) no tile — `data-media`, the avatar sized to 40 px.
92
+ `ref` → `HTMLSpanElement`. An `aria-hidden` 40 px tile with a tone fill; around the record's picture (an `Avatar.Root` or a `Thumbnail.Root` anywhere inside, also from your own component) no tile (`data-media`), the picture at 40 px.
92
93
 
93
94
  | Prop | Type | Default | Description |
94
95
  |---|---|---|---|
95
96
  | `tone` | `"neutral" \| "accent" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Soft fill and icon color; not used around a picture. |
96
- | `children` | `ReactNode` | — (required) | An `Icon` (sized to the m icon), or the record's picture: an `Avatar.Root` (sized to the tile) or a `Thumbnail.Root`. |
97
+ | `children` | `ReactNode` | — (required) | An `Icon` (sized to the m icon), or the record's picture: an `Avatar.Root` (size `l`) or a `Thumbnail.Root` (size `m`), set by the slot. |
97
98
  | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other attributes of the tile. |
98
99
 
99
100
  ### Drawer.Title
@@ -101,10 +102,11 @@ No DOM, no ref. State and dismiss policy.
101
102
 
102
103
  | Prop | Type | Default | Description |
103
104
  |---|---|---|---|
105
+ | `loading` | `boolean` | `false` | The name loads (a record opened by a link): a `Skeleton` holds the title line and `aria-busy` is set, so the header never moves; the name fades in. |
104
106
  | `…rest` | `Omit<HTMLAttributes<HTMLHeadingElement>, "id">` | — | `className`, `children` and the other attributes except `id` (the dialog owns it). |
105
107
 
106
108
  ### Drawer.Description
107
- `ref` → `HTMLParagraphElement`. `<p>` (body-s, muted) under the title; its id describes the dialog.
109
+ `ref` → `HTMLParagraphElement`. `<p>` (body-s, muted) under the title; its id describes the dialog. The record's status `Badge.Root`s go here: first in the line, chip tier `s` (20 px), the text wraps under them when narrow.
108
110
 
109
111
  | Prop | Type | Default | Description |
110
112
  |---|---|---|---|
@@ -128,7 +130,7 @@ No DOM, no ref. State and dismiss policy.
128
130
  | `…rest` | `Omit<HTMLAttributes<HTMLElement>, "title">` | — | `children` (the content and an optional `Drawer.Actions`), `className` and the other attributes of the `<section>`. |
129
131
 
130
132
  ### Drawer.Footer
131
- `ref` → `HTMLElement`. Renders `<footer>` with the actions, primary last, and an optional `Meta` at the start.
133
+ `ref` → `HTMLElement`. Renders `<footer>` with the actions, primary last; at the start an optional `Drawer.Actions` (an action apart from the main pair) and `Meta`. Stacked, the start comes first and the primary stays last.
132
134
 
133
135
  | Prop | Type | Default | Description |
134
136
  |---|---|---|---|
@@ -182,7 +184,7 @@ Below 640px of viewport every size is full width and loses its corners.
182
184
  | `fill` | equal-width buttons in one row | one full-width action («Понятно») | |
183
185
 
184
186
  ### tone (Icon)
185
- `neutral` (default) · `accent` · `success` · `warning` · `danger` · `info` — soft tile fill and icon color, as in Modal. Around the record's picture — an `Avatar.Root` or a `Thumbnail.Root` (found by element type) — there is no tile and no tone: `data-media`, the avatar sized to 40 px, so a person's or an object's card shows its picture in the header.
187
+ `neutral` (default) · `accent` · `success` · `warning` · `danger` · `info` — soft tile fill and icon color, as in Modal. Around the record's picture — an `Avatar.Root` or a `Thumbnail.Root` anywhere inside, also from your own wrapper component (`<ClientAvatar />`) — there is no tile and no tone: `data-media`, and the picture takes the tile's 40 px (avatar `l`, thumbnail `m`) whatever `size` it was given, so every record header has the same mark.
186
188
 
187
189
  ## States
188
190
  | State | Driven by | DOM |
@@ -195,16 +197,29 @@ Below 640px of viewport every size is full width and loses its corners.
195
197
  | busy | `Drawer.Guard busy` | Escape, the scrim, the close button and `Drawer.Close` do nothing; the close button and the `Close` child are `disabled` in place |
196
198
  | dirty | `Drawer.Guard dirty` | a dismiss opens the «Не сохранять изменения?» ConfirmDialog (`labels.discard*`); its confirm closes the drawer |
197
199
  | leaving the tab | `Drawer.Guard busy` or `dirty` | the browser asks before the tab closes or reloads (`beforeunload`), as with `Page.Guard` |
200
+ | title loading | `Drawer.Title loading` | a `Skeleton` holds the title line, `aria-busy` on the `<h2>`; the name fades in under the same element |
198
201
  | description loading | `Drawer.Description loading` | a `Skeleton` holds the line, `aria-busy` on the `<p>`; the data fades in under the same element |
199
202
  | actions below | the title would get less than 12rem beside `Drawer.Actions` | `data-actions-below` on the header: the actions move under the title and the description |
203
+ | picture mark | an `Avatar.Root` or a `Thumbnail.Root` inside `Drawer.Icon` | `data-media` on the icon, no tile; the picture's `data-size` is the slot's (`l` / `m`) |
204
+ | status in the line | a `Badge.Root` inside `Drawer.Description` | the line is a wrapping flex row, the badges first (`order`), each `data-tier="s"` |
200
205
 
201
206
  Motion: the scrim fades, the panel slides from its side over `slow` and leaves over `base` (`overlayMotion`); under `prefers-reduced-motion` it unmounts at once. The drawer has no touch gestures: it is not dragged or swiped away — it closes by its buttons, Escape and the scrim, with the same slide-out.
202
207
 
203
208
  ## Layout & spacing
209
+ - The header has one anatomy, the same in every drawer, modal and sheet:
210
+ ```
211
+ [mark] Title [icon buttons] [⋯] [×]
212
+ [status badges] description text
213
+ ```
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
+ - line 1 — `Drawer.Title`, the record's name.
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.
204
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:
205
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;
206
220
  - a form: «Отмена» (`Drawer.Close`, outline neutral) and the submit button (solid, last; `type="submit" form={formId}`), `Drawer.Meta` for «Есть несохранённые изменения»;
207
- - one acknowledgement: `<Drawer.Footer layout="fill">`.
221
+ - one acknowledgement: `<Drawer.Footer layout="fill">`;
222
+ - an action apart from the main pair («Выйти», «Удалить» — the latter through a ConfirmDialog): `Drawer.Actions` inside the footer puts it at the start, at the footer's tier; stacked on a phone it comes first and the primary stays last.
208
223
  Both the header × and the footer button stay: the × is the quick way out at the top, the footer is the end of the reading or editing path, where the hand already is.
209
224
  - Zone padding `--prime-drawer-padding`; hairlines between header, body and footer.
210
225
  - Full viewport height; only the body scrolls, with its own side padding.
@@ -212,8 +227,9 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
212
227
  - Fields inside get the surface field fill.
213
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`.
214
229
  - With a `Drawer.Meta` the buttons stay one row: when the footer runs short the status goes above them first.
215
- - `Drawer.Actions` keep their place beside the title while it has 12rem; on a phone, where a status and a ⋯ beside the × would fold the title into letters, they move under it by themselves.
216
- - A record whose data is still loading: the title from the list row, `Drawer.Description loading` and a `Skeleton` body in the data's geometry under a `Crossfade` — not a skeleton of your own in the header.
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
+ - 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.
232
+ - Focus on open lands on the panel itself, so a header action (a ⋯ with a Tooltip) is not focused and its tooltip does not pop up; a form that should start typing at once puts `autoFocus` on its first field.
217
233
 
218
234
  ## Accessibility
219
235
 
@@ -229,7 +245,7 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
229
245
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
230
246
 
231
247
  - `role="dialog"`, `aria-modal="true"`, named by `Drawer.Title` (or `aria-label`), described by `Drawer.Description`.
232
- - Focus moves into the panel on open and returns to the opener on close, including after a scrim click.
248
+ - On open focus moves to the panel itself (its title and description are read; an `autoFocus` field wins), without the ring after a click; Tab goes on to the first control. On close it returns to the opener, including after a scrim click.
233
249
  - The page behind is `inert`, scroll is locked; only the topmost layer reacts.
234
250
  - A drawer opened from a Modal stacks above it.
235
251
  - No swipe: the close button, `Drawer.Close`, Escape and the scrim are the ways out.
@@ -253,9 +269,11 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
253
269
  | [structure.tsx](examples/structure.tsx) | Optional parts: an icon tile in the header and one full-width action in the footer — `Drawer.Icon`, `Drawer.Footer`, `layout`. |
254
270
  | [sizes.tsx](examples/sizes.tsx) | Every panel width, 360 to 800 px; below 640 px of viewport the panel takes the full width — `size`. |
255
271
  | [placement.tsx](examples/placement.tsx) | The panel slides from the right for details and from the left for filters, rounded only on the edge facing the page — `side`. |
256
- | [header-actions.tsx](examples/header-actions.tsx) | A record card: its status and a ⋯ menu sit in the header before the close button, one tier smaller; on a phone, where the title has no room beside them, they move under it — `Drawer.Actions`. |
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
+ | [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`. |
257
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`. |
258
275
  | [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
+ | [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`. |
259
277
  | [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`. |
260
278
  | [long-content.tsx](examples/long-content.tsx) | A long body scrolls on its own while the header and the footer stay in place — `Drawer.Body`. |
261
279
  | [dismiss.tsx](examples/dismiss.tsx) | An import closes only from its buttons, and not at all while it runs — `closeOnOutsideClick`, `closeOnEscape`. |
@@ -269,15 +287,19 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
269
287
  - A delete confirmation in a Drawer → use ConfirmDialog.
270
288
  - A hand-made heading row (`Typography as="h3"` + buttons) over a group in the body → `Drawer.Section label` with `Drawer.Actions`.
271
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.
272
- - A `Skeleton` placed by hand in `Drawer.Description` while the record loads → `Drawer.Description loading`.
290
+ - 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
+ - A start-side button («Выйти») put inside `Drawer.Meta` → `Drawer.Actions` inside `Drawer.Footer`; `Meta` is status text only.
273
292
  - Busy or unsaved state lifted out of the form into Root props → put `Drawer.Guard` next to the form, inside Content.
274
293
  - 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.
275
- - A Badge or a menu as a custom child of the header text column → put them in `Drawer.Actions`.
294
+ - 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.
295
+ - A menu or a button as a custom child of the header text column → put it in `Drawer.Actions`.
296
+ - A Badge with its own `size` in `Drawer.Description` → leave `size` out: the line gives every header the same chip tier.
297
+ - 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.
276
298
  - Expecting Enter to submit via `Drawer.Confirm` → Drawer has no Confirm; use a `<form>` with a submit button.
277
299
  - `side="top"` or `side="bottom"` → only `left` and `right`; a panel from the bottom edge is a [Sheet](../sheet/COMPONENT.md).
278
300
  - Expecting a swipe to close a drawer → it has none; keep the header × and a footer button. A panel that swipes away is a [Sheet](../sheet/COMPONENT.md).
279
301
  - Padding on a wrapper inside Body → Body already pads and scrolls.
280
302
 
281
303
  ## Related
282
- - **Built from:** the dialog shared with Modal and Sheet (`internal/overlay/DialogParts`), [Button](../button/COMPONENT.md) (close button), [ScrollContainer](../scroll-container/COMPONENT.md) (`Drawer.Body`), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (a record's picture in `Drawer.Icon`), [Skeleton](../skeleton/COMPONENT.md) (`Drawer.Description loading`), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (the `Drawer.Guard` discard confirm), `Icon` (the close glyph)
304
+ - **Built from:** the dialog shared with Modal and Sheet (`internal/overlay/DialogParts`), [Button](../button/COMPONENT.md) (close button), [ScrollContainer](../scroll-container/COMPONENT.md) (`Drawer.Body`), `internal/mediaSlot` (a record's Avatar or Thumbnail in `Drawer.Icon`), [Skeleton](../skeleton/COMPONENT.md) (`Drawer.Title loading`, `Drawer.Description loading`), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (the `Drawer.Guard` discard confirm), `Icon` (the close glyph)
283
305
  - **See also:** [Modal](../modal/COMPONENT.md) (the same header, body, footer and `Guard` in the centre), [Sheet](../sheet/COMPONENT.md) (the same parts raised from the bottom edge for a short task), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (a destructive confirm), [Sidebar](../../layout/sidebar/COMPONENT.md), [Popover](../popover/COMPONENT.md), [Avatar](../avatar/COMPONENT.md) (a person's picture in `Drawer.Icon`)
@@ -0,0 +1,54 @@
1
+ /** 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`. */
2
+ import { Avatar, Button, DescriptionList, Drawer, Icon } from "prime-ui-kit";
3
+
4
+ const PROFILE = {
5
+ name: "Игорь Лебедев",
6
+ role: "Менеджер по продажам",
7
+ email: "i.lebedev@sev-veter.ru",
8
+ team: "Продажи · Москва",
9
+ };
10
+
11
+ export default function DrawerFooterActionsExample() {
12
+ return (
13
+ <Drawer.Root>
14
+ <Drawer.Trigger>
15
+ <Button.Root variant="soft" tone="neutral">
16
+ Открыть профиль
17
+ </Button.Root>
18
+ </Drawer.Trigger>
19
+ <Drawer.Content size="s">
20
+ <Drawer.Header>
21
+ <Drawer.Icon>
22
+ <Avatar.Root color="green">
23
+ <Avatar.Fallback>ИЛ</Avatar.Fallback>
24
+ </Avatar.Root>
25
+ </Drawer.Icon>
26
+ <Drawer.Title>{PROFILE.name}</Drawer.Title>
27
+ <Drawer.Description>{PROFILE.role}</Drawer.Description>
28
+ </Drawer.Header>
29
+ <Drawer.Body>
30
+ <DescriptionList.Root>
31
+ <DescriptionList.Item label="Почта">{PROFILE.email}</DescriptionList.Item>
32
+ <DescriptionList.Item label="Команда">{PROFILE.team}</DescriptionList.Item>
33
+ </DescriptionList.Root>
34
+ </Drawer.Body>
35
+ <Drawer.Footer>
36
+ <Drawer.Actions>
37
+ <Button.Root variant="ghost" tone="danger">
38
+ <Button.Icon>
39
+ <Icon name="action.logout" />
40
+ </Button.Icon>
41
+ Выйти
42
+ </Button.Root>
43
+ </Drawer.Actions>
44
+ <Drawer.Close>
45
+ <Button.Root variant="outline" tone="neutral">
46
+ Закрыть
47
+ </Button.Root>
48
+ </Drawer.Close>
49
+ <Button.Root>Изменить</Button.Root>
50
+ </Drawer.Footer>
51
+ </Drawer.Content>
52
+ </Drawer.Root>
53
+ );
54
+ }