prime-ui-kit 0.18.0 → 0.20.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.
Files changed (145) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/{chunk-OAXJWF3L.js → chunk-3PY52JP3.js} +1764 -1764
  3. package/dist/chunks/{chunk-X3JT5CLR.js → chunk-4KONMO7Z.js} +1 -1
  4. package/dist/chunks/{chunk-MB4CXSLL.js → chunk-W6EGTNG6.js} +3772 -2888
  5. package/dist/color-picker.js +2 -2
  6. package/dist/components/data-table/ColumnsMenu.d.ts +39 -17
  7. package/dist/components/data-table/DataTable.d.ts +41 -12
  8. package/dist/components/data-table/DataTableColumnsMenu.d.ts +45 -0
  9. package/dist/components/data-table/Head.d.ts +18 -5
  10. package/dist/components/data-table/Row.d.ts +10 -4
  11. package/dist/components/data-table/StateRows.d.ts +5 -3
  12. package/dist/components/data-table/columnOrder.d.ts +14 -0
  13. package/dist/components/data-table/reorder.d.ts +15 -5
  14. package/dist/components/data-table/types.d.ts +5 -0
  15. package/dist/components/data-table/useFrozenColumns.d.ts +6 -2
  16. package/dist/components/datepicker/DatepickerPanel.d.ts +1 -1
  17. package/dist/components/dnd/Dnd.d.ts +9 -5
  18. package/dist/components/dnd/inPlaceRow.d.ts +7 -0
  19. package/dist/components/dnd/useDragSource.d.ts +2 -0
  20. package/dist/components/dnd/useSortableList.d.ts +5 -0
  21. package/dist/components/field-list/FieldList.d.ts +37 -15
  22. package/dist/components/gantt/Gantt.d.ts +1 -1
  23. package/dist/components/index.d.ts +3 -1
  24. package/dist/components/kanban/Kanban.d.ts +4 -0
  25. package/dist/components/list/List.d.ts +9 -4
  26. package/dist/components/scroll-container/ScrollContainer.d.ts +1 -1
  27. package/dist/index.css +1 -1
  28. package/dist/index.js +4 -2
  29. package/dist/internal/ToolRow.d.ts +23 -0
  30. package/dist/internal/leaveGuard.d.ts +6 -0
  31. package/dist/internal/overlay/DialogGuard.d.ts +3 -2
  32. package/dist/internal/overlay/DialogParts.d.ts +14 -5
  33. package/dist/layout/index.d.ts +1 -1
  34. package/dist/layout/page/Page.d.ts +8 -14
  35. package/dist/skill/anti-slop.md +55 -1
  36. package/dist/skill/api-contract.md +2 -2
  37. package/dist/skill/cheatsheet.md +12 -4
  38. package/dist/skill/checklist.md +7 -0
  39. package/dist/skill/choosing.md +1 -0
  40. package/dist/skill/components.md +1 -1
  41. package/dist/skill/composition.md +196 -21
  42. package/dist/skill/layouts.md +1 -1
  43. package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
  44. package/dist/skill/reference/components/data-table/COMPONENT.md +110 -49
  45. package/dist/skill/reference/components/data-table/examples/actions-column.tsx +154 -0
  46. package/dist/skill/reference/components/data-table/examples/column-order.tsx +152 -0
  47. package/dist/skill/reference/components/data-table/examples/columns-visibility.tsx +18 -18
  48. package/dist/skill/reference/components/data-table/examples/controlled.tsx +32 -17
  49. package/dist/skill/reference/components/data-table/examples/examples.module.css +14 -36
  50. package/dist/skill/reference/components/data-table/examples/narrow.tsx +25 -19
  51. package/dist/skill/reference/components/data-table/examples/reorder.tsx +24 -2
  52. package/dist/skill/reference/components/data-table/examples/row-link.tsx +18 -22
  53. package/dist/skill/reference/components/data-table/examples/screen.tsx +12 -1
  54. package/dist/skill/reference/components/data-table/examples/selection.tsx +13 -18
  55. package/dist/skill/reference/components/data-table/examples/server.tsx +45 -31
  56. package/dist/skill/reference/components/data-table/examples/toolbar.tsx +22 -35
  57. package/dist/skill/reference/components/datepicker/COMPONENT.md +2 -2
  58. package/dist/skill/reference/components/dnd/COMPONENT.md +13 -7
  59. package/dist/skill/reference/components/dnd/examples/examples.module.css +4 -3
  60. package/dist/skill/reference/components/dnd/examples/handle.tsx +5 -5
  61. package/dist/skill/reference/components/dnd/examples/locked.tsx +1 -1
  62. package/dist/skill/reference/components/dnd/examples/table-columns.tsx +75 -0
  63. package/dist/skill/reference/components/dnd/examples/table.tsx +6 -6
  64. package/dist/skill/reference/components/drawer/COMPONENT.md +26 -10
  65. package/dist/skill/reference/components/drawer/examples/header-actions.tsx +1 -1
  66. package/dist/skill/reference/components/drawer/examples/loading.tsx +58 -0
  67. package/dist/skill/reference/components/field-list/COMPONENT.md +78 -47
  68. package/dist/skill/reference/components/field-list/examples/base-row.tsx +2 -1
  69. package/dist/skill/reference/components/field-list/examples/locked.tsx +11 -11
  70. package/dist/skill/reference/components/field-list/examples/narrow.tsx +108 -33
  71. package/dist/skill/reference/components/field-list/examples/reorder.tsx +1 -1
  72. package/dist/skill/reference/components/field-list/examples/row-menu.tsx +71 -0
  73. package/dist/skill/reference/components/field-list/examples/screen.module.css +1 -1
  74. package/dist/skill/reference/components/field-list/examples/screen.tsx +169 -162
  75. package/dist/skill/reference/components/field-list/examples/totals.tsx +9 -8
  76. package/dist/skill/reference/components/field-list/examples/variants.tsx +51 -0
  77. package/dist/skill/reference/components/gantt/COMPONENT.md +4 -4
  78. package/dist/skill/reference/components/kanban/COMPONENT.md +6 -2
  79. package/dist/skill/reference/components/kanban/examples/states.tsx +7 -5
  80. package/dist/skill/reference/components/list/COMPONENT.md +21 -11
  81. package/dist/skill/reference/components/list/examples/reorder.tsx +42 -7
  82. package/dist/skill/reference/components/modal/COMPONENT.md +23 -10
  83. package/dist/skill/reference/components/scroll-container/COMPONENT.md +1 -1
  84. package/dist/skill/reference/components/segmented-control/COMPONENT.md +3 -2
  85. package/dist/skill/reference/components/sheet/COMPONENT.md +18 -7
  86. package/dist/skill/reference/components/smart-filter/COMPONENT.md +2 -2
  87. package/dist/skill/reference/layout/page/COMPONENT.md +13 -17
  88. package/dist/skill/reference/layout/page/examples/screen.module.css +1 -1
  89. package/dist/skill/reference/layout/page/examples/screen.tsx +93 -104
  90. package/dist/skill/reference/layout/page/examples/sections.tsx +2 -15
  91. package/dist/skill/responsive.md +14 -10
  92. package/dist/tokens/semantic.d.ts +2 -2
  93. package/dist/tokens/themes/dark.d.ts +1 -1
  94. package/package.json +1 -1
  95. package/src/components/confirm-dialog/COMPONENT.md +1 -1
  96. package/src/components/data-table/COMPONENT.md +110 -49
  97. package/src/components/data-table/examples/actions-column.tsx +154 -0
  98. package/src/components/data-table/examples/column-order.tsx +152 -0
  99. package/src/components/data-table/examples/columns-visibility.tsx +18 -18
  100. package/src/components/data-table/examples/controlled.tsx +32 -17
  101. package/src/components/data-table/examples/examples.module.css +14 -36
  102. package/src/components/data-table/examples/narrow.tsx +25 -19
  103. package/src/components/data-table/examples/reorder.tsx +24 -2
  104. package/src/components/data-table/examples/row-link.tsx +18 -22
  105. package/src/components/data-table/examples/screen.tsx +12 -1
  106. package/src/components/data-table/examples/selection.tsx +13 -18
  107. package/src/components/data-table/examples/server.tsx +45 -31
  108. package/src/components/data-table/examples/toolbar.tsx +22 -35
  109. package/src/components/datepicker/COMPONENT.md +2 -2
  110. package/src/components/dnd/COMPONENT.md +13 -7
  111. package/src/components/dnd/examples/examples.module.css +4 -3
  112. package/src/components/dnd/examples/handle.tsx +5 -5
  113. package/src/components/dnd/examples/locked.tsx +1 -1
  114. package/src/components/dnd/examples/table-columns.tsx +75 -0
  115. package/src/components/dnd/examples/table.tsx +6 -6
  116. package/src/components/drawer/COMPONENT.md +26 -10
  117. package/src/components/drawer/examples/header-actions.tsx +1 -1
  118. package/src/components/drawer/examples/loading.tsx +58 -0
  119. package/src/components/field-list/COMPONENT.md +78 -47
  120. package/src/components/field-list/examples/base-row.tsx +2 -1
  121. package/src/components/field-list/examples/locked.tsx +11 -11
  122. package/src/components/field-list/examples/narrow.tsx +108 -33
  123. package/src/components/field-list/examples/reorder.tsx +1 -1
  124. package/src/components/field-list/examples/row-menu.tsx +71 -0
  125. package/src/components/field-list/examples/screen.module.css +1 -1
  126. package/src/components/field-list/examples/screen.tsx +169 -162
  127. package/src/components/field-list/examples/totals.tsx +9 -8
  128. package/src/components/field-list/examples/variants.tsx +51 -0
  129. package/src/components/gantt/COMPONENT.md +4 -4
  130. package/src/components/kanban/COMPONENT.md +6 -2
  131. package/src/components/kanban/examples/states.tsx +7 -5
  132. package/src/components/list/COMPONENT.md +21 -11
  133. package/src/components/list/examples/reorder.tsx +42 -7
  134. package/src/components/modal/COMPONENT.md +23 -10
  135. package/src/components/scroll-container/COMPONENT.md +1 -1
  136. package/src/components/segmented-control/COMPONENT.md +3 -2
  137. package/src/components/sheet/COMPONENT.md +18 -7
  138. package/src/components/smart-filter/COMPONENT.md +2 -2
  139. package/src/layout/page/COMPONENT.md +13 -17
  140. package/src/layout/page/examples/screen.module.css +1 -1
  141. package/src/layout/page/examples/screen.tsx +93 -104
  142. package/src/layout/page/examples/sections.tsx +2 -15
  143. package/src/styles/theme-dark.css +1 -1
  144. package/src/styles/theme-light.css +1 -1
  145. package/dist/components/data-table/context.d.ts +0 -14
