prime-ui-kit 0.21.2 → 0.21.3

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 (152) hide show
  1. package/README.md +2 -2
  2. package/THIRD_PARTY_NOTICES +13 -0
  3. package/dist/chunks/{chunk-VQ3UCVDV.js → chunk-BAJRN3QN.js} +2 -2
  4. package/dist/chunks/{chunk-T7IEJSTH.js → chunk-HMLQCKRL.js} +36 -0
  5. package/dist/chunks/{chunk-7JYPFRZ3.js → chunk-JO7E6SZQ.js} +29 -18
  6. package/dist/chunks/{chunk-ME4VEAQ5.js → chunk-R5LI5BO3.js} +2122 -1614
  7. package/dist/chunks/{chunk-SQGPQOUO.js → chunk-SUGCIPD7.js} +1 -1
  8. package/dist/color-picker.js +3 -3
  9. package/dist/components/chat-list/ChatList.d.ts +4 -3
  10. package/dist/components/command-menu/CommandMenu.d.ts +1 -4
  11. package/dist/components/composer/Composer.d.ts +1 -1
  12. package/dist/components/data-table/DataTable.d.ts +9 -7
  13. package/dist/components/data-table/Footer.d.ts +7 -3
  14. package/dist/components/datepicker/DatepickerPanel.d.ts +2 -2
  15. package/dist/components/dnd/labels.d.ts +2 -0
  16. package/dist/components/field-list/FieldList.d.ts +2 -4
  17. package/dist/components/file-upload/FileUpload.d.ts +1 -9
  18. package/dist/components/index.d.ts +2 -2
  19. package/dist/components/kanban/Kanban.d.ts +0 -2
  20. package/dist/components/list/List.d.ts +7 -1
  21. package/dist/components/message/Message.d.ts +5 -1
  22. package/dist/components/message/MessageParts.d.ts +7 -2
  23. package/dist/components/rich-text/RichText.d.ts +12 -4
  24. package/dist/components/select/selectContext.d.ts +0 -2
  25. package/dist/components/smart-filter/SmartFilter.d.ts +1 -5
  26. package/dist/components/sparkline/Sparkline.d.ts +50 -8
  27. package/dist/components/tag-select/TagSelect.d.ts +0 -2
  28. package/dist/components/textarea/Textarea.d.ts +1 -1
  29. package/dist/icon-set.d.ts +5 -1
  30. package/dist/icon-set.js +8 -2
  31. package/dist/icons/glyph.d.ts +12 -1
  32. package/dist/icons/glyphs/telegram.d.ts +2 -0
  33. package/dist/icons/glyphs/vk.d.ts +2 -0
  34. package/dist/icons/glyphs/whatsapp.d.ts +2 -0
  35. package/dist/icons/registry.d.ts +8 -1
  36. package/dist/index.css +1 -1
  37. package/dist/index.js +7 -3
  38. package/dist/internal/ToolRow.d.ts +3 -1
  39. package/dist/internal/barPlan.d.ts +7 -1
  40. package/dist/internal/blockStack.d.ts +7 -0
  41. package/dist/internal/overlay/DialogParts.d.ts +8 -6
  42. package/dist/internal/richText.d.ts +32 -1
  43. package/dist/internal/textFormatting.d.ts +2 -2
  44. package/dist/layout/page/Page.d.ts +2 -1
  45. package/dist/skill/SKILL.md +3 -2
  46. package/dist/skill/api-contract.md +2 -1
  47. package/dist/skill/cheatsheet.md +1 -1
  48. package/dist/skill/checklist.md +6 -1
  49. package/dist/skill/choosing.md +76 -5
  50. package/dist/skill/components.md +2 -2
  51. package/dist/skill/composition.md +108 -20
  52. package/dist/skill/foundations.md +1 -1
  53. package/dist/skill/layouts.md +4 -4
  54. package/dist/skill/reference/components/card/COMPONENT.md +1 -1
  55. package/dist/skill/reference/components/card/examples/screen.tsx +9 -11
  56. package/dist/skill/reference/components/chat-list/COMPONENT.md +6 -5
  57. package/dist/skill/reference/components/chat-list/examples/structure.tsx +4 -4
  58. package/dist/skill/reference/components/command-menu/COMPONENT.md +1 -2
  59. package/dist/skill/reference/components/command-menu/examples/states.tsx +1 -1
  60. package/dist/skill/reference/components/composer/COMPONENT.md +5 -4
  61. package/dist/skill/reference/components/composer/examples/formatting.tsx +1 -1
  62. package/dist/skill/reference/components/data-table/COMPONENT.md +17 -16
  63. package/dist/skill/reference/components/data-table/examples/server.tsx +2 -1
  64. package/dist/skill/reference/components/datepicker/COMPONENT.md +2 -2
  65. package/dist/skill/reference/components/dnd/COMPONENT.md +2 -1
  66. package/dist/skill/reference/components/drawer/COMPONENT.md +9 -6
  67. package/dist/skill/reference/components/drawer/examples/tab-switcher.tsx +158 -0
  68. package/dist/skill/reference/components/field-list/COMPONENT.md +7 -7
  69. package/dist/skill/reference/components/field-list/examples/limits.tsx +2 -2
  70. package/dist/skill/reference/components/file-upload/COMPONENT.md +8 -12
  71. package/dist/skill/reference/components/file-upload/examples/gallery.tsx +2 -2
  72. package/dist/skill/reference/components/file-upload/examples/in-form.tsx +2 -4
  73. package/dist/skill/reference/components/file-upload/examples/narrow.tsx +1 -0
  74. package/dist/skill/reference/components/file-upload/examples/overview.tsx +1 -5
  75. package/dist/skill/reference/components/file-upload/examples/sizes.tsx +1 -1
  76. package/dist/skill/reference/components/file-upload/examples/states.tsx +4 -5
  77. package/dist/skill/reference/components/file-upload/examples/validation.tsx +2 -7
  78. package/dist/skill/reference/components/kanban/COMPONENT.md +1 -2
  79. package/dist/skill/reference/components/list/COMPONENT.md +11 -6
  80. package/dist/skill/reference/components/list/examples/reorder.tsx +30 -3
  81. package/dist/skill/reference/components/message/COMPONENT.md +12 -7
  82. package/dist/skill/reference/components/message/examples/status.tsx +2 -1
  83. package/dist/skill/reference/components/message/examples/text-formatting.tsx +13 -3
  84. package/dist/skill/reference/components/modal/COMPONENT.md +5 -4
  85. package/dist/skill/reference/components/rich-text/COMPONENT.md +45 -7
  86. package/dist/skill/reference/components/rich-text/examples/entities.tsx +20 -0
  87. package/dist/skill/reference/components/select/COMPONENT.md +1 -2
  88. package/dist/skill/reference/components/sheet/COMPONENT.md +5 -4
  89. package/dist/skill/reference/components/smart-filter/COMPONENT.md +3 -5
  90. package/dist/skill/reference/components/sparkline/COMPONENT.md +40 -12
  91. package/dist/skill/reference/components/sparkline/examples/lower-is-better.tsx +46 -0
  92. package/dist/skill/reference/components/sparkline/examples/own-value.tsx +41 -0
  93. package/dist/skill/reference/components/sparkline/examples/reference.tsx +2 -1
  94. package/dist/skill/reference/components/tabs/COMPONENT.md +1 -1
  95. package/dist/skill/reference/components/tag-select/COMPONENT.md +0 -2
  96. package/dist/skill/reference/components/tag-select/examples/overview.tsx +0 -1
  97. package/dist/skill/reference/components/tag-select/examples/sizes.tsx +0 -1
  98. package/dist/skill/reference/components/textarea/COMPONENT.md +4 -3
  99. package/dist/skill/reference/icon-set.ts +5 -1
  100. package/dist/skill/reference/layout/page/COMPONENT.md +13 -12
  101. package/dist/skill/reference/layout/page/examples/collapse.tsx +45 -31
  102. package/dist/skill/reference/layout/page/examples/router.tsx +47 -14
  103. package/dist/skill/ux.md +79 -2
  104. package/package.json +1 -1
  105. package/src/components/card/COMPONENT.md +1 -1
  106. package/src/components/card/examples/screen.tsx +9 -11
  107. package/src/components/chat-list/COMPONENT.md +6 -5
  108. package/src/components/chat-list/examples/structure.tsx +4 -4
  109. package/src/components/command-menu/COMPONENT.md +1 -2
  110. package/src/components/command-menu/examples/states.tsx +1 -1
  111. package/src/components/composer/COMPONENT.md +5 -4
  112. package/src/components/composer/examples/formatting.tsx +1 -1
  113. package/src/components/data-table/COMPONENT.md +17 -16
  114. package/src/components/data-table/examples/server.tsx +2 -1
  115. package/src/components/datepicker/COMPONENT.md +2 -2
  116. package/src/components/dnd/COMPONENT.md +2 -1
  117. package/src/components/drawer/COMPONENT.md +9 -6
  118. package/src/components/drawer/examples/tab-switcher.tsx +158 -0
  119. package/src/components/field-list/COMPONENT.md +7 -7
  120. package/src/components/field-list/examples/limits.tsx +2 -2
  121. package/src/components/file-upload/COMPONENT.md +8 -12
  122. package/src/components/file-upload/examples/gallery.tsx +2 -2
  123. package/src/components/file-upload/examples/in-form.tsx +2 -4
  124. package/src/components/file-upload/examples/narrow.tsx +1 -0
  125. package/src/components/file-upload/examples/overview.tsx +1 -5
  126. package/src/components/file-upload/examples/sizes.tsx +1 -1
  127. package/src/components/file-upload/examples/states.tsx +4 -5
  128. package/src/components/file-upload/examples/validation.tsx +2 -7
  129. package/src/components/kanban/COMPONENT.md +1 -2
  130. package/src/components/list/COMPONENT.md +11 -6
  131. package/src/components/list/examples/reorder.tsx +30 -3
  132. package/src/components/message/COMPONENT.md +12 -7
  133. package/src/components/message/examples/status.tsx +2 -1
  134. package/src/components/message/examples/text-formatting.tsx +13 -3
  135. package/src/components/modal/COMPONENT.md +5 -4
  136. package/src/components/rich-text/COMPONENT.md +45 -7
  137. package/src/components/rich-text/examples/entities.tsx +20 -0
  138. package/src/components/select/COMPONENT.md +1 -2
  139. package/src/components/sheet/COMPONENT.md +5 -4
  140. package/src/components/smart-filter/COMPONENT.md +3 -5
  141. package/src/components/sparkline/COMPONENT.md +40 -12
  142. package/src/components/sparkline/examples/lower-is-better.tsx +46 -0
  143. package/src/components/sparkline/examples/own-value.tsx +41 -0
  144. package/src/components/sparkline/examples/reference.tsx +2 -1
  145. package/src/components/tabs/COMPONENT.md +1 -1
  146. package/src/components/tag-select/COMPONENT.md +0 -2
  147. package/src/components/tag-select/examples/overview.tsx +0 -1
  148. package/src/components/tag-select/examples/sizes.tsx +0 -1
  149. package/src/components/textarea/COMPONENT.md +4 -3
  150. package/src/layout/page/COMPONENT.md +13 -12
  151. package/src/layout/page/examples/collapse.tsx +45 -31
  152. package/src/layout/page/examples/router.tsx +47 -14
