prime-ui-kit 0.18.0 → 0.19.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 (104) hide show
  1. package/dist/chunks/{chunk-OAXJWF3L.js → chunk-RZNGLELR.js} +120 -114
  2. package/dist/chunks/{chunk-MB4CXSLL.js → chunk-WYCTXD63.js} +832 -619
  3. package/dist/chunks/{chunk-X3JT5CLR.js → chunk-ZTZ2ML2B.js} +1 -1
  4. package/dist/color-picker.js +2 -2
  5. package/dist/components/data-table/ColumnsMenu.d.ts +16 -20
  6. package/dist/components/data-table/DataTable.d.ts +14 -11
  7. package/dist/components/data-table/Head.d.ts +8 -1
  8. package/dist/components/data-table/Row.d.ts +7 -1
  9. package/dist/components/data-table/StateRows.d.ts +3 -1
  10. package/dist/components/field-list/FieldList.d.ts +8 -8
  11. package/dist/components/index.d.ts +1 -1
  12. package/dist/components/kanban/Kanban.d.ts +4 -0
  13. package/dist/components/list/List.d.ts +9 -4
  14. package/dist/index.css +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/internal/fieldCell.d.ts +6 -0
  17. package/dist/internal/leaveGuard.d.ts +6 -0
  18. package/dist/internal/overlay/DialogGuard.d.ts +3 -2
  19. package/dist/internal/overlay/DialogParts.d.ts +14 -5
  20. package/dist/layout/index.d.ts +1 -1
  21. package/dist/layout/page/Page.d.ts +1 -7
  22. package/dist/skill/anti-slop.md +23 -0
  23. package/dist/skill/api-contract.md +1 -1
  24. package/dist/skill/cheatsheet.md +7 -3
  25. package/dist/skill/checklist.md +4 -0
  26. package/dist/skill/composition.md +70 -12
  27. package/dist/skill/layouts.md +1 -1
  28. package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
  29. package/dist/skill/reference/components/data-table/COMPONENT.md +40 -40
  30. package/dist/skill/reference/components/data-table/examples/actions-column.tsx +154 -0
  31. package/dist/skill/reference/components/data-table/examples/columns-visibility.tsx +3 -12
  32. package/dist/skill/reference/components/data-table/examples/controlled.tsx +1 -1
  33. package/dist/skill/reference/components/data-table/examples/narrow.tsx +4 -11
  34. package/dist/skill/reference/components/data-table/examples/row-link.tsx +18 -22
  35. package/dist/skill/reference/components/data-table/examples/screen.tsx +12 -1
  36. package/dist/skill/reference/components/data-table/examples/toolbar.tsx +26 -32
  37. package/dist/skill/reference/components/dnd/COMPONENT.md +4 -3
  38. package/dist/skill/reference/components/dnd/examples/examples.module.css +4 -3
  39. package/dist/skill/reference/components/dnd/examples/handle.tsx +5 -5
  40. package/dist/skill/reference/components/dnd/examples/locked.tsx +1 -1
  41. package/dist/skill/reference/components/dnd/examples/table.tsx +6 -6
  42. package/dist/skill/reference/components/drawer/COMPONENT.md +26 -10
  43. package/dist/skill/reference/components/drawer/examples/header-actions.tsx +1 -1
  44. package/dist/skill/reference/components/drawer/examples/loading.tsx +58 -0
  45. package/dist/skill/reference/components/field-list/COMPONENT.md +29 -25
  46. package/dist/skill/reference/components/field-list/examples/base-row.tsx +2 -1
  47. package/dist/skill/reference/components/field-list/examples/locked.tsx +11 -11
  48. package/dist/skill/reference/components/field-list/examples/reorder.tsx +1 -1
  49. package/dist/skill/reference/components/field-list/examples/screen.module.css +1 -1
  50. package/dist/skill/reference/components/field-list/examples/screen.tsx +168 -162
  51. package/dist/skill/reference/components/field-list/examples/totals.tsx +9 -8
  52. package/dist/skill/reference/components/kanban/COMPONENT.md +6 -2
  53. package/dist/skill/reference/components/kanban/examples/states.tsx +7 -5
  54. package/dist/skill/reference/components/list/COMPONENT.md +21 -11
  55. package/dist/skill/reference/components/list/examples/reorder.tsx +42 -7
  56. package/dist/skill/reference/components/modal/COMPONENT.md +23 -10
  57. package/dist/skill/reference/components/segmented-control/COMPONENT.md +3 -2
  58. package/dist/skill/reference/components/sheet/COMPONENT.md +18 -7
  59. package/dist/skill/reference/layout/page/COMPONENT.md +5 -12
  60. package/dist/skill/reference/layout/page/examples/screen.module.css +1 -1
  61. package/dist/skill/reference/layout/page/examples/screen.tsx +93 -104
  62. package/dist/skill/reference/layout/page/examples/sections.tsx +2 -15
  63. package/dist/skill/responsive.md +7 -5
  64. package/dist/tokens/semantic.d.ts +1 -1
  65. package/dist/tokens/themes/dark.d.ts +1 -1
  66. package/package.json +1 -1
  67. package/src/components/confirm-dialog/COMPONENT.md +1 -1
  68. package/src/components/data-table/COMPONENT.md +40 -40
  69. package/src/components/data-table/examples/actions-column.tsx +154 -0
  70. package/src/components/data-table/examples/columns-visibility.tsx +3 -12
  71. package/src/components/data-table/examples/controlled.tsx +1 -1
  72. package/src/components/data-table/examples/narrow.tsx +4 -11
  73. package/src/components/data-table/examples/row-link.tsx +18 -22
  74. package/src/components/data-table/examples/screen.tsx +12 -1
  75. package/src/components/data-table/examples/toolbar.tsx +26 -32
  76. package/src/components/dnd/COMPONENT.md +4 -3
  77. package/src/components/dnd/examples/examples.module.css +4 -3
  78. package/src/components/dnd/examples/handle.tsx +5 -5
  79. package/src/components/dnd/examples/locked.tsx +1 -1
  80. package/src/components/dnd/examples/table.tsx +6 -6
  81. package/src/components/drawer/COMPONENT.md +26 -10
  82. package/src/components/drawer/examples/header-actions.tsx +1 -1
  83. package/src/components/drawer/examples/loading.tsx +58 -0
  84. package/src/components/field-list/COMPONENT.md +29 -25
  85. package/src/components/field-list/examples/base-row.tsx +2 -1
  86. package/src/components/field-list/examples/locked.tsx +11 -11
  87. package/src/components/field-list/examples/reorder.tsx +1 -1
  88. package/src/components/field-list/examples/screen.module.css +1 -1
  89. package/src/components/field-list/examples/screen.tsx +168 -162
  90. package/src/components/field-list/examples/totals.tsx +9 -8
  91. package/src/components/kanban/COMPONENT.md +6 -2
  92. package/src/components/kanban/examples/states.tsx +7 -5
  93. package/src/components/list/COMPONENT.md +21 -11
  94. package/src/components/list/examples/reorder.tsx +42 -7
  95. package/src/components/modal/COMPONENT.md +23 -10
  96. package/src/components/segmented-control/COMPONENT.md +3 -2
  97. package/src/components/sheet/COMPONENT.md +18 -7
  98. package/src/layout/page/COMPONENT.md +5 -12
  99. package/src/layout/page/examples/screen.module.css +1 -1
  100. package/src/layout/page/examples/screen.tsx +93 -104
  101. package/src/layout/page/examples/sections.tsx +2 -15
  102. package/src/styles/theme-dark.css +1 -1
  103. package/src/styles/theme-light.css +1 -1
  104. package/dist/components/data-table/context.d.ts +0 -14