@@ -15,9 +15,9 @@ playground page. What each screen teaches is in [Screen rules](#screen-rules) at
15
15
  |---|---|---|---|
16
16
  | [List page](#list-page) | data-table | [screen.tsx](reference/components/data-table/examples/screen.tsx) | the title, a line of figures, SmartFilter search and filters, export and one primary action in the page's top bar; statuses, row menu, pages, filtered empty state |
17
17
  | [Detail page](#detail-page) | description-list | [screen.tsx](reference/components/description-list/examples/screen.tsx) | a nested record: the path ending in the title, status and due date and the record's actions by weight in the top bar; main column + side column of Cards, Timeline |
18
- | [Settings page](#settings-page) | page | [screen.tsx](reference/layout/page/examples/screen.tsx) | the path in a collapsing top bar, the large title in the flow, heading column + panel per section, form with its own save, Switches that apply at once, danger zone with a confirm |
18
+ | [Settings page](#settings-page) | page | [screen.tsx](reference/layout/page/examples/screen.tsx) | the path in a collapsing top bar, the large title in the flow, a card with its own header per section, form with its own save, Switches that apply at once, danger zone with a confirm |
19
19
  | [Form in a Drawer](#form-in-a-drawer) | drawer | [screen.tsx](reference/components/drawer/examples/screen.tsx) | the title, counts, search and the Drawer trigger in the top bar; Drawer form: groups, errors on submit, focus on the first invalid field, loading, toast |
20
- | [Record editor](#record-editor) | field-list | [screen.tsx](reference/components/field-list/examples/screen.tsx) | a document page (a long form): the title and a line of description in `Page.Heading`, «Сохранить тариф» in `Page.Actions`, both in a `Page.Header` in the flow; a plan card edited in aside sections: name and description, specs in a `FieldList` (icon-only `Select` and `ColorPresets` in compact columns, pinned rows, reorder), the price and its changes by month in one list with the base row pinned, a setup fee in three currencies in one row under one heading, save with «Сохранено» on the button |
20
+ | [Record editor](#record-editor) | field-list | [screen.tsx](reference/components/field-list/examples/screen.tsx) | a document page (a long form): the title and a line of description in `Page.Heading`, «Сохранить тариф» in `Page.Actions`, both in a `Page.Header` in the flow; a plan card edited in card sections: name and description, specs in a `FieldList` (icon-only `Select` and `ColorPresets` in compact columns, pinned rows, reorder), the price and its changes by month in one list with the base row pinned, a setup fee in three currencies in one row under one heading, save with «Сохранено» on the button |
21
21
  | [Dashboard](#dashboard) | card | [screen.tsx](reference/components/card/examples/screen.tsx) | the large title + a line of data in the flow, period switch and report in a collapsing `Page.TopBar`; KPI row that cross-fades to the new period, a Sparkline revenue trend, invoiced vs paid in a ChartFrame, panels with progress and a split, short table + link to the list |
22
22
  | [Screen states](#screen-states) | crossfade | [screen.tsx](reference/components/crossfade/examples/screen.tsx) | title + the statement date in `Page.TopBar`, page error in a Banner with retry, region loading with a Skeleton, Crossfade between states, table error in place, first-run EmptyPage |
23
23
  | [Messenger](#messenger) | chat | [screen.tsx](reference/components/chat/examples/screen.tsx) | title + the unread count in `Page.TopBar`; a full-height Page.Split of a `ChatList` with its search (live last message, ticks, unread counts, «печатает…») and a `Chat`: `Chat.Header` with the back button, avatar, title, the status that turns into «печатает…» (`typing`) and the search button, `Chat.Search`, `Chat.Messages` from `items` + `renderMessage` (dates, runs and the unread band from the data, history loaded above with `hasOlder` / `onLoadOlder`, a first load in skeletons), text, a photo, a file and sent voice, reply and edit from the menu through `Composer.Context messageId` (`replyTo` / `editOf`), the jump to the quoted message (`Message.Reply target`), optimistic sends whose messages rise from the composer while the client's answer slides in, «печатает…» both ways (`onActivityChange` → `Chat.Root typing`), unread counts from `onNewMessage` and `onRead`, the Composer with attachments in `Chat.Footer` |
@@ -66,7 +66,7 @@ AppShell.Root once per app (layouts.md#app-frame-once-per-a
66
66
  │ │ ├─ Breadcrumb.Root only on a nested record: the levels above this page
67
67
  │ │ ├─ Page.Title the one h1, the path's last level
68
68
  │ │ └─ Page.Description a line of data
69
- │ ├─ Page.Tools sections, search and filter, view — kit controls in reading order
69
+ │ ├─ Page.Tools what acts on the whole page — sections, a period, view; a one-list page's search
70
70
  │ ├─ Page.Actions every button of the page, each with an icon; the primary last
71
71
  │ └─ Page.Chips SmartFilter.Chips: a line under everything, gone while empty
72
72
  └─ Page.Body blocks 24 apart, the first 24 under the bar's controls
@@ -98,9 +98,11 @@ order is Heading, Tools, Actions, Chips whatever the JSX order.
98
98
  then `Page.Title` with its name, on one line (the heading draws the chevron between them), and
99
99
  `Page.Description` under the whole line. The title is never a row of its own under the path, and
100
100
  the path never repeats the title as its last item.
101
- - **`Page.Tools`** — the page's own tools as kit controls, in the order a person reads them: sections
102
- (a `SegmentedControl` with counts), search and filter (`SmartFilter.Toolbar`), the view or the
103
- period. Named with `aria-label`; no buttons and no wrappers inside.
101
+ - **`Page.Tools`** — what acts on the whole page, as kit controls in the order a person reads them:
102
+ sections (a `SegmentedControl` with counts), the view, a period that drives several blocks; the
103
+ search and filter (`SmartFilter.Toolbar`) only when the page is one list
104
+ ([where the filters go](#where-the-filters-go)). Named with `aria-label`; no buttons and no
105
+ wrappers inside.
104
106
  - **`Page.Actions`** — the one place for the page's buttons: secondary ones (`soft` + `neutral`)
105
107
  first, the primary (`solid`) last. Every Button carries a `Button.Icon` beside its label: the bar
106
108
  shows it as the icon alone when the line runs short.
@@ -234,6 +236,39 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
234
236
  - **Card when** the block stands alone and has its own title: a KPI tile, a settings panel, facts in a
235
237
  side column, a chart. **No card when** the block is the only thing in its region: a section heading
236
238
  and the content are enough.
239
+ - **A titled block stands in the flow, its heading inside it.** A panel with a title and a line of
240
+ description (a settings section, a group of a long form) is a `Card.Root` whose `Card.Header`
241
+ holds `Card.Title as="h2"` and `Card.Description`; the cards stack 24 apart. Never a heading column
242
+ beside its panel: it takes a third of the width from the fields and breaks the reading line.
243
+
244
+ ```tsx partial
245
+ // bad — the heading in a column on the left, the panel on the right
246
+ <div className={styles.headingColumnAndPanel}>
247
+ <div>
248
+ <Typography as="h2" variant="title-m">Компания</Typography>
249
+ <Typography tone="secondary">Попадают в счета и акты.</Typography>
250
+ </div>
251
+ <Card.Root variant="panel">…</Card.Root>
252
+ </div>
253
+ ```
254
+
255
+ ```tsx good
256
+ // good — the card carries its own heading, stacked in the flow
257
+ <Card.Root variant="panel">
258
+ <Card.Header>
259
+ <Card.Title as="h2">Компания</Card.Title>
260
+ <Card.Description>Попадают в счета и акты.</Card.Description>
261
+ </Card.Header>
262
+ <Card.Body>
263
+ <Input.Root label="Название">
264
+ <Input.Wrapper>
265
+ <Input.Field name="name" />
266
+ </Input.Wrapper>
267
+ </Input.Root>
268
+ </Card.Body>
269
+ </Card.Root>
270
+ ```
271
+
237
272
  - `DataTable` is already a filled block — never inside a Card; a card in a card only as
238
273
  [the next layer, sparingly](foundations.md#card-in-a-card). No borders around blocks; `Divider`
239
274
  only inside a block (list rows, a form and its footer).
@@ -257,6 +292,9 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
257
292
  table cell.
258
293
  - Placement: every button of the page in one `Page.Actions` — of `Page.TopBar` on a work screen, of the flow `Page.Header` on a document page — the primary last; card actions in `Card.Footer`; dialog actions in
259
294
  `Modal.Footer` / `Drawer.Footer`; row actions in the last column; bulk actions in the table toolbar.
295
+ - A dialog footer is the dialog's answer: Cancel and the primary. Deleting the record the dialog
296
+ edits is not a third footer button: it is the last `tone="danger"` item of a ⋯ `Dropdown` in
297
+ `Modal.Actions` / `Drawer.Actions`, confirmed by a `ConfirmDialog`.
260
298
  - An empty state's action replaces the header's primary, it does not duplicate it.
261
299
  - Destructive: `tone="danger"`; a standalone trigger is `outline`, in a menu it is the last item, the
262
300
  confirm is a `ConfirmDialog` (its `onConfirm` returns the request's Promise).
@@ -268,13 +306,24 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
268
306
  - Labels above every field (`label`), placeholder is an example value, help in `hint`, problems in
269
307
  `error` — after blur or on submit. Mark the minority (`required` or `optional`).
270
308
  - Records of one shape that a person adds, edits and removes are a `FieldList`: one row per record,
271
- the column headings once above the rows, small attributes (an icon, a color) in `compact`
309
+ the column headings once above the rows, small attributes (an icon, then its color) in `compact`
272
310
  columns. Never a label in every row, never numbered labels, never one record split over lines.
311
+ FieldList draws every row's actions itself — the remove «×» and the handle after it, a pinned
312
+ row's remove off with its reason (`labels.lockedReason`) — so never add your own.
313
+ - The row's actions (`rowActions`): `"buttons"` (default) for short lists edited often on a desktop,
314
+ where moving rows is a main task — remove and handle in sight on every row; `"menu"` — one «⋯» with
315
+ «Выше», «Ниже», «Удалить» — for dense rows (three or more columns), touch-heavy screens, or a row
316
+ that would carry more than remove and move.
317
+ - The look (`variant`): `"plain"` (default) when the list is the content of its card or section;
318
+ `"surface"` when the list stands among other fields of a form or card, so its rows read as one
319
+ block apart from the fields around it.
273
320
  - One value in several units is one row of fields under one heading, each unit in its field's
274
321
  `Input.InlineAffix`; a base value with overrides is one `FieldList` with the base pinned first.
275
322
  The shape of the data decides the layout: [ux.md](ux.md#2-the-shape-of-the-data-decides-the-layout).
276
323
  - Rows that add up (shares of a bonus, hours, amounts) carry their sums under them in the same list:
277
- `FieldList renderTotals`, a short number in a `width: "xs"` column. Return the save's promise from
324
+ `FieldList renderTotals`, a short number in a `width: "xs"` column. A number with its unit
325
+ (`inputMode="decimal"` and an end `Input.InlineAffix`) ends at the field's end, and its column's
326
+ total follows it there by itself. Return the save's promise from
278
327
  its `onValueChange` when each edit saves at once.
279
328
  - Fields 20 apart; related short fields side by side in an
280
329
  [intrinsic grid](responsive.md#intrinsic-grids) with `align-items: start`
@@ -296,13 +345,130 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
296
345
 
297
346
  ### Where the filters go
298
347
 
299
- - The page's search and filters are the page's: `SmartFilter.Root` around `Page.Root`,
300
- `SmartFilter.Toolbar` in the bar's `Page.Tools`, `SmartFilter.Chips` in its `Page.Chips`
301
- ([the list page](#list-page)).
302
- A search alone is the same SmartFilter with `fields={[]}`.
303
- - The DataTable `toolbar` keeps the table's own tools: `DataTable.ColumnsMenu`, a bulk bar of the
304
- selected rows. Search and filters go there only when the table is not the page's main region (a
305
- table in a section of a record, in a Drawer).
348
+ A filter stands next to what it filters.
349
+
350
+ - `Page.TopBar` holds only what acts on the whole page: the heading with its line of data, a period
351
+ or a date range that drives several blocks (the KPI cards and the table), the page's sections or
352
+ view, the primary action.
353
+ - A page that holds more than its table — KPI cards, a chart, another block: the table's own
354
+ controls (search and filter, the chips, the column chooser, a table / cards view, bulk actions)
355
+ stand in the table's tools row. `SmartFilter.Root` wraps the `DataTable`; `SmartFilter.Toolbar`
356
+ and `DataTableColumnsMenu` go in its `toolbar`, `SmartFilter.Chips` in its `toolbarChips`
357
+ ([server.tsx](reference/components/data-table/examples/server.tsx)).
358
+ - A page that is one list — the table, the board or the schedule is the whole page: «next to the
359
+ list» and «on the page» are one place, so its search and filter stay in the bar. `SmartFilter.Root`
360
+ wraps `Page.Root`, `SmartFilter.Toolbar` in `Page.Tools`, `SmartFilter.Chips` in `Page.Chips`
361
+ ([the list page](#list-page)). A search alone is the same SmartFilter with `fields={[]}`.
362
+ - What picks the place: does the control change everything under the bar? A period that recomputes
363
+ the cards and the table does — the bar. A search that narrows only the rows does not — the
364
+ table's row.
365
+ - Both rows are one tool row of the kit: written in reading order, a search takes the free room,
366
+ switchers and buttons keep their width at the end, and a short row wraps by itself — pass the
367
+ controls as they are, never inside a flex wrapper of your own.
368
+
369
+ ```tsx partial
370
+ // bad — the table's search, filter, chips and chooser in the bar, above KPI cards they do not filter
371
+ <SmartFilter.Root fields={FIELDS}>
372
+ <Page.Root>
373
+ <Page.TopBar>
374
+ <Page.Heading>
375
+ <Page.Title>Модели</Page.Title>
376
+ </Page.Heading>
377
+ <Page.Tools aria-label="Модели">
378
+ <SmartFilter.Toolbar />
379
+ <DataTableColumnsMenu columns={COLUMNS} hiddenColumns={hidden} onHiddenColumnsChange={setHidden} />
380
+ </Page.Tools>
381
+ <Page.Chips>
382
+ <SmartFilter.Chips />
383
+ </Page.Chips>
384
+ </Page.TopBar>
385
+ <Page.Body>
386
+ <Page.Grid>{/* four KPI cards */}</Page.Grid>
387
+ <DataTable columns={COLUMNS} rows={models} hiddenColumns={hidden} />
388
+ </Page.Body>
389
+ </Page.Root>
390
+ </SmartFilter.Root>
391
+ ```
392
+
393
+ ```tsx good
394
+ // good — the bar keeps the title, the period that drives the cards and the table, the primary
395
+ // action; the table's search, filter, chips and chooser stand in the table's own tools row
396
+ // declare: const FIELDS: Parameters<typeof SmartFilter.Root>[0]["fields"]
397
+ // declare: const models: { id: string; name: string; rides: number }[]
398
+ // declare: const kpis: { label: string; value: string }[]
399
+ // declare: const hidden: string[]
400
+ // declare: const setHidden: (hidden: string[]) => void
401
+ <Page.Root>
402
+ <Page.TopBar>
403
+ <Page.Heading>
404
+ <Page.Title>Модели</Page.Title>
405
+ </Page.Heading>
406
+ <Page.Tools aria-label="Модели">
407
+ <Datepicker.Root mode="range" aria-label="Период" />
408
+ </Page.Tools>
409
+ <Page.Actions>
410
+ <Button.Root>
411
+ <Button.Icon>
412
+ <Icon name="action.add" />
413
+ </Button.Icon>
414
+ Добавить модель
415
+ </Button.Root>
416
+ </Page.Actions>
417
+ </Page.TopBar>
418
+ <Page.Body>
419
+ <Page.Grid>
420
+ {kpis.map((kpi) => (
421
+ <Page.Cell key={kpi.label} span={3}>
422
+ <Card.Root variant="stat-trend">
423
+ <Card.Label>{kpi.label}</Card.Label>
424
+ <Card.Value>{kpi.value}</Card.Value>
425
+ </Card.Root>
426
+ </Page.Cell>
427
+ ))}
428
+ </Page.Grid>
429
+ <SmartFilter.Root fields={FIELDS}>
430
+ <DataTable
431
+ columns={[
432
+ { id: "name", header: "Модель", accessor: "name", hideable: false },
433
+ { id: "rides", header: "Поездки", accessor: "rides", numeric: true },
434
+ ]}
435
+ rows={models}
436
+ getRowKey={(row) => row.id}
437
+ hiddenColumns={hidden}
438
+ toolbar={
439
+ <>
440
+ <SmartFilter.Toolbar />
441
+ <DataTableColumnsMenu
442
+ columns={[
443
+ { id: "name", header: "Модель", accessor: "name", hideable: false },
444
+ { id: "rides", header: "Поездки", accessor: "rides", numeric: true },
445
+ ]}
446
+ hiddenColumns={hidden}
447
+ onHiddenColumnsChange={setHidden}
448
+ />
449
+ </>
450
+ }
451
+ toolbarChips={<SmartFilter.Chips />}
452
+ />
453
+ </SmartFilter.Root>
454
+ </Page.Body>
455
+ </Page.Root>
456
+ ```
457
+
458
+ ### Controls of a table
459
+
460
+ - The column chooser has one place per table. Over the row actions: `columnsMenu` puts the icon
461
+ trigger in the head cell of the row-actions column (the last column, `hideHeader`), and it sticks
462
+ with `stickyLastColumn`. A table without row actions gets no chooser cell in its header: the
463
+ chooser is `<DataTableColumnsMenu>` beside the table's search ([where the filters go](#where-the-filters-go)),
464
+ bound to the same `hiddenColumns` / `columnOrder` state the table reads. Give the key column and
465
+ the actions column `hideable: false`.
466
+ - The DataTable `toolbar` holds two or more controls of the table itself — its search and filter,
467
+ the chooser, a view, a bulk bar of the selected rows — as they are, in reading order; its chips go
468
+ to `toolbarChips`.
469
+ - One control never gets a row of its own. A lone «Экспорт» or «Создать» is the page's
470
+ (`Page.Actions`); a lone chooser joins the table's search or the page's tools; a count or a total
471
+ of the list is data in `Page.Description`.
306
472
 
307
473
  ### Tables
308
474
 
@@ -321,6 +487,15 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
321
487
  - Phones: the table scrolls inside itself, `stickyFirstColumn` keeps the key column; never let the page
322
488
  scroll sideways. Short lists of objects can be `Card variant="list"` instead of a table.
323
489
 
490
+ ### List rows
491
+
492
+ - A row's own controls stand beside its element in `List.ItemActions`, one tier below the row.
493
+ - A plain settings row reads label first and ends with its toggle (the platform convention).
494
+ - A row people reorder leads with its toggle — `<List.ItemActions side="start">`, or the Switch as the
495
+ first child of a `Dnd.SortableItem` — so the on / off state reads first, like a checkbox, and the
496
+ grip keeps the end edge ([reorder.tsx](reference/components/list/examples/reorder.tsx)).
497
+ - Never a toggle between the label and the grip: «Почта … [Switch] [grip]» reads as two handles.
498
+
324
499
  ## 9. Feedback
325
500
 
326
501
  | Situation | Component |
@@ -455,10 +630,10 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
455
630
  [data-table/examples/screen.tsx](reference/components/data-table/examples/screen.tsx)
456
631
 
457
632
  - A work screen titles itself in its `Page.TopBar`: `Page.Title` is the only heading, its `Page.Description` is one line of figures («10 счетов · просрочено 2 на …»), not a sentence about the page; there is no second header in the flow.
458
- - `SmartFilter.Root` wraps the page: `SmartFilter.Toolbar` in `Page.Tools`, `SmartFilter.Chips` in `Page.Chips`; export (`soft` + `neutral`, an icon beside its label) and the one primary action (`solid`, last) in `Page.Actions`. The table itself has no `toolbar`.
633
+ - The table is the whole page, so its filters are the page's: `SmartFilter.Root` wraps the page, `SmartFilter.Toolbar` in `Page.Tools`, `SmartFilter.Chips` in `Page.Chips`; export (`soft` + `neutral`, an icon beside its label) and the one primary action (`solid`, last) in `Page.Actions`. The table itself has no `toolbar`; add a KPI row above it and the search, filter and chips move into the table's `toolbar` / `toolbarChips`.
459
634
  - A status is a `Badge` with one `tone` per meaning across the product (`STATUS` is one map); the text carries the meaning, the tone repeats it.
460
635
  - Numbers and dates are `numeric` columns with `headerAlign="end"` (tabular figures, values and heading on the end edge). On a phone the table scrolls inside itself and `stickyFirstColumn` keeps the number in place — the page never scrolls sideways.
461
- - Row actions are a `Dropdown` behind an `action.more` icon of size `s`: controls in cells are one tier below the table. Delete is `tone="danger"` at the end of the menu.
636
+ - Row actions are a `Dropdown` behind an `action.more` icon of size `s` in the last column (`hideHeader`), pinned to the end edge with `stickyLastColumn` so they stay in reach while the columns scroll: controls in cells are one tier below the table. Delete is `tone="danger"` at the end of the menu.
462
637
  - Empty after a filter is the table's `empty` with `EmptyPage size="s"` and a «Сбросить фильтры» action; the header and the filters stay in place.
463
638
 
464
639
  ### Detail page
@@ -478,7 +653,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
478
653
  [page/examples/screen.tsx](reference/layout/page/examples/screen.tsx)
479
654
 
480
655
  - A document page: `Page.Root maxWidth="wide"`, the large `Page.Title` with a sentence of `Page.Description` in the `Page.Heading` of a `Page.Header` in the flow, and a `Page.TopBar collapse` whose `Page.Heading` holds only the path and repeats the title once it scrolls away.
481
- - A section is `Page.Section layout="aside"`: a heading column (`label` + a line of `description`) and a `Card variant="panel"` panel; sections are 40 px apart; on a narrow screen the column moves above the panel by itself (flex-wrap, no breakpoints).
656
+ - A section is a `Card variant="panel"` with its own `Card.Header` (`Card.Title as="h2"` + a line of `Card.Description`), the cards stacked in the flow ([a titled block](#4-surfaces)).
482
657
  - The form wraps the whole `Card.Root` in `<form noValidate>`; the buttons are in `Card.Footer`: «Отменить изменения» `ghost` + `neutral`, then the primary «Сохранить». While the request runs — `loading` on the button, and one `Fieldset disabled` turns the fields off; the form stays on screen, so success is the button text «Сохранено» for two seconds, without a notification.
483
658
  - Fields are 20 px apart; two short related fields side by side while there is width, with `align-items: start` and `reserveSupportRow`, so an error does not shift the neighbour.
484
659
  - An error comes through the field's `error` after submit, not while typing; a format hint goes in `hint`.
@@ -502,10 +677,10 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
502
677
 
503
678
  [field-list/examples/screen.tsx](reference/components/field-list/examples/screen.tsx)
504
679
 
505
- - A record editor is a document page — a long form read top to bottom in aside sections, like Settings — so its title stands in the flow: `Page.Header` with the title and one line of description in `Page.Heading` and the primary action in `Page.Actions`; no second title anywhere.
680
+ - A record editor is a document page — a long form read top to bottom in card sections, like Settings — so its title stands in the flow: `Page.Header` with the title and one line of description in `Page.Heading` and the primary action in `Page.Actions`; no second title anywhere.
506
681
  - The shape of the data decides the layout: records a person adds and removes are a `FieldList`, one row per record; the column headings stand once above the rows, and the fields in the cells take them as their labels.
507
682
  - No label in every row and no numbered one («Преимущество 1»): a one-column list has no headings at all — its section names it ([reorder.tsx](reference/components/field-list/examples/reorder.tsx)).
508
- - Small attributes of a record go in compact columns of its row: the icon is a `Select` with only `Select.TriggerIcon`, the color `ColorPresets`; the first specs are pinned (`getLocked`) — the lock says so itself, without explanatory text.
683
+ - Small attributes of a record go in compact columns of its row, the color right after the icon it paints: the icon is a `Select` with only `Select.TriggerIcon`, the color `ColorPresets`; the first specs are pinned (`getLocked`) — the lock and the remove that is off say so themselves, without explanatory text.
509
684
  - The base price and its changes are one list: the base row is pinned first and shows when it applies, the changes are rows with a month and a price; the ruble sign is in the field (`Input.InlineAffix`), not in every heading.
510
685
  - One value in three currencies is three fields in one row under one group heading, each with its own name for screen readers and the currency inside the field.
511
686
  - The lists live in the page state, the name and description fields in the `<form>` of the first section; the primary «Сохранить тариф» in `Page.Actions` is tied to it through `form={formId}` and saves everything. While the request runs the fields and the lists are off (`Fieldset disabled`, `disabled`), afterwards «Сохранено» on the button.
@@ -566,7 +741,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
566
741
  - The placement of deals is `value` as `{ [stage]: id[] }`; `onValueChange` returns the request's promise and applies `value` only after the answer; a server refusal returns the card to its place.
567
742
  - Stage totals are in `renderColumnMeta` («2 сделки · 3 575 000 ₽»), the stage color is the column's `color`.
568
743
  - A deal opens through `onItemOpen` of `Kanban.Root` (click, Enter, Space; never after a drag) in a `Drawer` with a `DescriptionList`; no `onClick` / `onKeyDown` of your own on the card; a deal with its own page — `getItemHref`. The stage change is available in the panel too — dragging is not the only way; `Drawer.Guard busy` holds the panel while saving.
569
- - The first load is `loading` on `Kanban` and `DataTable`: skeletons in the geometry of cards and rows.
744
+ - The first load is `loading` on `Kanban` and `DataTable`: skeletons in the geometry of cards and rows. Stages that come from the server too: `loading` with empty `columns` draws `loadingColumns` placeholder columns — never a board skeleton of your own beside the `Kanban`.
570
745
 
571
746
  ### App frame
572
747
 
@@ -155,7 +155,7 @@ shows only icons); none on the children of a sub-list.
155
155
  page's title and tools are in its `Page.TopBar`, its blocks go into `Page.Body`
156
156
  ([the page skeleton](composition.md#1-page-skeleton)).
157
157
  - Document pages — settings and long forms: `Page.Root maxWidth="wide"` with `Page.Header` in the
158
- flow (the page's h1) and a `Page.Section layout="aside"` (heading column + panel) per section
158
+ flow (the page's h1) and a `Card.Root` with its own `Card.Header` per section
159
159
  ([the settings page](reference/layout/page/examples/screen.tsx)), or `maxWidth="readable"` for a single
160
160
  narrow column of fields.
161
161
  - Long reading text (terms, help): `Page.Root maxWidth="readable"` with `Page.Header` in the flow.
@@ -134,5 +134,5 @@ The dialog is a Modal `s` with the floating surface, a header and a footer; ther
134
134
  - `tone="accent"` for a delete → destructive is always `danger`.
135
135
 
136
136
  ## Related
137
- - **Built from:** the dialog Modal stands on (`internal/overlay/ModalDialog` at `s`, the header, body and footer of `internal/overlay/DialogParts`), [ScrollContainer](../scroll-container/COMPONENT.md) (the body), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (pictures the shared icon slot recognises), [Button](../button/COMPONENT.md) (Cancel, the confirm with `loading`), [Banner](../banner/COMPONENT.md) (the failure message), `Icon` (`status.danger` in the Banner)
137
+ - **Built from:** the dialog Modal stands on (`internal/overlay/ModalDialog` at `s`, the header, body and footer of `internal/overlay/DialogParts`), [ScrollContainer](../scroll-container/COMPONENT.md) (the body), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (pictures the shared icon slot recognises), [Skeleton](../skeleton/COMPONENT.md) (the shared description's loading line), [Button](../button/COMPONENT.md) (Cancel, the confirm with `loading`), [Banner](../banner/COMPONENT.md) (the failure message), `Icon` (`status.danger` in the Banner)
138
138
  - **See also:** [Modal](../modal/COMPONENT.md), [Drawer](../drawer/COMPONENT.md), [Notification](../notification/COMPONENT.md)