prime-ui-kit 0.17.0 → 0.18.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 (227) 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-A4WE3UNC.js → chunk-MB4CXSLL.js} +18473 -17911
  4. package/dist/chunks/{chunk-7WWZYGRC.js → chunk-OAXJWF3L.js} +4477 -3882
  5. package/dist/chunks/{chunk-2ODXLPW3.js → chunk-QFHE5GKW.js} +1 -0
  6. package/dist/chunks/{chunk-SVFWPLNW.js → chunk-X3JT5CLR.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/DataTable.d.ts +2 -0
  24. package/dist/components/data-table/Head.d.ts +3 -1
  25. package/dist/components/data-table/types.d.ts +5 -0
  26. package/dist/components/digit-input/DigitInput.d.ts +1 -1
  27. package/dist/components/drawer/Drawer.d.ts +3 -2
  28. package/dist/components/field-list/FieldList.d.ts +18 -6
  29. package/dist/components/hint/Hint.d.ts +8 -2
  30. package/dist/components/index.d.ts +6 -3
  31. package/dist/components/input/Input.d.ts +27 -5
  32. package/dist/components/input/inputSuggestions.d.ts +39 -0
  33. package/dist/components/kanban/Kanban.d.ts +4 -9
  34. package/dist/components/list/List.d.ts +19 -5
  35. package/dist/components/message/Message.d.ts +15 -3
  36. package/dist/components/message/MessageSticker.d.ts +27 -0
  37. package/dist/components/message/formatMessageTime.d.ts +5 -2
  38. package/dist/components/modal/Modal.d.ts +3 -2
  39. package/dist/components/native-select/NativeSelect.d.ts +1 -1
  40. package/dist/components/radio/Radio.d.ts +11 -2
  41. package/dist/components/rich-text/RichText.d.ts +23 -0
  42. package/dist/components/scroll-container/ScrollContainer.d.ts +1 -1
  43. package/dist/components/scroll-container/scrollGlide.d.ts +26 -0
  44. package/dist/components/scroll-container/useScrollMotion.d.ts +25 -0
  45. package/dist/components/segmented-control/SegmentedControl.d.ts +1 -1
  46. package/dist/components/select/selectContext.d.ts +7 -0
  47. package/dist/components/slider/Slider.d.ts +1 -1
  48. package/dist/components/switch/Switch.d.ts +6 -1
  49. package/dist/components/tag-select/TagSelect.d.ts +1 -1
  50. package/dist/components/textarea/Textarea.d.ts +6 -1
  51. package/dist/components/timeline/Timeline.d.ts +1 -1
  52. package/dist/icon-set.js +2 -2
  53. package/dist/index.css +1 -1
  54. package/dist/index.js +5 -3
  55. package/dist/internal/ChoiceField.d.ts +3 -1
  56. package/dist/internal/FieldFrame.d.ts +8 -2
  57. package/dist/internal/fieldCell.d.ts +1 -1
  58. package/dist/internal/formatClock.d.ts +23 -2
  59. package/dist/internal/leaveGuard.d.ts +21 -0
  60. package/dist/internal/messageList.d.ts +2 -0
  61. package/dist/internal/overlay/DialogParts.d.ts +27 -2
  62. package/dist/internal/swatch.d.ts +5 -0
  63. package/dist/internal/tokenDefaults.d.ts +1 -0
  64. package/dist/layout/index.d.ts +1 -1
  65. package/dist/layout/page/Page.d.ts +47 -8
  66. package/dist/skill/anti-slop.md +5 -0
  67. package/dist/skill/api-contract.md +14 -7
  68. package/dist/skill/cheatsheet.md +22 -7
  69. package/dist/skill/checklist.md +5 -0
  70. package/dist/skill/choosing.md +25 -12
  71. package/dist/skill/components.md +1 -0
  72. package/dist/skill/composition.md +19 -5
  73. package/dist/skill/layouts.md +5 -3
  74. package/dist/skill/reference/components/badge/COMPONENT.md +14 -1
  75. package/dist/skill/reference/components/badge/examples/examples.module.css +7 -0
  76. package/dist/skill/reference/components/badge/examples/tag-row.tsx +34 -0
  77. package/dist/skill/reference/components/breadcrumb/COMPONENT.md +9 -4
  78. package/dist/skill/reference/components/breadcrumb/examples/router.tsx +17 -0
  79. package/dist/skill/reference/components/button/COMPONENT.md +1 -1
  80. package/dist/skill/reference/components/button-group/COMPONENT.md +1 -0
  81. package/dist/skill/reference/components/card/COMPONENT.md +7 -1
  82. package/dist/skill/reference/components/card/examples/states.tsx +59 -0
  83. package/dist/skill/reference/components/chat/COMPONENT.md +4 -1
  84. package/dist/skill/reference/components/chat/examples/time-zone.tsx +83 -0
  85. package/dist/skill/reference/components/chat-list/COMPONENT.md +12 -6
  86. package/dist/skill/reference/components/chat-list/examples/links.tsx +1 -1
  87. package/dist/skill/reference/components/chat-list/examples/router.tsx +63 -0
  88. package/dist/skill/reference/components/checkbox/COMPONENT.md +1 -0
  89. package/dist/skill/reference/components/color-field/COMPONENT.md +2 -1
  90. package/dist/skill/reference/components/color-picker/COMPONENT.md +14 -5
  91. package/dist/skill/reference/components/color-picker/examples/presets-custom.tsx +31 -0
  92. package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
  93. package/dist/skill/reference/components/crossfade/COMPONENT.md +1 -1
  94. package/dist/skill/reference/components/data-table/COMPONENT.md +7 -2
  95. package/dist/skill/reference/components/data-table/examples/numeric.tsx +9 -2
  96. package/dist/skill/reference/components/datepicker/COMPONENT.md +3 -1
  97. package/dist/skill/reference/components/datepicker/examples/deadline.tsx +33 -0
  98. package/dist/skill/reference/components/digit-input/COMPONENT.md +1 -0
  99. package/dist/skill/reference/components/drawer/COMPONENT.md +15 -2
  100. package/dist/skill/reference/components/drawer/examples/client-card.tsx +32 -8
  101. package/dist/skill/reference/components/field-list/COMPONENT.md +15 -3
  102. package/dist/skill/reference/components/field-list/examples/saving.tsx +83 -0
  103. package/dist/skill/reference/components/field-list/examples/totals.tsx +82 -0
  104. package/dist/skill/reference/components/file-upload/COMPONENT.md +1 -0
  105. package/dist/skill/reference/components/hint/COMPONENT.md +16 -2
  106. package/dist/skill/reference/components/hint/examples/variants.tsx +48 -0
  107. package/dist/skill/reference/components/input/COMPONENT.md +39 -9
  108. package/dist/skill/reference/components/input/examples/affixes.tsx +4 -2
  109. package/dist/skill/reference/components/input/examples/dismiss.tsx +29 -0
  110. package/dist/skill/reference/components/input/examples/suggestions.tsx +26 -0
  111. package/dist/skill/reference/components/input/examples/time.tsx +24 -0
  112. package/dist/skill/reference/components/input/examples/with-select.tsx +44 -0
  113. package/dist/skill/reference/components/kanban/COMPONENT.md +6 -6
  114. package/dist/skill/reference/components/kanban/examples/columns.tsx +4 -2
  115. package/dist/skill/reference/components/list/COMPONENT.md +23 -7
  116. package/dist/skill/reference/components/list/examples/row-controls.tsx +80 -0
  117. package/dist/skill/reference/components/list/examples/with-icon.tsx +23 -2
  118. package/dist/skill/reference/components/login-form/COMPONENT.md +8 -7
  119. package/dist/skill/reference/components/login-form/examples/states.tsx +12 -14
  120. package/dist/skill/reference/components/message/COMPONENT.md +30 -9
  121. package/dist/skill/reference/components/message/examples/preview.tsx +28 -0
  122. package/dist/skill/reference/components/message/examples/sticker.tsx +35 -0
  123. package/dist/skill/reference/components/modal/COMPONENT.md +15 -3
  124. package/dist/skill/reference/components/modal/examples/long-content.tsx +37 -18
  125. package/dist/skill/reference/components/native-select/COMPONENT.md +1 -0
  126. package/dist/skill/reference/components/notification/COMPONENT.md +1 -1
  127. package/dist/skill/reference/components/radio/COMPONENT.md +2 -0
  128. package/dist/skill/reference/components/rich-text/COMPONENT.md +93 -0
  129. package/dist/skill/reference/components/rich-text/examples/overview.tsx +19 -0
  130. package/dist/skill/reference/components/scroll-container/COMPONENT.md +8 -5
  131. package/dist/skill/reference/components/segmented-control/COMPONENT.md +1 -0
  132. package/dist/skill/reference/components/segmented-control/examples/in-form.tsx +1 -1
  133. package/dist/skill/reference/components/select/COMPONENT.md +7 -3
  134. package/dist/skill/reference/components/slider/COMPONENT.md +1 -0
  135. package/dist/skill/reference/components/slider/examples/controlled.tsx +1 -1
  136. package/dist/skill/reference/components/switch/COMPONENT.md +1 -0
  137. package/dist/skill/reference/components/tag-select/COMPONENT.md +1 -0
  138. package/dist/skill/reference/components/textarea/COMPONENT.md +1 -0
  139. package/dist/skill/reference/components/timeline/COMPONENT.md +3 -3
  140. package/dist/skill/reference/components/timeline/examples/structure.tsx +2 -1
  141. package/dist/skill/reference/layout/app-shell/COMPONENT.md +2 -2
  142. package/dist/skill/reference/layout/bottom-nav/COMPONENT.md +1 -0
  143. package/dist/skill/reference/layout/page/COMPONENT.md +37 -8
  144. package/dist/skill/reference/layout/page/examples/examples.module.css +7 -0
  145. package/dist/skill/reference/layout/page/examples/guard.tsx +87 -0
  146. package/dist/skill/reference/layout/page/examples/loading.tsx +57 -0
  147. package/dist/skill/reference/layout/sidebar/COMPONENT.md +1 -0
  148. package/dist/skill/responsive.md +12 -7
  149. package/dist/skill/ux.md +5 -3
  150. package/dist/tokens/semantic.d.ts +2 -0
  151. package/package.json +2 -2
  152. package/src/components/badge/COMPONENT.md +14 -1
  153. package/src/components/badge/examples/examples.module.css +7 -0
  154. package/src/components/badge/examples/tag-row.tsx +34 -0
  155. package/src/components/breadcrumb/COMPONENT.md +9 -4
  156. package/src/components/breadcrumb/examples/router.tsx +17 -0
  157. package/src/components/button/COMPONENT.md +1 -1
  158. package/src/components/button-group/COMPONENT.md +1 -0
  159. package/src/components/card/COMPONENT.md +7 -1
  160. package/src/components/card/examples/states.tsx +59 -0
  161. package/src/components/chat/COMPONENT.md +4 -1
  162. package/src/components/chat/examples/time-zone.tsx +83 -0
  163. package/src/components/chat-list/COMPONENT.md +12 -6
  164. package/src/components/chat-list/examples/links.tsx +1 -1
  165. package/src/components/chat-list/examples/router.tsx +63 -0
  166. package/src/components/checkbox/COMPONENT.md +1 -0
  167. package/src/components/color-field/COMPONENT.md +2 -1
  168. package/src/components/color-picker/COMPONENT.md +14 -5
  169. package/src/components/color-picker/examples/presets-custom.tsx +31 -0
  170. package/src/components/color-swatches/COMPONENT.md +1 -0
  171. package/src/components/crossfade/COMPONENT.md +1 -1
  172. package/src/components/data-table/COMPONENT.md +7 -2
  173. package/src/components/data-table/examples/numeric.tsx +9 -2
  174. package/src/components/datepicker/COMPONENT.md +3 -1
  175. package/src/components/datepicker/examples/deadline.tsx +33 -0
  176. package/src/components/digit-input/COMPONENT.md +1 -0
  177. package/src/components/drawer/COMPONENT.md +15 -2
  178. package/src/components/drawer/examples/client-card.tsx +32 -8
  179. package/src/components/field-list/COMPONENT.md +15 -3
  180. package/src/components/field-list/examples/saving.tsx +83 -0
  181. package/src/components/field-list/examples/totals.tsx +82 -0
  182. package/src/components/file-upload/COMPONENT.md +1 -0
  183. package/src/components/hint/COMPONENT.md +16 -2
  184. package/src/components/hint/examples/variants.tsx +48 -0
  185. package/src/components/input/COMPONENT.md +39 -9
  186. package/src/components/input/examples/affixes.tsx +4 -2
  187. package/src/components/input/examples/dismiss.tsx +29 -0
  188. package/src/components/input/examples/suggestions.tsx +26 -0
  189. package/src/components/input/examples/time.tsx +24 -0
  190. package/src/components/input/examples/with-select.tsx +44 -0
  191. package/src/components/kanban/COMPONENT.md +6 -6
  192. package/src/components/kanban/examples/columns.tsx +4 -2
  193. package/src/components/list/COMPONENT.md +23 -7
  194. package/src/components/list/examples/row-controls.tsx +80 -0
  195. package/src/components/list/examples/with-icon.tsx +23 -2
  196. package/src/components/login-form/COMPONENT.md +8 -7
  197. package/src/components/login-form/examples/states.tsx +12 -14
  198. package/src/components/message/COMPONENT.md +30 -9
  199. package/src/components/message/examples/preview.tsx +28 -0
  200. package/src/components/message/examples/sticker.tsx +35 -0
  201. package/src/components/modal/COMPONENT.md +15 -3
  202. package/src/components/modal/examples/long-content.tsx +37 -18
  203. package/src/components/native-select/COMPONENT.md +1 -0
  204. package/src/components/notification/COMPONENT.md +1 -1
  205. package/src/components/radio/COMPONENT.md +2 -0
  206. package/src/components/rich-text/COMPONENT.md +93 -0
  207. package/src/components/rich-text/examples/overview.tsx +19 -0
  208. package/src/components/scroll-container/COMPONENT.md +8 -5
  209. package/src/components/segmented-control/COMPONENT.md +1 -0
  210. package/src/components/segmented-control/examples/in-form.tsx +1 -1
  211. package/src/components/select/COMPONENT.md +7 -3
  212. package/src/components/slider/COMPONENT.md +1 -0
  213. package/src/components/slider/examples/controlled.tsx +1 -1
  214. package/src/components/switch/COMPONENT.md +1 -0
  215. package/src/components/tag-select/COMPONENT.md +1 -0
  216. package/src/components/textarea/COMPONENT.md +1 -0
  217. package/src/components/timeline/COMPONENT.md +3 -3
  218. package/src/components/timeline/examples/structure.tsx +2 -1
  219. package/src/layout/app-shell/COMPONENT.md +2 -2
  220. package/src/layout/bottom-nav/COMPONENT.md +1 -0
  221. package/src/layout/page/COMPONENT.md +37 -8
  222. package/src/layout/page/examples/examples.module.css +7 -0
  223. package/src/layout/page/examples/guard.tsx +87 -0
  224. package/src/layout/page/examples/loading.tsx +57 -0
  225. package/src/layout/sidebar/COMPONENT.md +1 -0
  226. package/src/styles/theme-light.css +1 -0
  227. package/dist/components/scroll-container/useWheelToInline.d.ts +0 -10
@@ -10,7 +10,7 @@ confuse. Links: [components.md](components.md). Whole screens: [composition.md](
10
10
  | trigger an action (save, delete, go on) | Actions — Button, ButtonGroup, LinkButton |
11
11
  | type a value (text, number, code, file, photos), group the fields of a form, edit a list of records of one shape | Inputs — Input, Textarea, DigitInput, FileUpload, Label, Hint, Fieldset, FieldList |
12
12
  | pick from options, toggle, set a range, filter, a date or a color | Selection — Checkbox, Radio, Switch, SegmentedControl, Slider, Select, NativeSelect, TagSelect, SmartFilter, Datepicker, ColorField, ColorPicker, ColorSwatches |
13
- | see data: status labels, people, objects, numbers and their trend, properties of a record, rows, a board, a schedule, events, code, disclosed sections | Data display — Badge, Avatar, Thumbnail, Kbd, Card, DescriptionList, DataTable, Kanban, Gantt, Timeline, CodeBlock, Accordion, Sparkline, ChartFrame |
13
+ | see data: status labels, people, objects, numbers and their trend, properties of a record, rows, a board, a schedule, events, code, disclosed sections | Data display — Badge, Avatar, Thumbnail, Kbd, Card, DescriptionList, DataTable, Kanban, Gantt, Timeline, CodeBlock, RichText, Accordion, Sparkline, ChartFrame |
14
14
  | hold a conversation: pick a dialog, read and send chat messages of every kind | Messages — Chat, ChatList, Message, Gallery, VoicePlayer, VideoNote, Composer |
15
15
  | learn what happened, how far along it is, or watch a region load and change state; say it to a screen reader | Status and loading — Banner, Notification, ProgressBar, ProgressCircle, Spinner, Skeleton, Crossfade, EmptyPage, VisuallyHidden |
16
16
  | move between places: app sections, panels of a screen, the records of a list, the path, pages, steps | Navigation — Tabs, Breadcrumb, Pagination, List, Stepper, Sidebar, BottomNav |
@@ -33,7 +33,8 @@ Input with `Input.InlineAffix`. Search and filters above a list or table → Sma
33
33
  field outside a list (a picker, a settings section, a panel) → Input with `Input.Icon` + `type="search"`
34
34
  and `aria-label`. Several fields of a form under one heading → Fieldset (`label`, `disabled` while the
35
35
  form saves). Several records of the same fields that a person adds, edits and removes (invoice lines,
36
- contacts, specs, prices by period) → FieldList: one row per record, the headings once.
36
+ contacts, specs, prices by period) → FieldList: one row per record, the headings once; their sums
37
+ under them → its `renderTotals`.
37
38
 
38
39
  **Selection.**
39
40
 
@@ -46,21 +47,27 @@ contacts, specs, prices by period) → FieldList: one row per record, the headin
46
47
  | one of many (> 5) or a long list; icons, descriptions, search, groups | Select (`Select.Content searchable`) |
