prime-ui-kit 0.21.2 → 0.21.4

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 (171) hide show
  1. package/README.md +2 -2
  2. package/THIRD_PARTY_NOTICES +13 -0
  3. package/dist/chunks/{chunk-T7IEJSTH.js → chunk-HMLQCKRL.js} +36 -0
  4. package/dist/chunks/{chunk-7JYPFRZ3.js → chunk-PFASPQSJ.js} +393 -354
  5. package/dist/chunks/{chunk-SQGPQOUO.js → chunk-SUGCIPD7.js} +1 -1
  6. package/dist/chunks/{chunk-ME4VEAQ5.js → chunk-UNNQITFE.js} +2132 -1619
  7. package/dist/chunks/{chunk-VQ3UCVDV.js → chunk-WGXXPJDR.js} +5 -2
  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/input/Input.d.ts +1 -1
  20. package/dist/components/kanban/Kanban.d.ts +0 -2
  21. package/dist/components/list/List.d.ts +7 -1
  22. package/dist/components/login-form/LoginForm.d.ts +5 -1
  23. package/dist/components/message/Message.d.ts +5 -1
  24. package/dist/components/message/MessageParts.d.ts +7 -2
  25. package/dist/components/rich-text/RichText.d.ts +12 -4
  26. package/dist/components/select/selectContext.d.ts +0 -2
  27. package/dist/components/smart-filter/SmartFilter.d.ts +1 -5
  28. package/dist/components/sparkline/Sparkline.d.ts +50 -8
  29. package/dist/components/tag-select/TagSelect.d.ts +0 -2
  30. package/dist/components/textarea/Textarea.d.ts +2 -2
  31. package/dist/icon-set.d.ts +5 -1
  32. package/dist/icon-set.js +8 -2
  33. package/dist/icons/glyph.d.ts +12 -1
  34. package/dist/icons/glyphs/telegram.d.ts +2 -0
  35. package/dist/icons/glyphs/vk.d.ts +2 -0
  36. package/dist/icons/glyphs/whatsapp.d.ts +2 -0
  37. package/dist/icons/registry.d.ts +8 -1
  38. package/dist/index.css +1 -1
  39. package/dist/index.js +7 -3
  40. package/dist/internal/ToolRow.d.ts +3 -1
  41. package/dist/internal/autofill.d.ts +30 -0
  42. package/dist/internal/barPlan.d.ts +7 -1
  43. package/dist/internal/blockStack.d.ts +7 -0
  44. package/dist/internal/overlay/DialogParts.d.ts +8 -6
  45. package/dist/internal/richText.d.ts +32 -1
  46. package/dist/internal/textFormatting.d.ts +2 -2
  47. package/dist/layout/page/Page.d.ts +2 -1
  48. package/dist/skill/SKILL.md +3 -2
  49. package/dist/skill/api-contract.md +2 -1
  50. package/dist/skill/cheatsheet.md +1 -1
  51. package/dist/skill/checklist.md +8 -1
  52. package/dist/skill/choosing.md +76 -5
  53. package/dist/skill/components.md +2 -2
  54. package/dist/skill/composition.md +108 -20
  55. package/dist/skill/foundations.md +1 -1
  56. package/dist/skill/layouts.md +4 -4
  57. package/dist/skill/reference/components/button/examples/in-form.tsx +0 -1
  58. package/dist/skill/reference/components/card/COMPONENT.md +1 -1
  59. package/dist/skill/reference/components/card/examples/screen.tsx +9 -11
  60. package/dist/skill/reference/components/chat-list/COMPONENT.md +6 -5
  61. package/dist/skill/reference/components/chat-list/examples/structure.tsx +4 -4
  62. package/dist/skill/reference/components/command-menu/COMPONENT.md +1 -2
  63. package/dist/skill/reference/components/command-menu/examples/states.tsx +1 -1
  64. package/dist/skill/reference/components/composer/COMPONENT.md +5 -4
  65. package/dist/skill/reference/components/composer/examples/formatting.tsx +1 -1
  66. package/dist/skill/reference/components/data-table/COMPONENT.md +17 -16
  67. package/dist/skill/reference/components/data-table/examples/server.tsx +2 -1
  68. package/dist/skill/reference/components/datepicker/COMPONENT.md +2 -2
  69. package/dist/skill/reference/components/dnd/COMPONENT.md +2 -1
  70. package/dist/skill/reference/components/drawer/COMPONENT.md +9 -6
  71. package/dist/skill/reference/components/drawer/examples/screen.tsx +1 -5
  72. package/dist/skill/reference/components/drawer/examples/tab-switcher.tsx +158 -0
  73. package/dist/skill/reference/components/field-list/COMPONENT.md +7 -7
  74. package/dist/skill/reference/components/field-list/examples/limits.tsx +2 -2
  75. package/dist/skill/reference/components/file-upload/COMPONENT.md +8 -12
  76. package/dist/skill/reference/components/file-upload/examples/gallery.tsx +2 -2
  77. package/dist/skill/reference/components/file-upload/examples/in-form.tsx +2 -4
  78. package/dist/skill/reference/components/file-upload/examples/narrow.tsx +1 -0
  79. package/dist/skill/reference/components/file-upload/examples/overview.tsx +1 -5
  80. package/dist/skill/reference/components/file-upload/examples/sizes.tsx +1 -1
  81. package/dist/skill/reference/components/file-upload/examples/states.tsx +4 -5
  82. package/dist/skill/reference/components/file-upload/examples/validation.tsx +2 -7
  83. package/dist/skill/reference/components/input/COMPONENT.md +3 -0
  84. package/dist/skill/reference/components/input/examples/in-form.tsx +0 -1
  85. package/dist/skill/reference/components/input/examples/validation.tsx +1 -1
  86. package/dist/skill/reference/components/kanban/COMPONENT.md +1 -2
  87. package/dist/skill/reference/components/list/COMPONENT.md +11 -6
  88. package/dist/skill/reference/components/list/examples/reorder.tsx +30 -3
  89. package/dist/skill/reference/components/login-form/COMPONENT.md +2 -2
  90. package/dist/skill/reference/components/login-form/examples/sizes.tsx +6 -2
  91. package/dist/skill/reference/components/message/COMPONENT.md +12 -7
  92. package/dist/skill/reference/components/message/examples/status.tsx +2 -1
  93. package/dist/skill/reference/components/message/examples/text-formatting.tsx +13 -3
  94. package/dist/skill/reference/components/modal/COMPONENT.md +5 -4
  95. package/dist/skill/reference/components/rich-text/COMPONENT.md +45 -7
  96. package/dist/skill/reference/components/rich-text/examples/entities.tsx +20 -0
  97. package/dist/skill/reference/components/select/COMPONENT.md +1 -2
  98. package/dist/skill/reference/components/sheet/COMPONENT.md +5 -4
  99. package/dist/skill/reference/components/smart-filter/COMPONENT.md +3 -5
  100. package/dist/skill/reference/components/sparkline/COMPONENT.md +40 -12
  101. package/dist/skill/reference/components/sparkline/examples/lower-is-better.tsx +46 -0
  102. package/dist/skill/reference/components/sparkline/examples/own-value.tsx +41 -0
  103. package/dist/skill/reference/components/sparkline/examples/reference.tsx +2 -1
  104. package/dist/skill/reference/components/tabs/COMPONENT.md +1 -1
  105. package/dist/skill/reference/components/tabs/examples/orientation.tsx +1 -1
  106. package/dist/skill/reference/components/tag-select/COMPONENT.md +0 -2
  107. package/dist/skill/reference/components/tag-select/examples/overview.tsx +0 -1
  108. package/dist/skill/reference/components/tag-select/examples/sizes.tsx +0 -1
  109. package/dist/skill/reference/components/textarea/COMPONENT.md +6 -3
  110. package/dist/skill/reference/icon-set.ts +5 -1
  111. package/dist/skill/reference/layout/page/COMPONENT.md +13 -12
  112. package/dist/skill/reference/layout/page/examples/collapse.tsx +45 -31
  113. package/dist/skill/reference/layout/page/examples/router.tsx +47 -14
  114. package/dist/skill/ux.md +98 -4
  115. package/package.json +1 -1
  116. package/src/components/button/examples/in-form.tsx +0 -1
  117. package/src/components/card/COMPONENT.md +1 -1
  118. package/src/components/card/examples/screen.tsx +9 -11
  119. package/src/components/chat-list/COMPONENT.md +6 -5
  120. package/src/components/chat-list/examples/structure.tsx +4 -4
  121. package/src/components/command-menu/COMPONENT.md +1 -2
  122. package/src/components/command-menu/examples/states.tsx +1 -1
  123. package/src/components/composer/COMPONENT.md +5 -4
  124. package/src/components/composer/examples/formatting.tsx +1 -1
  125. package/src/components/data-table/COMPONENT.md +17 -16
  126. package/src/components/data-table/examples/server.tsx +2 -1
  127. package/src/components/datepicker/COMPONENT.md +2 -2
  128. package/src/components/dnd/COMPONENT.md +2 -1
  129. package/src/components/drawer/COMPONENT.md +9 -6
  130. package/src/components/drawer/examples/screen.tsx +1 -5
  131. package/src/components/drawer/examples/tab-switcher.tsx +158 -0
  132. package/src/components/field-list/COMPONENT.md +7 -7
  133. package/src/components/field-list/examples/limits.tsx +2 -2
  134. package/src/components/file-upload/COMPONENT.md +8 -12
  135. package/src/components/file-upload/examples/gallery.tsx +2 -2
  136. package/src/components/file-upload/examples/in-form.tsx +2 -4
  137. package/src/components/file-upload/examples/narrow.tsx +1 -0
  138. package/src/components/file-upload/examples/overview.tsx +1 -5
  139. package/src/components/file-upload/examples/sizes.tsx +1 -1
  140. package/src/components/file-upload/examples/states.tsx +4 -5
  141. package/src/components/file-upload/examples/validation.tsx +2 -7
  142. package/src/components/input/COMPONENT.md +3 -0
  143. package/src/components/input/examples/in-form.tsx +0 -1
  144. package/src/components/input/examples/validation.tsx +1 -1
  145. package/src/components/kanban/COMPONENT.md +1 -2
  146. package/src/components/list/COMPONENT.md +11 -6
  147. package/src/components/list/examples/reorder.tsx +30 -3
  148. package/src/components/login-form/COMPONENT.md +2 -2
  149. package/src/components/login-form/examples/sizes.tsx +6 -2
  150. package/src/components/message/COMPONENT.md +12 -7
  151. package/src/components/message/examples/status.tsx +2 -1
  152. package/src/components/message/examples/text-formatting.tsx +13 -3
  153. package/src/components/modal/COMPONENT.md +5 -4
  154. package/src/components/rich-text/COMPONENT.md +45 -7
  155. package/src/components/rich-text/examples/entities.tsx +20 -0
  156. package/src/components/select/COMPONENT.md +1 -2
  157. package/src/components/sheet/COMPONENT.md +5 -4
  158. package/src/components/smart-filter/COMPONENT.md +3 -5
  159. package/src/components/sparkline/COMPONENT.md +40 -12
  160. package/src/components/sparkline/examples/lower-is-better.tsx +46 -0
  161. package/src/components/sparkline/examples/own-value.tsx +41 -0
  162. package/src/components/sparkline/examples/reference.tsx +2 -1
  163. package/src/components/tabs/COMPONENT.md +1 -1
  164. package/src/components/tabs/examples/orientation.tsx +1 -1
  165. package/src/components/tag-select/COMPONENT.md +0 -2
  166. package/src/components/tag-select/examples/overview.tsx +0 -1
  167. package/src/components/tag-select/examples/sizes.tsx +0 -1
  168. package/src/components/textarea/COMPONENT.md +6 -3
  169. package/src/layout/page/COMPONENT.md +13 -12
  170. package/src/layout/page/examples/collapse.tsx +45 -31
  171. package/src/layout/page/examples/router.tsx +47 -14
