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
@@ -34,6 +34,7 @@ Modal.Root state and dismiss policy (no DOM)
34
34
  │ ├── Modal.Description <p>, describes the dialog
35
35
  │ └── Modal.Actions row of tier-s controls before the close button
36
36
  ├── Modal.Body the only scrolling zone
37
+ │ └── Modal.Section <section>: <h3> label, description, Modal.Actions at the heading's end
37
38
  └── Modal.Footer actions, primary last
38
39
  ├── Modal.Meta <p>, muted status at the start
39
40
  ├── Modal.Close clones its child; closes on click
@@ -83,7 +84,7 @@ No DOM, no ref. State and dismiss policy.
83
84
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `className` and the other attributes of the `<header>`. |
84
85
 
85
86
  ### Modal.Actions
86
- `ref` → `HTMLDivElement`. A row of controls at the end of the header, before the close button: a status Badge, a ⋯ Dropdown, an icon button. Controls inside take tier `s`.
87
+ `ref` → `HTMLDivElement`. A row of controls at the end of the header, before the close button: a status Badge, a ⋯ Dropdown; under the title when it would get less than 12rem; or at the end of a `Modal.Section` heading. Controls inside take tier `s`.
87
88
 
88
89
  | Prop | Type | Default | Description |
89
90
  |---|---|---|---|
@@ -98,12 +99,20 @@ No DOM, no ref. State and dismiss policy.
98
99
  | `children` | `ReactNode` | — (required) | An `Icon` (sized to the m icon), or the record's picture: an `Avatar.Root` (sized to the tile) or a `Thumbnail.Root`. |
99
100
  | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other attributes of the tile. |
100
101
 
101
- ### Modal.Title · Modal.Description
102
- `ref` → `HTMLHeadingElement` / `HTMLParagraphElement`. `<h2>` (title-m) / `<p>` (body-s, muted); their ids name and describe the dialog.
102
+ ### Modal.Title
103
+ `ref` → `HTMLHeadingElement`. `<h2>` (title-m); its id names the dialog.
103
104
 
104
105
  | Prop | Type | Default | Description |
105
106
  |---|---|---|---|
106
- | `…rest` | `Omit<HTMLAttributes<HTMLHeadingElement \| HTMLParagraphElement>, "id">` | — | `className`, `children` and the other attributes except `id` (the dialog owns it). |
107
+ | `…rest` | `Omit<HTMLAttributes<HTMLHeadingElement>, "id">` | — | `className`, `children` and the other attributes except `id` (the dialog owns it). |
108
+
109
+ ### Modal.Description
110
+ `ref` → `HTMLParagraphElement`. `<p>` (body-s, muted) under the title; its id describes the dialog.
111
+
112
+ | Prop | Type | Default | Description |
113
+ |---|---|---|---|
114
+ | `loading` | `boolean` | `false` | Its data loads: a `Skeleton` holds the line's place and `aria-busy` is set, so the header never moves; the data fades in. |
115
+ | `…rest` | `Omit<HTMLAttributes<HTMLParagraphElement>, "id">` | — | `className`, `children` and the other attributes except `id` (the dialog owns it). |
107
116
 
108
117
  ### Modal.Body
109
118
  `ref` → `HTMLDivElement`. The only scrolling zone (a ScrollContainer), 16 gap between blocks.
@@ -112,12 +121,21 @@ No DOM, no ref. State and dismiss policy.
112
121
  |---|---|---|---|
113
122
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `className`, `children` and the other attributes of the zone. |
114
123
 
124
+ ### Modal.Section
125
+ `ref` → `HTMLElement`. A titled `<section>` of the body: an `<h3>` (title-s, under the `<h2>` title), an optional description, a `Modal.Actions` at the end of the heading row; the content 16 below, sections 32 apart, in any wrapper. Fields group in a `Fieldset`.
126
+
127
+ | Prop | Type | Default | Description |
128
+ |---|---|---|---|
129
+ | `label` | `ReactNode` | — (required) | The heading of the section; it names the `<section>`. |
130
+ | `description` | `ReactNode` | — | One line of context under the heading (body-s). |
131
+ | `…rest` | `Omit<HTMLAttributes<HTMLElement>, "title">` | — | `children` (the content and an optional `Modal.Actions`), `className` and the other attributes of the `<section>`. |
132
+
115
133
  ### Modal.Footer
116
134
  `ref` → `HTMLElement`. Renders `<footer>` with the actions, primary last, and an optional `Meta` at the start.
117
135
 
118
136
  | Prop | Type | Default | Description |
119
137
  |---|---|---|---|
120
- | `layout` | `"fill" \| "end"` | `"fill"` for `xs` `s` `m`, `"end"` for `l` `xl` | `fill`: equal-width buttons in one row; `end`: auto width, at the end. Stacked on phones (viewport below 640 px) and in a dialog narrower than 360 px. |
138
+ | `layout` | `"fill" \| "end"` | `"fill"` for `xs` `s` `m`, `"end"` for `l` `xl` | `fill`: equal-width buttons in one row, wrapping rather than cutting a label; `end`: auto width, at the end. Stacked on phones (viewport below 640 px) and in a dialog narrower than 360 px. |
121
139
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `className`, `children` and the other attributes of the `<footer>`. |
122
140
 
123
141
  ### Modal.Meta
@@ -128,7 +146,7 @@ No DOM, no ref. State and dismiss policy.
128
146
  | `…rest` | `HTMLAttributes<HTMLParagraphElement>` | — | `className`, `children` and the other attributes of the `<p>`. |
129
147
 
130
148
  ### Modal.Guard
131
- No DOM, no ref. Holds the dialog from anywhere inside Content, where the form state lives; renders only the built-in «discard changes?» confirm. Closing from code is never held.
149
+ No DOM, no ref. Holds the dialog from anywhere inside Content, where the form state lives; renders only the built-in «discard changes?» confirm. While `busy` or `dirty` the browser also asks before the tab closes or reloads. Closing from code is never held.
132
150
 
133
151
  | Prop | Type | Default | Description |
134
152
  |---|---|---|---|
@@ -165,10 +183,10 @@ Below 640px of viewport a centred dialog of every size becomes a full-width bott
165
183
  ### layout (Footer)
166
184
  | Value | Looks like | Use when | Default |
167
185
  |---|---|---|---|
168
- | `fill` | Equal-width buttons in one row, gap 12 | Narrow dialogs with two actions | for `xs`, `s`, `m` |
186
+ | `fill` | Equal-width buttons in one row, gap 12; never narrower than their labels — when they do not fit, the last ones wrap to a row of their own | Narrow dialogs with two actions | for `xs`, `s`, `m` |
169
187
  | `end` | Auto-width buttons at the end, gap 8 | Wide dialogs | for `l`, `xl` |
170
188
 
