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
@@ -5,10 +5,12 @@ The app is navigation and the page: `AppShell.Root` with the Sidebar in `AppShel
5
5
  app's; everything inside Main is the page's — a screen renders one `Page.Root` with its own top
6
6
  (`Page.TopBar`: the page title and the page's tools, [composition.md](composition.md#1-page-skeleton))
7
7
  and its body in `Page.Body`.
8
- `AppShell.Main` hands the page its gutters and top air and `Page` spaces it — blocks 24, a titled
8
+ `AppShell.Main` hands the page its gutters and its edge air and `Page` spaces it — blocks 24, a titled
9
9
  `Page.Section` 40, tiles in a `Page.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-*`.
10
- The page's top air is part of the frame — 24, then 32 from 1024px: the page's first block
11
- starts there; never cancel it with a negative margin or a padding override.
10
+ The edge air is part of the frame. On top: 24, then 32 from 1024px, where a page without a bar
11
+ starts; under a `Page.TopBar` the body starts 24 below the bar's controls. At the bottom: a long
12
+ page keeps a 64 scroll tail, a `Page.Root fill` page only the side gutter, beside the Sidebar rail
13
+ as well. Never cancel it with a negative margin or a padding override.
12
14
 
13
15
  The page recipes are whole, working screens — the `screen.tsx` example of their main component
14
16
  ([the list](composition.md#screens)): start from the closest one and keep its skeleton. The rules behind them are in [composition.md](composition.md).
@@ -153,7 +155,7 @@ shows only icons); none on the children of a sub-list.
153
155
  page's title and tools are in its `Page.TopBar`, its blocks go into `Page.Body`
154
156
  ([the page skeleton](composition.md#1-page-skeleton)).
155
157
  - Document pages — settings and long forms: `Page.Root maxWidth="wide"` with `Page.Header` in the
156
- flow (the page's h1) and a `Page.Section layout="aside"` (heading column + panel) per section
158
+ flow (the page's h1) and a `Card.Root` with its own `Card.Header` per section
157
159
  ([the settings page](reference/layout/page/examples/screen.tsx)), or `maxWidth="readable"` for a single
158
160
  narrow column of fields.
159
161
  - Long reading text (terms, help): `Page.Root maxWidth="readable"` with `Page.Header` in the flow.
@@ -12,6 +12,7 @@
12
12
  - A selected value, keyword or applied filter the user can remove (`onRemove`).
13
13
  - Values in a filter panel that toggle on press and hide with a hover action (the `pressed` triad, `Badge.Action`).
14
14
  - A short code as a square tile (a file format «PDF») — `shape="square"`.
15
+ - Several badges as one value (the tags of a client, a record's labels) — `Badge.Group`: it wraps and keeps one gap.
15
16
 
16
17
  ## When not to use
17
18
  - A multi-select field with chips → use [TagSelect](../tag-select/COMPONENT.md) (it renders badges itself).
@@ -33,6 +34,8 @@ Badge.Root <span>: fill, tier dimensions
33
34
  │ (a toggle <button> with the pressed triad; a <span> with a trailing segment; none when read-only)
34
35
  ├─ Badge.Action optional end segment revealed on hover / focus (always shown on touch screens)
35
36
  └─ remove segment with onRemove: full height, the whole end is the hit area (Icon action.close)
37
+
38
+ Badge.Group <div>: a wrapping row of Badge.Root, one value; `size` sets every badge's tier
36
39
  ```
37
40
 
38
41
  ## API
@@ -60,6 +63,14 @@ Badge.Root <span>: fill, tier dimensions
60
63
  | `children` | `ReactNode` | — | Text, `Badge.Dot`, `Badge.Icon`, one `Badge.Action`. Only `Badge.Icon` children (no remove or action) make a square icon-only badge. |
61
64
  | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "onClick" \| "color">` | — | `aria-label` (an icon-only badge named by it is an image) and the other span attributes; no `onClick` — a pressable chip is the toggle. |
62
65
 
66
+ ### Badge.Group
67
+ `ref` → `HTMLDivElement`. A row of badges as one value (the tags of a record): it wraps onto more lines, the badges a chip gap apart.
68
+
69
+ | Prop | Type | Default | Description |
70
+ |---|---|---|---|
71
+ | `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | the host tier, one step down | Tier of every badge in the group; a badge's own `size` wins. Small tiers stand closer. |
72
+ | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (the `Badge.Root`s), `className`, `aria-label` (a named group is `role="group"`) and the other div attributes. |
73
+
63
74
  ### Badge.Icon
64
75
  `ref` → `HTMLSpanElement`. A `<span>` holding one icon at the tier icon size. At the first or last position it becomes a full-height edge segment.
65
76
 