@@ -33,7 +33,7 @@ FieldList field frame: label · the list · hint / error
33
33
  │ │ └─ <li> row [a cell per column] [end: × | ⋯] [grip: handle | lock]
34
34
  │ │ └─ cell one kit field without `label`, named by its column heading
35
35
  │ └─ totals row `renderTotals`: a group «Итого», a total per column on the same tracks
36
- └─ add row Button «addLabel» (+ `labels.maxReached` at `max`)
36
+ └─ add row Button «addLabel» (leaves at `max`)
37
37
  ```
38
38
  A leaf: rows come from `value` through `renderItem`, which returns one element per column (a fragment). Every framed kit field (Input, Select, Datepicker, NativeSelect, ColorField…) in a cell takes its column heading as its label: read by screen readers, not shown while the heading row is on screen, shown above the field once the rows stack in a narrow container. `ColorPresets` takes it as the name of its trigger. Every row, a one-column row too, ends with the same slots: the remove «×» (or the «⋯» menu) and, with `reorderable` buttons, the grip after it — never a segment inside a field, where a «×» would read as «clear the text».
39
39
 
@@ -53,7 +53,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
53
53
  | `value` | `readonly T[]` | — | The rows (controlled). |
54
54
  | `defaultValue` | `readonly T[]` | `[]` | The first rows (uncontrolled). |
55
55
  | `onValueChange` | `(value: T[]) => void \| Promise<void>` | — | The new list after an edit (`row.update`), an add, a remove or a move. Return the save's promise: the new list shows while it is pending, the owner's again on a reject (say why with `error`). |
56
- | `createItem` | `() => T` | — | A new empty item; with it the add button shows under the rows and focus goes to the new row's first field. |
56
+ | `createItem` | `() => T` | — | A new empty item; with it the add button shows under the rows (until `max`) and focus goes to the new row's first field. |
57
57
  | `addLabel` | `string` | — | Text of the add button: what is added («Добавить позицию»). |
58
58
  | `getLabel` | `(item: T) => string` | — | The row's own text (its title field) for the remove button, the handle, the menu button and move announcements; else the row number. |
59
59
  | `getLocked` | `(item: T) => boolean` | — | Pinned rows: the remove stays in its place, off, and says why (`labels.lockedReason`); with `reorderable` a lock takes the handle's place and the row holds it. In the menu every item is off. |
@@ -61,7 +61,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
61
61
  | `rowActions` | `"buttons" \| "menu"` | `"buttons"` | How a row offers its actions: `buttons` — a remove button «×» and, with `reorderable`, the handle after it; `menu` — one «⋯» button whose menu moves the row up or down and removes it. |
62
62
  | `variant` | `"plain" \| "surface"` | `"plain"` | Look of the list: `plain` — the rows on the host surface; `surface` — the rows on a fill of their own, one step up the surface ladder, with side padding. |
63
63
  | `min` | `number` | `0` | The fewest rows: at this count the remove buttons are disabled. |
64
- | `max` | `number` | — | The most rows: at this count the add button is disabled with `labels.maxReached` beside it. |
64
+ | `max` | `number` | — | The most rows: at this count the add button closes up in place and comes back once a row is removed. The list says no limit text of its own; a limit the person should know goes in `hint`. |
65
65
  | `label` | `ReactNode` | — | Heading of the list; names the `<ol>`. Omit it inside a `Fieldset` whose legend names the list. |
66
66
  | `aria-label` | `string` | — | Name of the `<ol>` when no visible `label` names it (a section heading above the list). |
67
67
  | `hint` | `ReactNode` | — | Help text under the list; hidden while `error` shows. |
@@ -101,7 +101,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
101
101
  | `reorderable` | buttons: a grip at the very end of every row, after the remove button, Alt + ↑ / ↓ on it; menu: «Выше» and «Ниже» in the menu, Alt + ↑ / ↓ on «⋯» | the order of the records matters (a pricing page, a checklist, payment stages) | `false` |
102
102
  | `getLocked` | the remove button in its place, off, with a tooltip saying why (`labels.lockedReason`); with `reorderable` a lock where the grip is and the row holds its place; in the menu every item off, «Удалить» says why | a base value, system records a person cannot drop | — |
103
103
  | `createItem` | the add button under the rows, aligned with the first column | the person grows the list | — |
104
- | `min` · `max` | remove buttons off at `min`; the add button off with «Не больше {max}» at `max` | the record count is bounded | `0` · — |
104
+ | `min` · `max` | remove buttons off at `min`; the add button closes up at `max` and comes back when a row leaves — no limit text of its own, a limit the person should know goes in `hint` | the record count is bounded | `0` · — |
105
105
  | `renderTotals` | a row of totals under the rows over a faint line, each on its column, aligned as the column's field text; «Итого» in the first cell without a total | the rows add up (shares, amounts, hours) | — |
106
106
 
107
107
  **Combinations**
@@ -115,7 +115,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
115
115
  |---|---|---|
116
116
  | empty | `value` with no items | no heading row; only the add button (with `createItem`) |
117
117
  | at min | `value.length <= min` | remove buttons (or «Удалить» items) `disabled` |
118
- | at max | `value.length >= max` | add button `disabled`, `labels.maxReached` beside it |
118
+ | at max | `value.length >= max` | no add button: it closes up in place (`partPresence`) and rises in again when a row leaves |
119
119
  | pinned row | `getLocked(item)` | `data-locked="true"` on the `<li>`; the remove button stays in its slot, `disabled` behind a focusable Tooltip host, its tooltip `labels.lockedReason`; with `reorderable` Dnd's lock in the grip box; in the menu every item `disabled`, «Удалить» described by `labels.lockedReason` |
120
120
  | hover | the pointer over a row of a `surface` list (`(hover: hover)` only; a `plain` row has no fill of its own) | the whole row takes the field fill (`--prime-color-fill-muted`) and its fields the surface fill (`--prime-color-layer-current`) |
121
121
  | invalid | `error` (the list), a field's own `error` | the list's message under the add row; a field's under the field in its row |
@@ -172,7 +172,6 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
172
172
  | `row` | `"строка {index}"` | Name of a row without `getLabel` text; `{index}` counts from 1. |
173
173
  | `namedRow` | `"«{label}»"` | Name of a row with `getLabel` text; `{label}` is that text. |
174
174
  | `lockedReason` | `"Закреплённую строку нельзя удалить"` | Why a pinned row (`getLocked`) cannot be removed: the tooltip of its remove button, the description of its menu's remove item. |
175
- | `maxReached` | `"Не больше {max}"` | Text beside the disabled add button once the list holds `max` rows. |
176
175
  | `totals` | `"Итого"` | Name of the totals row (`renderTotals`), shown in its first cell when that has no total. |
177
176
 
178
177
  ## Examples
@@ -185,7 +184,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
185
184
  | [row-menu.tsx](examples/row-menu.tsx) | Payment stages of a project on a touch-first screen, where a drag is awkward: one more-button per row holds move up, move down and remove, the moves are off at the ends and next to the pinned advance, and Alt+arrows on the button move the row too — `rowActions`, `reorderable`, `getLocked`. |
186
185
  | [locked.tsx](examples/locked.tsx) | Specs of a plan on one line each: an icon picker and its color side by side in compact columns, the first three specs are pinned with a lock in the handle's place and a remove button that is off and says why, the rest reorder below them — `getLocked`, `width`, `reorderable`. |
187
186
  | [base-row.tsx](examples/base-row.tsx) | A plan price and its changes by month in one table: the base price is the first, pinned row whose remove button is off and says why, each change is a row with its month and its price, and the unit sits in the field, not in a heading — `getLocked`, `labels`, `width`. |
188
- | [limits.tsx](examples/limits.tsx) | Recipients of invoice reminders, from one to three: the last row cannot be removed and the add button says why it is off once the list is full — `min`, `max`, `hint`. |
187
+ | [limits.tsx](examples/limits.tsx) | Recipients of invoice reminders, from one to three: the last row cannot be removed, the add button leaves once the list is full and the hint names the limit — `min`, `max`, `hint`. |
189
188
  | [errors.tsx](examples/errors.tsx) | Contacts of a client checked on save: a field's problem shows under that field in its row, a problem of the whole list under the list, and focus goes to the first wrong field; a `*` after a heading marks a required column — `error`, `required`. |
190
189
  | [totals.tsx](examples/totals.tsx) | A deal bonus split between the team: the share takes a narrow column, and the totals row under the lines sums the shares and the amounts, each ending where its fields end their numbers — `renderTotals`, `width`. |
191
190
  | [saving.tsx](examples/saving.tsx) | Removing a recipient is saved on the server: the row leaves at once and comes back with the reason if the server refuses — `onValueChange`, `error`. |
@@ -206,6 +205,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
206
205
  - A «⋯» menu or a «−» drawn by hand next to the rows → `rowActions`; a Card drawn around the list to set it apart from the form → `variant="surface"`.
207
206
  - `rowActions="menu"` on a two-column list a person reorders all day → the buttons: the handle and «×» in sight are one gesture, the menu is three.
208
207
  - `aria-label` written on every field in a cell → none: the column heading names the field.
208
+ - `createItem` dropped by hand once nothing is left to add (every section picked) → `max`: the add button leaves at the limit by itself; a limit the person should know is a `hint` («Не больше трёх получателей»), never a count beside the button.
209
209
  - A grip, a remove button and an add button drawn by hand around a `.map()` of fields → FieldList.
210
210
  - A total under the list typed by hand in a line of text, or a DataTable of fields to get a totals row → `renderTotals`.
211
211
 
@@ -1,4 +1,4 @@
1
- /** Recipients of invoice reminders, from one to three: the last row cannot be removed and the add button says why it is off once the list is full — `min`, `max`, `hint`. */
1
+ /** Recipients of invoice reminders, from one to three: the last row cannot be removed, the add button leaves once the list is full and the hint names the limit — `min`, `max`, `hint`. */
2
2
  import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
3
  import * as React from "react";
4
4
 
@@ -18,7 +18,7 @@ export default function FieldListLimitsExample() {
18
18
  return (
19
19
  <FieldList
20
20
  label="Кому напоминать об оплате"
21
- hint="Письмо уходит за три дня до срока."
21
+ hint="Письмо уходит за три дня до срока, не больше трёх получателей."
22
22
  columns={COLUMNS}
23
23
  min={1}
24
24
  max={3}
@@ -55,7 +55,7 @@ FileUpload.Root field frame: label → trigger → files → hin
55
55
  │ middle: picture, video frame + play mark or the file's sheet ·
56
56
  │ band at the bottom: name (no extension) … size (a video: duration);
57
57
  │ the upload disk over the middle
58
- └─ add tile trigger="tile": «+», «Добавить», `count из max`; the whole row when empty
58
+ └─ add tile trigger="tile": «+», «Добавить»; the whole row when empty, gone at `max`
59
59
 
60
60
  FileUpload.Queue the same files with no trigger of their own (a tray whose paperclip lives elsewhere)
61
61
 
@@ -67,7 +67,7 @@ FileUpload.Item a file row the set does not draw (own actions, a
67
67
  ├─ FileUpload.ItemActions buttons at the end of the row (kit Button)
68
68
  └─ FileUpload.ItemProgress the kit ProgressBar across the row
69
69
  ```