171
- With a `Modal.Meta` the footer is a row in any layout: the status takes the free room at the start, the buttons keep their width at the end. On phones (viewport below 640px, where the dialog is a bottom sheet) and in a dialog narrower than 360px, actions stack full width in DOM order (primary last), the status above them.
189
+ With a `Modal.Meta` the footer is a row in any layout: the status takes the free room at the start, the buttons keep their width at the end and stay one row — when the footer runs short the status goes above them first. On phones (viewport below 640px, where the dialog is a bottom sheet) and in a dialog narrower than 360px, actions stack full width in DOM order (primary last), the status above them.
172
190
 
173
191
  ### tone (Icon)
174
192
  | Value | Looks like | Use when | Default |
@@ -208,6 +226,9 @@ Around the record's picture — an `Avatar.Root` or a `Thumbnail.Root` (found by
208
226
  | closing | `open` turns false | the dialog keeps showing the children it had open while the exit motion plays |
209
227
  | busy | `Modal.Guard busy` | Escape, the scrim, a swipe, the close button and `Modal.Close` do nothing; the close button and the `Close` child are `disabled` in place |
210
228
  | dirty | `Modal.Guard dirty` | a dismiss opens the «Не сохранять изменения?» ConfirmDialog (`labels.discard*`); its confirm closes the dialog; a swipe past the threshold glides back and asks the same |
229
+ | leaving the tab | `Modal.Guard busy` or `dirty` | the browser asks before the tab closes or reloads (`beforeunload`), as with `Page.Guard` |
230
+ | description loading | `Modal.Description loading` | a `Skeleton` holds the line, `aria-busy` on the `<p>`; the data fades in under the same element |
231
+ | actions below | the title would get less than 12rem beside `Modal.Actions` | `data-actions-below` on the header: the actions move under the title and the description |
211
232
  | sheet | a centred dialog on a phone viewport | `data-sheet` on the dialog: the bottom-sheet surface, the grab handle while it swipes |
212
233
  | swiping | as a phone sheet, a drag down from the handle or the header (with `closeOnOutsideClick`; without it there is no handle and no swipe) | `data-swiping` and `--swipe-offset` on the dialog while the pointer drags it; past 30% of its height or on a flick it closes, otherwise it glides back; a busy `Modal.Guard` sets `data-swipe-disabled` and the handle takes no drag |
213
234
  | placement | `placement` on Content | `data-placement` on the dialog |
@@ -221,6 +242,7 @@ Motion (`overlayMotion`): the scrim fades in and out over `base`; the dialog fad
221
242
  - Max height = viewport minus `--prime-modal-viewport-padding` on both sides; only the body scrolls.
222
243
  - Tabs in the header: a `Tabs.List` inside `Modal.Header` (with `Tabs.Root` around the header and the body) spans the header's last row. The header becomes the tab strip, edge to edge and without the hairline; the active tab is the body rising into it, the panels sit in the body.
223
244
  - Fields inside take the floating layer's fill; a card inside is the next layer (`floating-1`), a flat tile. Group fields with a 20 gap.
245
+ - Titled groups of a long body: `Modal.Section label` — an `<h3>` under the dialog's `<h2>` title, an optional `description`, a `Modal.Actions` at the end of its heading row; content 16 below, sections 32 apart — directly in the body, in a `Tabs.Panel` or in a form, with no spacing wrapper of your own. A group of fields is a `Fieldset`.
224
246
  - Narrow viewport (< 640px), `placement="center"`: the shared bottom-sheet surface (`src/internal/sheet.module.css`) — full width, rounded top, a grab handle strip on top while it swipes, up to the sheet top gap high, the bottom safe-area inset below the content.
225
247
  - `placement="top"`: the dialog hangs from a line 12% of the viewport down (between `--prime-modal-viewport-padding` and `--prime-space-24`) and grows downward.
226
248
 
@@ -262,7 +284,7 @@ Motion (`overlayMotion`): the scrim fades in and out over `base`; the dialog fad
262
284
  | [overview.tsx](examples/overview.tsx) | A trigger opens a dialog with a title, a field and two actions; Enter presses the confirm — `Modal.Trigger`, `Modal.Confirm`. |
263
285
  | [structure.tsx](examples/structure.tsx) | Optional parts: an icon tile with a header-only notice, and a header with a status before the close button and a footer with no body between them — `Modal.Icon`, `Modal.Actions`, `Modal.Footer`. |
264
286
  | [sizes.tsx](examples/sizes.tsx) | Every width; xs, s and m fill the footer with equal buttons, l and xl align them to the end — `size`. |
265
- | [long-content.tsx](examples/long-content.tsx) | A long body scrolls on its own while the header and the footer stay in place; the primary closes the dialog and is the Enter target — `Modal.Body`, `Modal.Confirm`. |
287
+ | [long-content.tsx](examples/long-content.tsx) | A long body in titled sections scrolls on its own while the header and the footer stay in place; the primary closes the dialog and is the Enter target — `Modal.Body`, `Modal.Section`, `Modal.Confirm`. |
266
288
  | [custom-container.tsx](examples/custom-container.tsx) | The dialog portals into a given node instead of the document body — `container`. |
267
289
  | [guard.tsx](examples/guard.tsx) | An edit form that holds the dialog: changes make any dismiss ask first and show a note in the footer, the submit button saves the form, and while saving nothing closes it — `Modal.Guard`, `Modal.Meta`. |
268
290
  | [enter-key.tsx](examples/enter-key.tsx) | Enter in a dialog: handing a workspace over takes a click, never Enter in its field; in the tags dialog Enter adds the typed tag instead of closing — `confirmOnEnter`, `onEnterConfirm`. |
@@ -274,11 +296,14 @@ Motion (`overlayMotion`): the scrim fades in and out over `base`; the dialog fad
274
296
  - A destructive confirm built as a Modal → use ConfirmDialog. A destructive step that needs input (type the name to delete) stays a Modal with `closeOnOutsideClick={false}`.
275
297
  - Primary action not in `Modal.Confirm` → Enter does nothing; wrap it. In a form, the `type="submit"` button is the primary and stays outside `Modal.Confirm`: Enter in a field submits the form.
276
298
  - Primary action first in the footer → put it last (DOM order is the visual order).
299
+ - «Удалить» as a third footer button of an edit dialog → the footer is the dialog's answer (Cancel + the primary); deleting the record goes to the last `tone="danger"` item of a ⋯ `Dropdown` in `Modal.Actions`, confirmed by a ConfirmDialog.
300
+ - A wrapper with its own gap around `Modal.Section`s inside a `Tabs.Panel` or a form → not needed: sections keep 32 between them in any wrapper.
301
+ - A hand-made heading row (`Typography as="h3"` + buttons) over a group in the body → `Modal.Section label` with `Modal.Actions`.
277
302
  - Busy or unsaved state lifted out of the form into Root props → put `Modal.Guard` next to the form, inside Content.
278
303
  - `key` or a conditional around `Modal.Root` to reset a form → the dialog unmounts at once, without its exit; keep the Root mounted and drive `open`.
279
304
  - Scrolling wrapper inside Modal.Body → Body already scrolls.
280
305
  - Long multi-section forms in a modal → use Drawer.
