prime-ui-kit 0.17.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 (286) hide show
  1. package/README.md +2 -1
  2. package/dist/chunks/{chunk-PRP4B6OG.js → chunk-IVS3J7AS.js} +1 -1
  3. package/dist/chunks/{chunk-2ODXLPW3.js → chunk-QFHE5GKW.js} +1 -0
  4. package/dist/chunks/{chunk-7WWZYGRC.js → chunk-RZNGLELR.js} +4497 -3896
  5. package/dist/chunks/{chunk-A4WE3UNC.js → chunk-WYCTXD63.js} +18399 -17624
  6. package/dist/chunks/{chunk-SVFWPLNW.js → chunk-ZTZ2ML2B.js} +14 -3
  7. package/dist/color-picker.js +3 -3
  8. package/dist/components/badge/Badge.d.ts +17 -0
  9. package/dist/components/breadcrumb/Breadcrumb.d.ts +11 -2
  10. package/dist/components/button/Button.d.ts +1 -1
  11. package/dist/components/button-group/ButtonGroup.d.ts +1 -1
  12. package/dist/components/card/Card.d.ts +8 -4
  13. package/dist/components/chat/Chat.d.ts +3 -3
  14. package/dist/components/chat/MessageList.d.ts +5 -1
  15. package/dist/components/chat/groupMessages.d.ts +12 -4
  16. package/dist/components/chat-list/ChatList.d.ts +18 -9
  17. package/dist/components/chat-list/formatChatTime.d.ts +3 -3
  18. package/dist/components/checkbox/Checkbox.d.ts +6 -1
  19. package/dist/components/color-field/ColorField.d.ts +1 -1
  20. package/dist/components/color-picker/ColorPicker.d.ts +6 -1
  21. package/dist/components/color-picker/ColorPresets.d.ts +9 -1
  22. package/dist/components/crossfade/Crossfade.d.ts +1 -1
  23. package/dist/components/data-table/ColumnsMenu.d.ts +16 -20
  24. package/dist/components/data-table/DataTable.d.ts +16 -11
  25. package/dist/components/data-table/Head.d.ts +10 -1
  26. package/dist/components/data-table/Row.d.ts +7 -1
  27. package/dist/components/data-table/StateRows.d.ts +3 -1
  28. package/dist/components/data-table/types.d.ts +5 -0
  29. package/dist/components/digit-input/DigitInput.d.ts +1 -1
  30. package/dist/components/drawer/Drawer.d.ts +3 -2
  31. package/dist/components/field-list/FieldList.d.ts +24 -12
  32. package/dist/components/hint/Hint.d.ts +8 -2
  33. package/dist/components/index.d.ts +7 -4
  34. package/dist/components/input/Input.d.ts +27 -5
  35. package/dist/components/input/inputSuggestions.d.ts +39 -0
  36. package/dist/components/kanban/Kanban.d.ts +8 -9
  37. package/dist/components/list/List.d.ts +25 -6
  38. package/dist/components/message/Message.d.ts +15 -3
  39. package/dist/components/message/MessageSticker.d.ts +27 -0
  40. package/dist/components/message/formatMessageTime.d.ts +5 -2
  41. package/dist/components/modal/Modal.d.ts +3 -2
  42. package/dist/components/native-select/NativeSelect.d.ts +1 -1
  43. package/dist/components/radio/Radio.d.ts +11 -2
  44. package/dist/components/rich-text/RichText.d.ts +23 -0
  45. package/dist/components/scroll-container/ScrollContainer.d.ts +1 -1
  46. package/dist/components/scroll-container/scrollGlide.d.ts +26 -0
  47. package/dist/components/scroll-container/useScrollMotion.d.ts +25 -0
  48. package/dist/components/segmented-control/SegmentedControl.d.ts +1 -1
  49. package/dist/components/select/selectContext.d.ts +7 -0
  50. package/dist/components/slider/Slider.d.ts +1 -1
  51. package/dist/components/switch/Switch.d.ts +6 -1
  52. package/dist/components/tag-select/TagSelect.d.ts +1 -1
  53. package/dist/components/textarea/Textarea.d.ts +6 -1
  54. package/dist/components/timeline/Timeline.d.ts +1 -1
  55. package/dist/icon-set.js +2 -2
  56. package/dist/index.css +1 -1
  57. package/dist/index.js +5 -3
  58. package/dist/internal/ChoiceField.d.ts +3 -1
  59. package/dist/internal/FieldFrame.d.ts +8 -2
  60. package/dist/internal/fieldCell.d.ts +7 -1
  61. package/dist/internal/formatClock.d.ts +23 -2
  62. package/dist/internal/leaveGuard.d.ts +27 -0
  63. package/dist/internal/messageList.d.ts +2 -0
  64. package/dist/internal/overlay/DialogGuard.d.ts +3 -2
  65. package/dist/internal/overlay/DialogParts.d.ts +40 -6
  66. package/dist/internal/swatch.d.ts +5 -0
  67. package/dist/internal/tokenDefaults.d.ts +1 -0
  68. package/dist/layout/index.d.ts +1 -1
  69. package/dist/layout/page/Page.d.ts +48 -15
  70. package/dist/skill/anti-slop.md +28 -0
  71. package/dist/skill/api-contract.md +14 -7
  72. package/dist/skill/cheatsheet.md +28 -9
  73. package/dist/skill/checklist.md +9 -0
  74. package/dist/skill/choosing.md +25 -12
  75. package/dist/skill/components.md +1 -0
  76. package/dist/skill/composition.md +88 -16
  77. package/dist/skill/layouts.md +6 -4
  78. package/dist/skill/reference/components/badge/COMPONENT.md +14 -1
  79. package/dist/skill/reference/components/badge/examples/examples.module.css +7 -0
  80. package/dist/skill/reference/components/badge/examples/tag-row.tsx +34 -0
  81. package/dist/skill/reference/components/breadcrumb/COMPONENT.md +9 -4
  82. package/dist/skill/reference/components/breadcrumb/examples/router.tsx +17 -0
  83. package/dist/skill/reference/components/button/COMPONENT.md +1 -1
  84. package/dist/skill/reference/components/button-group/COMPONENT.md +1 -0
  85. package/dist/skill/reference/components/card/COMPONENT.md +7 -1
  86. package/dist/skill/reference/components/card/examples/states.tsx +59 -0
  87. package/dist/skill/reference/components/chat/COMPONENT.md +4 -1
  88. package/dist/skill/reference/components/chat/examples/time-zone.tsx +83 -0
  89. package/dist/skill/reference/components/chat-list/COMPONENT.md +12 -6
  90. package/dist/skill/reference/components/chat-list/examples/links.tsx +1 -1
  91. package/dist/skill/reference/components/chat-list/examples/router.tsx +63 -0
  92. package/dist/skill/reference/components/checkbox/COMPONENT.md +1 -0
  93. package/dist/skill/reference/components/color-field/COMPONENT.md +2 -1
  94. package/dist/skill/reference/components/color-picker/COMPONENT.md +14 -5
  95. package/dist/skill/reference/components/color-picker/examples/presets-custom.tsx +31 -0
  96. package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
  97. package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
  98. package/dist/skill/reference/components/crossfade/COMPONENT.md +1 -1
  99. package/dist/skill/reference/components/data-table/COMPONENT.md +46 -41
  100. package/dist/skill/reference/components/data-table/examples/actions-column.tsx +154 -0
  101. package/dist/skill/reference/components/data-table/examples/columns-visibility.tsx +3 -12
  102. package/dist/skill/reference/components/data-table/examples/controlled.tsx +1 -1
  103. package/dist/skill/reference/components/data-table/examples/narrow.tsx +4 -11
  104. package/dist/skill/reference/components/data-table/examples/numeric.tsx +9 -2
  105. package/dist/skill/reference/components/data-table/examples/row-link.tsx +18 -22
  106. package/dist/skill/reference/components/data-table/examples/screen.tsx +12 -1
  107. package/dist/skill/reference/components/data-table/examples/toolbar.tsx +26 -32
  108. package/dist/skill/reference/components/datepicker/COMPONENT.md +3 -1
  109. package/dist/skill/reference/components/datepicker/examples/deadline.tsx +33 -0
  110. package/dist/skill/reference/components/digit-input/COMPONENT.md +1 -0
  111. package/dist/skill/reference/components/dnd/COMPONENT.md +4 -3
  112. package/dist/skill/reference/components/dnd/examples/examples.module.css +4 -3
  113. package/dist/skill/reference/components/dnd/examples/handle.tsx +5 -5
  114. package/dist/skill/reference/components/dnd/examples/locked.tsx +1 -1
  115. package/dist/skill/reference/components/dnd/examples/table.tsx +6 -6
  116. package/dist/skill/reference/components/drawer/COMPONENT.md +38 -9
  117. package/dist/skill/reference/components/drawer/examples/client-card.tsx +32 -8
  118. package/dist/skill/reference/components/drawer/examples/header-actions.tsx +1 -1
  119. package/dist/skill/reference/components/drawer/examples/loading.tsx +58 -0
  120. package/dist/skill/reference/components/field-list/COMPONENT.md +40 -24
  121. package/dist/skill/reference/components/field-list/examples/base-row.tsx +2 -1
  122. package/dist/skill/reference/components/field-list/examples/locked.tsx +11 -11
  123. package/dist/skill/reference/components/field-list/examples/reorder.tsx +1 -1
  124. package/dist/skill/reference/components/field-list/examples/saving.tsx +83 -0
  125. package/dist/skill/reference/components/field-list/examples/screen.module.css +1 -1
  126. package/dist/skill/reference/components/field-list/examples/screen.tsx +168 -162
  127. package/dist/skill/reference/components/field-list/examples/totals.tsx +83 -0
  128. package/dist/skill/reference/components/file-upload/COMPONENT.md +1 -0
  129. package/dist/skill/reference/components/hint/COMPONENT.md +16 -2
  130. package/dist/skill/reference/components/hint/examples/variants.tsx +48 -0
  131. package/dist/skill/reference/components/input/COMPONENT.md +39 -9
  132. package/dist/skill/reference/components/input/examples/affixes.tsx +4 -2
  133. package/dist/skill/reference/components/input/examples/dismiss.tsx +29 -0
  134. package/dist/skill/reference/components/input/examples/suggestions.tsx +26 -0
  135. package/dist/skill/reference/components/input/examples/time.tsx +24 -0
  136. package/dist/skill/reference/components/input/examples/with-select.tsx +44 -0
  137. package/dist/skill/reference/components/kanban/COMPONENT.md +12 -8
  138. package/dist/skill/reference/components/kanban/examples/columns.tsx +4 -2
  139. package/dist/skill/reference/components/kanban/examples/states.tsx +7 -5
  140. package/dist/skill/reference/components/list/COMPONENT.md +39 -13
  141. package/dist/skill/reference/components/list/examples/reorder.tsx +42 -7
  142. package/dist/skill/reference/components/list/examples/row-controls.tsx +80 -0
  143. package/dist/skill/reference/components/list/examples/with-icon.tsx +23 -2
  144. package/dist/skill/reference/components/login-form/COMPONENT.md +8 -7
  145. package/dist/skill/reference/components/login-form/examples/states.tsx +12 -14
  146. package/dist/skill/reference/components/message/COMPONENT.md +30 -9
  147. package/dist/skill/reference/components/message/examples/preview.tsx +28 -0
  148. package/dist/skill/reference/components/message/examples/sticker.tsx +35 -0
  149. package/dist/skill/reference/components/modal/COMPONENT.md +35 -10
  150. package/dist/skill/reference/components/modal/examples/long-content.tsx +37 -18
  151. package/dist/skill/reference/components/native-select/COMPONENT.md +1 -0
  152. package/dist/skill/reference/components/notification/COMPONENT.md +1 -1
  153. package/dist/skill/reference/components/radio/COMPONENT.md +2 -0
  154. package/dist/skill/reference/components/rich-text/COMPONENT.md +93 -0
  155. package/dist/skill/reference/components/rich-text/examples/overview.tsx +19 -0
  156. package/dist/skill/reference/components/scroll-container/COMPONENT.md +8 -5
  157. package/dist/skill/reference/components/segmented-control/COMPONENT.md +4 -2
  158. package/dist/skill/reference/components/segmented-control/examples/in-form.tsx +1 -1
  159. package/dist/skill/reference/components/select/COMPONENT.md +7 -3
  160. package/dist/skill/reference/components/sheet/COMPONENT.md +18 -7
  161. package/dist/skill/reference/components/slider/COMPONENT.md +1 -0
  162. package/dist/skill/reference/components/slider/examples/controlled.tsx +1 -1
  163. package/dist/skill/reference/components/switch/COMPONENT.md +1 -0
  164. package/dist/skill/reference/components/tag-select/COMPONENT.md +1 -0
  165. package/dist/skill/reference/components/textarea/COMPONENT.md +1 -0
  166. package/dist/skill/reference/components/timeline/COMPONENT.md +3 -3
  167. package/dist/skill/reference/components/timeline/examples/structure.tsx +2 -1
  168. package/dist/skill/reference/layout/app-shell/COMPONENT.md +2 -2
  169. package/dist/skill/reference/layout/bottom-nav/COMPONENT.md +1 -0
  170. package/dist/skill/reference/layout/page/COMPONENT.md +42 -20
  171. package/dist/skill/reference/layout/page/examples/examples.module.css +7 -0
  172. package/dist/skill/reference/layout/page/examples/guard.tsx +87 -0
  173. package/dist/skill/reference/layout/page/examples/loading.tsx +57 -0
  174. package/dist/skill/reference/layout/page/examples/screen.module.css +1 -1
  175. package/dist/skill/reference/layout/page/examples/screen.tsx +93 -104
  176. package/dist/skill/reference/layout/page/examples/sections.tsx +2 -15
  177. package/dist/skill/reference/layout/sidebar/COMPONENT.md +1 -0
  178. package/dist/skill/responsive.md +19 -12
  179. package/dist/skill/ux.md +5 -3
  180. package/dist/tokens/semantic.d.ts +3 -1
  181. package/dist/tokens/themes/dark.d.ts +1 -1
  182. package/package.json +2 -2
  183. package/src/components/badge/COMPONENT.md +14 -1
  184. package/src/components/badge/examples/examples.module.css +7 -0
  185. package/src/components/badge/examples/tag-row.tsx +34 -0
  186. package/src/components/breadcrumb/COMPONENT.md +9 -4
  187. package/src/components/breadcrumb/examples/router.tsx +17 -0
  188. package/src/components/button/COMPONENT.md +1 -1
  189. package/src/components/button-group/COMPONENT.md +1 -0
  190. package/src/components/card/COMPONENT.md +7 -1
  191. package/src/components/card/examples/states.tsx +59 -0
  192. package/src/components/chat/COMPONENT.md +4 -1
  193. package/src/components/chat/examples/time-zone.tsx +83 -0
  194. package/src/components/chat-list/COMPONENT.md +12 -6
  195. package/src/components/chat-list/examples/links.tsx +1 -1
  196. package/src/components/chat-list/examples/router.tsx +63 -0
  197. package/src/components/checkbox/COMPONENT.md +1 -0
  198. package/src/components/color-field/COMPONENT.md +2 -1
  199. package/src/components/color-picker/COMPONENT.md +14 -5
  200. package/src/components/color-picker/examples/presets-custom.tsx +31 -0
  201. package/src/components/color-swatches/COMPONENT.md +1 -0
  202. package/src/components/confirm-dialog/COMPONENT.md +1 -1
  203. package/src/components/crossfade/COMPONENT.md +1 -1
  204. package/src/components/data-table/COMPONENT.md +46 -41
  205. package/src/components/data-table/examples/actions-column.tsx +154 -0
  206. package/src/components/data-table/examples/columns-visibility.tsx +3 -12
  207. package/src/components/data-table/examples/controlled.tsx +1 -1
  208. package/src/components/data-table/examples/narrow.tsx +4 -11
  209. package/src/components/data-table/examples/numeric.tsx +9 -2
  210. package/src/components/data-table/examples/row-link.tsx +18 -22
  211. package/src/components/data-table/examples/screen.tsx +12 -1
  212. package/src/components/data-table/examples/toolbar.tsx +26 -32
  213. package/src/components/datepicker/COMPONENT.md +3 -1
  214. package/src/components/datepicker/examples/deadline.tsx +33 -0
  215. package/src/components/digit-input/COMPONENT.md +1 -0
  216. package/src/components/dnd/COMPONENT.md +4 -3
  217. package/src/components/dnd/examples/examples.module.css +4 -3
  218. package/src/components/dnd/examples/handle.tsx +5 -5
  219. package/src/components/dnd/examples/locked.tsx +1 -1
  220. package/src/components/dnd/examples/table.tsx +6 -6
  221. package/src/components/drawer/COMPONENT.md +38 -9
  222. package/src/components/drawer/examples/client-card.tsx +32 -8
  223. package/src/components/drawer/examples/header-actions.tsx +1 -1
  224. package/src/components/drawer/examples/loading.tsx +58 -0
  225. package/src/components/field-list/COMPONENT.md +40 -24
  226. package/src/components/field-list/examples/base-row.tsx +2 -1
  227. package/src/components/field-list/examples/locked.tsx +11 -11
  228. package/src/components/field-list/examples/reorder.tsx +1 -1
  229. package/src/components/field-list/examples/saving.tsx +83 -0
  230. package/src/components/field-list/examples/screen.module.css +1 -1
  231. package/src/components/field-list/examples/screen.tsx +168 -162
  232. package/src/components/field-list/examples/totals.tsx +83 -0
  233. package/src/components/file-upload/COMPONENT.md +1 -0
  234. package/src/components/hint/COMPONENT.md +16 -2
  235. package/src/components/hint/examples/variants.tsx +48 -0
  236. package/src/components/input/COMPONENT.md +39 -9
  237. package/src/components/input/examples/affixes.tsx +4 -2
  238. package/src/components/input/examples/dismiss.tsx +29 -0
  239. package/src/components/input/examples/suggestions.tsx +26 -0
  240. package/src/components/input/examples/time.tsx +24 -0
  241. package/src/components/input/examples/with-select.tsx +44 -0
  242. package/src/components/kanban/COMPONENT.md +12 -8
  243. package/src/components/kanban/examples/columns.tsx +4 -2
  244. package/src/components/kanban/examples/states.tsx +7 -5
  245. package/src/components/list/COMPONENT.md +39 -13
  246. package/src/components/list/examples/reorder.tsx +42 -7
  247. package/src/components/list/examples/row-controls.tsx +80 -0
  248. package/src/components/list/examples/with-icon.tsx +23 -2
  249. package/src/components/login-form/COMPONENT.md +8 -7
  250. package/src/components/login-form/examples/states.tsx +12 -14
  251. package/src/components/message/COMPONENT.md +30 -9
  252. package/src/components/message/examples/preview.tsx +28 -0
  253. package/src/components/message/examples/sticker.tsx +35 -0
  254. package/src/components/modal/COMPONENT.md +35 -10
  255. package/src/components/modal/examples/long-content.tsx +37 -18
  256. package/src/components/native-select/COMPONENT.md +1 -0
  257. package/src/components/notification/COMPONENT.md +1 -1
  258. package/src/components/radio/COMPONENT.md +2 -0
  259. package/src/components/rich-text/COMPONENT.md +93 -0
  260. package/src/components/rich-text/examples/overview.tsx +19 -0
  261. package/src/components/scroll-container/COMPONENT.md +8 -5
  262. package/src/components/segmented-control/COMPONENT.md +4 -2
  263. package/src/components/segmented-control/examples/in-form.tsx +1 -1
  264. package/src/components/select/COMPONENT.md +7 -3
  265. package/src/components/sheet/COMPONENT.md +18 -7
  266. package/src/components/slider/COMPONENT.md +1 -0
  267. package/src/components/slider/examples/controlled.tsx +1 -1
  268. package/src/components/switch/COMPONENT.md +1 -0
  269. package/src/components/tag-select/COMPONENT.md +1 -0
  270. package/src/components/textarea/COMPONENT.md +1 -0
  271. package/src/components/timeline/COMPONENT.md +3 -3
  272. package/src/components/timeline/examples/structure.tsx +2 -1
  273. package/src/layout/app-shell/COMPONENT.md +2 -2
  274. package/src/layout/bottom-nav/COMPONENT.md +1 -0
  275. package/src/layout/page/COMPONENT.md +42 -20
  276. package/src/layout/page/examples/examples.module.css +7 -0
  277. package/src/layout/page/examples/guard.tsx +87 -0
  278. package/src/layout/page/examples/loading.tsx +57 -0
  279. package/src/layout/page/examples/screen.module.css +1 -1
  280. package/src/layout/page/examples/screen.tsx +93 -104
  281. package/src/layout/page/examples/sections.tsx +2 -15
  282. package/src/layout/sidebar/COMPONENT.md +1 -0
  283. package/src/styles/theme-dark.css +1 -1
  284. package/src/styles/theme-light.css +2 -1
  285. package/dist/components/data-table/context.d.ts +0 -14
  286. package/dist/components/scroll-container/useWheelToInline.d.ts +0 -10