47
48
  | one record out of thousands for a form field (a client, a contract), searched on the server | Select with `search` / `onSearchChange`, `filter={false}`, `loading`, `hasMore` + `onLoadMore` — never a read-only field that opens a CommandMenu |
48
49
  | one value from a plain text list on a mobile-first form, posted with the form (`name`) | NativeSelect — the OS picker opens on phones |
50
+ | free text the team often repeats (a place, a city), any other text allowed | Input with `suggestions` — never a `<datalist>`, never a Select |
51
+ | a pick that belongs to a text (an icon before a name) | Select with only `Select.TriggerIcon` inside `Input.Wrapper`, named by `aria-label` |
49
52
  | several of many, shown as tags, with search / creation | TagSelect (`Select.Root multiple` when tags are not needed) |
50
53
  | narrowing a list or table by several fields, with search and show / hide | SmartFilter |
51
54
  | an approximate number in a range | Slider (exact number → Input) |
52
55
  | a date or a period | Datepicker |
56
+ | a time of day without a date | Input with `Input.Field type="time"` (a date with a time → Datepicker `withTime`) |
53
57
  | a color from a fixed palette in a form or dialog | ColorSwatches (inline grid, default choice) |
54
- | a compact color next to a field or in a list row | ColorPresets (trigger + popover palette) |
58
+ | a compact color next to a field or in a list row | ColorPresets (trigger + popover palette; `allowCustom` when any hex is valid too) |
55
59
  | any color as a field of a form (swatch + hex, picker in a popover) | ColorField |
