prime-ui-kit 0.11.0 → 0.12.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 (57) hide show
  1. package/README.md +2 -1
  2. package/bin/prime-ui-kit.js +0 -0
  3. package/dist/chunks/{chunk-GKX3QAUC.js → chunk-OTOIOHVE.js} +1 -1
  4. package/dist/chunks/{chunk-WDTSHOTD.js → chunk-VOZ7J72X.js} +7 -2
  5. package/dist/chunks/{chunk-CUH6DJ57.js → chunk-YJO3NJIJ.js} +716 -548
  6. package/dist/color-picker.js +2 -2
  7. package/dist/components/index.d.ts +2 -0
  8. package/dist/components/list/List.d.ts +105 -0
  9. package/dist/index.css +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +6 -2
  12. package/dist/internal/VisuallyHidden.d.ts +6 -1
  13. package/dist/skill/api-contract.md +1 -1
  14. package/dist/skill/cheatsheet.md +1 -1
  15. package/dist/skill/choosing.md +4 -2
  16. package/dist/skill/components.md +2 -1
  17. package/dist/skill/composition.md +8 -0
  18. package/dist/skill/layouts.md +2 -1
  19. package/dist/skill/patterns/app-shell.module.css +18 -0
  20. package/dist/skill/patterns/app-shell.tsx +301 -0
  21. package/dist/skill/patterns/chat.module.css +0 -16
  22. package/dist/skill/patterns/chat.tsx +16 -19
  23. package/dist/skill/patterns/kanban-board.module.css +28 -0
  24. package/dist/skill/patterns/kanban-board.tsx +618 -0
  25. package/dist/skill/patterns/media-library.module.css +66 -0
  26. package/dist/skill/patterns/media-library.tsx +638 -0
  27. package/dist/skill/patterns/schedule.module.css +22 -0
  28. package/dist/skill/patterns/schedule.tsx +710 -0
  29. package/dist/skill/reference/components/file-upload/COMPONENT.md +3 -1
  30. package/dist/skill/reference/components/list/COMPONENT.md +180 -0
  31. package/dist/skill/reference/components/list/examples/as-link.tsx +37 -0
  32. package/dist/skill/reference/components/list/examples/examples.module.css +18 -0
  33. package/dist/skill/reference/components/list/examples/overflow.tsx +46 -0
  34. package/dist/skill/reference/components/list/examples/overview.tsx +63 -0
  35. package/dist/skill/reference/components/list/examples/sizes.tsx +35 -0
  36. package/dist/skill/reference/components/list/examples/states.tsx +27 -0
  37. package/dist/skill/reference/components/list/examples/with-icon.tsx +60 -0
  38. package/dist/skill/reference/components/page-content/COMPONENT.md +2 -2
  39. package/dist/skill/reference/components/page-content/examples/examples.module.css +5 -12
  40. package/dist/skill/reference/components/page-content/examples/list-detail.tsx +13 -18
  41. package/dist/skill/reference/components/visually-hidden/COMPONENT.md +6 -3
  42. package/dist/skill/reference/components/visually-hidden/examples/overview.tsx +2 -2
  43. package/package.json +3 -3
  44. package/src/components/file-upload/COMPONENT.md +3 -1
  45. package/src/components/list/COMPONENT.md +180 -0
  46. package/src/components/list/examples/as-link.tsx +37 -0
  47. package/src/components/list/examples/examples.module.css +18 -0
  48. package/src/components/list/examples/overflow.tsx +46 -0
  49. package/src/components/list/examples/overview.tsx +63 -0
  50. package/src/components/list/examples/sizes.tsx +35 -0
  51. package/src/components/list/examples/states.tsx +27 -0
  52. package/src/components/list/examples/with-icon.tsx +60 -0
  53. package/src/components/page-content/COMPONENT.md +2 -2
  54. package/src/components/page-content/examples/examples.module.css +5 -12
  55. package/src/components/page-content/examples/list-detail.tsx +13 -18
  56. package/src/components/visually-hidden/COMPONENT.md +6 -3
  57. package/src/components/visually-hidden/examples/overview.tsx +2 -2