@@ -163,7 +174,7 @@ A status takes `tone`; a category takes `color`. The two never go together (the
163
174
  - `Badge.Dot` is 6px, 8px from `l`; alone it takes the tier of the surrounding control; `color` paints it in the solid step of that hue.
164
175
  - The root never clips: the text ellipsizes in its own box, so the segments' touch areas reach past the chip.
165
176
  - `Badge.Action` room is reserved, so revealing it never changes the badge width.
166
- - Several badges in a row: `gap: var(--prime-space-2)`.
177
+ - Several badges in a row: `Badge.Group` — it wraps, badges 8 apart (4 at the `xs` and `s` tiers).
167
178
  - In a list row the badge is the trailing column; the title column gets `min-width: 0` and an ellipsis.
168
179
 
169
180
  ## Accessibility
@@ -206,6 +217,7 @@ A status takes `tone`; a category takes `color`. The two never go together (the
206
217
  | [server-tags.tsx](examples/server-tags.tsx) | Order tags removed on the server: a badge waits dimmed with a spinner, leaves once the server agrees and stays with an announcement when it refuses — `onRemove`, `labels`. |
207
218
  | [filter-values.tsx](examples/filter-values.tsx) | Filter values as toggles with a hide action that slides in without changing the width — `pressed`, `onPressedChange`, `Badge.Action`, `persistent`. |
208
219
  | [square.tsx](examples/square.tsx) | File formats as square tiles of text, large enough for three letters; the hue sorts them by kind — `shape`, `size`, `color`. |
220
+ | [tag-row.tsx](examples/tag-row.tsx) | The tags of a client as one value: the row wraps in a narrow column and keeps one gap between badges — `Badge.Group`, `size`. |
209
221
  | [controlled.tsx](examples/controlled.tsx) | Delivery cities as toggle chips whose state the screen owns: the summary counts the chosen ones — `pressed`, `onPressedChange`. |
210
222
 
211
223
  ## Mistakes
@@ -217,6 +229,7 @@ A status takes `tone`; a category takes `color`. The two never go together (the
217
229
  - Several removable badges with the default «Удалить» → pass `labels.remove` with the badge text.
218
230
  - A colored badge with no text → keep a word; color alone is not a signal.
219
231
  - An explicit `size` inside a control → drop it; the badge takes the tier one step down.
232
+ - A row of badges in a hand-made flex wrapper with its own gap → `Badge.Group`.
220
233
  - `onRemove` together with `Badge.Action` → one end segment per badge.
221
234
  - Removing the badge from your list first and putting it back when the request fails → return the request's promise and remove it after it succeeds; the badge waits meanwhile.
222
235
 
@@ -23,3 +23,10 @@
23
23
  align-items: center;
24
24
  gap: var(--prime-space-2);
25
25
  }
26
+
27
+ /* tag-row.tsx: a 320 px column, narrow enough for the row of badges to wrap. */
28
+ .column {
29
+ display: grid;
30
+ gap: var(--prime-space-6);
31
+ inline-size: min(100%, calc(var(--prime-space-20) * 4));
32
+ }
@@ -0,0 +1,34 @@
1
+ /** The tags of a client as one value: the row wraps in a narrow column and keeps one gap between badges — `Badge.Group`, `size`. */
2
+ import { Badge } from "prime-ui-kit";
3
+
4
+ import styles from "./examples.module.css";
5
+
6
+ const TAGS = [
7
+ { label: "Ключевой клиент", color: "purple" },
8
+ { label: "Оптовые закупки", color: "blue" },
9
+ { label: "Отсрочка 30 дней", color: "orange" },
10
+ { label: "ЭДО", color: "teal" },
11
+ { label: "Москва", color: "gray" },
12
+ { label: "Новый договор", color: "green" },
13
+ ] as const;
14
+
15
+ export default function BadgeTagRowExample() {
16
+ return (
17
+ <div className={styles.column}>
18
+ <Badge.Group aria-label="Теги клиента">
19
+ {TAGS.map((tag) => (
20
+ <Badge.Root key={tag.label} color={tag.color}>
21
+ {tag.label}
22
+ </Badge.Root>
23
+ ))}
24
+ </Badge.Group>
25
+ <Badge.Group size="s" aria-label="Теги заказа">
26
+ {TAGS.slice(0, 4).map((tag) => (
27
+ <Badge.Root key={tag.label} color={tag.color}>
28
+ {tag.label}
29
+ </Badge.Root>
30
+ ))}
31
+ </Badge.Group>
32
+ </div>
33
+ );
34
+ }
@@ -23,7 +23,7 @@ import { Breadcrumb } from "prime-ui-kit";
23
23
  ## Anatomy
24
24
  ```
25
25
  Breadcrumb.Root <nav aria-label> + <ol>; size; chevrons between levels; auto-collapse into a «…» menu
26
- ├─ Breadcrumb.Item <li>: a link (href → LinkButton), text, or the current page (current)
26
+ ├─ Breadcrumb.Item <li>: a link (href → LinkButton, or a router link with asChild), text, or the current page (current)
27
27
  └─ Breadcrumb.Ellipsis <li> with «…» for levels skipped on purpose
28
28
  ```
29
29
 
@@ -42,14 +42,15 @@ Breadcrumb.Root <nav aria-label> + <ol>; size; chevrons between levels
42
42
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `className` and the other `nav` attributes; an `aria-label` here overrides `labels.nav`. |
43
43
 
44
44
  ### Breadcrumb.Item
45
- `ref` → `HTMLLIElement`. `<li>`: a muted `LinkButton` (`href`), plain text, or the current page (`current`).
45
+ `ref` → `HTMLLIElement`. `<li>`: a muted `LinkButton` (`href`, or the consumer's router link with `asChild`), plain text, or the current page (`current`). A press on the link goes through `Page.Guard`.
46
46
 
47
47
  | Prop | Type | Default | Description |
48
48
  |---|---|---|---|
49
- | `href` | `string` | — | Renders a link; without it the item is text. |
49
+ | `href` | `string` | — | Renders a link; without it (and without `asChild`) the item is text. |
50
+ | `asChild` | `boolean` | `false` | The single child (a router `Link`) is the level's link with the same look; its own text is the level. In the «…» menu of a folded path it is the menu item. |
50
51
  | `current` | `boolean` | — | Current page: `aria-current="page"` (on the link when `href` is set), primary text, medium weight. Usually the last item, without `href`. |
51
52
  | `aria-label` | `string` | — | Name of a link without visible text (e.g. a home icon); set on the link, not the `li`. |
52
- | `children` | `ReactNode` | — | Text or an `Icon`. A string also becomes the `title` of a text item (full text when truncated). |
53
+ | `children` | `ReactNode` | — | Text or an `Icon`; with `asChild` one element. A string also becomes the `title` of a text item (full text when truncated). |
53
54
  | `…rest` | `Omit<LiHTMLAttributes<HTMLLIElement>, "aria-label">` | — | `className` and the other `li` attributes. |
54
55
 
55
56
  ### Breadcrumb.Ellipsis
@@ -77,6 +78,7 @@ Breadcrumb.Root <nav aria-label> + <ol>; size; chevrons between levels
77
78
  | text (no `href`, no `current`) | muted text | a level without its own page | yes |
78
79
  | current (`current`) | primary text, medium weight | the last item — this page | |
79
80
  | icon link (`href` + `aria-label`, `Icon` child) | icon at the tier icon size | a «home» root | |
81
+ | router link (`asChild`, one `Link` child) | the same muted link, the consumer's element | an app with a client router: the path moves without a reload | |
80
82
 
81
83
  Use links for every ancestor and `current` on the last item; `s` above a `heading-m` page title. Avoid `href` on the current item and two `current` items.
82
84
 
@@ -89,6 +91,7 @@ Use links for every ancestor and `current` on the last item; `s` above a `headin
89
91
  | auto-collapse | 2+ levels whose whole path does not fit the width uncut (measured on every resize) | `data-collapsible="true"`, `data-collapsed="true"`; middle levels are `display: none` and replaced by «… ›»: a ghost menu button (`Dropdown.Trigger`, named by `labels.ellipsis`) whose menu lists them — links stay links, a level without `href` is a disabled row |
90
92
  | deep collapse | the path folded as above still does not fit | `data-collapsed="deep"`; the first level folds too: «…» › current page, the menu lists every level before it |
91
93
  | collapse menu open | a press, `Enter` or `ArrowDown` on the «…» | `aria-expanded="true"` on the button; a `role="menu"` panel at the tier size |
94
+ | held | a `Page.Guard` that is `busy` or `dirty` on the page | a press on a level's link (in the path or the «…» menu) stops before it navigates; `dirty` asks first, then repeats it |
92
95
 
93
96
  Root attributes: `data-size`, `data-collapsible` (`"true"` / `"false"`), `data-collapsed` (`"true"` / `"deep"`).
94
97
 
@@ -133,12 +136,14 @@ Root attributes: `data-size`, `data-collapsible` (`"true"` / `"false"`), `data-c
133
136
  | [with-icon.tsx](examples/with-icon.tsx) | The root level as a home icon; a link without text is named for screen readers — `aria-label`. |
134
137
  | [overflow.tsx](examples/overflow.tsx) | A long path: levels truncate, and in a 320px container the middle ones collapse into a «…» menu that lists them — `Breadcrumb.Item`, `current`. |
135
138
  | [ellipsis.tsx](examples/ellipsis.tsx) | Levels skipped on purpose shown as «…» whose hidden text names them for screen readers — `Breadcrumb.Ellipsis`, `labels`. |
139
+ | [router.tsx](examples/router.tsx) | Router links as the levels above: the path moves without a reload; render it inside a router — `asChild`, `current`. |
136
140
 
137
141
  ## Mistakes
138
142
  - Separators by hand between items → the root draws the chevrons.
139
143
  - `<Breadcrumb.Item href="…" current>` → the current page has no `href`.
140
144
  - Breadcrumb inside a flex row collapsing to zero width → give it `flex: 1 1 auto; min-width: 0`.
141
145
  - Home icon link without `aria-label` → add `aria-label="Главная"`.
146
+ - `href` + `onClick` with `preventDefault()` and `navigate()` for a router → `asChild` with the router's `Link`.
142
147
 
143
148
  ## Related
144
149
  - **Built from:** [LinkButton](../link-button/COMPONENT.md) (`tone="muted"`), [Button](../button/COMPONENT.md) and [Dropdown](../dropdown/COMPONENT.md) (the collapse menu), Icon (`nav.chevronRight`), [VisuallyHidden](../visually-hidden/COMPONENT.md)
@@ -0,0 +1,17 @@
1
+ /** Router links as the levels above: the path moves without a reload; render it inside a router — `asChild`, `current`. */
2
+ import { Breadcrumb } from "prime-ui-kit";
3
+ import { Link } from "react-router-dom";
4
+
5
+ export default function BreadcrumbRouterExample() {
6
+ return (
7
+ <Breadcrumb.Root>
8
+ <Breadcrumb.Item asChild>
9
+ <Link to="/crm">CRM</Link>
10
+ </Breadcrumb.Item>
11
+ <Breadcrumb.Item asChild>
12
+ <Link to="/crm/clients">Клиенты</Link>
13
+ </Breadcrumb.Item>
14
+ <Breadcrumb.Item current>ООО «Северный ветер»</Breadcrumb.Item>
15
+ </Breadcrumb.Root>
16
+ );
17
+ }
@@ -66,7 +66,7 @@ Button.Root <button> (or the single child with asChild); variant, tone, s
66
66
  | `labels` | `Partial<ButtonLabels>` | — | Built-in strings, see Labels. |
67
67
  | `asChild` | `boolean` | `false` | Merges Button props and styles onto the single child element (a link) instead of rendering `<button>`. `disabled`/`loading` become `aria-disabled`; `progress` and `holdToConfirm` need a `<button>`. |
68
68
  | `disabled` | `boolean` | — | Disabled state; `loading` also disables. With `asChild` the child leaves the Tab order and a link loses its `href`. |
69
- | `children` | `ReactNode` | — | Label and `Button.Icon`. Only `Button.Icon` children → a square icon-only button that needs `aria-label`. A changed label morphs letter by letter. With `asChild`, the one child element. |
69
+ | `children` | `ReactNode` | — | Label and `Button.Icon`. Only `Button.Icon` children → a square icon-only button that needs `aria-label`. A changed label morphs letter by letter. With `asChild`, the one child element; its text is the label too. |
70
70
  | `aria-label` | `string` | — | The name of a button without a visible label; required with `shape="circle"`. |
71
71
  | `…rest` | `Omit<ButtonHTMLAttributes<HTMLButtonElement>, "size" \| "aria-pressed">` | — | `onClick`, `type` (`"button"` by default, not passed on with `asChild`), `className`, `aria-*`, `data-*` and the other button attributes. |
72
72
 
@@ -47,6 +47,7 @@ The root is a field root: `className`, `ref` and the native attributes land on t
47
47
  | `required` | `boolean` | `false` | Red `*` after the label and `aria-required` on the `group`. |
48
48
  | `optional` | `boolean` | `false` | Muted marker after the label (`labels.optional`). |
49
49
  | `hint` | `ReactNode` | — | Help text under the control (`aria-describedby`). Hidden while `error` is shown. |
50
+ | `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. |
50
51
  | `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
51
52
  | `invalid` | `boolean` | `false` | Danger inset ring, `aria-invalid` and `data-invalid` on the `group`; the control shakes once when an error arrives. |
52
53
  | `reserveSupportRow` | `boolean` | `false` | Always render the support row, so an appearing error does not shift the layout. |
@@ -47,6 +47,7 @@ Card.Root asChild <a> or <button> (the only child) holding the pa
47
47
  |---|---|---|---|
48
48
  | `variant` | `"panel" \| "mini" \| "stat-trend" \| "split" \| "cta" \| "list" \| "cover"` | `"panel"` | Structural template: titled block with zones (`panel`), KPI tiles, a call to action, a list or a cover tile. |
49
49
  | `flat` | `boolean` | `false` | No raised shadow: a flat tile for dense grids. |
50
+ | `loading` | `boolean` | `false` | The KPI is loading: `Card.Value`, `Card.Delta` and `Card.Meta` show a Skeleton in their own geometry and the root sets `aria-busy`; the data fades in when it arrives. Labels and titles stay. |
50
51
  | `asChild` | `boolean` | `false` | The whole card is one link or button: pass that element as the only child with the parts inside it; it gets hover, press and the focus ring. Nothing interactive may sit inside it. |
51
52
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `className`, `role` + `aria-labelledby` for a landmark block, and the other div attributes. |
52
53
 
@@ -154,6 +155,7 @@ Card.Root asChild <a> or <button> (the only child) holding the pa
154
155
  | Value | Looks like | Use when | Default |
155
156
  |---|---|---|---|
156
157
  | `flat` | same fill, no shadow | dense grids of tiles | `false` |
158
+ | `loading` | `Card.Value`, `Card.Delta` and `Card.Meta` hold a Skeleton in their own geometry (the value a bar one line of its role high); the data fades in under the same element | a KPI waits for its report | `false` |
157
159
  | `asChild` | the card is the link or button it wraps: lifts on hover (raised → overlay shadow), presses, focus ring | the whole tile opens its details | `false` |
158
160
 
159
161
  ## States
@@ -161,6 +163,7 @@ Card.Root asChild <a> or <button> (the only child) holding the pa
161
163
  |---|---|---|
162
164
  | template | `variant` | `data-variant` |
163
165
  | flat | `flat` | `data-flat` |
166
+ | loading | `loading` | `aria-busy` on the root; `data-loading` on `Card.Value`, `Card.Delta`, `Card.Meta`, each holding a `Skeleton`; on `false` the data fades in (`useSwapIn`, foundation §1 rule 7) |
164
167
  | depth | nesting: one above the nearest surface (the page and the AppShell panel are 0) | `data-depth` (`1`–`4`, `floating-1`/`floating-2` inside a floating layer); the fill is `--prime-color-layer-current`, the shadow only at depth 1 |
165
168
  | card width | container queries | `split` stacks below 22rem, `Header` wraps below 22rem (a header control under its description), the `stat-trend` value changes at 20rem / 36rem |
166
169
  | hover | `asChild`, a fine pointer | the fill stays (a whole surface never changes its fill, foundation §8); the shadow lifts to `--prime-shadow-overlay` over `fast` |
@@ -195,6 +198,7 @@ Without `asChild` a card is static: no hover, focus or press of its own. With it
195
198
  - `Card.Root` is a plain `<div>`; give it `role="region"` + `aria-labelledby` only when it is a landmark-worthy block.
196
199
  - `Card.Title` renders `<h3>`; directly under the page title pass `as="h2"`.
197
200
  - Decorative icons in `Card.Icon` and cover images in `Card.Media` get `aria-hidden`; controls in `Card.Header` need their own names.
201
+ - A `loading` card sets `aria-busy` on its root; the Skeletons are decorative (`aria-hidden`).
198
202
 
199
203
  ### Labels
200
204
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
@@ -211,6 +215,7 @@ No `labels`.
211
215
  | [interactive.tsx](examples/interactive.tsx) | Whole cards as links to their reports: one tab stop each, a lift on hover, a press and the focus ring — `asChild`. |
212
216
  | [content-templates.tsx](examples/content-templates.tsx) | Content templates: a call to action, an events list and a campaign tile with a cover — `Card.Footer`, `Card.List`, `Card.Media`. |
213
217
  | [flat.tsx](examples/flat.tsx) | A flat tile without the raised shadow next to the default one, for dense grids — `flat`. |
218
+ | [states.tsx](examples/states.tsx) | A KPI tile while its report loads next to the loaded one, in the `stat-trend` and `mini` templates — `loading`. |
214
219
  | [narrow.tsx](examples/narrow.tsx) | The card is a size container: the split template stacks its cells below 22rem and the trend value shrinks below 20rem — `variant`. |
215
220
  | [screen.tsx](examples/screen.tsx) | A dashboard: the title and a line of data in the page flow, the period switch and the report in the top bar that keeps the title once it scrolls away; one grid of KPI tiles that cross-fade to the new period, a revenue trend, invoiced against paid beside two panels, and the latest payments with a link to the list — `collapse`, `Page.Grid`. |
216
221
 
@@ -225,7 +230,8 @@ No `labels`.
225
230
  - A clickable `<div>` card with `onClick` → `asChild` with a real `<a href>` (Cmd-click, a new tab) or a `<button>`.
226
231
  - Buttons or links inside an `asChild` card → nested interactive elements; move them out of the card.
227
232
  - The reference figures written into `Card.Delta` or `Card.Label` → `Card.Meta`.
233
+ - Skeletons placed by hand inside `Card.Value` → `Card.Root loading`; keep the parts mounted while loading so each holds its place.
228
234
 
229
235
  ## Related
230
- - **Built from:** —
236
+ - **Built from:** [Skeleton](../skeleton/COMPONENT.md)
231
237
  - **See also:** [Page](../../layout/page/COMPONENT.md), [DescriptionList](../description-list/COMPONENT.md) (record details inside a `Card.Body`), [Fieldset](../fieldset/COMPONENT.md) (groups of fields inside a settings card), [DataTable](../data-table/COMPONENT.md), [Badge](../badge/COMPONENT.md), [ProgressBar](../progress-bar/COMPONENT.md), [SegmentedControl](../segmented-control/COMPONENT.md), [Sparkline](../sparkline/COMPONENT.md) (a trend with its total inside a `panel`), [ChartFrame](../chart-frame/COMPONENT.md) (a full chart with legend and states)
@@ -0,0 +1,59 @@
1
+ /** A KPI tile while its report loads next to the loaded one, in the `stat-trend` and `mini` templates — `loading`. */
2
+ import { Card, Icon, Typography } from "prime-ui-kit";
3
+
4
+ export default function CardStatesExample() {
5
+ return (
6
+ <>
7
+ <div>
8
+ <div>
9
+ <Card.Root variant="stat-trend" loading>
10
+ <Card.Label>Выручка за месяц</Card.Label>
11
+ <Card.Value />
12
+ <Card.Delta />
13
+ <Card.Meta />
14
+ </Card.Root>
15
+ <Typography as="span" variant="caption" tone="muted">
16
+ stat-trend · loading
17
+ </Typography>
18
+ </div>
19
+ <div>
20
+ <Card.Root variant="stat-trend">
21
+ <Card.Label>Выручка за месяц</Card.Label>
22
+ <Card.Value>4,2 млн ₽</Card.Value>
23
+ <Card.Delta tone="success">+18% к сентябрю</Card.Delta>
24
+ <Card.Meta>Лидов 1 240 · сделок 186</Card.Meta>
25
+ </Card.Root>
26
+ <Typography as="span" variant="caption" tone="muted">
27
+ stat-trend
28
+ </Typography>
29
+ </div>
30
+ </div>
31
+ <div>
32
+ <div>
33
+ <Card.Root variant="mini" loading>
34
+ <Card.Icon>
35
+ <Icon name="object.users" />
36
+ </Card.Icon>
37
+ <Card.Label>Активные сессии</Card.Label>
38
+ <Card.Value />
39
+ </Card.Root>
40
+ <Typography as="span" variant="caption" tone="muted">
41
+ mini · loading
42
+ </Typography>
43
+ </div>
44
+ <div>
45
+ <Card.Root variant="mini">
46
+ <Card.Icon>
47
+ <Icon name="object.users" />
48
+ </Card.Icon>
49
+ <Card.Label>Активные сессии</Card.Label>
50
+ <Card.Value>1 248</Card.Value>
51
+ </Card.Root>
52
+ <Typography as="span" variant="caption" tone="muted">
53
+ mini
54
+ </Typography>
55
+ </div>
56
+ </div>
57
+ </>
58
+ );
59
+ }
@@ -81,6 +81,7 @@ Chat.Root <div> column, fills its host; owns selection,
81
81
  - `renderMessage` may be an inline function: an item renders again only when it or its place changes, so whatever a row shows lives in its item (an edit, a reaction, a pin). Another message (a reply's original) comes from `entry.find(key)`.
82
82
  - `onRead` reports an incoming message once at least half of it, or half the view, has been on screen while the page is visible (`IntersectionObserver`); batched per frame, each `value` once per mount.
83
83
  - One sound at a time on the page: a `VoicePlayer`, a `VideoNote` and a video in `Message.Media` pause each other; nothing to wire.
84
+ - Times in a profile's zone: `timeZone` (an IANA name) on `Chat.Messages` moves the date separators, the runs and every message's time to it; without it they follow the browser.
84
85
 
85
86
  ## API
86
87
 
@@ -174,7 +175,8 @@ Chat.Root <div> column, fills its host; owns selection,
174
175
  | `unread` | `string` | — | The key of the first unread item: the «Непрочитанные сообщения» band stands above it. |
175
176
  | `groupWindow` | `number` | `5` | Messages of one author further apart than this many minutes start a new run. |
176
177
  | `now` | `Date` | today | «Today» for the dates. |
177
- | `formatDate` | `(date: Date, now: Date) => string` | `formatMessageDate` | The label of a date. |
178
+ | `timeZone` | `string` | the browser's zone | The IANA zone of the date separators, the runs and every message time (`"Asia/Vladivostok"`): a profile's zone, not the browser's. |
179
+ | `formatDate` | `(date: Date, now: Date, timeZone?: string) => string` | `formatMessageDate` | The label of a date; gets the `timeZone`. |
178
180
  | `loading` | `boolean` | `false` | The first load: bubble-shaped `Skeleton`s on both sides stand in the list's place (`aria-busy`, `labels.loading` announced), then cross-fade into the messages; none of them counts as new. |
179
181
  | `hasOlder` | `boolean` | `false` | Older history exists: a bubble-shaped placeholder stands above the first message and `onLoadOlder` runs when the reader reaches the top. |
180
182
  | `onLoadOlder` | `() => void \| Promise<void>` | — | Load older messages and prepend them (while `hasOlder`); the reader's place is kept. |
@@ -334,6 +336,7 @@ Chat.Root <div> column, fills its host; owns selection,
334
336
  | [reactions.tsx](examples/reactions.tsx) | A team's own reactions for approvals: a double tap or `L` puts the approval mark, a long press or `R` opens the team's set, and a chip toggles them back — `quickReaction`, `reactionOptions`, `onReactionToggle`. |
335
337
  | [live.tsx](examples/live.tsx) | A live support chat: the client types — the header and the end of the list say so — then answers; new messages enter at the end, read receipts come from what is on screen, and scrolled up the round button counts what arrived — `typing`, `onNewMessage`, `onRead`. |
336
338
  | [search.tsx](examples/search.tsx) | Search and pinned messages: the search bar marks every match in the texts and transcripts, counts them and steps between them while the others step back; a pinned message's press jumps to it and blinks it — `Chat.SearchButton`, `Chat.Search`, `defaultSearch`, `Chat.Pinned`. |
339
+ | [time-zone.tsx](examples/time-zone.tsx) | The conversation in the zone of the manager's profile, not the browser's: the date separators, the runs and every time follow it — `timeZone`, `items`, `renderMessage`. |
337
340
  | [states.tsx](examples/states.tsx) | The first load of a conversation: bubble-shaped placeholders on both sides stand in the list's place, then cross-fade into the messages — `loading`. |
338
341
  | [controlled.tsx](examples/controlled.tsx) | Selection mode owned by the screen: the Select item of a message's menu starts it, a check stands beside every message, a press toggles one while the others step back, the bar counts and acts, Escape leaves — `selected`, `onSelectedChange`, `Chat.SelectionBar`. |
339
342
  | [narrow.tsx](examples/narrow.tsx) | At phone width the header keeps its actions and lets the title ellipsize, bubbles take up to 85% of the chat, long links wrap and an album keeps its layout — `Chat.Root`, `Chat.Header`, `Chat.Messages`, `Chat.Footer`. |
@@ -0,0 +1,83 @@
1
+ /** The conversation in the zone of the manager's profile, not the browser's: the date separators, the runs and every time follow it — `timeZone`, `items`, `renderMessage`. */
2
+ import { Card, Chat, Message, SegmentedControl } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ const ZONES = [
8
+ { value: "Europe/Moscow", label: "Москва" },
9
+ { value: "Asia/Vladivostok", label: "Владивосток" },
10
+ ];
11
+
12
+ const NOW = new Date(Date.UTC(2026, 9, 15, 4, 0));
13
+ const utc = (day: number, hour: number, minute: number) =>
14
+ new Date(Date.UTC(2026, 9, day, hour, minute));
15
+
16
+ const MESSAGES = [
17
+ {
18
+ id: "tz-1",
19
+ author: "client",
20
+ sentAt: utc(14, 12, 40),
21
+ text: "Добрый вечер! Машина с заказом № 4821 уже вышла?",
22
+ },
23
+ {
24
+ id: "tz-2",
25
+ author: "manager",
26
+ sentAt: utc(14, 13, 5),
27
+ text: "Вышла в 18:00, будет у вас завтра до обеда.",
28
+ },
29
+ {
30
+ id: "tz-3",
31
+ author: "client",
32
+ sentAt: utc(14, 14, 30),
33
+ text: "Спасибо, предупрежу склад.",
34
+ },
35
+ {
36
+ id: "tz-4",
37
+ author: "manager",
38
+ sentAt: utc(15, 0, 10),
39
+ text: "Водитель на месте, выгрузка через 10 минут.",
40
+ },
41
+ ];
42
+
43
+ type ChatMessage = (typeof MESSAGES)[number];
44
+
45
+ export default function ChatTimeZoneExample() {
46
+ const [zone, setZone] = React.useState("Asia/Vladivostok");
47
+
48
+ return (
49
+ <div className={styles.live}>
50
+ <div className={styles.liveBar}>
51
+ <SegmentedControl.Root value={zone} onValueChange={setZone} aria-label="Пояс профиля">
52
+ {ZONES.map((item) => (
53
+ <SegmentedControl.Item key={item.value} value={item.value}>
54
+ {item.label}
55
+ </SegmentedControl.Item>
56
+ ))}
57
+ </SegmentedControl.Root>
58
+ </div>
59
+ <Card.Root className={styles.short}>
60
+ <Chat.Root>
61
+ <Chat.Messages
62
+ aria-label="Переписка по заказу № 4821"
63
+ timeZone={zone}
64
+ now={NOW}
65
+ items={MESSAGES}
66
+ getKey={(message: ChatMessage) => message.id}
67
+ getAuthor={(message: ChatMessage) => message.author}
68
+ getDate={(message: ChatMessage) => message.sentAt}
69
+ renderMessage={(message: ChatMessage) => (
70
+ <Message.Root
71
+ direction={message.author === "manager" ? "outgoing" : "incoming"}
72
+ time={message.sentAt}
73
+ status={message.author === "manager" ? "read" : undefined}
74
+ >
75
+ <Message.Text>{message.text}</Message.Text>
76
+ </Message.Root>
77
+ )}
78
+ />
79
+ </Chat.Root>
80
+ </Card.Root>
81
+ </div>
82
+ );
83
+ }
@@ -28,7 +28,7 @@ ChatList.Root <div> column filling its host (container `chat-lis
28
28
  ├─ ScrollContainer the list's own scroller; onReachEnd → onLoadMore while hasMore
29
29
  │ └─ Crossfade loading (placeholder List rows) ⇄ empty (EmptyPage compact) ⇄ the list
30
30
  │ └─ List.Root <ul> aria-label «Диалоги»; spacer <li aria-hidden> stand for rows out of view
31
- │ └─ ChatList.Item List.Item <li> + <button> | <a href>; current from value; one Tab stop
31
+ │ └─ ChatList.Item List.Item <li> + <button> | <a href> | your element (asChild); current from value; one Tab stop
32
32
  │ ├─ ChatList.ItemIcon before the name: avatar slot (aria-hidden); after it: a glyph
33
33
  │ ├─ ChatList.ItemText the name; muted glyph after it
34
34
  │ ├─ ChatList.ItemDescription author · media word · text — cross-fades with draft and «печатает…»
@@ -62,12 +62,13 @@ ChatList.Root <div> column filling its host (container `chat-lis
62
62
  | `empty` | `ReactNode` | — | The empty state without a search (an `EmptyPage` with an action). Default: «Нет диалогов». |
63
63
  | `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | host tier, else `"m"` | Row tier: height, text, avatar, glyphs and the search field. Without it the tier of the host. |
64
64
  | `now` | `Date` | — | «Now» for the times of the rows. Default: the moment of the render. |
65
- | `formatTime` | `(date: Date, now: Date) => string` | `formatChatTime` | Display of a row's `Date` time: «14:32» today, «пн» this week, «12 мар», «12.03.2025». |
65
+ | `timeZone` | `string` | the browser's zone | The IANA zone the times of the rows are read in (`"Asia/Vladivostok"`): a profile's zone, not the browser's. |
66
+ | `formatTime` | `(date: Date, now: Date, timeZone?: string) => string` | `formatChatTime` | Display of a row's `Date` time: «14:32» today, «пн» this week, «12 мар», «12.03.2025»; gets the `timeZone`. |
66
67
  | `labels` | `Partial<ChatListLabels>` | — | Built-in strings, see Labels. |
67
68
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `className` and the other `div` attributes of the column. |
68
69
 
69
70
  ### ChatList.Item
70
- `ref` → `HTMLElement` (the `<button>` or `<a>`, not the `<li>`). A dialog: a `List.Item` row with the avatar, the name and the time over the preview and the counts. Its key and `current` come from the root.
71
+ `ref` → `HTMLElement` (the `<button>`, `<a>` or `asChild` element, not the `<li>`). A dialog: a `List.Item` row with the avatar, the name and the time over the preview and the counts. Its key and `current` come from the root.
71
72
 
72
73
  | Prop | Type | Default | Description |
73
74
  |---|---|---|---|
@@ -83,7 +84,8 @@ ChatList.Root <div> column filling its host (container `chat-lis
83
84
  | `href` | `string` | — | Renders an `<a>` instead of a `<button>` (a dialog with its own URL). |
84
85
  | `target` | `string` | — | Where the `<a>` opens (`href` only). |
85
86
  | `rel` | `string` | — | Relationship of the `<a>` (`href` only). |
86
- | `children` | `ReactNode` | — | `ChatList.ItemIcon`, `ChatList.ItemText`, `ChatList.ItemDescription`, `ChatList.ItemMenu`. |
87
+ | `asChild` | `boolean` | `false` | The one child element (a router `Link`) is the row; the parts go inside it. |
88
+ | `children` | `ReactNode` | — | `ChatList.ItemIcon`, `ChatList.ItemText`, `ChatList.ItemDescription`, `ChatList.ItemMenu` — inside the child element with `asChild`. |
87
89
  | `…rest` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | `className` and the native attributes of the row element. |
88
90
 
89
91
  ### ChatList.ItemIcon
@@ -150,6 +152,7 @@ The search field takes the tier too; counts are one tier down (the Badge's own r
150
152
  | `muted` | a muted glyph after the name; the count soft gray | notifications are off | `false` |
151
153
  | `mention` | a solid «@» before the count | the reader is mentioned | `false` |
152
154
  | `pinned` | a pin under the time while nothing is unread | pinned by the reader — order pinned first in `items` | `false` |
155
+ | `asChild` | the same row, rendered as your one child element with the parts inside it | a router's `Link` opens the dialog without a reload | `false` |
153
156
  | `status` | ✓ / ✓✓ in the accent / clock / failure mark before the time | the reader wrote the last message | — |
154
157
 
155
158
  ## States
@@ -195,7 +198,7 @@ Root attribute: `data-size`. Every motion honours reduced motion (durations coll
195
198
  ### ARIA
196
199
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
197
200
 
198
- - The list is a `<ul>` named «Диалоги» (`labels.list`); each row an `<li>` with a button, or a link with `href`.
201
+ - The list is a `<ul>` named «Диалоги» (`labels.list`); each row an `<li>` with a button, a link with `href`, or your link with `asChild`.
199
202
  - The open dialog is `aria-current="true"` on a button, `"page"` on a link. Selection does not follow the focus: arrows only move it (a dialog may load a conversation); Enter, Space and a click open.
200
203
  - The row is named by its content: the name, glyphs with a `label` («Без звука», a verified mark), the preview, the delivery mark («Прочитано»), the time, then «упоминание» and «непрочитанных: 3» instead of the bare badges; the avatar is `aria-hidden`.
201
204
  - While the other side types, the preview is `aria-hidden` and «печатает…» is read; a draft reads «Черновик: …».
@@ -243,7 +246,8 @@ Root attribute: `data-size`. Every motion honours reduced motion (durations coll
243
246
  | [search.tsx](examples/search.tsx) | The search field filters dialogs by name and by the last message, marks the match in both, says how many were found and shows a quiet state when none match — `defaultSearch`, `onSearchChange`. |
244
247
  | [live.tsx](examples/live.tsx) | A live inbox: a client types, then writes — the preview turns into the typing line and back, the count rolls up and the dialog glides to the top; opening it clears the count — `typing`, `unread`, `items`. |
245
248
  | [long-list.tsx](examples/long-list.tsx) | A sales inbox of a thousand dialogs: only the rows around the view render, arrows walk through all of them, and the next page loads as the end comes near — `hasMore`, `onLoadMore`. |
246
- | [links.tsx](examples/links.tsx) | Dialogs with their own address: every row is a link the router opens, the time reads as how long ago, and the list is named for its queue — `href`, `formatTime`, `labels`. |
249
+ | [links.tsx](examples/links.tsx) | Dialogs with their own address: every row is a link to the ticket's page, the time reads as how long ago, and the list is named for its queue — `href`, `formatTime`, `labels`. |
250
+ | [router.tsx](examples/router.tsx) | Router links as the dialogs, the times in the zone of the operator's profile; render it inside a router — `asChild`, `timeZone`. |
247
251
  | [states.tsx](examples/states.tsx) | Loading and empty: placeholder rows in the rows' geometry cross-fade into the dialogs; a new workspace without dialogs shows its own empty state with an action — `loading`, `empty`. |
248
252
  | [controlled.tsx](examples/controlled.tsx) | The open dialog lives in the screen: a row opens it beside the list, and a next-unread button opens the next one from outside — `value`, `onValueChange`. |
249
253
  | [narrow.tsx](examples/narrow.tsx) | At phone width long names and previews truncate while the time and counts stay whole; a pane squeezed under 240px keeps only the names and the counts — `ChatList.Root`, `search`. |
@@ -256,6 +260,8 @@ Root attribute: `data-size`. Every motion honours reduced motion (durations coll
256
260
  - An `Avatar.Root size="s"` to fit the row → leave `size` out; the slot sizes the avatar to the tier.
257
261
  - A «⋯» button nested inside the row for the menu → `ChatList.ItemMenu`: it opens on right click, a long press and Shift+F10.
258
262
  - Deleting from the menu without a question → open a `ConfirmDialog` from the menu item.
263
+ - `href` with an `onClick` that calls `preventDefault()` and the router's `navigate` → `asChild` with the router's `Link`, the parts inside it.
264
+ - Times shifted by hand to a profile's zone before `time` → `timeZone` on the root: «сегодня», the weekday and the clock are read in it.
259
265
 
260
266
  ## Related
261
267
  - **Built from:** [List](../list/COMPONENT.md) (rows, current fill, focus, touch height), [Input](../input/COMPONENT.md) (search), [ScrollContainer](../scroll-container/COMPONENT.md), [Crossfade](../crossfade/COMPONENT.md), [Skeleton](../skeleton/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [Badge](../badge/COMPONENT.md) (counts), [Dropdown](../dropdown/COMPONENT.md) (row menu), [VisuallyHidden](../visually-hidden/COMPONENT.md), `useFlipList` (the glide of a new order)
@@ -1,4 +1,4 @@
1
- /** Dialogs with their own address: every row is a link the router opens, the time reads as how long ago, and the list is named for its queue — `href`, `formatTime`, `labels`. */
1
+ /** Dialogs with their own address: every row is a link to the ticket's page, the time reads as how long ago, and the list is named for its queue — `href`, `formatTime`, `labels`. */
2
2
  import { Avatar, Card, ChatList, hueOf } from "prime-ui-kit";
3
3
 
4
4
  import styles from "./examples.module.css";
@@ -0,0 +1,63 @@
1
+ /** Router links as the dialogs, the times in the zone of the operator's profile; render it inside a router — `asChild`, `timeZone`. */
2
+ import { Avatar, Card, ChatList, hueOf } from "prime-ui-kit";
3
+ import { Link } from "react-router-dom";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ const NOW = new Date(Date.UTC(2026, 9, 15, 4, 0));
8
+ const utc = (day: number, hour: number, minute: number) =>
9
+ new Date(Date.UTC(2026, 9, day, hour, minute));
10
+
11
+ const DIALOGS = [
12
+ {
13
+ id: "c-311",
14
+ name: "ООО «Вектор»",
15
+ initials: "ВК",
16
+ preview: "Машина на месте, начинаем выгрузку",
17
+ at: utc(15, 0, 10),
18
+ unread: 1,
19
+ },
20
+ {
21
+ id: "c-308",
22
+ name: "ООО «Гранит»",
23
+ initials: "ГР",
24
+ preview: "Счёт № 2041 оплачен",
25
+ at: utc(14, 14, 30),
26
+ unread: 0,
27
+ },
28
+ {
29
+ id: "c-297",
30
+ name: "ИП Козлов",
31
+ initials: "ИК",
32
+ preview: "Пришлите, пожалуйста, акт сверки",
33
+ at: utc(12, 6, 15),
34
+ unread: 0,
35
+ },
36
+ ];
37
+
38
+ export default function ChatListRouterExample() {
39
+ return (
40
+ <Card.Root className={styles.short}>
41
+ <ChatList.Root
42
+ items={DIALOGS}
43
+ getKey={(item) => item.id}
44
+ defaultValue="c-311"
45
+ now={NOW}
46
+ timeZone="Asia/Vladivostok"
47
+ renderItem={(item) => (
48
+ <ChatList.Item asChild time={item.at} unread={item.unread}>
49
+ <Link to={`/chats/${item.id}`}>
50
+ <ChatList.ItemIcon>
51
+ <Avatar.Root color={hueOf(item.id)}>
52
+ <Avatar.Fallback>{item.initials}</Avatar.Fallback>
53
+ </Avatar.Root>
54
+ </ChatList.ItemIcon>
55
+ <ChatList.ItemText>{item.name}</ChatList.ItemText>
56
+ <ChatList.ItemDescription>{item.preview}</ChatList.ItemDescription>
57
+ </Link>
58
+ </ChatList.Item>
59
+ )}
60
+ />
61
+ </Card.Root>
62
+ );
63
+ }
@@ -50,6 +50,7 @@ Checkbox.Indicator the box alone, no input — a mark inside option / menu r
50
50
  | `optional` | `boolean` | `false` | Muted `labels.optional` after the label text; the field may stay off. |
51
51
  | `labels` | `Partial<CheckboxLabels>` | — | Built-in strings, see Labels. |
52
52
  | `hint` | `ReactNode` | — | Help text under the label text. Hidden while `error` is shown. |
53
+ | `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. |
53
54
  | `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
54
55
  | `invalid` | `boolean` | `false` | Danger ring on the unchecked box and `aria-invalid`. A non-empty `error` implies it. |
55
56
  | `disabled` | `boolean` | `false` | Disabled fill, dimmed label and hint, no toggling. |