56
60
  | any color in a panel of its own (hex, eyedropper, channels) | ColorPicker |
57
61
 
58
62
  **Data.** Status, count, removable value or applied filter → Badge (`tone` for a status, `color` for a
59
- category, `onRemove` to remove, `pressed` / `onPressedChange` to toggle). Person or organization → Avatar. Object (product, vehicle, file, cover) → Thumbnail with a
63
+ category, `onRemove` to remove, `pressed` / `onPressedChange` to toggle); several badges as one value
64
+ (a client's tags) → `Badge.Group`. Person or organization → Avatar. Object (product, vehicle, file, cover) → Thumbnail with a
60
65
  `ratio` — never a round Avatar for things. Keyboard shortcut → Kbd. One block of related
61
66
  numbers/content → Card (pick the template; a whole tile that opens its details → `Card.Root asChild`
62
67
  with a link). Read-only properties of one record («label — value») → DescriptionList, never a
63
- hand-made grid of text pairs. Rows with columns, sorting, selection → DataTable.
68
+ hand-made grid of text pairs. Rows with columns, sorting, selection → DataTable (a header that
69
+ needs explaining → the column's `description`). A KPI tile waiting for its figure →
70
+ `Card.Root loading`, never Skeletons inside its parts.
64
71
  Items that flow through statuses and are moved by hand → Kanban (a single list to reorder, or items
65
72
  dropped onto folders or assignees → Dnd directly). Its look: `plain` (default; `line` for a quieter header over a line) when the board is
66
73
  the page's main region or sits in a Card / Drawer — headers and cards stand on the host, one level of
@@ -69,7 +76,7 @@ columns, a dense board of narrow columns). Never your own box around a column.
69
76
  Records with a start and an end compared across time — who is busy when, a project plan with
70
77
  phases and milestones → Gantt (never a DataTable of dates or hand-drawn bars); moved and resized by
71
78
  hand through `onBarChange`.
72
- Chronological events → Timeline; a conversation between people (support, a team chat) → Message —
79
+ Chronological events → Timeline (two facts under an event → two `Timeline.ItemMeta`, they stack); a conversation between people (support, a team chat) → Message —
73
80
  never a Timeline or a list of Cards styled as bubbles. Code → CodeBlock. A number with its trend over days or weeks (revenue, orders, sign-ups) → Sparkline: the latest value, its change and a line you can scrub; values with a sign by period → Sparkline `variant="bars"`; against a plan → `reference`; missing days → `value: null`; a chart with axes or several series → ChartFrame around a chart library or your own SVG (the panel, legend, tooltip, states and the screen-reader table come from the kit, the colors from `series` / `chartColors` — never status tones); values to read, sort or compare row by row → DataTable, not a chart.
74
81
 
75
82
  **Messages.** A whole conversation → `Chat`: `Chat.Root` with `Chat.Header` (avatar, `Chat.Title`,
@@ -92,7 +99,11 @@ comment, a report) → `Gallery`; one picture of an object → `Thumbnail`. A vi
92
99
  `Message.Media` or `Thumbnail.Video`, not VideoNote. «Печатает…» → `Chat.Root typing` (or
93
100
  `Message.Typing`), never a text row; a jump to a message → `Message.Reply target`, `Chat.Pinned` or
94
101
  `Chat.Root jumpTo`, never `scrollIntoView`; search inside a chat → `Chat.Search`. Files, photos,
95
- places and contacts sent in a chat → the `Message` parts, not FileUpload rows or Cards.
102
+ places and contacts sent in a chat → the `Message` parts, not FileUpload rows or Cards; a sticker →
103
+ `Message.Sticker` (a Lottie or video sticker is your player inside it). A message shown outside a
104
+ chat (a bot's welcome message in its settings) → `Message.Root preview`; its formatted text alone →
105
+ `RichText`, never `dangerouslySetInnerHTML`. Times and date separators in a profile's zone →
106
+ `timeZone` on `Chat.Messages`, `ChatList.Root` and a lone `Message.Root`.
96
107
 
97
108
  **Status and loading.** See the pair below. Progress of a single task → ProgressBar `value`; parts of a whole →
98
109
  ProgressBar `segments` (categories wear `color`, meanings wear `tone`; `legend` for color · name ·
@@ -111,7 +122,8 @@ loading, data, empty and error, or showing one record at a time → its content
111
122
  **Overlays.** See the pairs below. Global search over commands and pages (⌘K) → CommandMenu. Menus,
112
123
  selects, pickers and Modal turn into bottom sheets below 640px by themselves — never build your own.
113
124
  A photo, a scan or a video opened full screen from its thumbnail (a Gallery tile, a Thumbnail button)
114
- → MediaViewer, never a Modal with an `<img>`.
125
+ → MediaViewer, never a Modal with an `<img>`. A titled block inside a Drawer or a Modal (a client's
126
+ contacts, its deals) → `Drawer.Section` / `Modal.Section`.
115
127
 
116
128
  **Phone navigation.** App navigation is Sidebar on a wide screen and `BottomNav` in `AppShell.Footer`
117
129
  on a phone for 2–6 main sections (the shell hides it by itself while the Sidebar rail is on screen;
@@ -129,8 +141,9 @@ active filters (`Page.Chips`) — [the top bar](composition.md#the-top-bar). A d
129
141
  page (settings, a long form, help) or an overview has its large title in the flow (`Page.Header`
130
142
  outside the bar), with an optional `Page.TopBar collapse` that repeats it once it scrolls away. A record opened from a table: its own page → DataTable `getRowHref`; in place →
131
143
  `onRowClick` + Drawer; a list worked through record by record → `Page.Split` with a `List` of
132
- the records (tickets, letters, invoices; `current` on the open one) or a `ChatList` of the
133
- conversations. Sections of a long rail → `Sidebar.Group collapsible`; a section with sub-pages →
144
+ the records (tickets, letters, invoices; `current` on the open one; a row's own Switch or menu in
145
+ `List.ItemActions`) or a `ChatList` of the conversations. A record edited on a page of its own →
146
+ `Page.Guard` holds leaving it with unsaved changes. Sections of a long rail → `Sidebar.Group collapsible`; a section with sub-pages →
134
147
  `Sidebar.Sub`; the signed-in user → `Sidebar.Account` in a `Dropdown.Trigger` at the bottom of
135
148
  `Sidebar.Footer`.
136
149
 
@@ -146,7 +159,7 @@ conversations. Sections of a long rail → `Sidebar.Group collapsible`; a sectio
146
159
  | Select / Dropdown / CommandMenu / TagSelect | Select picks a **value** for a field (form data). Dropdown runs **actions** (Edit, Duplicate, Delete). CommandMenu searches commands and pages across the app (locally or on the server) — it is not a field. TagSelect picks **several values** as tags. |
147
160
  | SmartFilter / SegmentedControl / Select | Search and filters over a list → SmartFilter (`fields={[]}` for a search alone), even with one field. A switch of what the same data shows (sections with counts, a period, a view) → SegmentedControl, or a Select when the options are many. |
148
161
  | Badge / Button | A badge labels or holds a value (status, filter, keyword): `onRemove` drops it, `pressed` toggles it. A command (save, open, go) is always a Button. |
149
- | Banner / Notification / Hint | Banner: persistent message in the page flow about a page/section state (sync failed, trial ends). Notification: transient toast about the result of an action (saved, failed). Hint / `error`: help or validation text under one field. |
162
+ | Banner / Notification / Hint | Banner: persistent message in the page flow about a page/section state (sync failed, trial ends). Notification: transient toast about the result of an action (saved, failed). Hint / `error`: help or validation text under one field; a status of its value (a deadline that nears) → `hintTone` on the field, `tone` on a lone Hint. |
150
163
  | ProgressBar / ProgressCircle / Spinner | ProgressBar: a share with width to spare and words beside it (an upload row, an import, a quota line). ProgressCircle: a share on the data scale (24–80) where width is short or a ring reads better (a KPI goal, a ring breakdown, a named import card); `value={null}` while the share is unknown, `spin` while a share grows, an icon in the centre. Spinner: an anonymous «busy» on the icon scale inside a control or a status line, no share, no name of its own. A ring you can press (cancel, retry, download a transfer) → Button `shape="circle"` with `progress`, not any of the three. |
151
164
  | Button / LinkButton | Button does something (submit, open, delete). LinkButton goes somewhere (a URL, «Подробнее», «Все заказы»). |
152
165
  | Checkbox / Switch / Radio | Checkbox: choice submitted later with a button, can be multiple. Switch: on/off that applies at once (no Save button). Radio: one of several, submitted later. A privacy toggle inside a create form that is submitted → Checkbox. |
@@ -163,7 +176,7 @@ conversations. Sections of a long rail → `Sidebar.Group collapsible`; a sectio
163
176
  | Gantt / Timeline / Kanban / DataTable | Gantt: records with a start and an end compared across time (who is busy when, a plan with phases and milestones), moved, stretched and drawn by hand (`onBarChange`, `onBarCreate`). Timeline: a history of events at points in time, read top to bottom. Kanban: work items moved between statuses, time does not matter. DataTable: many records compared by many attributes, sorted, filtered and selected — dates are only columns. |
164
177
  | DescriptionList / DataTable / Card | DescriptionList: the properties of **one** record as «label — value» pairs (requisites, deal terms, a summary before a confirm). DataTable: **many** records with the same properties. Card: the bounded block they sit in, or one headline number (`mini`, `stat-trend`) — a DescriptionList inside a `Card.Body`, never a Card per property. |
165
178
  | ConfirmDialog / Modal / `holdToConfirm` / undo | Which of them an action deserves is its cost: [ux.md → friction](ux.md#5-friction-matches-the-cost-of-a-mistake). ConfirmDialog only asks a question (`onConfirm` returns the request; pending, failure and dismiss rules are built in); a form, a choice or more than a sentence of content → Modal; never a Modal hand-built as a confirm. |
166
- | Composer / Textarea | Composer: the message input of a conversation — Enter sends, attachments, a reply or edit bar, voice and round video. Textarea: long text saved with a form (a description, a comment field) by its submit button. |
179
+ | Composer / Textarea / RichText | Composer: the message input of a conversation — Enter sends, attachments, a reply or edit bar, voice and round video. Textarea: long text saved with a form (a description, a comment field) by its submit button; `formatting` when the text keeps the chat's Markdown. RichText: that Markdown shown, read-only, outside a message (a template, a bot's welcome text). |
167
180
  | Chat / Message / Gallery | Chat: a conversation — the header, search, pinned, the one message list (dates, runs, selection, jumps, read receipts, typing) and the Composer in its footer; a list without the rest is `Chat.Root` with `Chat.Messages`. Message: one message and its content parts — a row of that list, or a bubble on its own (a quoted reply in a ticket). Gallery: an album of photos and videos laid out by their ratios — `Message.Media` in a message, `Gallery` anywhere else (a post, a comment, a report). |
168
181
  | Message / Timeline / Notification | Message: what people say to each other in a conversation, in bubbles. Timeline: what happened to a record (status changes, an audit log). Notification: the result of the user's own action, for a few seconds. A chat event («Анна добавила Олега») is `Chat.Service`, not a Notification. |
169
182
  | FileUpload list / grid / gallery / strip | One component, four shapes. **List** (`layout="list"`, the default, with the drop zone): documents of a form, where the name matters; rows do not reorder. **Cards** (`trigger="tile"` `layout="grid"`): mixed photos and documents whose order matters (an inspection report). **Gallery** (`trigger="tile"` `layout="grid"` `cover` `accept="image/*"`): the photos of one object (a listing, a product); the first is the cover and dragging a photo first makes it the cover — no «make cover» button. **Strip** (`trigger="button"` + `FileUpload.Queue layout="strip"`): a paperclip and one scrolling row of tiles above a message field; the Composer already is this. Files on a server are `value` + `onValueChange` (return a promise to save); uploads are `queue` from `useUploadQueue`. One picture of a person is the avatar zone, not a gallery. |
@@ -78,6 +78,7 @@ Show data: labels, people, objects, cards, tables, boards, feeds, code, disclose
78
78
  | Gantt | A resource or project schedule: rows of people, rooms, vehicles or tasks with bars on a day / week / month axis, edited by pointer and keyboard. | [COMPONENT.md](reference/components/gantt/COMPONENT.md) · [examples](reference/components/gantt/examples/) |
79
79
  | Timeline | An event feed: dots on a thin line, event title and date, an optional amount on the right, grouped under labels. | [COMPONENT.md](reference/components/timeline/COMPONENT.md) · [examples](reference/components/timeline/examples/) |
80
80
  | CodeBlock | A static TypeScript / TSX snippet with syntax highlighting, on a filled panel or bare inside a host. | [COMPONENT.md](reference/components/code-block/COMPONENT.md) · [examples](reference/components/code-block/examples/) |
81
+ | RichText | Text in the chat's Markdown subset outside a message — a bot's welcome text, a template, a comment — rendered as elements, never as HTML. | [COMPONENT.md](reference/components/rich-text/COMPONENT.md) · [examples](reference/components/rich-text/examples/) |
81
82
  | Accordion | Collapsible sections: FAQ, settings groups, checkout steps. | [COMPONENT.md](reference/components/accordion/COMPONENT.md) · [examples](reference/components/accordion/examples/) |
82
83
  | Sparkline | A small chart — a smooth line over a fading area, a bare line or bars — with its headline: the value at rest, its change and its date; any point can be scrubbed. | [COMPONENT.md](reference/components/sparkline/COMPONENT.md) · [examples](reference/components/sparkline/examples/) |
83
84
  | ChartFrame | A dashboard panel around any chart — Recharts, ECharts, visx, Chart.js or a hand-drawn SVG — with the title, a headline value, a measured plot area, a series legend, a tooltip body and a data table for screen readers. | [COMPONENT.md](reference/components/chart-frame/COMPONENT.md) · [examples](reference/components/chart-frame/examples/) |
@@ -57,9 +57,10 @@ Never two titles; never no `<h1>`. A bar whose page shows no title still holds a
57
57
  ```
58
58
  AppShell.Root once per app (layouts.md#app-frame-once-per-app)
59
59
  ├─ AppShell.Nav → Sidebar navigation rail, one layer above the page
60
- └─ AppShell.Main gutters + top air (24, 32 from 1024px); the page renders here
60
+ └─ AppShell.Main gutters, top air (24, 32 from 1024px), bottom air 64; the page renders here
61
61
  └─ SmartFilter.Root a filtered page only: the filter's state, no DOM, around the page
62
62
  └─ Page.Root the page you deliver
63
+ ├─ Page.Guard a page that edits a record: holds leaving it (busy, dirty); no DOM
63
64
  ├─ Page.TopBar sticky; led by the menu button while the Sidebar is off-canvas
64
65
  │ ├─ Page.Heading the path › the title on one line, the line of data under it
65
66
  │ │ ├─ Breadcrumb.Root only on a nested record: the levels above this page
@@ -68,7 +69,7 @@ AppShell.Root once per app (layouts.md#app-frame-once-per-a
68
69
  │ ├─ Page.Tools sections, search and filter, view — kit controls in reading order
69
70
  │ ├─ Page.Actions every button of the page, each with an icon; the primary last
70
71
  │ └─ Page.Chips SmartFilter.Chips: a line under everything, gone while empty
71
- └─ Page.Body blocks 24 apart, starting at main's top air
72
+ └─ Page.Body blocks 24 apart, the first 24 under the bar's controls
72
73
  ├─ Page.Grid every tile of the screen, 16 on both axes
73
74
  │ └─ Page.Cell span (of 12), rows; the tile fills it
74
75
  └─ Page.Section a new topic: h2 label (+ Page.Actions), 40 from its neighbours
@@ -84,7 +85,7 @@ Document page or overview: the title in the flow
84
85
  `NotificationProvider` and `AppShell` live at the app root.
85
86
  - `Page.Root` for app pages (full panel width); `Page.Root maxWidth="wide"` for settings,
86
87
  `maxWidth="readable"` for one narrow column of text or fields.
87
- - No outer padding, no margins: `AppShell.Main` hands the page its gutters and top air, `Page` has
88
+ - No outer padding, no margins: `AppShell.Main` hands the page its gutters and its top and bottom air, `Page` has
88
89
  block → block 24, section → section 40, tile → tile 16 (and header → body 24). Your CSS lays out
89
90
  only the inside of a block.
90
91
 
@@ -190,11 +191,12 @@ Every gap is a `--prime-space-N` token (N × 4px) set as `gap` on the parent. Cl
190
191
  | tile → tile, both axes (KPI, charts, panels) | 16 | `Page.Grid` |
191
192
  | field → field, switch → switch | 20 | `--prime-space-5` |
192
193
  | main column → side column | 24 | `--prime-space-6` |
193
- | top bar → the page's first block | 12 + 24, 12 + 32 from 1024px | the bar's bottom air + the page's top air from `AppShell.Main` |
194
+ | top bar → the page's first block | 24 under the bar's controls | the rhythm's header → body; the bar's own bottom padding is part of it |
194
195
  | flow header → body (a document page) | 24 | `Page.Header` |
195
196
  | block → block on a page | 24 | `Page.Body` |
196
197
  | group → group in a form | 32 | `--prime-space-8` |
197
198
  | section → section (a new topic with an `h2`) | 40 | `Page.Section` |
199
+ | the page's last block → the panel's bottom edge | 64; a `Page.Root fill` page the side gutter | `AppShell.Main` (beside the Sidebar rail too) |
198
200
 
199
201
  A screen with one gap everywhere reads as a list of unrelated things. Use at least three levels: inside
200
202
  an item, between items, between blocks. The tiles of one screen are one board: put them all in one
@@ -211,6 +213,7 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
211
213
  | section heading without a card | `Page.Section label` (an `h2` in `title-m`) |
212
214
  | card / panel title | `Card.Title as="h2"` (under the page title) |
213
215
  | heading of a group of fields | `Fieldset label` (`title-s` in the `<legend>`) |
216
+ | section heading inside a Drawer or a Modal | `Drawer.Section label` / `Modal.Section label` (an `h3` in `title-s`, `Drawer.Actions` at its end) |
214
217
  | other group heading inside a card | `Typography as="h3" variant="title-s"` |
215
218
  | body text, cells | `body-m` |
216
219
  | lead under a heading | `body-m` + `tone="secondary"` |
@@ -270,6 +273,9 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
270
273
  - One value in several units is one row of fields under one heading, each unit in its field's
271
274
  `Input.InlineAffix`; a base value with overrides is one `FieldList` with the base pinned first.
272
275
  The shape of the data decides the layout: [ux.md](ux.md#2-the-shape-of-the-data-decides-the-layout).
276
+ - Rows that add up (shares of a bonus, hours, amounts) carry their sums under them in the same list:
277
+ `FieldList renderTotals`, a short number in a `width: "xs"` column. Return the save's promise from
278
+ its `onValueChange` when each edit saves at once.
273
279
  - Fields 20 apart; related short fields side by side in an
274
280
  [intrinsic grid](responsive.md#intrinsic-grids) with `align-items: start`
275
281
  (`reserveSupportRow` on Input / Textarea keeps neighbours aligned); groups 32 apart, each a
@@ -323,6 +329,7 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
323
329
  | a form saved and still on screen | the submit button's text: «Сохранено» |
324
330
  | a state of the page or a section that persists (sync failed, trial ends, read-only) | `Banner` as the first block of `Page.Body` (or inside the Card it concerns) |
325
331
  | a problem with one field | the field's `error` |
332
+ | a refused sign-in | `invalid` on the fields it concerns and the reason in an always-mounted `VisuallyHidden live="assertive"` — no Banner on a sign-in or sign-up form ([LoginForm](reference/components/login-form/COMPONENT.md)) |
326
333
  | a region failed to load | the region shows it in place: DataTable `loadError`, or `EmptyPage tone="danger"` with `EmptyPage.RetryButton` |
327
334
  | a region is loading | DataTable `loading`; otherwise a `Skeleton` in the geometry of the content + `aria-busy` on the region; `Spinner` only when there is no shape to hold |
328
335
  | a region changes state (loading → data → empty → error, another record) | its content in `Crossfade state={status}` — always, so the change flows instead of flipping |
@@ -353,6 +360,12 @@ Every overlay closes on Escape and on a press outside unless you turn it off
353
360
  closing with `Modal.Guard` / `Drawer.Guard` (`busy` while a request runs, `dirty` for unsaved
354
361
  changes) instead.
355
362
 
363
+ A toast is no layer of that stack: `notify()` from inside a Drawer leaves it open, and a press on
364
+ the toast closes nothing below it. Titled groups of a dialog's body (a client's contacts, its deals)
365
+ are `Drawer.Section` / `Modal.Section` with their own `Drawer.Actions` / `Modal.Actions`, not a
366
+ hand-made heading row; a footer's `Drawer.Meta` / `Modal.Meta` goes above the buttons by itself when
367
+ the footer runs short.
368
+
356
369
  ## 11. Narrow screens
357
370
 
358
371
  - Layouts are [intrinsic](responsive.md#intrinsic-grids) — no breakpoints needed, and they work in
@@ -504,7 +517,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
504
517
  - An overview: the large `Page.Title` «Обзор» with a `Page.Description` line of data (what and as of when) in a `Page.Header` in the flow; a `Page.TopBar collapse` above it keeps the page's tools and buttons in reach and repeats the title once it scrolls away; the body starts with the tiles.
505
518
  - The period is a `SegmentedControl` in the bar's `Page.Tools` and the report a `soft` + `neutral` button with an icon in its `Page.Actions`: the period changes the values on this same screen. Tabs (`Tabs`) are for different content, not for a period.
506
519
  - Every tile of the screen is one `Page.Grid`: KPI `Card variant="stat-trend"` in `Page.Cell span={3}`, 16 px on both axes; `Card.Delta` with a `tone` by the meaning of the change: overdue growing is `danger`, its decline `success`.
507
- - Another period — other values: each KPI tile is wrapped in `Crossfade state={period}` and flows instead of blinking.
520
+ - Another period — other values: each KPI tile is wrapped in `Crossfade state={period}` and flows instead of blinking; a Crossfade right in a `Page.Cell` fills it, so the tiles of a row keep one height.
508
521
  - A trend over days is a `Sparkline` in a `Card` in a full-width cell: the last value, the change and a line you can scrub with a finger; a new series appears by itself.
509
522
  - A chart with axes and several series is a `ChartFrame` in `Page.Cell span={8} rows={2}` beside two panels; `ChartFrame.Plot height="fill"` takes their height. A sized plot area (`renderPlot`), a legend that turns series off and a table for screen readers; series colors come from `series`, never your own.
510
523
  - Panels are `Card variant="panel"` with `Card.Title as="h2"` in `Card.Header` and quiet context on the right after the title (`caption` + `muted`).
@@ -519,6 +532,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
519
532
  - The top bar carries the page: `Page.Title` with a `Page.Description` line of data — the accounts and the date of the statement the screen stands on; there is no second header in the flow.
520
533
  - An error that concerns the whole page is a `Banner tone="danger"` as the first block of `Page.Body` with a «Повторить» action; the action's result is a `Notification`.
521
534
  - A region's error is in the region's place: `loadError` on the `DataTable` (the table head stays), not a pop-up.
535
+ - The page's line of data that waits for a load (`Page.Description` with counts or sums) stays mounted with `loading` — mounting it after the load moves the title in the bar.
522
536
  - Table loading is `loading` + `loadingRows` (a skeleton of rows); card loading is a `Skeleton` in the shape of its rows and `aria-busy` on the card, and the block's height does not change. The state change is smooth: the card's region is in `Crossfade state={status}`, the table body changes by itself.
523
537
  - Stale data beats emptiness: the balances are shown as of the previous date, and that is said on the right in `Card.Header`.
524
538
  - Empty on the first run is `EmptyPage size="s"` inside the panel: an icon with `tone="accent"`, what it is and why, one action. An empty filter result is the table's `empty`.
@@ -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).
@@ -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`. |