@@ -33,9 +33,11 @@
33
33
 
34
34
  ## Import
35
35
  ```tsx
36
- import { FileUpload, type FileUploadFile, useUploadQueue } from "prime-ui-kit";
36
+ import { FileUpload, type FileUploadFile, formatFileSize, useUploadQueue } from "prime-ui-kit";
37
37
  ```
38
38
 
39
+ `formatFileSize(bytes)` writes a size the way the rows do («820 КБ», «1,2 МБ»): use it wherever a file size appears beside the kit's.
40
+
39
41
  ## Anatomy
40
42
  ```
41
43
  FileUpload.Root field frame: label → trigger → files → hint | error
@@ -0,0 +1,180 @@
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
+
12
+ ## When not to use
13
+ - App sections → use [Sidebar](../../layout/sidebar/COMPONENT.md) (wide) and [BottomNav](../../layout/bottom-nav/COMPONENT.md) (phone).
14
+ - Panels of one screen → use [Tabs](../tabs/COMPONENT.md).
15
+ - Many records compared by columns, sorted, filtered or selected in bulk → use [DataTable](../data-table/COMPONENT.md).
16
+ - A static list of facts inside a card (no selection, nothing opens) → use `Card.List` of [Card](../card/COMPONENT.md) `variant="list"`.
17
+ - Picking a value from options → use [Select](../select/COMPONENT.md) or [Radio](../radio/COMPONENT.md); a menu of commands → [Dropdown](../dropdown/COMPONENT.md).
18
+
19
+ ## Import
20
+ ```tsx
21
+ import { List } from "prime-ui-kit";
22
+ ```
23
+
24
+ ## Anatomy
25
+ ```
26
+ List.Root <ul aria-label>; size → row tier (ControlSizeProvider)
27
+ └─ List.Item <li> with a full-width <button> | <a href> | asChild element; current, disabled
28
+ ├─ List.ItemIcon leading Avatar.Root / Icon (aria-hidden); after the text — a trailing glyph
29
+ ├─ List.ItemText primary line, truncates
30
+ ├─ List.ItemDescription second line, muted, truncates
31
+ ├─ List.ItemMeta trailing small text (time, date, amount)
32
+ └─ List.ItemCount trailing Badge, one tier down
33
+ ```
34
+
35
+ ## API
36
+
37
+ <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
38
+
39
+ ### List.Root
40
+ `ref` → `HTMLUListElement`. `<ul>` of selectable rows: the master half of a list + detail screen; sets the row tier for its items and their parts.
41
+
42
+ | Prop | Type | Default | Description |
43
+ |---|---|---|---|
44
+ | `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | `"m"` | Row tier: height, text, icon and avatar size; counts take one tier down. |
45
+ | `aria-label` | `string` | — | Name of the list for screen readers («Диалоги»), when no visible heading names it. |
46
+ | `children` | `ReactNode` | — | `List.Item`s. |
47
+ | `…rest` | `HTMLAttributes<HTMLUListElement>` | — | `className` and the other `ul` attributes. |
48
+
49
+ ### List.Item
50
+ `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`).
51
+
52
+ | Prop | Type | Default | Description |
53
+ |---|---|---|---|
54
+ | `current` | `boolean` | `false` | The open record: the selected fill and `aria-current` — `"true"` on a button, `"page"` on a link. |
55
+ | `onClick` | `(event: MouseEvent<HTMLButtonElement>) => void` | — | Opens the record; not called while `disabled`. |
56
+ | `href` | `string` | — | Renders an `<a>` instead of a `<button>`; dropped while `disabled`. |
57
+ | `asChild` | `boolean` | `false` | Renders the single child (a router link) as the row; its children are the content. |
58
+ | `disabled` | `boolean` | `false` | Muted and out of the Tab order: a disabled button, or `aria-disabled` and no navigation on a link. |
59
+ | `children` | `ReactNode` | — | `List.ItemIcon`, `List.ItemText`, `List.ItemDescription`, `List.ItemMeta`, `List.ItemCount`; loose text becomes the text line. |
60
+ | `…rest` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | `className` and native attributes of the row element (`target`, `rel` on a link). |
61
+
62
+ ### List.ItemIcon
63
+ `ref` → `HTMLSpanElement`. Leading `aria-hidden` slot: an `Avatar.Root` sized to the tier, or an `Icon`. After the text it is a trailing glyph.
64
+
65
+ | Prop | Type | Default | Description |
66
+ |---|---|---|---|
67
+ | `children` | `ReactNode` | — (required) | An `Avatar.Root` or an `Icon`. |
68
+ | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other `span` attributes. |
69
+
70
+ ### List.ItemText
71
+ `ref` → `HTMLSpanElement`. The primary line: medium weight, one line, truncated with an ellipsis.
72
+
73
+ | Prop | Type | Default | Description |
74
+ |---|---|---|---|
75
+ | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `className`, `title` (full text when truncated) and the other `span` attributes. |
76
+
77
+ ### List.ItemDescription
78
+ `ref` → `HTMLSpanElement`. The second line (a preview, a status): muted, smaller, truncated.
79
+
80
+ | Prop | Type | Default | Description |
81
+ |---|---|---|---|
82
+ | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `className` and the other `span` attributes. |
83
+
84
+ ### List.ItemMeta
85
+ `ref` → `HTMLSpanElement`. Small muted text at the end of the row (a time, a date, an amount); on top beside two lines.
86
+
87
+ | Prop | Type | Default | Description |
88
+ |---|---|---|---|
89
+ | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `className` and the other `span` attributes. |
90
+
91
+ ### List.ItemCount
92
+ `ref` → `HTMLSpanElement` (the Badge). A soft `Badge` one tier below the rows at the end of the row; below the meta beside two lines.
93
+
94
+ | Prop | Type | Default | Description |
95
+ |---|---|---|---|
96
+ | `color` | `"gray" \| "blue" \| "green" \| "orange" \| "red" \| "yellow" \| "purple" \| "sky" \| "pink" \| "teal"` | `"gray"` | Badge hue; a hue for what needs attention (unread). |
97
+ | `children` | `ReactNode` | — (required) | The number or a short status. |
98
+ | `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children" \| "color">` | — | `className` and the other `span` attributes. |
99
+
100
+ ## Variants
101
+
102
+ ### size
103
+ | Value | Looks like | Use when | Default |
104
+ |---|---|---|---|
105
+ | `xs` | 28 one-line row, 12 text, caption description, 20 avatar | dense side panels | |
106
+ | `s` | 32 row, 13 text, caption description, 24 avatar | compact panes | |
107
+ | `m` | 36 row, 14 text, body-s description, 32 avatar | default | yes |
108
+ | `l` | 40 row, 16 text, 40 avatar | roomy panes, touch-first screens | |
109
+ | `xl` | 48 row, 16 text, 48 avatar | hero lists | |
110
+
111
+ A second line or an avatar makes a row taller than the one-line height; the vertical padding stays the tier's.
112
+
113
+ ### Item kind
114
+ | Value | Looks like | Use when | Default |
115
+ |---|---|---|---|
116
+ | button | the row opens a record in place (`onClick`) | a detail pane on the same screen | yes |
117
+ | link (`href`) | the same row as an `<a>` | each record has its own URL | |
118
+ | router link (`asChild`) | the same row as your `<Link>` | client routing | |
119
+
120
+ ### List.ItemCount color
121
+ `gray` (default) for a plain number; a hue for what needs attention (`blue` for unread). Any palette hue.
122
+
123
+ ## States
124
+ | State | Driven by | DOM |
125
+ |---|---|---|
126
+ | rest | — | transparent row, secondary text |
127
+ | hover | pointer (`@media (hover: hover)`) | `fill-subtle` wash, primary text |
128
+ | pressed | `:active` | `fill-subtle-active` |
129
+ | current | `current` | `aria-current="true"` (button) or `"page"` (link); `fill-muted-hover` fill, primary text |
130
+ | focus-visible | keyboard | the focus ring inside the row edge |
131
+ | 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 |
132
+
133
+ Root attribute: `data-size`. The fill changes on `background-color` with `--prime-motion-duration-xfast` both ways (0 under reduced motion).
134
+
135
+ ## Layout & spacing
136
+ - The root fills its container (`width: 100%`); rows are 4px apart, each row a full-width button with the tier radius and field padding.
137
+ - 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.
138
+ - 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.
139
+ - Text and description truncate with an ellipsis; give a long `List.ItemText` a `title` with the full text.
140
+ - On a coarse pointer a row is at least the touch target (44px).
141
+
142
+ ## Accessibility
143
+
144
+ ### Keyboard
145
+ | Key | Action |
146
+ |---|---|
147
+ | `Tab` | Moves focus between rows; a disabled row is skipped. |
148
+ | `Enter` · `Space` | Opens the record; a link row takes Enter only. |
149
+
150
+ ### ARIA
151
+ - `<ul>` named by `aria-label` (or `aria-labelledby` to a visible heading); each row is an `<li>` with a button or link.
152
+ - 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.
153
+ - 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.
154
+ - Arrow keys do not move between rows: rows are ordinary Tab stops, as in the Sidebar.
155
+
156
+ ### Labels
157
+ <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
158
+
159
+ No `labels`.
160
+
161
+ ## Examples
162
+ | Example | Shows |
163
+ |---|---|
164
+ | [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`. |
165
+ | [sizes.tsx](examples/sizes.tsx) | Every size tier: row height, text, icon and count follow the tier — `size`. |
166
+ | [states.tsx](examples/states.tsx) | The open record, a regular row and an unavailable one — `current`, `disabled`. |
167
+ | [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`. |
168
+ | [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. |
169
+ | [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`. |
170
+
171
+ ## Mistakes
172
+ - Rows hand-drawn from `Button.Root variant="ghost"` with a `soft` current → `List.Item current`.
173
+ - An `Avatar.Root size="s"` in `List.ItemIcon` to fit the row → leave `size` out; the slot sizes the avatar to the tier.
174
+ - A list as app navigation → Sidebar; as tabs of a screen → Tabs.
175
+ - Several rows `current` at once → one open record; bulk selection belongs to DataTable.
176
+ - A long list without a scroller → wrap it in `ScrollContainer` inside the pane.
177
+
178
+ ## Related
179
+ - **Built from:** [Badge](../badge/COMPONENT.md) (`List.ItemCount`)
180
+ - **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,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
+ }
@@ -170,7 +170,7 @@ One `PageContent.Title` (`<h1>`) per page; sub-sections inside Body use `title-s
170
170
  - Header is a wrapping row: actions stay to the right of the title while the heading keeps half a reading measure, otherwise they wrap below it — no breakpoint needed, works from 320px.
171
171
  - Title and Description wrap long words (`overflow-wrap: anywhere`).
172
172
  - `fill` needs a main with a height: `AppShell.Root fillViewport` (main scrolls inside the panel). The body passes its free height on to a filling child — DataTable `scrollHeight="fill"`, Crossfade `fill`, `PageContent.Split`.
173
- - Split takes the free height of a filling page and lays itself out from its own width (container `prime-split`), never from the window: a split beside an open Sidebar may be narrow on a wide screen. Each pane is a column whose last child takes the free height — put a Card with its own `ScrollContainer` there, so the list and the thread scroll separately.
173
+ - Split takes the free height of a filling page and lays itself out from its own width (container `prime-split`), never from the window: a split beside an open Sidebar may be narrow on a wide screen. Each pane is a column whose last child takes the free height — put a Card with its own `ScrollContainer` there, so the list and the thread scroll separately. The records of the list pane are a [List](../list/COMPONENT.md).
174
174
  - The detail pane is on screen all the time on a wide split: while nothing is chosen it shows a short hint («Выберите диалог»), never an empty card.
175
175
 
176
176
  ## Accessibility
@@ -212,4 +212,4 @@ One `PageContent.Title` (`<h1>`) per page; sub-sections inside Body use `title-s
212
212
 
213
213
  ## Related
214
214
  - **Built from:** [Button](../button/COMPONENT.md) (`PageContent.BackButton`), Icon (`nav.chevronLeft`)
215
- - **See also:** [AppShell](../../layout/app-shell/COMPONENT.md), [Sidebar](../../layout/sidebar/COMPONENT.md), [Card](../card/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md), [Drawer](../drawer/COMPONENT.md)
215
+ - **See also:** [AppShell](../../layout/app-shell/COMPONENT.md), [Sidebar](../../layout/sidebar/COMPONENT.md), [Card](../card/COMPONENT.md), [List](../list/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md), [Drawer](../drawer/COMPONENT.md)
@@ -46,24 +46,17 @@
46
46
  min-height: 0;
47
47
  }
48
48
 
49
- .dialogs,
50
- .messages {
51
- display: flex;
52
- flex-direction: column;
53
- gap: var(--prime-space-1);
54
- margin: 0;
49
+ .dialogs {
55
50
  padding: var(--prime-space-2);
56
- list-style: none;
57
51
  }
58
52
 
59
53
  .messages {
54
+ display: flex;
55
+ flex-direction: column;
60
56
  gap: var(--prime-space-3);
57
+ margin: 0;
61
58
  padding: var(--prime-space-2) var(--prime-space-4) var(--prime-space-4);
62
- }
63
-
64
- /* A dialog row: the name at the start, the unread count at the end. */
65
- .dialog {
66
- justify-content: space-between;
59
+ list-style: none;
67
60
  }
68
61
 
69
62
  .hint {
@@ -1,5 +1,5 @@
1
1
  /** A support inbox as list + detail on a full-height page: side by side on a wide panel, one pane at a time with a back button on a narrow one — `PageContent.Split`, `PageContent.BackButton`, `fill`. */
2
- import { Badge, Button, Card, PageContent, ScrollContainer, Typography } from "prime-ui-kit";
2
+ import { Card, List, PageContent, ScrollContainer, Typography } from "prime-ui-kit";
3
3
  import * as React from "react";
4
4
 
5
5
  import styles from "./examples.module.css";
@@ -58,25 +58,20 @@ export default function PageContentListDetailExample() {
58
58
  <PageContent.List aria-label="Диалоги">
59
59
  <Card.Root className={styles.paneCard}>
60
60
  <ScrollContainer className={styles.pane}>
61
- <ul className={styles.dialogs}>
61
+ <List.Root className={styles.dialogs}>
62
62
  {DIALOGS.map((dialog) => (
63
- <li key={dialog.id}>
64
- <Button.Root
65
- variant={dialog.id === dialogId ? "soft" : "ghost"}
66
- tone="neutral"
67
- fullWidth
68
- aria-current={dialog.id === dialogId ? "true" : undefined}
69
- className={styles.dialog}
70
- onClick={() => open(dialog)}
71
- >
72
- {dialog.client}
73
- {dialog.unread > 0 ? (
74
- <Badge.Root color="blue">{dialog.unread}</Badge.Root>
75
- ) : null}
76
- </Button.Root>
77
- </li>
63
+ <List.Item
64
+ key={dialog.id}
65
+ current={dialog.id === dialogId}
66
+ onClick={() => open(dialog)}
67
+ >
68
+ <List.ItemText>{dialog.client}</List.ItemText>
69
+ {dialog.unread > 0 ? (
70
+ <List.ItemCount color="blue">{dialog.unread}</List.ItemCount>
71
+ ) : null}
72
+ </List.Item>
78
73
  ))}
79
- </ul>
74
+ </List.Root>
80
75
  </ScrollContainer>
81
76
  </Card.Root>
82
77
  </PageContent.List>
@@ -36,7 +36,8 @@ VisuallyHidden <span>: out of sight (1px, clipped), pinned to its containing
36
36
  | Prop | Type | Default | Description |
37
37
  |---|---|---|---|
38
38
  | `children` | `ReactNode` | — | The text to read. |
39
- | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `id` (to point `aria-describedby` at it), `role="status"` with `aria-live` for an announcement, `className` and the other span attributes. |
39
+ | `live` | `"polite" \| "assertive"` | — | An announcement region, read as a whole on every change: `polite` — `role="status"`, `assertive` — `role="alert"` (urgent only). |
40
+ | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `id` (to point `aria-describedby` at it), `className` and the other span attributes. |
40
41
 
41
42
  ## Variants
42
43
  One look: none. It renders no pixels and takes no room.
@@ -45,7 +46,7 @@ One look: none. It renders no pixels and takes no room.
45
46
  | State | Driven by | DOM |
46
47
  |---|---|---|
47
48
  | hidden text | children | a clipped `<span>` |
48
- | live region | `role="status"` + `aria-live="polite"` | read when its text changes; mount it empty first, then set the message |
49
+ | live region | `live="polite"` (`"assertive"` for an urgent one) | `role="status"` (`"alert"`), `aria-live`, `aria-atomic`: read as a whole when its text changes; mount it empty first, then set the message |
49
50
 
50
51
  ## Layout & spacing
51
52
  - Takes no room and never scrolls its column: it is pinned to the start of the nearest positioned ancestor.
@@ -58,7 +59,7 @@ No keyboard interaction.
58
59
 
59
60
  ### ARIA
60
61
  - The text stays in the accessibility tree: it joins the accessible name of the element around it.
61
- - With `role="status"` it is a polite live region; keep it mounted and change its text — a region mounted together with its message is often not read.
62
+ - With `live="polite"` it is a polite live region (`role="status"`), with `live="assertive"` an alert (`role="alert"`) for what cannot wait; keep it mounted and change its text — a region mounted together with its message is often not read.
62
63
 
63
64
  ### Labels
64
65
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
@@ -73,6 +74,8 @@ No `labels`.
73
74
  ## Mistakes
74
75
  - `display: none` or `hidden` for screen-reader text → both remove it from the accessibility tree.
75
76
  - A live region mounted with its first message → mount it empty, then set the text.
77
+ - `role="status"` and `aria-live` written by hand → `live="polite"` sets them together, atomic.
78
+ - `live="assertive"` for a routine result («Сохранено») → it interrupts the reader; keep it for errors that block.
76
79
  - VisuallyHidden as the only name of a button that has `aria-label` → the `aria-label` wins; use one of them.
77
80
  - Hidden text that repeats the visible label → screen readers read it twice.
78
81
 
@@ -1,4 +1,4 @@
1
- /** A count that sighted users read from the layout, said in words to screen readers, and a polite announcement after an action — `VisuallyHidden`. */
1
+ /** A count that sighted users read from the layout, said in words to screen readers, and a polite announcement after an action — `VisuallyHidden`, `live`. */
2
2
  import { Badge, Button, Typography, VisuallyHidden } from "prime-ui-kit";
3
3
  import * as React from "react";
4
4
 
@@ -14,7 +14,7 @@ export default function VisuallyHiddenOverviewExample() {
14
14
  <Button.Root variant="soft" tone="neutral" onClick={() => setArchived((count) => count + 1)}>
15
15
  В архив
16
16
  </Button.Root>
17
- <VisuallyHidden role="status" aria-live="polite">
17
+ <VisuallyHidden live="polite">
18
18
  {archived > 0 ? `Счетов в архиве: ${archived}` : ""}
19
19
  </VisuallyHidden>
20
20
  </>