prime-ui-kit 0.13.0 → 0.14.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 (215) hide show
  1. package/README.md +7 -6
  2. package/dist/chunks/{chunk-ZSJMNA46.js → chunk-P6ZO37JM.js} +1 -1
  3. package/dist/chunks/{chunk-Z3WKKAS6.js → chunk-SYEX7OVT.js} +3572 -2865
  4. package/dist/chunks/{chunk-E6SQBSDF.js → chunk-WXKRUVSE.js} +199 -162
  5. package/dist/color-picker.js +2 -2
  6. package/dist/components/banner/Banner.d.ts +5 -2
  7. package/dist/components/chart-frame/ChartFrame.d.ts +19 -6
  8. package/dist/components/chat/Chat.d.ts +2 -2
  9. package/dist/components/chat/ChatPinned.d.ts +2 -2
  10. package/dist/components/chat/MessageList.d.ts +1 -1
  11. package/dist/components/chat/chatContext.d.ts +1 -1
  12. package/dist/components/field-list/FieldList.d.ts +90 -0
  13. package/dist/components/index.d.ts +3 -1
  14. package/dist/components/input/Input.d.ts +2 -2
  15. package/dist/components/kanban/Kanban.d.ts +6 -3
  16. package/dist/components/page-content/PageContent.d.ts +55 -4
  17. package/dist/components/sparkline/Sparkline.d.ts +64 -15
  18. package/dist/components/sparkline/geometry.d.ts +26 -0
  19. package/dist/components/tooltip/Tooltip.d.ts +5 -2
  20. package/dist/hooks/usePosition.d.ts +15 -4
  21. package/dist/index.css +1 -1
  22. package/dist/index.js +4 -2
  23. package/dist/internal/ChartTooltip.d.ts +26 -0
  24. package/dist/internal/FieldAction.d.ts +6 -7
  25. package/dist/internal/fieldCell.d.ts +21 -0
  26. package/dist/internal/messageList.d.ts +2 -2
  27. package/dist/internal/palette.d.ts +7 -0
  28. package/dist/skill/SKILL.md +24 -14
  29. package/dist/skill/anti-slop.md +77 -4
  30. package/dist/skill/cheatsheet.md +24 -5
  31. package/dist/skill/checklist.md +19 -1
  32. package/dist/skill/choosing.md +10 -5
  33. package/dist/skill/components.md +4 -3
  34. package/dist/skill/composition.md +28 -15
  35. package/dist/skill/foundations.md +8 -6
  36. package/dist/skill/layouts.md +6 -6
  37. package/dist/skill/patterns/app-shell.module.css +1 -8
  38. package/dist/skill/patterns/app-shell.tsx +11 -9
  39. package/dist/skill/patterns/chat.tsx +23 -5
  40. package/dist/skill/patterns/dashboard.module.css +1 -31
  41. package/dist/skill/patterns/dashboard.tsx +150 -136
  42. package/dist/skill/patterns/detail-page.module.css +0 -3
  43. package/dist/skill/patterns/detail-page.tsx +4 -7
  44. package/dist/skill/patterns/form-drawer.tsx +2 -2
  45. package/dist/skill/patterns/kanban-board.tsx +4 -2
  46. package/dist/skill/patterns/list-page.tsx +2 -2
  47. package/dist/skill/patterns/media-library.module.css +0 -22
  48. package/dist/skill/patterns/media-library.tsx +17 -18
  49. package/dist/skill/patterns/record-editor.module.css +18 -0
  50. package/dist/skill/patterns/record-editor.tsx +356 -0
  51. package/dist/skill/patterns/screen-states.module.css +3 -17
  52. package/dist/skill/patterns/screen-states.tsx +70 -69
  53. package/dist/skill/patterns/settings-page.module.css +1 -21
  54. package/dist/skill/patterns/settings-page.tsx +58 -79
  55. package/dist/skill/reference/components/avatar/COMPONENT.md +4 -4
  56. package/dist/skill/reference/components/avatar/examples/presence.tsx +1 -1
  57. package/dist/skill/reference/components/avatar/examples/states.tsx +1 -1
  58. package/dist/skill/reference/components/banner/COMPONENT.md +6 -6
  59. package/dist/skill/reference/components/banner/examples/dismissible.tsx +22 -11
  60. package/dist/skill/reference/components/banner/examples/examples.module.css +3 -3
  61. package/dist/skill/reference/components/banner/examples/narrow.tsx +1 -1
  62. package/dist/skill/reference/components/breadcrumb/COMPONENT.md +1 -1
  63. package/dist/skill/reference/components/breadcrumb/examples/overflow.tsx +1 -1
  64. package/dist/skill/reference/components/card/COMPONENT.md +5 -4
  65. package/dist/skill/reference/components/card/examples/examples.module.css +0 -28
  66. package/dist/skill/reference/components/card/examples/kpi-media.tsx +13 -11
  67. package/dist/skill/reference/components/card/examples/narrow.tsx +1 -1
  68. package/dist/skill/reference/components/card/examples/panel-chart.tsx +45 -22
  69. package/dist/skill/reference/components/chart-frame/COMPONENT.md +10 -7
  70. package/dist/skill/reference/components/chart-frame/examples/tooltip.tsx +20 -24
  71. package/dist/skill/reference/components/chat/COMPONENT.md +3 -3
  72. package/dist/skill/reference/components/chat/examples/search.tsx +1 -1
  73. package/dist/skill/reference/components/chat-list/COMPONENT.md +1 -1
  74. package/dist/skill/reference/components/checkbox/examples/indicator.tsx +24 -2
  75. package/dist/skill/reference/components/color-picker/COMPONENT.md +2 -2
  76. package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
  77. package/dist/skill/reference/components/datepicker/COMPONENT.md +1 -0
  78. package/dist/skill/reference/components/dnd/COMPONENT.md +3 -3
  79. package/dist/skill/reference/components/field-list/COMPONENT.md +164 -0
  80. package/dist/skill/reference/components/field-list/examples/base-row.tsx +76 -0
  81. package/dist/skill/reference/components/field-list/examples/controlled.tsx +72 -0
  82. package/dist/skill/reference/components/field-list/examples/errors.tsx +85 -0
  83. package/dist/skill/reference/components/field-list/examples/examples.module.css +34 -0
  84. package/dist/skill/reference/components/field-list/examples/limits.tsx +55 -0
  85. package/dist/skill/reference/components/field-list/examples/locked.tsx +139 -0
  86. package/dist/skill/reference/components/field-list/examples/narrow.tsx +68 -0
  87. package/dist/skill/reference/components/field-list/examples/overview.tsx +66 -0
  88. package/dist/skill/reference/components/field-list/examples/reorder.tsx +42 -0
  89. package/dist/skill/reference/components/field-list/examples/sizes.tsx +42 -0
  90. package/dist/skill/reference/components/field-list/examples/states.tsx +51 -0
  91. package/dist/skill/reference/components/input/examples/examples.module.css +2 -7
  92. package/dist/skill/reference/components/kanban/COMPONENT.md +13 -9
  93. package/dist/skill/reference/components/kanban/examples/limits.tsx +2 -1
  94. package/dist/skill/reference/components/kanban/examples/variants.tsx +16 -8
  95. package/dist/skill/reference/components/list/COMPONENT.md +1 -1
  96. package/dist/skill/reference/components/media-viewer/COMPONENT.md +2 -2
  97. package/dist/skill/reference/components/message/COMPONENT.md +2 -2
  98. package/dist/skill/reference/components/notification/COMPONENT.md +1 -0
  99. package/dist/skill/reference/components/page-content/COMPONENT.md +70 -14
  100. package/dist/skill/reference/components/page-content/examples/grid.tsx +107 -0
  101. package/dist/skill/reference/components/page-content/examples/overview.tsx +2 -2
  102. package/dist/skill/reference/components/page-content/examples/sections.tsx +81 -0
  103. package/dist/skill/reference/components/page-toolbar/COMPONENT.md +1 -1
  104. package/dist/skill/reference/components/segmented-control/COMPONENT.md +1 -0
  105. package/dist/skill/reference/components/select/COMPONENT.md +4 -2
  106. package/dist/skill/reference/components/select/examples/icon-only.tsx +38 -0
  107. package/dist/skill/reference/components/sparkline/COMPONENT.md +83 -30
  108. package/dist/skill/reference/components/sparkline/examples/compact.tsx +1 -1
  109. package/dist/skill/reference/components/sparkline/examples/curve.tsx +28 -0
  110. package/dist/skill/reference/components/sparkline/examples/gaps.tsx +33 -0
  111. package/dist/skill/reference/components/sparkline/examples/overview.tsx +11 -2
  112. package/dist/skill/reference/components/sparkline/examples/period.tsx +7 -2
  113. package/dist/skill/reference/components/sparkline/examples/reference.tsx +37 -0
  114. package/dist/skill/reference/components/sparkline/examples/variants.tsx +2 -2
  115. package/dist/skill/reference/components/tabs/examples/examples.module.css +0 -6
  116. package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +11 -13
  117. package/dist/skill/reference/components/timeline/examples/examples.module.css +0 -8
  118. package/dist/skill/reference/components/timeline/examples/narrow.tsx +2 -2
  119. package/dist/skill/reference/components/timeline/examples/sizes.tsx +3 -3
  120. package/dist/skill/reference/components/timeline/examples/variants.tsx +4 -4
  121. package/dist/skill/reference/components/tooltip/COMPONENT.md +6 -4
  122. package/dist/skill/reference/components/tooltip/examples/placement.tsx +2 -1
  123. package/dist/skill/reference/components/video-note/COMPONENT.md +1 -1
  124. package/dist/skill/reference/components/voice-player/COMPONENT.md +1 -1
  125. package/dist/skill/reference/layout/app-header/examples/in-app-shell.tsx +2 -2
  126. package/dist/skill/reference/layout/app-shell/COMPONENT.md +1 -1
  127. package/dist/skill/reference/layout/app-shell/examples/footer.tsx +2 -2
  128. package/dist/skill/reference/layout/app-shell/examples/overview.tsx +2 -2
  129. package/dist/skill/reference/layout/app-shell/examples/template.tsx +2 -2
  130. package/dist/skill/reference/layout/bottom-nav/examples/floating-action.tsx +2 -2
  131. package/dist/skill/reference/layout/bottom-nav/examples/floating.tsx +2 -2
  132. package/dist/skill/reference/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
  133. package/dist/skill/responsive.md +5 -3
  134. package/dist/skill/ux.md +145 -0
  135. package/dist/tokens/semantic.d.ts +2 -0
  136. package/package.json +1 -1
  137. package/src/components/avatar/COMPONENT.md +4 -4
  138. package/src/components/avatar/examples/presence.tsx +1 -1
  139. package/src/components/avatar/examples/states.tsx +1 -1
  140. package/src/components/banner/COMPONENT.md +6 -6
  141. package/src/components/banner/examples/dismissible.tsx +22 -11
  142. package/src/components/banner/examples/examples.module.css +3 -3
  143. package/src/components/banner/examples/narrow.tsx +1 -1
  144. package/src/components/breadcrumb/COMPONENT.md +1 -1
  145. package/src/components/breadcrumb/examples/overflow.tsx +1 -1
  146. package/src/components/card/COMPONENT.md +5 -4
  147. package/src/components/card/examples/examples.module.css +0 -28
  148. package/src/components/card/examples/kpi-media.tsx +13 -11
  149. package/src/components/card/examples/narrow.tsx +1 -1
  150. package/src/components/card/examples/panel-chart.tsx +45 -22
  151. package/src/components/chart-frame/COMPONENT.md +10 -7
  152. package/src/components/chart-frame/examples/tooltip.tsx +20 -24
  153. package/src/components/chat/COMPONENT.md +3 -3
  154. package/src/components/chat/examples/search.tsx +1 -1
  155. package/src/components/chat-list/COMPONENT.md +1 -1
  156. package/src/components/checkbox/examples/indicator.tsx +24 -2
  157. package/src/components/color-picker/COMPONENT.md +2 -2
  158. package/src/components/color-swatches/COMPONENT.md +1 -0
  159. package/src/components/datepicker/COMPONENT.md +1 -0
  160. package/src/components/dnd/COMPONENT.md +3 -3
  161. package/src/components/field-list/COMPONENT.md +164 -0
  162. package/src/components/field-list/examples/base-row.tsx +76 -0
  163. package/src/components/field-list/examples/controlled.tsx +72 -0
  164. package/src/components/field-list/examples/errors.tsx +85 -0
  165. package/src/components/field-list/examples/examples.module.css +34 -0
  166. package/src/components/field-list/examples/limits.tsx +55 -0
  167. package/src/components/field-list/examples/locked.tsx +139 -0
  168. package/src/components/field-list/examples/narrow.tsx +68 -0
  169. package/src/components/field-list/examples/overview.tsx +66 -0
  170. package/src/components/field-list/examples/reorder.tsx +42 -0
  171. package/src/components/field-list/examples/sizes.tsx +42 -0
  172. package/src/components/field-list/examples/states.tsx +51 -0
  173. package/src/components/input/examples/examples.module.css +2 -7
  174. package/src/components/kanban/COMPONENT.md +13 -9
  175. package/src/components/kanban/examples/limits.tsx +2 -1
  176. package/src/components/kanban/examples/variants.tsx +16 -8
  177. package/src/components/list/COMPONENT.md +1 -1
  178. package/src/components/media-viewer/COMPONENT.md +2 -2
  179. package/src/components/message/COMPONENT.md +2 -2
  180. package/src/components/notification/COMPONENT.md +1 -0
  181. package/src/components/page-content/COMPONENT.md +70 -14
  182. package/src/components/page-content/examples/grid.tsx +107 -0
  183. package/src/components/page-content/examples/overview.tsx +2 -2
  184. package/src/components/page-content/examples/sections.tsx +81 -0
  185. package/src/components/page-toolbar/COMPONENT.md +1 -1
  186. package/src/components/segmented-control/COMPONENT.md +1 -0
  187. package/src/components/select/COMPONENT.md +4 -2
  188. package/src/components/select/examples/icon-only.tsx +38 -0
  189. package/src/components/sparkline/COMPONENT.md +83 -30
  190. package/src/components/sparkline/examples/compact.tsx +1 -1
  191. package/src/components/sparkline/examples/curve.tsx +28 -0
  192. package/src/components/sparkline/examples/gaps.tsx +33 -0
  193. package/src/components/sparkline/examples/overview.tsx +11 -2
  194. package/src/components/sparkline/examples/period.tsx +7 -2
  195. package/src/components/sparkline/examples/reference.tsx +37 -0
  196. package/src/components/sparkline/examples/variants.tsx +2 -2
  197. package/src/components/tabs/examples/examples.module.css +0 -6
  198. package/src/components/tabs/examples/in-drawer.tsx +11 -13
  199. package/src/components/timeline/examples/examples.module.css +0 -8
  200. package/src/components/timeline/examples/narrow.tsx +2 -2
  201. package/src/components/timeline/examples/sizes.tsx +3 -3
  202. package/src/components/timeline/examples/variants.tsx +4 -4
  203. package/src/components/tooltip/COMPONENT.md +6 -4
  204. package/src/components/tooltip/examples/placement.tsx +2 -1
  205. package/src/components/video-note/COMPONENT.md +1 -1
  206. package/src/components/voice-player/COMPONENT.md +1 -1
  207. package/src/layout/app-header/examples/in-app-shell.tsx +2 -2
  208. package/src/layout/app-shell/COMPONENT.md +1 -1
  209. package/src/layout/app-shell/examples/footer.tsx +2 -2
  210. package/src/layout/app-shell/examples/overview.tsx +2 -2
  211. package/src/layout/app-shell/examples/template.tsx +2 -2
  212. package/src/layout/bottom-nav/examples/floating-action.tsx +2 -2
  213. package/src/layout/bottom-nav/examples/floating.tsx +2 -2
  214. package/src/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
  215. package/src/styles/theme-light.css +1 -0
