prime-ui-kit 0.20.1 → 0.21.1
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.
- package/README.md +1 -1
- package/dist/chunks/{chunk-4KONMO7Z.js → chunk-6L7VWEY6.js} +1 -1
- package/dist/chunks/{chunk-3PY52JP3.js → chunk-S27VR4ON.js} +5828 -5596
- package/dist/chunks/{chunk-4374EBWV.js → chunk-S4H2ZKWJ.js} +1049 -644
- package/dist/color-picker.js +2 -2
- package/dist/components/badge/Badge.d.ts +19 -2
- package/dist/components/card/Card.d.ts +10 -3
- package/dist/components/composer/Composer.d.ts +49 -1
- package/dist/components/crossfade/Crossfade.d.ts +6 -4
- package/dist/components/data-table/DataTable.d.ts +10 -2
- package/dist/components/data-table/Row.d.ts +12 -1
- package/dist/components/data-table/SelectionMenu.d.ts +20 -0
- package/dist/components/data-table/useRowSelection.d.ts +2 -0
- package/dist/components/empty-page/EmptyPage.d.ts +5 -2
- package/dist/components/hint/Hint.d.ts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/input/Input.d.ts +15 -3
- package/dist/components/message/Message.d.ts +6 -3
- package/dist/components/segmented-control/SegmentedControl.d.ts +5 -1
- package/dist/components/smart-filter/SmartFilter.d.ts +4 -0
- package/dist/components/spinner/Spinner.d.ts +3 -2
- package/dist/components/tabs/Tabs.d.ts +10 -1
- package/dist/hooks/useModalLayer.d.ts +15 -8
- package/dist/index.css +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/RegionSwap.d.ts +18 -10
- package/dist/internal/RollingNumber.d.ts +7 -5
- package/dist/internal/ToolRow.d.ts +6 -0
- package/dist/internal/mediaSlot.d.ts +29 -0
- package/dist/internal/overlay/DialogParts.d.ts +30 -6
- package/dist/internal/overlay/focus.d.ts +18 -3
- package/dist/internal/partPresence.d.ts +19 -2
- package/dist/layout/page/Page.d.ts +1 -1
- package/dist/layout/sidebar/Sidebar.d.ts +3 -2
- package/dist/skill/anti-slop.md +35 -37
- package/dist/skill/api-contract.md +50 -2
- package/dist/skill/cheatsheet.md +15 -10
- package/dist/skill/checklist.md +20 -6
- package/dist/skill/choosing.md +82 -8
- package/dist/skill/components.md +1 -1
- package/dist/skill/composition.md +268 -51
- package/dist/skill/foundations.md +17 -6
- package/dist/skill/layouts.md +65 -14
- package/dist/skill/motion.md +9 -3
- package/dist/skill/reference/components/avatar/COMPONENT.md +1 -0
- package/dist/skill/reference/components/badge/COMPONENT.md +29 -5
- package/dist/skill/reference/components/badge/examples/examples.module.css +14 -0
- package/dist/skill/reference/components/badge/examples/overflow-count.tsx +32 -0
- package/dist/skill/reference/components/banner/COMPONENT.md +1 -1
- package/dist/skill/reference/components/card/COMPONENT.md +8 -4
- package/dist/skill/reference/components/card/examples/person-header.tsx +38 -0
- package/dist/skill/reference/components/composer/COMPONENT.md +35 -5
- package/dist/skill/reference/components/composer/examples/bot-keyboard.tsx +43 -0
- package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
- package/dist/skill/reference/components/crossfade/COMPONENT.md +18 -10
- package/dist/skill/reference/components/data-table/COMPONENT.md +17 -11
- package/dist/skill/reference/components/data-table/examples/screen.tsx +6 -9
- package/dist/skill/reference/components/data-table/examples/selection.tsx +60 -18
- package/dist/skill/reference/components/drawer/COMPONENT.md +41 -19
- package/dist/skill/reference/components/drawer/examples/footer-actions.tsx +54 -0
- package/dist/skill/reference/components/drawer/examples/header-actions.tsx +46 -25
- package/dist/skill/reference/components/drawer/examples/link-loading.tsx +57 -0
- package/dist/skill/reference/components/drawer/examples/screen.tsx +3 -5
- package/dist/skill/reference/components/empty-page/COMPONENT.md +2 -2
- package/dist/skill/reference/components/field-list/COMPONENT.md +4 -4
- package/dist/skill/reference/components/field-list/examples/row-menu.tsx +1 -1
- package/dist/skill/reference/components/hint/COMPONENT.md +1 -1
- package/dist/skill/reference/components/input/COMPONENT.md +16 -3
- package/dist/skill/reference/components/input/examples/password-policy.tsx +65 -0
- package/dist/skill/reference/components/list/COMPONENT.md +1 -1
- package/dist/skill/reference/components/login-form/COMPONENT.md +13 -11
- package/dist/skill/reference/components/login-form/examples/overview.tsx +5 -5
- package/dist/skill/reference/components/login-form/examples/states.tsx +7 -6
- package/dist/skill/reference/components/message/COMPONENT.md +5 -5
- package/dist/skill/reference/components/message/examples/bot-actions.tsx +47 -28
- package/dist/skill/reference/components/modal/COMPONENT.md +31 -12
- package/dist/skill/reference/components/modal/examples/structure.tsx +2 -4
- package/dist/skill/reference/components/segmented-control/COMPONENT.md +11 -6
- package/dist/skill/reference/components/segmented-control/examples/router.tsx +22 -0
- package/dist/skill/reference/components/select/COMPONENT.md +4 -1
- package/dist/skill/reference/components/sheet/COMPONENT.md +31 -12
- package/dist/skill/reference/components/sheet/examples/header-actions.tsx +5 -3
- package/dist/skill/reference/components/skeleton/COMPONENT.md +3 -2
- package/dist/skill/reference/components/smart-filter/COMPONENT.md +2 -1
- package/dist/skill/reference/components/smart-filter/examples/ranges.tsx +8 -2
- package/dist/skill/reference/components/spinner/COMPONENT.md +1 -1
- package/dist/skill/reference/components/tabs/COMPONENT.md +7 -4
- package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +18 -2
- package/dist/skill/reference/components/thumbnail/COMPONENT.md +1 -0
- package/dist/skill/reference/components/tooltip/COMPONENT.md +3 -3
- package/dist/skill/reference/foundation.md +15 -9
- package/dist/skill/reference/layout/page/COMPONENT.md +10 -6
- package/dist/skill/reference/layout/page/examples/router.tsx +124 -0
- package/dist/skill/reference/layout/page/examples/tools.tsx +12 -22
- package/dist/skill/reference/layout/sidebar/COMPONENT.md +8 -3
- package/dist/skill/reference/layout/sidebar/examples/account-profile.tsx +66 -0
- package/dist/skill/responsive.md +28 -6
- package/dist/skill/ux.md +97 -2
- package/dist/tokens/semantic.d.ts +2 -1
- package/dist/tokens/themes/dark.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/avatar/COMPONENT.md +1 -0
- package/src/components/badge/COMPONENT.md +29 -5
- package/src/components/badge/examples/examples.module.css +14 -0
- package/src/components/badge/examples/overflow-count.tsx +32 -0
- package/src/components/banner/COMPONENT.md +1 -1
- package/src/components/card/COMPONENT.md +8 -4
- package/src/components/card/examples/person-header.tsx +38 -0
- package/src/components/composer/COMPONENT.md +35 -5
- package/src/components/composer/examples/bot-keyboard.tsx +43 -0
- package/src/components/confirm-dialog/COMPONENT.md +1 -1
- package/src/components/crossfade/COMPONENT.md +18 -10
- package/src/components/data-table/COMPONENT.md +17 -11
- package/src/components/data-table/examples/screen.tsx +6 -9
- package/src/components/data-table/examples/selection.tsx +60 -18
- package/src/components/drawer/COMPONENT.md +41 -19
- package/src/components/drawer/examples/footer-actions.tsx +54 -0
- package/src/components/drawer/examples/header-actions.tsx +46 -25
- package/src/components/drawer/examples/link-loading.tsx +57 -0
- package/src/components/drawer/examples/screen.tsx +3 -5
- package/src/components/empty-page/COMPONENT.md +2 -2
- package/src/components/field-list/COMPONENT.md +4 -4
- package/src/components/field-list/examples/row-menu.tsx +1 -1
- package/src/components/hint/COMPONENT.md +1 -1
- package/src/components/input/COMPONENT.md +16 -3
- package/src/components/input/examples/password-policy.tsx +65 -0
- package/src/components/list/COMPONENT.md +1 -1
- package/src/components/login-form/COMPONENT.md +13 -11
- package/src/components/login-form/examples/overview.tsx +5 -5
- package/src/components/login-form/examples/states.tsx +7 -6
- package/src/components/message/COMPONENT.md +5 -5
- package/src/components/message/examples/bot-actions.tsx +47 -28
- package/src/components/modal/COMPONENT.md +31 -12
- package/src/components/modal/examples/structure.tsx +2 -4
- package/src/components/segmented-control/COMPONENT.md +11 -6
- package/src/components/segmented-control/examples/router.tsx +22 -0
- package/src/components/select/COMPONENT.md +4 -1
- package/src/components/sheet/COMPONENT.md +31 -12
- package/src/components/sheet/examples/header-actions.tsx +5 -3
- package/src/components/skeleton/COMPONENT.md +3 -2
- package/src/components/smart-filter/COMPONENT.md +2 -1
- package/src/components/smart-filter/examples/ranges.tsx +8 -2
- package/src/components/spinner/COMPONENT.md +1 -1
- package/src/components/tabs/COMPONENT.md +7 -4
- package/src/components/tabs/examples/in-drawer.tsx +18 -2
- package/src/components/thumbnail/COMPONENT.md +1 -0
- package/src/components/tooltip/COMPONENT.md +3 -3
- package/src/layout/page/COMPONENT.md +10 -6
- package/src/layout/page/examples/router.tsx +124 -0
- package/src/layout/page/examples/tools.tsx +12 -22
- package/src/layout/sidebar/COMPONENT.md +8 -3
- package/src/layout/sidebar/examples/account-profile.tsx +66 -0
- package/src/styles/theme-dark.css +1 -0
- package/src/styles/theme-light.css +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The signed-in person opens a profile panel, not a menu, so the row draws no chevron — `Sidebar.Account`, `description`. */
|
|
2
|
+
import { Avatar, Button, DescriptionList, Drawer, Icon, Sidebar } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
const PROFILE = [
|
|
7
|
+
{ term: "Почта", value: "anna@company.ru" },
|
|
8
|
+
{ term: "Роль", value: "Руководитель отдела продаж" },
|
|
9
|
+
{ term: "Часовой пояс", value: "Москва, UTC+3" },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export default function SidebarAccountProfileExample() {
|
|
13
|
+
return (
|
|
14
|
+
<div className={styles.stage}>
|
|
15
|
+
<Sidebar.Root offCanvas="never">
|
|
16
|
+
<Sidebar.Content>
|
|
17
|
+
<Sidebar.Item current>
|
|
18
|
+
<Sidebar.ItemIcon>
|
|
19
|
+
<Icon name="nav.home" />
|
|
20
|
+
</Sidebar.ItemIcon>
|
|
21
|
+
Обзор
|
|
22
|
+
</Sidebar.Item>
|
|
23
|
+
<Sidebar.Item>
|
|
24
|
+
<Sidebar.ItemIcon>
|
|
25
|
+
<Icon name="object.document" />
|
|
26
|
+
</Sidebar.ItemIcon>
|
|
27
|
+
Сделки
|
|
28
|
+
</Sidebar.Item>
|
|
29
|
+
</Sidebar.Content>
|
|
30
|
+
<Sidebar.Footer>
|
|
31
|
+
<Drawer.Root>
|
|
32
|
+
<Drawer.Trigger>
|
|
33
|
+
<Sidebar.Account description="anna@company.ru">
|
|
34
|
+
<Avatar.Root color="purple">
|
|
35
|
+
<Avatar.Fallback>АС</Avatar.Fallback>
|
|
36
|
+
</Avatar.Root>
|
|
37
|
+
Анна Смирнова
|
|
38
|
+
</Sidebar.Account>
|
|
39
|
+
</Drawer.Trigger>
|
|
40
|
+
<Drawer.Content size="s">
|
|
41
|
+
<Drawer.Header>
|
|
42
|
+
<Drawer.Title>Анна Смирнова</Drawer.Title>
|
|
43
|
+
<Drawer.Description>Профиль</Drawer.Description>
|
|
44
|
+
</Drawer.Header>
|
|
45
|
+
<Drawer.Body>
|
|
46
|
+
<DescriptionList.Root>
|
|
47
|
+
{PROFILE.map(({ term, value }) => (
|
|
48
|
+
<DescriptionList.Item key={term} label={term}>
|
|
49
|
+
{value}
|
|
50
|
+
</DescriptionList.Item>
|
|
51
|
+
))}
|
|
52
|
+
</DescriptionList.Root>
|
|
53
|
+
</Drawer.Body>
|
|
54
|
+
<Drawer.Footer>
|
|
55
|
+
<Button.Root variant="outline" tone="neutral">
|
|
56
|
+
Выйти
|
|
57
|
+
</Button.Root>
|
|
58
|
+
</Drawer.Footer>
|
|
59
|
+
</Drawer.Content>
|
|
60
|
+
</Drawer.Root>
|
|
61
|
+
</Sidebar.Footer>
|
|
62
|
+
</Sidebar.Root>
|
|
63
|
+
<div className={styles.content} />
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
package/dist/skill/responsive.md
CHANGED
|
@@ -119,7 +119,7 @@ const FIELDS: SmartFilterField[] = [
|
|
|
119
119
|
{ key: "city", label: "Город", options: [{ value: "msk", label: "Москва" }] },
|
|
120
120
|
];
|
|
121
121
|
|
|
122
|
-
export function
|
|
122
|
+
export function DealsPage() {
|
|
123
123
|
const [section, setSection] = useState("all");
|
|
124
124
|
const [period, setPeriod] = useState("30d");
|
|
125
125
|
const [filter, setFilter] = useState<SmartFilterValue>({});
|
|
@@ -136,10 +136,10 @@ export function OrdersPage() {
|
|
|
136
136
|
<Page.Root>
|
|
137
137
|
<Page.TopBar>
|
|
138
138
|
<Page.Heading>
|
|
139
|
-
<Page.Title
|
|
140
|
-
<Page.Description>128 в работе · 6
|
|
139
|
+
<Page.Title>Сделки</Page.Title>
|
|
140
|
+
<Page.Description>128 в работе · 6,5 млн ₽</Page.Description>
|
|
141
141
|
</Page.Heading>
|
|
142
|
-
<Page.Tools aria-label="
|
|
142
|
+
<Page.Tools aria-label="Сделки">
|
|
143
143
|
<SegmentedControl.Root value={section} onValueChange={setSection} aria-label="Раздел">
|
|
144
144
|
<SegmentedControl.Item value="all">
|
|
145
145
|
Все<SegmentedControl.Count>128</SegmentedControl.Count>
|
|
@@ -170,14 +170,14 @@ export function OrdersPage() {
|
|
|
170
170
|
<Button.Icon>
|
|
171
171
|
<Icon name="action.add" />
|
|
172
172
|
</Button.Icon>
|
|
173
|
-
|
|
173
|
+
Новая сделка
|
|
174
174
|
</Button.Root>
|
|
175
175
|
</Page.Actions>
|
|
176
176
|
<Page.Chips>
|
|
177
177
|
<SmartFilter.Chips />
|
|
178
178
|
</Page.Chips>
|
|
179
179
|
</Page.TopBar>
|
|
180
|
-
<Page.Body>{/* the
|
|
180
|
+
<Page.Body>{/* the deals as a board or a list: the filter in the bar narrows both views */}</Page.Body>
|
|
181
181
|
</Page.Root>
|
|
182
182
|
</SmartFilter.Root>
|
|
183
183
|
);
|
|
@@ -210,6 +210,28 @@ export function OrdersPage() {
|
|
|
210
210
|
segments' padding, and one whose items all have icons then collapses to icons (each named by its
|
|
211
211
|
tooltip) before it scrolls. On a 320px phone two sections with counts fit a line of their own;
|
|
212
212
|
more, or long labels, scroll — keep the section a person needs first at the start.
|
|
213
|
+
- **Overflow is never fixed by merging.** A tab or a section is one topic; when the topics outgrow
|
|
214
|
+
the line the strip scrolls as above — never two topics joined in one tab («Доступ и история»),
|
|
215
|
+
never the last ones hidden in a «Ещё» of your own, never a second row. Shorten a label to one word
|
|
216
|
+
first; a topic that is rarely opened still keeps its own tab, at the end.
|
|
217
|
+
|
|
218
|
+
```tsx partial
|
|
219
|
+
// bad — the history folded into «Доступ» because five tabs did not fit the drawer
|
|
220
|
+
<Tabs.Item value="access">Доступ и история</Tabs.Item>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```tsx good
|
|
224
|
+
// good — every topic its own tab; the strip scrolls when it runs out of width
|
|
225
|
+
<Tabs.Root defaultValue="profile">
|
|
226
|
+
<Tabs.List>
|
|
227
|
+
<Tabs.Item value="profile">Профиль</Tabs.Item>
|
|
228
|
+
<Tabs.Item value="access">Доступ</Tabs.Item>
|
|
229
|
+
<Tabs.Item value="notifications">Уведомления</Tabs.Item>
|
|
230
|
+
<Tabs.Item value="security">Безопасность</Tabs.Item>
|
|
231
|
+
<Tabs.Item value="history">История</Tabs.Item>
|
|
232
|
+
</Tabs.List>
|
|
233
|
+
</Tabs.Root>
|
|
234
|
+
```
|
|
213
235
|
- Your duty as its host: give the switcher a bounded width. A flex or grid child that holds one sets
|
|
214
236
|
`min-width: 0`, otherwise the switcher grows the row instead of scrolling
|
|
215
237
|
([foundation §9](reference/foundation.md#9-layout-and-responsiveness)).
|
package/dist/skill/ux.md
CHANGED
|
@@ -32,19 +32,55 @@ adds text that explains what the layout failed to show.
|
|
|
32
32
|
| Records of one shape a person reads, sorts, filters | a table | `DataTable` |
|
|
33
33
|
| One value in several units or currencies (a deposit in ₽, $, ฿) | the fields side by side in one row under one heading, the unit in each field | `Input.InlineAffix` |
|
|
34
34
|
| A base value and its overrides (a price and its changes by month) | one list: the base is the first, pinned row | `FieldList` `getLocked` |
|
|
35
|
-
| Small attributes of a record (an icon, a color, a flag) | compact controls in the record's row, not fields of their own | icon-only `Select`, `ColorPresets`, `
|
|
35
|
+
| Small attributes of a record (an icon, a color, a flag) | compact controls in the record's row, not fields of their own | icon-only `Select`, `ColorPresets`, `Switch` |
|
|
36
36
|
| Short texts in an order the person sets | one column, a handle per row | `FieldList` `reorderable` |
|
|
37
37
|
| Settings that most people never touch | behind a disclosure, after the common ones | `Accordion`, a «Дополнительно» section |
|
|
38
38
|
| One of a few options, sent with a form | all visible | `Radio.Group` |
|
|
39
39
|
| One of a few modes of what the screen shows | all visible | `SegmentedControl` |
|
|
40
40
|
| One of many options | a list that opens | `Select` |
|
|
41
41
|
| Many values picked from options | tags | `TagSelect` |
|
|
42
|
+
| A section named after a set of records (sessions, devices, API keys, members) | the records themselves, each with its own actions | `List` with `List.ItemActions` |
|
|
42
43
|
|
|
43
44
|
How many options count as «a few» and the other selection cases: [choosing.md](choosing.md#2-inside-a-category).
|
|
44
45
|
|
|
45
46
|
One record is one row or one block, never half a record per line. If a record does not fit one
|
|
46
47
|
row, it is not a row: open it in a `Drawer`.
|
|
47
48
|
|
|
49
|
+
**A section is what its name says.** A heading that names a set of records — «Сеансы», «Устройства»,
|
|
50
|
+
«Ключи API», «Участники» — promises those records: show them, one row each (where, when, which one is
|
|
51
|
+
this one), with the action on one record in its row and the action on all of them in the section's
|
|
52
|
+
actions. One button standing for the set («Выйти на других устройствах») leaves the person to act on
|
|
53
|
+
records they cannot see. A set that can be empty shows its empty state, never a missing section.
|
|
54
|
+
|
|
55
|
+
```tsx partial
|
|
56
|
+
// bad — «Сеансы» with nothing but a button
|
|
57
|
+
<Drawer.Section label="Сеансы">
|
|
58
|
+
<Button.Root variant="soft" tone="neutral">Выйти на других устройствах</Button.Root>
|
|
59
|
+
</Drawer.Section>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```tsx good
|
|
63
|
+
// good — the sessions themselves, an end per row, the end of all of them beside the heading
|
|
64
|
+
<Drawer.Section label="Сеансы">
|
|
65
|
+
<Drawer.Actions>
|
|
66
|
+
<Button.Root variant="soft" tone="neutral">Завершить остальные</Button.Root>
|
|
67
|
+
</Drawer.Actions>
|
|
68
|
+
<List.Root aria-label="Сеансы">
|
|
69
|
+
<List.Item value="mac">
|
|
70
|
+
<List.ItemText>Chrome · macOS</List.ItemText>
|
|
71
|
+
<List.ItemDescription>Москва · это устройство</List.ItemDescription>
|
|
72
|
+
</List.Item>
|
|
73
|
+
<List.Item value="iphone">
|
|
74
|
+
<List.ItemText>Safari · iPhone</List.ItemText>
|
|
75
|
+
<List.ItemDescription>Казань · вчера в 21:40</List.ItemDescription>
|
|
76
|
+
<List.ItemActions>
|
|
77
|
+
<Button.Root size="s" variant="ghost" tone="neutral">Завершить</Button.Root>
|
|
78
|
+
</List.ItemActions>
|
|
79
|
+
</List.Item>
|
|
80
|
+
</List.Root>
|
|
81
|
+
</Drawer.Section>
|
|
82
|
+
```
|
|
83
|
+
|
|
48
84
|
## 3. Label economy
|
|
49
85
|
|
|
50
86
|
Every word on screen must carry something the person does not see already.
|
|
@@ -60,7 +96,21 @@ Every word on screen must carry something the person does not see already.
|
|
|
60
96
|
beside the number when the row stacks, and a screen reader hears it with the field (the affix
|
|
61
97
|
describes the input; `aria-label="рублей"` on it when the sign reads badly).
|
|
62
98
|
- **No text that explains the layout.** «Двигатель, места и скорость всегда первыми» — the lock on
|
|
63
|
-
those rows says it.
|
|
99
|
+
those rows says it.
|
|
100
|
+
- **A hint is the few words the label and the control leave out** — two to five: a format («ДД.ММ.ГГГГ»),
|
|
101
|
+
a limit («До 10 МБ»), a consequence nobody would expect («Увидит клиент»). Never what the control
|
|
102
|
+
already shows («Применяется сразу» under a Switch, «Время в CRM показывается в этом поясе» under
|
|
103
|
+
«Часовой пояс»), never the obvious outcome of the action or what stays unchanged («пароль
|
|
104
|
+
останется прежним», «история его действий останется»), never the label again in other words. A
|
|
105
|
+
field that needs no hint has none; a hint that needs a sentence is a missing label or a missing
|
|
106
|
+
control.
|
|
107
|
+
|
|
108
|
+
| bad hint | good |
|
|
109
|
+
|---|---|
|
|
110
|
+
| «Часовой пояс» — «Время в CRM показывается в этом поясе» | no hint |
|
|
111
|
+
| «Звук» (Switch) — «Применяется сразу, без сохранения» | no hint |
|
|
112
|
+
| «Отключить доступ» — «Сотрудник не сможет войти, история его действий останется» | «Входы прекратятся» |
|
|
113
|
+
| «Почта для входа» — «На эту почту придёт ссылка для входа в систему» | «Для входа и уведомлений» |
|
|
64
114
|
- **One name per thing.** The same action, entity and status keep one word on the whole screen
|
|
65
115
|
(and across screens): «Счёт», not «Счёт» in the title and «Инвойс» in the button.
|
|
66
116
|
- **Count the labels.** On a form of repeating rows, labels on screen ≤ number of columns + the
|
|
@@ -157,3 +207,48 @@ Code that passes every check can still read badly. Open the screen in a browser
|
|
|
157
207
|
button without an object — remove or rewrite it.
|
|
158
208
|
- **Fill it badly:** the longest real name, an empty list, an error on every field, the slowest
|
|
159
209
|
load. Nothing overflows, jumps or flips.
|
|
210
|
+
|
|
211
|
+
## 10. Where a setting lives
|
|
212
|
+
|
|
213
|
+
A setting lives with what it governs, as a field of the form that already holds its neighbours.
|
|
214
|
+
|
|
215
|
+
- **Group by what it changes, not by how it looks.** A signal (a sound, a desktop banner, an e-mail
|
|
216
|
+
digest) changes how the person is told — it goes to the notifications; the theme, the language and
|
|
217
|
+
the time zone change how the app reads — profile preferences; what one record does goes to that
|
|
218
|
+
record. Ask «what would a person open to change this?» and put it there.
|
|
219
|
+
- **No section for one setting.** A section with a single field joins the closest section; a new
|
|
220
|
+
section starts at three related fields.
|
|
221
|
+
- **A stored preference is a field.** A setting with options (the theme: «Системная», «Светлая»,
|
|
222
|
+
«Тёмная») is a `Select` beside its related fields in the profile form — never a `SegmentedControl`,
|
|
223
|
+
which switches what a screen shows right now ([a flag, a level, a set](choosing.md#a-flag-a-level-a-set)).
|
|
224
|
+
|
|
225
|
+
```tsx partial
|
|
226
|
+
// bad — a section «Оформление» for the theme as a switch, and the dialog sounds next to it
|
|
227
|
+
<Drawer.Section label="Оформление">
|
|
228
|
+
<SegmentedControl.Root label="Тема" defaultValue="system">…</SegmentedControl.Root>
|
|
229
|
+
<Switch.Root><Switch.Label>Звуки диалогов</Switch.Label></Switch.Root>
|
|
230
|
+
</Drawer.Section>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```tsx good
|
|
234
|
+
// good — the theme is one field of the profile, the sounds live with the notifications
|
|
235
|
+
<>
|
|
236
|
+
<Drawer.Section label="Профиль">
|
|
237
|
+
<Select.Root label="Тема" defaultValue="system">
|
|
238
|
+
<Select.Trigger>
|
|
239
|
+
<Select.Value />
|
|
240
|
+
</Select.Trigger>
|
|
241
|
+
<Select.Content>
|
|
242
|
+
<Select.Item value="system">Системная</Select.Item>
|
|
243
|
+
<Select.Item value="light">Светлая</Select.Item>
|
|
244
|
+
<Select.Item value="dark">Тёмная</Select.Item>
|
|
245
|
+
</Select.Content>
|
|
246
|
+
</Select.Root>
|
|
247
|
+
</Drawer.Section>
|
|
248
|
+
<Drawer.Section label="Уведомления">
|
|
249
|
+
<Switch.Root name="dialogSounds">
|
|
250
|
+
<Switch.Label>Звуки диалогов</Switch.Label>
|
|
251
|
+
</Switch.Root>
|
|
252
|
+
</Drawer.Section>
|
|
253
|
+
</>
|
|
254
|
+
```
|
|
@@ -117,6 +117,7 @@ export declare const semanticTokens: {
|
|
|
117
117
|
readonly fg: "{color.white}";
|
|
118
118
|
readonly soft: "{color.green.100}";
|
|
119
119
|
readonly text: "{color.green.700}";
|
|
120
|
+
readonly border: "{color.green.500}";
|
|
120
121
|
};
|
|
121
122
|
readonly warning: {
|
|
122
123
|
readonly default: "{color.orange.700}";
|
|
@@ -410,7 +411,7 @@ export declare const semanticTokens: {
|
|
|
410
411
|
/** State that glides into place (thumbs, indicators, checkmarks); pair with `base`. */
|
|
411
412
|
readonly emphasized: "{easing.emphasized}";
|
|
412
413
|
};
|
|
413
|
-
/** Step between items of a staggered
|
|
414
|
+
/** Step between items of a staggered entrance of parts that appear after their page is shown. */
|
|
414
415
|
readonly stagger: "{duration.80}";
|
|
415
416
|
/**
|
|
416
417
|
* Period unit of a loading loop (Spinner turns once in 3 units, 6 under reduced motion). A
|
|
@@ -60,6 +60,7 @@ export declare const darkThemeOverrides: {
|
|
|
60
60
|
readonly fg: "{color.gray.950}";
|
|
61
61
|
readonly soft: "color-mix(in srgb, {color.green.500} 16%, transparent)";
|
|
62
62
|
readonly text: "{color.green.300}";
|
|
63
|
+
readonly border: "{color.green.400}";
|
|
63
64
|
};
|
|
64
65
|
readonly warning: {
|
|
65
66
|
readonly default: "{color.orange.400}";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prime-ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"packageManager": "bun@1.3.13",
|
|
5
5
|
"description": "React 19 UI kit for product interfaces: 79 composable components on one design contract — forms, selects, tables, overlays, navigation, app shell. CSS Modules, --prime-* tokens, light and dark themes, TypeScript, a11y, Next.js App Router ready. Ships COMPONENT.md docs, copyable examples and an agent skill.",
|
|
6
6
|
"bin": {
|
|
@@ -121,6 +121,7 @@ The soft fill is opaque: the soft hue is composited over the ground the avatar s
|
|
|
121
121
|
- Avatar → name: space step 3 (12px) in lists and cards, step 2 (8px) in table cells.
|
|
122
122
|
- Group: members overlap by an eighth of the diameter (initials are never cut) with a 2px ring in the ground they stand on: the current layer of the surface around them (no surface sets anything), or `--avatar-ring` from a host whose ground is not a layer (a reaction chip sets its own fill); the fills of initials and «+N» are composited over the same ground.
|
|
123
123
|
- An icon fallback is half the diameter: a kit `Icon` reads `--prime-icon-size`, a raw svg too.
|
|
124
|
+
- In a leading media slot (`Drawer.Icon`, `Modal.Icon`, `Sheet.Icon`, `Card.Icon`) the avatar takes the slot's size (`l`, 40px) whatever `size` it was given, also through a wrapper component of yours; the slot then draws no tile.
|
|
124
125
|
|
|
125
126
|
## Accessibility
|
|
126
127
|
|
|
@@ -35,7 +35,9 @@ Badge.Root <span>: fill, tier dimensions
|
|
|
35
35
|
├─ Badge.Action optional end segment revealed on hover / focus (always shown on touch screens)
|
|
36
36
|
└─ remove segment with onRemove: full height, the whole end is the hit area (Icon action.close)
|
|
37
37
|
|
|
38
|
-
Badge.Group <div>: a
|
|
38
|
+
Badge.Group <div>: a row of Badge.Root, one value; `size` sets every badge's tier
|
|
39
|
+
├─ Badge.Root … one per value; with overflow="count" the ones that do not fit are hidden
|
|
40
|
+
└─ «+N» badge overflow="count": a focusable Badge.Root in a Tooltip listing the hidden values
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
## API
|
|
@@ -64,12 +66,14 @@ Badge.Group <div>: a wrapping row of Badge.Root, one value; `size` sets
|
|
|
64
66
|
| `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "onClick" \| "color">` | — | `aria-label` (an icon-only badge named by it is an image) and the other span attributes; no `onClick` — a pressable chip is the toggle. |
|
|
65
67
|
|
|
66
68
|
### Badge.Group
|
|
67
|
-
`ref` → `HTMLDivElement`. A row of badges as one value (the tags of a record): it wraps onto more lines, the badges a chip gap apart.
|
|
69
|
+
`ref` → `HTMLDivElement`. A row of badges as one value (the tags of a record): it wraps onto more lines, the badges a chip gap apart, or with `overflow` stays on one line behind a «+N» badge.
|
|
68
70
|
|
|
69
71
|
| Prop | Type | Default | Description |
|
|
70
72
|
|---|---|---|---|
|
|
71
73
|
| `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | the host tier, one step down | Tier of every badge in the group; a badge's own `size` wins. Small tiers stand closer. |
|
|
72
|
-
|
|
|
74
|
+
| `overflow` | `"wrap" \| "count"` | `"wrap"` | `wrap` — the row wraps onto more lines. `count` — one line (a table cell): the badges that fit the container, the rest behind a focusable «+N» badge whose tooltip lists them. |
|
|
75
|
+
| `labels` | `Partial<BadgeGroupLabels>` | — | Built-in strings of the «+N» badge, see Labels. |
|
|
76
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (the `Badge.Root`s, one per value), `className`, `aria-label` (a named group is `role="group"`) and the other div attributes. |
|
|
73
77
|
|
|
74
78
|
### Badge.Icon
|
|
75
79
|
`ref` → `HTMLSpanElement`. A `<span>` holding one icon at the tier icon size. At the first or last position it becomes a full-height edge segment.
|
|
@@ -146,6 +150,12 @@ A status takes `tone`; a category takes `color`. The two never go together (the
|
|
|
146
150
|
| `xl` | 32px high, text 14, icon 16 | large headers | |
|
|
147
151
|
| — (omitted) | one tier below the surrounding control | inside Button, Input, Tabs | yes (inside controls) |
|
|
148
152
|
|
|
153
|
+
### overflow (Badge.Group)
|
|
154
|
+
| Value | Looks like | Use when | Default |
|
|
155
|
+
|---|---|---|---|
|
|
156
|
+
| `wrap` | the row wraps onto more lines | tags of a record in a card, a form, a detail pane | yes |
|
|
157
|
+
| `count` | one line: the badges that fit, then «+N» | a table cell, a list row — a set of values that must keep the row's height | |
|
|
158
|
+
|
|
149
159
|
### Flags
|
|
150
160
|
| Value | Looks like | Use when | Default |
|
|
151
161
|
|---|---|---|---|
|
|
@@ -167,6 +177,7 @@ A status takes `tone`; a category takes `color`. The two never go together (the
|
|
|
167
177
|
| disabled | `disabled` | `data-disabled`, `aria-disabled`; inner buttons disabled, no hover wash |
|
|
168
178
|
| icon-only, named | only `Badge.Icon` children and `aria-label` | `role="img"` with that name |
|
|
169
179
|
| count | a number as the only child | the digits roll to the new value when the number changes |
|
|
180
|
+
| overflowed | `Badge.Group overflow="count"`, the badge does not fit the row | `data-overflowed` on the badge (out of the flow, `visibility: hidden`); the group's `data-overflow="count"`; re-measured on every resize of the row or a badge (`useResizeEffect`), the «+N» text from `labels.more` |
|
|
170
181
|
|
|
171
182
|
## Layout & spacing
|
|
172
183
|
- Inline-flex, `width: fit-content`, `max-width: 100%`; text never wraps and ends in an ellipsis.
|
|
@@ -175,6 +186,7 @@ A status takes `tone`; a category takes `color`. The two never go together (the
|
|
|
175
186
|
- The root never clips: the text ellipsizes in its own box, so the segments' touch areas reach past the chip.
|
|
176
187
|
- `Badge.Action` room is reserved, so revealing it never changes the badge width.
|
|
177
188
|
- Several badges in a row: `Badge.Group` — it wraps, badges 8 apart (4 at the `xs` and `s` tiers).
|
|
189
|
+
- `overflow="count"` fits the container, not the viewport: a DataTable cell, a 320px column. At least the first badge stays; when it alone does not fit it ellipsizes.
|
|
178
190
|
- In a list row the badge is the trailing column; the title column gets `min-width: 0` and an ellipsis.
|
|
179
191
|
|
|
180
192
|
## Accessibility
|
|
@@ -184,7 +196,7 @@ A status takes `tone`; a category takes `color`. The two never go together (the
|
|
|
184
196
|
|
|
185
197
|
| Key | Action |
|
|
186
198
|
|---|---|
|
|
187
|
-
| `Tab` | Focuses the body of a toggle chip, then its action or remove segment. |
|
|
199
|
+
| `Tab` | Focuses the body of a toggle chip, then its action or remove segment; in `Badge.Group` with `overflow` the «+N» badge, which opens its tooltip with the hidden values. |
|
|
188
200
|
| `Enter` · `Space` | Presses the focused body (the toggle flips), remove or action button. |
|
|
189
201
|
|
|
190
202
|
### ARIA
|
|
@@ -195,15 +207,25 @@ A status takes `tone`; a category takes `color`. The two never go together (the
|
|
|
195
207
|
- Give every remove button a unique name with `labels.remove`; after removing, move focus to a neighbouring badge or the source field.
|
|
196
208
|
- While a removal is saved the badge has `aria-busy` and the segment `aria-disabled`; once a removal waits, the badge mounts a polite live region that speaks `labels.removeFailed` if the promise rejects.
|
|
197
209
|
- `Badge.Dot` is `aria-hidden`; an icon-only badge needs `aria-label` and then is `role="img"`. Color never carries meaning alone, a `tone` neither.
|
|
210
|
+
- `overflow="count"`: the badges that did not fit are `visibility: hidden` (out of the tree and the Tab order); the «+N» badge is a tab stop read as `labels.moreCount`; its tooltip, open while it has focus, lists the hidden values as its description.
|
|
198
211
|
|
|
199
212
|
### Labels
|
|
200
213
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
201
214
|
|
|
215
|
+
#### Badge.Root
|
|
216
|
+
|
|
202
217
|
| Key | Default | Used for |
|
|
203
218
|
|---|---|---|
|
|
204
219
|
| `remove` | `"Удалить"` | Accessible name of the remove segment; include the badge text («Убрать фильтр «Москва»»). |
|
|
205
220
|
| `removeFailed` | `"Не удалось удалить"` | Spoken when the `onRemove` promise rejects and the badge stays; name the badge («Фильтр «Москва» не убран»). |
|
|
206
221
|
|
|
222
|
+
#### Badge.Group
|
|
223
|
+
|
|
224
|
+
| Key | Default | Used for |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `more` | `"+{count}"` | Visible text of the badge for the badges that did not fit; `{count}` — how many. |
|
|
227
|
+
| `moreCount` | `"Ещё {count}"` | What assistive tech reads for that badge instead of its visible text; `{count}` — how many. The values themselves come in its tooltip. |
|
|
228
|
+
|
|
207
229
|
## Examples
|
|
208
230
|
| Example | Shows |
|
|
209
231
|
|---|---|
|
|
@@ -218,6 +240,7 @@ A status takes `tone`; a category takes `color`. The two never go together (the
|
|
|
218
240
|
| [filter-values.tsx](examples/filter-values.tsx) | Filter values as toggles with a hide action that slides in without changing the width — `pressed`, `onPressedChange`, `Badge.Action`, `persistent`. |
|
|
219
241
|
| [square.tsx](examples/square.tsx) | File formats as square tiles of text, large enough for three letters; the hue sorts them by kind — `shape`, `size`, `color`. |
|
|
220
242
|
| [tag-row.tsx](examples/tag-row.tsx) | The tags of a client as one value: the row wraps in a narrow column and keeps one gap between badges — `Badge.Group`, `size`. |
|
|
243
|
+
| [overflow-count.tsx](examples/overflow-count.tsx) | The sections of each role in one line of a narrow cell: what does not fit waits behind «+N» with the full list in its tooltip — `Badge.Group`, `overflow`. |
|
|
221
244
|
| [controlled.tsx](examples/controlled.tsx) | Delivery cities as toggle chips whose state the screen owns: the summary counts the chosen ones — `pressed`, `onPressedChange`. |
|
|
222
245
|
|
|
223
246
|
## Mistakes
|
|
@@ -230,9 +253,10 @@ A status takes `tone`; a category takes `color`. The two never go together (the
|
|
|
230
253
|
- A colored badge with no text → keep a word; color alone is not a signal.
|
|
231
254
|
- An explicit `size` inside a control → drop it; the badge takes the tier one step down.
|
|
232
255
|
- A row of badges in a hand-made flex wrapper with its own gap → `Badge.Group`.
|
|
256
|
+
- A set of values in a table cell that wraps and grows the row, or a hand-made «+N» → `Badge.Group overflow="count"`.
|
|
233
257
|
- `onRemove` together with `Badge.Action` → one end segment per badge.
|
|
234
258
|
- Removing the badge from your list first and putting it back when the request fails → return the request's promise and remove it after it succeeds; the badge waits meanwhile.
|
|
235
259
|
|
|
236
260
|
## Related
|
|
237
|
-
- **Built from:** `Icon` (remove ×, `Badge.Action` −), [Spinner](../spinner/COMPONENT.md) (removing), `internal/chipTier` (tiers), `internal/glyphSwap` (× ⇄ spinner), `internal/partPresence` (leaving), [VisuallyHidden](../visually-hidden/COMPONENT.md)
|
|
261
|
+
- **Built from:** `Icon` (remove ×, `Badge.Action` −), [Spinner](../spinner/COMPONENT.md) (removing), `internal/chipTier` (tiers), `internal/glyphSwap` (× ⇄ spinner), `internal/partPresence` (leaving), [VisuallyHidden](../visually-hidden/COMPONENT.md), [Tooltip](../tooltip/COMPONENT.md) (the «+N» list), `useResizeEffect` (fitting the row), `internal/formatLabel` (`labels`)
|
|
238
262
|
- **See also:** [TagSelect](../tag-select/COMPONENT.md), [Select](../select/COMPONENT.md) (row badges), [SegmentedControl](../segmented-control/COMPONENT.md) (`Count`, color dots), [SmartFilter](../smart-filter/COMPONENT.md), [Kbd](../kbd/COMPONENT.md), [Avatar](../avatar/COMPONENT.md), [DataTable](../data-table/COMPONENT.md)
|
|
@@ -30,3 +30,17 @@
|
|
|
30
30
|
gap: var(--prime-space-6);
|
|
31
31
|
inline-size: min(100%, calc(var(--prime-space-20) * 4));
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/* overflow-count.tsx: rows of a narrow table — the role, then its sections in a cell. */
|
|
35
|
+
.roles {
|
|
36
|
+
display: grid;
|
|
37
|
+
gap: var(--prime-space-3);
|
|
38
|
+
inline-size: min(100%, var(--prime-layout-form-width-s));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.role {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: var(--prime-space-3);
|
|
46
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** The sections of each role in one line of a narrow cell: what does not fit waits behind «+N» with the full list in its tooltip — `Badge.Group`, `overflow`. */
|
|
2
|
+
import { Badge, Typography } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
const ROLES = [
|
|
7
|
+
{
|
|
8
|
+
name: "Администратор",
|
|
9
|
+
sections: ["Сделки", "Контакты", "Счета", "Отчёты", "Склад", "Настройки"],
|
|
10
|
+
},
|
|
11
|
+
{ name: "Менеджер", sections: ["Сделки", "Контакты", "Задачи"] },
|
|
12
|
+
{ name: "Бухгалтер", sections: ["Счета", "Акты", "Платежи", "Отчёты"] },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export default function BadgeOverflowCountExample() {
|
|
16
|
+
return (
|
|
17
|
+
<div className={styles.roles}>
|
|
18
|
+
{ROLES.map((role) => (
|
|
19
|
+
<div key={role.name} className={styles.role}>
|
|
20
|
+
<Typography as="span" variant="body-m">
|
|
21
|
+
{role.name}
|
|
22
|
+
</Typography>
|
|
23
|
+
<Badge.Group overflow="count" aria-label={`Разделы роли «${role.name}»`}>
|
|
24
|
+
{role.sections.map((section) => (
|
|
25
|
+
<Badge.Root key={section}>{section}</Badge.Root>
|
|
26
|
+
))}
|
|
27
|
+
</Badge.Group>
|
|
28
|
+
</div>
|
|
29
|
+
))}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -117,7 +117,7 @@ Banner.Root <div>; variant, tone, size, placement
|
|
|
117
117
|
| dismissible | `onDismiss` | corner ghost Button (`xs`, `s` on `l` / `xl`), or the last outline Button in `Banner.Actions` |
|
|
118
118
|
| narrow | own width < 36rem (a banner with `Banner.Actions` is a size container) | actions move under the text; the close button returns to the corner |
|
|
119
119
|
|
|
120
|
-
No `open` prop: the parent mounts and unmounts the banner. A
|
|
120
|
+
No `open` prop: the parent mounts and unmounts the banner. A banner that appears on a page already shown enters (a short fade and rise), one that mounts with its page stands still; an unmounted one closes up in place, so the content below glides up instead of jumping. To swap the banner for another block in the same spot (a «restore» button), wrap both in [Crossfade](../crossfade/COMPONENT.md).
|
|
121
121
|
|
|
122
122
|
## Layout & spacing
|
|
123
123
|
- `width: 100%`; text column capped at `--prime-layout-reading-max-width`.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- KPI tiles on a dashboard (`mini`, `stat-trend`, `split`).
|
|
10
10
|
- A chart or text widget with a header and controls (`panel`).
|
|
11
11
|
- A settings or profile form grouped on its own surface (`panel` + `Body` + `Footer`).
|
|
12
|
+
- A block about one person or object — a linked account, a contact — headed by its picture (`Card.Icon` with an `Avatar.Root` in `Card.Header`).
|
|
12
13
|
- An activity list (`list`), a call to action (`cta`), a tile with an image on top (`cover`).
|
|
13
14
|
- A tile that is itself the way to its details — a KPI that opens its report, a project tile (`asChild` with a link or a button).
|
|
14
15
|
|
|
@@ -25,7 +26,7 @@ import { Card } from "prime-ui-kit";
|
|
|
25
26
|
|
|
26
27
|
## Anatomy
|
|
27
28
|
```
|
|
28
|
-
Card.Root variant="panel" Header(Title, Description?, …trailing) · Body · Media · Footer
|
|
29
|
+
Card.Root variant="panel" Header(Icon?, Title, Description?, …trailing) · Body · Media · Footer
|
|
29
30
|
Card.Root variant="mini" Icon · Label · Value · Meta? · Media?
|
|
30
31
|
Card.Root variant="stat-trend" Label · Value · Delta · Meta?
|
|
31
32
|
or Header(Badge or Icon, Value) · Description
|
|
@@ -52,7 +53,7 @@ Card.Root asChild <a> or <button> (the only child) holding the pa
|
|
|
52
53
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `className`, `role` + `aria-labelledby` for a landmark block, and the other div attributes. |
|
|
53
54
|
|
|
54
55
|
### Card.Header
|
|
55
|
-
`ref` → `HTMLDivElement`. The top row: `Card.Title`
|
|
56
|
+
`ref` → `HTMLDivElement`. The top row: an optional `Card.Icon`, `Card.Title` with an optional `Card.Description` under it, anything else (a control, a caption) at the end. Wraps below 22rem.
|
|
56
57
|
|
|
57
58
|
| Prop | Type | Default | Description |
|
|
58
59
|
|---|---|---|---|
|
|
@@ -95,7 +96,7 @@ Card.Root asChild <a> or <button> (the only child) holding the pa
|
|
|
95
96
|
| `…rest` | `HTMLAttributes<HTMLElement>` | — | `children`, `className` and the other attributes of the element. |
|
|
96
97
|
|
|
97
98
|
### Card.Icon
|
|
98
|
-
`ref` → `HTMLDivElement`. The 40px accent tile of
|
|
99
|
+
`ref` → `HTMLDivElement`. The 40px accent tile of one icon: beside the label and value in `mini` and a `split` cell, first in `Card.Header`. An `Avatar.Root` or `Thumbnail.Root` inside (also in a wrapper) is the record's picture: no tile, 40px, `aria-hidden`.
|
|
99
100
|
|
|
100
101
|
| Prop | Type | Default | Description |
|
|
101
102
|
|---|---|---|---|
|
|
@@ -179,6 +180,7 @@ Without `asChild` a card is static: no hover, focus or press of its own. With it
|
|
|
179
180
|
- Depth comes from nesting (foundation §4): on the page — the AppShell panel is the page — a card is layer 1 (white in light) with the raised whisper; inside another card it is the next layer, a flat tile. The ladder stops at layer 4.
|
|
180
181
|
- Long values wrap; labels truncate, except in a `mini` tile and a `split` cell, where a label beside the icon wraps instead; a chart SVG needs a CSS height.
|
|
181
182
|
- In a `Header` the title takes the free width; a control or a caption after it sits at the end without a wrapper. A `Card.Description` in the header stacks under the title, 4 apart; the control stays at the end, centred on both lines.
|
|
183
|
+
- A `Card.Icon` in a `Header` leads the row, 12 before the title, centred on the title and the description: the accent tile, or the record's picture — an `Avatar.Root` or a `Thumbnail.Root` anywhere inside, also from your own wrapper component, with no tile; it takes 40 px whatever size it was given — for a card about one person or object (a linked account, a contact).
|
|
182
184
|
- `Card.Meta` in `stat-trend` takes the bottom of the tile (`margin-top: auto`), so tiles of one row line their meta up; in `mini` and a `split` cell it sits in the text column under the value.
|
|
183
185
|
|
|
184
186
|
## Accessibility
|
|
@@ -212,6 +214,7 @@ No `labels`.
|
|
|
212
214
|
| [variants.tsx](examples/variants.tsx) | KPI templates: an icon tile with a value, a badge with a value in the header row, and a large value with its change in every tone and a reference line — `variant`, `Card.Delta`, `tone`, `Card.Meta`. |
|
|
213
215
|
| [kpi-media.tsx](examples/kpi-media.tsx) | A KPI with a sparkline or a fill level in the bottom slot of `mini` — `Card.Media`. |
|
|
214
216
|
| [panel-chart.tsx](examples/panel-chart.tsx) | A chart widget: a header with a short description and a period switch, and a Sparkline with the period total over its chart — `Card.Header`, `Card.Description`, `Card.Body`. |
|
|
217
|
+
| [person-header.tsx](examples/person-header.tsx) | A linked Telegram account in an employee card: the avatar leads the header, the handle stands under the name and the unlink button sits at the end — `Card.Icon`, `Avatar.Root`, `Card.Description`. |
|
|
215
218
|
| [interactive.tsx](examples/interactive.tsx) | Whole cards as links to their reports: one tab stop each, a lift on hover, a press and the focus ring — `asChild`. |
|
|
216
219
|
| [content-templates.tsx](examples/content-templates.tsx) | Content templates: a call to action, an events list and a campaign tile with a cover — `Card.Footer`, `Card.List`, `Card.Media`. |
|
|
217
220
|
| [flat.tsx](examples/flat.tsx) | A flat tile without the raised shadow next to the default one, for dense grids — `flat`. |
|
|
@@ -225,6 +228,7 @@ No `labels`.
|
|
|
225
228
|
- `Card.Delta tone="success"` for every «+» → choose the tone by meaning.
|
|
226
229
|
- Custom padding on a `panel` root → padding belongs to `Header` / `Body` / `Footer`.
|
|
227
230
|
- A wrapper `<div>` around the label and the value of `mini` → put `Icon`, `Label`, `Value` straight into the root (or into a `split` cell); the template lays them out.
|
|
231
|
+
- An avatar, a name and a handle hand-laid in `Card.Body` with a button beside them → `Card.Header` with `Card.Icon` (the `Avatar.Root`), `Card.Title`, `Card.Description` and the button.
|
|
228
232
|
- A chart SVG with only a `viewBox` → give it a CSS height.
|
|
229
233
|
- A hand-drawn trend SVG in a chart widget → `Sparkline` in the `Body` (or `Media`); a chart with a legend and states → `ChartFrame`.
|
|
230
234
|
- A clickable `<div>` card with `onClick` → `asChild` with a real `<a href>` (Cmd-click, a new tab) or a `<button>`.
|
|
@@ -233,5 +237,5 @@ No `labels`.
|
|
|
233
237
|
- Skeletons placed by hand inside `Card.Value` → `Card.Root loading`; keep the parts mounted while loading so each holds its place.
|
|
234
238
|
|
|
235
239
|
## Related
|
|
236
|
-
- **Built from:** [Skeleton](../skeleton/COMPONENT.md)
|
|
240
|
+
- **Built from:** [Skeleton](../skeleton/COMPONENT.md), `internal/mediaSlot` (a record's Avatar or Thumbnail in `Card.Icon`)
|
|
237
241
|
- **See also:** [Page](../../layout/page/COMPONENT.md), [DescriptionList](../description-list/COMPONENT.md) (record details inside a `Card.Body`), [Fieldset](../fieldset/COMPONENT.md) (groups of fields inside a settings card), [DataTable](../data-table/COMPONENT.md), [Badge](../badge/COMPONENT.md), [ProgressBar](../progress-bar/COMPONENT.md), [SegmentedControl](../segmented-control/COMPONENT.md), [Sparkline](../sparkline/COMPONENT.md) (a trend with its total inside a `panel`), [ChartFrame](../chart-frame/COMPONENT.md) (a full chart with legend and states)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** A linked Telegram account in an employee card: the avatar leads the header, the handle stands under the name and the unlink button sits at the end — `Card.Icon`, `Avatar.Root`, `Card.Description`. */
|
|
2
|
+
import { Avatar, Button, Card, Typography } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
const ACCOUNT = {
|
|
7
|
+
name: "Анна Соколова",
|
|
8
|
+
initials: "АС",
|
|
9
|
+
handle: "@a.sokolova",
|
|
10
|
+
linkedAt: "Привязан 12 сентября",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default function CardPersonHeaderExample() {
|
|
14
|
+
return (
|
|
15
|
+
<div className={styles.panel}>
|
|
16
|
+
<Card.Root>
|
|
17
|
+
<Card.Header>
|
|
18
|
+
<Card.Icon>
|
|
19
|
+
<Avatar.Root color="blue">
|
|
20
|
+
<Avatar.Fallback>{ACCOUNT.initials}</Avatar.Fallback>
|
|
21
|
+
</Avatar.Root>
|
|
22
|
+
</Card.Icon>
|
|
23
|
+
<Card.Title>{ACCOUNT.name}</Card.Title>
|
|
24
|
+
<Card.Description>{ACCOUNT.handle}</Card.Description>
|
|
25
|
+
<Button.Root variant="outline" tone="neutral">
|
|
26
|
+
Отвязать
|
|
27
|
+
</Button.Root>
|
|
28
|
+
</Card.Header>
|
|
29
|
+
<Card.Body>
|
|
30
|
+
<Typography as="p" variant="body-s" tone="secondary">
|
|
31
|
+
{ACCOUNT.linkedAt}. Бот присылает сюда новые заказы клиентов сотрудника и напоминания о
|
|
32
|
+
просроченных счетах.
|
|
33
|
+
</Typography>
|
|
34
|
+
</Card.Body>
|
|
35
|
+
</Card.Root>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|