prime-ui-kit 0.19.0 → 0.20.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-RZNGLELR.js → chunk-3PY52JP3.js} +1603 -1609
- package/dist/chunks/{chunk-WYCTXD63.js → chunk-4374EBWV.js} +3593 -2889
- package/dist/chunks/{chunk-ZTZ2ML2B.js → chunk-4KONMO7Z.js} +1 -1
- package/dist/color-picker.js +2 -2
- package/dist/components/data-table/ColumnsMenu.d.ts +34 -8
- package/dist/components/data-table/DataTable.d.ts +31 -5
- package/dist/components/data-table/DataTableColumnsMenu.d.ts +45 -0
- package/dist/components/data-table/Head.d.ts +14 -8
- package/dist/components/data-table/Row.d.ts +8 -8
- package/dist/components/data-table/StateRows.d.ts +5 -5
- package/dist/components/data-table/columnOrder.d.ts +14 -0
- package/dist/components/data-table/reorder.d.ts +15 -5
- package/dist/components/data-table/types.d.ts +5 -0
- package/dist/components/data-table/useFrozenColumns.d.ts +6 -2
- package/dist/components/datepicker/DatepickerPanel.d.ts +1 -1
- package/dist/components/dnd/Dnd.d.ts +9 -5
- package/dist/components/dnd/inPlaceRow.d.ts +7 -0
- package/dist/components/dnd/useDragSource.d.ts +2 -0
- package/dist/components/dnd/useSortableList.d.ts +5 -0
- package/dist/components/field-list/FieldList.d.ts +34 -12
- package/dist/components/gantt/Gantt.d.ts +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/scroll-container/ScrollContainer.d.ts +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +4 -2
- package/dist/internal/ToolRow.d.ts +23 -0
- package/dist/internal/fieldCell.d.ts +0 -6
- package/dist/layout/page/Page.d.ts +7 -7
- package/dist/skill/anti-slop.md +43 -12
- package/dist/skill/api-contract.md +1 -1
- package/dist/skill/cheatsheet.md +7 -3
- package/dist/skill/checklist.md +4 -1
- package/dist/skill/choosing.md +1 -0
- package/dist/skill/components.md +1 -1
- package/dist/skill/composition.md +137 -20
- package/dist/skill/reference/components/data-table/COMPONENT.md +101 -40
- package/dist/skill/reference/components/data-table/examples/column-order.tsx +152 -0
- package/dist/skill/reference/components/data-table/examples/columns-visibility.tsx +20 -11
- package/dist/skill/reference/components/data-table/examples/controlled.tsx +32 -17
- package/dist/skill/reference/components/data-table/examples/examples.module.css +14 -36
- package/dist/skill/reference/components/data-table/examples/narrow.tsx +24 -11
- package/dist/skill/reference/components/data-table/examples/reorder.tsx +24 -2
- package/dist/skill/reference/components/data-table/examples/selection.tsx +13 -18
- package/dist/skill/reference/components/data-table/examples/server.tsx +45 -31
- package/dist/skill/reference/components/data-table/examples/toolbar.tsx +5 -12
- package/dist/skill/reference/components/datepicker/COMPONENT.md +2 -2
- package/dist/skill/reference/components/dnd/COMPONENT.md +12 -7
- package/dist/skill/reference/components/dnd/examples/table-columns.tsx +75 -0
- package/dist/skill/reference/components/field-list/COMPONENT.md +70 -43
- package/dist/skill/reference/components/field-list/examples/narrow.tsx +108 -33
- package/dist/skill/reference/components/field-list/examples/reorder.tsx +1 -1
- package/dist/skill/reference/components/field-list/examples/row-menu.tsx +71 -0
- package/dist/skill/reference/components/field-list/examples/screen.tsx +2 -1
- package/dist/skill/reference/components/field-list/examples/variants.tsx +51 -0
- package/dist/skill/reference/components/gantt/COMPONENT.md +4 -4
- package/dist/skill/reference/components/scroll-container/COMPONENT.md +1 -1
- package/dist/skill/reference/components/smart-filter/COMPONENT.md +2 -2
- package/dist/skill/reference/layout/page/COMPONENT.md +8 -5
- package/dist/skill/responsive.md +10 -8
- package/dist/tokens/semantic.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/data-table/COMPONENT.md +101 -40
- package/src/components/data-table/examples/column-order.tsx +152 -0
- package/src/components/data-table/examples/columns-visibility.tsx +20 -11
- package/src/components/data-table/examples/controlled.tsx +32 -17
- package/src/components/data-table/examples/examples.module.css +14 -36
- package/src/components/data-table/examples/narrow.tsx +24 -11
- package/src/components/data-table/examples/reorder.tsx +24 -2
- package/src/components/data-table/examples/selection.tsx +13 -18
- package/src/components/data-table/examples/server.tsx +45 -31
- package/src/components/data-table/examples/toolbar.tsx +5 -12
- package/src/components/datepicker/COMPONENT.md +2 -2
- package/src/components/dnd/COMPONENT.md +12 -7
- package/src/components/dnd/examples/table-columns.tsx +75 -0
- package/src/components/field-list/COMPONENT.md +70 -43
- package/src/components/field-list/examples/narrow.tsx +108 -33
- package/src/components/field-list/examples/reorder.tsx +1 -1
- package/src/components/field-list/examples/row-menu.tsx +71 -0
- package/src/components/field-list/examples/screen.tsx +2 -1
- package/src/components/field-list/examples/variants.tsx +51 -0
- package/src/components/gantt/COMPONENT.md +4 -4
- package/src/components/scroll-container/COMPONENT.md +1 -1
- package/src/components/smart-filter/COMPONENT.md +2 -2
- package/src/layout/page/COMPONENT.md +8 -5
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/** Sort, page and the column choice owned by the parent (a URL or a store): a header click goes asc → desc → none, a new sort returns to page 1 — `sort`, `onSortChange`, `page`, `onPageChange`, `hiddenColumns`, `onHiddenColumnsChange`. */
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DataTable,
|
|
4
|
+
type DataTableColumn,
|
|
5
|
+
DataTableColumnsMenu,
|
|
6
|
+
type DataTableSortState,
|
|
7
|
+
} from "prime-ui-kit";
|
|
3
8
|
import * as React from "react";
|
|
4
9
|
|
|
10
|
+
import styles from "./examples.module.css";
|
|
11
|
+
|
|
5
12
|
type Order = { id: number; customer: string; city: string; total: number };
|
|
6
13
|
|
|
7
14
|
const CUSTOMERS = ["Анна Соколова", "Борис Кравец", "Вера Львова", "Глеб Миронов", "Дарья Носова"];
|
|
@@ -48,21 +55,29 @@ export default function DataTableControlledExample() {
|
|
|
48
55
|
const [hiddenColumns, setHiddenColumns] = React.useState<string[]>(["city"]);
|
|
49
56
|
|
|
50
57
|
return (
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
<div className={styles.withTools}>
|
|
59
|
+
<div className={styles.tools}>
|
|
60
|
+
<DataTableColumnsMenu
|
|
61
|
+
columns={COLUMNS}
|
|
62
|
+
hiddenColumns={hiddenColumns}
|
|
63
|
+
onHiddenColumnsChange={setHiddenColumns}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<DataTable
|
|
67
|
+
columns={COLUMNS}
|
|
68
|
+
rows={ORDERS}
|
|
69
|
+
getRowKey={(row) => row.id}
|
|
70
|
+
hiddenColumns={hiddenColumns}
|
|
71
|
+
onHiddenColumnsChange={setHiddenColumns}
|
|
72
|
+
sort={sort}
|
|
73
|
+
onSortChange={(next) => {
|
|
74
|
+
setSort(next);
|
|
75
|
+
setPage(1);
|
|
76
|
+
}}
|
|
77
|
+
page={page}
|
|
78
|
+
onPageChange={setPage}
|
|
79
|
+
pageSize={5}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
67
82
|
);
|
|
68
83
|
}
|
|
@@ -1,41 +1,5 @@
|
|
|
1
1
|
/* Layout of the DataTable scenarios: semantic tokens only. */
|
|
2
2
|
|
|
3
|
-
/* Toolbar content: two groups pushed to the edges; they wrap on narrow widths. */
|
|
4
|
-
.toolbar {
|
|
5
|
-
display: flex;
|
|
6
|
-
flex-wrap: wrap;
|
|
7
|
-
align-items: center;
|
|
8
|
-
justify-content: space-between;
|
|
9
|
-
gap: var(--prime-space-2);
|
|
10
|
-
width: 100%;
|
|
11
|
-
min-width: 0;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/* A SmartFilter in the toolbar: the search and filter row over the applied filters. */
|
|
15
|
-
.filterColumn {
|
|
16
|
-
display: flex;
|
|
17
|
-
flex: 1 1 auto;
|
|
18
|
-
flex-direction: column;
|
|
19
|
-
gap: var(--prime-space-3);
|
|
20
|
-
min-width: 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.filters {
|
|
24
|
-
display: flex;
|
|
25
|
-
flex: 1 1 auto;
|
|
26
|
-
flex-wrap: wrap;
|
|
27
|
-
align-items: center;
|
|
28
|
-
gap: var(--prime-space-2);
|
|
29
|
-
min-width: 0;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/* The search grows from 12rem, at most 20rem. */
|
|
33
|
-
.search {
|
|
34
|
-
flex: 1 1 calc(var(--prime-space-24) * 2);
|
|
35
|
-
min-width: 0;
|
|
36
|
-
max-width: calc(var(--prime-space-20) * 4);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
3
|
/* appearance / content-width: a table with its caption under it. */
|
|
40
4
|
.specimen {
|
|
41
5
|
display: flex;
|
|
@@ -73,3 +37,17 @@
|
|
|
73
37
|
min-width: 0;
|
|
74
38
|
margin-inline: auto;
|
|
75
39
|
}
|
|
40
|
+
|
|
41
|
+
/* A table under the tools of its page: the column chooser (`DataTableColumnsMenu`) at the end. */
|
|
42
|
+
.withTools {
|
|
43
|
+
display: grid;
|
|
44
|
+
grid-template-columns: minmax(0, 1fr);
|
|
45
|
+
gap: var(--prime-space-3);
|
|
46
|
+
min-width: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.tools {
|
|
50
|
+
display: flex;
|
|
51
|
+
justify-content: flex-end;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
/** A 320 px support queue: columns scroll inside the table with the first one pinned, the column chooser stays
|
|
2
|
-
import { Badge, DataTable, type DataTableColumn } from "prime-ui-kit";
|
|
1
|
+
/** A 320 px support queue: columns scroll inside the table with the first one pinned, the column chooser stays in reach above it, the footer reflows and the pager turns compact — `stickyFirstColumn`, `DataTableColumnsMenu`, `size`. */
|
|
2
|
+
import { Badge, DataTable, type DataTableColumn, DataTableColumnsMenu } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
|
|
4
5
|
import styles from "./examples.module.css";
|
|
5
6
|
|
|
@@ -31,17 +32,29 @@ const COLUMNS: DataTableColumn<Ticket>[] = [
|
|
|
31
32
|
];
|
|
32
33
|
|
|
33
34
|
export default function DataTableNarrowExample() {
|
|
35
|
+
const [hidden, setHidden] = React.useState<string[]>([]);
|
|
36
|
+
|
|
34
37
|
return (
|
|
35
38
|
<div className={styles.narrow}>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
<div className={styles.withTools}>
|
|
40
|
+
<div className={styles.tools}>
|
|
41
|
+
<DataTableColumnsMenu
|
|
42
|
+
size="s"
|
|
43
|
+
columns={COLUMNS}
|
|
44
|
+
hiddenColumns={hidden}
|
|
45
|
+
onHiddenColumnsChange={setHidden}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
<DataTable
|
|
49
|
+
size="s"
|
|
50
|
+
columns={COLUMNS}
|
|
51
|
+
hiddenColumns={hidden}
|
|
52
|
+
rows={TICKETS}
|
|
53
|
+
getRowKey={(row) => row.id}
|
|
54
|
+
stickyFirstColumn
|
|
55
|
+
pageSize={5}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
45
58
|
</div>
|
|
46
59
|
);
|
|
47
60
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
/** Pipeline stages put in order by the
|
|
2
|
-
import { Badge, DataTable, type DataTableColumn, moveBefore } from "prime-ui-kit";
|
|
1
|
+
/** Pipeline stages put in order by a grip at the end of the row, at its start or by the whole row, or with Alt + ↑ / ↓; each move is saved with a spinner in its grip, and sorting by a column turns reordering off — `onReorder`, `reorderHandle`, `getRowLabel`. */
|
|
2
|
+
import { Badge, DataTable, type DataTableColumn, moveBefore, SegmentedControl } from "prime-ui-kit";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
type Stage = { id: string; name: string; owner: string; deals: number; probability: number };
|
|
6
|
+
type Handle = "end" | "start" | "row";
|
|
6
7
|
|
|
7
8
|
const STAGES: Stage[] = [
|
|
8
9
|
{ id: "lead", name: "Новый лид", owner: "Отдел продаж", deals: 48, probability: 10 },
|
|
@@ -19,6 +20,12 @@ const STAGES: Stage[] = [
|
|
|
19
20
|
{ id: "payment", name: "Оплата", owner: "Бухгалтерия", deals: 4, probability: 95 },
|
|
20
21
|
];
|
|
21
22
|
|
|
23
|
+
const HANDLES: { value: Handle; label: string }[] = [
|
|
24
|
+
{ value: "end", label: "Ручка в конце" },
|
|
25
|
+
{ value: "start", label: "Ручка в начале" },
|
|
26
|
+
{ value: "row", label: "Вся строка" },
|
|
27
|
+
];
|
|
28
|
+
|
|
22
29
|
const COLUMNS: DataTableColumn<Stage>[] = [
|
|
23
30
|
{ id: "name", header: "Этап", accessor: "name", hideable: false },
|
|
24
31
|
{ id: "owner", header: "Ответственный", accessor: "owner" },
|
|
@@ -38,6 +45,7 @@ const saveOrder = () => new Promise<void>((resolve) => setTimeout(resolve, 700))
|
|
|
38
45
|
|
|
39
46
|
export default function DataTableReorderExample() {
|
|
40
47
|
const [stages, setStages] = React.useState(STAGES);
|
|
48
|
+
const [handle, setHandle] = React.useState<Handle>("end");
|
|
41
49
|
return (
|
|
42
50
|
<DataTable
|
|
43
51
|
columns={COLUMNS}
|
|
@@ -45,6 +53,20 @@ export default function DataTableReorderExample() {
|
|
|
45
53
|
paging="none"
|
|
46
54
|
getRowKey={(row) => row.id}
|
|
47
55
|
getRowLabel={(row) => row.name}
|
|
56
|
+
reorderHandle={handle}
|
|
57
|
+
toolbar={
|
|
58
|
+
<SegmentedControl.Root
|
|
59
|
+
value={handle}
|
|
60
|
+
onValueChange={(next) => setHandle(next as Handle)}
|
|
61
|
+
aria-label="За что брать строку"
|
|
62
|
+
>
|
|
63
|
+
{HANDLES.map((item) => (
|
|
64
|
+
<SegmentedControl.Item key={item.value} value={item.value}>
|
|
65
|
+
{item.label}
|
|
66
|
+
</SegmentedControl.Item>
|
|
67
|
+
))}
|
|
68
|
+
</SegmentedControl.Root>
|
|
69
|
+
}
|
|
48
70
|
onReorder={async (id, beforeId) => {
|
|
49
71
|
await saveOrder();
|
|
50
72
|
setStages((current) =>
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
import { Button, DataTable, type DataTableColumn, Typography } from "prime-ui-kit";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
|
-
import styles from "./examples.module.css";
|
|
6
|
-
|
|
7
5
|
type Member = { id: string; name: string; email: string; role: string };
|
|
8
6
|
|
|
9
7
|
const MEMBERS: Member[] = [
|
|
@@ -35,25 +33,22 @@ export default function DataTableSelectionExample() {
|
|
|
35
33
|
onSelectedChange={setSelected}
|
|
36
34
|
paging="none"
|
|
37
35
|
toolbar={
|
|
38
|
-
|
|
36
|
+
<>
|
|
39
37
|
<Typography as="span" variant="body-s" tone="secondary">
|
|
40
38
|
Выбрано: {selected.length} из {MEMBERS.length}
|
|
41
39
|
</Typography>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
</Button.Root>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
40
|
+
<Button.Root
|
|
41
|
+
variant="outline"
|
|
42
|
+
tone="neutral"
|
|
43
|
+
disabled={selected.length === 0}
|
|
44
|
+
onClick={() => setSelected([])}
|
|
45
|
+
>
|
|
46
|
+
Снять выбор
|
|
47
|
+
</Button.Root>
|
|
48
|
+
<Button.Root variant="soft" tone="danger" disabled={selected.length === 0}>
|
|
49
|
+
Удалить
|
|
50
|
+
</Button.Root>
|
|
51
|
+
</>
|
|
57
52
|
}
|
|
58
53
|
/>
|
|
59
54
|
);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
/** A CRM deal list the server sorts, pages and searches; a new search returns to page 1 — `source`, `rowCount`, `sort`, `page`, `loading`. */
|
|
1
|
+
/** A CRM deal list the server sorts, pages and searches, with its tools in its own row — search, filter and column chooser, the active filters under them; a new search returns to page 1 — `source`, `rowCount`, `sort`, `page`, `loading`, `toolbar`, `toolbarChips`. */
|
|
2
2
|
import {
|
|
3
3
|
Badge,
|
|
4
4
|
DataTable,
|
|
5
5
|
type DataTableColumn,
|
|
6
|
+
DataTableColumnsMenu,
|
|
6
7
|
type DataTableSortState,
|
|
7
8
|
matchesSmartFilter,
|
|
8
9
|
SmartFilter,
|
|
@@ -11,8 +12,6 @@ import {
|
|
|
11
12
|
} from "prime-ui-kit";
|
|
12
13
|
import * as React from "react";
|
|
13
14
|
|
|
14
|
-
import styles from "./examples.module.css";
|
|
15
|
-
|
|
16
15
|
type Stage = "new" | "talks" | "won" | "lost";
|
|
17
16
|
type Deal = { id: number; client: string; manager: string; stage: Stage; amount: number };
|
|
18
17
|
type DealsRequest = {
|
|
@@ -79,7 +78,15 @@ const RUB = new Intl.NumberFormat("ru-RU", {
|
|
|
79
78
|
});
|
|
80
79
|
|
|
81
80
|
const COLUMNS: DataTableColumn<Deal>[] = [
|
|
82
|
-
{
|
|
81
|
+
{
|
|
82
|
+
id: "id",
|
|
83
|
+
header: "Сделка",
|
|
84
|
+
accessor: "id",
|
|
85
|
+
sortable: true,
|
|
86
|
+
numeric: true,
|
|
87
|
+
align: "start",
|
|
88
|
+
hideable: false,
|
|
89
|
+
},
|
|
83
90
|
{ id: "client", header: "Клиент", accessor: "client", sortable: true },
|
|
84
91
|
{ id: "manager", header: "Менеджер", accessor: "manager", sortable: true },
|
|
85
92
|
{
|
|
@@ -125,6 +132,7 @@ export default function DataTableServerExample() {
|
|
|
125
132
|
const [search, setSearch] = React.useState("");
|
|
126
133
|
const [sort, setSort] = React.useState<DataTableSortState>({ columnId: "amount", order: "desc" });
|
|
127
134
|
const [page, setPage] = React.useState(1);
|
|
135
|
+
const [hidden, setHidden] = React.useState<string[]>([]);
|
|
128
136
|
const [result, setResult] = React.useState<DealsPage>({ rows: [], total: 0 });
|
|
129
137
|
const [loading, setLoading] = React.useState(true);
|
|
130
138
|
|
|
@@ -153,33 +161,39 @@ export default function DataTableServerExample() {
|
|
|
153
161
|
};
|
|
154
162
|
|
|
155
163
|
return (
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
164
|
+
<SmartFilter.Root
|
|
165
|
+
fields={FIELDS}
|
|
166
|
+
value={filter}
|
|
167
|
+
onValueChange={changeFilter}
|
|
168
|
+
search={search}
|
|
169
|
+
onSearchChange={changeSearch}
|
|
170
|
+
>
|
|
171
|
+
<DataTable
|
|
172
|
+
source="server"
|
|
173
|
+
columns={COLUMNS}
|
|
174
|
+
hiddenColumns={hidden}
|
|
175
|
+
rows={result.rows}
|
|
176
|
+
rowCount={result.total}
|
|
177
|
+
getRowKey={(row) => row.id}
|
|
178
|
+
pageSize={PAGE_SIZE}
|
|
179
|
+
page={page}
|
|
180
|
+
onPageChange={setPage}
|
|
181
|
+
sort={sort}
|
|
182
|
+
onSortChange={setSort}
|
|
183
|
+
loading={loading}
|
|
184
|
+
empty="Сделок не найдено — измените запрос или фильтры"
|
|
185
|
+
toolbar={
|
|
186
|
+
<>
|
|
178
187
|
<SmartFilter.Toolbar />
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
<DataTableColumnsMenu
|
|
189
|
+
columns={COLUMNS}
|
|
190
|
+
hiddenColumns={hidden}
|
|
191
|
+
onHiddenColumnsChange={setHidden}
|
|
192
|
+
/>
|
|
193
|
+
</>
|
|
194
|
+
}
|
|
195
|
+
toolbarChips={<SmartFilter.Chips />}
|
|
196
|
+
/>
|
|
197
|
+
</SmartFilter.Root>
|
|
184
198
|
);
|
|
185
199
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A campaign table with its own search
|
|
1
|
+
/** A campaign table with its own tools row: the search takes the free room, the status switch keeps its width at the end, and on a narrow table they wrap; the empty text when nothing matches — `toolbar`, `empty`. */
|
|
2
2
|
import {
|
|
3
3
|
Badge,
|
|
4
4
|
DataTable,
|
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
} from "prime-ui-kit";
|
|
10
10
|
import * as React from "react";
|
|
11
11
|
|
|
12
|
-
import styles from "./examples.module.css";
|
|
13
|
-
|
|
14
12
|
type Status = "active" | "paused" | "archived";
|
|
15
13
|
type Campaign = { id: string; name: string; status: Status; clicks: number; ctr: number };
|
|
16
14
|
|
|
@@ -86,8 +84,8 @@ export default function DataTableToolbarExample() {
|
|
|
86
84
|
pageSize={5}
|
|
87
85
|
empty="Ничего не найдено — измените запрос или фильтр"
|
|
88
86
|
toolbar={
|
|
89
|
-
|
|
90
|
-
<Input.Root
|
|
87
|
+
<>
|
|
88
|
+
<Input.Root>
|
|
91
89
|
<Input.Wrapper>
|
|
92
90
|
<Input.Icon side="start">
|
|
93
91
|
<Icon name="action.search" />
|
|
@@ -101,17 +99,12 @@ export default function DataTableToolbarExample() {
|
|
|
101
99
|
/>
|
|
102
100
|
</Input.Wrapper>
|
|
103
101
|
</Input.Root>
|
|
104
|
-
<SegmentedControl.Root
|
|
105
|
-
size="s"
|
|
106
|
-
aria-label="Статус"
|
|
107
|
-
value={status}
|
|
108
|
-
onValueChange={setStatus}
|
|
109
|
-
>
|
|
102
|
+
<SegmentedControl.Root aria-label="Статус" value={status} onValueChange={setStatus}>
|
|
110
103
|
<SegmentedControl.Item value="all">Все</SegmentedControl.Item>
|
|
111
104
|
<SegmentedControl.Item value="active">Активные</SegmentedControl.Item>
|
|
112
105
|
<SegmentedControl.Item value="paused">На паузе</SegmentedControl.Item>
|
|
113
106
|
</SegmentedControl.Root>
|
|
114
|
-
|
|
107
|
+
</>
|
|
115
108
|
}
|
|
116
109
|
/>
|
|
117
110
|
);
|
|
@@ -78,7 +78,7 @@ panel presets column (range) · 1–2 month grids · promp
|
|
|
78
78
|
| `name` | `string` | — | Submits the value with a form through hidden inputs: `yyyy-MM-dd` (`yyyy-MM-ddTHH:mm` with `withTime`); a range submits two values under the name, `from` then `to` (empty for an open bound). |
|
|
79
79
|
| `aria-label · aria-labelledby · aria-describedby` | `string` | — | Name without a `label` (the value is appended) and extra description ids. |
|
|
80
80
|
| `months` | `1 \| 2` | `1` | Months side by side; one when there is no room. |
|
|
81
|
-
| `presets` | `DatepickerPreset[] \| false` | `false` | Preset column (`range` only): `DEFAULT_DATEPICKER_PRESETS` or picks from `datepickerPresets`; a row above the calendar when narrow. |
|
|
81
|
+
| `presets` | `DatepickerPreset[] \| false` | `false` | Preset column (`range` only): `DEFAULT_DATEPICKER_PRESETS` (ends with «Всё время») or picks from `datepickerPresets`; a row above the calendar when narrow. |
|
|
82
82
|
| `prompt` | `boolean` | `false` | Step prompt under the calendar (`labels.pickStart` / `pickEnd` / `pickDate`). |
|
|
83
83
|
| `footer` | `boolean` | `false` | Footer with date fields and Reset / Apply; without it a pick applies at once. |
|
|
84
84
|
| `withTime` | `boolean` | `false` | Time fields in the footer (00:00 — 23:59 by default). |
|
|
@@ -103,7 +103,7 @@ panel presets column (range) · 1–2 month grids · promp
|
|
|
103
103
|
| `onValueChange` | `(value) => void` | — | Called when a pick, a preset, Apply or Reset changes the value. |
|
|
104
104
|
| `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | host tier, else `"m"` | Panel tier (day cell 24 · 28 · 32 · 36 · 40). Without it the tier of its host, else `m`. |
|
|
105
105
|
| `months` | `1 \| 2` | `1` | Months side by side; one when there is no room. |
|
|
106
|
-
| `presets` | `DatepickerPreset[] \| false` | `false` | Preset column (`range` only): `DEFAULT_DATEPICKER_PRESETS` or picks from `datepickerPresets`; a row above the calendar when narrow. |
|
|
106
|
+
| `presets` | `DatepickerPreset[] \| false` | `false` | Preset column (`range` only): `DEFAULT_DATEPICKER_PRESETS` (ends with «Всё время») or picks from `datepickerPresets`; a row above the calendar when narrow. |
|
|
107
107
|
| `prompt` | `boolean` | `false` | Step prompt under the calendar (`labels.pickStart` / `pickEnd` / `pickDate`). |
|
|
108
108
|
| `footer` | `boolean` | `false` | Footer with date fields and Reset / Apply; without it a pick applies at once. |
|
|
109
109
|
| `withTime` | `boolean` | `false` | Time fields in the footer (00:00 — 23:59 by default). |
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- Moving an item between lists to an exact position: a board of columns (several `Dnd.Sortable` with one `kind`; Alt + ← / → moves across from the keyboard).
|
|
12
12
|
- A list with a pinned first row or a system item that must hold its place (`locked`).
|
|
13
13
|
- Reordering the rows of a table of your own (`as="tbody"`; a group's heading row in `header`). For records in a [DataTable](../data-table/COMPONENT.md) use its `onReorder`.
|
|
14
|
+
- Reordering the columns of a table of your own (`as="tr"` on the head row; the body follows the order on the drop). For a [DataTable](../data-table/COMPONENT.md) use its `columnOrder`.
|
|
14
15
|
- Moving an item onto a target with no inner order: a folder, an assignee (`Dnd.Draggable` + `Dnd.DropZone`).
|
|
15
16
|
|
|
16
17
|
## When not to use
|
|
@@ -29,14 +30,14 @@ import { Dnd, moveBefore } from "prime-ui-kit";
|
|
|
29
30
|
Dnd.Root session owner: overlay (portal), live region, labels — mount once
|
|
30
31
|
├─ Dnd.Sortable list container and drop target; renders items in the drawn order
|
|
31
32
|
│ ├─ header optional content before the items that never moves (a group's heading row)
|
|
32
|
-
│ ├─ Dnd.SortableItem one item (li in ul/ol, div otherwise); the gap is drawn before it
|
|
33
|
+
│ ├─ Dnd.SortableItem one item (li in ul/ol, tr in tbody, th in tr, div otherwise); the gap is drawn before it
|
|
33
34
|
│ │ └─ Dnd.Handle optional grip, last in the item: a ghost icon-only Button (with `handle` on the list); a lock in a `locked` item; busy while the item's move is saved
|
|
34
35
|
│ ├─ empty the `empty` content while the list has no items
|
|
35
36
|
│ └─ children optional content after the items that never moves (an add tile)
|
|
36
37
|
├─ Dnd.Draggable an item to carry elsewhere; stays in place, faded, until the drop
|
|
37
38
|
└─ Dnd.DropZone a box that takes dropped items
|
|
38
39
|
```
|
|
39
|
-
A drag in flight is drawn by `Dnd.Root` as a clone of the lifted element above every layer, following the pointer. The clone flies inside empty copies
|
|
40
|
+
A drag in flight is drawn by `Dnd.Root` as a clone of the lifted element above every layer, following the pointer. The clone keeps the original's size and flies inside empty copies of all its ancestors up to the page — `display: contents`, except a size container (`container-type`), which flies as an invisible box of its measured content width, so its container queries resolve as at the source — and the host's variables, `[data-…]` selectors, surface and scoped theme (a List row's tier padding and radius, a narrow FieldList's stacked row) dress it exactly as the item in place, and it sets down without a jump; an item without a fill of its own flies on the fill of the surface it was lifted from (`--prime-color-layer-current`). A refusal with a reason shows the reason in the tooltip's chip under the clone. The page gets `data-dnd-active="mouse | touch | pen"` on `<html>` for the length of the gesture (the holding cursor), and every list of the carried item's kind gets `data-dnd-carrying`.
|
|
40
41
|
|
|
41
42
|
## API
|
|
42
43
|
|
|
@@ -67,19 +68,20 @@ Generic `Dnd.Sortable<T>`; `ref` → `HTMLElement`. The list and its drop target
|
|
|
67
68
|
| `kind` | `string` | unique per list | Lists sharing a `kind` exchange items (a board): an item from another list arrives in `onReorder` with an `id` not in `items`. Alt + the arrows across the axis move it by keyboard. Name each list: it is spoken. |
|
|
68
69
|
| `canDrop` | `(id: string) => boolean \| string` | — | Refuses an item by id: `false`, or a string that says why (spoken and shown under the carried copy). The list turns `danger` before the release. |
|
|
69
70
|
| `empty` | `ReactNode` | — | Shown while the list has no items, inside a dashed drop slot while the list takes drops; fades out when a gap opens in the list, so the gap takes its place. |
|
|
70
|
-
| `as` | `"div" \| "ul" \| "ol" \| "tbody"` | `"div"` | Root element; `ul` / `ol` render items as `li`, `tbody` as
|
|
71
|
-
| `header` | `ReactNode` | — | Content in front of the items that never moves and takes no drop position: the heading row of a group in a `tbody
|
|
71
|
+
| `as` | `"div" \| "ul" \| "ol" \| "tbody" \| "tr"` | `"div"` | Root element; `ul` / `ol` render items as `li`, `tbody` as rows `tr` (a gap row, a one-row copy), `tr` as head cells `th` with `axis="x"` (cells keep their columns, the gap glides among them). |
|
|
72
|
+
| `header` | `ReactNode` | — | Content in front of the items that never moves and takes no drop position: the heading row of a group in a `tbody`, the leading cells of a head row. |
|
|
72
73
|
| `aria-label` | `string` | — | Accessible name of the list. |
|
|
73
74
|
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "children">` | — | `className` (the list layout), `aria-label` (the name of the list) and the other attributes of the list element. |
|
|
74
75
|
|
|
75
76
|
### Dnd.SortableItem
|
|
76
|
-
`ref` → `HTMLElement`. `<li>` inside `ul` / `ol`, `<tr>` inside `tbody`, `<div>` otherwise; throws outside `Dnd.Sortable`. One item; presses on controls inside never drag. Without `handle` it takes the keys, and a named `<div>` item is a `button`.
|
|
77
|
+
`ref` → `HTMLElement`. `<li>` inside `ul` / `ol`, `<tr>` inside `tbody`, `<th>` inside `tr`, `<div>` otherwise; throws outside `Dnd.Sortable`. One item; presses on controls inside never drag. Without `handle` it takes the keys, and a named `<div>` item is a `button`.
|
|
77
78
|
|
|
78
79
|
| Prop | Type | Default | Description |
|
|
79
80
|
|---|---|---|---|
|
|
80
81
|
| `id` | `string` | — (required) | The item id (same as `getId`). |
|
|
81
82
|
| `disabled` | `boolean` | `false` | This item cannot be dragged or moved (others may still move past it). |
|
|
82
83
|
| `locked` | `boolean` | `false` | Holds its place: it does not move and nothing is put where it would shift it. Its `Dnd.Handle` turns into a lock. A pinned first row, a system column. |
|
|
84
|
+
| `dragFromControls` | `boolean` | `false` | Without `handle`: a press on a control inside picks the item up too once it travels (a finger: holds); a click stays the control's. A table header whose sort button fills it. |
|
|
83
85
|
| `className` | `string` | — | The row look: fill, radius, padding. |
|
|
84
86
|
| `children` | `ReactNode` | — | The row content. |
|
|
85
87
|
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "id">` | — | `aria-labelledby` / `aria-describedby` (the visible title and description) and the other attributes. |
|
|
@@ -148,6 +150,7 @@ Returns `items` with `id` moved in front of `beforeId` (`null` = last): the body
|
|
|
148
150
|
| `div` | a flex column / row / grid of `div` items | most lists | yes |
|
|
149
151
|
| `ul` · `ol` | the same with `li` items | a list that reads as one to assistive tech | |
|
|
150
152
|
| `tbody` | table rows (`tr` items) in the table's own layout; the gap is a row across every column; the row flies inside a one-row copy of its table at its column widths | rows of a table (`axis="y"`) | |
|
|
153
|
+
| `tr` | a head row whose cells (`th` items) are the columns: every cell keeps its column, so the body stays still while a header is carried; the carried cell stays in the layout as the gap — the same dashed, tinted slot as every sortable gap, drawn inside the cell — and it and the others glide by transforms to where they will stand; the copy is the head cell alone in a one-cell copy of the table; the body takes the new order on the drop | reorderable columns of a table (`axis="x"`); keep each column's width by id across the reorder so the head cells land where they were drawn (DataTable does) | |
|
|
151
154
|
|
|
152
155
|
### Flags
|
|
153
156
|
| Value | Looks like | Use when | Default |
|
|
@@ -155,6 +158,7 @@ Returns `items` with `id` moved in front of `beforeId` (`null` = last): the body
|
|
|
155
158
|
| `handle` | only the grip starts a drag; the row keeps its controls | rows with buttons, switches, text | `false` |
|
|
156
159
|
| `kind` shared | lists exchange items at an exact position; Alt + ← / → moves across | boards | unique per list |
|
|
157
160
|
| `locked` (item) | a lock where the grip would be; the item holds its place, nothing moves past it | a pinned first row, a system column | `false` |
|
|
161
|
+
| `dragFromControls` (item) | without `handle`: a press on a control inside picks the item up once it travels (a finger: holds); a click stays the control's, and the click that ends a drag is swallowed | a table header whose sort button fills it | `false` |
|
|
158
162
|
| `empty` | the list's own line inside a dashed drop slot while it is empty (no slot when the list is `disabled`); a gap opening takes its place | columns, lists that may run out | — |
|
|
159
163
|
| `canDrop` → string | the refusal says why: live region and a chip under the clone | workflow rules a person must understand | — |
|
|
160
164
|
| `flashOnDrop` | after a drop the zone's fill starts at `accent-soft` and fades back to its own | the eye must follow where the item went | `false` |
|
|
@@ -203,7 +207,7 @@ An item's own `onPointerDown` / `onKeyDown` run before the kit's: `preventDefaul
|
|
|
203
207
|
- A grid (`axis="grid"`) is `display: grid`: the host sets the tracks (`grid-template-columns`) and the cells' size; the gap takes a cell, and a carried item resizes to the cell it would land in.
|
|
204
208
|
- A `Dnd.Handle` ends its item: the last child, after the item's own trailing controls (a remove button), at the inline end (on the left in RTL); a `locked` item's lock takes the same place, so the grips of every row stand on one line. In a table it is the last cell. An on / off switch leads the item (its first child), so its state reads first and nothing stands between the text and the grip.
|
|
205
209
|
- The reason chip stands `--prime-tooltip-offset` under the carried copy, start-aligned, at most `--prime-tooltip-max-width` wide.
|
|
206
|
-
- The look of a row is the consumer's: give `Dnd.SortableItem` / `Dnd.Draggable` a radius on its own class (the gap and the clone's fill take it) and
|
|
210
|
+
- The look of a row is the consumer's: give `Dnd.SortableItem` / `Dnd.Draggable` a radius on its own class (the gap and the clone's fill take it) and the row's ancestors may dress it — the clone flies inside empty copies of all of them (a size container as a box of its width), so their variables, selectors and container queries still apply. A table row flies in a one-row copy of its table inside them.
|
|
207
211
|
- A scrollable ancestor under the pointer auto-scrolls within 64 px of its edge (a `ScrollContainer` strip works).
|
|
208
212
|
- Keep a zone's height stable between states; it only changes fill and outline.
|
|
209
213
|
|
|
@@ -258,6 +262,7 @@ An item's own `onPointerDown` / `onKeyDown` run before the kit's: `preventDefaul
|
|
|
258
262
|
| [board.tsx](examples/board.tsx) | A ticket board: columns share one kind, so a ticket lands in another column at an exact position or moves there with Alt + ← / →, an empty column says so, and a full column refuses it before the release with the reason under the ticket — `kind`, `canDrop`, `empty`, `onReorder`. |
|
|
259
263
|
| [drop-zones.tsx](examples/drop-zones.tsx) | Documents carried onto folders: a zone takes items by kind and gets the document as data, a locked folder refuses before the release and says why under the carried file, a syncing folder is no target at all, a document on signing stays put, the line above follows the drag, the target flashes where the file landed — `Dnd.Draggable`, `Dnd.DropZone`, `data`, `canDrop`, `disabled`, `onDragStart`, `onDragEnd`, `onEnter`, `onLeave`, `flashOnDrop`. |
|
|
260
264
|
| [table.tsx](examples/table.tsx) | A project estimate as a table: each stage is its own body of rows under a heading row that never moves, a row is carried by its grip within a stage or into another, and the rows stay table rows — `as`, `header`, `kind`, `handle`. |
|
|
265
|
+
| [table-columns.tsx](examples/table-columns.tsx) | A sales report whose columns change places: the head row is a sortable list, a header is carried alone while the rows stay still and the gap opens among the headers where it lands, and the month column holds its place — `as`, `axis`, `locked`. |
|
|
261
266
|
| [states.tsx](examples/states.tsx) | A contract's approval route: a step already signed stays where it is while the rest reorder around it, and once the route is approved the whole list holds still — `Dnd.SortableItem`, `Dnd.Sortable`, `disabled`. |
|
|
262
267
|
| [narrow.tsx](examples/narrow.tsx) | A row of status filters in a 320 px strip: tags reorder sideways and the strip scrolls when a tag is held near its edge — `axis`. |
|
|
263
268
|
|
|
@@ -266,7 +271,7 @@ An item's own `onPointerDown` / `onKeyDown` run before the kit's: `preventDefaul
|
|
|
266
271
|
- A `Dnd.SortableItem` `id` that differs from `getId(item)` → the item never moves.
|
|
267
272
|
- Rendering items yourself instead of through `renderItem` → the list cannot draw the gap or the optimistic order.
|
|
268
273
|
- Using `index` as the id → ids must survive a reorder.
|
|
269
|
-
- Styling a row from
|
|
274
|
+
- Styling a row from its siblings (`li + li`, `:first-child`) → the flying clone stands alone and loses it; keep the row's look on the row and its ancestors.
|
|
270
275
|
- Starting a drag from a button inside a row without `handle` → presses on controls are theirs; use `Dnd.Handle`.
|
|
271
276
|
- A drag-only move with no alternative → add a button or a menu for the same action.
|
|
272
277
|
- `aria-label` on an item that shows a title and a description → it hides them; point `aria-labelledby` / `aria-describedby` at them.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** A sales report whose columns change places: the head row is a sortable list, a header is carried alone while the rows stay still and the gap opens among the headers where it lands, and the month column holds its place — `as`, `axis`, `locked`. */
|
|
2
|
+
import { Dnd, moveBefore, Typography } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import styles from "./examples.module.css";
|
|
6
|
+
|
|
7
|
+
type Month = { id: string; month: string; deals: number; revenue: string; average: string };
|
|
8
|
+
type Column = { id: keyof Month; title: string; numeric: boolean; locked: boolean };
|
|
9
|
+
|
|
10
|
+
const COLUMNS: Column[] = [
|
|
11
|
+
{ id: "month", title: "Месяц", numeric: false, locked: true },
|
|
12
|
+
{ id: "deals", title: "Сделки", numeric: true, locked: false },
|
|
13
|
+
{ id: "revenue", title: "Выручка", numeric: true, locked: false },
|
|
14
|
+
{ id: "average", title: "Средний чек", numeric: true, locked: false },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const MONTHS: Month[] = [
|
|
18
|
+
{ id: "jul", month: "Июль", deals: 42, revenue: "3 150 000 ₽", average: "75 000 ₽" },
|
|
19
|
+
{ id: "aug", month: "Август", deals: 37, revenue: "2 960 000 ₽", average: "80 000 ₽" },
|
|
20
|
+
{ id: "sep", month: "Сентябрь", deals: 51, revenue: "4 080 000 ₽", average: "80 000 ₽" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export default function DndTableColumnsExample() {
|
|
24
|
+
const [columns, setColumns] = React.useState(COLUMNS);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Dnd.Root>
|
|
28
|
+
<table className={styles.table}>
|
|
29
|
+
<caption className={styles.caption}>
|
|
30
|
+
<Typography as="span" variant="title-s">
|
|
31
|
+
Продажи по месяцам
|
|
32
|
+
</Typography>
|
|
33
|
+
</caption>
|
|
34
|
+
<thead>
|
|
35
|
+
<Dnd.Sortable
|
|
36
|
+
as="tr"
|
|
37
|
+
axis="x"
|
|
38
|
+
aria-label="Колонки отчёта"
|
|
39
|
+
items={columns}
|
|
40
|
+
getId={(column) => column.id}
|
|
41
|
+
getLabel={(column) => column.title}
|
|
42
|
+
onReorder={(id, beforeId) =>
|
|
43
|
+
setColumns((current) => moveBefore(current, id, beforeId, (column) => column.id))
|
|
44
|
+
}
|
|
45
|
+
renderItem={(column) => (
|
|
46
|
+
<Dnd.SortableItem
|
|
47
|
+
id={column.id}
|
|
48
|
+
locked={column.locked}
|
|
49
|
+
scope="col"
|
|
50
|
+
className={column.numeric ? styles.numeric : styles.cell}
|
|
51
|
+
>
|
|
52
|
+
<Typography as="span" variant="caption" tone="muted">
|
|
53
|
+
{column.title}
|
|
54
|
+
</Typography>
|
|
55
|
+
</Dnd.SortableItem>
|
|
56
|
+
)}
|
|
57
|
+
/>
|
|
58
|
+
</thead>
|
|
59
|
+
<tbody>
|
|
60
|
+
{MONTHS.map((row) => (
|
|
61
|
+
<tr key={row.id}>
|
|
62
|
+
{columns.map((column) => (
|
|
63
|
+
<td key={column.id} className={column.numeric ? styles.numeric : styles.cell}>
|
|
64
|
+
<Typography as="span" variant="body-m">
|
|
65
|
+
{row[column.id]}
|
|
66
|
+
</Typography>
|
|
67
|
+
</td>
|
|
68
|
+
))}
|
|
69
|
+
</tr>
|
|
70
|
+
))}
|
|
71
|
+
</tbody>
|
|
72
|
+
</table>
|
|
73
|
+
</Dnd.Root>
|
|
74
|
+
);
|
|
75
|
+
}
|