prime-ui-kit 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +7 -6
  2. package/dist/chunks/{chunk-ZSJMNA46.js → chunk-P6ZO37JM.js} +1 -1
  3. package/dist/chunks/{chunk-Z3WKKAS6.js → chunk-SYEX7OVT.js} +3572 -2865
  4. package/dist/chunks/{chunk-E6SQBSDF.js → chunk-WXKRUVSE.js} +199 -162
  5. package/dist/color-picker.js +2 -2
  6. package/dist/components/banner/Banner.d.ts +5 -2
  7. package/dist/components/chart-frame/ChartFrame.d.ts +19 -6
  8. package/dist/components/chat/Chat.d.ts +2 -2
  9. package/dist/components/chat/ChatPinned.d.ts +2 -2
  10. package/dist/components/chat/MessageList.d.ts +1 -1
  11. package/dist/components/chat/chatContext.d.ts +1 -1
  12. package/dist/components/field-list/FieldList.d.ts +90 -0
  13. package/dist/components/index.d.ts +3 -1
  14. package/dist/components/input/Input.d.ts +2 -2
  15. package/dist/components/kanban/Kanban.d.ts +6 -3
  16. package/dist/components/page-content/PageContent.d.ts +55 -4
  17. package/dist/components/sparkline/Sparkline.d.ts +64 -15
  18. package/dist/components/sparkline/geometry.d.ts +26 -0
  19. package/dist/components/tooltip/Tooltip.d.ts +5 -2
  20. package/dist/hooks/usePosition.d.ts +15 -4
  21. package/dist/index.css +1 -1
  22. package/dist/index.js +4 -2
  23. package/dist/internal/ChartTooltip.d.ts +26 -0
  24. package/dist/internal/FieldAction.d.ts +6 -7
  25. package/dist/internal/fieldCell.d.ts +21 -0
  26. package/dist/internal/messageList.d.ts +2 -2
  27. package/dist/internal/palette.d.ts +7 -0
  28. package/dist/skill/SKILL.md +24 -14
  29. package/dist/skill/anti-slop.md +77 -4
  30. package/dist/skill/cheatsheet.md +24 -5
  31. package/dist/skill/checklist.md +19 -1
  32. package/dist/skill/choosing.md +10 -5
  33. package/dist/skill/components.md +4 -3
  34. package/dist/skill/composition.md +28 -15
  35. package/dist/skill/foundations.md +8 -6
  36. package/dist/skill/layouts.md +6 -6
  37. package/dist/skill/patterns/app-shell.module.css +1 -8
  38. package/dist/skill/patterns/app-shell.tsx +11 -9
  39. package/dist/skill/patterns/chat.tsx +23 -5
  40. package/dist/skill/patterns/dashboard.module.css +1 -31
  41. package/dist/skill/patterns/dashboard.tsx +150 -136
  42. package/dist/skill/patterns/detail-page.module.css +0 -3
  43. package/dist/skill/patterns/detail-page.tsx +4 -7
  44. package/dist/skill/patterns/form-drawer.tsx +2 -2
  45. package/dist/skill/patterns/kanban-board.tsx +4 -2
  46. package/dist/skill/patterns/list-page.tsx +2 -2
  47. package/dist/skill/patterns/media-library.module.css +0 -22
  48. package/dist/skill/patterns/media-library.tsx +17 -18
  49. package/dist/skill/patterns/record-editor.module.css +18 -0
  50. package/dist/skill/patterns/record-editor.tsx +356 -0
  51. package/dist/skill/patterns/screen-states.module.css +3 -17
  52. package/dist/skill/patterns/screen-states.tsx +70 -69
  53. package/dist/skill/patterns/settings-page.module.css +1 -21
  54. package/dist/skill/patterns/settings-page.tsx +58 -79
  55. package/dist/skill/reference/components/avatar/COMPONENT.md +4 -4
  56. package/dist/skill/reference/components/avatar/examples/presence.tsx +1 -1
  57. package/dist/skill/reference/components/avatar/examples/states.tsx +1 -1
  58. package/dist/skill/reference/components/banner/COMPONENT.md +6 -6
  59. package/dist/skill/reference/components/banner/examples/dismissible.tsx +22 -11
  60. package/dist/skill/reference/components/banner/examples/examples.module.css +3 -3
  61. package/dist/skill/reference/components/banner/examples/narrow.tsx +1 -1
  62. package/dist/skill/reference/components/breadcrumb/COMPONENT.md +1 -1
  63. package/dist/skill/reference/components/breadcrumb/examples/overflow.tsx +1 -1
  64. package/dist/skill/reference/components/card/COMPONENT.md +5 -4
  65. package/dist/skill/reference/components/card/examples/examples.module.css +0 -28
  66. package/dist/skill/reference/components/card/examples/kpi-media.tsx +13 -11
  67. package/dist/skill/reference/components/card/examples/narrow.tsx +1 -1
  68. package/dist/skill/reference/components/card/examples/panel-chart.tsx +45 -22
  69. package/dist/skill/reference/components/chart-frame/COMPONENT.md +10 -7
  70. package/dist/skill/reference/components/chart-frame/examples/tooltip.tsx +20 -24
  71. package/dist/skill/reference/components/chat/COMPONENT.md +3 -3
  72. package/dist/skill/reference/components/chat/examples/search.tsx +1 -1
  73. package/dist/skill/reference/components/chat-list/COMPONENT.md +1 -1
  74. package/dist/skill/reference/components/checkbox/examples/indicator.tsx +24 -2
  75. package/dist/skill/reference/components/color-picker/COMPONENT.md +2 -2
  76. package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
  77. package/dist/skill/reference/components/datepicker/COMPONENT.md +1 -0
  78. package/dist/skill/reference/components/dnd/COMPONENT.md +3 -3
  79. package/dist/skill/reference/components/field-list/COMPONENT.md +164 -0
  80. package/dist/skill/reference/components/field-list/examples/base-row.tsx +76 -0
  81. package/dist/skill/reference/components/field-list/examples/controlled.tsx +72 -0
  82. package/dist/skill/reference/components/field-list/examples/errors.tsx +85 -0
  83. package/dist/skill/reference/components/field-list/examples/examples.module.css +34 -0
  84. package/dist/skill/reference/components/field-list/examples/limits.tsx +55 -0
  85. package/dist/skill/reference/components/field-list/examples/locked.tsx +139 -0
  86. package/dist/skill/reference/components/field-list/examples/narrow.tsx +68 -0
  87. package/dist/skill/reference/components/field-list/examples/overview.tsx +66 -0
  88. package/dist/skill/reference/components/field-list/examples/reorder.tsx +42 -0
  89. package/dist/skill/reference/components/field-list/examples/sizes.tsx +42 -0
  90. package/dist/skill/reference/components/field-list/examples/states.tsx +51 -0
  91. package/dist/skill/reference/components/input/examples/examples.module.css +2 -7
  92. package/dist/skill/reference/components/kanban/COMPONENT.md +13 -9
  93. package/dist/skill/reference/components/kanban/examples/limits.tsx +2 -1
  94. package/dist/skill/reference/components/kanban/examples/variants.tsx +16 -8
  95. package/dist/skill/reference/components/list/COMPONENT.md +1 -1
  96. package/dist/skill/reference/components/media-viewer/COMPONENT.md +2 -2
  97. package/dist/skill/reference/components/message/COMPONENT.md +2 -2
  98. package/dist/skill/reference/components/notification/COMPONENT.md +1 -0
  99. package/dist/skill/reference/components/page-content/COMPONENT.md +70 -14
  100. package/dist/skill/reference/components/page-content/examples/grid.tsx +107 -0
  101. package/dist/skill/reference/components/page-content/examples/overview.tsx +2 -2
  102. package/dist/skill/reference/components/page-content/examples/sections.tsx +81 -0
  103. package/dist/skill/reference/components/page-toolbar/COMPONENT.md +1 -1
  104. package/dist/skill/reference/components/segmented-control/COMPONENT.md +1 -0
  105. package/dist/skill/reference/components/select/COMPONENT.md +4 -2
  106. package/dist/skill/reference/components/select/examples/icon-only.tsx +38 -0
  107. package/dist/skill/reference/components/sparkline/COMPONENT.md +83 -30
  108. package/dist/skill/reference/components/sparkline/examples/compact.tsx +1 -1
  109. package/dist/skill/reference/components/sparkline/examples/curve.tsx +28 -0
  110. package/dist/skill/reference/components/sparkline/examples/gaps.tsx +33 -0
  111. package/dist/skill/reference/components/sparkline/examples/overview.tsx +11 -2
  112. package/dist/skill/reference/components/sparkline/examples/period.tsx +7 -2
  113. package/dist/skill/reference/components/sparkline/examples/reference.tsx +37 -0
  114. package/dist/skill/reference/components/sparkline/examples/variants.tsx +2 -2
  115. package/dist/skill/reference/components/tabs/examples/examples.module.css +0 -6
  116. package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +11 -13
  117. package/dist/skill/reference/components/timeline/examples/examples.module.css +0 -8
  118. package/dist/skill/reference/components/timeline/examples/narrow.tsx +2 -2
  119. package/dist/skill/reference/components/timeline/examples/sizes.tsx +3 -3
  120. package/dist/skill/reference/components/timeline/examples/variants.tsx +4 -4
  121. package/dist/skill/reference/components/tooltip/COMPONENT.md +6 -4
  122. package/dist/skill/reference/components/tooltip/examples/placement.tsx +2 -1
  123. package/dist/skill/reference/components/video-note/COMPONENT.md +1 -1
  124. package/dist/skill/reference/components/voice-player/COMPONENT.md +1 -1
  125. package/dist/skill/reference/layout/app-header/examples/in-app-shell.tsx +2 -2
  126. package/dist/skill/reference/layout/app-shell/COMPONENT.md +1 -1
  127. package/dist/skill/reference/layout/app-shell/examples/footer.tsx +2 -2
  128. package/dist/skill/reference/layout/app-shell/examples/overview.tsx +2 -2
  129. package/dist/skill/reference/layout/app-shell/examples/template.tsx +2 -2
  130. package/dist/skill/reference/layout/bottom-nav/examples/floating-action.tsx +2 -2
  131. package/dist/skill/reference/layout/bottom-nav/examples/floating.tsx +2 -2
  132. package/dist/skill/reference/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
  133. package/dist/skill/responsive.md +5 -3
  134. package/dist/skill/ux.md +145 -0
  135. package/dist/tokens/semantic.d.ts +2 -0
  136. package/package.json +1 -1
  137. package/src/components/avatar/COMPONENT.md +4 -4
  138. package/src/components/avatar/examples/presence.tsx +1 -1
  139. package/src/components/avatar/examples/states.tsx +1 -1
  140. package/src/components/banner/COMPONENT.md +6 -6
  141. package/src/components/banner/examples/dismissible.tsx +22 -11
  142. package/src/components/banner/examples/examples.module.css +3 -3
  143. package/src/components/banner/examples/narrow.tsx +1 -1
  144. package/src/components/breadcrumb/COMPONENT.md +1 -1
  145. package/src/components/breadcrumb/examples/overflow.tsx +1 -1
  146. package/src/components/card/COMPONENT.md +5 -4
  147. package/src/components/card/examples/examples.module.css +0 -28
  148. package/src/components/card/examples/kpi-media.tsx +13 -11
  149. package/src/components/card/examples/narrow.tsx +1 -1
  150. package/src/components/card/examples/panel-chart.tsx +45 -22
  151. package/src/components/chart-frame/COMPONENT.md +10 -7
  152. package/src/components/chart-frame/examples/tooltip.tsx +20 -24
  153. package/src/components/chat/COMPONENT.md +3 -3
  154. package/src/components/chat/examples/search.tsx +1 -1
  155. package/src/components/chat-list/COMPONENT.md +1 -1
  156. package/src/components/checkbox/examples/indicator.tsx +24 -2
  157. package/src/components/color-picker/COMPONENT.md +2 -2
  158. package/src/components/color-swatches/COMPONENT.md +1 -0
  159. package/src/components/datepicker/COMPONENT.md +1 -0
  160. package/src/components/dnd/COMPONENT.md +3 -3
  161. package/src/components/field-list/COMPONENT.md +164 -0
  162. package/src/components/field-list/examples/base-row.tsx +76 -0
  163. package/src/components/field-list/examples/controlled.tsx +72 -0
  164. package/src/components/field-list/examples/errors.tsx +85 -0
  165. package/src/components/field-list/examples/examples.module.css +34 -0
  166. package/src/components/field-list/examples/limits.tsx +55 -0
  167. package/src/components/field-list/examples/locked.tsx +139 -0
  168. package/src/components/field-list/examples/narrow.tsx +68 -0
  169. package/src/components/field-list/examples/overview.tsx +66 -0
  170. package/src/components/field-list/examples/reorder.tsx +42 -0
  171. package/src/components/field-list/examples/sizes.tsx +42 -0
  172. package/src/components/field-list/examples/states.tsx +51 -0
  173. package/src/components/input/examples/examples.module.css +2 -7
  174. package/src/components/kanban/COMPONENT.md +13 -9
  175. package/src/components/kanban/examples/limits.tsx +2 -1
  176. package/src/components/kanban/examples/variants.tsx +16 -8
  177. package/src/components/list/COMPONENT.md +1 -1
  178. package/src/components/media-viewer/COMPONENT.md +2 -2
  179. package/src/components/message/COMPONENT.md +2 -2
  180. package/src/components/notification/COMPONENT.md +1 -0
  181. package/src/components/page-content/COMPONENT.md +70 -14
  182. package/src/components/page-content/examples/grid.tsx +107 -0
  183. package/src/components/page-content/examples/overview.tsx +2 -2
  184. package/src/components/page-content/examples/sections.tsx +81 -0
  185. package/src/components/page-toolbar/COMPONENT.md +1 -1
  186. package/src/components/segmented-control/COMPONENT.md +1 -0
  187. package/src/components/select/COMPONENT.md +4 -2
  188. package/src/components/select/examples/icon-only.tsx +38 -0
  189. package/src/components/sparkline/COMPONENT.md +83 -30
  190. package/src/components/sparkline/examples/compact.tsx +1 -1
  191. package/src/components/sparkline/examples/curve.tsx +28 -0
  192. package/src/components/sparkline/examples/gaps.tsx +33 -0
  193. package/src/components/sparkline/examples/overview.tsx +11 -2
  194. package/src/components/sparkline/examples/period.tsx +7 -2
  195. package/src/components/sparkline/examples/reference.tsx +37 -0
  196. package/src/components/sparkline/examples/variants.tsx +2 -2
  197. package/src/components/tabs/examples/examples.module.css +0 -6
  198. package/src/components/tabs/examples/in-drawer.tsx +11 -13
  199. package/src/components/timeline/examples/examples.module.css +0 -8
  200. package/src/components/timeline/examples/narrow.tsx +2 -2
  201. package/src/components/timeline/examples/sizes.tsx +3 -3
  202. package/src/components/timeline/examples/variants.tsx +4 -4
  203. package/src/components/tooltip/COMPONENT.md +6 -4
  204. package/src/components/tooltip/examples/placement.tsx +2 -1
  205. package/src/components/video-note/COMPONENT.md +1 -1
  206. package/src/components/voice-player/COMPONENT.md +1 -1
  207. package/src/layout/app-header/examples/in-app-shell.tsx +2 -2
  208. package/src/layout/app-shell/COMPONENT.md +1 -1
  209. package/src/layout/app-shell/examples/footer.tsx +2 -2
  210. package/src/layout/app-shell/examples/overview.tsx +2 -2
  211. package/src/layout/app-shell/examples/template.tsx +2 -2
  212. package/src/layout/bottom-nav/examples/floating-action.tsx +2 -2
  213. package/src/layout/bottom-nav/examples/floating.tsx +2 -2
  214. package/src/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
  215. package/src/styles/theme-light.css +1 -0