@@ -15,10 +15,11 @@ is, and is shown live with its code in the playground (category «Компози
15
15
  | One record | [detail-page.tsx](patterns/detail-page.tsx) | Breadcrumb + status in the header, actions by weight, main column + side column of Cards, Timeline |
16
16
  | Settings | [settings-page.tsx](patterns/settings-page.tsx) | heading column + panel per section, form with its own save, Switches that apply at once, danger zone with a confirm |
17
17
  | Create in a side panel | [form-drawer.tsx](patterns/form-drawer.tsx) | Drawer form: groups, errors on submit, focus on the first invalid field, loading, toast |
18
+ | Record editor | [record-editor.tsx](patterns/record-editor.tsx) | a plan card edited on a page: name and description, specs in a `FieldList` (icon-only `Select` and `ColorPresets` in compact columns, pinned rows, reorder), selling points as one reorderable column, 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 |
18
19
  | Dashboard | [dashboard.tsx](patterns/dashboard.tsx) | period switch, 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 |
19
20
  | Screen states | [screen-states.tsx](patterns/screen-states.tsx) | page error in a Banner with retry, region loading with a Skeleton, Crossfade between states, table error in place, first-run EmptyPage |
20
21
  | Messenger | [chat.tsx](patterns/chat.tsx) | PageContent.Split of a `ChatList` (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.Pinned`, `Chat.Messages` from `items` + a stable `renderMessage` (dates, runs, the unread band and service events from the data, history loaded above with `hasOlder` / `onLoadOlder`), every kind of Message (photos, file, contact, voice in a VoicePlayer with a transcript, round video), reactions, reply and edit from the menu through `Composer.Context value` (`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`, selection with `Chat.SelectionBar`, delivery ticks, the Composer in `Chat.Footer` |
21
- | Sales pipeline board | [kanban-board.tsx](patterns/kanban-board.tsx) | header with one primary action, PageToolbar with SmartFilter search and a board / list SegmentedControl, Crossfade between a Kanban and a DataTable of the same deals, column totals in `renderColumnMeta`, moves saved through an `onValueChange` promise that the server can refuse, movable stages with locked first and last, the deal in a Drawer with a DescriptionList and stage actions, skeleton first load |
22
+ | Sales pipeline board | [kanban-board.tsx](patterns/kanban-board.tsx) | header with one primary action, PageToolbar with SmartFilter search and a board / list SegmentedControl, Crossfade between a `plain` Kanban (stages without wells, deal cards on the page) and a DataTable of the same deals, column totals in `renderColumnMeta`, moves saved through an `onValueChange` promise that the server can refuse, movable stages with locked first and last, the deal in a Drawer with a DescriptionList and stage actions, skeleton first load |
22
23
  | App frame for desktop and phone (root layout) | [app-shell.tsx](patterns/app-shell.tsx) | Sidebar rail on a wide screen, floating BottomNav in `AppShell.Footer` on a phone («Ещё» opens the Sidebar panel), AppHeader with the menu button, title and search to CommandMenu, counts on items, a home page that cross-fades between sections |
23
24
  | Media library | [media-library.tsx](patterns/media-library.tsx) | PageToolbar with sections and counts, search (`SmartFilter` with `fields={[]}`), order and «Загрузить»; uploads through `useUploadQueue` in `FileUpload` tiles (`trigger="tile"`, `layout="grid"`) with progress, a failure and retry, a done file moving into the library; a grid of `Thumbnail` / `Thumbnail.Video` tiles that open `MediaViewer` out of the tile (`getOrigin`, `thumbnails`, `renderActions`) or, in selection mode, are picked (`checked`) for bulk download and a `ConfirmDialog` delete; a Skeleton first load and a filtered `EmptyPage` in a `Crossfade` |
24
25
  | Resource schedule | [schedule.tsx](patterns/schedule.tsx) | full-height `Gantt` (`scrollHeight="fill"`) under a `PageToolbar` with search, role / client filters and team sections; people with avatars, client hues, lanes for overlaps, locked absences and milestones; moves saved through an async `onBarChange` or refused and glided back, new work drawn with `onBarCreate` and named in a Modal (cancel fades the ghost), `onSelect` details in a Drawer with a `DescriptionList` and a confirmed removal, `loading` and filtered `empty` |
@@ -33,21 +34,24 @@ AppShell.Root once per app (layouts.md#app-frame-once-per-a
33
34
  ├─ AppShell.Nav → Sidebar navigation rail, one layer above the page
34
35
  ├─ AppHeader.Root menu button / title or path / ⌘K search / app actions
35
36
  └─ AppShell.Main gutters; the page renders here
36
- └─ PageContent.Section | Root the page you deliver
37
- ├─ PageContent.Header
37
+ └─ PageContent.Page | Root the page you deliver
38
+ ├─ PageContent.Header 24 above the body
38
39
  │ ├─ Breadcrumb only below a top-level section; first child → 8 above the title
39
40
  │ ├─ PageContent.Title the one h1
40
41
  │ ├─ PageContent.Description one sentence of context (or a status Badge + the key fact)
41
42
  │ └─ PageContent.Actions page buttons; wrap under the title on narrow screens
42
- └─ PageContent.Body blocks 40 apart: Banner, KPI grid, table, sections, Cards
43
+ └─ PageContent.Body blocks 24 apart: Banner, the tile grid, a table, Cards
44
+ ├─ PageContent.Grid every tile of the screen, 16 on both axes
45
+ │ └─ PageContent.Cell span (of 12), rows; the tile fills it
46
+ └─ PageContent.Section a new topic: h2 label (+ Actions), 40 from its neighbours
43
47
  ```
44
48
 
45
49
  - A screen component renders only its page. Styles, `applyTheme`, `NotificationProvider` and
46
50
  `AppShell` live at the app root.
47
- - `PageContent.Section` for app pages (full panel width); `PageContent.Root maxWidth="wide"` for
51
+ - `PageContent.Page` for app pages (full panel width); `PageContent.Root maxWidth="wide"` for
48
52
  settings, `maxWidth="readable"` for one narrow column of text or fields.
49
- - No outer padding, no margins: `AppShell.Main` has the gutters, `PageContent` has header → body 32 and
50
- block → block 40. Your CSS lays out only the inside of a block.
53
+ - No outer padding, no margins: `AppShell.Main` has the gutters, `PageContent` has header → body 24,
54
+ block → block 24, section → section 40 and tile → tile 16. Your CSS lays out only the inside of a block.
51
55
 
52
56
  ```tsx
53
57
  import { Button, Dropdown, Icon, PageContent } from "prime-ui-kit";
@@ -90,16 +94,19 @@ Every gap is a `--prime-space-N` token (N × 4px) set as `gap` on the parent. Cl
90
94
  | label → field, field → hint | 4–8 | built into fields — never add |
91
95
  | buttons, chips, filters in a row | 8 | `--prime-space-2` |
92
96
  | text lines of one item (name over meta) | 0–4 | `--prime-space-1` |
93
- | heading → its content (group, section without a card) | 16 | `--prime-space-4` |
94
- | tile → tile in a grid (KPI, panels) | 16 | `--prime-space-4` |
97
+ | heading → its content (group, section) | 16 | `--prime-space-4` (`PageContent.Section` does it) |
98
+ | tile → tile, both axes (KPI, charts, panels) | 16 | `PageContent.Grid` |
95
99
  | field → field, switch → switch | 20 | `--prime-space-5` |
96
100
  | main column → side column | 24 | `--prime-space-6` |
101
+ | page header → body | 24 | `PageContent` |
102
+ | block → block on a page | 24 | `PageContent.Body` |
97
103
  | group → group in a form | 32 | `--prime-space-8` |
98
- | page header → body | 32 | `PageContent` |
99
- | block → block on a page | 40 | `PageContent.Body` |
104
+ | section → section (a new topic with an `h2`) | 40 | `PageContent.Section` |
100
105
 
101
106
  A screen with one gap everywhere reads as a list of unrelated things. Use at least three levels: inside
102
- an item, between items, between blocks.
107
+ an item, between items, between blocks. The tiles of one screen are one board: put them all in one
108
+ `PageContent.Grid` (one gap on both axes), never a grid per row — and keep the large air for a new topic
109
+ with its own heading (`PageContent.Section`).
103
110
 
104
111
  ## 3. Hierarchy — air first, then type
105
112
 
@@ -108,7 +115,7 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
108
115
  | Text | Use |
109
116
  |---|---|
110
117
  | page title | `PageContent.Title` (`heading-m`, the only `h1`) |
111
- | section heading without a card | `Typography as="h2" variant="title-m"` |
118
+ | section heading without a card | `PageContent.Section label` (an `h2` in `title-m`) |
112
119
  | card / panel title | `Card.Title as="h2"` (under the page title) |
113
120
  | heading of a group of fields | `Fieldset label` (`title-s` in the `<legend>`) |
114
121
  | other group heading inside a card | `Typography as="h3" variant="title-s"` |
@@ -162,6 +169,12 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
162
169
 
163
170
  - Labels above every field (`label`), placeholder is an example value, help in `hint`, problems in
164
171
  `error` — after blur or on submit. Mark the minority (`required` or `optional`).
172
+ - Records of one shape that a person adds, edits and removes are a `FieldList`: one row per record,
173
+ the column headings once above the rows, small attributes (an icon, a color) in `compact`
174
+ columns. Never a label in every row, never numbered labels, never one record split over lines.
175
+ - One value in several units is one row of fields under one heading, each unit in its field's
176
+ `Input.InlineAffix`; a base value with overrides is one `FieldList` with the base pinned first.
177
+ The shape of the data decides the layout: [ux.md](ux.md#2-the-shape-of-the-data-decides-the-layout).
165
178
  - Fields 20 apart; related short fields side by side in an `auto-fit` grid with `align-items: start`
166
179
  (`reserveSupportRow` on Input / Textarea keeps neighbours aligned); groups 32 apart, each a
167
180
  `Fieldset label` (its `title-s` heading 16 above the fields, no frame); no dividers between groups.
@@ -315,6 +328,6 @@ export function RevenuePanel() {
315
328
  }
316
329
  ```
317
330
 
318
- For a plain KPI row use `Card.Root variant="stat-trend"` (Label · Value · Delta) in an `auto-fit` grid —
319
- see [patterns/dashboard.tsx](patterns/dashboard.tsx). When the trend over days matters, put a
331
+ For a plain KPI row use `Card.Root variant="stat-trend"` (Label · Value · Delta), each in a
332
+ `PageContent.Cell span={3}` of the page's `PageContent.Grid` — see [patterns/dashboard.tsx](patterns/dashboard.tsx). When the trend over days matters, put a
320
333
  `Sparkline` in the `Card.Body` instead: it brings the value, the change and a line to scrub.
@@ -35,11 +35,13 @@ Things that belong together sit closer than things that do not.
35
35
  | inline controls in a row (buttons, filters) | 8 | `--prime-space-2` |
36
36
  | switch → switch in a settings list | 20 | `--prime-space-5` |
37
37
  | group → group (fieldset, form section) | 32 | `--prime-space-8` |
38
- | card → card in a grid | 16 | `--prime-space-4` |
39
- | section → section on a page | 40 | `--prime-space-10` — `PageContent.Body` already does it |
38
+ | tile → tile, both axes (KPI cards, charts, panels) | 16 | `--prime-space-4` — `PageContent.Grid` already does it |
39
+ | block → block on a page (page header → body too) | 24 | `--prime-space-6` — `PageContent` already does it |
40
+ | section → section on a page (a new topic with an `h2`) | 40 | `PageContent.Section` already does it |
40
41
 
41
- `PageContent.Body` already spaces its direct blocks (cards, tables, sections) 40 apart and `AppShell.Main`
42
- already carries the page gutters — do not add your own outer padding.
42
+ `PageContent.Body` already spaces its direct blocks 24 apart, a `PageContent.Section` 40 from its
43
+ neighbours, `PageContent.Grid` its tiles 16 on both axes, and `AppShell.Main` already carries the page
44
+ gutters — do not add your own outer padding.
43
45
 
44
46
  ## 3. Size axis
45
47
 
@@ -132,7 +134,7 @@ A chart color does one job, and the job picks the colors:
132
134
  | `body-m` | 14/20 | default text |
133
135
  | `body-l` | 16/24 | reading text |
134
136
  | `title-s` | 14/20 600 | card title, group heading inside a card or form |
135
- | `title-m` | 16/24 600 | page section heading without a card, modal title |
137
+ | `title-m` | 16/24 600 | page section heading (`PageContent.Section` draws it), modal title |
136
138
  | `title-l` | 18/24 600 | large block title |
137
139
  | `heading-s` | 20/28 600 | page sub-heading |
138
140
  | `heading-m` | 24/32 600 | page title (`PageContent.Title`) |
@@ -163,7 +165,7 @@ level of air: a page with uniform 16px gaps everywhere reads as a list of unrela
163
165
  - Flex children with text: `min-width: 0` + ellipsis or `overflow-wrap: anywhere`.
164
166
  - Wide tables on phones: they scroll inside themselves with edge shadows; `stickyFirstColumn`, a column
165
167
  chooser through `hiddenColumns`; never let the page scroll sideways.
166
- - Card grids: `grid-template-columns: repeat(auto-fill, minmax(min(100%, calc(var(--prime-space-16) * 4)), 1fr))`.
168
+ - Card grids on a page: `PageContent.Grid` + `PageContent.Cell span` (folds by its own width); inside one block: `repeat(auto-fill, minmax(min(100%, …), 1fr))`.
167
169
  - A page panel with sections, filter and search, view and the primary action is `PageToolbar`.
168
170
  - Sticky parts come from the kit: `AppHeader` (with the `PageToolbar` in `AppHeader.Tools`), the
169
171
  DataTable head and `stickyFirstColumn`, the Gantt header and first column — each sticks inside its
@@ -2,9 +2,9 @@
2
2
 
3
3
  Every screen sits in one frame: `AppShell.Root` with Nav / `AppHeader` / Main (the content panel is the page,
4
4
  the nav rail one layer above it) → `PageContent` (header + body). `AppShell.Main` carries the page gutters
5
- and `PageContent.Body` spaces its blocks 40 apart, so your code adds **no outer padding and no
6
- margins**. Your CSS only lays out the inside of a block (grids, rows) with `gap` on `--prime-space-*`.
7
- Main's top space under the header is part of the frame — 24, then 40 from 1024px: a page header starts
5
+ and `PageContent` spaces the page — blocks 24, a titled `PageContent.Section` 40, tiles in a
6
+ `PageContent.Grid` 16 on both axes — so your code adds **no outer padding and no margins**. Your CSS only lays out the inside of a block (grids, rows) with `gap` on `--prime-space-*`.
7
+ Main's top space under the header is part of the frame — 24, then 32 from 1024px: a page header starts
8
8
  there; never cancel it with a negative margin or a padding override.
9
9
 
10
10
  The page recipes are whole, working screens in [patterns/](patterns/) — start from the closest one
@@ -181,9 +181,9 @@ from what people do, not from how the backend is split. The full guide with the
181
181
 
182
182
  ## Page wrapper
183
183
 
184
- - App pages with actions (dashboard, list, detail): `PageContent.Section` — the full width of the panel.
185
- - Settings and long forms: `PageContent.Root maxWidth="wide"` with a heading column + panel per section
186
- ([patterns/settings-page.tsx](patterns/settings-page.tsx)), or `maxWidth="readable"` for a single
184
+ - App pages with actions (dashboard, list, detail): `PageContent.Page` — the full width of the panel.
185
+ - Settings and long forms: `PageContent.Root maxWidth="wide"` with a `PageContent.Section layout="aside"`
186
+ (heading column + panel) per section ([patterns/settings-page.tsx](patterns/settings-page.tsx)), or `maxWidth="readable"` for a single
187
187
  narrow column of fields.
188
188
  - Long reading text (terms, help): `PageContent.Root maxWidth="readable"`.
189
189
  - A page as high as the panel (a log, a board, a chat): `AppShell.Root fillViewport` +
@@ -1,12 +1,5 @@
1
1
  /* App shell: the frame (Sidebar / BottomNav / AppHeader) lays itself out; this file only places the
2
- home page's blocks. Blocks 40 apart come from PageContent.Body. */
3
-
4
- /* KPI row: as many ~14rem tiles as fit, one column on a phone. */
5
- .kpis {
6
- display: grid;
7
- grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(var(--prime-space-14) * 4)), 1fr));
8
- gap: var(--prime-space-4);
9
- }
2
+ home page's blocks. The rhythm comes from PageContent.Body, the tiles from PageContent.Grid. */
10
3
 