281
306
 
282
307
  ## Related
283
- - **Built from:** the dialog shared with Drawer and Sheet (`internal/overlay/DialogParts`, `internal/overlay/ModalDialog`), [Button](../button/COMPONENT.md) (header close button), [ScrollContainer](../scroll-container/COMPONENT.md) (`Modal.Body`), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (a record's picture in `Modal.Icon`), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (the `Modal.Guard` discard confirm), `Icon` (the close glyph)
308
+ - **Built from:** the dialog shared with Drawer and Sheet (`internal/overlay/DialogParts`, `internal/overlay/ModalDialog`), [Button](../button/COMPONENT.md) (header close button), [ScrollContainer](../scroll-container/COMPONENT.md) (`Modal.Body`), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (a record's picture in `Modal.Icon`), [Skeleton](../skeleton/COMPONENT.md) (`Modal.Description loading`), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (the `Modal.Guard` discard confirm), `Icon` (the close glyph)
284
309
  - **See also:** [ConfirmDialog](../confirm-dialog/COMPONENT.md) (a destructive confirm built on Modal `s`), [Drawer](../drawer/COMPONENT.md) (the same header, body, footer and `Guard` at the edge), [Popover](../popover/COMPONENT.md), [CommandMenu](../command-menu/COMPONENT.md)
@@ -1,19 +1,34 @@
1
- /** A long body scrolls on its own while the header and the footer stay in place; the primary closes the dialog and is the Enter target — `Modal.Body`, `Modal.Confirm`. */
1
+ /** A long body in titled sections scrolls on its own while the header and the footer stay in place; the primary closes the dialog and is the Enter target — `Modal.Body`, `Modal.Section`, `Modal.Confirm`. */
2
2
  import { Button, Modal, Typography } from "prime-ui-kit";
3
3
 
4
- const TERMS = [
5
- "Исполнитель обрабатывает персональные данные только для оказания услуги.",
6
- "Данные не передаются третьим лицам без письменного согласия заказчика.",
7
- "Доступ к данным получают только сотрудники, которым он нужен для работы.",
8
- "Резервные копии хранятся в зашифрованном виде в дата-центрах на территории РФ.",
9
- "Заказчик может запросить выгрузку своих данных в течение 10 рабочих дней.",
10
- "По запросу заказчика данные удаляются в течение 30 дней после расторжения договора.",
11
- "О каждом инциденте безопасности исполнитель сообщает заказчику в течение 24 часов.",
12
- "Журналы доступа хранятся не меньше одного года и выдаются по запросу.",
13
- "Исполнитель ежегодно проходит внешний аудит защиты информации.",
14
- "Субподрядчики допускаются только с согласия заказчика и на тех же условиях.",
15
- "Изменения условий публикуются за 30 дней до вступления в силу.",
16
- "Споры решаются в арбитражном суде по месту нахождения исполнителя.",
4
+ const SECTIONS = [
5
+ {
6
+ title: "Обработка и хранение",
7
+ terms: [
8
+ "Исполнитель обрабатывает персональные данные только для оказания услуги.",
9
+ "Данные не передаются третьим лицам без письменного согласия заказчика.",
10
+ "Доступ к данным получают только сотрудники, которым он нужен для работы.",
11
+ "Резервные копии хранятся в зашифрованном виде в дата-центрах на территории РФ.",
12
+ ],
13
+ },
14
+ {
15
+ title: "Права заказчика",
16
+ terms: [
17
+ "Заказчик может запросить выгрузку своих данных в течение 10 рабочих дней.",
18
+ "По запросу заказчика данные удаляются в течение 30 дней после расторжения договора.",
19
+ "О каждом инциденте безопасности исполнитель сообщает заказчику в течение 24 часов.",
20
+ "Журналы доступа хранятся не меньше одного года и выдаются по запросу.",
21
+ ],
22
+ },
23
+ {
24
+ title: "Аудит и изменения",
25
+ terms: [
26
+ "Исполнитель ежегодно проходит внешний аудит защиты информации.",
27
+ "Субподрядчики допускаются только с согласия заказчика и на тех же условиях.",
28
+ "Изменения условий публикуются за 30 дней до вступления в силу.",
29
+ "Споры решаются в арбитражном суде по месту нахождения исполнителя.",
30
+ ],
31
+ },
17
32
  ];
18
33
 
19
34
  export default function ModalLongContentExample() {
@@ -30,10 +45,14 @@ export default function ModalLongContentExample() {
30
45
  <Modal.Description>Редакция от 1 октября 2026 года</Modal.Description>
31
46
  </Modal.Header>
32
47
  <Modal.Body>
33
- {TERMS.map((line) => (
34
- <Typography key={line} variant="body-m" tone="secondary">
35
- {line}
36
- </Typography>
48
+ {SECTIONS.map((section) => (
49
+ <Modal.Section key={section.title} label={section.title}>
50
+ {section.terms.map((line) => (
51
+ <Typography key={line} variant="body-m" tone="secondary">
52
+ {line}
53
+ </Typography>
54
+ ))}
55
+ </Modal.Section>
37
56
  ))}
38
57
  </Modal.Body>
39
58
  <Modal.Footer>
@@ -41,6 +41,7 @@ NativeSelect field frame: label row · control · support row
41
41
  |---|---|---|---|
42
42
  | `label` | `ReactNode` | — | Field label above; a `<label htmlFor>` of the select. |
43
43
  | `hint` | `ReactNode` | — | Support text under the field; linked by `aria-describedby`. |
44
+ | `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. |
44
45
  | `error` | `ReactNode` | — | Error message; replaces the hint and implies `invalid`. |
45
46
  | `required` | `boolean` | `false` | Red `*` after the label and native `required`. |
46
47
  | `optional` | `boolean` | — | Muted `labels.optional` after the label. |
@@ -156,7 +156,7 @@ Collapsed stacks show up to 3 cards peeking behind each other. Hover or keyboard
156
156
  - Each stack is an `<ol>` named by the region label of its position; each card is an `<article>`: `role="status"` + `aria-live="polite"`, or `role="alert"` + `aria-live="assertive"` for `warning` / `danger`.
157
157
  - The icon and the countdown are `aria-hidden`; the close button is named by `labels.close`.
158
158
  - Focus never leaves with a toast: a card that goes while it holds focus hands it to the front card still open in its stack, else back to where focus came from.
159
- - A toast is a non-modal live region, not a layer of the overlay stack: Escape and a press elsewhere belong to the page and to the topmost open layer, so a toast takes no `closeOnEscape` / `closeOnOutsideClick` — it leaves by its timer, the close button, a swipe or `dismiss()`.
159
+ - A toast is a non-modal live region, not a layer of the overlay stack: Escape and a press elsewhere belong to the page and to the topmost open layer, so a toast takes no `closeOnEscape` / `closeOnOutsideClick` — it leaves by its timer, the close button, a swipe or `dismiss()`. The toasts stay usable above an open Modal or Drawer, and a press on a toast is outside no layer: it never closes the dialog below.
160
160
  - Do not keep the only copy of important information in a toast with a timer — use `persistent` or a Banner.
161
161
 
162
162
  ### Labels
@@ -47,6 +47,7 @@ Radio.Group field: group label → role="radiogroup" → hint | erro
47
47
  | `required` | `boolean` | `false` | Red `*` after the label, native `required` on the radios and `aria-required` on the group. |
48
48
  | `optional` | `boolean` | `false` | Muted marker right after the label text (`labels.optional`). |
49
49
  | `hint` | `ReactNode` | — | Help text under the options. 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 under the options; implies `invalid`. |
51
52
  | `invalid` | `boolean` | `false` | Danger ring on the unchecked circles and `aria-invalid` on the group and the inputs. A non-empty `error` implies it. |
52
53
  | `disabled` | `boolean` | `false` | Disables every option. |
@@ -64,6 +65,7 @@ Radio.Group field: group label → role="radiogroup" → hint | erro
64
65
  |---|---|---|---|
65
66
  | `value` | `string` | — (required) | Value reported to the group when this option is chosen. |
66
67
  | `hint` | `ReactNode` | — | Description under the option text, linked through `aria-describedby`. |
68
+ | `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. |
67
69
  | `disabled` | `boolean` | `false` | Disables this option only. |
68
70
  | `children` | `ReactNode` | — | `Radio.Label`. Without it only the circle renders — give the root an `aria-label`. |
69
71
  | `className` | `string` | — | Class on the option `<div>`. |
@@ -0,0 +1,93 @@
1
+ # RichText
2
+
3
+ **Category:** data-display
4
+ **Kind:** primitive
5
+
6
+ > 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.
7
+
8
+ ## When to use
9
+ - A text written with the formatting toolbar of `Composer` or `Textarea formatting` is shown somewhere other than a chat: a bot's settings, a template list, a card.
10
+ - Text from users or bots that may carry `**bold**`, links or a `||spoiler||` and must be rendered safely.
11
+
12
+ ## When not to use
13
+ - The text of a chat message → [Message](../message/COMPONENT.md) `Message.Text` (it marks search matches and holds the meta's room).
14
+ - A whole message shown outside a chat (a bot's welcome message with its keyboard) → `Message.Root preview`.
15
+ - Plain text with a role → [Typography](../typography/COMPONENT.md).
16
+ - Source code → [CodeBlock](../code-block/COMPONENT.md).
17
+
18
+ ## Import
19
+ ```tsx
20
+ import { RichText } from "prime-ui-kit";
21
+ ```
22
+
23
+ ## Anatomy
24
+ ```
25
+ RichText <div>; the source as children
26
+ ├─ <p> a run of text, line breaks kept
27
+ ├─ <blockquote> lines starting with "> "
28
+ └─ <pre> fenced code
29
+ ```
30
+
31
+ ## API
32
+
33
+ <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
34
+
35
+ ### RichText
36
+ `ref` → `HTMLDivElement`. Text in the chat's Markdown subset outside a message, rendered as elements in the type of its host.
37
+
38
+ | Prop | Type | Default | Description |
39
+ |---|---|---|---|
40
+ | `children` | `string` | — (required) | The source: `**bold**`, `_italic_`, `~~strike~~`, `\|\|spoiler\|\|`, inline and fenced code, links (`[label]` then the address in parentheses), bare URLs, `> ` quotes; line breaks are kept. |
41
+ | `labels` | `Partial<RichTextLabels>` | — | Built-in strings, see Labels. |
42
+ | `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "children">` | — | `className`, `id`, `data-*` and the other div attributes. |
43
+
44
+ ## Variants
45
+ One look: the type of its host. Wrap it in `Typography as="div"` to pick a text role.
46
+
47
+ ## States
48
+ | State | Driven by | DOM |
49
+ |---|---|---|
50
+ | masked spoiler | `\|\|text\|\|` in `children` | `role="button"`, `data-state="masked"`: a wash over transparent words |
51
+ | revealed spoiler | a press, `Enter` or `Space` | `data-state="revealed"`: the wash and the words cross-fade on the fade clock; one way, like a chat |
52
+
53
+ ## Layout & spacing
54
+ - Paragraphs, quotes and code blocks keep their line breaks and wrap anywhere, so a long address never widens the host; blocks stand 4px apart.
55
+ - A quote is a wash with an accent bar on its start edge; code is the mono face on a wash.
56
+ - No size of its own: the root takes the font and color of its host.
57
+
58
+ ## Accessibility
59
+
60
+ ### Keyboard
61
+ <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
62
+
63
+ | Key | Action |
64
+ |---|---|
65
+ | `Tab` | Moves through the links and the masked spoilers. |
66
+ | `Enter` · `Space` | On a masked spoiler: reveals it and moves the focus onto its text. |
67
+
68
+ ### ARIA
69
+ <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
70
+
71
+ - A masked spoiler is a `role="button"` named `labels.spoiler`; its words are out of the accessibility tree until it is revealed.
72
+ - Links take only http(s), `mailto:` and `tel:` addresses and open in a new tab (`rel="noopener noreferrer"`); markup is never parsed as HTML.
73
+
74
+ ### Labels
75
+ <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
76
+
77
+ | Key | Default | Used for |
78
+ |---|---|---|
79
+ | `spoiler` | `"Скрытый текст"` | The name of a masked spoiler until it is revealed. |
80
+
81
+ ## Examples
82
+ | Example | Shows |
83
+ |---|---|
84
+ | [overview.tsx](examples/overview.tsx) | The welcome text of a support bot shown outside the chat: bold, a link, a quote, code and a spoiler from the chat's Markdown — `children`. |
85
+
86
+ ## Mistakes
87
+ - `dangerouslySetInnerHTML` with a Markdown library for a chat text → `RichText`: the same subset as the chat, never HTML.
88
+ - A `Message.Root` with no time and no author only to render formatted text → `RichText`.
89
+ - Sizing the text with a class on `RichText` → put it inside the host that sets the type.
90
+
91
+ ## Related
92
+ - **Built from:** `internal/richText` (the parser and the elements, shared with `Message.Text`)
93
+ - **See also:** [Message](../message/COMPONENT.md), [Composer](../composer/COMPONENT.md), [Textarea](../textarea/COMPONENT.md), [Typography](../typography/COMPONENT.md)
@@ -0,0 +1,19 @@
1
+ /** The welcome text of a support bot shown outside the chat: bold, a link, a quote, code and a spoiler from the chat's Markdown — `children`. */
2
+ import { Card, RichText } from "prime-ui-kit";
3
+
4
+ const WELCOME = [
5
+ "**Здравствуйте!** Это бот поддержки «Прайм».",
6
+ "Счета и акты — в [личном кабинете](https://prime.app/billing), номер договора начинается с `PR-`.",
7
+ "> Отвечаем в рабочие дни с 9:00 до 19:00 по Москве.",
8
+ "Код для входа в приложение: ||4821||",
9
+ ].join("\n");
10
+
11
+ export default function RichTextOverviewExample() {
12
+ return (
13
+ <Card.Root>
14
+ <Card.Body>
15
+ <RichText>{WELCOME}</RichText>
16
+ </Card.Body>
17
+ </Card.Root>
18
+ );
19
+ }
@@ -44,7 +44,7 @@ ScrollContainer <div> (or the `as` tag); thin scrollbar, soft hidden edges
44
44
  | `edges` | `"fade" \| "shadow" \| "none"` | `"fade"` | Edges that hide content: `fade` dissolves them over any background; `shadow` — a soft shadow on the region's own fill (a floating panel, a code well); `none` — plain edges. |
45
45
  | `scrollbar` | `"thin" \| "hidden"` | `"thin"` | `thin` — the kit's quiet scrollbar; `hidden` — no scrollbar, keep `edges` on so the overflow stays visible. |
46
46
  | `lock` | `boolean` | `false` | The person cannot scroll the region (wheel, touch, keys, a drag's auto-scroll); code still can. Sets `data-lock`. |
47
- | `snap` | `boolean \| "mandatory"` | `false` | Scrolling comes to rest on the start of a child: `true` near one (strips of tiles and cards), `"mandatory"` always on one (board columns, a carousel). A Dnd drag inside turns it off; not with `anchor="end"`. |
47
+ | `snap` | `boolean \| "mandatory"` | `false` | Once a scroll ends, the region glides to the start of a child: `true` near one (strips of tiles), `"mandatory"` always (board columns, a carousel). Native scrolling meanwhile; any input stops the glide. |
48
48
  | `anchor` | `"start" \| "end"` | `"start"` | `end` — a chat or a log: opens at the end and follows new content while the person is at the end; scrolled away, the view holds still. |
49
49
  | `onReachStart` | `() => void \| Promise<void>` | — | Fires within 120 px of the start, once per approach: load older content. A returned promise blocks repeats and sets `aria-busy`; content added above keeps the view. |
50
50
  | `onReachEnd` | `() => void \| Promise<void>` | — | The scroll position came within 120 px of the end: load the next page. Same once-per-approach and promise rules as `onReachStart`. |
@@ -93,11 +93,12 @@ Only the reader scrolling back unpins the end: content that keeps growing (an an
93
93
  | Value | Looks like | Use when | Default |
94
94
  |---|---|---|---|
95
95
  | `lock` | nothing the person does scrolls the region (wheel, touch, keys, a drag's edge auto-scroll); the position stays, and code may still set it | a list behind an open review, a gesture that must not move it | off |
96
- | `snap` | a scroll comes to rest on the start of a direct child near one (proximity); a host aligns deeper items itself | strips of tiles or cards | off |
97
- | `snap="mandatory"` | a scroll always comes to rest on the start of a child | board columns, a carousel of cards | off |
96
+ | `snap` | once a scroll ends near the start of a direct child (within 30% of the view), the region glides there; a host marks deeper items with `scroll-snap-align` itself | strips of tiles or cards | off |
97
+ | `snap="mandatory"` | once a scroll ends, the region always glides to the nearest start of a child | board columns on a phone, a carousel of cards | off |
98
98
 
99
99
  ### Wheel and auto-scroll
100
- - `axis="horizontal"`: a vertical mouse wheel scrolls the strip sideways while it can move, gliding to an accumulated target (instant under reduced motion). A trackpad's sideways swipe and Shift + wheel stay native; a wheel gesture that began outside the strip keeps scrolling the page; at the strip's edge the wheel chains to the page. There is no drag-to-scroll: a press-and-drag in a strip belongs to its items (reordering tiles, tabs); touch pans natively.
100
+ - Snapping never takes over the scroll: the trackpad, touch, the scrollbar and the keys scroll natively with their momentum, and the browser does not snap (no `scroll-snap-type` — it damps a trackpad's momentum and pulls a scroll near the end back to merged rest points). Once the scroll ends — no movement for a moment, nothing held — the region glides to its rest point on the kit's own frames, and any wheel, touch, press or key stops that glide at once; it settles again when the input goes quiet. A Dnd drag inside never settles.
101
+ - `axis="horizontal"`: a vertical mouse wheel (whole notches; a trackpad stays native) scrolls the strip sideways while it can move — by the wheel's distance, or a rest point per notch on a strip that snaps — on the kit's frames (instant under reduced motion), each notch from where the glide is heading, so a turn the other way goes back mid-glide. Shift + wheel stays native; a wheel gesture that began outside the strip keeps scrolling the page; at the strip's edge the wheel chains to the page. There is no drag-to-scroll: a press-and-drag in a strip belongs to its items (reordering tiles, tabs); touch pans natively.
101
102
  - A gesture held near an edge — a Dnd drag, a Gantt bar moved or drawn, a drag that paints a selection over table rows or messages — scrolls the region toward it, faster the closer the pointer (`src/internal/edgeAutoScroll`, one speed curve for the kit). A `lock`ed region or one whose content fits never moves.
102
103
 
103
104
  ### Edge events
@@ -116,7 +117,7 @@ Each fires once per approach — again after the position leaves the zone or the
116
117
  | edge treatment | `edges` | `data-edges="fade" \| "shadow"` (none for `none`) |
117
118
  | content hidden at an edge | scroll position (with `edges="fade"`), measured at most once per frame on scroll, on resize of the region or a child and when children are added; only the scroll axes; logical in RTL | `data-overflow-top`, `data-overflow-bottom`, `data-overflow-start`, `data-overflow-end` = `"true"`; `--edge-*` depths written only while they change (within the last fade depth of an edge, `--scroll-fade-depth`), on non-inherited properties |
118
119
  | locked | `lock` | `data-lock`, `overflow: hidden` |
119
- | snapping | `snap` | `data-snap="proximity" \| "mandatory"`, `scroll-snap-type: <axis> <strictness>`; `scroll-snap-align: start` on the children; off while a Dnd list inside carries an item (`data-dnd-carrying`) |
120
+ | snapping | `snap` | `data-snap="proximity" \| "mandatory"`; `scroll-snap-align: start` on the children marks the rest points (no `scroll-snap-type`); no settling while a Dnd list inside carries an item (`data-dnd-carrying`) |
120
121
  | kit anchoring | `anchor="end"` or `onReachStart` | `data-anchor="end" \| "start"`, `overflow-anchor: none`: the kit keeps the visible entry in place (or the end, while pinned) before paint, the same in every browser |
121
122
  | pinned to the end | `anchor="end"` and the position within 4 px of the end | content changes scroll to the new end at once (high-frequency: no animation) |
122
123
  | loading at an edge | a pending promise from `onReachStart` / `onReachEnd` | `aria-busy="true"` |
@@ -176,6 +177,8 @@ No `labels`.
176
177
  - `edges="fade"` on a region that paints its own fill or shadow → the mask dissolves them; use `edges="shadow"`.
177
178
  - A hand-made edge auto-scroll loop for a drag → `createEdgeAutoScroll` from `src/internal/edgeAutoScroll`.
178
179
  - `snap` together with `anchor="end"` → snapping fights the pin to the end.
180
+ - `scroll-snap-type` on a region or its host → `snap`: the browser's snapping damps a trackpad's momentum and pulls a scroll near the end back.
181
+ - `snap="mandatory"` on a strip of children narrower than the view (a wide board) → their rest points merge at the end; snap only where each child fills most of the view, or use `snap`.
179
182
  - Wrapping a Modal body or Dropdown panel in another ScrollContainer → they already scroll.
180
183
  - `scrollTop = scrollHeight` in an effect after every message → `anchor="end"` (it also leaves a reader who scrolled up alone).
181
184
  - `flex-direction: column-reverse` to keep a chat at the bottom → it reverses the reading and Tab order; use `anchor="end"`.
@@ -53,6 +53,7 @@ The root is a field root: `className`, `ref` and the native attributes land on t
53
53
  | `required` | `boolean` | `false` | Red `*` after the label and `aria-required` on the `radiogroup`. |
54
54
  | `optional` | `boolean` | `false` | Muted marker after the label (`labels.optional`). |
55
55
  | `hint` | `ReactNode` | — | Help text under the control (`aria-describedby`). Hidden while `error` is shown. |
56
+ | `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. |
56
57
  | `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
57
58
  | `invalid` | `boolean` | `false` | Danger inset ring, `aria-invalid` and `data-invalid` on the `radiogroup`; the control shakes once when an error arrives. |
58
59
  | `reserveSupportRow` | `boolean` | `false` | Always render the support row, so an appearing error does not shift the layout. |
@@ -157,14 +158,15 @@ Colour all items or none; do not give an item and its Count the same hue; prefer
157
158
  | optional | `optional` | a muted `labels.optional` marker after the label |
158
159
  | support row reserved | `reserveSupportRow` | the support row stays rendered while empty, so an error arriving shifts nothing |
159
160
  | focus-visible | keyboard | focus ring inside the segment |
160
- | collapsed | row wider than the container and every segment has an icon | row `data-collapse="icon"`: titles, counts and descriptions leave the row, segments turn square; a `VisuallyHidden` copy of the title names each segment and a `Tooltip` shows it |
161
+ | compact | row wider than the container at the tier's padding | row `data-collapse="compact"`: single-line segments tighten their padding by one step, so a row a few pixels too wide keeps its last segment and count whole; if it still does not fit (and not every segment has an icon) it scrolls |
162
+ | collapsed | even the compact row does not fit and every segment has an icon | row `data-collapse="icon"`: titles, counts and descriptions leave the row, segments turn square; a `VisuallyHidden` copy of the title names each segment and a `Tooltip` shows it |
161
163
  | overflow | row still wider than the container | root `data-overflow-start` / `data-overflow-end`: edge fades in the track color |
162
164
 
163
165
  Item DOM: `data-state`, `data-disabled`, `data-icon-only`, `data-two-line`, `data-value`, `data-color`. The thumb (the shared selection indicator) glides (`emphasized` + `base`, `data-animate`) only into a user's choice (click, arrows); a value set from outside and layout changes snap it; it stays still under `prefers-reduced-motion`. It sits inside the segment track, which clips it, so moving the selection never changes the row's scroll area. When the row scrolls, the selected segment is kept in view on mount and on every value change: only the row scrolls, never the page, and the scroll glides only after a user's choice.
164
166
 
165
167
  ## Layout & spacing
166
168
  - In a form it is a field: label → track `label-gap`, track → support row `hint-gap`, field → field `--prime-space-5` like every field.
167
- - Width is content-based by default; the row is a switcher (foundation §9): segments never wrap or truncate. In a narrow container segments with icons drop their titles first, then the row scrolls horizontally with fading edges.
169
+ - Width is content-based by default; the row is a switcher (foundation §9): segments never wrap or truncate. In a narrow container the segments first tighten their padding, then segments with icons drop their titles, then the row scrolls horizontally with fading edges.
168
170
  - Without `size` the control takes the tier of its host (a toolbar, a form), else `m`.
169
171
  - On a coarse pointer each segment's hit area grows to the 44 px touch target in height; the track keeps its tier height.
170
172
  - In a toolbar: gap `--prime-space-3` between controls; keep every control at one size.
@@ -15,7 +15,7 @@ export default function SegmentedControlInFormExample() {
15
15
 
16
16
  return (
17
17
  <form className={styles.form} noValidate onSubmit={submit}>
18
- <Input.Root label="Сумма, ₽" required>
18
+ <Input.Root label="Сумма" required>
19
19
  <Input.Wrapper>
20
20
  <Input.Field name="amount" inputMode="decimal" defaultValue="12 400" />
21
21
  <Input.InlineAffix side="end">₽</Input.InlineAffix>
@@ -11,12 +11,13 @@
11
11
  - A value out of thousands of records searched on the server (`search`, `onSearchChange`, `filter={false}`, `hasMore` + `onLoadMore`).
12
12
  - A few values from a short list shown as comma-separated text (`multiple`).
13
13
  - Options that need a picture, an avatar, a second line, a badge or a trailing value (rich options).
14
+ - A pick that belongs to a text field (an icon before a name): put the Select inside `Input.Wrapper` — it becomes the box's start segment, named by its `aria-label`.
14
15
 
15
16
  ## When not to use
16
17
  - 2–5 options that should all be visible → use [Radio](../radio/COMPONENT.md) or [SegmentedControl](../segmented-control/COMPONENT.md).
17
18
  - The OS picker on a mobile-first form → use [NativeSelect](../native-select/COMPONENT.md).
18
19
  - Many values shown as removable tags, or creating new values → use [TagSelect](../tag-select/COMPONENT.md).
19
- - Free text → use [Input](../input/COMPONENT.md).
20
+ - Free text, even with suggestions → use [Input](../input/COMPONENT.md) (`suggestions`).
20
21
  - A menu of actions (not a value) → use [Dropdown](../dropdown/COMPONENT.md).
21
22
  - Global search / command palette → use [CommandMenu](../command-menu/COMPONENT.md).
22
23
  - Dates → use [Datepicker](../datepicker/COMPONENT.md).
@@ -60,7 +61,7 @@ an icon) goes inside `Select.ItemMeta`.
60
61
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
61
62
 
62
63
  ### Select.Root
63
- `ref` → `HTMLDivElement`. The field frame (label row · control · support row) holding the value, the open state and the search query.
64
+ `ref` → `HTMLDivElement`. The field frame (label row · control · support row) holding the value, the open state and the search query. Inside `Input.Wrapper` it is the box's start segment instead: no frame, the trigger named by `aria-label` and the value.
64
65
 
65
66
  | Prop | Type | Default | Description |
66
67
  |---|---|---|---|
@@ -75,6 +76,7 @@ an icon) goes inside `Select.ItemMeta`.
75
76
  | `closeOnOutsideClick` | `boolean` | `true` | A press outside the list and its trigger closes it; focus follows the pointer. |
76
77
  | `label` | `ReactNode` | — | Field label above; names the trigger. |
77
78
  | `hint` | `ReactNode` | — | Support text under the field; linked by `aria-describedby`. |
79
+ | `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. |
78
80
  | `error` | `ReactNode` | — | Error message; replaces the hint and implies `invalid`. |
79
81
  | `required` | `boolean` | `false` | Red `*` after the label and `aria-required` on the trigger. |
80
82
  | `optional` | `boolean` | — | Muted `labels.optional` after the label. |
@@ -224,6 +226,7 @@ No `variant` or `tone`. The trigger is the field look (fill, inset control borde
224
226
  | searching | typing in the search | panel `data-searching="true"`; groups without matches and separators hide; the match in an option's title, description and plain text is a `<mark>` in `accent-text` (not in the trigger); the query clears when the list closes (`onSearchChange("")`) |
225
227
  | nothing to show | no option matches (and not loading) | a compact EmptyPage with `role="status"`: `labels.empty`, plus `labels.emptyHint` while a query is typed; a `Select.Action` stays under it, highlighted |
226
228
  | sheet | viewport below 640px | a scrim and a full-width bottom sheet with an `aria-hidden` grab handle around the panel; the sheet carries `data-state`, the panel keeps its role and ref; page scroll locked; a swipe down from the handle closes it |
229
+ | segment | inside `Input.Wrapper` | no frame (label, hint, error, support row): the root is `data-field-segment`, the trigger `data-segment="true"` — a tinted section flush with the box's start edge, a stronger fill on hover and while open, an inset focus ring; named «`aria-label`: value» |
227
230
  | option highlighted / selected / disabled | keyboard and pointer / value / `disabled` | `data-highlighted`, `data-selected` + `aria-selected`, `data-disabled` + `aria-disabled`; highlight is a fill only |
228
231
 
229
232
  ## Layout & spacing
@@ -350,8 +353,9 @@ No `variant` or `tone`. The trigger is the field look (fill, inset control borde
350
353
  - `<ClientAvatar />` or `<RoleBadge />` (your own component) inside an item → the item reads parts by element type and puts an unknown component in the text column; call the helper as a function (`{clientAvatar(client)}`) so `Avatar.Root` / `Badge.Root` is the direct child.
351
354
  - A custom trailing element (a Kbd, an icon with a price) as a bare child → wrap it in `Select.ItemMeta`.
352
355
  - A «Новый клиент» row faked as a `Select.Item` with a sentinel value, or a button under the field → use `Select.Action`: never filtered, reached by the arrows, never a value.
356
+ - An icon-only Select set beside an Input for one value (an icon + a name) → put it inside `Input.Wrapper` with an `aria-label`; `label`, `hint` and `error` belong to the Input there.
353
357
  - Saving the choice in `onValueChange` and resetting the value by hand on failure → return the promise; the Select shows the value, waits and rolls back by itself.
354
358
 
355
359
  ## Related
356
- - **Built from:** [ScrollContainer](../scroll-container/COMPONENT.md) (list), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in `multiple`), [Spinner](../spinner/COMPONENT.md) (`loading` and saving in the trigger), [Skeleton](../skeleton/COMPONENT.md) (loading rows), [EmptyPage](../empty-page/COMPONENT.md) (empty state, load error), [Button](../button/COMPONENT.md) (retry), [Divider](../divider/COMPONENT.md) (`Separator`), [Thumbnail](../thumbnail/COMPONENT.md) and [Avatar](../avatar/COMPONENT.md) (row media), [Badge](../badge/COMPONENT.md) (row badge), [VisuallyHidden](../visually-hidden/COMPONENT.md) (loading and save-failure announcements), `Icon` (chevron, clear, check, search, retry), [Label](../label/COMPONENT.md) and [Hint](../hint/COMPONENT.md) (field frame)
360
+ - **Built from:** [ScrollContainer](../scroll-container/COMPONENT.md) (list), [Checkbox](../checkbox/COMPONENT.md) (`Checkbox.Indicator` in `multiple`), [Spinner](../spinner/COMPONENT.md) (`loading` and saving in the trigger), [Skeleton](../skeleton/COMPONENT.md) (loading rows), [EmptyPage](../empty-page/COMPONENT.md) (empty state, load error), [Button](../button/COMPONENT.md) (retry), [Divider](../divider/COMPONENT.md) (`Separator`), [Thumbnail](../thumbnail/COMPONENT.md) and [Avatar](../avatar/COMPONENT.md) (row media), [Badge](../badge/COMPONENT.md) (row badge), [VisuallyHidden](../visually-hidden/COMPONENT.md) (loading and save-failure announcements), `Icon` (chevron, clear, check, search, retry), [Label](../label/COMPONENT.md) and [Hint](../hint/COMPONENT.md) (field frame), [Input](../input/COMPONENT.md) (the start segment of `Input.Wrapper`)
357
361
  - **See also:** [NativeSelect](../native-select/COMPONENT.md), [TagSelect](../tag-select/COMPONENT.md), [Dropdown](../dropdown/COMPONENT.md), [Radio](../radio/COMPONENT.md), [SegmentedControl](../segmented-control/COMPONENT.md)
@@ -77,7 +77,7 @@ No DOM, no ref. State and dismiss policy.
77
77
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `className` and the other attributes of the `<header>`. |
78
78
 
79
79
  ### Sheet.Actions
80
- `ref` → `HTMLDivElement`. A row of controls at the end of the header, before the close button: a status Badge, a ⋯ Dropdown, an icon button. Controls inside take tier `s`.
80
+ `ref` → `HTMLDivElement`. A row of controls at the end of the header, before the close button: a status Badge, a ⋯ Dropdown; under the title when it would get less than 12rem. Controls inside take tier `s`.
81
81
 
82
82
  | Prop | Type | Default | Description |
83
83
  |---|---|---|---|
@@ -92,12 +92,20 @@ No DOM, no ref. State and dismiss policy.
92
92
  | `children` | `ReactNode` | — (required) | An `Icon` (sized to the m icon), or the record's picture: an `Avatar.Root` (sized to the tile) or a `Thumbnail.Root`. |
93
93
  | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other attributes of the tile. |
94
94
 
95
- ### Sheet.Title · Sheet.Description
96
- `ref` → `HTMLHeadingElement` / `HTMLParagraphElement`. `<h2>` (title-m) / `<p>` (body-s, muted); their ids name and describe the dialog.
95
+ ### Sheet.Title
96
+ `ref` → `HTMLHeadingElement`. `<h2>` (title-m); its id names the dialog.
97
97
 
98
98
  | Prop | Type | Default | Description |
99
99
  |---|---|---|---|
100
- | `…rest` | `Omit<HTMLAttributes<HTMLHeadingElement \| HTMLParagraphElement>, "id">` | — | `className`, `children` and the other attributes except `id` (the dialog owns it). |
100
+ | `…rest` | `Omit<HTMLAttributes<HTMLHeadingElement>, "id">` | — | `className`, `children` and the other attributes except `id` (the dialog owns it). |
101
+
102
+ ### Sheet.Description
103
+ `ref` → `HTMLParagraphElement`. `<p>` (body-s, muted) under the title; its id describes the dialog.
104
+
105
+ | Prop | Type | Default | Description |
106
+ |---|---|---|---|
107
+ | `loading` | `boolean` | `false` | Its data loads: a `Skeleton` holds the line's place and `aria-busy` is set, so the header never moves; the data fades in. |
108
+ | `…rest` | `Omit<HTMLAttributes<HTMLParagraphElement>, "id">` | — | `className`, `children` and the other attributes except `id` (the dialog owns it). |
101
109
 
102
110
  ### Sheet.Body
103
111
  `ref` → `HTMLDivElement`. The only scrolling zone (a ScrollContainer), 16 gap between blocks.
@@ -111,7 +119,7 @@ No DOM, no ref. State and dismiss policy.
111
119
 
112
120
  | Prop | Type | Default | Description |
113
121
  |---|---|---|---|
114
- | `layout` | `"fill" \| "end"` | `"end"` | `fill`: equal-width buttons in one row; `end`: auto width, at the end. Stacked on phones (viewport below 640 px) and in a dialog narrower than 360 px. |
122
+ | `layout` | `"fill" \| "end"` | `"end"` | `fill`: equal-width buttons in one row, wrapping rather than cutting a label; `end`: auto width, at the end. Stacked on phones (viewport below 640 px) and in a dialog narrower than 360 px. |
115
123
  | `…rest` | `HTMLAttributes<HTMLElement>` | — | `className`, `children` and the other attributes of the `<footer>`. |
116
124
 
117
125
  ### Sheet.Meta
@@ -122,7 +130,7 @@ No DOM, no ref. State and dismiss policy.
122
130
  | `…rest` | `HTMLAttributes<HTMLParagraphElement>` | — | `className`, `children` and the other attributes of the `<p>`. |
123
131
 
124
132
  ### Sheet.Guard
125
- No DOM, no ref. Holds the dialog from anywhere inside Content, where the form state lives; renders only the built-in «discard changes?» confirm. Closing from code is never held.
133
+ No DOM, no ref. Holds the dialog from anywhere inside Content, where the form state lives; renders only the built-in «discard changes?» confirm. While `busy` or `dirty` the browser also asks before the tab closes or reloads. Closing from code is never held.
126
134
 
127
135
  | Prop | Type | Default | Description |
128
136
  |---|---|---|---|
@@ -165,6 +173,9 @@ Sheet has no `variant`, `side` or `size`: it always rises from the bottom edge,
165
173
  | closing | `open` turns false | the panel keeps showing the children it had open while it slides down |
166
174
  | busy | `Sheet.Guard busy` | Escape, the scrim, a swipe, the close button and `Sheet.Close` do nothing; the close button and the `Close` child are `disabled` in place; the handle stays, `data-swipe-disabled` on the panel (no grab cursor) |
167
175
  | dirty | `Sheet.Guard dirty` | a dismiss opens the «Не сохранять изменения?» ConfirmDialog (`labels.discard*`); a swipe past the threshold glides back and asks the same |
176
+ | leaving the tab | `Sheet.Guard busy` or `dirty` | the browser asks before the tab closes or reloads (`beforeunload`), as with `Page.Guard` |
177
+ | description loading | `Sheet.Description loading` | a `Skeleton` holds the line, `aria-busy` on the `<p>`; the data fades in under the same element |
178
+ | actions below | the title would get less than 12rem beside `Sheet.Actions` | `data-actions-below` on the header: the actions move under the title and the description |
168
179
 
169
180
  Motion: the scrim fades, the sheet slides up over `slow` and down over `base` (`overlayMotion`); under `prefers-reduced-motion` it unmounts at once. A swipe down moves the sheet with the pointer; on release it closes past 30% of its height or on a quick flick and glides the rest of the way out, otherwise it glides back — both over `base` on the `emphasized` (iOS sheet) curve. A pull upward meets growing resistance and stops at 24; the sheet's fill continues that far below the screen edge. The scrim thins as the sheet goes down. The drag starts from the handle or the header, never from the body, so its content scrolls and its controls work as usual.
170
181
 
@@ -237,5 +248,5 @@ Motion: the scrim fades, the sheet slides up over `slow` and down over `base` (`
237
248
  - `closeOnOutsideClick={false}` and `closeOnEscape={false}` with no header × and no `Sheet.Close` → nothing closes it; keep a button that answers.
238
249
 
239
250
  ## Related
240
- - **Built from:** the dialog shared with Modal and Drawer (`internal/overlay/DialogParts`), [Button](../button/COMPONENT.md) (close button), [ScrollContainer](../scroll-container/COMPONENT.md) (`Sheet.Body`), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (a record's picture in `Sheet.Icon`), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (the `Sheet.Guard` discard confirm), `Icon` (the close glyph)
251
+ - **Built from:** the dialog shared with Modal and Drawer (`internal/overlay/DialogParts`), [Button](../button/COMPONENT.md) (close button), [ScrollContainer](../scroll-container/COMPONENT.md) (`Sheet.Body`), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (a record's picture in `Sheet.Icon`), [Skeleton](../skeleton/COMPONENT.md) (`Sheet.Description loading`), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (the `Sheet.Guard` discard confirm), `Icon` (the close glyph)
241
252
  - **See also:** [Drawer](../drawer/COMPONENT.md) (the same parts as a side panel), [Modal](../modal/COMPONENT.md) (the same parts in the centre), [Datepicker](../datepicker/COMPONENT.md) (`Datepicker.Panel` in a sheet), [Dropdown](../dropdown/COMPONENT.md)
@@ -59,6 +59,7 @@ Slider field frame (FieldFrame): className, ref, rest
59
59
  | `required` | `boolean` | — | Red `*` after the label (`aria-hidden`); a range always has a value, so there is no native `required`. |
60
60
  | `optional` | `boolean` | — | Muted marker right after the label text (`labels.optional`). |
61
61
  | `hint` | `ReactNode` | — | Help text under the track (`aria-describedby`). Hidden while `error` is shown. |
62
+ | `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. |
62
63
  | `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
63
64
  | `invalid` | `boolean` | — | `aria-invalid` on the range input, a danger fill (over any `tone`) and a danger focus ring on the thumb. A non-empty `error` implies it. |
64
65
  | `showValue` | `boolean` | `false` | Shows the current value at the end of the label row (tabular numbers, `aria-hidden` — the input announces it). |
@@ -24,7 +24,7 @@ export default function SliderControlledExample() {
24
24
  type="number"
25
25
  min={0}
26
26
  max={MAX_DISCOUNT}
27
- aria-label="Скидка для клиента, %"
27
+ aria-label="Скидка для клиента"
28
28
  value={discount}
29
29
  onValueChange={(text) => {
30
30
  const next = Number(text);