70
- The built-in zone body (icon, `labels.title`, `labels.description` and a decorative soft Button) renders when there are no children; the built-in button content is the paperclip and `labels.browse`. The set draws its rows and tiles itself from `value` and `queue`; the `Item` parts are for a row it does not draw (a download button, a report being prepared).
70
+ The built-in zone body (icon, `labels.title` and a decorative soft Button; formats and limits go in `hint`) renders when there are no children; the built-in button content is the paperclip and `labels.browse`. The set draws its rows and tiles itself from `value` and `queue`; the `Item` parts are for a row it does not draw (a download button, a report being prepared).
71
71
 
72
72
  **One set of files.** The files are application data: `value` (`{ id, name, size?, type?, src?, alt?, href? }`, controlled, or `defaultValue`) in its order, then every upload of `queue` that is not in it, in the queue's order. A file of `value` whose `id` is an upload's id shows that upload's state. `type` is the MIME type (without it the kind — picture, video, other — is read from the name's extension); `src` is a picture or a video; `alt` is spoken in place of the name and never drawn. `href` is the file's address: the row's name (a neutral `LinkButton`) or the whole tile under its chips becomes a link that opens the file in a new tab — the server's `Content-Disposition` decides open or download. Every change — a reorder by drag or Alt + ← / →, a removal, files added to the queue — goes through `onValueChange` with the whole new list, uploads included; the queue follows (a removed upload leaves it and stops, a moved one keeps its order through `queue.move`). Without `value` and `defaultValue` the set is the queue alone: an uncontrolled list of uploads that reorders through `queue.move`. Return a promise from `onValueChange` to save on a server: the touched files wait busy, a moved file or new files stand in their new places meanwhile, a removed file stays until the answer. Resolve and the change stands (a controlled owner applies `value` before resolving); reject and the set goes back and `labels.changeFailed` is announced.
