prime-ui-kit 0.9.1 → 0.9.2
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 +22 -8
- package/dist/chunks/{chunk-KCQFOORX.js → chunk-IQELQNMR.js} +1 -1
- package/dist/chunks/{chunk-DB2Y3XXA.js → chunk-LXGRKN34.js} +2323 -1500
- package/dist/chunks/{chunk-NHOWNIAR.js → chunk-ZEFCDEEQ.js} +790 -536
- package/dist/color-picker.js +2 -2
- package/dist/components/data-table/DataTable.d.ts +6 -1
- package/dist/components/data-table/types.d.ts +5 -0
- package/dist/components/dnd/Dnd.d.ts +1 -1
- package/dist/components/dnd/dragSession.d.ts +9 -0
- package/dist/components/dnd/useDropTarget.d.ts +5 -0
- package/dist/components/dnd/useFlipList.d.ts +3 -3
- package/dist/components/drawer/Drawer.d.ts +8 -5
- package/dist/components/dropdown/Dropdown.d.ts +17 -0
- package/dist/components/index.d.ts +5 -1
- package/dist/components/kanban/Kanban.d.ts +129 -0
- package/dist/components/modal/Modal.d.ts +5 -2
- package/dist/components/page-toolbar/PageToolbar.d.ts +53 -0
- package/dist/components/segmented-control/SegmentedControl.d.ts +5 -2
- package/dist/components/tabs/Tabs.d.ts +5 -2
- package/dist/hooks/useEdgeOverflow.d.ts +12 -0
- package/dist/hooks/useMediaQuery.d.ts +7 -0
- package/dist/hooks/useSwipeDismiss.d.ts +34 -0
- package/dist/index.css +2271 -1163
- package/dist/index.js +10 -2
- package/dist/internal/overlay/FloatingPanel.d.ts +3 -1
- package/dist/internal/overlay/useFloatingLayer.d.ts +11 -1
- package/dist/internal/touchTarget.d.ts +4 -0
- package/dist/layout/app-header/AppHeader.d.ts +110 -0
- package/dist/layout/app-shell/AppShell.d.ts +14 -9
- package/dist/layout/bottom-nav/BottomNav.d.ts +80 -0
- package/dist/layout/index.d.ts +5 -1
- package/dist/tokens/primitives.d.ts +4 -0
- package/dist/tokens/semantic.d.ts +56 -0
- package/dist/tokens/themes/dark.d.ts +4 -0
- package/package.json +3 -2
- package/src/components/accordion/COMPONENT.md +1 -1
- package/src/components/badge/COMPONENT.md +2 -2
- package/src/components/banner/COMPONENT.md +1 -1
- package/src/components/checkbox/examples/examples.module.css +4 -2
- package/src/components/crossfade/COMPONENT.md +1 -1
- package/src/components/data-table/COMPONENT.md +19 -6
- package/src/components/data-table/examples/columns-visibility.tsx +167 -0
- package/src/components/digit-input/COMPONENT.md +10 -10
- package/src/components/digit-input/examples/examples.module.css +8 -0
- package/src/components/digit-input/examples/in-form.tsx +2 -5
- package/src/components/digit-input/examples/sizes.tsx +1 -1
- package/src/components/divider/COMPONENT.md +1 -1
- package/src/components/dnd/COMPONENT.md +3 -3
- package/src/components/drawer/COMPONENT.md +17 -9
- package/src/components/drawer/examples/date-sheet.tsx +41 -0
- package/src/components/drawer/examples/examples.module.css +5 -0
- package/src/components/drawer/examples/placement.tsx +4 -2
- package/src/components/dropdown/COMPONENT.md +19 -5
- package/src/components/dropdown/examples/checkbox-items.tsx +48 -0
- package/src/components/empty-page/COMPONENT.md +1 -1
- package/src/components/kanban/COMPONENT.md +181 -0
- package/src/components/kanban/examples/controlled.tsx +78 -0
- package/src/components/kanban/examples/examples.module.css +31 -0
- package/src/components/kanban/examples/limits.tsx +58 -0
- package/src/components/kanban/examples/narrow.tsx +76 -0
- package/src/components/kanban/examples/overview.tsx +130 -0
- package/src/components/kanban/examples/states.tsx +75 -0
- package/src/components/kanban/examples/structure.tsx +76 -0
- package/src/components/login-form/COMPONENT.md +1 -1
- package/src/components/modal/COMPONENT.md +9 -6
- package/src/components/notification/COMPONENT.md +2 -2
- package/src/components/page-content/COMPONENT.md +1 -1
- package/src/components/page-toolbar/COMPONENT.md +142 -0
- package/src/components/page-toolbar/examples/examples.module.css +16 -0
- package/src/components/page-toolbar/examples/narrow.tsx +103 -0
- package/src/components/page-toolbar/examples/overview.tsx +113 -0
- package/src/components/page-toolbar/examples/save-changes.tsx +40 -0
- package/src/components/page-toolbar/examples/without-search.tsx +60 -0
- package/src/components/popover/COMPONENT.md +4 -3
- package/src/components/progress-bar/COMPONENT.md +1 -1
- package/src/components/progress-circle/COMPONENT.md +1 -1
- package/src/components/scroll-container/COMPONENT.md +1 -1
- package/src/components/segmented-control/COMPONENT.md +8 -8
- package/src/components/segmented-control/examples/full-width.tsx +1 -1
- package/src/components/select/COMPONENT.md +2 -1
- package/src/components/skeleton/COMPONENT.md +1 -1
- package/src/components/smart-filter/COMPONENT.md +2 -0
- package/src/components/spinner/COMPONENT.md +1 -1
- package/src/components/tabs/COMPONENT.md +11 -8
- package/src/components/tabs/examples/closable.tsx +1 -1
- package/src/layout/app-header/COMPONENT.md +178 -0
- package/src/layout/app-header/examples/breadcrumbs.tsx +36 -0
- package/src/layout/app-header/examples/examples.module.css +47 -0
- package/src/layout/app-header/examples/in-app-shell.tsx +70 -0
- package/src/layout/app-header/examples/narrow.tsx +29 -0
- package/src/layout/app-header/examples/overview.tsx +36 -0
- package/src/layout/app-shell/COMPONENT.md +27 -21
- package/src/layout/app-shell/examples/examples.module.css +15 -0
- package/src/layout/app-shell/examples/footer.tsx +59 -0
- package/src/layout/app-shell/examples/overview.tsx +19 -8
- package/src/layout/bottom-nav/COMPONENT.md +153 -0
- package/src/layout/bottom-nav/examples/examples.module.css +39 -0
- package/src/layout/bottom-nav/examples/floating.tsx +89 -0
- package/src/layout/bottom-nav/examples/icon-only.tsx +41 -0
- package/src/layout/bottom-nav/examples/in-app-shell.tsx +64 -0
- package/src/layout/bottom-nav/examples/overview.tsx +44 -0
- package/src/layout/bottom-nav/examples/router.tsx +31 -0
- package/src/layout/bottom-nav/examples/structure.tsx +47 -0
- package/src/layout/sidebar/COMPONENT.md +11 -10
- package/src/styles/theme-dark.css +3 -0
- package/src/styles/theme-light.css +24 -0
- package/src/styles/tokens.css +2 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** A release board: cards move within and between columns by drag, a touch hold or Alt + arrows; a card shows its meta line, labels, comments and assignee — `Kanban.Root`, `Kanban.Item`, `renderItem`, `renderColumnActions`. */
|
|
2
|
+
import { Avatar, Badge, Button, Icon, Kanban, type PaletteColor } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
type Task = {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
due: string;
|
|
10
|
+
labels: { text: string; color: PaletteColor }[];
|
|
11
|
+
comments: number;
|
|
12
|
+
assignee: { initials: string; name: string; color: PaletteColor };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const COLUMNS = [
|
|
16
|
+
{ id: "backlog", title: "Бэклог" },
|
|
17
|
+
{ id: "progress", title: "В работе" },
|
|
18
|
+
{ id: "review", title: "На проверке" },
|
|
19
|
+
{ id: "done", title: "Готово" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const ANNA = { initials: "АК", name: "Анна Котова", color: "purple" } as const;
|
|
23
|
+
const ILYA = { initials: "ИС", name: "Илья Смирнов", color: "teal" } as const;
|
|
24
|
+
const MARIA = { initials: "МВ", name: "Мария Волкова", color: "orange" } as const;
|
|
25
|
+
|
|
26
|
+
const TASKS: Task[] = [
|
|
27
|
+
{
|
|
28
|
+
id: "PRJ-214",
|
|
29
|
+
title: "Экспорт счетов в CSV для бухгалтерии",
|
|
30
|
+
due: "14 окт",
|
|
31
|
+
labels: [{ text: "Биллинг", color: "blue" }],
|
|
32
|
+
comments: 3,
|
|
33
|
+
assignee: ANNA,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "PRJ-221",
|
|
37
|
+
title: "Фильтр заказов по складу",
|
|
38
|
+
due: "17 окт",
|
|
39
|
+
labels: [{ text: "Заказы", color: "green" }],
|
|
40
|
+
comments: 0,
|
|
41
|
+
assignee: ILYA,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "PRJ-198",
|
|
45
|
+
title: "Повторная отправка приглашений в команду",
|
|
46
|
+
due: "10 окт",
|
|
47
|
+
labels: [
|
|
48
|
+
{ text: "Команда", color: "purple" },
|
|
49
|
+
{ text: "Срочно", color: "red" },
|
|
50
|
+
],
|
|
51
|
+
comments: 5,
|
|
52
|
+
assignee: MARIA,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "PRJ-205",
|
|
56
|
+
title: "Тёмная тема для отчётов",
|
|
57
|
+
due: "12 окт",
|
|
58
|
+
labels: [{ text: "Дизайн", color: "pink" }],
|
|
59
|
+
comments: 2,
|
|
60
|
+
assignee: ANNA,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "PRJ-187",
|
|
64
|
+
title: "Лимиты API для партнёров",
|
|
65
|
+
due: "9 окт",
|
|
66
|
+
labels: [{ text: "API", color: "sky" }],
|
|
67
|
+
comments: 8,
|
|
68
|
+
assignee: ILYA,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "PRJ-176",
|
|
72
|
+
title: "Онбординг нового проекта",
|
|
73
|
+
due: "3 окт",
|
|
74
|
+
labels: [{ text: "Проекты", color: "yellow" }],
|
|
75
|
+
comments: 1,
|
|
76
|
+
assignee: MARIA,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const PLACEMENT = {
|
|
81
|
+
backlog: ["PRJ-214", "PRJ-221"],
|
|
82
|
+
progress: ["PRJ-198", "PRJ-205"],
|
|
83
|
+
review: ["PRJ-187"],
|
|
84
|
+
done: ["PRJ-176"],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default function KanbanOverviewExample() {
|
|
88
|
+
return (
|
|
89
|
+
<Kanban.Root
|
|
90
|
+
aria-label="Задачи релиза 2.4"
|
|
91
|
+
className={styles.board}
|
|
92
|
+
columns={COLUMNS}
|
|
93
|
+
items={TASKS}
|
|
94
|
+
getId={(task) => task.id}
|
|
95
|
+
getLabel={(task) => task.title}
|
|
96
|
+
defaultValue={PLACEMENT}
|
|
97
|
+
renderColumnActions={(column) => (
|
|
98
|
+
<Button.Root variant="ghost" tone="neutral" aria-label={`Добавить в «${column.title}»`}>
|
|
99
|
+
<Button.Icon>
|
|
100
|
+
<Icon name="action.add" />
|
|
101
|
+
</Button.Icon>
|
|
102
|
+
</Button.Root>
|
|
103
|
+
)}
|
|
104
|
+
renderItem={(task) => (
|
|
105
|
+
<Kanban.Item>
|
|
106
|
+
<Kanban.ItemTitle>{task.title}</Kanban.ItemTitle>
|
|
107
|
+
<Kanban.ItemDescription>
|
|
108
|
+
{task.id} · до {task.due}
|
|
109
|
+
</Kanban.ItemDescription>
|
|
110
|
+
<Kanban.ItemBadges>
|
|
111
|
+
{task.labels.map((label) => (
|
|
112
|
+
<Badge.Root key={label.text} color={label.color}>
|
|
113
|
+
{label.text}
|
|
114
|
+
</Badge.Root>
|
|
115
|
+
))}
|
|
116
|
+
</Kanban.ItemBadges>
|
|
117
|
+
<Kanban.ItemFooter>
|
|
118
|
+
<Kanban.ItemCount>
|
|
119
|
+
<Icon name="object.message" />
|
|
120
|
+
{task.comments}
|
|
121
|
+
</Kanban.ItemCount>
|
|
122
|
+
<Avatar.Root size="s" color={task.assignee.color} aria-label={task.assignee.name}>
|
|
123
|
+
<Avatar.Fallback>{task.assignee.initials}</Avatar.Fallback>
|
|
124
|
+
</Avatar.Root>
|
|
125
|
+
</Kanban.ItemFooter>
|
|
126
|
+
</Kanban.Item>
|
|
127
|
+
)}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Loading draws skeleton cards in the card geometry and cross-fades the cards in; an empty column keeps its drop area; a read-only board drags nothing — `loading`, `disabled`. */
|
|
2
|
+
import { Button, Icon, Kanban, Switch } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import styles from "./examples.module.css";
|
|
6
|
+
|
|
7
|
+
type Order = { id: string; title: string; total: string };
|
|
8
|
+
|
|
9
|
+
const COLUMNS = [
|
|
10
|
+
{ id: "new", title: "Новые" },
|
|
11
|
+
{ id: "packing", title: "Сборка" },
|
|
12
|
+
{ id: "shipped", title: "Отгружены" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const ORDERS: Order[] = [
|
|
16
|
+
{ id: "ORD-5531", title: "ООО «Север» · 12 позиций", total: "48 300 ₽" },
|
|
17
|
+
{ id: "ORD-5529", title: "ИП Лебедев · 3 позиции", total: "7 950 ₽" },
|
|
18
|
+
{ id: "ORD-5524", title: "АО «Гранит» · 40 позиций", total: "212 000 ₽" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const PLACEMENT = { new: ["ORD-5531", "ORD-5529"], packing: [], shipped: ["ORD-5524"] };
|
|
22
|
+
|
|
23
|
+
const LOAD_MS = 1200;
|
|
24
|
+
|
|
25
|
+
export default function KanbanStatesExample() {
|
|
26
|
+
const [loading, setLoading] = React.useState(true);
|
|
27
|
+
const [readOnly, setReadOnly] = React.useState(false);
|
|
28
|
+
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
if (!loading) return;
|
|
31
|
+
const timer = window.setTimeout(() => setLoading(false), LOAD_MS);
|
|
32
|
+
return () => window.clearTimeout(timer);
|
|
33
|
+
}, [loading]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className={styles.stage}>
|
|
37
|
+
<div className={styles.toolbar}>
|
|
38
|
+
<Switch.Root checked={readOnly} onCheckedChange={setReadOnly}>
|
|
39
|
+
<Switch.Label>Только просмотр</Switch.Label>
|
|
40
|
+
</Switch.Root>
|
|
41
|
+
<div className={styles.spacer} />
|
|
42
|
+
<Button.Root
|
|
43
|
+
variant="soft"
|
|
44
|
+
tone="neutral"
|
|
45
|
+
loading={loading}
|
|
46
|
+
onClick={() => setLoading(true)}
|
|
47
|
+
>
|
|
48
|
+
<Button.Icon>
|
|
49
|
+
<Icon name="action.refresh" />
|
|
50
|
+
</Button.Icon>
|
|
51
|
+
Обновить
|
|
52
|
+
</Button.Root>
|
|
53
|
+
</div>
|
|
54
|
+
<Kanban.Root
|
|
55
|
+
aria-label="Заказы склада"
|
|
56
|
+
className={styles.board}
|
|
57
|
+
columns={COLUMNS}
|
|
58
|
+
items={ORDERS}
|
|
59
|
+
getId={(order) => order.id}
|
|
60
|
+
getLabel={(order) => order.id}
|
|
61
|
+
defaultValue={PLACEMENT}
|
|
62
|
+
loading={loading}
|
|
63
|
+
disabled={readOnly}
|
|
64
|
+
renderItem={(order) => (
|
|
65
|
+
<Kanban.Item>
|
|
66
|
+
<Kanban.ItemTitle>{order.id}</Kanban.ItemTitle>
|
|
67
|
+
<Kanban.ItemDescription>
|
|
68
|
+
{order.title} · {order.total}
|
|
69
|
+
</Kanban.ItemDescription>
|
|
70
|
+
</Kanban.Item>
|
|
71
|
+
)}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Cards take only the parts they need: a title alone, a meta line, labels, a footer with counts and an assignee — `Kanban.ItemTitle`, `Kanban.ItemDescription`, `Kanban.ItemBadges`, `Kanban.ItemFooter`, `Kanban.ItemCount`. */
|
|
2
|
+
import { Avatar, Badge, Icon, Kanban } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
type Card = {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
meta?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
comments?: number;
|
|
12
|
+
attachments?: number;
|
|
13
|
+
assignee?: { initials: string; name: string };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const COLUMNS = [
|
|
17
|
+
{ id: "inbox", title: "Входящие" },
|
|
18
|
+
{ id: "planned", title: "Запланировано" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const CARDS: Card[] = [
|
|
22
|
+
{ id: "c1", title: "Уточнить реквизиты поставщика" },
|
|
23
|
+
{ id: "c2", title: "Сверка остатков на складе", meta: "INV-3102 · до 15 окт" },
|
|
24
|
+
{ id: "c3", title: "Новый тариф для партнёров", label: "Продажи" },
|
|
25
|
+
{
|
|
26
|
+
id: "c4",
|
|
27
|
+
title: "Акт сверки с ООО «Вектор»",
|
|
28
|
+
meta: "INV-3088 · до 11 окт",
|
|
29
|
+
label: "Финансы",
|
|
30
|
+
comments: 4,
|
|
31
|
+
attachments: 2,
|
|
32
|
+
assignee: { initials: "ОБ", name: "Ольга Белова" },
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const PLACEMENT = { inbox: ["c1", "c2"], planned: ["c3", "c4"] };
|
|
37
|
+
|
|
38
|
+
export default function KanbanStructureExample() {
|
|
39
|
+
return (
|
|
40
|
+
<Kanban.Root
|
|
41
|
+
aria-label="Задачи отдела закупок"
|
|
42
|
+
className={styles.board}
|
|
43
|
+
columns={COLUMNS}
|
|
44
|
+
items={CARDS}
|
|
45
|
+
getId={(card) => card.id}
|
|
46
|
+
getLabel={(card) => card.title}
|
|
47
|
+
defaultValue={PLACEMENT}
|
|
48
|
+
renderItem={(card) => (
|
|
49
|
+
<Kanban.Item>
|
|
50
|
+
<Kanban.ItemTitle>{card.title}</Kanban.ItemTitle>
|
|
51
|
+
{card.meta ? <Kanban.ItemDescription>{card.meta}</Kanban.ItemDescription> : null}
|
|
52
|
+
{card.label ? (
|
|
53
|
+
<Kanban.ItemBadges>
|
|
54
|
+
<Badge.Root color="blue">{card.label}</Badge.Root>
|
|
55
|
+
</Kanban.ItemBadges>
|
|
56
|
+
) : null}
|
|
57
|
+
{card.assignee ? (
|
|
58
|
+
<Kanban.ItemFooter>
|
|
59
|
+
<Kanban.ItemCount>
|
|
60
|
+
<Icon name="object.message" />
|
|
61
|
+
{card.comments}
|
|
62
|
+
</Kanban.ItemCount>
|
|
63
|
+
<Kanban.ItemCount>
|
|
64
|
+
<Icon name="object.document" />
|
|
65
|
+
{card.attachments}
|
|
66
|
+
</Kanban.ItemCount>
|
|
67
|
+
<Avatar.Root size="s" color="green" aria-label={card.assignee.name}>
|
|
68
|
+
<Avatar.Fallback>{card.assignee.initials}</Avatar.Fallback>
|
|
69
|
+
</Avatar.Root>
|
|
70
|
+
</Kanban.ItemFooter>
|
|
71
|
+
) : null}
|
|
72
|
+
</Kanban.Item>
|
|
73
|
+
)}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -26,6 +26,7 @@ import { Modal } from "prime-ui-kit";
|
|
|
26
26
|
Modal.Root state and dismiss policy (no DOM)
|
|
27
27
|
├── Modal.Trigger clones its child; opens on click
|
|
28
28
|
└── Modal.Content portal + scrim + role="dialog"
|
|
29
|
+
├── (grab handle) below 640px only: aria-hidden bar, a drag starts here
|
|
29
30
|
├── Modal.Header grid: [Icon] [Title + Description] [close button]
|
|
30
31
|
│ ├── Modal.Icon 40 tile with a tone fill
|
|
31
32
|
│ ├── Modal.Title <h2>, names the dialog
|
|
@@ -49,7 +50,7 @@ No DOM, no ref. State and dismiss policy.
|
|
|
49
50
|
| `defaultOpen` | `boolean` | `false` | Initial visibility, uncontrolled. |
|
|
50
51
|
| `onOpenChange` | `(open: boolean) => void` | — | Called on every open and close: trigger, close button, `Close`, Escape, scrim click, code. |
|
|
51
52
|
| `closeOnEscape` | `boolean` | `true` | Escape closes the dialog. |
|
|
52
|
-
| `closeOnOutsideClick` | `boolean` | `true` | A click on the scrim closes the dialog; turn off for destructive confirms. |
|
|
53
|
+
| `closeOnOutsideClick` | `boolean` | `true` | A click on the scrim (and a swipe on a bottom sheet) closes the dialog; turn off for destructive confirms. |
|
|
53
54
|
| `labels` | `Partial<ModalLabels>` | — | Built-in strings, see Labels. |
|
|
54
55
|
| `children` | `ReactNode` | — | Trigger and Content. |
|
|
55
56
|
| `confirmOnEnter` | `boolean` | `true` | Enter inside the dialog clicks the element wrapped in `Modal.Confirm`; Enter on a button or link activates that element instead. |
|
|
@@ -60,7 +61,7 @@ No DOM, no ref. State and dismiss policy.
|
|
|
60
61
|
|
|
61
62
|
| Prop | Type | Default | Description |
|
|
62
63
|
|---|---|---|---|
|
|
63
|
-
| `size` | `"s" \| "m" \| "l" \| "xl"` | `"m"` | Width: 440 · 560 · 720 · 960. Below 640 px of viewport — a full-width bottom sheet. |
|
|
64
|
+
| `size` | `"s" \| "m" \| "l" \| "xl"` | `"m"` | Width: 440 · 560 · 720 · 960. Below 640 px of viewport — a full-width bottom sheet with a grab handle; a swipe down from the handle or the header closes it. |
|
|
64
65
|
| `container` | `HTMLElement \| null` | `document.body` | Portal target. |
|
|
65
66
|
| `aria-label` | `string` | — | Dialog name when there is no Title. |
|
|
66
67
|
| `aria-labelledby` | `string` | — | Overrides the Title id. |
|
|
@@ -96,7 +97,7 @@ No DOM, no ref. State and dismiss policy.
|
|
|
96
97
|
|
|
97
98
|
| Prop | Type | Default | Description |
|
|
98
99
|
|---|---|---|---|
|
|
99
|
-
| `layout` | `"fill" \| "end"` | `"fill" (s/m), "end" (l/xl)` | `fill`: equal-width buttons in one row; `end`: auto width, at the end.
|
|
100
|
+
| `layout` | `"fill" \| "end"` | `"fill" (s/m), "end" (l/xl)` | `fill`: equal-width buttons in one row; `end`: auto width, at the end. Stacked on phones (viewport below 640 px) and in a dialog narrower than 360 px. |
|
|
100
101
|
|
|
101
102
|
### Modal.Trigger · Modal.Close · Modal.Confirm
|
|
102
103
|
No DOM: clone the single child and chain its `onClick` (unless the child's handler calls `preventDefault()`).
|
|
@@ -116,7 +117,7 @@ The dialog has one look: `bg-raised`, `--prime-modal-radius` (16), `shadow-modal
|
|
|
116
117
|
| `l` | 720 wide, footer buttons right-aligned | Two-column forms | |
|
|
117
118
|
| `xl` | 960 wide, footer buttons right-aligned | Tables, previews | |
|
|
118
119
|
|
|
119
|
-
Below 640px of viewport every size becomes a full-width bottom sheet.
|
|
120
|
+
Below 640px of viewport every size becomes a full-width bottom sheet with a grab handle.
|
|
120
121
|
|
|
121
122
|
### layout (Footer)
|
|
122
123
|
| Value | Looks like | Use when | Default |
|
|
@@ -124,7 +125,7 @@ Below 640px of viewport every size becomes a full-width bottom sheet.
|
|
|
124
125
|
| `fill` | Equal-width buttons in one row, gap 12 | Narrow dialogs with two actions | for `s`, `m` |
|
|
125
126
|
| `end` | Auto-width buttons at the end, gap 8 | Wide dialogs | for `l`, `xl` |
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
On phones (viewport below 640px, where the dialog is a bottom sheet) and in a dialog narrower than 360px, actions stack full width in DOM order (primary last).
|
|
128
129
|
|
|
129
130
|
### tone (Icon)
|
|
130
131
|
| Value | Looks like | Use when | Default |
|
|
@@ -157,6 +158,7 @@ When the dialog itself is narrower than 360px, actions stack full width in DOM o
|
|
|
157
158
|
| footer layout | `layout` or the size default | `data-layout` on the footer |
|
|
158
159
|
| icon tone | `tone` on Icon | `data-tone` on the tile |
|
|
159
160
|
| while open | — | focus trapped inside, page scroll locked, siblings of the portal `inert` (the toast region stays usable) |
|
|
161
|
+
| swiping | below 640px, a drag down from the handle or the header (with `closeOnOutsideClick`) | `data-swiping` and `--swipe-offset` on the dialog while the pointer drags it; past 30% of its height or on a flick it closes, otherwise it glides back |
|
|
160
162
|
|
|
161
163
|
Loading or disabled actions use the Button props (`loading`, `disabled`) inside the footer.
|
|
162
164
|
|
|
@@ -164,7 +166,7 @@ Loading or disabled actions use the Button props (`loading`, `disabled`) inside
|
|
|
164
166
|
- Dialog padding `--prime-modal-padding` (24) on every zone; header 24 top / 20 bottom, body 20 with a 16 gap between blocks, footer 20 top / 24 bottom.
|
|
165
167
|
- Max height = viewport minus `--prime-modal-viewport-padding` on both sides; only the body scrolls.
|
|
166
168
|
- Fields inside get the surface field fill; cards inside become sunken tiles. Group fields with a 20 gap.
|
|
167
|
-
- Narrow viewport (< 640px): bottom sheet, full width, top corners
|
|
169
|
+
- Narrow viewport (< 640px): bottom sheet, full width, top corners `--prime-sheet-radius`, a grab handle strip on top, up to `100dvh − --prime-sheet-top-gap` high, the bottom safe-area inset below the content.
|
|
168
170
|
|
|
169
171
|
## Accessibility
|
|
170
172
|
|
|
@@ -180,6 +182,7 @@ Loading or disabled actions use the Button props (`loading`, `disabled`) inside
|
|
|
180
182
|
- Focus moves into the dialog on open (an `autoFocus` field wins) and returns to the opener on close — also after a scrim click (foundation §8).
|
|
181
183
|
- The page behind is `inert` and its scroll is locked.
|
|
182
184
|
- Only the topmost layer reacts: a Select open inside the dialog closes first.
|
|
185
|
+
- On a narrow viewport a swipe down is an extra way out; the close button and Escape stay. A destructive confirm (`closeOnOutsideClick={false}`) does not close by swipe. The grab handle is `aria-hidden`.
|
|
183
186
|
|
|
184
187
|
### Labels
|
|
185
188
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Notification
|
|
2
2
|
|
|
3
|
-
**Category:**
|
|
3
|
+
**Category:** status
|
|
4
4
|
**Kind:** overlay
|
|
5
5
|
|
|
6
6
|
> Pop-up toast notifications: `NotificationProvider` at the app root and `notify()` from any screen.
|
|
@@ -136,7 +136,7 @@ The card is always a raised surface without a border; only the icon, the badge a
|
|
|
136
136
|
Collapsed stacks show up to 3 cards peeking behind each other. Hover or keyboard focus expands the stack. Toasts enter from their edge and leave toward it, faster than they came; a swipe toward the edge dismisses (past `--prime-space-12` or a quick flick). Under `prefers-reduced-motion` toasts are removed without an exit animation.
|
|
137
137
|
|
|
138
138
|
## Layout & spacing
|
|
139
|
-
- A fixed, portaled viewport; zones are `--prime-space-5` from the edge (`--prime-space-3` below 640px), at most 24rem wide.
|
|
139
|
+
- A fixed, portaled viewport; zones are `--prime-space-5` from the edge (`--prime-space-3` below 640px) plus the safe-area insets (`env(safe-area-inset-*)`), at most 24rem wide.
|
|
140
140
|
- Cards in an expanded stack are `--prime-space-2` apart.
|
|
141
141
|
- The viewport takes pointer events only over the cards.
|
|
142
142
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# PageToolbar
|
|
2
|
+
|
|
3
|
+
**Category:** page
|
|
4
|
+
**Kind:** layout
|
|
5
|
+
|
|
6
|
+
> The panel at the top of a page — sections, filter and search, view options and the primary action — laid out from its own width: one row when wide, exactly two rows when narrow, every slot in a fixed place.
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
- A list, registry or dashboard page whose top holds several of: sections with counts, a filter and search, a period or view switch, the primary action.
|
|
10
|
+
- A settings page with sections and a «Сохранить N изменений» action that must stay reachable on a phone.
|
|
11
|
+
- Any page that has to work from 320px with the same controls as on desktop — nothing is hidden, the slots only move.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
- A page with a title, a description and one or two buttons → [PageContent](../page-content/COMPONENT.md) `Header` with `Actions`.
|
|
15
|
+
- Tools that belong to one table → the DataTable `toolbar` slot.
|
|
16
|
+
- Switching panels inside a card or a section → [Tabs](../tabs/COMPONENT.md).
|
|
17
|
+
- App-level navigation → [Sidebar](../../layout/sidebar/COMPONENT.md).
|
|
18
|
+
|
|
19
|
+
## Import
|
|
20
|
+
```tsx
|
|
21
|
+
import { PageToolbar } from "prime-ui-kit";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Anatomy
|
|
25
|
+
```
|
|
26
|
+
PageToolbar.Root container: its own width picks the layout
|
|
27
|
+
├─ PageToolbar.Sections sections of the page: a fullWidth SegmentedControl with counts
|
|
28
|
+
├─ PageToolbar.Tools filter button + search: SmartFilter.Toolbar
|
|
29
|
+
├─ PageToolbar.View period, table / cards, columns
|
|
30
|
+
├─ PageToolbar.Actions the primary action (and its secondary twin, e.g. «Отменить»)
|
|
31
|
+
├─ PageToolbar.Chips active filters: SmartFilter.Chips, a row under the panel
|
|
32
|
+
└─ break hidden line break of the two-row layout (rendered by Root)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## API
|
|
36
|
+
|
|
37
|
+
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
38
|
+
|
|
39
|
+
### PageToolbar.Root
|
|
40
|
+
`ref` → `HTMLDivElement`. The page panel: lays its slots out from its own width — one row from a 56rem container, exactly two rows below it. Parts may be left out; JSX order is the Tab order.
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Default | Description |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children`, `className` and the other div attributes. |
|
|
45
|
+
|
|
46
|
+
### PageToolbar.Sections
|
|
47
|
+
`ref` → `HTMLDivElement`. Sections of the page — a `fullWidth` SegmentedControl with counts. Top row; sized by content when wide, stretches when narrow and scrolls inside when it does not fit.
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Description |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children`, `className` and the other div attributes. |
|
|
52
|
+
|
|
53
|
+
### PageToolbar.Tools
|
|
54
|
+
`ref` → `HTMLDivElement`. Filter button and search (`SmartFilter.Toolbar`): the stretchy item of its row; gives way down to 18rem (the button and a 9rem search on one line), then the View wraps instead.
|
|
55
|
+
|
|
56
|
+
| Prop | Type | Default | Description |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children`, `className` and the other div attributes. |
|
|
59
|
+
|
|
60
|
+
### PageToolbar.View
|
|
61
|
+
`ref` → `HTMLDivElement`. How the data is shown: period, table / cards, columns. Sized by content next to the Tools; alone in its row it stretches and its controls share the row equally.
|
|
62
|
+
|
|
63
|
+
| Prop | Type | Default | Description |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children`, `className` and the other div attributes. |
|
|
66
|
+
|
|
67
|
+
### PageToolbar.Actions
|
|
68
|
+
`ref` → `HTMLDivElement`. The primary action of the page: at the end of the top row at every width, never wraps down.
|
|
69
|
+
|
|
70
|
+
| Prop | Type | Default | Description |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children`, `className` and the other div attributes. |
|
|
73
|
+
|
|
74
|
+
### PageToolbar.Chips
|
|
75
|
+
`ref` → `HTMLDivElement`. Active filters (`SmartFilter.Chips`) in their own row under the panel; the row takes no space while it is empty.
|
|
76
|
+
|
|
77
|
+
| Prop | Type | Default | Description |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children`, `className` and the other div attributes. |
|
|
80
|
+
|
|
81
|
+
## Variants
|
|
82
|
+
PageToolbar has no variants: the layout follows the toolbar's width.
|
|
83
|
+
|
|
84
|
+
### Layout by width
|
|
85
|
+
| Toolbar width | Looks like | Default |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `≥ 56rem` | one row that never wraps: Sections (content width) · Tools (stretch, at least 18rem) · View (content width) · Actions; short of room the Sections shrink and scroll inside | |
|
|
88
|
+
| `< 56rem` | two rows: Sections (stretch) + Actions on top; Tools (stretch) + View below | yes (mobile first) |
|
|
89
|
+
| any | Chips in their own row under the panel, only while there are chips | |
|
|
90
|
+
|
|
91
|
+
## States
|
|
92
|
+
| State | Driven by | DOM |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| wide | container ≥ 56rem | one row, no wrapping, break hidden; Chips rendered under the bar |
|
|
95
|
+
| narrow | container < 56rem | `order` moves Actions to the top row, the break forces the second row |
|
|
96
|
+
| one row empty | a row has no slot | break hidden, no empty gap |
|
|
97
|
+
| alone in a row | Sections without Actions, Tools without View, View without Tools | that slot stretches to the full row; the View's controls share it equally |
|
|
98
|
+
| tight row | Tools would go under 18rem (the filter button and a 9rem search) | the View wraps whole onto its own row and stretches there; the tools stay one line |
|
|
99
|
+
| no chips | `PageToolbar.Chips` empty (`SmartFilter.Chips` renders nothing) | the chips row takes no space |
|
|
100
|
+
|
|
101
|
+
## Layout & spacing
|
|
102
|
+
- 12 between slots and between rows, 8 inside a slot. No padding or fill of its own: it sits at the top of `PageContent.Body` (or replaces `PageContent.Header` on dense pages that keep their `h1` in the shell).
|
|
103
|
+
- One stretchy item per row: Sections on top, Tools below. The primary action is at the end of the top row at every width and never wraps down; with no sections it is still pushed to the end.
|
|
104
|
+
- Sections take a `fullWidth` SegmentedControl: the toolbar decides when it stretches (content width when wide, the full row when narrow), its items tend to equal width and it scrolls inside itself when they do not fit.
|
|
105
|
+
- All controls share one tier, so they share one height and wrapped rows stay level: leave `size` off the controls (default `m`); for a denser page wrap the toolbar in `<ControlSizeProvider value="s">`.
|
|
106
|
+
- On a phone the primary action may drop its text and keep the icon with `aria-label`; never remove it.
|
|
107
|
+
- The toolbar breaks by its own width, so it behaves the same with the sidebar open or closed.
|
|
108
|
+
|
|
109
|
+
## Accessibility
|
|
110
|
+
|
|
111
|
+
### Keyboard
|
|
112
|
+
No keyboard interaction.
|
|
113
|
+
|
|
114
|
+
### ARIA
|
|
115
|
+
- A plain `<div>`: name it with `aria-label` when a page has more than one panel.
|
|
116
|
+
- Slots move only visually (`order`); the Tab order is the JSX order. Write the slots in the reading order of the wide layout — Sections, Tools, View, Actions, Chips. In the narrow layout the action is shown before the tools but reached after them.
|
|
117
|
+
- An icon-only primary action on a narrow page carries `aria-label`.
|
|
118
|
+
|
|
119
|
+
### Labels
|
|
120
|
+
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
121
|
+
|
|
122
|
+
No `labels`.
|
|
123
|
+
|
|
124
|
+
## Examples
|
|
125
|
+
| Example | Shows |
|
|
126
|
+
|---|---|
|
|
127
|
+
| [overview.tsx](examples/overview.tsx) | The panel of an orders page: sections with counts, filter and search, the period and the primary action in one row; active filters in a row under it — `PageToolbar.Sections`, `PageToolbar.Tools`, `PageToolbar.View`, `PageToolbar.Actions`, `PageToolbar.Chips`. |
|
|
128
|
+
| [without-search.tsx](examples/without-search.tsx) | A dashboard panel without search on a tablet-width column: the sections fill the top row, the period selector alone in the bottom row stretches to the full width — `PageToolbar.Sections`, `PageToolbar.View`. |
|
|
129
|
+
| [save-changes.tsx](examples/save-changes.tsx) | A settings page: sections of the form on the left, the save action with the number of changes at the end of the top row — never a sticky bar at the bottom of a phone screen — `PageToolbar.Actions`. |
|
|
130
|
+
| [narrow.tsx](examples/narrow.tsx) | A phone-width column: exactly two rows — sections and the primary action on top, filter, search and the period below; each row has one stretchy item — `PageToolbar.Root`. |
|
|
131
|
+
|
|
132
|
+
## Mistakes
|
|
133
|
+
- A hand-built flex row of tabs, search and buttons with `flex-wrap` → rows wrap «as they come», the action drifts down; use `PageToolbar`.
|
|
134
|
+
- `size="s"` on one control inside → a step in the row; one tier for all, through `ControlSizeProvider` around the toolbar.
|
|
135
|
+
- A SegmentedControl without `fullWidth` in Sections → it never stretches on narrow widths.
|
|
136
|
+
- Stat cards above a table for the section totals → counts inside the sections (`SegmentedControl.Count`).
|
|
137
|
+
- Hiding the search or the action on phones → they move, they never disappear.
|
|
138
|
+
- A sticky «Сохранить» bar at the bottom of a phone → the save action with its count in `PageToolbar.Actions`.
|
|
139
|
+
|
|
140
|
+
## Related
|
|
141
|
+
- **Built from:** —
|
|
142
|
+
- **See also:** [SegmentedControl](../segmented-control/COMPONENT.md), [SmartFilter](../smart-filter/COMPONENT.md), [PageContent](../page-content/COMPONENT.md), [DataTable](../data-table/COMPONENT.md), [Select](../select/COMPONENT.md)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* Layout of the PageToolbar scenarios: page columns of a capped width, so the toolbar shows its
|
|
2
|
+
two-row form. A cap (`max-width`), not a width: the column is as wide as the stage allows. */
|
|
3
|
+
|
|
4
|
+
/* A phone-width page column (360px). */
|
|
5
|
+
.phone {
|
|
6
|
+
width: 100%;
|
|
7
|
+
max-width: calc(var(--prime-space-24) * 3.75);
|
|
8
|
+
margin-inline: auto;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* A tablet-width page column (600px). */
|
|
12
|
+
.tablet {
|
|
13
|
+
width: 100%;
|
|
14
|
+
max-width: calc(var(--prime-space-24) * 6.25);
|
|
15
|
+
margin-inline: auto;
|
|
16
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** A phone-width column: exactly two rows — sections and the primary action on top, filter, search and the period below; each row has one stretchy item — `PageToolbar.Root`. */
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Icon,
|
|
5
|
+
PageToolbar,
|
|
6
|
+
SegmentedControl,
|
|
7
|
+
Select,
|
|
8
|
+
SmartFilter,
|
|
9
|
+
type SmartFilterField,
|
|
10
|
+
type SmartFilterValue,
|
|
11
|
+
} from "prime-ui-kit";
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
|
|
14
|
+
import styles from "./examples.module.css";
|
|
15
|
+
|
|
16
|
+
const SECTIONS = [
|
|
17
|
+
{ value: "all", label: "Все", count: 128 },
|
|
18
|
+
{ value: "active", label: "В работе", count: 12 },
|
|
19
|
+
{ value: "done", label: "Доставлены", count: 96 },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const PERIODS = [
|
|
23
|
+
{ value: "7d", label: "7 дней" },
|
|
24
|
+
{ value: "30d", label: "30 дней" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const FIELDS: SmartFilterField[] = [
|
|
28
|
+
{
|
|
29
|
+
key: "city",
|
|
30
|
+
label: "Город",
|
|
31
|
+
options: [
|
|
32
|
+
{ value: "msk", label: "Москва" },
|
|
33
|
+
{ value: "spb", label: "Санкт-Петербург" },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export default function PageToolbarNarrowExample() {
|
|
39
|
+
const [section, setSection] = React.useState("all");
|
|
40
|
+
const [period, setPeriod] = React.useState("7d");
|
|
41
|
+
const [filter, setFilter] = React.useState<SmartFilterValue>({});
|
|
42
|
+
const [search, setSearch] = React.useState("");
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className={styles.phone}>
|
|
46
|
+
<SmartFilter.Root
|
|
47
|
+
fields={FIELDS}
|
|
48
|
+
value={filter}
|
|
49
|
+
onValueChange={setFilter}
|
|
50
|
+
search={search}
|
|
51
|
+
onSearchChange={setSearch}
|
|
52
|
+
>
|
|
53
|
+
<PageToolbar.Root aria-label="Заказы">
|
|
54
|
+
<PageToolbar.Sections>
|
|
55
|
+
<SegmentedControl.Root
|
|
56
|
+
fullWidth
|
|
57
|
+
value={section}
|
|
58
|
+
onValueChange={setSection}
|
|
59
|
+
aria-label="Раздел"
|
|
60
|
+
>
|
|
61
|
+
{SECTIONS.map((item) => (
|
|
62
|
+
<SegmentedControl.Item key={item.value} value={item.value}>
|
|
63
|
+
{item.label}
|
|
64
|
+
<SegmentedControl.Count>{item.count}</SegmentedControl.Count>
|
|
65
|
+
</SegmentedControl.Item>
|
|
66
|
+
))}
|
|
67
|
+
</SegmentedControl.Root>
|
|
68
|
+
</PageToolbar.Sections>
|
|
69
|
+
<PageToolbar.Tools>
|
|
70
|
+
<SmartFilter.Toolbar />
|
|
71
|
+
</PageToolbar.Tools>
|
|
72
|
+
<PageToolbar.View>
|
|
73
|
+
<Select.Root value={period} onValueChange={setPeriod}>
|
|
74
|
+
<Select.Trigger aria-label="Период">
|
|
75
|
+
<Select.TriggerIcon>
|
|
76
|
+
<Icon name="field.calendar" />
|
|
77
|
+
</Select.TriggerIcon>
|
|
78
|
+
<Select.Value />
|
|
79
|
+
</Select.Trigger>
|
|
80
|
+
<Select.Content>
|
|
81
|
+
{PERIODS.map((item) => (
|
|
82
|
+
<Select.Item key={item.value} value={item.value}>
|
|
83
|
+
{item.label}
|
|
84
|
+
</Select.Item>
|
|
85
|
+
))}
|
|
86
|
+
</Select.Content>
|
|
87
|
+
</Select.Root>
|
|
88
|
+
</PageToolbar.View>
|
|
89
|
+
<PageToolbar.Actions>
|
|
90
|
+
<Button.Root aria-label="Новый заказ">
|
|
91
|
+
<Button.Icon>
|
|
92
|
+
<Icon name="action.add" />
|
|
93
|
+
</Button.Icon>
|
|
94
|
+
</Button.Root>
|
|
95
|
+
</PageToolbar.Actions>
|
|
96
|
+
<PageToolbar.Chips>
|
|
97
|
+
<SmartFilter.Chips />
|
|
98
|
+
</PageToolbar.Chips>
|
|
99
|
+
</PageToolbar.Root>
|
|
100
|
+
</SmartFilter.Root>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|