prime-ui-kit 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunks/{chunk-4KONMO7Z.js → chunk-6L7VWEY6.js} +1 -1
- package/dist/chunks/{chunk-3PY52JP3.js → chunk-S27VR4ON.js} +5828 -5596
- package/dist/chunks/{chunk-W6EGTNG6.js → chunk-S4H2ZKWJ.js} +1099 -661
- package/dist/color-picker.js +2 -2
- package/dist/components/badge/Badge.d.ts +19 -2
- package/dist/components/card/Card.d.ts +10 -3
- package/dist/components/composer/Composer.d.ts +49 -1
- package/dist/components/crossfade/Crossfade.d.ts +6 -4
- package/dist/components/data-table/DataTable.d.ts +10 -2
- package/dist/components/data-table/Row.d.ts +12 -1
- package/dist/components/data-table/SelectionMenu.d.ts +20 -0
- package/dist/components/data-table/useRowSelection.d.ts +2 -0
- package/dist/components/empty-page/EmptyPage.d.ts +5 -2
- package/dist/components/hint/Hint.d.ts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/input/Input.d.ts +15 -3
- package/dist/components/message/Message.d.ts +6 -3
- package/dist/components/segmented-control/SegmentedControl.d.ts +5 -1
- package/dist/components/smart-filter/SmartFilter.d.ts +4 -0
- package/dist/components/spinner/Spinner.d.ts +3 -2
- package/dist/components/tabs/Tabs.d.ts +10 -1
- package/dist/hooks/useModalLayer.d.ts +15 -8
- package/dist/index.css +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/RegionSwap.d.ts +18 -10
- package/dist/internal/RollingNumber.d.ts +7 -5
- package/dist/internal/ToolRow.d.ts +6 -0
- package/dist/internal/mediaSlot.d.ts +29 -0
- package/dist/internal/overlay/DialogParts.d.ts +30 -6
- package/dist/internal/overlay/focus.d.ts +18 -3
- package/dist/internal/partPresence.d.ts +19 -2
- package/dist/layout/sidebar/Sidebar.d.ts +3 -2
- package/dist/skill/anti-slop.md +35 -37
- package/dist/skill/api-contract.md +50 -2
- package/dist/skill/cheatsheet.md +15 -10
- package/dist/skill/checklist.md +19 -6
- package/dist/skill/choosing.md +79 -6
- package/dist/skill/components.md +1 -1
- package/dist/skill/composition.md +211 -51
- package/dist/skill/foundations.md +17 -6
- package/dist/skill/layouts.md +64 -14
- package/dist/skill/motion.md +9 -3
- package/dist/skill/reference/components/avatar/COMPONENT.md +1 -0
- package/dist/skill/reference/components/badge/COMPONENT.md +29 -5
- package/dist/skill/reference/components/badge/examples/examples.module.css +14 -0
- package/dist/skill/reference/components/badge/examples/overflow-count.tsx +32 -0
- package/dist/skill/reference/components/banner/COMPONENT.md +1 -1
- package/dist/skill/reference/components/card/COMPONENT.md +8 -4
- package/dist/skill/reference/components/card/examples/person-header.tsx +38 -0
- package/dist/skill/reference/components/composer/COMPONENT.md +35 -5
- package/dist/skill/reference/components/composer/examples/bot-keyboard.tsx +43 -0
- package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
- package/dist/skill/reference/components/crossfade/COMPONENT.md +18 -10
- package/dist/skill/reference/components/data-table/COMPONENT.md +17 -11
- package/dist/skill/reference/components/data-table/examples/screen.tsx +6 -9
- package/dist/skill/reference/components/data-table/examples/selection.tsx +60 -18
- package/dist/skill/reference/components/dnd/COMPONENT.md +3 -3
- package/dist/skill/reference/components/drawer/COMPONENT.md +41 -19
- package/dist/skill/reference/components/drawer/examples/footer-actions.tsx +54 -0
- package/dist/skill/reference/components/drawer/examples/header-actions.tsx +46 -25
- package/dist/skill/reference/components/drawer/examples/link-loading.tsx +57 -0
- package/dist/skill/reference/components/drawer/examples/screen.tsx +3 -5
- package/dist/skill/reference/components/empty-page/COMPONENT.md +2 -2
- package/dist/skill/reference/components/field-list/COMPONENT.md +7 -7
- package/dist/skill/reference/components/field-list/examples/row-menu.tsx +1 -1
- package/dist/skill/reference/components/hint/COMPONENT.md +1 -1
- package/dist/skill/reference/components/input/COMPONENT.md +16 -3
- package/dist/skill/reference/components/input/examples/password-policy.tsx +65 -0
- package/dist/skill/reference/components/list/COMPONENT.md +1 -1
- package/dist/skill/reference/components/login-form/COMPONENT.md +13 -11
- package/dist/skill/reference/components/login-form/examples/overview.tsx +5 -5
- package/dist/skill/reference/components/login-form/examples/states.tsx +7 -6
- package/dist/skill/reference/components/message/COMPONENT.md +5 -5
- package/dist/skill/reference/components/message/examples/bot-actions.tsx +47 -28
- package/dist/skill/reference/components/modal/COMPONENT.md +31 -12
- package/dist/skill/reference/components/modal/examples/structure.tsx +2 -4
- package/dist/skill/reference/components/segmented-control/COMPONENT.md +11 -6
- package/dist/skill/reference/components/segmented-control/examples/router.tsx +22 -0
- package/dist/skill/reference/components/select/COMPONENT.md +4 -1
- package/dist/skill/reference/components/sheet/COMPONENT.md +31 -12
- package/dist/skill/reference/components/sheet/examples/header-actions.tsx +5 -3
- package/dist/skill/reference/components/skeleton/COMPONENT.md +3 -2
- package/dist/skill/reference/components/smart-filter/COMPONENT.md +2 -1
- package/dist/skill/reference/components/smart-filter/examples/ranges.tsx +8 -2
- package/dist/skill/reference/components/spinner/COMPONENT.md +1 -1
- package/dist/skill/reference/components/tabs/COMPONENT.md +7 -4
- package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +18 -2
- package/dist/skill/reference/components/thumbnail/COMPONENT.md +1 -0
- package/dist/skill/reference/components/tooltip/COMPONENT.md +3 -3
- package/dist/skill/reference/foundation.md +15 -9
- package/dist/skill/reference/layout/page/COMPONENT.md +1 -0
- package/dist/skill/reference/layout/sidebar/COMPONENT.md +4 -2
- package/dist/skill/reference/layout/sidebar/examples/account-profile.tsx +66 -0
- package/dist/skill/responsive.md +28 -6
- package/dist/skill/ux.md +97 -2
- package/dist/tokens/semantic.d.ts +2 -1
- package/dist/tokens/themes/dark.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/avatar/COMPONENT.md +1 -0
- package/src/components/badge/COMPONENT.md +29 -5
- package/src/components/badge/examples/examples.module.css +14 -0
- package/src/components/badge/examples/overflow-count.tsx +32 -0
- package/src/components/banner/COMPONENT.md +1 -1
- package/src/components/card/COMPONENT.md +8 -4
- package/src/components/card/examples/person-header.tsx +38 -0
- package/src/components/composer/COMPONENT.md +35 -5
- package/src/components/composer/examples/bot-keyboard.tsx +43 -0
- package/src/components/confirm-dialog/COMPONENT.md +1 -1
- package/src/components/crossfade/COMPONENT.md +18 -10
- package/src/components/data-table/COMPONENT.md +17 -11
- package/src/components/data-table/examples/screen.tsx +6 -9
- package/src/components/data-table/examples/selection.tsx +60 -18
- package/src/components/dnd/COMPONENT.md +3 -3
- package/src/components/drawer/COMPONENT.md +41 -19
- package/src/components/drawer/examples/footer-actions.tsx +54 -0
- package/src/components/drawer/examples/header-actions.tsx +46 -25
- package/src/components/drawer/examples/link-loading.tsx +57 -0
- package/src/components/drawer/examples/screen.tsx +3 -5
- package/src/components/empty-page/COMPONENT.md +2 -2
- package/src/components/field-list/COMPONENT.md +7 -7
- package/src/components/field-list/examples/row-menu.tsx +1 -1
- package/src/components/hint/COMPONENT.md +1 -1
- package/src/components/input/COMPONENT.md +16 -3
- package/src/components/input/examples/password-policy.tsx +65 -0
- package/src/components/list/COMPONENT.md +1 -1
- package/src/components/login-form/COMPONENT.md +13 -11
- package/src/components/login-form/examples/overview.tsx +5 -5
- package/src/components/login-form/examples/states.tsx +7 -6
- package/src/components/message/COMPONENT.md +5 -5
- package/src/components/message/examples/bot-actions.tsx +47 -28
- package/src/components/modal/COMPONENT.md +31 -12
- package/src/components/modal/examples/structure.tsx +2 -4
- package/src/components/segmented-control/COMPONENT.md +11 -6
- package/src/components/segmented-control/examples/router.tsx +22 -0
- package/src/components/select/COMPONENT.md +4 -1
- package/src/components/sheet/COMPONENT.md +31 -12
- package/src/components/sheet/examples/header-actions.tsx +5 -3
- package/src/components/skeleton/COMPONENT.md +3 -2
- package/src/components/smart-filter/COMPONENT.md +2 -1
- package/src/components/smart-filter/examples/ranges.tsx +8 -2
- package/src/components/spinner/COMPONENT.md +1 -1
- package/src/components/tabs/COMPONENT.md +7 -4
- package/src/components/tabs/examples/in-drawer.tsx +18 -2
- package/src/components/thumbnail/COMPONENT.md +1 -0
- package/src/components/tooltip/COMPONENT.md +3 -3
- package/src/layout/page/COMPONENT.md +1 -0
- package/src/layout/sidebar/COMPONENT.md +4 -2
- package/src/layout/sidebar/examples/account-profile.tsx +66 -0
- package/src/styles/theme-dark.css +1 -0
- 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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
297
|
-
|
|
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
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</
|
|
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
|
}
|
|
@@ -37,7 +37,7 @@ Dnd.Root session owner: overlay (portal), live region, l
|
|
|
37
37
|
├─ Dnd.Draggable an item to carry elsewhere; stays in place, faded, until the drop
|
|
38
38
|
└─ Dnd.DropZone a box that takes dropped items
|
|
39
39
|
```
|
|
40
|
-
A drag in flight is drawn by `Dnd.Root` as a clone of the lifted element above every layer, following the pointer. The clone flies inside empty copies
|
|
40
|
+
A drag in flight is drawn by `Dnd.Root` as a clone of the lifted element above every layer, following the pointer. The clone keeps the original's size and flies inside empty copies of all its ancestors up to the page — `display: contents`, except a size container (`container-type`), which flies as an invisible box of its measured content width, so its container queries resolve as at the source — and the host's variables, `[data-…]` selectors, surface and scoped theme (a List row's tier padding and radius, a narrow FieldList's stacked row) dress it exactly as the item in place, and it sets down without a jump; an item without a fill of its own flies on the fill of the surface it was lifted from (`--prime-color-layer-current`). A refusal with a reason shows the reason in the tooltip's chip under the clone. The page gets `data-dnd-active="mouse | touch | pen"` on `<html>` for the length of the gesture (the holding cursor), and every list of the carried item's kind gets `data-dnd-carrying`.
|
|
41
41
|
|
|
42
42
|
## API
|
|
43
43
|
|
|
@@ -207,7 +207,7 @@ An item's own `onPointerDown` / `onKeyDown` run before the kit's: `preventDefaul
|
|
|
207
207
|
- A grid (`axis="grid"`) is `display: grid`: the host sets the tracks (`grid-template-columns`) and the cells' size; the gap takes a cell, and a carried item resizes to the cell it would land in.
|
|
208
208
|
- A `Dnd.Handle` ends its item: the last child, after the item's own trailing controls (a remove button), at the inline end (on the left in RTL); a `locked` item's lock takes the same place, so the grips of every row stand on one line. In a table it is the last cell. An on / off switch leads the item (its first child), so its state reads first and nothing stands between the text and the grip.
|
|
209
209
|
- The reason chip stands `--prime-tooltip-offset` under the carried copy, start-aligned, at most `--prime-tooltip-max-width` wide.
|
|
210
|
-
- The look of a row is the consumer's: give `Dnd.SortableItem` / `Dnd.Draggable` a radius on its own class (the gap and the clone's fill take it) and
|
|
210
|
+
- The look of a row is the consumer's: give `Dnd.SortableItem` / `Dnd.Draggable` a radius on its own class (the gap and the clone's fill take it) and the row's ancestors may dress it — the clone flies inside empty copies of all of them (a size container as a box of its width), so their variables, selectors and container queries still apply. A table row flies in a one-row copy of its table inside them.
|
|
211
211
|
- A scrollable ancestor under the pointer auto-scrolls within 64 px of its edge (a `ScrollContainer` strip works).
|
|
212
212
|
- Keep a zone's height stable between states; it only changes fill and outline.
|
|
213
213
|
|
|
@@ -271,7 +271,7 @@ An item's own `onPointerDown` / `onKeyDown` run before the kit's: `preventDefaul
|
|
|
271
271
|
- A `Dnd.SortableItem` `id` that differs from `getId(item)` → the item never moves.
|
|
272
272
|
- Rendering items yourself instead of through `renderItem` → the list cannot draw the gap or the optimistic order.
|
|
273
273
|
- Using `index` as the id → ids must survive a reorder.
|
|
274
|
-
- Styling a row from
|
|
274
|
+
- Styling a row from its siblings (`li + li`, `:first-child`) → the flying clone stands alone and loses it; keep the row's look on the row and its ancestors.
|
|
275
275
|
- Starting a drag from a button inside a row without `handle` → presses on controls are theirs; use `Dnd.Handle`.
|
|
276
276
|
- A drag-only move with no alternative → add a button or a menu for the same action.
|
|
277
277
|
- `aria-label` on an item that shows a title and a description → it hides them; point `aria-labelledby` / `aria-describedby` at them.
|
|
@@ -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
|
|
34
|
-
│ ├─ Drawer.Title <h2
|
|
35
|
-
│ ├─ Drawer.Description <p
|
|
36
|
-
│ └─ Drawer.Actions
|
|
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`.
|
|
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`
|
|
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` (
|
|
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
|
|
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`
|
|
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
|
|
216
|
-
-
|
|
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
|
-
-
|
|
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) |
|
|
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.
|
|
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
|
|
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`),
|
|
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
|
+
}
|