73
73
 
@@ -127,7 +127,7 @@ A rejection of `upload` fails the file with its message as the reason; retry que
127
127
  | `disabled` | `boolean` | `false` | Blocks picking and drag-and-drop; the files are read-only: no add tile, no reorder, no remove. |
128
128
  | `accept` | `string` | — | Native `accept` of the file input; dropped files outside it are not taken (MIME type, `type/*` or `.ext`; a file with no MIME type by its extension). |
129
129
  | `multiple` | `boolean` | — | Allows several files at once in the picker. |
130
- | `max` | `number` | — | Most files in the set: picked and dropped files are cut to the room left; the add tile counts `count из max` (the digits roll) and at the limit says «Лимит файлов» and takes nothing. |
130
+ | `max` | `number` | — | Most files in the set: picked and dropped files are cut to the room left; at the limit the add tile closes up in place and comes back once a file leaves. The limit a person should know goes in `hint`. |
131
131
  | `name` | `string` | — | Name of the file input inside a form. |
132
132
  | `onFilesChange` | `(files: File[]) => void` | — | Called with the picked or dropped files (of `accept`, cut to `max`); the input is reset, so the same file can be picked again. Not called when `queue.add` takes them. |
133
133
  | `picker` | `Ref<{ open: (options?: { accept?: string }) => void }>` | — | Opens the file picker from code (a custom body button, a menu item): `open()`, or `open({ accept })` for one pick of other types that also filter its files. A disabled or full field does not open. |
@@ -250,7 +250,7 @@ The upload queue of FileUpload: `concurrency` files go at once through `upload`,
250
250
  |---|---|---|---|
251
251
  | `dropzone` | the drop area with an icon, a title, a description and a decorative browse button, the files under it | a standalone upload field of a form; documents | yes |
252
252
  | `button` | a kit Button of the tier, no zone and no drop: soft with the paperclip and text, ghost when its content is a `Button.Icon` alone | a composer's paperclip, an «attach» action in a toolbar or a dense form; pair it with `FileUpload.Queue` when the files live elsewhere | |
253
- | `tile` | a dashed add tile — «+», «Добавить», `count из max` — as the last cell of the files; the whole row while empty; files drop anywhere on the set | file cards and the image gallery: files are added where they will lie | |
253
+ | `tile` | a dashed add tile — «+», «Добавить» — as the last cell of the files; the whole row while empty; files drop anywhere on the set | file cards and the image gallery: files are added where they will lie | |
254
254
  | `none` | nothing of its own: a hidden file input that `picker.open({ accept })` clicks, no drop | a menu of attachment kinds whose items each open the picker with their own types (`Composer.Attach` with `Composer.Item`) | |
255
255
 
256
256
  ### variant (FileUpload.Root)
@@ -299,7 +299,7 @@ The upload queue of FileUpload: `concurrency` files go at once through `upload`,
299
299
  ### Flags
300
300
  | Value | Looks like | Use when | Default |
301
301
  |---|---|---|---|
302
- | `max` | the add tile counts `count из max` (the digits roll); at the limit it says «Лимит файлов» and takes nothing | the set has a limit | — |
302
+ | `max` | at the limit the add tile closes up in place and comes back once a file leaves; the limit is stated in `hint` | the set has a limit | — |
303
303
  | `invalid` (Root) | danger line and icon | a rejected file without a message; otherwise pass `error` | — |
304
304
  | `invalid` (Item) | danger soft fill, danger ring, danger description | an upload failed | `false` |
305
305
  | `disabled` | disabled fill and text, drops ignored; the files read-only | uploading is unavailable, a published set | `false` |
@@ -322,7 +322,7 @@ The upload queue of FileUpload: `concurrency` files go at once through `upload`,
322
322
  | disabled | `disabled` | `data-disabled="true"`, disabled input, disabled fill and text, drops ignored; no add tile, actions or drag; a disabled button with `trigger="button"` |
323
323
  | empty | no files, `trigger="tile"` | the add tile spans the row with «Нажмите или перетащите файлы» |
324
324
  | cover | `cover`, the first tile | «Обложка» in place of the format pill, `data-cover` on the tiles wrapper (the first tile takes a 2 × 2 cell in a grid) |
325
- | full | the set reaches `max` | `data-full` on the add tile: «Лимит файлов», faded; the file input is `aria-disabled` and opens no picker, so the field label stays undimmed |
325
+ | full | the set reaches `max` | the add tile (and its file input) closes up in place; focus that was on it moves to the last file; it enters again once a file leaves |
326
326
  | queued | queue item `status: "queued"` | a row reads «size · В очереди» over an empty bar; a tile (`data-upload="queued"`) dims under a 40px disk on the `media` shade (Button `shape="circle"` `variant="shade"`) whose ring turns at its least arc, described by «В очереди» |
327
327
  | loading | `status: "loading"` | a row reads «size · Загрузка 40%» and its bar fills (`accent`); a tile's white arc grows clockwise from 12 o'clock with the share sent (the `progress` ring of a shade circle Button, one variable `--ring-value`, gliding `base` while it turns); the disk is the remove («Отменить загрузку: name», described by the state), the corner × is not drawn; the tile still reorders, its carried copy keeps the disk |
328
328
  | done | `status: "done"` | a row reads «size» over a full `success` bar; a tile's ring fills, then its disk fades and shrinks to 0.9 (`fast`, `inert`), the picture returns to full or the sheet comes back |
@@ -379,15 +379,11 @@ Also `data-size`, `data-variant` on the zone, `data-size` on Item, `data-status`
379
379
  | Key | Default | Used for |
380
380
  |---|---|---|
381
381
  | `title` | `"Выберите файл или перетащите его сюда"` | Title of the built-in zone body. |
382
- | `description` | `"JPEG, PNG, PDF, MP4 до 50 МБ"` | Description of the built-in body; an empty string hides it. |
383
382
  | `browse` | `"Выбрать файл"` | Text of the browse button: decorative in the built-in zone body, the control itself with `trigger="button"`. |
384
383
  | `optional` | `"необязательно"` | Marker after the label when `optional`. |
385
384
  | `add` | `"Добавить"` | Text of the add tile (`trigger="tile"`). |
386
385
  | `addLabel` | `"Добавить файлы"` | Accessible name of the add tile's file input. |
387
386
  | `empty` | `"Нажмите или перетащите файлы"` | Text of the add tile of an empty set (it spans the row). |
388
- | `emptyHint` | `"Можно несколько сразу"` | Second line of the empty add tile without `max` (with it the counter stands there). |
389
- | `full` | `"Лимит файлов"` | Text of the add tile when `max` is reached. |
390
- | `count` | `"{count} из {max}"` | Counter on the add tile (with `max`). |
391
387
  | `cover` | `"Обложка"` | Pill of the first tile with `cover`. |
392
388
  | `queued` | `"В очереди"` | A file waiting for a free slot (a row's status, a tile's name and its empty ring). |
393
389
  | `uploading` | `"Загрузка {percent}%"` | A file in flight. |
@@ -415,7 +411,7 @@ Also `data-size`, `data-variant` on the zone, `data-size` on Item, `data-status`
415
411
  | [sizes.tsx](examples/sizes.tsx) | Every tier of the zone and of its file rows: padding, icon, button, text and the format badge follow it — `size`. |
416
412
  | [states.tsx](examples/states.tsx) | A default zone, a disabled one whose files are read-only and an invalid one without a message — `disabled`, `invalid`. |
417
413
  | [validation.tsx](examples/validation.tsx) | A required scan checked as soon as it is picked: a wrong type or a large file shakes in an error that leaves when the file is removed; a hint and an optional zone — `required`, `hint`, `error`, `optional`. |