@@ -46,6 +46,7 @@ Switch.Root field grid; ref and input props go to the native input (rol
46
46
  | `optional` | `boolean` | `false` | Muted `labels.optional` after the label text; the field may stay off. |
47
47
  | `labels` | `Partial<SwitchLabels>` | — | Built-in strings, see Labels. |
48
48
  | `hint` | `ReactNode` | — | Help text under the label text. Hidden while `error` is shown. |
49
+ | `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
49
50
  | `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
50
51
  | `invalid` | `boolean` | `false` | Danger ring on the off track and `aria-invalid`. A non-empty `error` implies it. |
51
52
  | `disabled` | `boolean` | `false` | No toggling; muted track (on = `accent-soft`), dimmed text and hint. |
@@ -60,6 +60,7 @@ TagSelect field frame: label · control · hint/error (sin
60
60
  | `onOptionDelete` | `(value: string) => void \| Promise<void>` | — | Enables «Удалить» in the row menu; the value also leaves the selection. A returned Promise defers it: resolve deletes, reject keeps everything (e.g. after a confirmation). |
61
61
  | `label` | `ReactNode` | — | Field label above; names the input. |
62
62
  | `hint` | `ReactNode` | — | Support text under the field; linked by `aria-describedby`. |
63
+ | `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
63
64
  | `error` | `ReactNode` | — | Error message; replaces the hint and implies `invalid`. |
64
65
  | `required` | `boolean` | `false` | Red `*` after the label and `aria-required` on the input. |
65
66
  | `optional` | `boolean` | — | Muted `labels.optional` after the label. |
@@ -46,6 +46,7 @@ Unlike Input, Textarea has no Wrapper / Field parts: `Textarea.Root` renders the
46
46
  | `required` | `boolean` | — | Native `required` on the textarea and a red `*` after the label (`aria-hidden`). |
47
47
  | `optional` | `boolean` | `false` | Muted marker right after the label text (`labels.optional`). |
48
48
  | `hint` | `ReactNode` | — | Help text under the field. Hidden while `error` is shown. |
49
+ | `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
49
50
  | `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
50
51
  | `invalid` | `boolean` | `false` | Danger inset ring on the field box, `aria-invalid` on the textarea. A non-empty `error` implies it. |
51
52
  | `focusRing` | `boolean` | `true` | `false` hides only the visual focus ring on the field box (`data-focus-ring="false"`); focus, keyboard, ARIA and the invalid ring stay. |
@@ -27,7 +27,7 @@ Timeline.Root size container, sets the tier (size, else the h
27
27
  └─ Timeline.Group label labelled <ol role="list">; the line runs from its first dot to its last
28
28
  ├─ Timeline.Item <li> + row (div | button | a | asChild element) with the dot
29
29
  │ ├─ Timeline.ItemTitle first line
30
- │ ├─ Timeline.ItemMeta second line (date); <strong> emphasizes a part
30
+ │ ├─ Timeline.ItemMeta second line (date); <strong> emphasizes a part; repeats, lines stack
31
31
  │ └─ Timeline.ItemValue trailing amount
32
32
  │ └─ Timeline.ItemMeta second line under the amount
33
33
  └─ Timeline.Gap <li>: interval between events (dashed segment, hollow dot)
@@ -73,7 +73,7 @@ Timeline.Root size container, sets the tier (size, else the h
73
73
  | `…rest` | `Omit<HTMLAttributes<HTMLElement>, "children" \| "color" \| "onClick">` | — | The other row attributes (`target`, `rel`, `download` with `href`). |
74
74
 
75
75
  ### Timeline.ItemTitle · Timeline.ItemMeta
76
- `ref` → `HTMLSpanElement`. `<span>` lines: the event (medium, accent on the highlighted row, wraps) and the muted secondary line with tabular numbers — under the title the date (emphasize a part with `<strong>`), inside `Timeline.ItemValue` the value's second line.
76
+ `ref` → `HTMLSpanElement`. `<span>` lines: the event (medium, wraps) and the muted secondary line, tabular — under the title the date (`<strong>` emphasizes a part; several `Timeline.ItemMeta` stack), inside `Timeline.ItemValue` the value's second line.
77
77
 
78
78
  | Prop | Type | Default | Description |
79
79
  |---|---|---|---|
@@ -180,7 +180,7 @@ No `labels`.
180
180
  | [overview.tsx](examples/overview.tsx) | An operations feed: one labelled group, a line through the dots, the row under the pointer highlighted — `Timeline.Group`, `Timeline.ItemValue`. |
181
181
  | [variants.tsx](examples/variants.tsx) | Dot hues for categories at reduced emphasis, and status tones on items, values and gaps at full emphasis — `color`, `tone`. |
182
182
  | [sizes.tsx](examples/sizes.tsx) | Every tier scales text, dot and row rhythm, rows 40 to 76 px — `size`. |
183
- | [structure.tsx](examples/structure.tsx) | A service history with intervals between events, a caption on the right of a gap and a second value line — `Timeline.Gap`, `Timeline.Description`, `Timeline.ItemMeta`. |
183
+ | [structure.tsx](examples/structure.tsx) | A service history with intervals between events, a caption on the right of a gap, two meta lines under a repair and a second value line — `Timeline.Gap`, `Timeline.Description`, `Timeline.ItemMeta`. |
184
184
  | [links.tsx](examples/links.tsx) | Rows that open something: an operation page by a link or by your router link through the slot, a receipt in place by a button; one Tab stop each with a focus ring — `href`, `asChild`, `onClick`. |
185
185
  | [narrow.tsx](examples/narrow.tsx) | At 375 px the title wraps and the amount stays right; below 20rem of its own width the amount moves under the meta line — `Timeline.ItemValue`. |
186
186
 
@@ -1,4 +1,4 @@
1
- /** A service history with intervals between events, a caption on the right of a gap and a second value line — `Timeline.Gap`, `Timeline.Description`, `Timeline.ItemMeta`. */
1
+ /** A service history with intervals between events, a caption on the right of a gap, two meta lines under a repair and a second value line — `Timeline.Gap`, `Timeline.Description`, `Timeline.ItemMeta`. */
2
2
  import { Timeline } from "prime-ui-kit";
3
3
 
4
4
  import styles from "./examples.module.css";
@@ -36,6 +36,7 @@ export default function TimelineStructureExample() {
36
36
  <Timeline.ItemMeta>
37
37
  <strong>07.06.26</strong> · 12 100 км
38
38
  </Timeline.ItemMeta>
39
+ <Timeline.ItemMeta>Мастер Олег Смирнов · гарантия 6 месяцев</Timeline.ItemMeta>
39
40
  <Timeline.ItemValue>
40
41
  2 800 ₽<Timeline.ItemMeta>Ремонт</Timeline.ItemMeta>
41
42
  </Timeline.ItemValue>
@@ -126,8 +126,8 @@ The navigation of the nearest `AppShell.Root` for a trigger outside the rail (a
126
126
 
127
127
  ## Layout & spacing
128
128
  - Two full-height planes edge to edge: no inset, radius, shadow or border — the boundary is the fill change (the Sidebar rail is layer 1, the panel the page: white rail beside a gray page in light, a rail one step lighter than the near-black page in dark).
129
- - Main hands the page its edge air: `--page-gutter` is the page gutter `--prime-layout-gutter` — `--prime-layout-gutter-s` (16) → `-m` (24) from 640px → `-l` (32) from 1024px; `--page-top` is `--prime-space-6` (24) → `--prime-space-8` (32) from 1024px. `Page.Root` pads itself with them (its top bar steps out to the panel's edges). Main itself pads only the bottom: `--prime-space-16`. Inside Main the page gutter is the small one again, so a frame nested in the page starts from its own gutter, not the shell's.
130
- - A page that fills the panel height (`Page.Root fill`): Main's bottom padding drops to the side gutter, so no long scroll tail stays under it.
129
+ - Main hands the page its edge air: `--page-gutter` is the page gutter `--prime-layout-gutter` — `--prime-layout-gutter-s` (16) → `-m` (24) from 640px → `-l` (32) from 1024px; `--page-top` is `--prime-space-6` (24) → `--prime-space-8` (32) from 1024px. `Page.Root` pads itself with them (its top bar steps out to the panel's edges). Main itself pads only the bottom: `--prime-space-16`, the scroll tail of a long page. Inside Main the page gutter is the small one again, so a frame nested in the page starts from its own gutter, not the shell's.
130
+ - A page that fills the panel height (`Page.Root fill`): Main's bottom padding drops to the side gutter, so no long scroll tail stays under it — beside the Sidebar rail too.
131
131
  - Main scrolls with `overscroll-behavior: contain`, so its scroll never chains to the document.
132
132
  - Nav column: sticky at the top, viewport high, `z-index: --prime-z-sticky` (above the panel, so the compact Sidebar toggle can ride the rail edge); with `fillViewport` it fills the shell height instead.
133
133
  - Footer: no padding of its own (BottomNav keeps the bottom safe-area inset), sticky at the bottom, `z-index: --prime-z-sticky`.
@@ -107,6 +107,7 @@ Without flags the bar is flat at the screen edge on the surface, with a faint to
107
107
  | State | Driven by | DOM |
108
108
  |---|---|---|
109
109
  | current | `current` or a router link setting `aria-current` | `aria-current="page"` on the item; flat, its glyph on a `fill-muted` pill |
110
+ | held | a `Page.Guard` that is `busy` or `dirty` on the page | a press on an item stops before it navigates (a router link included); `dirty` asks first, then repeats it |
110
111
  | pressed | pointer down | the glyph scales to `--prime-motion-press-scale-compact` (`internal/press`), flat and floating |
111
112
  | overflow | items wider than the bar | the items scroll inside the bar with faded edges; no label is cut |
112
113
  | focus | keyboard | focus ring inside the item edge |
@@ -12,14 +12,15 @@
12
12
  - A nested record: a `Breadcrumb` path in `Page.Heading`; in the bar the title reads as its last level.
13
13
  - A text page held to a reading measure (`maxWidth="readable"`), a wide dashboard capped at the content width (`maxWidth="wide"`).
14
14
  - The tiles of a dashboard — KPI cards, charts, panels — as one board (`Page.Grid` + `Page.Cell`).
15
- - A new topic of the page with its own heading and a link or button beside it (`Page.Section`); a settings page with a heading column beside each panel (`layout="aside"`).
15
+ - A new topic of the page with its own heading and a link or button beside it (`Page.Section`, the heading over its content).
16
16
  - A page whose body takes the rest of the panel's height — a log table, a board, a chat (`fill`).
17
17
  - Mail, a messenger, a ticket queue: a list and the chosen record side by side, one at a time on a narrow panel (`Page.Split`).
18
+ - A form that is the whole page (a record's editor, settings with one save): `Page.Guard dirty busy` holds leaving it through the navigation, the path and the browser.
18
19
 
19
20
  ## When not to use
20
21
  - The app frame (navigation rail, scrolling main, phone footer) → use [AppShell](../app-shell/COMPONENT.md); there is no global app header — a bar belongs to its page.
21
22
  - A search across the whole app → a page of its own or a [CommandMenu](../../components/command-menu/COMPONENT.md); the top bar carries only what its page needs.
22
- - A bounded block inside the page → use [Card](../../components/card/COMPONENT.md).
23
+ - A bounded block inside the page, a titled panel of a settings page included → use [Card](../../components/card/COMPONENT.md) with its own `Card.Header`.
23
24
  - An empty / error / not-found page → use [EmptyPage](../../components/empty-page/COMPONENT.md).
24
25
  - A heading inside a card → `Card.Title`; text headings inside prose → use [Typography](../../components/typography/COMPONENT.md).
25
26
  - A grid inside one block (thumbnails, fields, bars in a panel) → the block's own CSS grid; `Page.Grid` places the page's tiles.
@@ -33,6 +34,7 @@ import { Page } from "prime-ui-kit";
33
34
  ## Anatomy
34
35
  ```
35
36
  Page.Root the page column (<div>); maxWidth, fill, labels
37
+ ├─ Page.Guard no DOM: holds leaving the page; busy, dirty (anywhere inside)
36
38
  ├─ Page.TopBar <header> strip at the top edge, sticky; sticky, collapse
37
39
  │ │ (+ the menu button while the Sidebar is off-canvas)
38
40
  │ │ groups in any JSX order, rendered in this order:
@@ -43,7 +45,8 @@ Page.Root the page column (<div>); maxWidth, fill, labe
43
45
  │ ├─ Page.Tools the page's controls in the order written, one wrapper each
44
46
  │ │ ├─ SegmentedControl.Root sections, view (fullWidth unless set)
45
47
  │ │ ├─ SmartFilter.Toolbar search and filter: grows first on a line of its own
46
- │ │ └─ Select.Root … a period, columns
48
+ │ │ ├─ Datepicker.Root a period (fullWidth unless set)
49
+ │ │ └─ Select.Root … a period, columns; the trigger fills its place
47
50
  │ ├─ Page.Actions every button of the page; icons, then «⋯» when short
48
51
  │ └─ Page.Chips SmartFilter.Chips on a line under everything
49
52
  ├─ Page.Header the document header in the flow
@@ -114,10 +117,11 @@ Page.Root the page column (<div>); maxWidth, fill, labe
114
117
 
115
118
  | Prop | Type | Default | Description |
116
119
  |---|---|---|---|
120
+ | `loading` | `boolean` | `false` | Its data is loading: a Skeleton keeps the line's place, so the title never moves; the data then fades in. Sets `aria-busy`. |
117
121
  | `…rest` | `HTMLAttributes<HTMLParagraphElement>` | — | `children`, `className` and the other paragraph attributes. |
118
122
 
119
123
  ### Page.Tools
120
- `ref` → `HTMLDivElement`. The page's tools as kit controls in the order written: sections, `SmartFilter.Toolbar`, view. On a line of their own a search grows first; a `SegmentedControl.Root` is `fullWidth` unless set. Named, a `group`.
124
+ `ref` → `HTMLDivElement`. The page's tools in the order written: sections, `SmartFilter.Toolbar`, view. On a line of their own a search grows first, the rest share the line: a `SegmentedControl.Root` and a `Datepicker.Root` are `fullWidth` unless set. Named, a `group`.
121
125
 
122
126
  | Prop | Type | Default | Description |
123
127
  |---|---|---|---|
@@ -151,18 +155,17 @@ Page.Root the page column (<div>); maxWidth, fill, labe
151
155
  |---|---|---|---|
152
156
  | `label` | `ReactNode` | — (required) | The visible heading (`h2`); it also names the section. |
153
157
  | `description` | `ReactNode` | — | One line of context under the heading, in secondary body-s. |
154
- | `layout` | `"stack" \| "aside"` | `"stack"` | `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. |
155
158
  | `…rest` | `Omit<HTMLAttributes<HTMLElement>, "title">` | — | `children` (the content; `Page.Actions` goes to the heading row), `className` and the other section attributes. |
156
159
 
157
160
  ### Page.Grid
158
- `ref` → `HTMLDivElement`. The grid of the page's tiles (KPI cards, charts, panels): 12 columns, 16 apart on both axes, so its rows read as one board. A size container: cells fold by its own width (see Grid columns).
161
+ `ref` → `HTMLDivElement`. The grid of the page's tiles (KPI cards, charts, panels): 12 columns, 16 apart on both axes, so its rows read as one board. A size container: cells fold by its own width, a wide cell next to a narrow one pairs up (see Grid columns).
159
162
 
160
163
  | Prop | Type | Default | Description |
161
164
  |---|---|---|---|
162
165
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (`Page.Cell`), `className` and the other div attributes. |
163
166
 
164
167
  ### Page.Cell
165
- `ref` → `HTMLDivElement`. One tile place in `Page.Grid`; its child (a Card, a ChartFrame) stretches to it, so the tiles of a row share one height.
168
+ `ref` → `HTMLDivElement`. One tile place in `Page.Grid`; its child (a Card, a ChartFrame) stretches to it, so the tiles of a row share one height; a `Crossfade` child gets `fill` unless it sets it.
166
169
 
167
170
  | Prop | Type | Default | Description |
168
171
  |---|---|---|---|
@@ -170,6 +173,14 @@ Page.Root the page column (<div>); maxWidth, fill, labe
170
173
  | `rows` | `1 \| 2 \| 3 \| 4` | `1` | Rows the cell spans from 40rem: a chart beside two stacked tiles (`ChartFrame.Plot height="fill"` fills the height). |
171
174
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (one tile), `className` and the other div attributes. |
172
175
 
176
+ ### Page.Guard
177
+ No DOM, no ref. Holds the page from anywhere inside it: a press on a `Sidebar.Item`, `BottomNav.Item` or `Breadcrumb.Item` link (a router link too) stops before it navigates, and the browser asks before the tab closes. Renders only its `ConfirmDialog`.
178
+
179
+ | Prop | Type | Default | Description |
180
+ |---|---|---|---|
181
+ | `busy` | `boolean` | `false` | A request runs (a save): a press on those items does nothing until it ends; the browser asks before the tab closes. |
182
+ | `dirty` | `boolean` | `false` | Changes are not saved: a press on those items asks «leave without saving?»; its confirm (`labels.leaveConfirm`) repeats the press, its cancel keeps the page. The browser asks before the tab closes. |
183
+
173
184
  ### Page.Split
174
185
  `ref` → `HTMLDivElement`. List + detail (mail, a messenger, a ticket queue): side by side from 48rem of its own width, one pane at a time below it, focus following the pane that comes into view.
175
186
 
@@ -224,12 +235,8 @@ The size follows the place; the consumer passes nothing, and there are no title
224
235
  | `fill` (Root) | the page is as high as main; the body takes what the bar and the header leave; main keeps gutter-high air under it | a full-height table, board, chat or `Page.Split` in an `AppShell.Root fillViewport` | `false` |
225
236
  | `sticky` (TopBar) | the bar stays at the top of main while the page scrolls under it | every page; `false` for a bar that should scroll away | `true` |
226
237
  | `collapse` (TopBar) | the flow title rises into the bar's title row once it scrolls away | a document page or an overview with the title in the flow | `false` |
227
-
228
- ### layout (Section)
229
- | Value | Looks like | Use when | Default |
230
- |---|---|---|---|
231
- | `stack` | the heading row over the content, 16 apart | a topic of a dashboard, a list or a detail page | ✓ |
232
- | `aside` | a ~16rem heading column beside the content, above it once the content would get < 30rem | a settings page: a heading column beside each panel | |
238
+ | `dirty` (Guard) | nothing until a person leaves: a press on the navigation or the path asks «leave without saving?» | a form that is the page has unsaved changes | `false` |
239
+ | `busy` (Guard) | nothing until a person leaves: a press on the navigation or the path does nothing | the page's save request runs | `false` |
233
240
 
234
241
  ### Top bar plan
235
242
  One plan, chosen from the natural widths of the groups (`data-plan` on the bar's row). The bar takes the first step whose first line fits:
@@ -237,7 +244,7 @@ One plan, chosen from the natural widths of the groups (`data-plan` on the bar's
237
244
  | Step | Looks like | Use when | Default |
238
245
  |---|---|---|---|
239
246
  | `one` | `[Heading 1fr][Tools][Actions]` on one line | everything fits | yes |
240
- | `below` | the tools on a line of their own under the heading and the actions, filling it: a search (`SmartFilter.Toolbar`, `Input.Root`) grows first, switchers share the rest and wrap when one line is not enough | heading + tools + actions do not fit | |
247
+ | `below` | the tools on a line of their own under the heading and the actions, filling it: a search (`SmartFilter.Toolbar`, `Input.Root`) grows first, switchers and fields (`SegmentedControl.Root`, `Datepicker.Root`, a Select) share the rest and wrap when one line is not enough | heading + tools + actions do not fit | |
241
248
  | `icons-secondary` | as `below`; secondary Buttons with a `Button.Icon` show only the icon, the label is their name and their tooltip | heading + actions still do not fit | |
242
249
  | `icons` | the primary Button gives way to its icon too | still too wide | |
243
250
  | `fold` | the secondary Buttons fold into «⋯» (`labels.more`); the primary stays | still too wide, and «⋯» is narrower than their icons (a single secondary keeps its icon); past it a Breadcrumb path folds into «…»; if the path and the title still do not share the line, the heading stacks: the path on its own line over the title, the title and the description wrapping (`data-heading="stack"`); a heading without a path truncates | |
@@ -249,9 +256,11 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
249
256
  |---|---|---|---|---|---|
250
257
  | < 30rem | the row | the row | the row | the row | — |
251
258
  | 30–40rem | half | the row | the row | the row | — |
252
- | 40–64rem | half | a third | half | the row | spans |
259
+ | 40–64rem | half | a third | half (a third in a pair) | the row (2/3 in a pair) | spans |
253
260
  | ≥ 64rem | its span | its span | its span | its span | spans |
254
261
 
262
+ A wide cell (`span` 7–11) written next to a narrow one (`span` 4–5) is a pair: on a 40–64rem grid it takes 2/3 and its neighbour 1/3, so an 8 + 4 row keeps no hole. Cells pair in the order written, two at a time; a `span` 12 never pairs. The flow is `dense`: a small cell fills a hole left by a wide one.
263
+
255
264
  ### Split layout
256
265
  | Value | Looks like | Use when | Default |
257
266
  |---|---|---|---|
@@ -262,6 +271,7 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
262
271
  | State | Driven by | DOM |
263
272
  |---|---|---|
264
273
  | capped page | `maxWidth` | `data-max-width="readable" \| "wide"` on Root (absent for `full`) |
274
+ | line of data loading | `loading` on Description | a Skeleton in the line (its height kept, so the title never moves), `aria-busy`; the data fades in under the same element |
265
275
  | filling | `fill` | `data-fill="true"` on Root; `AppShell.Main` drops its long bottom air for it |
266
276
  | sticky bar | `sticky` | `data-sticky="true"` on TopBar |
267
277
  | title repeated in the bar | `collapse` and the flow title out of view | `data-collapsed="true"` on TopBar; the echo rises in (`transform` on the fast movement clock, `opacity` on the fast fade; reduced motion keeps the fade). After a `Breadcrumb` path the echo is the path's last level (an `aria-hidden`, `inert` `Breadcrumb.Item`), so the path folds its levels into «…» for it (`data-collapsed` on the Breadcrumb) and the title stays readable; a path whose last level already reads as the title (the same words, however spaced or cased) gets no echo |
@@ -271,14 +281,15 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
271
281
  | folded buttons | plan `fold` | the secondary Buttons' wrappers `hidden`; a «⋯» Dropdown in their place, their `menuitem`s inside |
272
282
  | heading place | inside TopBar or in the flow | `data-place="bar" \| "flow"` on Heading |
273
283
  | with actions | `Page.Actions` child | `data-has-actions="true"` on Header / the section heading row |
274
- | section heading column | `layout="aside"` on Section | `data-layout="aside"` on Section (absent for `stack`) |
275
284
  | tile span | `span` / `rows` on Cell | `data-span="1"…"12"` (always), `data-rows="2"…"4"` (absent for 1) |
285
+ | tile pair | a `span` 7–11 Cell next to a `span` 4–5 Cell in the Grid | `data-pair="wide" \| "narrow"` on both Cells (set by the Grid) |
286
+ | held | `Page.Guard` `busy` or `dirty` | a press on a `Sidebar.Item`, `Sidebar.Brand`, `BottomNav.Item` or `Breadcrumb.Item` link is stopped (capture phase, before the item's and the router link's handlers); `dirty` opens the `ConfirmDialog` and its confirm repeats the press once the dialog has closed (its texts are the `leave*` keys of `labels` on `Page.Root`); a `beforeunload` listener while either is on. A press that opens a new tab (a modifier key, a middle click, `target="_blank"`) and navigation from code are never held |
276
287
  | detail open | `open` / `defaultOpen` on Split | `data-open="true"` on Split; narrow: the list is `display: none`, else the detail is |
277
288
  | pane swap | `open` changes on a narrow split | the pane that comes into view enters once (`enterMotion.enterBase`); focus moves into the detail, or back to the list control |
278
289
 
279
290
  ## Layout & spacing
280
291
  - The edge air comes from the host: `AppShell.Main` sets `--page-gutter` (the page gutter, 16 → 24 → 32) and `--page-top` (24 → 32 from 1024px); `Page.Root` pads itself with them. Outside a host the page brings no air. The page resets both for what it holds, so a page nested in it starts clean.
281
- - `Page.TopBar` steps out of the gutter to the panel's edges and keeps the gutter inside; padding 12 above (or the top safe-area inset) and below; the page's top air goes under it. The first line is as high as the Sidebar brand row (control m + 8), so the two read as one line. Groups 16 apart on a line, lines 12 apart; tools 8 apart; buttons 8 apart.
292
+ - `Page.TopBar` steps out of the gutter to the panel's edges and keeps the gutter inside; padding 12 above (or the top safe-area inset) and below; what follows starts 24 under its controls (header → body; the bar's own 12 counts in). The first line is as high as the Sidebar brand row (control m + 8), so the two read as one line. Groups 16 apart on a line, lines 12 apart; tools 8 apart; buttons 8 apart.
282
293
  - In the bar only the heading's column gives (`minmax(0, 1fr)`): the tools and the actions keep their width. The heading keeps its natural width until every button step is taken; then a heading with a path stacks (the path over the title, the title and the description wrapping), so no level is cut to a fragment; a heading without a path truncates its title.
283
294
  - The plan is measured, never chosen by breakpoints: the bar lays itself out from its own width.
284
295
  - Header (flow) → Body 24; blocks inside Body 24; a `Page.Section` 40 from its neighbours (the body's 24 + 16); tile → tile in a Grid 16 on both axes; section heading → content 16, heading → description 4; flow Title → Description 8; heading → Actions 24 side by side, 16 once they wrap below.
@@ -303,6 +314,7 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
303
314
  - The menu button carries `aria-expanded` and `aria-controls` → the Sidebar's `<nav>`. A Button that shows only its icon keeps its label as its accessible name; its tooltip repeats it. Folded Buttons are `hidden` in the bar and `menuitem`s in the «⋯» menu (named by `labels.more`), named by the Button's `aria-label` or its text, disabled with it; their `onClick` runs from the menu.
304
315
  - `Page.TopBar` renders its groups in reading order — `Page.Heading`, `Page.Tools`, `Page.Actions`, `Page.Chips` — whatever the JSX order; the plan moves them only visually. A named `Page.Tools` is a `group`. The chevron between the path and the title is decorative (`aria-hidden`): the title is the `<h1>`, outside the path's `<nav>`.
305
316
  - `Page.Section` is a `<section>` named by its own `<h2>` (`label`) through `aria-labelledby`; the heading levels go h1 (page) → h2 (section) → h3 (a card title inside it).
317
+ - `Page.Guard` holds a press made by pointer or keyboard alike (Enter on a link is a click). Its confirm is a `ConfirmDialog` (`alertdialog`): focus moves into it and, on «stay», returns to the item that was pressed.
306
318
  - Name both panes of `Page.Split` with `aria-label`: a named pane is `role="region"`. On a narrow split opening the detail moves focus into it and going back returns focus to the list control; the hidden pane is `display: none`. `Page.BackButton` is named by `labels.back` and shown only while the split shows one pane.
307
319
 
308
320
  ### Labels
@@ -313,6 +325,10 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
313
325
  | `menu` | `"Открыть меню"` | Name of the menu button `Page.TopBar` draws while the shell's Sidebar is off-canvas. |
314
326
  | `more` | `"Ещё действия"` | Name of the «⋯» button the secondary Buttons of the top bar's `Page.Actions` fold into. |
315
327
  | `back` | `"Назад к списку"` | Name of `Page.BackButton`. |
328
+ | `leaveTitle` | `"Уйти без сохранения?"` | Title of the confirm `Page.Guard` opens when a person leaves a `dirty` page. |
329
+ | `leaveDescription` | `"Изменения на странице пропадут."` | Its description. |
330
+ | `leaveConfirm` | `"Уйти"` | Its confirm button: leave and lose the changes. |
331
+ | `leaveCancel` | `"Остаться"` | Its cancel button: stay on the page. |
316
332
 
317
333
  ## Examples
318
334
  | Example | Shows |
@@ -321,16 +337,19 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
321
337
  | [variants.tsx](examples/variants.tsx) | The same page at three caps: the whole main, a wide dashboard column, a reading column — `maxWidth`. |
322
338
  | [structure.tsx](examples/structure.tsx) | A document page in the flow: the path over the large title, its line of data and the page's buttons beside them, then the body with a section — `Page.Header`, `Page.Heading`, `Page.Description`, `Page.Section`. |
323
339
  | [top-bar.tsx](examples/top-bar.tsx) | A nested record in the top bar: the path ending in the title, a line of data, the page's tools and its buttons — `Page.TopBar`, `Page.Heading`, `Page.Tools`, `Page.Actions`. |
340
+ | [loading.tsx](examples/loading.tsx) | A page whose figures load after it opens: the line of data keeps its place under the title while it loads, so nothing in the bar moves — `loading`. |
324
341
  | [collapse.tsx](examples/collapse.tsx) | An overview whose large title scrolls away under the top bar: the bar repeats it with its line of data and keeps the period and the page's buttons in reach — `collapse`. |
325
342
  | [tools.tsx](examples/tools.tsx) | The orders page's tools in its top bar: sections with counts, search and filter, the period, in the order written; the active filters on a line under everything — `Page.Tools`, `Page.Chips`. |
326
343
  | [actions-collapse.tsx](examples/actions-collapse.tsx) | The buttons of an invoices page on a tablet-wide column: the line runs short, so the secondary buttons show only their icons with the label as the name and the tooltip, the primary keeps its label — `Page.Actions`, `Button.Icon`. |
327
- | [sections.tsx](examples/sections.tsx) | A page with titled sections: tiles of one screen close together, a new topic set off by air, a link at the end of the heading, a heading column beside a panel — `Page.Section`, `label`, `description`, `Page.Actions`, `layout`. |
344
+ | [sections.tsx](examples/sections.tsx) | A page with titled sections: tiles of one screen close together, a new topic set off by air, a link at the end of the heading — `Page.Section`, `label`, `description`, `Page.Actions`. |
328
345
  | [grid.tsx](examples/grid.tsx) | A dashboard board: four KPI tiles, a chart two rows high beside two panels, one gap on both axes — `Page.Grid`, `Page.Cell`, `span`, `rows`. |
329
346
  | [list-detail.tsx](examples/list-detail.tsx) | A ticket queue as list + detail on a full-height page: side by side on a wide panel, one pane at a time with a back button on a narrow one; the chosen ticket cross-fades in — `Page.Split`, `Page.List`, `Page.Detail`, `Page.BackButton`, `fill`. |
347
+ | [guard.tsx](examples/guard.tsx) | A client's card edited on its own page: after an edit a press on the navigation or the path asks before the changes are lost, and while it saves nothing leaves — `Page.Guard`, `dirty`, `busy`. |
330
348
  | [narrow.tsx](examples/narrow.tsx) | On a phone the top bar leads with the menu button that opens the navigation by itself, the primary button gives way to its icon and the secondary ones fold into «⋯» — `offCanvas`, `Page.Actions`. |
331
- | [screen.tsx](examples/screen.tsx) | A settings page as a document: the path in the top bar that repeats the title once it scrolls away, the large title in the flow, sections with a heading column and a panel, a form saved by its own button, switches that apply at once and a danger zone with a confirm — `collapse`, `layout`, `maxWidth`. |
349
+ | [screen.tsx](examples/screen.tsx) | A settings page as a document: the path in the top bar that repeats the title once it scrolls away, the large title in the flow, each section a card with its own heading, a form saved by its own button, switches that apply at once and a danger zone with a confirm — `collapse`, `maxWidth`, `Card.Header`. |
332
350
 
333
351
  ## Mistakes
352
+ - `{ready ? <Page.Description>…</Page.Description> : null}` → the line appears after the load and the title jumps; keep it mounted with `loading` until the data comes.
334
353
  - A global header above every page → the app is navigation and the page; each page renders its own `Page.TopBar` with only what it needs.
335
354
  - Padding on `Page.Root` or around it to imitate gutters → the page takes them from `AppShell.Main`.
336
355
  - Two titles — one in the bar, one in the flow → one `Page.Title`; a flow title with `collapse` is repeated in the bar by itself.
@@ -344,10 +363,13 @@ The actions stay on the first line beside the heading at every step; `Page.Chips
344
363
  - Hiding buttons with media queries on a phone → `Page.Actions` in the bar collapses and folds them by measured width.
345
364
  - Wrapping a group of the bar or `Page.Title` in your own component → the bar and the heading find their parts among their direct children.
346
365
  - A hand-made `<section>` + `Typography as="h2"` + own gap CSS → `Page.Section label`.
366
+ - A heading column beside a panel (the section's title and description on the left, its card on the right) → it wastes the width and breaks the reading line; a titled block is a `Card.Root` with its own `Card.Header` (`Card.Title as="h2"`, `Card.Description`) stacked in the flow.
347
367
  - Each row of cards in its own grid → put every tile of the screen in one `Page.Grid`.
348
368
  - Computing a height from `100vh` minus the bar for a full-height table or chat → `Page.Root fill` and a filling child.
349
369
  - A list + detail built with window media queries → `Page.Split` follows its own width.
370
+ - A `beforeunload` listener and a ConfirmDialog on a crumb of your own for unsaved changes → `Page.Guard dirty`; it also holds the Sidebar and BottomNav items.
371
+ - Tiles of a row cut to 6 + 6 so a mid-width grid keeps no hole → write the 8 and the 4 next to each other; the grid pairs them as 2/3 + 1/3.
350
372
 
351
373
  ## Related
352
- - **Built from:** [AppShell](../app-shell/COMPONENT.md) (the navigation the menu button opens), [Breadcrumb](../../components/breadcrumb/COMPONENT.md) (the path the heading places), [Button](../../components/button/COMPONENT.md) (the menu button, «⋯», `Page.BackButton`; the icon-only label hook), [Dropdown](../../components/dropdown/COMPONENT.md) (folded buttons), [Tooltip](../../components/tooltip/COMPONENT.md) (the label of an icon-only button), [SegmentedControl](../../components/segmented-control/COMPONENT.md) (`fullWidth` in `Page.Tools`), [SmartFilter](../../components/smart-filter/COMPONENT.md) and [Input](../../components/input/COMPONENT.md) (the search that grows first in `Page.Tools`), Icon (`nav.menu`, `nav.chevronRight`, `action.more`, `nav.chevronLeft`)
374
+ - **Built from:** [AppShell](../app-shell/COMPONENT.md) (the navigation the menu button opens), [Breadcrumb](../../components/breadcrumb/COMPONENT.md) (the path the heading places), [Button](../../components/button/COMPONENT.md) (the menu button, «⋯», `Page.BackButton`; the icon-only label hook), [Dropdown](../../components/dropdown/COMPONENT.md) (folded buttons), [Tooltip](../../components/tooltip/COMPONENT.md) (the label of an icon-only button), [SegmentedControl](../../components/segmented-control/COMPONENT.md) and [Datepicker](../../components/datepicker/COMPONENT.md) (`fullWidth` in `Page.Tools`), [SmartFilter](../../components/smart-filter/COMPONENT.md) and [Input](../../components/input/COMPONENT.md) (the search that grows first in `Page.Tools`), [Crossfade](../../components/crossfade/COMPONENT.md) (a `Page.Cell` child gets `fill`), [Skeleton](../../components/skeleton/COMPONENT.md) (the line of data while it loads), [ConfirmDialog](../../components/confirm-dialog/COMPONENT.md) (the confirm of `Page.Guard`), Icon (`nav.menu`, `nav.chevronRight`, `action.more`, `nav.chevronLeft`)
353
375
  - **See also:** [Sidebar](../sidebar/COMPONENT.md), [BottomNav](../bottom-nav/COMPONENT.md), [Select](../../components/select/COMPONENT.md), [Card](../../components/card/COMPONENT.md), [ChartFrame](../../components/chart-frame/COMPONENT.md), [List](../../components/list/COMPONENT.md), [EmptyPage](../../components/empty-page/COMPONENT.md), [Drawer](../../components/drawer/COMPONENT.md), [VisuallyHidden](../../components/visually-hidden/COMPONENT.md)
@@ -77,6 +77,13 @@
77
77
  padding: var(--prime-space-2) var(--prime-space-4);
78
78
  }
79
79
 
80
+ /* guard.tsx: the fields of the form, one column of the form width. */
81
+ .fields {
82
+ display: grid;
83
+ gap: var(--prime-space-4);
84
+ max-width: var(--prime-layout-form-width-m);
85
+ }
86
+
80
87
  /* actions-collapse.tsx: a tablet-width page column (600px). */
81
88
  .tablet {
82
89
  width: 100%;
@@ -0,0 +1,87 @@
1
+ /** A client's card edited on its own page: after an edit a press on the navigation or the path asks before the changes are lost, and while it saves nothing leaves — `Page.Guard`, `dirty`, `busy`. */
2
+ import { AppShell, Breadcrumb, Button, Card, Icon, Input, Page, Sidebar } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ const SAVED_NAME = "ООО «Северный ветер»";
8
+ const SAVED_MANAGER = "Анна Морозова";
9
+
10
+ export default function PageGuardExample() {
11
+ const [name, setName] = React.useState(SAVED_NAME);
12
+ const [manager, setManager] = React.useState(SAVED_MANAGER);
13
+ const [saved, setSaved] = React.useState({ name: SAVED_NAME, manager: SAVED_MANAGER });
14
+ const [saving, setSaving] = React.useState(false);
15
+ const formId = React.useId();
16
+ const dirty = name !== saved.name || manager !== saved.manager;
17
+
18
+ const save = (event: React.FormEvent<HTMLFormElement>) => {
19
+ event.preventDefault();
20
+ setSaving(true);
21
+ window.setTimeout(() => {
22
+ setSaved({ name, manager });
23
+ setSaving(false);
24
+ }, 1200);
25
+ };
26
+
27
+ return (
28
+ <div className={styles.window}>
29
+ <AppShell.Root fillViewport className={styles.shell}>
30
+ <AppShell.Nav>
31
+ <Sidebar.Root>
32
+ <Sidebar.Content>
33
+ <Sidebar.Item href="#orders">
34
+ <Sidebar.ItemIcon>
35
+ <Icon name="object.package" />
36
+ </Sidebar.ItemIcon>
37
+ Заказы
38
+ </Sidebar.Item>
39
+ <Sidebar.Item href="#clients" current>
40
+ <Sidebar.ItemIcon>
41
+ <Icon name="object.users" />
42
+ </Sidebar.ItemIcon>
43
+ Клиенты
44
+ </Sidebar.Item>
45
+ </Sidebar.Content>
46
+ </Sidebar.Root>
47
+ </AppShell.Nav>
48
+ <AppShell.Main>
49
+ <Page.Root>
50
+ <Page.Guard dirty={dirty} busy={saving} />
51
+ <Page.TopBar>
52
+ <Page.Heading>
53
+ <Breadcrumb.Root>
54
+ <Breadcrumb.Item href="#clients">Клиенты</Breadcrumb.Item>
55
+ </Breadcrumb.Root>
56
+ <Page.Title>{saved.name}</Page.Title>
57
+ </Page.Heading>
58
+ <Page.Actions>
59
+ <Button.Root type="submit" form={formId} loading={saving} disabled={!dirty}>
60
+ Сохранить
61
+ </Button.Root>
62
+ </Page.Actions>
63
+ </Page.TopBar>
64
+ <Page.Body>
65
+ <Card.Root>
66
+ <Card.Body>
67
+ <form id={formId} className={styles.fields} onSubmit={save}>
68
+ <Input.Root label="Название">
69
+ <Input.Wrapper>
70
+ <Input.Field value={name} onValueChange={setName} />
71
+ </Input.Wrapper>
72
+ </Input.Root>
73
+ <Input.Root label="Менеджер">
74
+ <Input.Wrapper>
75
+ <Input.Field value={manager} onValueChange={setManager} />
76
+ </Input.Wrapper>
77
+ </Input.Root>
78
+ </form>
79
+ </Card.Body>
80
+ </Card.Root>
81
+ </Page.Body>
82
+ </Page.Root>
83
+ </AppShell.Main>
84
+ </AppShell.Root>
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,57 @@
1
+ /** A page whose figures load after it opens: the line of data keeps its place under the title while it loads, so nothing in the bar moves — `loading`. */
2
+ import { Button, Card, Crossfade, Icon, Page, Skeleton, Typography } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ const ORDERS = [
6
+ { id: "№ 4821", client: "ООО «Север»", sum: "84 000 ₽" },
7
+ { id: "№ 4820", client: "ИП Котов", sum: "12 500 ₽" },
8
+ { id: "№ 4819", client: "ООО «Вектор»", sum: "230 000 ₽" },
9
+ ];
10
+
11
+ const LOAD_MS = 1200;
12
+
13
+ export default function PageLoadingExample() {
14
+ const [loading, setLoading] = React.useState(true);
15
+
16
+ React.useEffect(() => {
17
+ if (!loading) return;
18
+ const timer = window.setTimeout(() => setLoading(false), LOAD_MS);
19
+ return () => window.clearTimeout(timer);
20
+ }, [loading]);
21
+
22
+ return (
23
+ <Page.Root>
24
+ <Page.TopBar sticky={false}>
25
+ <Page.Heading>
26
+ <Page.Title>Заказы</Page.Title>
27
+ <Page.Description loading={loading}>128 в работе · 6 ждут оплаты</Page.Description>
28
+ </Page.Heading>
29
+ <Page.Actions>
30
+ <Button.Root variant="soft" tone="neutral" onClick={() => setLoading(true)}>
31
+ <Button.Icon>
32
+ <Icon name="action.refresh" />
33
+ </Button.Icon>
34
+ Обновить
35
+ </Button.Root>
36
+ </Page.Actions>
37
+ </Page.TopBar>
38
+ <Page.Body>
39
+ <Card.Root variant="list" aria-busy={loading || undefined}>
40
+ <Card.Body>
41
+ <Crossfade state={loading ? "loading" : "data"}>
42
+ {loading ? (
43
+ <Skeleton lines={ORDERS.length} />
44
+ ) : (
45
+ ORDERS.map((order) => (
46
+ <Typography key={order.id} variant="body-m">
47
+ {order.id} · {order.client} · {order.sum}
48
+ </Typography>
49
+ ))
50
+ )}
51
+ </Crossfade>
52
+ </Card.Body>
53
+ </Card.Root>
54
+ </Page.Body>
55
+ </Page.Root>
56
+ );
57
+ }
@@ -1,4 +1,4 @@
1
- /* Settings page: each section is a Page.Section layout="aside"; here only the panels' insides. */
1
+ /* Settings page: each section is a Card with its own header; here only the cards' insides. */
2
2
 
3
3
  /* Field → field 20. */
4
4