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