prime-ui-kit 0.11.1 → 0.13.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 (171) hide show
  1. package/README.md +4 -1
  2. package/dist/chunks/{chunk-WDTSHOTD.js → chunk-E6SQBSDF.js} +140 -30
  3. package/dist/chunks/{chunk-CUH6DJ57.js → chunk-Z3WKKAS6.js} +6572 -5024
  4. package/dist/chunks/{chunk-GKX3QAUC.js → chunk-ZSJMNA46.js} +14 -5
  5. package/dist/color-picker.js +2 -2
  6. package/dist/components/chat/Chat.d.ts +11 -12
  7. package/dist/components/chat/chatContext.d.ts +2 -0
  8. package/dist/components/chat-list/ChatList.d.ts +191 -0
  9. package/dist/components/chat-list/formatChatTime.d.ts +6 -0
  10. package/dist/components/command-menu/CommandMenu.d.ts +10 -5
  11. package/dist/components/composer/Composer.d.ts +3 -10
  12. package/dist/components/data-table/DataTable.d.ts +16 -2
  13. package/dist/components/data-table/Row.d.ts +23 -7
  14. package/dist/components/data-table/StateRows.d.ts +5 -1
  15. package/dist/components/data-table/reorder.d.ts +3 -1
  16. package/dist/components/data-table/rows.d.ts +20 -4
  17. package/dist/components/file-upload/FileUpload.d.ts +7 -0
  18. package/dist/components/gantt/Gantt.d.ts +19 -1
  19. package/dist/components/gantt/GanttBar.d.ts +4 -1
  20. package/dist/components/gantt/GanttRow.d.ts +8 -1
  21. package/dist/components/index.d.ts +11 -7
  22. package/dist/components/kanban/Kanban.d.ts +17 -2
  23. package/dist/components/list/List.d.ts +120 -0
  24. package/dist/components/message/Message.d.ts +13 -0
  25. package/dist/components/message/MessageParts.d.ts +2 -0
  26. package/dist/components/modal/DialogParts.d.ts +5 -1
  27. package/dist/components/select/Select.d.ts +11 -5
  28. package/dist/components/select/SelectItem.d.ts +19 -0
  29. package/dist/components/select/selectContext.d.ts +4 -0
  30. package/dist/components/sparkline/Sparkline.d.ts +14 -3
  31. package/dist/components/textarea/Textarea.d.ts +11 -3
  32. package/dist/hooks/useUploadQueue.d.ts +5 -1
  33. package/dist/icons/registry.d.ts +2 -0
  34. package/dist/index.css +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +10 -2
  37. package/dist/internal/VisuallyHidden.d.ts +6 -1
  38. package/dist/internal/attachmentTile.d.ts +5 -1
  39. package/dist/internal/richText.d.ts +7 -2
  40. package/dist/internal/textFormatting.d.ts +52 -0
  41. package/dist/internal/textFormattingToolbar.d.ts +26 -0
  42. package/dist/skill/api-contract.md +1 -1
  43. package/dist/skill/cheatsheet.md +13 -1
  44. package/dist/skill/choosing.md +9 -4
  45. package/dist/skill/components.md +7 -5
  46. package/dist/skill/composition.md +9 -1
  47. package/dist/skill/layouts.md +4 -1
  48. package/dist/skill/patterns/app-shell.module.css +18 -0
  49. package/dist/skill/patterns/app-shell.tsx +301 -0
  50. package/dist/skill/patterns/chat.module.css +0 -25
  51. package/dist/skill/patterns/chat.tsx +72 -29
  52. package/dist/skill/patterns/kanban-board.module.css +28 -0
  53. package/dist/skill/patterns/kanban-board.tsx +612 -0
  54. package/dist/skill/patterns/media-library.module.css +66 -0
  55. package/dist/skill/patterns/media-library.tsx +638 -0
  56. package/dist/skill/patterns/schedule.module.css +22 -0
  57. package/dist/skill/patterns/schedule.tsx +722 -0
  58. package/dist/skill/reference/components/chat/COMPONENT.md +13 -4
  59. package/dist/skill/reference/components/chat/examples/states.tsx +100 -0
  60. package/dist/skill/reference/components/chat-list/COMPONENT.md +256 -0
  61. package/dist/skill/reference/components/chat-list/examples/controlled.tsx +103 -0
  62. package/dist/skill/reference/components/chat-list/examples/examples.module.css +66 -0
  63. package/dist/skill/reference/components/chat-list/examples/live.tsx +133 -0
  64. package/dist/skill/reference/components/chat-list/examples/long-list.tsx +87 -0
  65. package/dist/skill/reference/components/chat-list/examples/narrow.tsx +88 -0
  66. package/dist/skill/reference/components/chat-list/examples/overview.tsx +194 -0
  67. package/dist/skill/reference/components/chat-list/examples/search.tsx +71 -0
  68. package/dist/skill/reference/components/chat-list/examples/sizes.tsx +61 -0
  69. package/dist/skill/reference/components/chat-list/examples/states.tsx +107 -0
  70. package/dist/skill/reference/components/chat-list/examples/structure.tsx +135 -0
  71. package/dist/skill/reference/components/command-menu/COMPONENT.md +15 -11
  72. package/dist/skill/reference/components/command-menu/examples/server-search.tsx +11 -9
  73. package/dist/skill/reference/components/composer/COMPONENT.md +8 -7
  74. package/dist/skill/reference/components/composer/examples/formatting.tsx +1 -1
  75. package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -0
  76. package/dist/skill/reference/components/data-table/COMPONENT.md +21 -6
  77. package/dist/skill/reference/components/data-table/examples/groups.tsx +2 -1
  78. package/dist/skill/reference/components/data-table/examples/trend.tsx +85 -0
  79. package/dist/skill/reference/components/drawer/COMPONENT.md +7 -6
  80. package/dist/skill/reference/components/drawer/examples/client-card.tsx +50 -0
  81. package/dist/skill/reference/components/fieldset/COMPONENT.md +1 -0
  82. package/dist/skill/reference/components/file-upload/COMPONENT.md +12 -8
  83. package/dist/skill/reference/components/file-upload/examples/overview.tsx +14 -3
  84. package/dist/skill/reference/components/gantt/COMPONENT.md +15 -5
  85. package/dist/skill/reference/components/gantt/examples/row-link.tsx +96 -0
  86. package/dist/skill/reference/components/kanban/COMPONENT.md +12 -4
  87. package/dist/skill/reference/components/kanban/examples/item-link.tsx +92 -0
  88. package/dist/skill/reference/components/list/COMPONENT.md +195 -0
  89. package/dist/skill/reference/components/list/examples/as-link.tsx +37 -0
  90. package/dist/skill/reference/components/list/examples/examples.module.css +18 -0
  91. package/dist/skill/reference/components/list/examples/overflow.tsx +46 -0
  92. package/dist/skill/reference/components/list/examples/overview.tsx +63 -0
  93. package/dist/skill/reference/components/list/examples/reorder.tsx +48 -0
  94. package/dist/skill/reference/components/list/examples/sizes.tsx +35 -0
  95. package/dist/skill/reference/components/list/examples/states.tsx +27 -0
  96. package/dist/skill/reference/components/list/examples/with-icon.tsx +60 -0
  97. package/dist/skill/reference/components/message/COMPONENT.md +18 -2
  98. package/dist/skill/reference/components/message/examples/bot-actions.tsx +51 -0
  99. package/dist/skill/reference/components/modal/COMPONENT.md +6 -4
  100. package/dist/skill/reference/components/page-content/COMPONENT.md +2 -2
  101. package/dist/skill/reference/components/page-content/examples/examples.module.css +5 -12
  102. package/dist/skill/reference/components/page-content/examples/list-detail.tsx +13 -18
  103. package/dist/skill/reference/components/select/COMPONENT.md +38 -7
  104. package/dist/skill/reference/components/select/examples/create-action.tsx +107 -0
  105. package/dist/skill/reference/components/select/examples/saving.tsx +47 -0
  106. package/dist/skill/reference/components/sheet/COMPONENT.md +4 -4
  107. package/dist/skill/reference/components/sparkline/COMPONENT.md +16 -2
  108. package/dist/skill/reference/components/sparkline/examples/compact.tsx +41 -0
  109. package/dist/skill/reference/components/sparkline/examples/examples.module.css +12 -0
  110. package/dist/skill/reference/components/textarea/COMPONENT.md +22 -2
  111. package/dist/skill/reference/components/textarea/examples/formatting.tsx +20 -0
  112. package/dist/skill/reference/components/visually-hidden/COMPONENT.md +6 -3
  113. package/dist/skill/reference/components/visually-hidden/examples/overview.tsx +2 -2
  114. package/package.json +2 -2
  115. package/src/components/chat/COMPONENT.md +13 -4
  116. package/src/components/chat/examples/states.tsx +100 -0
  117. package/src/components/chat-list/COMPONENT.md +256 -0
  118. package/src/components/chat-list/examples/controlled.tsx +103 -0
  119. package/src/components/chat-list/examples/examples.module.css +66 -0
  120. package/src/components/chat-list/examples/live.tsx +133 -0
  121. package/src/components/chat-list/examples/long-list.tsx +87 -0
  122. package/src/components/chat-list/examples/narrow.tsx +88 -0
  123. package/src/components/chat-list/examples/overview.tsx +194 -0
  124. package/src/components/chat-list/examples/search.tsx +71 -0
  125. package/src/components/chat-list/examples/sizes.tsx +61 -0
  126. package/src/components/chat-list/examples/states.tsx +107 -0
  127. package/src/components/chat-list/examples/structure.tsx +135 -0
  128. package/src/components/command-menu/COMPONENT.md +15 -11
  129. package/src/components/command-menu/examples/server-search.tsx +11 -9
  130. package/src/components/composer/COMPONENT.md +8 -7
  131. package/src/components/composer/examples/formatting.tsx +1 -1
  132. package/src/components/confirm-dialog/COMPONENT.md +1 -0
  133. package/src/components/data-table/COMPONENT.md +21 -6
  134. package/src/components/data-table/examples/groups.tsx +2 -1
  135. package/src/components/data-table/examples/trend.tsx +85 -0
  136. package/src/components/drawer/COMPONENT.md +7 -6
  137. package/src/components/drawer/examples/client-card.tsx +50 -0
  138. package/src/components/fieldset/COMPONENT.md +1 -0
  139. package/src/components/file-upload/COMPONENT.md +12 -8
  140. package/src/components/file-upload/examples/overview.tsx +14 -3
  141. package/src/components/gantt/COMPONENT.md +15 -5
  142. package/src/components/gantt/examples/row-link.tsx +96 -0
  143. package/src/components/kanban/COMPONENT.md +12 -4
  144. package/src/components/kanban/examples/item-link.tsx +92 -0
  145. package/src/components/list/COMPONENT.md +195 -0
  146. package/src/components/list/examples/as-link.tsx +37 -0
  147. package/src/components/list/examples/examples.module.css +18 -0
  148. package/src/components/list/examples/overflow.tsx +46 -0
  149. package/src/components/list/examples/overview.tsx +63 -0
  150. package/src/components/list/examples/reorder.tsx +48 -0
  151. package/src/components/list/examples/sizes.tsx +35 -0
  152. package/src/components/list/examples/states.tsx +27 -0
  153. package/src/components/list/examples/with-icon.tsx +60 -0
  154. package/src/components/message/COMPONENT.md +18 -2
  155. package/src/components/message/examples/bot-actions.tsx +51 -0
  156. package/src/components/modal/COMPONENT.md +6 -4
  157. package/src/components/page-content/COMPONENT.md +2 -2
  158. package/src/components/page-content/examples/examples.module.css +5 -12
  159. package/src/components/page-content/examples/list-detail.tsx +13 -18
  160. package/src/components/select/COMPONENT.md +38 -7
  161. package/src/components/select/examples/create-action.tsx +107 -0
  162. package/src/components/select/examples/saving.tsx +47 -0
  163. package/src/components/sheet/COMPONENT.md +4 -4
  164. package/src/components/sparkline/COMPONENT.md +16 -2
  165. package/src/components/sparkline/examples/compact.tsx +41 -0
  166. package/src/components/sparkline/examples/examples.module.css +12 -0
  167. package/src/components/textarea/COMPONENT.md +22 -2
  168. package/src/components/textarea/examples/formatting.tsx +20 -0
  169. package/src/components/visually-hidden/COMPONENT.md +6 -3
  170. package/src/components/visually-hidden/examples/overview.tsx +2 -2
  171. package/dist/components/composer/format.d.ts +0 -39