package/dist/index.js CHANGED
@@ -87,7 +87,7 @@ import {
87
87
  useSidebar,
88
88
  useTokenValue,
89
89
  useUploadQueue
90
- } from "./chunks/chunk-MB4CXSLL.js";
90
+ } from "./chunks/chunk-WYCTXD63.js";
91
91
  import {
92
92
  Avatar,
93
93
  Badge,
@@ -122,7 +122,7 @@ import {
122
122
  tierMappings,
123
123
  tones,
124
124
  variants
125
- } from "./chunks/chunk-OAXJWF3L.js";
125
+ } from "./chunks/chunk-RZNGLELR.js";
126
126
  import {
127
127
  ControlSizeProvider,
128
128
  useOptionalControlSize
@@ -17,6 +17,12 @@ export type FieldCell = {
17
17
  hidden: "wide" | "always";
18
18
  /** The column is required: the field in the cell is required too (`useFieldFrame`). */
19
19
  required: boolean;
20
+ /**
21
+ * The row's action the field holds at its end: a one-column list puts each row's remove button
22
+ * inside its field. A text field (`Input.Wrapper`, `Textarea`) renders it as its last action
23
+ * segment; a list whose field holds none keeps the remove in a column of its own.
24
+ */
25
+ action?: React.ReactNode;
20
26
  };
21
27
  export declare const FieldCellContext: React.Context<FieldCell | null>;
22
28
  export declare function useFieldCell(): FieldCell | null;
@@ -18,4 +18,10 @@ export declare function leaveGuardCapture(onClickCapture?: React.MouseEventHandl
18
18
  * it is `busy` or `dirty` the browser asks before the tab closes or reloads (`beforeunload`).
19
19
  */
20
20
  export declare function useLeaveGuard({ busy, dirty }: LeaveGuardState, ask: (leave: () => void) => void): void;
21
+ /**
22
+ * While `hold` is true the browser asks before the tab closes or reloads (`beforeunload`): a page
23
+ * guard, and a dialog guard (`Modal.Guard`, `Drawer.Guard`, `Sheet.Guard`) whose form is not saved.
24
+ * `released` lets a leave the person already agreed to go without a second prompt.
25
+ */
26
+ export declare function useUnloadGuard(hold: boolean, released?: () => boolean): void;
21
27
  export {};
@@ -10,8 +10,9 @@ export type DialogGuardProps = {
10
10
  };
11
11
  /**
12
12
  * Holds a Modal, Drawer or Sheet from inside its content, where the form state lives: put it anywhere in
13
- * `Content`. Renders nothing but the «discard changes?» confirm when it is asked. Closing from code
14
- * (`open={false}`) is never held.
13
+ * `Content`. Renders nothing but the «discard changes?» confirm when it is asked. While `busy` or
14
+ * `dirty` the browser also asks before the tab closes or reloads, as `Page.Guard` does. Closing from
15
+ * code (`open={false}`) is never held.
15
16
  */
16
17
  export declare function DialogGuard({ busy, dirty }: DialogGuardProps): import("react/jsx-runtime").JSX.Element;
17
18
  export declare namespace DialogGuard {
@@ -148,9 +148,17 @@ export declare namespace DialogTitle {
148
148
  var displayName: string;
149
149
  }
150
150
  export type DialogDescriptionProps = Omit<React.HTMLAttributes<HTMLParagraphElement>, "id"> & {
151
+ /**
152
+ * Its data loads: a `Skeleton` holds the line's place and `aria-busy` is set, so the header never moves; the data fades in.
153
+ */
154
+ loading?: boolean;
151
155
  ref?: React.Ref<HTMLParagraphElement>;
152
156
  };
153
- export declare function DialogDescription({ className, ...rest }: DialogDescriptionProps): import("react/jsx-runtime").JSX.Element;
157
+ /**
158
+ * The line under the title. While its data loads (`loading`) it keeps its place with a Skeleton, so
159
+ * the header never moves when the data arrives; the data then fades in under the same element.
160
+ */
161
+ export declare function DialogDescription({ loading, className, children, ...rest }: DialogDescriptionProps): import("react/jsx-runtime").JSX.Element;
154
162
  export declare namespace DialogDescription {
155
163
  var displayName: string;
156
164
  }
@@ -163,9 +171,10 @@ export type DialogHeaderProps = React.HTMLAttributes<HTMLElement> & {
163
171
  };
164
172
  /**
165
173
  * `[Icon] [Title + Description] [Actions] [close]` in one row. Layout is a CSS grid, so child
166
- * order does not matter; text children stack in the text column.
174
+ * order does not matter; text children stack in the text column. When the row is too narrow for
175
+ * the title beside the actions (a phone), the actions move under the text.
167
176
  */
168
- export declare function DialogHeader({ showClose, className, children, ...rest }: DialogHeaderProps): import("react/jsx-runtime").JSX.Element;
177
+ export declare function DialogHeader({ showClose, className, children, ref, ...rest }: DialogHeaderProps): import("react/jsx-runtime").JSX.Element;
169
178
  export declare namespace DialogHeader {
170
179
  var displayName: string;
171
180
  }
@@ -205,13 +214,13 @@ export type DialogSectionProps = Omit<React.HTMLAttributes<HTMLElement>, "title"
205
214
  * at the end of the heading row, then the content 16 below. Sections stand further apart than the
206
215
  * plain blocks of the body.
207
216
  */
208
- export declare function DialogSection({ label, description, className, children, ...rest }: DialogSectionProps): import("react/jsx-runtime").JSX.Element;
217
+ export declare function DialogSection({ label, description, className, children, ref, ...rest }: DialogSectionProps): import("react/jsx-runtime").JSX.Element;
209
218
  export declare namespace DialogSection {
210
219
  var displayName: string;
211
220
  }
212
221
  export type DialogFooterProps = React.HTMLAttributes<HTMLElement> & {
213
222
  /**
214
- * `fill`: equal-width buttons in one row; `end`: auto width, at the end. Stacked on phones (viewport below 640 px) and in a dialog narrower than 360 px.
223
+ * `fill`: equal-width buttons in one row, wrapping rather than cutting a label; `end`: auto width, at the end. Stacked on phones (viewport below 640 px) and in a dialog narrower than 360 px.
215
224
  */
216
225
  layout?: DialogFooterLayout;
217
226
  ref?: React.Ref<HTMLElement>;
@@ -2,7 +2,7 @@ export type { AppShellFooterProps, AppShellMainProps, AppShellNavProps, AppShell
2
2
  export { AppShell, useAppShellNav } from "./app-shell/AppShell";
3
3
  export type { BottomNavActionProps, BottomNavItemCountProps, BottomNavItemIconProps, BottomNavItemProps, BottomNavLabels, BottomNavRootProps, } from "./bottom-nav/BottomNav";
4
4
  export { BottomNav } from "./bottom-nav/BottomNav";
5
- export type { PageActionsProps, PageBackButtonProps, PageBodyProps, PageCellProps, PageCellRows, PageCellSpan, PageChipsProps, PageDescriptionProps, PageDetailProps, PageGridProps, PageGuardProps, PageHeaderProps, PageHeadingProps, PageLabels, PageListProps, PageMaxWidth, PageRootProps, PageSectionLayout, PageSectionProps, PageSplitProps, PageTitleProps, PageToolsProps, PageTopBarProps, } from "./page/Page";
5
+ export type { PageActionsProps, PageBackButtonProps, PageBodyProps, PageCellProps, PageCellRows, PageCellSpan, PageChipsProps, PageDescriptionProps, PageDetailProps, PageGridProps, PageGuardProps, PageHeaderProps, PageHeadingProps, PageLabels, PageListProps, PageMaxWidth, PageRootProps, PageSectionProps, PageSplitProps, PageTitleProps, PageToolsProps, PageTopBarProps, } from "./page/Page";
6
6
  export { Page } from "./page/Page";
7
7
  export type { SidebarAccountProps, SidebarBrandLogoProps, SidebarBrandProps, SidebarContentProps, SidebarFooterProps, SidebarGroupProps, SidebarHeaderProps, SidebarItemActionProps, SidebarItemCountProps, SidebarItemIconProps, SidebarItemProps, SidebarItemShortcutProps, SidebarLabels, SidebarMode, SidebarRootProps, SidebarSubContentProps, SidebarSubProps, SidebarSubTriggerProps, SidebarToggleProps, } from "./sidebar/Sidebar";
8
8
  export { Sidebar, useSidebar } from "./sidebar/Sidebar";
@@ -151,8 +151,6 @@ declare function PageBody({ className, ...rest }: PageBodyProps): import("react/
151
151
  declare namespace PageBody {
152
152
  var displayName: string;
153
153
  }
154
- /** Where the heading of a section stands: over its content or in a column beside it. */
155
- export type PageSectionLayout = "stack" | "aside";
156
154
  export type PageSectionProps = Omit<React.HTMLAttributes<HTMLElement>, "title"> & {
157
155
  /**
158
156
  * The visible heading (`h2`); it also names the section.
@@ -162,10 +160,6 @@ export type PageSectionProps = Omit<React.HTMLAttributes<HTMLElement>, "title">
162
160
  * One line of context under the heading, in secondary body-s.
163
161
  */
164
162
  description?: React.ReactNode;
165
- /**
166
- * `stack` — the heading over the content; `aside` — a ~16rem heading column beside it (a settings page), above it once the content would get narrower than 30rem.
167
- */
168
- layout?: PageSectionLayout;
169
163
  ref?: React.Ref<HTMLElement>;
170
164
  };
171
165
  /**
@@ -173,7 +167,7 @@ export type PageSectionProps = Omit<React.HTMLAttributes<HTMLElement>, "title">
173
167
  * at its end) over its content. Sections stand further apart than the plain blocks of the body,
174
168
  * so the air shows where a new topic starts.
175
169
  */
176
- declare function PageSection({ label, description, layout, className, children, ...rest }: PageSectionProps): import("react/jsx-runtime").JSX.Element;
170
+ declare function PageSection({ label, description, className, children, ...rest }: PageSectionProps): import("react/jsx-runtime").JSX.Element;
177
171
  declare namespace PageSection {
178
172
  var displayName: string;
179
173
  }
@@ -292,6 +292,29 @@ The same for one value in several units — one heading, the fields in a row, th
292
292
  />
293
293
  ```
294
294
 
295
+ A bar above the table for a single button — the column chooser, an export — wastes a row and splits
296
+ the page's actions ([controls of a table](composition.md#controls-of-a-table)):
297
+
298
+ ```tsx partial
299
+ // bad — a toolbar that holds one control
300
+ <DataTable columns={columns} rows={orders} toolbar={<Button.Root>Колонки</Button.Root>} />
301
+ ```
302
+
303
+ ```tsx good
304
+ // good — the chooser sits in the header row; the page's own actions live in its top bar
305
+ // declare: const orders: { id: string; client: string; total: number }[]
306
+ <DataTable
307
+ columns={[
308
+ { id: "id", header: "Заказ", accessor: "id", hideable: false },
309
+ { id: "client", header: "Клиент", accessor: "client" },
310
+ { id: "total", header: "Сумма", accessor: "total", numeric: true },
311
+ ]}
312
+ rows={orders}
313
+ getRowKey={(row) => row.id}
314
+ columnsMenu
315
+ />
316
+ ```
317
+
295
318
  ## 7b. A top bar out of its groups
296
319
 
297
320
  The bar lays itself out only from its four groups ([the top bar](composition.md#the-top-bar)); a
@@ -295,7 +295,7 @@ export function DeleteProject({ onDelete }: { onDelete: () => Promise<void> }) {
295
295
 
296
296
  Leaving a form with unsaved changes is the same contract: `Modal.Guard` / `Drawer.Guard` inside the
297
297
  content with `dirty` (any dismiss asks «Не сохранять изменения?») and `busy` (nothing closes it while
298
- it saves). A form that is the whole page takes `Page.Guard` (`dirty`, `busy`) inside the page: it
298
+ it saves); while either holds, the browser also asks before the tab closes or reloads. A form that is the whole page takes `Page.Guard` (`dirty`, `busy`) inside the page: it
299
299
  holds a press on `Sidebar.Item`, `BottomNav.Item` and `Breadcrumb.Item` links — router links
300
300
  included — and the browser's own close or reload; never a `beforeunload` or a ConfirmDialog on a
301
301
  crumb of your own.
@@ -86,7 +86,7 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
86
86
  - A work screen (a list, a board, a chat, a record) titles itself in `<Page.TopBar>`: `<Page.Heading>` with `<Page.Title>` + `<Page.Description>` carrying a line of data («128 в работе · 6 ждут оплаты») — not a sentence about the page.
87
87
  - The bar holds only its four groups: `<Page.Heading>`, `<Page.Tools aria-label>` (sections, `<SmartFilter.Toolbar />`, the view — in reading order), `<Page.Actions>`, `<Page.Chips>` (`<SmartFilter.Chips />`) — not a hand-made flex row of title, tabs, search and buttons ([the top bar](composition.md#the-top-bar)).
88
88
  - A nested record: `<Breadcrumb.Root>` first in `<Page.Heading>`, then `<Page.Title>` with the record's name — the path ends in the title on one line; not a title row under the path, not a breadcrumb in the body. With a router each crumb is `<Breadcrumb.Item asChild>` around its `Link`.
89
- - A line of data that loads after the page opens: `<Page.Description loading={!summary}>` from the first render — not a Description mounted when the data arrives, which moves the title.
89
+ - A line of data that loads after the page opens: `<Page.Description loading={!summary}>` from the first render (`Drawer.Description` / `Modal.Description` alike) — not a Description mounted when the data arrives, which moves the title, nor a `Skeleton` placed in it by hand.
90
90
  - A page that edits a record: `<Page.Guard dirty={changed} busy={saving} />` inside `Page.Root` — the Sidebar, BottomNav and path links ask before the changes are lost; not your own `beforeunload` or a confirm wired to each link.
91
91
  - Every button of the page goes into one `<Page.Actions>`, secondary first, the primary last, each with a `<Button.Icon>` beside its label — not buttons split between two places, not a button with only text.
92
92
  - A filtered page: `<SmartFilter.Root>` wraps `<Page.Root>` — it draws nothing; not `SmartFilter.Root` inside the bar.
@@ -97,6 +97,7 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
97
97
 
98
98
  - `Card.Root` only for a bounded block with its own title: a KPI tile (`variant="stat-trend"`), a settings panel (`variant="panel"`), side facts — not around a whole section, a form field or a page.
99
99
  - Never a `DataTable` inside a Card — the table is already a filled block; a card in a card only sparingly ([the rule](foundations.md#card-in-a-card)).
100
+ - A titled panel (a settings section, a group of a long form) is `<Card.Root>` with `<Card.Header>` (`<Card.Title as="h2">`, `<Card.Description>`) stacked in the flow — never a heading column beside its panel ([a titled block](composition.md#4-surfaces)).
100
101
  - Section without a card: `<Page.Section label="…">` + the content (a link or button beside the heading in `Page.Actions`) — not a hand-made `<section>` + `Typography as="h2"` + own gap.
101
102
  - The tiles of a screen (KPI cards, charts, panels): one `<Page.Grid>` with a `<Page.Cell span>` per tile — not a grid per row (the rows would stand 24 apart instead of 16). A chart beside a panel is an 8 and a 4 written next to each other — the grid pairs them at mid widths; not 6 + 6 to dodge a hole.
102
103
  - A KPI tile whose figure loads: `<Card.Root variant="stat-trend" loading>` with its parts mounted — not Skeletons placed in `Card.Value` by hand.
@@ -108,6 +109,7 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
108
109
  ## Tables
109
110
 
110
111
  - A list page is a `<DataTable columns rows getRowKey>` under the page's `<SmartFilter.Root fields value onValueChange>` with `<SmartFilter.Toolbar />` in its `Page.Tools` and `<SmartFilter.Chips />` in its `Page.Chips` ([where the filters go](composition.md#where-the-filters-go)); filter rows with `matchesSmartFilter` — not a search Input and a row of Selects above the table.
112
+ - The column chooser is `<DataTable columnsMenu>` in the header row; `toolbar` only for several controls of the table itself (bulk actions, a section table's own search and filters) ([controls of a table](composition.md#controls-of-a-table)) — never a toolbar for one button.
111
113
  - Search over a table or a list page is always SmartFilter, even with no filter fields yet (`fields={[]}` is a plain search that grows into filters). A search `Input` only outside a filtered list: inside a picker, a settings section, a panel.
112
114
  - Paging is built in: `paging="pages"` (default) with `pageSize`, `paging="infinite"` for feeds, `paging="none"` for short lists — not a separate `<Pagination>` under a DataTable.
113
115
  - Long lists are virtualized inside the kit: pass the whole array to `DataTable` (with `scrollHeight` or `paging="infinite"`; over 100 rows — grouped, `onReorder` and `renderExpanded` bodies render every row, keep those short or paged), `TagSelect options`, `Chat.Messages items`, `Gantt rows` — never slice it by hand, never wrap a kit list in react-window / react-virtual or your own windowing. Big remote data is still paged or loaded on scroll by the server (`source="server"`, `onLoadMore`).
@@ -120,6 +122,8 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
120
122
  - A list row with controls of its own (a Switch, a ⋯ menu): `List.ItemActions` inside `List.Item`, a stage colour as `Badge.Dot color` in `List.ItemIcon` — not a control inside the row's button, not a row rebuilt as a `div`.
121
123
  - Rows people order by hand: `onReorder(id, beforeId)` (apply it with `moveBefore`, return the save's Promise) — not a Dnd list rebuilt next to the table.
122
124
  - A short list of records people order by hand (stages, priorities) is `<List.Root onReorder>` with `List.Item value` — not up / down buttons or a hand-made `Dnd.Sortable`.
125
+ - A toggle in a reorderable row leads it (`List.ItemActions side="start"`) and a plain settings row ends with it ([list rows](composition.md#list-rows)) — never a Switch between the label and the grip.
126
+ - The grip ends its row: a `Dnd.Handle` is the last child of its item, after the row's own controls, and a locked row's lock takes the same place (List, FieldList and Kanban place theirs there) — not before the row's text.
123
127
  - Groups that fold: pass `defaultCollapsedGroups={[]}` (or the controlled `collapsedGroups` + `onCollapsedGroupsChange`) — not your own button in `renderGroupHeader` or filtering `rows`. One heatmap over several columns: the same `scale: { min, max }` on each.
124
128
  - A trend in a table row is `<Sparkline layout="compact">` in the column's `cell` (a column `width`, series built once, not inline, the same `min` / `max` on every row when rows compare by size); the number itself goes in its own column. A missing day is `value: null`, never `0`.
125
129
  - Properties of one record: `<DescriptionList.Root>` of `DescriptionList.Item` — not a hand-made grid of label / value text, not a one-row DataTable.
@@ -160,7 +164,7 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
160
164
  - A destructive confirm is `<ConfirmDialog.Root title description confirmLabel onConfirm={remove}>` with a `ConfirmDialog.Trigger` — not a Modal assembled as a confirm with your own pending state; a reversible delete offers «Отменить» in the notification instead.
161
165
  - Overlays close smoothly only if they stay mounted: render the `Root` always and drive visibility with `open` alone — never `{open && <Drawer.Root>}`, never a changing `key` on the Root. The kit keeps the last content on screen while the exit plays.
162
166
  - Placement of a menu, popover, select or picker: leave the defaults — `side="bottom"`, `align="auto"`: it opens below the trigger, lines up with the trigger edge that has the room (a «⋯» at the end of a row grows toward the start) and flips to the top by itself. Never write `side` or `align` to restate that; set `align` only when the panel must line up with one edge of a block whatever room there is. A Popover under a wide anchor that must never be narrower than it: `<Popover.Content width="min-trigger">`.
163
- - While a request runs: `<Modal.Guard busy={saving} />` inside the content (`Drawer.Guard`, `Sheet.Guard` alike) — Escape, a press outside, × and `Close` are held, × and `Close` disabled; add `dirty` to ask before unsaved changes are lost. Not `closeOnEscape={false}` or `showClose={false}` by hand.
167
+ - While a request runs: `<Modal.Guard busy={saving} />` inside the content (`Drawer.Guard`, `Sheet.Guard` alike) — Escape, a press outside, × and `Close` are held, × and `Close` disabled; add `dirty` to ask before unsaved changes are lost; both also hold the browser's tab close. Not `closeOnEscape={false}` or `showClose={false}` by hand, not your own `beforeunload`.
164
168
 
165
169
  ## Motion — it comes with the components
166
170
 
@@ -204,7 +208,7 @@ Full rules: [responsive.md](responsive.md).
204
208
 
205
209
  - Rearrange, never hide: every desktop function is on the phone, in another place or a menu — not `display: none` below a breakpoint.
206
210
  - Breakpoints only in [their scope](responsive.md#breakpoints); inside a page — [intrinsic grids](responsive.md#intrinsic-grids) and container queries.
207
- - A wide table scrolls inside itself; offer `DataTable.ColumnsMenu` in its `toolbar` — never drop columns by breakpoint, never hand-build the chooser.
211
+ - A wide table scrolls inside itself; offer the column chooser with `columnsMenu` — never drop columns by breakpoint, never hand-build the chooser.
208
212
  - A row that opens a record page is `getRowHref` (a real link: ⌘-click, new tab), a row that opens a drawer is `onRowClick` — never `navigate()` in `onRowClick`, never your own `event.target` checks for buttons in the row.
209
213
  - A list + detail screen is `Page.Split` (`Page.List`, `Page.Detail`) in a `Page.Root fill` page, its records a `List` (`current` on the open one) — never ghost Buttons with a soft current; a full-height table is `scrollHeight="fill"` — never a height computed from `100vh`.
210
214
  - App sections on phones: `BottomNav` in `AppShell.Footer` — not in Main, not `position: fixed`, no own media query: the shell hides the footer while the Sidebar rail is on screen; a short task from the bottom edge: `Sheet` — Select, Dropdown, Popover and Modal become sheets below 640 by themselves.
@@ -11,6 +11,8 @@ checks.
11
11
  - [ ] Every button of the page is in one `Page.Actions`, the primary last, each with a `Button.Icon` ([the top bar](composition.md#the-top-bar)).
12
12
  - [ ] `SmartFilter.Root` wraps `Page.Root`; its toolbar is in `Page.Tools`, its chips in `Page.Chips` ([the top bar](composition.md#the-top-bar)).
13
13
  - [ ] A Card only for a standalone titled block — never around a table, a single field or the page ([surfaces](composition.md#4-surfaces)).
14
+ - [ ] A titled panel carries its heading in its `Card.Header` and stacks in the flow; no heading column beside a panel ([surfaces](composition.md#4-surfaces)).
15
+ - [ ] The column chooser is `columnsMenu` in the table's header row; a DataTable `toolbar` holds two or more table controls or does not exist ([controls of a table](composition.md#controls-of-a-table)).
14
16
 
15
17
  ## UX ([ux.md](ux.md))
16
18
  - [ ] I wrote the five-line brief before the JSX ([brief](ux.md#1-brief-before-code)).
@@ -35,6 +37,7 @@ checks.
35
37
  ## Sizes and alignment
36
38
  - [ ] Default size `m`; a tier changes for a whole region, never for one control ([the host tier](api-contract.md#size-and-the-host-tier)).
37
39
  - [ ] Every row of controls uses one size and its heights line up ([density](composition.md#5-density-and-size)).
40
+ - [ ] A reorderable row's toggle leads it and the grip ends it; a plain settings row ends with its toggle ([list rows](composition.md#list-rows)).
38
41
  - [ ] Controls in table cells are one tier below the table; no CSS sets row or cell heights ([size axis](foundations.md#3-size-axis)).
39
42
  - [ ] Icon-only buttons are square (round only for media and transfers) and named ([icons](api-contract.md#icons)).
40
43
 
@@ -59,6 +62,7 @@ checks.
59
62
 
60
63
  ## Hierarchy
61
64
  - [ ] One primary action per area, last in its row; the rest `soft` / `ghost` / `outline` neutral ([actions](composition.md#6-actions)).
65
+ - [ ] A dialog footer holds Cancel and the primary only; deleting the edited record is the last item of the header ⋯ menu ([actions](composition.md#6-actions)).
62
66
  - [ ] The same action has the same label and variant everywhere on the screen ([secondary actions](api-contract.md#secondary-actions)).
63
67
 
64
68
  ## States
@@ -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` |
@@ -234,6 +234,39 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
234
234
  - **Card when** the block stands alone and has its own title: a KPI tile, a settings panel, facts in a
235
235
  side column, a chart. **No card when** the block is the only thing in its region: a section heading
236
236
  and the content are enough.
237
+ - **A titled block stands in the flow, its heading inside it.** A panel with a title and a line of
238
+ description (a settings section, a group of a long form) is a `Card.Root` whose `Card.Header`
239
+ holds `Card.Title as="h2"` and `Card.Description`; the cards stack 24 apart. Never a heading column
240
+ beside its panel: it takes a third of the width from the fields and breaks the reading line.
241
+
242
+ ```tsx partial
243
+ // bad — the heading in a column on the left, the panel on the right
244
+ <div className={styles.headingColumnAndPanel}>
245
+ <div>
246
+ <Typography as="h2" variant="title-m">Компания</Typography>
247
+ <Typography tone="secondary">Попадают в счета и акты.</Typography>
248
+ </div>
249
+ <Card.Root variant="panel">…</Card.Root>
250
+ </div>
251
+ ```
252
+
253
+ ```tsx good
254
+ // good — the card carries its own heading, stacked in the flow
255
+ <Card.Root variant="panel">
256
+ <Card.Header>
257
+ <Card.Title as="h2">Компания</Card.Title>
258
+ <Card.Description>Попадают в счета и акты.</Card.Description>
259
+ </Card.Header>
260
+ <Card.Body>
261
+ <Input.Root label="Название">
262
+ <Input.Wrapper>
263
+ <Input.Field name="name" />
264
+ </Input.Wrapper>
265
+ </Input.Root>
266
+ </Card.Body>
267
+ </Card.Root>
268
+ ```
269
+
237
270
  - `DataTable` is already a filled block — never inside a Card; a card in a card only as
238
271
  [the next layer, sparingly](foundations.md#card-in-a-card). No borders around blocks; `Divider`
239
272
  only inside a block (list rows, a form and its footer).
@@ -257,6 +290,9 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
257
290
  table cell.
258
291
  - 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
292
  `Modal.Footer` / `Drawer.Footer`; row actions in the last column; bulk actions in the table toolbar.
293
+ - A dialog footer is the dialog's answer: Cancel and the primary. Deleting the record the dialog
294
+ edits is not a third footer button: it is the last `tone="danger"` item of a ⋯ `Dropdown` in
295
+ `Modal.Actions` / `Drawer.Actions`, confirmed by a `ConfirmDialog`.
260
296
  - An empty state's action replaces the header's primary, it does not duplicate it.
261
297
  - Destructive: `tone="danger"`; a standalone trigger is `outline`, in a menu it is the last item, the
262
298
  confirm is a `ConfirmDialog` (its `onConfirm` returns the request's Promise).
@@ -268,13 +304,17 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
268
304
  - Labels above every field (`label`), placeholder is an example value, help in `hint`, problems in
269
305
  `error` — after blur or on submit. Mark the minority (`required` or `optional`).
270
306
  - 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`
307
+ the column headings once above the rows, small attributes (an icon, then its color) in `compact`
272
308
  columns. Never a label in every row, never numbered labels, never one record split over lines.
309
+ FieldList draws every row's actions itself — the remove inside the field of a one-column list, a
310
+ pinned row's remove off with its reason (`labels.lockedReason`) — so never add your own.
273
311
  - One value in several units is one row of fields under one heading, each unit in its field's
274
312
  `Input.InlineAffix`; a base value with overrides is one `FieldList` with the base pinned first.
275
313
  The shape of the data decides the layout: [ux.md](ux.md#2-the-shape-of-the-data-decides-the-layout).
276
314
  - 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
315
+ `FieldList renderTotals`, a short number in a `width: "xs"` column. A number with its unit
316
+ (`inputMode="decimal"` and an end `Input.InlineAffix`) ends at the field's end, and its column's
317
+ total follows it there by itself. Return the save's promise from
278
318
  its `onValueChange` when each edit saves at once.
279
319
  - Fields 20 apart; related short fields side by side in an
280
320
  [intrinsic grid](responsive.md#intrinsic-grids) with `align-items: start`
@@ -300,9 +340,18 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
300
340
  `SmartFilter.Toolbar` in the bar's `Page.Tools`, `SmartFilter.Chips` in its `Page.Chips`
301
341
  ([the list page](#list-page)).
302
342
  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).
343
+
344
+ ### Controls of a table
345
+
346
+ - The column chooser is part of the table: `columnsMenu` puts it at the end of the header row — in
347
+ the head cell of a trailing row-actions column (`hideHeader`), else in a narrow cell of its own —
348
+ and it sticks with `stickyLastColumn`. Give the key column and the actions column `hideable: false`.
349
+ - The DataTable `toolbar` holds several controls of the table itself: a bulk bar of the selected
350
+ rows, or the search and filters of a table that is not the page's main region (a table in a
351
+ section of a record, in a Drawer).
352
+ - One control never gets a bar of its own. A lone «Экспорт» or «Создать» is the page's
353
+ (`Page.Actions`); the period, the view switch, the search and filters of the page go to
354
+ `Page.Tools`; a count or a total of the list is data in `Page.Description`.
306
355
 
307
356
  ### Tables
308
357
 
@@ -321,6 +370,15 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
321
370
  - Phones: the table scrolls inside itself, `stickyFirstColumn` keeps the key column; never let the page
322
371
  scroll sideways. Short lists of objects can be `Card variant="list"` instead of a table.
323
372
 
373
+ ### List rows
374
+
375
+ - A row's own controls stand beside its element in `List.ItemActions`, one tier below the row.
376
+ - A plain settings row reads label first and ends with its toggle (the platform convention).
377
+ - A row people reorder leads with its toggle — `<List.ItemActions side="start">`, or the Switch as the
378
+ first child of a `Dnd.SortableItem` — so the on / off state reads first, like a checkbox, and the
379
+ grip keeps the end edge ([reorder.tsx](reference/components/list/examples/reorder.tsx)).
380
+ - Never a toggle between the label and the grip: «Почта … [Switch] [grip]» reads as two handles.
381
+
324
382
  ## 9. Feedback
325
383
 
326
384
  | Situation | Component |
@@ -458,7 +516,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
458
516
  - `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`.
459
517
  - 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
518
  - 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.
519
+ - 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
520
  - 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
521
 
464
522
  ### Detail page
@@ -478,7 +536,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
478
536
  [page/examples/screen.tsx](reference/layout/page/examples/screen.tsx)
479
537
 
480
538
  - 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).
539
+ - 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
540
  - 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
541
  - 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
542
  - An error comes through the field's `error` after submit, not while typing; a format hint goes in `hint`.
@@ -502,10 +560,10 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
502
560
 
503
561
  [field-list/examples/screen.tsx](reference/components/field-list/examples/screen.tsx)
504
562
 
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.
563
+ - 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
564
  - 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
565
  - 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.
566
+ - 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
567
  - 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
568
  - 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
569
  - 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 +624,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
566
624
  - 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
625
  - Stage totals are in `renderColumnMeta` («2 сделки · 3 575 000 ₽»), the stage color is the column's `color`.
568
626
  - 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.
627
+ - 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
628
 
571
629
  ### App frame
572
630
 
@@ -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)