418
- | [gallery.tsx](examples/gallery.tsx) | An image gallery of an office listing: the first photo is the cover on two tiles and a photo dragged first takes its place, new photos upload in their tiles, one fails and retries, and the add tile counts up to the limit and then says so — `trigger`, `layout`, `cover`, `max`, `value`, `queue`. |
414
+ | [gallery.tsx](examples/gallery.tsx) | An image gallery of an office listing: the first photo is the cover on two tiles and a photo dragged first takes its place, new photos upload in their tiles, one fails and retries, and the add tile leaves at the limit — `trigger`, `layout`, `cover`, `max`, `value`, `queue`. |
419
415
  | [file-cards.tsx](examples/file-cards.tsx) | Materials of a warehouse inspection as cards: photos and documents in one grid, each with its picture or format and the name with the size in megabytes as the report gives it; new files upload in their cards and the order is set by drag or Alt + arrows — `trigger`, `layout`, `defaultValue`, `queue`, `formatSize`. |
420
416
  | [attachment-tray.tsx](examples/attachment-tray.tsx) | A reply to a client with a paperclip instead of a drop zone: the icon-only picker button, and the attachments as one scrolling row of tiles above the message — `trigger`, `aria-label`, `FileUpload.Queue`, `layout`. |
421
417
  | [saving.tsx](examples/saving.tsx) | Published listing photos with no picker, every change saved on the server: a moved or removed photo waits busy, and a change the server refuses puts the set back — `FileUpload.Queue`, `onValueChange`, `value`, `cover`. |
@@ -431,7 +427,7 @@ Also `data-size`, `data-variant` on the zone, `data-size` on Item, `data-status`
431
427
  - A `Label` and a `Hint` placed around the zone by hand → pass `label`, `hint`, `error`.
432
428
  - Nesting `FileUpload.Root` in another `<label>` → it already is one.
433
429
  - A plain `<button>` in a custom body expecting it to open the picker → call `picker.current?.open()` in its `onClick` (`open({ accept })` for one pick of other types); an inline «browse» link is `<LinkButton asChild><button type="button" onClick={…}>`.
434
- - `labels.hint` → the built-in secondary line is `labels.description`; `hint` is the field hint under the zone.
430
+ - Formats and size limits written into the zone → the zone states none of its own; they go in `hint`, under the zone.
435
431
  - A hand-written upload loop with its own progress, retry and abort per row → `useUploadQueue` + `queue`.
436
432
  - A zone recolored into a button for a paperclip → `trigger="button"`.
437
433
  - An image gallery — a grid of photos, an add tile, a cover and drag — written by hand, or looked for as a separate component → it is this one: `trigger="tile"`, `layout="grid"`, `cover`.
@@ -1,4 +1,4 @@
1
- /** An image gallery of an office listing: the first photo is the cover on two tiles and a photo dragged first takes its place, new photos upload in their tiles, one fails and retries, and the add tile counts up to the limit and then says so — `trigger`, `layout`, `cover`, `max`, `value`, `queue`. */
1
+ /** An image gallery of an office listing: the first photo is the cover on two tiles and a photo dragged first takes its place, new photos upload in their tiles, one fails and retries, and the add tile leaves at the limit — `trigger`, `layout`, `cover`, `max`, `value`, `queue`. */
2
2
  import { FileUpload, type FileUploadFile, type UploadContext, useUploadQueue } from "prime-ui-kit";
3
3
  import * as React from "react";
4
4
 