@@ -0,0 +1,72 @@
1
+ /** The parent owns the lines: it sums them under the list and puts the saved lines back on reset — `value`, `onValueChange`. */
2
+ import { Button, FieldList, type FieldListColumn, Input, Typography } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ type Line = { id: string; service: string; amount: string };
8
+
9
+ const COLUMNS: FieldListColumn[] = [{ label: "Услуга" }, { label: "Сумма", width: "m" }];
10
+
11
+ const SAVED: Line[] = [
12
+ { id: "license", service: "Лицензия на 12 месяцев", amount: "96 000" },
13
+ { id: "migration", service: "Перенос данных", amount: "24 000" },
14
+ ];
15
+
16
+ const RUB = new Intl.NumberFormat("ru-RU", {
17
+ style: "currency",
18
+ currency: "RUB",
19
+ maximumFractionDigits: 0,
20
+ });
21
+
22
+ function totalOf(lines: Line[]) {
23
+ return lines.reduce((sum, line) => sum + (Number(line.amount.replace(/\s/g, "")) || 0), 0);
24
+ }
25
+
26
+ export default function FieldListControlledExample() {
27
+ const [lines, setLines] = React.useState(SAVED);
28
+
29
+ return (
30
+ <div className={styles.stack}>
31
+ <FieldList
32
+ label="Состав сделки"
33
+ columns={COLUMNS}
34
+ value={lines}
35
+ onValueChange={setLines}
36
+ getId={(line) => line.id}
37
+ getLabel={(line) => line.service}
38
+ createItem={() => ({ id: crypto.randomUUID(), service: "", amount: "" })}
39
+ addLabel="Добавить услугу"
40
+ renderItem={(line, row) => (
41
+ <>
42
+ <Input.Root>
43
+ <Input.Wrapper>
44
+ <Input.Field
45
+ value={line.service}
46
+ onChange={(event) => row.update({ service: event.target.value })}
47
+ />
48
+ </Input.Wrapper>
49
+ </Input.Root>
50
+ <Input.Root>
51
+ <Input.Wrapper>
52
+ <Input.Field
53
+ value={line.amount}
54
+ inputMode="decimal"
55
+ placeholder="0"
56
+ onChange={(event) => row.update({ amount: event.target.value })}
57
+ />
58
+ <Input.InlineAffix side="end">₽</Input.InlineAffix>
59
+ </Input.Wrapper>
60
+ </Input.Root>
61
+ </>
62
+ )}
63
+ />
64
+ <div className={styles.summary}>
65
+ <Typography variant="title-m">Итого {RUB.format(totalOf(lines))}</Typography>
66
+ <Button.Root variant="ghost" tone="neutral" onClick={() => setLines(SAVED)}>
67
+ Вернуть сохранённое
68
+ </Button.Root>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,85 @@
1
+ /** 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 — `error`. */
2
+ import { Button, FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ type Contact = { id: string; role: string; phone: string };
8
+
9
+ const COLUMNS: FieldListColumn[] = [{ label: "Должность" }, { label: "Телефон", width: "m" }];
10
+
11
+ const CONTACTS: Contact[] = [
12
+ { id: "buyer", role: "Закупщик", phone: "+7 912 404-18-20" },
13
+ { id: "lawyer", role: "Юрист", phone: "+7 912 40" },
14
+ ];
15
+
16
+ const PHONE = /^\+7 \d{3} \d{3}-\d{2}-\d{2}$/;
17
+
18
+ function phoneError(phone: string) {
19
+ if (phone.trim() === "") return "Укажите телефон";
20
+ return PHONE.test(phone) ? undefined : "Формат: +7 912 404-18-20";
21
+ }
22
+
23
+ export default function FieldListErrorsExample() {
24
+ const [contacts, setContacts] = React.useState(CONTACTS);
25
+ // Every save attempt validates the form; after it renders, focus goes to the first wrong field.
26
+ const [attempts, setAttempts] = React.useState(0);
27
+ const formRef = React.useRef<HTMLFormElement>(null);
28
+ const checked = attempts > 0;
29
+ React.useEffect(() => {
30
+ if (attempts === 0) return;
31
+ formRef.current?.querySelector<HTMLElement>("[aria-invalid='true']")?.focus();
32
+ }, [attempts]);
33
+ const listError = checked && contacts.length === 0 ? "Добавьте хотя бы один контакт" : undefined;
34
+
35
+ return (
36
+ <form
37
+ ref={formRef}
38
+ noValidate
39
+ className={styles.form}
40
+ onSubmit={(event) => {
41
+ event.preventDefault();
42
+ setAttempts((count) => count + 1);
43
+ }}
44
+ >
45
+ <FieldList
46
+ label="Контакты клиента"
47
+ error={listError}
48
+ columns={COLUMNS}
49
+ value={contacts}
50
+ onValueChange={setContacts}
51
+ getId={(contact) => contact.id}
52
+ getLabel={(contact) => contact.role}
53
+ createItem={() => ({ id: crypto.randomUUID(), role: "", phone: "" })}
54
+ addLabel="Добавить контакт"
55
+ renderItem={(contact, row) => (
56
+ <>
57
+ <Input.Root
58
+ error={checked && contact.role.trim() === "" ? "Укажите должность" : undefined}
59
+ >
60
+ <Input.Wrapper>
61
+ <Input.Field
62
+ value={contact.role}
63
+ onChange={(event) => row.update({ role: event.target.value })}
64
+ />
65
+ </Input.Wrapper>
66
+ </Input.Root>
67
+ <Input.Root error={checked ? phoneError(contact.phone) : undefined}>
68
+ <Input.Wrapper>
69
+ <Input.Field
70
+ type="tel"
71
+ value={contact.phone}
72
+ placeholder="+7 912 404-18-20"
73
+ onChange={(event) => row.update({ phone: event.target.value })}
74
+ />
75
+ </Input.Wrapper>
76
+ </Input.Root>
77
+ </>
78
+ )}
79
+ />
80
+ <Button.Root type="submit" className={styles.submit}>
81
+ Сохранить контакты
82
+ </Button.Root>
83
+ </form>
84
+ );
85
+ }
@@ -0,0 +1,34 @@
1
+ /* Layout of the FieldList scenarios: semantic tokens only. */
2
+
3
+ /* A form: the list, then its submit button at the start, a group apart. */
4
+ .form {
5
+ display: grid;
6
+ gap: var(--prime-space-8);
7
+ width: 100%;
8
+ min-width: 0;
9
+ }
10
+
11
+ .submit {
12
+ justify-self: start;
13
+ }
14
+
15
+ /* A 320 px column: the rows stack into blocks and every field shows its label. */
16
+ .narrow {
17
+ inline-size: min(100%, calc(var(--prime-space-20) * 4));
18
+ }
19
+
20
+ /* The total of the lines and the reset beside it. */
21
+ .summary {
22
+ display: flex;
23
+ flex-wrap: wrap;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ gap: var(--prime-space-3);
27
+ }
28
+
29
+ .stack {
30
+ display: grid;
31
+ gap: var(--prime-space-6);
32
+ width: 100%;
33
+ min-width: 0;
34
+ }
@@ -0,0 +1,55 @@
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`. */
2
+ import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ type Recipient = { id: string; name: string; email: string };
6
+
7
+ const COLUMNS: FieldListColumn[] = [{ label: "Имя" }, { label: "Почта" }];
8
+
9
+ const RECIPIENTS: Recipient[] = [
10
+ { id: "accounting", name: "Бухгалтерия", email: "buh@severstroy.ru" },
11
+ { id: "director", name: "Ирина Котова", email: "kotova@severstroy.ru" },
12
+ { id: "manager", name: "Олег Демин", email: "demin@severstroy.ru" },
13
+ ];
14
+
15
+ export default function FieldListLimitsExample() {
16
+ const [recipients, setRecipients] = React.useState(RECIPIENTS);
17
+
18
+ return (
19
+ <FieldList
20
+ label="Кому напоминать об оплате"
21
+ hint="Письмо уходит за три дня до срока."
22
+ columns={COLUMNS}
23
+ min={1}
24
+ max={3}
25
+ value={recipients}
26
+ onValueChange={setRecipients}
27
+ getId={(recipient) => recipient.id}
28
+ getLabel={(recipient) => recipient.name}
29
+ createItem={() => ({ id: crypto.randomUUID(), name: "", email: "" })}
30
+ addLabel="Добавить получателя"
31
+ renderItem={(recipient, row) => (
32
+ <>
33
+ <Input.Root>
34
+ <Input.Wrapper>
35
+ <Input.Field
36
+ value={recipient.name}
37
+ onChange={(event) => row.update({ name: event.target.value })}
38
+ />
39
+ </Input.Wrapper>
40
+ </Input.Root>
41
+ <Input.Root>
42
+ <Input.Wrapper>
43
+ <Input.Field
44
+ type="email"
45
+ value={recipient.email}
46
+ placeholder="name@company.ru"
47
+ onChange={(event) => row.update({ email: event.target.value })}
48
+ />
49
+ </Input.Wrapper>
50
+ </Input.Root>
51
+ </>
52
+ )}
53
+ />
54
+ );
55
+ }
@@ -0,0 +1,139 @@
1
+ /** Specs of a plan on one line each: an icon picker and a color swatch take compact columns, the first three specs are pinned with a lock and the rest reorder below them — `getLocked`, `width`, `reorderable`. */
2
+ import {
3
+ ColorPresets,
4
+ FieldList,
5
+ type FieldListColumn,
6
+ Icon,
7
+ type IconName,
8
+ Input,
9
+ Select,
10
+ } from "prime-ui-kit";
11
+ import * as React from "react";
12
+
13
+ type Spec = {
14
+ id: string;
15
+ icon: IconName;
16
+ name: string;
17
+ value: string;
18
+ color: string;
19
+ pinned?: boolean;
20
+ };
21
+
22
+ const COLUMNS: FieldListColumn[] = [
23
+ { label: "Иконка", width: "compact" },
24
+ { label: "Параметр" },
25
+ { label: "Значение" },
26
+ { label: "Цвет", width: "compact" },
27
+ ];
28
+
29
+ const ICONS: { name: IconName; label: string }[] = [
30
+ { name: "object.users", label: "Пользователи" },
31
+ { name: "object.storage", label: "Хранилище" },
32
+ { name: "object.headset", label: "Поддержка" },
33
+ { name: "object.chart", label: "Отчёты" },
34
+ { name: "object.key", label: "Доступ" },
35
+ { name: "object.globe", label: "Интеграции" },
36
+ ];
37
+
38
+ const SPECS: Spec[] = [
39
+ {
40
+ id: "seats",
41
+ icon: "object.users",
42
+ name: "Сотрудники",
43
+ value: "до 50",
44
+ color: "#5068f5",
45
+ pinned: true,
46
+ },
47
+ {
48
+ id: "storage",
49
+ icon: "object.storage",
50
+ name: "Хранилище",
51
+ value: "200 ГБ",
52
+ color: "#22c55e",
53
+ pinned: true,
54
+ },
55
+ {
56
+ id: "support",
57
+ icon: "object.headset",
58
+ name: "Поддержка",
59
+ value: "8/5",
60
+ color: "#f97316",
61
+ pinned: true,
62
+ },
63
+ { id: "reports", icon: "object.chart", name: "Отчёты", value: "12 шаблонов", color: "#a855f7" },
64
+ { id: "sso", icon: "object.key", name: "Вход через SSO", value: "SAML, OIDC", color: "#14b8a6" },
65
+ ];
66
+
67
+ export default function FieldListLockedExample() {
68
+ const [specs, setSpecs] = React.useState(SPECS);
69
+
70
+ return (
71
+ <FieldList
72
+ label="Характеристики тарифа"
73
+ columns={COLUMNS}
74
+ reorderable
75
+ value={specs}
76
+ onValueChange={setSpecs}
77
+ getId={(spec) => spec.id}
78
+ getLabel={(spec) => spec.name}
79
+ getLocked={(spec) => spec.pinned ?? false}
80
+ createItem={(): Spec => ({
81
+ id: crypto.randomUUID(),
82
+ icon: "object.globe",
83
+ name: "",
84
+ value: "",
85
+ color: "#14b8a6",
86
+ })}
87
+ addLabel="Добавить характеристику"
88
+ renderItem={(spec, row) => (
89
+ <>
90
+ <Select.Root
91
+ value={spec.icon}
92
+ onValueChange={(icon) => row.update({ icon: icon as IconName })}
93
+ >
94
+ <Select.Trigger>
95
+ <Select.TriggerIcon>
96
+ <Icon name={spec.icon} />
97
+ </Select.TriggerIcon>
98
+ </Select.Trigger>
99
+ <Select.Content>
100
+ {ICONS.map((icon) => (
101
+ <Select.Item key={icon.name} value={icon.name}>
102
+ <Select.ItemIcon>
103
+ <Icon name={icon.name} />
104
+ </Select.ItemIcon>
105
+ {icon.label}
106
+ </Select.Item>
107
+ ))}
108
+ </Select.Content>
109
+ </Select.Root>
110
+ <Input.Root>
111
+ <Input.Wrapper>
112
+ <Input.Field
113
+ value={spec.name}
114
+ onChange={(event) => row.update({ name: event.target.value })}
115
+ />
116
+ </Input.Wrapper>
117
+ </Input.Root>
118
+ <Input.Root>
119
+ <Input.Wrapper>
120
+ <Input.Field
121
+ value={spec.value}
122
+ onChange={(event) => row.update({ value: event.target.value })}
123
+ />
124
+ </Input.Wrapper>
125
+ </Input.Root>
126
+ <ColorPresets.Root
127
+ value={spec.color}
128
+ onValueChange={(color) => row.update({ color: color ?? spec.color })}
129
+ >
130
+ <ColorPresets.Trigger>
131
+ <ColorPresets.SwatchButton />
132
+ </ColorPresets.Trigger>
133
+ <ColorPresets.Content />
134
+ </ColorPresets.Root>
135
+ </>
136
+ )}
137
+ />
138
+ );
139
+ }
@@ -0,0 +1,68 @@
1
+ /** The invoice lines in a 320 px column: the headings give way, each row stacks its fields with their own labels and rows stand a group apart — `columns`. */
2
+ import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ type Line = { id: string; service: string; quantity: string; price: string };
8
+
9
+ const COLUMNS: FieldListColumn[] = [
10
+ { label: "Услуга" },
11
+ { label: "Кол-во", width: "s" },
12
+ { label: "Цена", width: "m" },
13
+ ];
14
+
15
+ const LINES: Line[] = [
16
+ { id: "setup", service: "Внедрение CRM", quantity: "1", price: "180 000" },
17
+ { id: "training", service: "Обучение менеджеров", quantity: "3", price: "15 000" },
18
+ ];
19
+
20
+ export default function FieldListNarrowExample() {
21
+ const [lines, setLines] = React.useState(LINES);
22
+
23
+ return (
24
+ <div className={styles.narrow}>
25
+ <FieldList
26
+ label="Позиции счёта"
27
+ columns={COLUMNS}
28
+ value={lines}
29
+ onValueChange={setLines}
30
+ getId={(line) => line.id}
31
+ getLabel={(line) => line.service}
32
+ createItem={() => ({ id: crypto.randomUUID(), service: "", quantity: "1", price: "" })}
33
+ addLabel="Добавить позицию"
34
+ renderItem={(line, row) => (
35
+ <>
36
+ <Input.Root>
37
+ <Input.Wrapper>
38
+ <Input.Field
39
+ value={line.service}
40
+ onChange={(event) => row.update({ service: event.target.value })}
41
+ />
42
+ </Input.Wrapper>
43
+ </Input.Root>
44
+ <Input.Root>
45
+ <Input.Wrapper>
46
+ <Input.Field
47
+ value={line.quantity}
48
+ inputMode="numeric"
49
+ onChange={(event) => row.update({ quantity: event.target.value })}
50
+ />
51
+ </Input.Wrapper>
52
+ </Input.Root>
53
+ <Input.Root>
54
+ <Input.Wrapper>
55
+ <Input.Field
56
+ value={line.price}
57
+ inputMode="decimal"
58
+ onChange={(event) => row.update({ price: event.target.value })}
59
+ />
60
+ <Input.InlineAffix side="end">₽</Input.InlineAffix>
61
+ </Input.Wrapper>
62
+ </Input.Root>
63
+ </>
64
+ )}
65
+ />
66
+ </div>
67
+ );
68
+ }
@@ -0,0 +1,66 @@
1
+ /** Lines of an invoice: the column headings stand once above the rows, each row has a remove button and the add button sits under them — `columns`, `renderItem`, `createItem`, `addLabel`. */
2
+ import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ type Line = { id: string; service: string; quantity: string; price: string };
6
+
7
+ const COLUMNS: FieldListColumn[] = [
8
+ { label: "Услуга" },
9
+ { label: "Кол-во", width: "s" },
10
+ { label: "Цена", width: "m" },
11
+ ];
12
+
13
+ const LINES: Line[] = [
14
+ { id: "setup", service: "Внедрение CRM", quantity: "1", price: "180 000" },
15
+ { id: "training", service: "Обучение менеджеров", quantity: "3", price: "15 000" },
16
+ ];
17
+
18
+ export default function FieldListOverviewExample() {
19
+ const [lines, setLines] = React.useState(LINES);
20
+
21
+ return (
22
+ <FieldList
23
+ label="Позиции счёта"
24
+ columns={COLUMNS}
25
+ value={lines}
26
+ onValueChange={setLines}
27
+ getId={(line) => line.id}
28
+ getLabel={(line) => line.service}
29
+ createItem={() => ({ id: crypto.randomUUID(), service: "", quantity: "1", price: "" })}
30
+ addLabel="Добавить позицию"
31
+ renderItem={(line, row) => (
32
+ <>
33
+ <Input.Root>
34
+ <Input.Wrapper>
35
+ <Input.Field
36
+ value={line.service}
37
+ placeholder="Поддержка, 1 месяц"
38
+ onChange={(event) => row.update({ service: event.target.value })}
39
+ />
40
+ </Input.Wrapper>
41
+ </Input.Root>
42
+ <Input.Root>
43
+ <Input.Wrapper>
44
+ <Input.Field
45
+ value={line.quantity}
46
+ inputMode="numeric"
47
+ onChange={(event) => row.update({ quantity: event.target.value })}
48
+ />
49
+ </Input.Wrapper>
50
+ </Input.Root>
51
+ <Input.Root>
52
+ <Input.Wrapper>
53
+ <Input.Field
54
+ value={line.price}
55
+ inputMode="decimal"
56
+ placeholder="0"
57
+ onChange={(event) => row.update({ price: event.target.value })}
58
+ />
59
+ <Input.InlineAffix side="end">₽</Input.InlineAffix>
60
+ </Input.Wrapper>
61
+ </Input.Root>
62
+ </>
63
+ )}
64
+ />
65
+ );
66
+ }
@@ -0,0 +1,42 @@
1
+ /** Selling points of a plan in the order of the pricing page: a one-column list has no headings and no numbers, a point moves by its handle or Alt+arrows — `reorderable`, `getLabel`. */
2
+ import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ type Point = { id: string; text: string };
6
+
7
+ const COLUMNS: FieldListColumn[] = [{ label: "Преимущество" }];
8
+
9
+ const POINTS: Point[] = [
10
+ { id: "users", text: "До 50 сотрудников в одном пространстве" },
11
+ { id: "storage", text: "200 ГБ для документов и вложений" },
12
+ { id: "support", text: "Поддержка в рабочие часы, ответ за 2 часа" },
13
+ { id: "api", text: "Доступ к API и вебхукам" },
14
+ ];
15
+
16
+ export default function FieldListReorderExample() {
17
+ const [points, setPoints] = React.useState(POINTS);
18
+
19
+ return (
20
+ <FieldList
21
+ label="Преимущества тарифа"
22
+ columns={COLUMNS}
23
+ reorderable
24
+ value={points}
25
+ onValueChange={setPoints}
26
+ getId={(point) => point.id}
27
+ getLabel={(point) => point.text}
28
+ createItem={() => ({ id: crypto.randomUUID(), text: "" })}
29
+ addLabel="Добавить преимущество"
30
+ renderItem={(point, row) => (
31
+ <Input.Root>
32
+ <Input.Wrapper>
33
+ <Input.Field
34
+ value={point.text}
35
+ onChange={(event) => row.update({ text: event.target.value })}
36
+ />
37
+ </Input.Wrapper>
38
+ </Input.Root>
39
+ )}
40
+ />
41
+ );
42
+ }
@@ -0,0 +1,42 @@
1
+ /** Every tier: the headings, the fields, the handle and the buttons follow the list's size — `size`. */
2
+ import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
+
4
+ type Contact = { id: string; name: string; phone: string };
5
+
6
+ const SIZES = ["xs", "s", "m", "l", "xl"] as const;
7
+
8
+ const COLUMNS: FieldListColumn[] = [{ label: "Контакт" }, { label: "Телефон", width: "m" }];
9
+
10
+ const CONTACTS: Contact[] = [{ id: "buyer", name: "Анна Лебедева", phone: "+7 912 404-18-20" }];
11
+
12
+ export default function FieldListSizesExample() {
13
+ return (
14
+ <>
15
+ {SIZES.map((size) => (
16
+ <FieldList
17
+ key={size}
18
+ size={size}
19
+ label={size}
20
+ columns={COLUMNS}
21
+ defaultValue={CONTACTS}
22
+ getId={(contact) => contact.id}
23
+ getLabel={(contact) => contact.name}
24
+ renderItem={(contact) => (
25
+ <>
26
+ <Input.Root>
27
+ <Input.Wrapper>
28
+ <Input.Field defaultValue={contact.name} />
29
+ </Input.Wrapper>
30
+ </Input.Root>
31
+ <Input.Root>
32
+ <Input.Wrapper>
33
+ <Input.Field type="tel" defaultValue={contact.phone} />
34
+ </Input.Wrapper>
35
+ </Input.Root>
36
+ </>
37
+ )}
38
+ />
39
+ ))}
40
+ </>
41
+ );
42
+ }
@@ -0,0 +1,51 @@
1
+ /** The same list editable and disabled while the request saves it: fields, handles and buttons turn off together — `disabled`. */
2
+ import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
3
+
4
+ type Stage = { id: string; name: string; days: string };
5
+
6
+ const COLUMNS: FieldListColumn[] = [{ label: "Этап" }, { label: "Дней", width: "s" }];
7
+
8
+ const STAGES: Stage[] = [
9
+ { id: "brief", name: "Бриф и смета", days: "3" },
10
+ { id: "design", name: "Дизайн", days: "10" },
11
+ ];
12
+
13
+ const STATES = [
14
+ { label: "Этапы проекта", disabled: false },
15
+ { label: "disabled", disabled: true },
16
+ ];
17
+
18
+ export default function FieldListStatesExample() {
19
+ return (
20
+ <>
21
+ {STATES.map((state) => (
22
+ <FieldList
23
+ key={state.label}
24
+ label={state.label}
25
+ disabled={state.disabled}
26
+ reorderable
27
+ columns={COLUMNS}
28
+ defaultValue={STAGES}
29
+ getId={(stage) => stage.id}
30
+ getLabel={(stage) => stage.name}
31
+ createItem={() => ({ id: crypto.randomUUID(), name: "", days: "" })}
32
+ addLabel="Добавить этап"
33
+ renderItem={(stage) => (
34
+ <>
35
+ <Input.Root>
36
+ <Input.Wrapper>
37
+ <Input.Field defaultValue={stage.name} />
38
+ </Input.Wrapper>
39
+ </Input.Root>
40
+ <Input.Root>
41
+ <Input.Wrapper>
42
+ <Input.Field defaultValue={stage.days} inputMode="numeric" />
43
+ </Input.Wrapper>
44
+ </Input.Root>
45
+ </>
46
+ )}
47
+ />
48
+ ))}
49
+ </>
50
+ );
51
+ }
@@ -16,7 +16,8 @@
16
16
 
17
17
  .formFields {
18
18
  display: grid;
19
- grid-template-columns: repeat(2, minmax(0, 1fr));
19
+ /* Two columns while each keeps 12rem, one column in a narrower form — from its own width. */
20
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(var(--prime-space-16) * 3)), 1fr));
20
21
  align-items: start;
21
22
  gap: var(--prime-space-5) var(--prime-space-4);
22
23
  }
@@ -30,9 +31,3 @@
30
31
  justify-content: flex-end;
31
32
  gap: var(--prime-space-2);
32
33
  }
33
-
34
- @media (max-width: 639px) {
35
- .formFields {
36
- grid-template-columns: minmax(0, 1fr);
37
- }
38
- }