11
4
  /* A list row: the text at the start, the meta (time, sum) at the end. */
12
5
  .row {
@@ -213,7 +213,7 @@ export default function AppShellPattern() {
213
213
  >
214
214
  {/* Another section is another page: it cross-fades instead of flipping. */}
215
215
  <Crossfade state={current}>
216
- <PageContent.Section aria-labelledby="page-title">
216
+ <PageContent.Page aria-labelledby="page-title">
217
217
  <PageContent.Header>
218
218
  <PageContent.Title id="page-title">{section.label}</PageContent.Title>
219
219
  <PageContent.Description>
@@ -225,15 +225,17 @@ export default function AppShellPattern() {
225
225
  <PageContent.Body>
226
226
  {current === "today" ? (
227
227
  <>
228
- <div className={styles.kpis}>
228
+ <PageContent.Grid>
229
229
  {KPIS.map((kpi) => (
230
- <Card.Root key={kpi.label} variant="stat-trend">
231
- <Card.Label>{kpi.label}</Card.Label>
232
- <Card.Value>{kpi.value}</Card.Value>
233
- <Card.Delta tone={kpi.tone}>{kpi.delta}</Card.Delta>
234
- </Card.Root>
230
+ <PageContent.Cell key={kpi.label} span={4}>
231
+ <Card.Root variant="stat-trend">
232
+ <Card.Label>{kpi.label}</Card.Label>
233
+ <Card.Value>{kpi.value}</Card.Value>
234
+ <Card.Delta tone={kpi.tone}>{kpi.delta}</Card.Delta>
235
+ </Card.Root>
236
+ </PageContent.Cell>
235
237
  ))}
236
- </div>
238
+ </PageContent.Grid>
237
239
  <Card.Root variant="list">
238
240
  <Card.Header>
239
241
  <Card.Title as="h2">Задачи на сегодня</Card.Title>
@@ -276,7 +278,7 @@ export default function AppShellPattern() {
276
278
  </Card.Root>
277
279
  )}
278
280
  </PageContent.Body>
279
- </PageContent.Section>
281
+ </PageContent.Page>
280
282
  </Crossfade>
281
283
  </AppShell.Template>
282
284
 
@@ -1,4 +1,4 @@
1
- /** A support messenger on `Chat`: dialogs beside the conversation (one pane at a time on a phone), history that loads above as you scroll up, dates and runs from data, every kind of message, search, pinned messages, replies and edits from the menu with a jump to the quoted message, read receipts and unread counts from what was seen, the typing signal both ways, and the Composer whose messages rise into the list while the client answers. */
1
+ /** A support messenger on `Chat`: dialogs with a search over them beside the conversation (one pane at a time on a phone), history that loads above as you scroll up, dates and runs from data, every kind of message, search, pinned messages, replies and edits from the menu with a jump to the quoted message, read receipts and unread counts from what was seen, the typing signal both ways, and the Composer whose messages rise into the list while the client answers. */
2
2
  import {
3
3
  Avatar,
4
4
  Button,
@@ -550,6 +550,23 @@ export default function ChatPattern() {
550
550
  };
551
551
  }, []);
552
552
  const dialog = DIALOGS.find((item) => item.id === dialogId) ?? DIALOGS[0];
553
+ /** The search over the dialogs: by name and by the last message a row shows. */
554
+ const [dialogQuery, setDialogQuery] = React.useState("");
555
+ // The open dialog shows its live last message; the others their stored one.
556
+ const lastOf = (item: Dialog) =>
557
+ item.id === dialogId ? messages[messages.length - 1] : undefined;
558
+ const previewOf = (item: Dialog) => {
559
+ const last = lastOf(item);
560
+ return last ? excerpt(last) : item.preview;
561
+ };
562
+ const needle = dialogQuery.trim().toLowerCase();
563
+ const foundDialogs = needle
564
+ ? DIALOGS.filter(
565
+ (item) =>
566
+ item.name.toLowerCase().includes(needle) ||
567
+ previewOf(item).toLowerCase().includes(needle),
568
+ )
569
+ : DIALOGS;
553
570
  /** The dialog whose history has arrived: opening another one loads it first. */
554
571
  const [loadedId, setLoadedId] = React.useState<string | null>(null);
555
572
  React.useEffect(() => {
@@ -765,14 +782,15 @@ export default function ChatPattern() {
765
782
  <PageContent.List aria-label="Диалоги">
766
783
  <Card.Root className={styles.paneCard}>
767
784
  <ChatList.Root
768
- items={DIALOGS}
785
+ items={foundDialogs}
769
786
  getKey={(item) => item.id}
770
787
  value={dialogId}
771
788
  onValueChange={setDialogId}
789
+ search={dialogQuery}
790
+ onSearchChange={setDialogQuery}
772
791
  now={NOW}
773
792
  renderItem={(item) => {
774
- // The open dialog shows its live last message; the others their stored one.
775
- const last = item.id === dialogId ? messages[messages.length - 1] : undefined;
793
+ const last = lastOf(item);
776
794
  return (
777
795
  <ChatList.Item
778
796
  time={last?.sentAt ?? item.at}
@@ -788,7 +806,7 @@ export default function ChatPattern() {
788
806
  </ChatList.ItemIcon>
789
807
  <ChatList.ItemText>{item.name}</ChatList.ItemText>
790
808
  <ChatList.ItemDescription author={last?.from === "me" ? "Вы" : undefined}>
791
- {last ? excerpt(last) : item.preview}
809
+ {previewOf(item)}
792
810
  </ChatList.ItemDescription>
793
811
  </ChatList.Item>
794
812
  );
@@ -1,19 +1,4 @@
1
- /* Dashboard: blocks 40 apart come from PageContent.Body; tiles inside a block 16 apart. */
2
-
3
- /* KPI row: as many ~14rem tiles as fit, one column on a phone. */
4
- .kpis {
5
- display: grid;
6
- grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(var(--prime-space-14) * 4)), 1fr));
7
- gap: var(--prime-space-4);
8
- }
9
-
10
- /* Panels: two columns from ~40rem of width; neighbours share one height. */
11
- .panels {
12
- display: grid;
13
- grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(var(--prime-space-20) * 4)), 1fr));
14
- align-items: stretch;
15
- gap: var(--prime-space-4);
16
- }
1
+ /* Dashboard: the board of tiles is PageContent.Grid; here only the inside of the tiles. */
17
2
 
18
3
  /* Bars inside a panel: 20 apart, like fields. */
19
4
  .bars {
@@ -21,21 +6,6 @@
21
6
  gap: var(--prime-space-5);
22
7
  }
23
8
 
24
- /* A section without a card: heading row 16 above the table. */
25
- .recent {
26
- display: grid;
27
- gap: var(--prime-space-4);
28
- min-width: 0;
29
- }
30
-
31
- .recentHeader {
32
- display: flex;
33
- flex-wrap: wrap;
34
- align-items: baseline;
35
- justify-content: space-between;
36
- gap: var(--prime-space-2) var(--prime-space-4);
37
- }
38
-
39
9
  .client {
40
10
  display: inline-flex;
41
11
  flex-wrap: wrap;