@@ -18,7 +18,7 @@ playground page. What each screen teaches is in [Screen rules](#screen-rules) at
18
18
  | [Settings page](#settings-page) | page | [screen.tsx](reference/layout/page/examples/screen.tsx) | the path in a collapsing top bar, the large title in the flow, a card with its own header per section, form with its own save, Switches that apply at once, danger zone with a confirm |
19
19
  | [Form in a Drawer](#form-in-a-drawer) | drawer | [screen.tsx](reference/components/drawer/examples/screen.tsx) | the title, counts and the Drawer trigger in the top bar, the search in the table's toolbar; Drawer form: groups, errors on submit, focus on the first invalid field, loading, toast |
20
20
  | [Record editor](#record-editor) | field-list | [screen.tsx](reference/components/field-list/examples/screen.tsx) | a document page (a long form): the title and a line of description in `Page.Heading`, «Сохранить тариф» in `Page.Actions`, both in a `Page.Header` in the flow; a plan card edited in card sections: name and description, specs in a `FieldList` (icon-only `Select` and `ColorPresets` in compact columns, pinned rows, reorder), the price and its changes by month in one list with the base row pinned, a setup fee in three currencies in one row under one heading, save with «Сохранено» on the button |
21
- | [Dashboard](#dashboard) | card | [screen.tsx](reference/components/card/examples/screen.tsx) | the large title + a line of data in the flow, period switch and report in a collapsing `Page.TopBar`; KPI row that cross-fades to the new period, a Sparkline revenue trend, invoiced vs paid in a ChartFrame, panels with progress and a split, short table + link to the list |
21
+ | [Dashboard](#dashboard) | card | [screen.tsx](reference/components/card/examples/screen.tsx) | an overview titled in its `Page.TopBar`: the title + a line of data, the period switch and the report; KPI row that cross-fades to the new period, a Sparkline revenue trend, invoiced vs paid in a ChartFrame, panels with progress and a split, short table + link to the list |
22
22
  | [Screen states](#screen-states) | crossfade | [screen.tsx](reference/components/crossfade/examples/screen.tsx) | title + the statement date in `Page.TopBar`, page error in a Banner with retry, region loading with a Skeleton, Crossfade between states, table error in place, first-run EmptyPage |
23
23
  | [Messenger](#messenger) | chat | [screen.tsx](reference/components/chat/examples/screen.tsx) | title + the unread count in `Page.TopBar`; a full-height Page.Split of a `ChatList` with its search (live last message, ticks, unread counts, «печатает…») and a `Chat`: `Chat.Header` with the back button, avatar, title, the status that turns into «печатает…» (`typing`) and the search button, `Chat.Search`, `Chat.Messages` from `items` + `renderMessage` (dates, runs and the unread band from the data, history loaded above with `hasOlder` / `onLoadOlder`, a first load in skeletons), text, a photo, a file and sent voice, reply and edit from the menu through `Composer.Context messageId` (`replyTo` / `editOf`), the jump to the quoted message (`Message.Reply target`), optimistic sends whose messages rise from the composer while the client's answer slides in, «печатает…» both ways (`onActivityChange` → `Chat.Root typing`), unread counts from `onNewMessage` and `onRead`, the Composer with attachments in `Chat.Footer` |
24
24
  | [Sales pipeline board](#sales-pipeline-board) | kanban | [screen.tsx](reference/components/kanban/examples/screen.tsx) | `Page.TopBar` titles the page (`h1` + a Description with the open deals and their sum) with SmartFilter search and a board / list SegmentedControl in `Page.Tools` and the one primary action in `Page.Actions`; Crossfade between a `plain` Kanban (stages without wells, deal cards on the page) and a DataTable of the same deals, column totals in `renderColumnMeta`, moves saved through an `onValueChange` promise that the server can refuse, the deal in a Drawer with a DescriptionList and stage actions, skeleton first load |
@@ -38,22 +38,57 @@ its `Page.TopBar` (sticky) and its `Page.Body`, plus a `Page.Header` in the flow
38
38
  **One title per page.** `Page.Title` is the page's only `<h1>`, a `Page.Description` with it is one
39
39
  line that carries data («Среда, 14 октября · 3 задачи», «Отправлен · оплатить до 14 октября»), and a
40
40
  nested record puts the path up (a `Breadcrumb.Root` before the title) — all three inside
41
- `Page.Heading`, the title group. Where the heading stands follows the kind of page; its size follows
41
+ `Page.Heading`, the title group. Where the heading stands follows what the body is; its size follows
42
42
  the place:
43
43
 
44
- - **Work screens** — a list, a board, a chat, a schedule, a record: `Page.Heading` stands in
44
+ - **Every screen of data** — a list, a board, a chat, a schedule, a record, and every overview
45
+ (a dashboard, analytics, statistics, finances, a builder): `Page.Heading` stands in
45
46
  `Page.TopBar` beside the page's tools and buttons ([the top bar](#the-top-bar)); there is no header
46
- in the flow.
47
- - **Document pages** — settings, a long form, an article or help text: `Page.Header` in the flow
48
- holds `Page.Heading` (the large title with one sentence of `Page.Description`) and the page's save
47
+ in the flow, and the body starts with the first block.
48
+ - **Document pages** — settings, a long form, an article or help text, read top to bottom in one
49
+ column with no page tools (no period, view or sections): `Page.Header` in the flow holds
50
+ `Page.Heading` (the large title with one sentence of `Page.Description`) and the page's save
49
51
  action in `Page.Actions`; optionally a `Page.TopBar collapse` with only the path in its
50
52
  `Page.Heading`, which repeats the title once it scrolls away.
51
- - **Overviews** — dashboards: a `Page.TopBar collapse` with the page's tools (the period) in
52
- `Page.Tools` and its buttons (a report) in `Page.Actions`, and the titled `Page.Header` in the flow.
53
53
 
54
+ The test: a page with tools (a period, a view, sections), tiles or charts is a screen of data, and its
55
+ title stands in the bar. `collapse` belongs only to a bar that holds a `Page.Heading` (the path of a
56
+ document page); a bar of tools alone over a large title in the flow is two headers for one page.
54
57
  Never two titles; never no `<h1>`. A bar whose page shows no title still holds a `Page.Title` — inside
55
58
  `VisuallyHidden` — unless a flow `Page.Header` has one.
56
59
 
60
+ ```tsx partial
61
+ // bad — an overview split in two: the bar holds only the period, the title stands in the flow
62
+ <Page.Root>
63
+ <Page.TopBar collapse>
64
+ <Page.Tools aria-label="Аналитика">…</Page.Tools>
65
+ </Page.TopBar>
66
+ <Page.Header>
67
+ <Page.Heading>
68
+ <Page.Title>Аналитика</Page.Title>
69
+ <Page.Description>Продажи · на 8 октября, 9:40</Page.Description>
70
+ </Page.Heading>
71
+ </Page.Header>
72
+ <Page.Body>…</Page.Body>
73
+ </Page.Root>
74
+ ```
75
+
76
+ ```tsx good
77
+ // good — the overview titles itself in its bar, like every screen of data
78
+ <Page.Root>
79
+ <Page.TopBar>
80
+ <Page.Heading>
81
+ <Page.Title>Аналитика</Page.Title>
82
+ <Page.Description>Продажи · на 8 октября, 9:40</Page.Description>
83
+ </Page.Heading>
84
+ <Page.Tools aria-label="Аналитика">
85
+ <Datepicker.Root mode="range" aria-label="Период" />
86
+ </Page.Tools>
87
+ </Page.TopBar>
88
+ <Page.Body>…</Page.Body>
89
+ </Page.Root>
90
+ ```
91
+
57
92
  ```
58
93
  AppShell.Root once per app (layouts.md#app-frame-once-per-app)
59
94
  ├─ AppShell.Nav → Sidebar navigation rail, one layer above the page
@@ -74,9 +109,9 @@ AppShell.Root once per app (layouts.md#app-frame-once-per-a
74
109
  │ └─ Page.Cell span (of 12), rows; the tile fills it
75
110
  └─ Page.Section a new topic: h2 label (+ Page.Actions), 40 from its neighbours
76
111
 
77
- Document page or overview: the title in the flow
112
+ Document page: the title in the flow
78
113
  └─ Page.Root maxWidth="wide" | "readable"
79
- ├─ Page.TopBar collapse the path (an overview: its tools and buttons); repeats the title once it scrolls away
114
+ ├─ Page.TopBar collapse only the path in its Page.Heading; repeats the title once it scrolls away
80
115
  ├─ Page.Header Page.Heading (the one h1, a sentence of description) + Page.Actions; 24 above the body
81
116
  └─ Page.Body
82
117
  ```
@@ -191,8 +226,9 @@ with «Сделки» and «Документы». Each facet is a route of its o
191
226
  the current link of the switch, never a second title or a title that names the facet.
192
227
  - **The switch is links in `Page.Tools`**: a `SegmentedControl` whose items are router links
193
228
  (`SegmentedControl.Item asChild`), `value` = the current path. It is a navigation (Tab moves
194
- through the links, the current one is `aria-current="page"`), on a phone it takes a line of its
195
- own under the title by itself.
229
+ through the links, the current one is `aria-current="page"`), and the bar always gives it a line
230
+ of its own under the title, at the start edge, so the switch stands still from one sub-page to the
231
+ next whatever each sub-page puts beside the title.
196
232
  - **The body is the sub-page**, swapped in a `Crossfade` keyed by the path (`Routes` given the
197
233
  `location`, so the leaving sub-page stays on its route while it fades).
198
234
  - **The Sidebar stays on the section.** Its item is current on every sub-route: a `NavLink` to
@@ -345,7 +381,7 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
345
381
  - **One primary per area** (the page, card, dialog, form): `solid`, last in its row. Secondary:
346
382
  `soft` + `tone="neutral"`. Dismiss / revert: `ghost` + `neutral` in pages and cards, `outline` +
347
383
  `neutral` in Modal / Drawer footers.
348
- - Placement: every button of the page in one `Page.Actions` — of `Page.TopBar` on a work screen, of the flow `Page.Header` on a document page — the primary last; card actions in `Card.Footer`; dialog actions in
384
+ - Placement: every button of the page in one `Page.Actions` — of `Page.TopBar` on a screen of data, of the flow `Page.Header` on a document page — the primary last; card actions in `Card.Footer`; dialog actions in
349
385
  `Modal.Footer` / `Drawer.Footer`; row actions in the last column; actions on the selected rows of a
350
386
  table in its selection menu ([controls of a table](#controls-of-a-table)).
351
387
  - A dialog footer is the dialog's answer: Cancel and the primary. Deleting the record the dialog
@@ -444,7 +480,8 @@ row of a `FieldList` follows the same count ([forms](#7-forms)).
444
480
 
445
481
  ### Where the filters go
446
482
 
447
- A control stands with what it changes. Ask of each one: what does it narrow or switch?
483
+ A control stands with what it changes. Ask of each one: what does it narrow or switch? (In a
484
+ drawer, the open tab is a «what» too: [a control for the whole tab](#a-record-in-a-drawer).)
448
485
 
449
486
  - **One table** — its search and filter fields, their chips, its column chooser and the actions on
450
487
  its selected rows belong to the `DataTable`, on every page: the page that is only this table, a
@@ -619,8 +656,10 @@ A control stands with what it changes. Ask of each one: what does it narrow or s
619
656
 
620
657
  ### Tables
621
658
 
622
- - Paging is built in: `paging="pages"` (default, Pagination and the «Показано 1–6 из 10» range in the
623
- footer), `paging="infinite"` for feeds, `paging="none"` for short lists on dashboards and details.
659
+ - Paging is built in: `paging="pages"` (default, Pagination in the footer once there is a second
660
+ page), `paging="infinite"` for feeds (its footer shows only while the next rows load),
661
+ `paging="none"` for short lists on dashboards and details. The «Показано 1–6 из 10» range is
662
+ `showRange`, only where the count matters to the task ([helper text is opt-in](ux.md#3-label-economy)).
624
663
  - When the server holds more than you load (a CRM list, a register): `source="server"` — `rows` is the
625
664
  page the server returned, `rowCount` its total; `sort` / `page` / the SmartFilter value make the
626
665
  request, a new search or filter sets `page` to 1, and the old rows stay with `loading` until the
@@ -642,6 +681,8 @@ A control stands with what it changes. Ask of each one: what does it narrow or s
642
681
  first child of a `Dnd.SortableItem` — so the on / off state reads first, like a checkbox, and the
643
682
  grip keeps the end edge ([reorder.tsx](reference/components/list/examples/reorder.tsx)).
644
683
  - Never a toggle between the label and the grip: «Почта … [Switch] [grip]» reads as two handles.
684
+ - Rows that cannot move stay in the same list with `List.Item locked` — the lock takes the grip's
685
+ place, so every row keeps one grid ([one kind, one list](ux.md#2-the-shape-of-the-data-decides-the-layout)).
645
686
 
646
687
  ## 9. Feedback
647
688
 
@@ -685,7 +726,8 @@ changes) instead.
685
726
  A toast is no layer of that stack: `notify()` from inside a Drawer leaves it open, and a press on
686
727
  the toast closes nothing below it. Titled groups of a dialog's body (a client's contacts, its deals)
687
728
  are `Drawer.Section` / `Modal.Section` with their own `Drawer.Actions` / `Modal.Actions`, not a
688
- hand-made heading row; a footer's `Drawer.Meta` / `Modal.Meta` goes above the buttons by itself when
729
+ hand-made heading row — and only where the heading names what the blocks do not
730
+ ([label economy](ux.md#3-label-economy)); a footer's `Drawer.Meta` / `Modal.Meta` goes above the buttons by itself when
689
731
  the footer runs short.
690
732
 
691
733
  ### A record in a drawer
@@ -716,6 +758,13 @@ wherever it opens:
716
758
  the save the button leaves, so the success is a `Notification`.
717
759
  - **Tabs are topics.** Each tab holds one topic, and a record's history (its log) is always a tab of
718
760
  its own. Tabs that outgrow the width never merge: the strip scrolls ([switchers](responsive.md#switchers)).
761
+ - **A control for the whole tab stands in the header.** A switch that changes every block of the
762
+ open tab — the language of its texts, the period of its figures — is one control in
763
+ `Drawer.Actions`, before the buttons, rendered only while a tab it applies to is open
764
+ ([tab-switcher.tsx](reference/components/drawer/examples/tab-switcher.tsx)). It is the same rule as
765
+ [where the filters go](#where-the-filters-go): what it changes is the tab, and the tab's place is
766
+ the header. A control that changes one block stays in that block. A period starts from the
767
+ period of the page the drawer opened from, so the drawer's figures match the row it came from.
719
768
  - **Data at the drawer's level.** The drawer loads the record and the data of its tabs; a tab never
720
769
  fetches on mount, so coming back to it shows what it showed instead of a skeleton that collapses
721
770
  it. A tab that keeps a draft or its scroll keeps its panel with `Tabs.Panel keepMounted`. The
@@ -758,6 +807,45 @@ wherever it opens:
758
807
  </Drawer.Content>
759
808
  ```
760
809
 
810
+ ```tsx partial
811
+ // bad — the tab's language switch repeated in the heading of every section
812
+ <Tabs.Panel value="description">
813
+ <Drawer.Section label="Название">
814
+ <Drawer.Actions>
815
+ <SegmentedControl.Root aria-label="Язык" defaultValue="ru">…</SegmentedControl.Root>
816
+ </Drawer.Actions>
817
+ …
818
+ </Drawer.Section>
819
+ <Drawer.Section label="Описание">
820
+ <Drawer.Actions>
821
+ <SegmentedControl.Root aria-label="Язык" defaultValue="ru">…</SegmentedControl.Root>
822
+ </Drawer.Actions>
823
+ …
824
+ </Drawer.Section>
825
+ </Tabs.Panel>
826
+ ```
827
+
828
+ ```tsx good
829
+ // good — one switch for the open tab, in the header before ⋯, only on the tab it applies to
830
+ // declare: const tab: string
831
+ <Drawer.Header>
832
+ <Drawer.Title>Кресло «Орион»</Drawer.Title>
833
+ <Drawer.Actions>
834
+ {tab === "description" ? (
835
+ <SegmentedControl.Root aria-label="Язык текстов" defaultValue="ru">
836
+ <SegmentedControl.Item value="ru">RU</SegmentedControl.Item>
837
+ <SegmentedControl.Item value="en">EN</SegmentedControl.Item>
838
+ </SegmentedControl.Root>
839
+ ) : null}
840
+ <Button.Root variant="ghost" tone="neutral" aria-label="Действия с моделью">
841
+ <Button.Icon>
842
+ <Icon name="action.more" />
843
+ </Button.Icon>
844
+ </Button.Root>
845
+ </Drawer.Actions>
846
+ </Drawer.Header>
847
+ ```
848
+
761
849
  ## 11. Narrow screens
762
850
 
763
851
  - Layouts are [intrinsic](responsive.md#intrinsic-grids) — no breakpoints needed, and they work in
@@ -836,7 +924,7 @@ export function RevenuePanel() {
836
924
 
837
925
  For a plain KPI row use `Card.Root variant="stat-trend"` (Label · Value · Delta), each in a
838
926
  `Page.Cell span={3}` of the page's `Page.Grid` — see [the dashboard screen](reference/components/card/examples/screen.tsx). When the trend over days matters, put a
839
- `Sparkline` in the `Card.Body` instead: it brings the value, the change and a line to scrub.
927
+ `Sparkline` in the `Card.Body` instead: it brings the value and a line to scrub, the change with `compareTo`.
840
928
 
841
929
  ## Screen rules
842
930
 
@@ -906,11 +994,11 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
906
994
 
907
995
  [card/examples/screen.tsx](reference/components/card/examples/screen.tsx)
908
996
 
909
- - 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.
997
+ - An overview titles itself in its `Page.TopBar` like any screen of data: `Page.Title` «Обзор» with a `Page.Description` line of data (what and as of when) in `Page.Heading`, beside the page's tools and buttons; no header in the flow, the body starts with the tiles.
910
998
  - 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.
911
999
  - 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`.
912
1000
  - 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.
913
- - 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.
1001
+ - A trend over days is a `Sparkline` in a `Card` in a full-width cell: the period's sum (`summary`) and a line you can scrub with a finger; a new series appears by itself.
914
1002
  - 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.
915
1003
  - Panels are `Card variant="panel"` with `Card.Title as="h2"` in `Card.Header` and quiet context on the right after the title (`caption` + `muted`).
916
1004
  - Progress of one value is a `ProgressBar` with `label` and `showValue`; parts of a whole are `ProgressBar segments` with `legend` and `formatValue`: statuses are colored by `tone`, categories by `color`.
@@ -146,7 +146,7 @@ A chart color does one job, and the job picks the colors:
146
146
  | `title-m` | 16/24 600 | page section heading (`Page.Section` draws it), modal title |
147
147
  | `title-l` | 18/24 600 | large block title |
148
148
  | `heading-s` | 20/28 600 | page sub-heading |
149
- | `heading-m` | 24/32 600 | the page title in the flow (`Page.Title` of a document page or an overview); in `Page.TopBar` the same `Page.Title` is `title-m` |
149
+ | `heading-m` | 24/32 600 | the page title in the flow (`Page.Title` of a document page); in `Page.TopBar` the same `Page.Title` is `title-m` |
150
150
  | `heading-l` | 30/36 600 | marketing / auth title |
151
151
  | `display-*` | 36–60 | hero numbers only |
152
152
  | `code` | 13/20 mono | identifiers, inline code |
@@ -112,8 +112,8 @@ export function OrdersPage() {
112
112
  ```
113
113
 
114
114
  What stands in the bar — the path ending in the title, the tools, every button, the active filters —
115
- is [the top bar](composition.md#the-top-bar); where the title stands on a document page or an
116
- overview is [the page skeleton](composition.md#1-page-skeleton).
115
+ is [the top bar](composition.md#the-top-bar); the one exception, a document page with its title in
116
+ the flow, is [the page skeleton](composition.md#1-page-skeleton).
117
117
 
118
118
  With React Router the frame's `children` is `<Outlet />` and every item is `Sidebar.Item asChild`
119
119
  around a `NavLink` ([the router example](reference/layout/sidebar/examples/router.tsx)). The same frame
@@ -152,8 +152,8 @@ shows only icons); none on the children of a sub-list.
152
152
 
153
153
  ## Page wrapper
154
154
 
155
- - App pages (dashboard, list, detail, a board): `Page.Root` — the full width of the panel; the
156
- page's title and tools are in its `Page.TopBar`, its blocks go into `Page.Body`
155
+ - App pages (dashboard, analytics, list, detail, a board): `Page.Root` — the full width of the
156
+ panel; the page's title and tools are in its `Page.TopBar`, its blocks go into `Page.Body`
157
157
  ([the page skeleton](composition.md#1-page-skeleton)).
158
158
  - Document pages — settings and long forms: `Page.Root maxWidth="wide"` with `Page.Header` in the
159
159
  flow (the page's h1) and a `Card.Root` with its own `Card.Header` per section
@@ -220,7 +220,7 @@ No `labels`.
220
220
  | [flat.tsx](examples/flat.tsx) | A flat tile without the raised shadow next to the default one, for dense grids — `flat`. |
221
221
  | [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`. |
222
222
  | [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`. |
223
- | [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`. |
223
+ | [screen.tsx](examples/screen.tsx) | A dashboard: the title with a line of data, the period switch and the report in the page's top bar; 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 — `Page.TopBar`, `Page.Grid`. |
224
224
 
225
225
  ## Mistakes
226
226
  - A `<div>` with a border styled as a card → use `Card.Root`; depth comes from fill, not lines.
@@ -1,4 +1,4 @@
1
- /** 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`. */
1
+ /** A dashboard: the title with a line of data, the period switch and the report in the page's top bar; 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 — `Page.TopBar`, `Page.Grid`. */
2
2
  import {
3
3
  Badge,
4
4
  Button,
@@ -157,10 +157,14 @@ export default function CardScreenExample() {
157
157
 
158
158
  return (
159
159
  <Page.Root>
160
- {/* An overview: the large title in the flow; the bar keeps the period and the report in
161
- reach and repeats the title once it scrolls away. */}
162
- <Page.TopBar collapse>
163
- <Page.Tools>
160
+ {/* An overview titles itself in its bar like any work screen: the title with its line of
161
+ data, the period that drives the tiles, the report. */}
162
+ <Page.TopBar>
163
+ <Page.Heading>
164
+ <Page.Title>Обзор</Page.Title>
165
+ <Page.Description>Продажи и оплаты · на 8 октября, 9:40</Page.Description>
166
+ </Page.Heading>
167
+ <Page.Tools aria-label="Обзор">
164
168
  <SegmentedControl.Root
165
169
  aria-label="Период"
166
170
  value={period}
@@ -180,12 +184,6 @@ export default function CardScreenExample() {
180
184
  </Button.Root>
181
185
  </Page.Actions>
182
186
  </Page.TopBar>
183
- <Page.Header>
184
- <Page.Heading>
185
- <Page.Title>Обзор</Page.Title>
186
- <Page.Description>Продажи и оплаты · на 8 октября, 9:40</Page.Description>
187
- </Page.Heading>
188
- </Page.Header>
189
187
  <Page.Body>
190
188
  {/* One board: every tile of the screen in one grid, 16 apart on both axes. */}
191
189
  <Page.Grid>
@@ -29,7 +29,7 @@ ChatList.Root <div> column filling its host (container `chat-lis
29
29
  │ └─ Crossfade loading (placeholder List rows) ⇄ empty (EmptyPage compact) ⇄ the list
30
30
  │ └─ List.Root <ul> aria-label «Диалоги»; spacer <li aria-hidden> stand for rows out of view
31
31
  │ └─ ChatList.Item List.Item <li> + <button> | <a href> | your element (asChild); current from value; one Tab stop
32
- │ ├─ ChatList.ItemIcon before the name: avatar slot (aria-hidden); after it: a glyph
32
+ │ ├─ ChatList.ItemIcon before the name: avatar slot (aria-hidden); after it: a glyph (role="img" with aria-label)
33
33
  │ ├─ ChatList.ItemText the name; muted glyph after it
34
34
  │ ├─ ChatList.ItemDescription author · media word · text — cross-fades with draft and «печатает…»
35
35
  │ ├─ (meta) delivery ticks · time
@@ -89,12 +89,12 @@ ChatList.Root <div> column filling its host (container `chat-lis
89
89
  | `…rest` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | `className` and the native attributes of the row element. |
90
90
 
91
91
  ### ChatList.ItemIcon
92
- `ref` → `HTMLSpanElement`. Before the name: the avatar slot (`aria-hidden`), an `Avatar.Root` sized by the row. After the name: a glyph beside it (verified, bot, group).
92
+ `ref` → `HTMLSpanElement`. Before the name: the avatar slot (`aria-hidden`), an `Avatar.Root` sized by the row. After the name: a glyph beside it (verified, bot, channel type), any kit icon sized by the row; with `aria-label` it is `role="img"` of that name.
93
93
 
94
94
  | Prop | Type | Default | Description |
95
95
  |---|---|---|---|
96
- | `children` | `ReactNode` | — (required) | An `Avatar.Root` (with `Avatar.Status`), or after the name an `Icon` with a `label`. |
97
- | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other `span` attributes. |
96
+ | `children` | `ReactNode` | — (required) | An `Avatar.Root` (with `Avatar.Status`), or after the name an icon: `<Icon name>` or a glyph of `prime-ui-kit/icons` (`<SendIcon />`), as is. |
97
+ | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className`, `aria-label` (names a glyph after the name: its channel, «Официальный аккаунт») and the other `span` attributes. |
98
98
 
99
99
  ### ChatList.ItemText
100
100
  `ref` → `HTMLSpanElement`. The name of the dialog: one line, truncated, the glyphs after it kept whole; the search query is marked.
@@ -200,7 +200,7 @@ Root attribute: `data-size`. Every motion honours reduced motion (durations coll
200
200
 
201
201
  - The list is a `<ul>` named «Диалоги» (`labels.list`); each row an `<li>` with a button, a link with `href`, or your link with `asChild`.
202
202
  - The open dialog is `aria-current="true"` on a button, `"page"` on a link. Selection does not follow the focus: arrows only move it (a dialog may load a conversation); Enter, Space and a click open.
203
- - The row is named by its content: the name, glyphs with a `label` («Без звука», a verified mark), the preview, the delivery mark («Прочитано»), the time, then «упоминание» and «непрочитанных: 3» instead of the bare badges; the avatar is `aria-hidden`.
203
+ - The row is named by its content: the name, the glyphs (`ChatList.ItemIcon` `aria-label`, «Без звука»), the preview, the delivery mark («Прочитано»), the time, then «упоминание» and «непрочитанных: 3» instead of the bare badges; the avatar is `aria-hidden`.
204
204
  - While the other side types, the preview is `aria-hidden` and «печатает…» is read; a draft reads «Черновик: …».
205
205
  - A row with a menu has `aria-keyshortcuts="Shift+F10"`; the menu is a `Dropdown` (`menu` / `menuitem`).
206
206
  - The search field is `type="search"` named «Поиск по диалогам»; after a search a polite status says «Найдено: 4» or «Ничего не найдено».
@@ -257,6 +257,7 @@ Root attribute: `data-size`. Every motion honours reduced motion (durations coll
257
257
  - Filtering inside `renderItem` (returning `null`) → filter `items`: the list counts, announces and glides by `items`.
258
258
  - Pinned dialogs mixed into the time order → put them first in `items`; `pinned` only draws the pin.
259
259
  - `onValueChange` used to mark a dialog read on focus → arrows only move the focus; mark read when the conversation shows it (`Chat.Messages onRead`) or on open.
260
+ - A glyph after the name cloned with `label` and `size` → put the icon in `ChatList.ItemIcon` as is and name the part with `aria-label`: the row sizes it.
260
261
  - An `Avatar.Root size="s"` to fit the row → leave `size` out; the slot sizes the avatar to the tier.
261
262
  - A «⋯» button nested inside the row for the menu → `ChatList.ItemMenu`: it opens on right click, a long press and Shift+F10.
262
263
  - Deleting from the menu without a question → open a `ConfirmDialog` from the menu item.
@@ -111,13 +111,13 @@ export default function ChatListStructureExample() {
111
111
  </ChatList.ItemIcon>
112
112
  <ChatList.ItemText>{item.name}</ChatList.ItemText>
113
113
  {item.verified ? (
114
- <ChatList.ItemIcon>
115
- <Icon name="status.success" tone="accent" label="Официальный аккаунт" />
114
+ <ChatList.ItemIcon aria-label="Официальный аккаунт">
115
+ <Icon name="status.success" tone="accent" />
116
116
  </ChatList.ItemIcon>
117
117
  ) : null}
118
118
  {item.group ? (
119
- <ChatList.ItemIcon>
120
- <Icon name="object.users" label="Группа" />
119
+ <ChatList.ItemIcon aria-label="Группа">
120
+ <Icon name="object.users" />
121
121
  </ChatList.ItemIcon>
122
122
  ) : null}
123
123
  <ChatList.ItemDescription
@@ -136,7 +136,7 @@ CommandMenu.Root Modal + top-aligned palette panel, query and lo
136
136
  | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `children` (the title), `className` and the other span attributes. |
137
137
 
138
138
  ### CommandMenu.Empty
139
- `ref` → `HTMLDivElement`. A compact EmptyPage (`role="status"`) shown while nothing matches and nothing loads: `labels.empty` and `labels.emptyHint`, text only. An action for the query is a `persistent` `CommandMenu.Item` at the end of the list.
139
+ `ref` → `HTMLDivElement`. A compact EmptyPage (`role="status"`) shown while nothing matches and nothing loads: `labels.empty`, text only. An action for the query is a `persistent` `CommandMenu.Item` at the end of the list.
140
140
 
141
141
  | Prop | Type | Default | Description |
142
142
  |---|---|---|---|
@@ -236,7 +236,6 @@ One look: the floating layer of the ladder (it is a Modal), `--prime-modal-radiu
236
236
  |---|---|---|
237
237
  | `search` | `"Поиск"` | Default placeholder and accessible name of `CommandMenu.Input`. |
238
238
  | `empty` | `"Ничего не найдено"` | `CommandMenu.Empty`: nothing matches the query. |
239
- | `emptyHint` | `"Попробуйте изменить запрос"` | Second line of `CommandMenu.Empty`; `""` hides it. |
240
239
  | `loading` | `"Загрузка…"` | Announced while `loading` or `loadingMore`. |
241
240
  | `retry` | `"Повторить"` | The retry button under `loadError` (`onRetry`). |
242
241
 
@@ -46,7 +46,7 @@ export default function CommandMenuStatesExample() {
46
46
  onSearchChange={setSearch}
47
47
  loading={state === "loading"}
48
48
  aria-label="Выбор склада"
49
- labels={{ empty: "Склад не найден", emptyHint: "В этом городе склада пока нет" }}
49
+ labels={{ empty: "В этом городе склада пока нет" }}
50
50
  >
51
51
  <CommandMenu.Input placeholder="Город склада" />
52
52
  <CommandMenu.List>
@@ -109,7 +109,7 @@ In an optimistic chat the send returns at once: add the message to the list with
109
109
  | `defaultRecordMode` | `"voice" \| "video"` | `"voice"` | Initial record mode, uncontrolled. |
110
110
  | `onRecordModeChange` | `(mode: "voice" \| "video") => void` | — | A double click / double tap on the record button (or ↑ / ↓ on it) switched voice and video. |
111
111
  | `queue` | `Pick<UploadQueue, "items" \| "add" \| "retry" \| "remove" \| "clear" \| "move">` | — | Attachments, usually `useUploadQueue(…)`: the paperclip, a paste and a drop add to it, and a tray above the row shows them as one reorderable strip of tiles. Without `queue` paste and drop of files do nothing. |
112
- | `formatting` | `boolean` | `true` | Selecting text shows a floating toolbar of seven Markdown tools and enables their hotkeys; a tool wraps the selection, a second press unwraps it. `false` turns both off. |
112
+ | `formatting` | `boolean` | `true` | Selecting text shows a floating toolbar of eight Markdown tools and enables their hotkeys; a tool wraps the selection, a second press unwraps it. `false` turns both off. |
113
113
  | `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | host tier, else `"m"` | Tier of the field, the buttons and the bar text. Without it the tier of its host, else `m`. |
114
114
  | `disabled` | `boolean` | `false` | A closed conversation: the field, the paperclip and the button are disabled; nothing is pasted or dropped. A disabled `Fieldset` around it sets it too (`data-disabled` included). |
115
115
  | `inputRef` | `Ref<HTMLTextAreaElement>` | — | The message `<textarea>`: focus it after «Ответить» in a message menu. |
@@ -197,7 +197,7 @@ Composer has no `variant`; what the row shows follows its state.
197
197
  ### Flags
198
198
  | Value | Looks like | Use when | Default |
199
199
  |---|---|---|---|
200
- | `formatting` | a floating toolbar over a selection (bold, italic, strikethrough, code, spoiler, link, quote); Mod-hotkeys | always, unless the chat shows plain text only | `true` |
200
+ | `formatting` | a floating toolbar over a selection (bold, italic, underline, strikethrough, code, spoiler, link, quote); Mod-hotkeys | always, unless the chat shows plain text only | `true` |
201
201
  | `disabled` | the field, paperclip and button disabled | a closed conversation, no permission to write | `false` |
202
202
  | `queue` | the tray above the field: one strip of tiles — photos, videos and files; paste and drop take files | the chat accepts files | — |
203
203
  | `onSendVoice` / `onSendVideoNote` | an empty field offers the record button | the chat takes voice / video messages | — |
@@ -250,7 +250,7 @@ Motion: the tray opens and closes as a Crossfade (fade and height glide, the row
250
250
  | `Mod + Enter` | Always sends, on a touch screen too. |
251
251
  | `↑` | In an empty field opens your last message for editing (`onEditLast`). |
252
252
  | `Escape` | Clears a selection with the toolbar, then cancels the reply or edit; while recording throws the recording away and stops there, so an enclosing Modal or Drawer stays open. |
253
- | `Mod + B` · `Mod + I` · `Mod + Shift + X` · `Mod + E` · `Mod + Shift + H` · `Mod + K` · `Mod + Shift + 9` | Bold, italic, strikethrough, code, spoiler, link and quote for the selection; the physical key counts, so any layout works. |
253
+ | `Mod + B` · `Mod + I` · `Mod + U` · `Mod + Shift + X` · `Mod + E` · `Mod + Shift + H` · `Mod + K` · `Mod + Shift + 9` | Bold, italic, underline, strikethrough, code, spoiler, link and quote for the selection; the physical key counts, so any layout works. |
254
254
  | `Enter` · `Space` | On the record button: record hands-free at once. During a hands-free recording, wherever the focus is in the composer: Enter sends the recording (a paused one too), Space pauses and resumes it — the timer and the waveform stand, the dot turns into a pause glyph. |
255
255
  | `↑` · `↓` | On the record button, with voice and video both offered: switches voice and video (`aria-keyshortcuts`, named in the button's description). |
256
256
  | `←` · `→` | Move between the formatting buttons. |
@@ -310,6 +310,7 @@ Motion: the tray opens and closes as a Crossfade (fade and height glide, the row
310
310
  | `formatting` | `"Форматирование"` | Accessible name of the formatting toolbar. |
311
311
  | `bold` | `"Жирный"` | Bold tool. |
312
312
  | `italic` | `"Курсив"` | Italic tool. |
313
+ | `underline` | `"Подчёркнутый"` | Underline tool (`__…__`). |
313
314
  | `strikethrough` | `"Зачёркнутый"` | Strikethrough tool. |
314
315
  | `code` | `"Моноширинный"` | Code tool. |
315
316
  | `spoiler` | `"Скрытый текст"` | Spoiler tool (`\|\|…\|\|`). |
@@ -333,7 +334,7 @@ Motion: the tray opens and closes as a Crossfade (fade and height glide, the row
333
334
  | [attach-menu.tsx](examples/attach-menu.tsx) | The paperclip as a menu of attachment kinds: photos and videos, documents, any file, and an invoice from the system that puts its link into the message and returns the focus to the field — `Composer.Item`, `Composer.ItemIcon`, `accept`, `onSelect`, `inputRef`. |
334
335
  | [reply-edit.tsx](examples/reply-edit.tsx) | Answering a client's message and correcting your own: the bar above the field quotes the message, Escape or the cross cancels, ↑ in an empty field opens your last message, send becomes save and hands over the message id — `Composer.Context`, `mode`, `messageId`, `onDismiss`, `onEditLast`. |
335
336
  | [typing.tsx](examples/typing.tsx) | The other side's status line follows what you do: typing, recording a voice or a video message, then nothing — `onActivityChange`. |
336
- | [formatting.tsx](examples/formatting.tsx) | Selected text gets a toolbar of bold, italic, strikethrough, code, spoiler, link and quote, each with its shortcut, written as Markdown around the selection — `formatting`. |
337
+ | [formatting.tsx](examples/formatting.tsx) | Selected text gets a toolbar of bold, italic, underline, strikethrough, code, spoiler, link and quote, each with its shortcut, written as Markdown around the selection — `formatting`. |
337
338
  | [voice.tsx](examples/voice.tsx) | Voice and round video messages: hold the button to record and release to send, slide left to cancel, slide up or tap to record hands-free; a double tap or double click switches voice and video — `onSendVoice`, `onSendVideoNote`, `recordMode`. |
338
339
  | [bot-keyboard.tsx](examples/bot-keyboard.tsx) | A bot's reply keyboard under the field: a key sends its text as a message, the field asks for a key, the keyboard hides after use and the toggle brings it back — `Composer.Keyboard`, `rows`, `placeholder`, `oneTime`. |
339
340
  | [states.tsx](examples/states.tsx) | A closed conversation is disabled, a slow send keeps the text with a busy button until the server answers — `disabled`, `onSend`. |
@@ -1,4 +1,4 @@
1
- /** Selected text gets a toolbar of bold, italic, strikethrough, code, spoiler, link and quote, each with its shortcut, written as Markdown around the selection — `formatting`. */
1
+ /** Selected text gets a toolbar of bold, italic, underline, strikethrough, code, spoiler, link and quote, each with its shortcut, written as Markdown around the selection — `formatting`. */
2
2
  import { Composer } from "prime-ui-kit";
3
3
 
4
4
  import styles from "./examples.module.css";