@@ -0,0 +1,92 @@
1
+ /** Tickets that open: each card is a link to its page, so ⌘-click or a middle click opens a new tab, a plain click, Enter or Space opens it in place, and a drag only moves it — `getItemHref`, `onItemClick`. */
2
+ import { Badge, Kanban, type KanbanValue, Typography } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ type Ticket = {
8
+ id: string;
9
+ title: string;
10
+ client: string;
11
+ priority: { text: string; color: "red" | "orange" | "gray" };
12
+ };
13
+
14
+ const COLUMNS = [
15
+ { id: "new", title: "Новые" },
16
+ { id: "progress", title: "В работе" },
17
+ { id: "waiting", title: "Ждут клиента" },
18
+ ];
19
+
20
+ const TICKETS: Ticket[] = [
21
+ {
22
+ id: "SUP-482",
23
+ title: "Не выгружаются счета в 1С",
24
+ client: "ООО «Север»",
25
+ priority: { text: "Высокий", color: "red" },
26
+ },
27
+ {
28
+ id: "SUP-479",
29
+ title: "Сбросить пароль менеджеру склада",
30
+ client: "АО «Гранит»",
31
+ priority: { text: "Обычный", color: "gray" },
32
+ },
33
+ {
34
+ id: "SUP-475",
35
+ title: "Дубли заказов после импорта",
36
+ client: "ООО «Вектор»",
37
+ priority: { text: "Средний", color: "orange" },
38
+ },
39
+ {
40
+ id: "SUP-470",
41
+ title: "Акт сверки за третий квартал",
42
+ client: "ИП Лебедев",
43
+ priority: { text: "Обычный", color: "gray" },
44
+ },
45
+ ];
46
+
47
+ const PLACEMENT: KanbanValue = {
48
+ new: ["SUP-482", "SUP-479"],
49
+ progress: ["SUP-475"],
50
+ waiting: ["SUP-470"],
51
+ };
52
+
53
+ export default function KanbanItemLinkExample() {
54
+ const [opened, setOpened] = React.useState<Ticket | null>(null);
55
+
56
+ return (
57
+ <div className={styles.stage}>
58
+ <div className={styles.toolbar}>
59
+ <Typography as="span" variant="body-s" tone="muted" aria-live="polite">
60
+ {opened
61
+ ? `Открыт ${opened.id} · ${opened.client}`
62
+ : "Откройте обращение кликом или Enter"}
63
+ </Typography>
64
+ </div>
65
+ <Kanban.Root
66
+ aria-label="Обращения в поддержку"
67
+ className={styles.board}
68
+ columns={COLUMNS}
69
+ items={TICKETS}
70
+ getId={(ticket) => ticket.id}
71
+ getLabel={(ticket) => `${ticket.id}: ${ticket.title}`}
72
+ defaultValue={PLACEMENT}
73
+ getItemHref={(ticket) => `/support/${ticket.id}`}
74
+ onItemClick={(ticket, event) => {
75
+ // A client-side router goes here (navigate(href)); the page itself never reloads.
76
+ event.preventDefault();
77
+ setOpened(ticket);
78
+ }}
79
+ renderItem={(ticket) => (
80
+ <Kanban.Item>
81
+ <Kanban.ItemHeader>
82
+ <span>{ticket.id}</span>
83
+ <Badge.Root color={ticket.priority.color}>{ticket.priority.text}</Badge.Root>
84
+ </Kanban.ItemHeader>
85
+ <Kanban.ItemTitle>{ticket.title}</Kanban.ItemTitle>
86
+ <Kanban.ItemDescription>{ticket.client}</Kanban.ItemDescription>
87
+ </Kanban.Item>
88
+ )}
89
+ />
90
+ </div>
91
+ );
92
+ }
@@ -0,0 +1,195 @@
1
+ # List
2
+
3
+ **Category:** navigation
4
+ **Kind:** navigation
5
+
6
+ > A selectable navigation list of rows: the master half of a list + detail screen (conversations, tickets, mail, records).
7
+
8
+ ## When to use
9
+ - The list side of a list + detail screen (`PageContent.Split` → `PageContent.List`): dialogs, tickets, letters, invoices worked through one by one; the open one is `current`.
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.
12
+
13
+ ## When not to use
14
+ - App sections → use [Sidebar](../../layout/sidebar/COMPONENT.md) (wide) and [BottomNav](../../layout/bottom-nav/COMPONENT.md) (phone).
15
+ - Panels of one screen → use [Tabs](../tabs/COMPONENT.md).
16
+ - Many records compared by columns, sorted, filtered or selected in bulk → use [DataTable](../data-table/COMPONENT.md).
17
+ - A static list of facts inside a card (no selection, nothing opens) → use `Card.List` of [Card](../card/COMPONENT.md) `variant="list"`.
18
+ - Picking a value from options → use [Select](../select/COMPONENT.md) or [Radio](../radio/COMPONENT.md); a menu of commands → [Dropdown](../dropdown/COMPONENT.md).
19
+
20
+ ## Import
21
+ ```tsx
22
+ import { List } from "prime-ui-kit";
23
+ ```
24
+
25
+ ## Anatomy
26
+ ```
27
+ List.Root <ul aria-label>; size → row tier (ControlSizeProvider); onReorder → Dnd.Sortable <ul>
28
+ └─ List.Item <li> with a full-width <button> | <a href> | asChild element; current, disabled
29
+ │ value; with onReorder: Dnd.Handle (or a Spinner while saving) before the row element
30
+ ├─ List.ItemIcon leading Avatar.Root / Icon (aria-hidden); after the text — a trailing glyph
31
+ ├─ List.ItemText primary line, truncates
32
+ ├─ List.ItemDescription second line, muted, truncates
33
+ ├─ List.ItemMeta trailing small text (time, date, amount)
34
+ └─ List.ItemCount trailing Badge, one tier down
35
+ ```
36
+
37
+ ## API
38
+
39
+ <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
40
+
41
+ ### List.Root
42
+ `ref` → `HTMLUListElement`. `<ul>` of selectable rows: the master half of a list + detail screen; sets the row tier for its items and their parts.
43
+
44
+ | Prop | Type | Default | Description |
45
+ |---|---|---|---|
46
+ | `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | `"m"` | Row tier: height, text, icon and avatar size; counts take one tier down. |
47
+ | `onReorder` | `(id: string, beforeId: string \| null) => void \| Promise<void>` | — | Rows reorder by a leading handle (drag, touch hold, Alt + ↑ / ↓): item `value` `id` goes before `beforeId` (`null` = last). Apply with `moveBefore`; a returned promise keeps the row busy, a rejection moves it back. |
48
+ | `labels` | `Partial<ListLabels>` | — | Built-in strings, see Labels. |
49
+ | `aria-label` | `string` | — | Name of the list for screen readers («Диалоги»), when no visible heading names it. |
50
+ | `children` | `ReactNode` | — | `List.Item`s. |
51
+ | `…rest` | `HTMLAttributes<HTMLUListElement>` | — | `className` and the other `ul` attributes. |
52
+
53
+ ### List.Item
54
+ `ref` → `HTMLElement` (the `<button>`, `<a>` or `asChild` element, not the `<li>`). `<li>` holding a full-width row: a button, a link (`href`) or your router link (`asChild`).
55
+
56
+ | Prop | Type | Default | Description |
57
+ |---|---|---|---|
58
+ | `value` | `string` | — | Stable id of the row; required in a reorderable list (`onReorder` reports it). |
59
+ | `current` | `boolean` | `false` | The open record: the selected fill and `aria-current` — `"true"` on a button, `"page"` on a link. |
60
+ | `onClick` | `(event: MouseEvent<HTMLButtonElement>) => void` | — | Opens the record; not called while `disabled`. |
61
+ | `href` | `string` | — | Renders an `<a>` instead of a `<button>`; dropped while `disabled`. |
62
+ | `asChild` | `boolean` | `false` | Renders the single child (a router link) as the row; its children are the content. |
63
+ | `disabled` | `boolean` | `false` | Muted and out of the Tab order: a disabled button, or `aria-disabled` and no navigation on a link. |
64
+ | `children` | `ReactNode` | — | `List.ItemIcon`, `List.ItemText`, `List.ItemDescription`, `List.ItemMeta`, `List.ItemCount`; loose text becomes the text line. |
65
+ | `…rest` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | `className` and native attributes of the row element (`target`, `rel` on a link). |
66
+
67
+ ### List.ItemIcon
68
+ `ref` → `HTMLSpanElement`. Leading `aria-hidden` slot: an `Avatar.Root` sized to the tier, or an `Icon`. After the text it is a trailing glyph.
69
+
70
+ | Prop | Type | Default | Description |
71
+ |---|---|---|---|
72
+ | `children` | `ReactNode` | — (required) | An `Avatar.Root` or an `Icon`. |
73
+ | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other `span` attributes. |
74
+
75
+ ### List.ItemText
76
+ `ref` → `HTMLSpanElement`. The primary line: medium weight, one line, truncated with an ellipsis.
77
+
78
+ | Prop | Type | Default | Description |
79
+ |---|---|---|---|
80
+ | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `className`, `title` (full text when truncated) and the other `span` attributes. |
81
+
82
+ ### List.ItemDescription
83
+ `ref` → `HTMLSpanElement`. The second line (a preview, a status): muted, smaller, truncated.
84
+
85
+ | Prop | Type | Default | Description |
86
+ |---|---|---|---|
87
+ | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `className` and the other `span` attributes. |
88
+
89
+ ### List.ItemMeta
90
+ `ref` → `HTMLSpanElement`. Small muted text at the end of the row (a time, a date, an amount); on top beside two lines.
91
+
92
+ | Prop | Type | Default | Description |
93
+ |---|---|---|---|
94
+ | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `className` and the other `span` attributes. |
95
+
96
+ ### List.ItemCount
97
+ `ref` → `HTMLSpanElement` (the Badge). A soft `Badge` one tier below the rows at the end of the row; below the meta beside two lines.
98
+
99
+ | Prop | Type | Default | Description |
100
+ |---|---|---|---|
101
+ | `color` | `"gray" \| "blue" \| "green" \| "orange" \| "red" \| "yellow" \| "purple" \| "sky" \| "pink" \| "teal"` | `"gray"` | Badge hue; a hue for what needs attention (unread). |
102
+ | `children` | `ReactNode` | — (required) | The number or a short status. |
103
+ | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children" \| "color">` | — | `className` and the other `span` attributes. |
104
+
105
+ ## Variants
106
+
107
+ ### size
108
+ | Value | Looks like | Use when | Default |
109
+ |---|---|---|---|
110
+ | `xs` | 28 one-line row, 12 text, caption description, 20 avatar | dense side panels | |
111
+ | `s` | 32 row, 13 text, caption description, 24 avatar | compact panes | |
112
+ | `m` | 36 row, 14 text, body-s description, 32 avatar | default | yes |
113
+ | `l` | 40 row, 16 text, 40 avatar | roomy panes, touch-first screens | |
114
+ | `xl` | 48 row, 16 text, 48 avatar | hero lists | |
115
+
116
+ A second line or an avatar makes a row taller than the one-line height; the vertical padding stays the tier's.
117
+
118
+ ### Item kind
119
+ | Value | Looks like | Use when | Default |
120
+ |---|---|---|---|
121
+ | button | the row opens a record in place (`onClick`) | a detail pane on the same screen | yes |
122
+ | link (`href`) | the same row as an `<a>` | each record has its own URL | |
123
+ | router link (`asChild`) | the same row as your `<Link>` | client routing | |
124
+
125
+ ### List.ItemCount color
126
+ `gray` (default) for a plain number; a hue for what needs attention (`blue` for unread). Any palette hue.
127
+
128
+ ## States
129
+ | State | Driven by | DOM |
130
+ |---|---|---|
131
+ | rest | — | transparent row, secondary text |
132
+ | hover | pointer (`@media (hover: hover)`) | `fill-subtle` wash, primary text |
133
+ | pressed | `:active` | `fill-subtle-active` |
134
+ | current | `current` | `aria-current="true"` (button) or `"page"` (link); `fill-muted-hover` fill, primary text |
135
+ | focus-visible | keyboard | the focus ring inside the row edge |
136
+ | 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 |
137
+ | reorderable | `onReorder` | `data-reorderable="true"` on the root (a `Dnd.Sortable` `<ul>`); each `<li>` is a sortable item (`data-dnd-item` = `value`) with a leading `Dnd.Handle`; drag by the handle (a touch hold on touch), Alt + ↑ / ↓ from the handle or the row; a lifted row leaves a dashed gap, its neighbours glide, the copy follows the pointer and sets down in its new place |
138
+ | saving a move | `onReorder` returned a pending promise | the row stays in its new place, `aria-busy="true"`, a `Spinner` stands in for the handle, the row content dims to 0.6 (`--prime-motion-duration-fast` both ways); a rejection glides the row back |
139
+
140
+ Root attribute: `data-size`. The fill changes on `background-color` with `--prime-motion-duration-xfast` both ways (0 under reduced motion).
141
+
142
+ ## Layout & spacing
143
+ - The root fills its container (`width: 100%`); rows are 4px apart, each row a full-width button with the tier radius and field padding.
144
+ - Put the list on a surface: a `Card.Root` pane in `PageContent.List`, with a `ScrollContainer` around it when it is long — the list never scrolls by itself.
145
+ - Row: leading icon · body (text over description) · trail. Beside two lines the trail stacks — meta on top, count below; beside one line they sit in a row.
146
+ - Text and description truncate with an ellipsis; give a long `List.ItemText` a `title` with the full text.
147
+ - On a coarse pointer a row is at least the touch target (44px).
148
+ - Reorderable (`onReorder`): an `xs` handle box sits before each row, 4px from it; the row keeps its own padding and fill.
149
+
150
+ ## Accessibility
151
+
152
+ ### Keyboard
153
+ | Key | Action |
154
+ |---|---|
155
+ | `Tab` | Moves focus between rows; a disabled row is skipped. |
156
+ | `Enter` · `Space` | Opens the record; a link row takes Enter only. |
157
+ | `Alt + ↑` · `Alt + ↓` | With `onReorder`, moves the row one place up or down (from its handle or the row). |
158
+
159
+ ### ARIA
160
+ - `<ul>` named by `aria-label` (or `aria-labelledby` to a visible heading); each row is an `<li>` with a button or link.
161
+ - The open record: `aria-current="true"` on a button (a selection inside the screen), `aria-current="page"` on a link (`href`, `asChild`) — the link points at the current URL. A router link may set it itself.
162
+ - 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.
163
+ - Arrow keys do not move between rows: rows are ordinary Tab stops, as in the Sidebar.
164
+ - With `onReorder` each row gets a handle named «Переместить {label}» (`labels.reorderItem`, `{label}` is the row's text line) and `aria-keyshortcuts`; the new place is announced politely («Новый лид: позиция 2 из 5»), a row being saved is `aria-busy`.
165
+
166
+ ### Labels
167
+ <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
168
+
169
+ | Key | Default | Used for |
170
+ |---|---|---|
171
+ | `reorderItem` | `"Переместить {label}"` | `aria-label` of a row's drag handle (`onReorder`); `{label}` is the row's text line. |
172
+
173
+ ## Examples
174
+ | Example | Shows |
175
+ |---|---|
176
+ | [overview.tsx](examples/overview.tsx) | Support dialogs with a preview, the time and the unread count; a click opens a dialog and makes it the current row — `current`, `onClick`. |
177
+ | [sizes.tsx](examples/sizes.tsx) | Every size tier: row height, text, icon and count follow the tier — `size`. |
178
+ | [states.tsx](examples/states.tsx) | The open record, a regular row and an unavailable one — `current`, `disabled`. |
179
+ | [with-icon.tsx](examples/with-icon.tsx) | Leading avatars of clients and icons of queues; the slot sizes the avatar to the row tier — `List.ItemIcon`. |
180
+ | [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. |
181
+ | [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`. |
182
+ | [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 — `onReorder`, `value`. |
183
+
184
+ ## Mistakes
185
+ - Rows hand-drawn from `Button.Root variant="ghost"` with a `soft` current → `List.Item current`.
186
+ - An `Avatar.Root size="s"` in `List.ItemIcon` to fit the row → leave `size` out; the slot sizes the avatar to the tier.
187
+ - A list as app navigation → Sidebar; as tabs of a screen → Tabs.
188
+ - Several rows `current` at once → one open record; bulk selection belongs to DataTable.
189
+ - A long list without a scroller → wrap it in `ScrollContainer` inside the pane.
190
+ - 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.
191
+ - `onReorder` that rolls the order back itself on an error → return the promise; a rejection moves the row back.
192
+
193
+ ## Related
194
+ - **Built from:** [Badge](../badge/COMPONENT.md) (`List.ItemCount`), [Dnd](../dnd/COMPONENT.md) (`onReorder`: `Dnd.Sortable`, `Dnd.Handle`), [Spinner](../spinner/COMPONENT.md) (a move being saved)
195
+ - **See also:** [PageContent](../page-content/COMPONENT.md), [Sidebar](../../layout/sidebar/COMPONENT.md), [Tabs](../tabs/COMPONENT.md), [DataTable](../data-table/COMPONENT.md), [Card](../card/COMPONENT.md), [Avatar](../avatar/COMPONENT.md)
@@ -0,0 +1,37 @@
1
+ /** Rows as links to the invoice pages: a plain link, or your router link through the slot — `href`, `asChild`. */
2
+ import { Card, List } from "prime-ui-kit";
3
+
4
+ import styles from "./examples.module.css";
5
+
6
+ const INVOICES = [
7
+ { id: "4830", client: "ООО «Гранит»", amount: "184 200 ₽", status: "Оплачен" },
8
+ { id: "4829", client: "ООО «Лето»", amount: "56 900 ₽", status: "Ждёт оплаты" },
9
+ { id: "4828", client: "ИП Козлов", amount: "12 400 ₽", status: "Просрочен" },
10
+ ];
11
+
12
+ export default function ListAsLinkExample() {
13
+ return (
14
+ <Card.Root className={styles.pane}>
15
+ <Card.Body>
16
+ <List.Root aria-label="Счета">
17
+ {INVOICES.map((invoice, index) => (
18
+ <List.Item key={invoice.id} href={`#invoice-${invoice.id}`} current={index === 0}>
19
+ <List.ItemText>Счёт № {invoice.id}</List.ItemText>
20
+ <List.ItemDescription>
21
+ {invoice.client} · {invoice.status}
22
+ </List.ItemDescription>
23
+ <List.ItemMeta>{invoice.amount}</List.ItemMeta>
24
+ </List.Item>
25
+ ))}
26
+ {/* A router link (`<Link to>`) goes in the same way; the row keeps its `href`. */}
27
+ <List.Item asChild>
28
+ <a href="#invoices-archive">
29
+ <List.ItemText>Архив счетов</List.ItemText>
30
+ <List.ItemDescription>Все счета за 2025 год</List.ItemDescription>
31
+ </a>
32
+ </List.Item>
33
+ </List.Root>
34
+ </Card.Body>
35
+ </Card.Root>
36
+ );
37
+ }
@@ -0,0 +1,18 @@
1
+ /* Layout of the List scenarios: the list stands in a pane as wide as the master side of a split. */
2
+
3
+ .pane {
4
+ width: min(100%, calc(var(--prime-space-24) * 4));
5
+ }
6
+
7
+ /* overflow: a pane of a fixed height whose list scrolls. */
8
+ .scroll {
9
+ height: calc(var(--prime-space-24) * 3);
10
+ }
11
+
12
+ /* sizes: a caption above each tier's list. */
13
+ .tier {
14
+ display: flex;
15
+ width: min(100%, calc(var(--prime-space-24) * 4));
16
+ flex-direction: column;
17
+ gap: var(--prime-space-2);
18
+ }
@@ -0,0 +1,46 @@
1
+ /** A long inbox in a pane of a fixed height: the list scrolls in a ScrollContainer and long names and previews truncate. */
2
+ import { Card, List, ScrollContainer } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ const TICKETS = [
8
+ { id: "1207", subject: "Возврат оплаты по счёту № 4821 за сентябрь", client: "ООО «Гранит»" },
9
+ { id: "1206", subject: "Смена тарифа", client: "ООО «Лето»" },
10
+ { id: "1205", subject: "Подключение ЭДО через Диадок", client: "ИП Козлов" },
11
+ {
12
+ id: "1204",
13
+ subject: "Акт сверки",
14
+ client: "АО «Северо-Западная транспортная компания»",
15
+ },
16
+ { id: "1203", subject: "Доступ к API для интеграции со складом", client: "ООО «Логистик Про»" },
17
+ { id: "1202", subject: "Перенос поставки", client: "ООО «Альфа-Трейд»" },
18
+ { id: "1201", subject: "Новый пользователь в команде", client: "ООО «Вектор»" },
19
+ { id: "1200", subject: "Ошибка в реквизитах", client: "ИП Смирнова" },
20
+ ];
21
+
22
+ export default function ListOverflowExample() {
23
+ const [ticketId, setTicketId] = React.useState("1207");
24
+
25
+ return (
26
+ <Card.Root className={styles.pane}>
27
+ <ScrollContainer className={styles.scroll}>
28
+ <Card.Body>
29
+ <List.Root aria-label="Обращения">
30
+ {TICKETS.map((ticket) => (
31
+ <List.Item
32
+ key={ticket.id}
33
+ current={ticket.id === ticketId}
34
+ onClick={() => setTicketId(ticket.id)}
35
+ >
36
+ <List.ItemText>{ticket.subject}</List.ItemText>
37
+ <List.ItemDescription>{ticket.client}</List.ItemDescription>
38
+ <List.ItemMeta>№ {ticket.id}</List.ItemMeta>
39
+ </List.Item>
40
+ ))}
41
+ </List.Root>
42
+ </Card.Body>
43
+ </ScrollContainer>
44
+ </Card.Root>
45
+ );
46
+ }
@@ -0,0 +1,63 @@
1
+ /** Support dialogs with a preview, the time and the unread count; a click opens a dialog and makes it the current row — `current`, `onClick`. */
2
+ import { Card, List } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ const DIALOGS = [
8
+ {
9
+ id: "granit",
10
+ client: "ООО «Гранит»",
11
+ preview: "Счёт № 4821 пришёл без НДС",
12
+ time: "15:02",
13
+ unread: 2,
14
+ },
15
+ {
16
+ id: "leto",
17
+ client: "ООО «Лето»",
18
+ preview: "Можно перенести доставку на среду?",
19
+ time: "13:40",
20
+ unread: 0,
21
+ },
22
+ {
23
+ id: "kozlov",
24
+ client: "ИП Козлов",
25
+ preview: "Подключите, пожалуйста, ЭДО",
26
+ time: "вчера",
27
+ unread: 1,
28
+ },
29
+ {
30
+ id: "sever",
31
+ client: "АО «Северсталь-Сервис»",
32
+ preview: "Акт сверки за сентябрь подписан",
33
+ time: "6 окт",
34
+ unread: 0,
35
+ },
36
+ ];
37
+
38
+ export default function ListOverviewExample() {
39
+ const [dialogId, setDialogId] = React.useState("granit");
40
+
41
+ return (
42
+ <Card.Root className={styles.pane}>
43
+ <Card.Body>
44
+ <List.Root aria-label="Диалоги">
45
+ {DIALOGS.map((dialog) => (
46
+ <List.Item
47
+ key={dialog.id}
48
+ current={dialog.id === dialogId}
49
+ onClick={() => setDialogId(dialog.id)}
50
+ >
51
+ <List.ItemText>{dialog.client}</List.ItemText>
52
+ <List.ItemDescription>{dialog.preview}</List.ItemDescription>
53
+ <List.ItemMeta>{dialog.time}</List.ItemMeta>
54
+ {dialog.unread > 0 ? (
55
+ <List.ItemCount color="blue">{dialog.unread}</List.ItemCount>
56
+ ) : null}
57
+ </List.Item>
58
+ ))}
59
+ </List.Root>
60
+ </Card.Body>
61
+ </Card.Root>
62
+ );
63
+ }
@@ -0,0 +1,48 @@
1
+ /** Funnel stages in the team's order: a stage moves by its handle or Alt+arrows and is saved on the server — `onReorder`, `value`. */
2
+ import { Card, List, moveBefore } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ const STAGES = [
8
+ { id: "lead", name: "Новый лид", description: "Заявка с сайта или от партнёра", deals: 42 },
9
+ { id: "qualified", name: "Квалификация", description: "Бюджет и сроки подтверждены", deals: 18 },
10
+ { id: "proposal", name: "Коммерческое предложение", description: "КП отправлено", deals: 11 },
11
+ { id: "contract", name: "Договор", description: "Согласование с юристами", deals: 6 },
12
+ { id: "won", name: "Оплата", description: "Счёт выставлен", deals: 3 },
13
+ ];
14
+
15
+ const SAVE_DELAY_MS = 600;
16
+
17
+ /** A stand-in for the request that stores the new order. */
18
+ function saveOrder(): Promise<void> {
19
+ return new Promise((resolve) => {
20
+ setTimeout(resolve, SAVE_DELAY_MS);
21
+ });
22
+ }
23
+
24
+ export default function ListReorderExample() {
25
+ const [stages, setStages] = React.useState(STAGES);
26
+
27
+ return (
28
+ <Card.Root className={styles.pane}>
29
+ <Card.Body>
30
+ <List.Root
31
+ aria-label="Этапы воронки"
32
+ onReorder={async (id, beforeId) => {
33
+ await saveOrder();
34
+ setStages((current) => moveBefore(current, id, beforeId, (stage) => stage.id));
35
+ }}
36
+ >
37
+ {stages.map((stage) => (
38
+ <List.Item key={stage.id} value={stage.id}>
39
+ <List.ItemText>{stage.name}</List.ItemText>
40
+ <List.ItemDescription>{stage.description}</List.ItemDescription>
41
+ <List.ItemCount>{stage.deals}</List.ItemCount>
42
+ </List.Item>
43
+ ))}
44
+ </List.Root>
45
+ </Card.Body>
46
+ </Card.Root>
47
+ );
48
+ }
@@ -0,0 +1,35 @@
1
+ /** Every size tier: row height, text, icon and count follow the tier — `size`. */
2
+ import { Icon, List, Typography } from "prime-ui-kit";
3
+
4
+ import styles from "./examples.module.css";
5
+
6
+ const SIZES = ["xs", "s", "m", "l", "xl"] as const;
7
+
8
+ export default function ListSizesExample() {
9
+ return (
10
+ <>
11
+ {SIZES.map((size) => (
12
+ <div key={size} className={styles.tier}>
13
+ <Typography as="span" variant="caption" tone="muted">
14
+ {size}
15
+ </Typography>
16
+ <List.Root size={size} aria-label={`Очереди, ${size}`}>
17
+ <List.Item current>
18
+ <List.ItemIcon>
19
+ <Icon name="object.inbox" />
20
+ </List.ItemIcon>
21
+ <List.ItemText>Входящие</List.ItemText>
22
+ <List.ItemCount color="blue">12</List.ItemCount>
23
+ </List.Item>
24
+ <List.Item>
25
+ <List.ItemIcon>
26
+ <Icon name="status.sent" />
27
+ </List.ItemIcon>
28
+ <List.ItemText>Отправленные</List.ItemText>
29
+ </List.Item>
30
+ </List.Root>
31
+ </div>
32
+ ))}
33
+ </>
34
+ );
35
+ }
@@ -0,0 +1,27 @@
1
+ /** The open record, a regular row and an unavailable one — `current`, `disabled`. */
2
+ import { Card, List } from "prime-ui-kit";
3
+
4
+ import styles from "./examples.module.css";
5
+
6
+ export default function ListStatesExample() {
7
+ return (
8
+ <Card.Root className={styles.pane}>
9
+ <Card.Body>
10
+ <List.Root aria-label="Заявки">
11
+ <List.Item current>
12
+ <List.ItemText>Заявка № 1207 · Возврат оплаты</List.ItemText>
13
+ <List.ItemDescription>Открыта сейчас</List.ItemDescription>
14
+ </List.Item>
15
+ <List.Item>
16
+ <List.ItemText>Заявка № 1206 · Смена тарифа</List.ItemText>
17
+ <List.ItemDescription>Ждёт ответа клиента</List.ItemDescription>
18
+ </List.Item>
19
+ <List.Item disabled>
20
+ <List.ItemText>Заявка № 1198 · Доступ к API</List.ItemText>
21
+ <List.ItemDescription>В архиве, недоступна</List.ItemDescription>
22
+ </List.Item>
23
+ </List.Root>
24
+ </Card.Body>
25
+ </Card.Root>
26
+ );
27
+ }
@@ -0,0 +1,60 @@
1
+ /** Leading avatars of clients and icons of queues; the slot sizes the avatar to the row tier — `List.ItemIcon`. */
2
+ import { Avatar, Card, Icon, List } from "prime-ui-kit";
3
+
4
+ import styles from "./examples.module.css";
5
+
6
+ const CLIENTS = [
7
+ { id: "belova", name: "Ирина Белова", company: "ООО «Гранит»", initials: "ИБ", color: "blue" },
8
+ { id: "orlov", name: "Павел Орлов", company: "ООО «Лето»", initials: "ПО", color: "green" },
9
+ { id: "kozlov", name: "Сергей Козлов", company: "ИП Козлов", initials: "СК", color: "orange" },
10
+ ] as const;
11
+
12
+ export default function ListWithIconExample() {
13
+ return (
14
+ <>
15
+ <Card.Root className={styles.pane}>
16
+ <Card.Body>
17
+ <List.Root aria-label="Клиенты">
18
+ {CLIENTS.map((client, index) => (
19
+ <List.Item key={client.id} current={index === 0}>
20
+ <List.ItemIcon>
21
+ <Avatar.Root color={client.color}>
22
+ <Avatar.Fallback>{client.initials}</Avatar.Fallback>
23
+ </Avatar.Root>
24
+ </List.ItemIcon>
25
+ <List.ItemText>{client.name}</List.ItemText>
26
+ <List.ItemDescription>{client.company}</List.ItemDescription>
27
+ </List.Item>
28
+ ))}
29
+ </List.Root>
30
+ </Card.Body>
31
+ </Card.Root>
32
+ <Card.Root className={styles.pane}>
33
+ <Card.Body>
34
+ <List.Root aria-label="Очереди">
35
+ <List.Item current>
36
+ <List.ItemIcon>
37
+ <Icon name="object.headset" />
38
+ </List.ItemIcon>
39
+ <List.ItemText>Поддержка</List.ItemText>
40
+ <List.ItemCount color="blue">8</List.ItemCount>
41
+ </List.Item>
42
+ <List.Item>
43
+ <List.ItemIcon>
44
+ <Icon name="object.receipt" />
45
+ </List.ItemIcon>
46
+ <List.ItemText>Биллинг</List.ItemText>
47
+ <List.ItemCount>3</List.ItemCount>
48
+ </List.Item>
49
+ <List.Item>
50
+ <List.ItemIcon>
51
+ <Icon name="object.truck" />
52
+ </List.ItemIcon>
53
+ <List.ItemText>Доставка</List.ItemText>
54
+ </List.Item>
55
+ </List.Root>
56
+ </Card.Body>
57
+ </Card.Root>
58
+ </>
59
+ );
60
+ }