@@ -88,7 +88,7 @@ export default function FileUploadGalleryExample() {
88
88
  <div className={styles.wide}>
89
89
  <FileUpload.Root
90
90
  label="Фотографии помещения"
91
- hint="JPG или PNG, первое фото — обложка в каталоге"
91
+ hint="JPG или PNG, до 6 фото; первое фото — обложка в каталоге"
92
92
  trigger="tile"
93
93
  layout="grid"
94
94
  cover
@@ -4,9 +4,8 @@ import * as React from "react";
4
4
 
5
5
  import styles from "./examples.module.css";
6
6
 
7
- const HINT = "Все страницы, с подписями обеих сторон";
8
- /** The zone names the formats the check takes. */
9
- const ZONE_LABELS = { description: "PDF до 20 МБ" };
7
+ /** The hint names the formats the check takes. */
8
+ const HINT = "PDF до 20 МБ, все страницы с подписями обеих сторон";
10
9
  const MAX_SIZE = 20 * 1024 * 1024;
11
10
 
12
11
  /** A picked file as a file of the set. */
@@ -52,7 +51,6 @@ export default function FileUploadInFormExample() {
52
51
  accept=".pdf"
53
52
  required
54
53
  hint={HINT}
55
- labels={ZONE_LABELS}
56
54
  error={error}
57
55
  value={scan}
58
56
  onValueChange={change}
@@ -58,6 +58,7 @@ export default function FileUploadNarrowExample() {
58
58
  <FileUpload.Root label="Чек об оплате" defaultValue={RECEIPT} />
59
59
  <FileUpload.Root
60
60
  label="Фотографии скутера"
61
+ hint="До 10 фото"
61
62
  trigger="tile"
62
63
  layout="grid"
63
64
  cover
@@ -5,9 +5,6 @@ import styles from "./examples.module.css";
5
5
 
6
6
  const MAX_SIZE = 10 * 1024 * 1024;
7
7
 
8
- /** The zone names the limit the stand-in server holds. */
9
- const ZONE_LABELS = { description: "Любые файлы до 10 МБ" };
10
-
11
8
  const ATTACHED: readonly FileUploadFile[] = [
12
9
  {
13
10
  id: "deal-contract",
@@ -53,8 +50,7 @@ export default function FileUploadOverviewExample() {
53
50
  <div className={styles.column}>
54
51
  <FileUpload.Root
55
52
  label="Документы сделки"
56
- hint="Договор, смета и акты по сделке; можно несколько сразу"
57
- labels={ZONE_LABELS}
53
+ hint="Договор, смета и акты по сделке; любые файлы до 10 МБ"
58
54
  multiple
59
55
  defaultValue={ATTACHED}
60
56
  queue={queue}
@@ -16,7 +16,7 @@ export default function FileUploadSizesExample() {
16
16
  size={size}
17
17
  label={size}
18
18
  defaultValue={CONTRACT}
19
- labels={{ title: "Перетащите файл", description: "" }}
19
+ labels={{ title: "Перетащите файл" }}
20
20
  />
21
21
  ))}
22
22
  </div>
@@ -1,8 +1,8 @@
1
1
  /** A default zone, a disabled one whose files are read-only and an invalid one without a message — `disabled`, `invalid`. */
2
2
  import { FileUpload, type FileUploadFile } from "prime-ui-kit";
3
3
 
4
- /** The zone names the formats the order takes. */
5
- const ZONE_LABELS = { description: "PDF или PNG до 20 МБ" };
4
+ /** The hint names the formats the order takes. */
5
+ const HINT = "PDF или PNG до 20 МБ";
6
6
 
7
7
  const ACT: readonly FileUploadFile[] = [
8
8
  { id: "act", name: "Акт сверки.pdf", size: 310_000, type: "application/pdf" },
@@ -11,15 +11,14 @@ const ACT: readonly FileUploadFile[] = [
11
11
  export default function FileUploadStatesExample() {
12
12
  return (
13
13
  <div>
14
- <FileUpload.Root label="default" labels={ZONE_LABELS} />
14
+ <FileUpload.Root label="default" hint={HINT} />
15
15
  <FileUpload.Root
16
16
  label="disabled"
17
17
  disabled
18
- labels={ZONE_LABELS}
19
18
  defaultValue={ACT}
20
19
  hint="Заказ закрыт, документы только для просмотра"
21
20
  />
22
- <FileUpload.Root label="invalid" invalid labels={ZONE_LABELS} />
21
+ <FileUpload.Root label="invalid" invalid hint={HINT} />
23
22
  </div>
24
23
  );
25
24
  }
@@ -6,9 +6,6 @@ import styles from "./examples.module.css";
6
6
 
7
7
  const MAX_SIZE = 20 * 1024 * 1024;
8
8
 
9
- /** The zone names the formats the check takes. */
10
- const ZONE_LABELS = { description: "PDF до 20 МБ" };
11
-
12
9
  /** A picked file as a file of the set. */
13
10
  const toFile = (file: File): FileUploadFile => ({
14
11
  id: `${file.name}-${file.size}-${file.lastModified}`,
@@ -33,8 +30,7 @@ export default function FileUploadValidationExample() {
33
30
  <FileUpload.Root
34
31
  label="Скан договора"
35
32
  required
36
- hint="Все страницы, с подписями обеих сторон"
37
- labels={ZONE_LABELS}
33
+ hint="PDF до 20 МБ, все страницы с подписями обеих сторон"
38
34
  error={scanError(scan)}
39
35
  value={scan}
40
36
  onValueChange={setScan}
@@ -43,8 +39,7 @@ export default function FileUploadValidationExample() {
43
39
  <FileUpload.Root
44
40
  label="Доверенность"
45
41
  optional
46
- hint="Если подписывает не директор"
47
- labels={ZONE_LABELS}
42
+ hint="PDF до 20 МБ, если подписывает не директор"
48
43
  />
49
44
  </div>
50
45
  );
@@ -96,6 +96,7 @@ The trailing side has a fixed CSS `order`, independent of JSX order: value · en
96
96
  | `onValueChange` | `(value: string) => void` | — | Called with the new string; native `onChange` still fires first. |
97
97
  | `aria-describedby` | `string` | — | Merged with the affix and hint/error ids from the root. |
98
98
  | `required` | `boolean` | — | Overrides the root's `required` for the native input. |
99
+ | `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
99
100
  | `…rest` | `Omit<InputHTMLAttributes<HTMLInputElement>, "size" \| "id">` | — | `value`, `defaultValue`, `onChange`, `type`, `disabled`, `readOnly`, `maxLength`, `placeholder`… |
100
101
 
101
102
  ### Input.Icon
@@ -272,6 +273,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
272
273
  - `minStrength` uses native constraint validation: below the level the input's `validationMessage` is `labels.strengthTooWeak`, the form refuses to submit, and the message becomes the field's error (`aria-invalid`).
273
274
  - `Input.Icon` is decorative (`aria-hidden`). `Input.Affix` and `Input.InlineAffix` are `aria-hidden` in the reading order but describe the input (`aria-describedby`, before the hint): the unit is heard with the field, so the label never repeats it. An `aria-label` on the affix is said instead of its text («рублей» for `₽`).
274
275
  - `suggestions` make the input a `combobox` (`aria-autocomplete="list"`, `aria-expanded`, `aria-controls` on the open `listbox`, which the label names); the highlight is `aria-activedescendant`, so focus never leaves the field.
276
+ - Autofill is off unless you pass `autoComplete`: without a token the input carries `autocomplete="off"` and the opt-outs of 1Password, LastPass, Bitwarden and Dashlane (`data-1p-ignore`, `data-lpignore`, `data-bwignore`, `data-form-type="other"`); a token passes through and drops them. With `suggestions` it stays off. Inside `LoginForm.Form` a field without a token is left to the browser.
275
277
  - A `Select` inside `Input.Wrapper` is the box's start segment: its trigger is named by the select root's `aria-label` and the picked value («Иконка: Звезда»).
276
278
  - `Input.ClearButton` is a real button named by `labels.clear`, with `aria-controls` on the input.
277
279
  - `Input.Action` and `Input.CopyButton` are buttons (or a link with `asChild`) named by their label, with a tooltip; copying announces `labels.copied` through a polite live region.
@@ -338,6 +340,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
338
340
  - A Select and an Input side by side for one value (an icon + a name) → the Select inside `Input.Wrapper`, named by its `aria-label`.
339
341
  - A text field with a hand-made time mask, or a Datepicker for a time of day → `Input.Field type="time"`.
340
342
  - Returning `false` from `onCommit` to refuse → reject the Promise (throw) and set `error`.
343
+ - `autoComplete="email"` / `"name"` on a search, filter or record field → leave it out: autofill is off unless you pass `autoComplete`; tokens belong on sign-in and sign-up fields (`LoginForm.Form`) and on a new password (`new-password`).
341
344
 
342
345
  ## Related
343
346
  - **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (`Input.Action`, `Input.CopyButton`), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [ProgressBar](../progress-bar/COMPONENT.md) (`strength`), `Icon` (segments, the `valid` check), [VisuallyHidden](../visually-hidden/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md) (the `suggestions` list)
@@ -40,7 +40,6 @@ export default function InputInFormExample() {
40
40
  name="name"
41
41
  onValueChange={() => edit("name")}
42
42
  defaultValue="ООО «Северный ветер»"
43
- autoComplete="organization"
44
43
  />
45
44
  </Input.Wrapper>
46
45
  </Input.Root>
@@ -28,7 +28,7 @@ export default function InputValidationExample() {
28
28
  <>
29
29
  <Input.Root label="Название организации" required hint="Как в учредительных документах">
30
30
  <Input.Wrapper>
31
- <Input.Field defaultValue="ООО «Северный ветер»" autoComplete="organization" />
31
+ <Input.Field defaultValue="ООО «Северный ветер»" />
32
32
  </Input.Wrapper>
33
33
  </Input.Root>
34
34
  <Input.Root
@@ -159,7 +159,7 @@ Do not wrap a `plain` board's columns in your own boxes, and do not put a `well`
159
159
  | refused | `limit` reached, `canDrop` returns `false` or a reason | `data-dnd-reject` on the `<ul>`: `danger-soft` fill; the reason in a chip under the carried card |
160
160
  | locked column | `locked` | `data-locked` on the column; the grip is a lock named «Закреплено» |
161
161
  | full | count ≥ `limit` | `data-full` on the column, orange count `count / limit` |
162
- | empty | no cards | a compact `EmptyPage` in Dnd's dashed drop slot at the top (`fill-strong` dashed line, no fill): «Нет карточек» and the hint «Перетащите сюда карточку»; a `disabled` board shows the text alone; the whole column stays a drop area |
162
+ | empty | no cards | a compact `EmptyPage` in Dnd's dashed drop slot at the top (`fill-strong` dashed line, no fill): «Нет карточек»; the whole column stays a drop area |
163
163
  | loading | `loading` | `aria-busy` and `data-loading` on the root; `Skeleton` cards; `Crossfade` swaps them for the cards |
164
164
  | loading, no columns yet | `loading` with empty `columns` | an `aria-hidden` row of `loadingColumns` placeholder columns; the columns fade in where they stood (`enterMotion`) |
165
165
  | disabled | `disabled` / `Kanban.Item disabled` | `data-disabled` on the root / card; no `aria-roledescription`, no shortcuts, no column grips |
@@ -217,7 +217,6 @@ A card's own `onKeyDown` runs before the board's: `preventDefault()` in it keeps
217
217
  | `count` | `"Карточек: {count}"` | Spoken name of the count badge. |
218
218
  | `countLimit` | `"Карточек: {count} из {limit}"` | Spoken name of the count badge of a column with a `limit`. |
219
219
  | `empty` | `"Нет карточек"` | Text of an empty column. |
220
- | `emptyHint` | `"Перетащите сюда карточку"` | Hint under the empty-column text, while cards can be dropped (not on a `disabled` board). |
221
220
  | `full` | `"Колонка «{column}» заполнена ({limit})"` | Why a full column refuses a card: spoken, and shown under the carried card. |
222
221
  | `moveColumn` | `"Переместить колонку «{column}»"` | Name of the column grip (`reorderableColumns`). |
223
222
  | `column` | `"Колонка «{column}»"` | A column as the live region names it when it is carried or moved («Колонка «Демо»: позиция 2 из 4»). |
@@ -8,7 +8,7 @@
8
8
  ## When to use
9
9
  - The list side of a list + detail screen (`Page.Split` → `Page.List`): tickets, letters, invoices worked through one by one; the open one is `current`.
10
10
  - A short column of records that each open something — a page (`href`, `asChild` with a router link) or a detail pane (`onClick`).
11
- - A short list the team puts in order (funnel stages, queues, priorities) — `onReorder`, a `value` per item.
11
+ - A short list the team puts in order (funnel stages, queues, priorities) — `onReorder`, a `value` per item; rows the system fixes stay in the same list with `locked`.
12
12
  - Rows with controls of their own beside the row that opens the record (a stage shown on the board, a ⋯ menu) — `List.ItemActions`.
13
13
 
14
14
  ## When not to use
@@ -67,6 +67,7 @@ List.Root <ul aria-label>; size (else the host tier) → row t
67
67
  | `rel` | `string` | — | Relationship of the `<a>` (`href` only). |
68
68
  | `asChild` | `boolean` | `false` | Renders the single child (a router link) as the row; its children are the content. |
69
69
  | `disabled` | `boolean` | `false` | Muted and out of the Tab order: a disabled button, or `aria-disabled` and no navigation on a link. |
70
+ | `locked` | `boolean` | `false` | With `onReorder`: the row holds its place (a system stage) and shows a lock in the grip's box; no row moves past it. The row and its `List.ItemActions` work as on any row. |
70
71
  | `children` | `ReactNode` | — | `List.ItemIcon`, `List.ItemText`, `List.ItemDescription`, `List.ItemMeta`, `List.ItemCount`, `List.ItemActions`; loose text becomes the text line. |
71
72
  | `…rest` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | `className` and native attributes of the row element (`target`, `rel` on a link). |
72
73
 
@@ -155,6 +156,7 @@ A second line or an avatar makes a row taller than the one-line height; the vert
155
156
  | focus-visible | keyboard | the focus ring inside the row edge |
156
157
  | disabled | `disabled` | `disabled` on a button; `aria-disabled="true"`, no `href` on a link, `tabindex="-1"` on an `asChild` element; `data-disabled="true"`; disabled text; in a reorderable list the handle is disabled and the row holds its place |
157
158
  | reorderable | `onReorder` | `data-reorderable="true"` on the root (a `Dnd.Sortable` `<ul>`); each `<li>` is a sortable item (`data-dnd-item` = `value`) with a `Dnd.Handle` at its end; drag by the handle (a touch hold on touch), Alt + ↑ / ↓ from the handle or the row; a lifted row leaves a dashed gap in the row's radius, its neighbours glide, the copy (handle and row, in the row's padding and radius) follows the pointer and sets down in its new place without a jump |
159
+ | locked | `locked` (with `onReorder`) | `data-locked` on the `<li>`; the Dnd lock (`role="img"`, «{label}: закреплено») in the grip's box, so the columns line up with the movable rows; the row does not drag or move by Alt + arrows, and no other row is put where it would shift it — a keyboard step over it is refused aloud; the row element and its `List.ItemActions` work as on any row |
158
160
  | saving a move | `onReorder` returned a pending promise | the row stays in its new place, `aria-busy="true"`, the handle is busy (Dnd: a spinner in the disabled grip), the row content dims to 0.6 (`--prime-motion-fade-fast` both ways); a rejection glides the row back |
159
161
  | own controls | `List.ItemActions` in the item | the row element and the controls stand in one frame that takes the hover, press and current fills for both; the element itself stays clear; `side="start"` controls come before the element in the frame, `data-side` on the actions |
160
162
  | rows come and go | the data adds or takes out a `List.Item` (`value`, else its key) | a new row rises in once (`enterMotion`), a removed one closes up in place (`listPresence`); the first render stays still |
@@ -168,7 +170,8 @@ Root attributes: `data-size`, `role="list"` (the list style reset would drop the
168
170
  - Text and description truncate with an ellipsis; give a long `List.ItemText` a `title` with the full text.
169
171
  - On a coarse pointer a row is at least the touch target (44px).
170
172
  - Reorderable (`onReorder`): an `xs` handle box ends each row, 4px after the row element and its `List.ItemActions` (inline-end: on the left in RTL); the row keeps its own padding and fill, so every row's handle stands on one line.
171
- - `List.ItemActions`: the controls one tier below the row, 4px apart, as far from the row's edge as a control box of that tier stands from its top and bottom — at any row height: one line, with `List.ItemDescription`, with an avatar, the touch row. The end edge, or the start edge with `side="start"`, where the row's own padding then parts the controls from its icon or text.
173
+ - `List.ItemActions`: the controls one tier below the row, 4px apart, as far from the row's edge as a control box of that tier stands from its top and bottom — at any row height: one line, with `List.ItemDescription`, with an avatar, the touch row. The row's height is measured onto it (a ResizeObserver), so the inset never rests on a percentage height that engines resolve differently. The end edge, or the start edge with `side="start"`, where the row's own padding then parts the controls from its icon or text.
174
+ - A locked row keeps every column of the movable rows: its `List.ItemActions` on the same side, the lock in the grip's box. System stages and the team's stages are one list, not two.
172
175
  - One toggle place per row: a plain row ends with its toggle (label first); a reorderable row leads with it (`side="start"`) and ends with the grip — never a toggle between the label and the grip.
173
176
  - A colour dot in `List.ItemIcon` is the large `Badge.Dot` (8px) centred in an icon-wide box, so the text lines up with the rows that carry icons.
174
177
 
@@ -181,7 +184,7 @@ Root attributes: `data-size`, `role="list"` (the list style reset would drop the
181
184
  |---|---|
182
185
  | `Tab` | Moves focus between rows; a disabled row is skipped. |
183
186
  | `Enter` · `Space` | Opens the record; a link row takes Enter only. |
184
- | `Alt + ↑` · `Alt + ↓` | With `onReorder`, moves the row one place up or down (from its handle or the row). |
187
+ | `Alt + ↑` · `Alt + ↓` | With `onReorder`, moves the row one place up or down (from its handle or the row); a `locked` row does not move, and a step over one is refused and announced. |
185
188
 
186
189
  ### ARIA
187
190
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
@@ -191,7 +194,7 @@ Root attributes: `data-size`, `role="list"` (the list style reset would drop the
191
194
  - The row's name is its text, description, meta and count, in that order; the leading icon or avatar is `aria-hidden` — the text names the row.
192
195
  - Arrow keys do not move between rows: rows are ordinary Tab stops, as in the Sidebar.
193
196
  - `List.ItemActions` stand beside the row element, never inside it: the row's name stays its text. Tab follows the row as it reads: the `side="start"` controls, the row, the `side="end"` controls.
194
- - With `onReorder` each row gets a handle named «Переместить {label}» (`labels.reorderItem`, `{label}` is the row's text line) and `aria-keyshortcuts`, last in the row: Tab reaches the row with its `List.ItemActions`, then the handle; the new place is announced politely («Новый лид: позиция 2 из 5»), a row being saved is `aria-busy`.
197
+ - With `onReorder` each row gets a handle named «Переместить {label}» (`labels.reorderItem`, `{label}` is the row's text line) and `aria-keyshortcuts`, last in the row: Tab reaches the row with its `List.ItemActions`, then the handle; the new place is announced politely («Новый лид: позиция 2 из 5»), a row being saved is `aria-busy`. A `locked` row ends with a lock instead: `role="img"` named «{label}: закреплено» (`labels.lockedItem`); a keyboard step over it is refused aloud («Переговоры: «Новая» закреплено, дальше не переместить», Dnd `labels.blocked`).
195
198
 
196
199
  ### Labels
197
200
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
@@ -199,6 +202,7 @@ Root attributes: `data-size`, `role="list"` (the list style reset would drop the
199
202
  | Key | Default | Used for |
200
203
  |---|---|---|
201
204
  | `reorderItem` | `"Переместить {label}"` | `aria-label` of a row's drag handle (`onReorder`); `{label}` is the row's text line. |
205
+ | `lockedItem` | `"{label}: закреплено"` | Name of the lock a `locked` row shows in the grip's place; `{label}` is the row's text line. |
202
206
 
203
207
  ## Examples
204
208
  | Example | Shows |
@@ -209,7 +213,7 @@ Root attributes: `data-size`, `role="list"` (the list style reset would drop the
209
213
  | [with-icon.tsx](examples/with-icon.tsx) | Leading avatars of clients, icons of queues and colour dots of projects; an avatar takes the row tier, a dot stands in an icon-wide box — `List.ItemIcon`, `Badge.Dot`. |
210
214
  | [overflow.tsx](examples/overflow.tsx) | A long inbox in a pane of a fixed height: the list scrolls in a ScrollContainer and long names and previews truncate — `List.ItemText`, `List.ItemDescription`. |
211
215
  | [as-link.tsx](examples/as-link.tsx) | Rows as links to the invoice pages: a plain link, or your router link through the slot — `href`, `asChild`. |
212
- | [reorder.tsx](examples/reorder.tsx) | Funnel stages in the team's order: a stage moves by its handle or Alt+arrows and is saved on the server; its switch leads the row, the grip ends it — `onReorder`, `value`, `side`. |
216
+ | [reorder.tsx](examples/reorder.tsx) | Funnel stages in one list: the team's stages move by the handle or Alt+arrows and are saved on the server, the system ones hold their places with a lock where the grip is; the switch leads every row, the grip ends it — `onReorder`, `value`, `locked`, `side`. |
213
217
  | [row-controls.tsx](examples/row-controls.tsx) | Funnel stages with their own controls: a colour dot names the stage's hue, a switch shows it on the board and a menu edits it, all beside the row that opens the stage — `List.ItemActions`, `Badge.Dot`. |
214
218
 
215
219
  ## Mistakes
@@ -221,9 +225,10 @@ Root attributes: `data-size`, `role="list"` (the list style reset would drop the
221
225
  - Up / down buttons on rows or a hand-made `Dnd.Sortable` of rows to order them → `List.Root onReorder` with a `value` on every item.
222
226
  - `onReorder` that rolls the order back itself on an error → return the promise; a rejection moves the row back.
223
227
  - A Switch or a menu button put inside a row, or a row rebuilt as a `div` to hold one → `List.ItemActions` beside the row element.
228
+ - System stages in a list of their own above or below the movable ones, or `disabled` to pin them (it dims the row and lets neighbours pass) → one list, `locked` on the system rows.
224
229
  - A Switch at the end of a reorderable row, between its label and the grip → `List.ItemActions side="start"`; the grip keeps the end.
225
230
  - A hollow `Icon` circle or a hand-drawn dot as a stage colour → `Badge.Dot color` in `List.ItemIcon`.
226
231
 
227
232
  ## Related
228
- - **Built from:** [Badge](../badge/COMPONENT.md) (`List.ItemCount`; `Badge.Dot` in `List.ItemIcon`), [Avatar](../avatar/COMPONENT.md) (`List.ItemIcon` gives an avatar the row tier), [Dnd](../dnd/COMPONENT.md) (`onReorder`: `Dnd.Sortable`, the busy `Dnd.Handle`)
233
+ - **Built from:** [Badge](../badge/COMPONENT.md) (`List.ItemCount`; `Badge.Dot` in `List.ItemIcon`), [Avatar](../avatar/COMPONENT.md) (`List.ItemIcon` gives an avatar the row tier), [Dnd](../dnd/COMPONENT.md) (`onReorder`: `Dnd.Sortable`, the busy `Dnd.Handle`, the lock of a `locked` row)
229
234
  - **See also:** [Page](../../layout/page/COMPONENT.md), [Sidebar](../../layout/sidebar/COMPONENT.md), [Tabs](../tabs/COMPONENT.md), [DataTable](../data-table/COMPONENT.md), [Card](../card/COMPONENT.md)
@@ -1,16 +1,25 @@
1
- /** Funnel stages in the team's order: a stage moves by its handle or Alt+arrows and is saved on the server; its switch leads the row, the grip ends it — `onReorder`, `value`, `side`. */
1
+ /** Funnel stages in one list: the team's stages move by the handle or Alt+arrows and are saved on the server, the system ones hold their places with a lock where the grip is; the switch leads every row, the grip ends it — `onReorder`, `value`, `locked`, `side`. */
2
2
  import { Card, List, moveBefore, Switch } from "prime-ui-kit";
3
3
  import * as React from "react";
4
4
 
5
5
  import styles from "./examples.module.css";
6
6
 
7
7
  const STAGES = [
8
+ {
9
+ id: "new",
10
+ name: "Новая",
11
+ description: "Сюда попадает каждая заявка",
12
+ deals: 57,
13
+ shown: true,
14
+ system: true,
15
+ },
8
16
  {
9
17
  id: "lead",
10
18
  name: "Новый лид",
11
19
  description: "Заявка с сайта или от партнёра",
12
20
  deals: 42,
13
21
  shown: true,
22
+ system: false,
14
23
  },
15
24
  {
16
25
  id: "qualified",
@@ -18,6 +27,7 @@ const STAGES = [
18
27
  description: "Бюджет и сроки подтверждены",
19
28
  deals: 18,
20
29
  shown: true,
30
+ system: false,
21
31
  },
22
32
  {
23
33
  id: "proposal",
@@ -25,6 +35,7 @@ const STAGES = [
25
35
  description: "КП отправлено",
26
36
  deals: 11,
27
37
  shown: true,
38
+ system: false,
28
39
  },
29
40
  {
30
41
  id: "contract",
@@ -32,8 +43,24 @@ const STAGES = [
32
43
  description: "Согласование с юристами",
33
44
  deals: 6,
34
45
  shown: true,
46
+ system: false,
47
+ },
48
+ {
49
+ id: "won",
50
+ name: "Успешно",
51
+ description: "Сделка закрыта",
52
+ deals: 24,
53
+ shown: false,
54
+ system: true,
55
+ },
56
+ {
57
+ id: "lost",
58
+ name: "Отказ",
59
+ description: "Сделка не состоялась",
60
+ deals: 9,
61
+ shown: false,
62
+ system: true,
35
63
  },
36
- { id: "won", name: "Оплата", description: "Счёт выставлен", deals: 3, shown: false },
37
64
  ];
38
65
 
39
66
  const SAVE_DELAY_MS = 600;
@@ -59,7 +86,7 @@ export default function ListReorderExample() {
59
86
  }}
60
87
  >
61
88
  {stages.map((stage) => (
62
- <List.Item key={stage.id} value={stage.id}>
89
+ <List.Item key={stage.id} value={stage.id} locked={stage.system}>
63
90
  <List.ItemActions side="start">
64
91
  <Switch.Root
65
92
  aria-label={`Показывать «${stage.name}» на доске`}
@@ -99,7 +99,7 @@ Every part is optional except `Root`, `Title` and `Form`. Fields, buttons, divid
99
99
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | The other div attributes. |
100
100
 
101
101
  ### LoginForm.Form
102
- `ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props.
102
+ `ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props. The one place autofill stays on: a kit field inside it without `autoComplete` is left to the browser and the password managers.
103
103
 
104
104
  | Prop | Type | Default | Description |
105
105
  |---|---|---|---|
@@ -184,7 +184,7 @@ Root carries `data-size`, `data-align` and `data-flat`.
184
184
  - The title is a real heading (`h1` by default) and the form is a native `<form>`: Enter submits.
185
185
  - The logo is decorative; with an `aria-label` it is the product mark image (`role="img"`).
186
186
  - Give `LoginForm.Form` an `aria-label` (or `aria-labelledby` pointing to the title id) so the form is a landmark.
187
- - Fields use `autoComplete` (`email`, `current-password`, `new-password`, `one-time-code`) so password managers and OTP autofill work.
187
+ - Every field names its purpose with `autoComplete` — `email` or `username`, `current-password` to sign in, `new-password` to sign up, reset or accept an invite, `one-time-code` for a code (`DigitInput` sets it) — so password managers fill and save the right field. Kit fields elsewhere keep autofill off.
188
188
  - Field errors come from the fields (`aria-invalid`, described by the error). A refused sign-in has no Banner: the e-mail takes `invalid` and the `error` of the password says why, announced by the support row of the field.
189
189
 
190
190
  ### Labels
@@ -16,12 +16,16 @@ export default function LoginFormSizesExample() {
16
16
  <LoginForm.Form onSubmit={(e) => e.preventDefault()}>
17
17
  <Input.Root label="Email">
18
18
  <Input.Wrapper>
19
- <Input.Field type="email" placeholder="name@company.ru" />
19
+ <Input.Field type="email" autoComplete="email" placeholder="name@company.ru" />
20
20
  </Input.Wrapper>
21
21
  </Input.Root>
22
22
  <Input.Root label="Пароль">
23
23
  <Input.Wrapper>
24
- <Input.Field type="password" placeholder="********" />
24
+ <Input.Field
25
+ type="password"
26
+ autoComplete="current-password"
27
+ placeholder="********"
28
+ />
25
29
  </Input.Wrapper>
26
30
  </Input.Root>
27
31
  <Button.Root type="submit" fullWidth>