prime-ui-kit 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunks/{chunk-OAXJWF3L.js → chunk-3PY52JP3.js} +1764 -1764
- package/dist/chunks/{chunk-X3JT5CLR.js → chunk-4KONMO7Z.js} +1 -1
- package/dist/chunks/{chunk-MB4CXSLL.js → chunk-W6EGTNG6.js} +3772 -2888
- package/dist/color-picker.js +2 -2
- package/dist/components/data-table/ColumnsMenu.d.ts +39 -17
- package/dist/components/data-table/DataTable.d.ts +41 -12
- package/dist/components/data-table/DataTableColumnsMenu.d.ts +45 -0
- package/dist/components/data-table/Head.d.ts +18 -5
- package/dist/components/data-table/Row.d.ts +10 -4
- package/dist/components/data-table/StateRows.d.ts +5 -3
- 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 +37 -15
- package/dist/components/gantt/Gantt.d.ts +1 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/kanban/Kanban.d.ts +4 -0
- package/dist/components/list/List.d.ts +9 -4
- 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/leaveGuard.d.ts +6 -0
- package/dist/internal/overlay/DialogGuard.d.ts +3 -2
- package/dist/internal/overlay/DialogParts.d.ts +14 -5
- package/dist/layout/index.d.ts +1 -1
- package/dist/layout/page/Page.d.ts +8 -14
- package/dist/skill/anti-slop.md +55 -1
- package/dist/skill/api-contract.md +2 -2
- package/dist/skill/cheatsheet.md +12 -4
- package/dist/skill/checklist.md +7 -0
- package/dist/skill/choosing.md +1 -0
- package/dist/skill/components.md +1 -1
- package/dist/skill/composition.md +196 -21
- package/dist/skill/layouts.md +1 -1
- package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
- package/dist/skill/reference/components/data-table/COMPONENT.md +110 -49
- package/dist/skill/reference/components/data-table/examples/actions-column.tsx +154 -0
- package/dist/skill/reference/components/data-table/examples/column-order.tsx +152 -0
- package/dist/skill/reference/components/data-table/examples/columns-visibility.tsx +18 -18
- 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 +25 -19
- package/dist/skill/reference/components/data-table/examples/reorder.tsx +24 -2
- package/dist/skill/reference/components/data-table/examples/row-link.tsx +18 -22
- package/dist/skill/reference/components/data-table/examples/screen.tsx +12 -1
- 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 +22 -35
- package/dist/skill/reference/components/datepicker/COMPONENT.md +2 -2
- package/dist/skill/reference/components/dnd/COMPONENT.md +13 -7
- package/dist/skill/reference/components/dnd/examples/examples.module.css +4 -3
- package/dist/skill/reference/components/dnd/examples/handle.tsx +5 -5
- package/dist/skill/reference/components/dnd/examples/locked.tsx +1 -1
- package/dist/skill/reference/components/dnd/examples/table-columns.tsx +75 -0
- package/dist/skill/reference/components/dnd/examples/table.tsx +6 -6
- package/dist/skill/reference/components/drawer/COMPONENT.md +26 -10
- package/dist/skill/reference/components/drawer/examples/header-actions.tsx +1 -1
- package/dist/skill/reference/components/drawer/examples/loading.tsx +58 -0
- package/dist/skill/reference/components/field-list/COMPONENT.md +78 -47
- package/dist/skill/reference/components/field-list/examples/base-row.tsx +2 -1
- package/dist/skill/reference/components/field-list/examples/locked.tsx +11 -11
- 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.module.css +1 -1
- package/dist/skill/reference/components/field-list/examples/screen.tsx +169 -162
- package/dist/skill/reference/components/field-list/examples/totals.tsx +9 -8
- 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/kanban/COMPONENT.md +6 -2
- package/dist/skill/reference/components/kanban/examples/states.tsx +7 -5
- package/dist/skill/reference/components/list/COMPONENT.md +21 -11
- package/dist/skill/reference/components/list/examples/reorder.tsx +42 -7
- package/dist/skill/reference/components/modal/COMPONENT.md +23 -10
- package/dist/skill/reference/components/scroll-container/COMPONENT.md +1 -1
- package/dist/skill/reference/components/segmented-control/COMPONENT.md +3 -2
- package/dist/skill/reference/components/sheet/COMPONENT.md +18 -7
- package/dist/skill/reference/components/smart-filter/COMPONENT.md +2 -2
- package/dist/skill/reference/layout/page/COMPONENT.md +13 -17
- package/dist/skill/reference/layout/page/examples/screen.module.css +1 -1
- package/dist/skill/reference/layout/page/examples/screen.tsx +93 -104
- package/dist/skill/reference/layout/page/examples/sections.tsx +2 -15
- package/dist/skill/responsive.md +14 -10
- package/dist/tokens/semantic.d.ts +2 -2
- package/dist/tokens/themes/dark.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/confirm-dialog/COMPONENT.md +1 -1
- package/src/components/data-table/COMPONENT.md +110 -49
- package/src/components/data-table/examples/actions-column.tsx +154 -0
- package/src/components/data-table/examples/column-order.tsx +152 -0
- package/src/components/data-table/examples/columns-visibility.tsx +18 -18
- 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 +25 -19
- package/src/components/data-table/examples/reorder.tsx +24 -2
- package/src/components/data-table/examples/row-link.tsx +18 -22
- package/src/components/data-table/examples/screen.tsx +12 -1
- 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 +22 -35
- package/src/components/datepicker/COMPONENT.md +2 -2
- package/src/components/dnd/COMPONENT.md +13 -7
- package/src/components/dnd/examples/examples.module.css +4 -3
- package/src/components/dnd/examples/handle.tsx +5 -5
- package/src/components/dnd/examples/locked.tsx +1 -1
- package/src/components/dnd/examples/table-columns.tsx +75 -0
- package/src/components/dnd/examples/table.tsx +6 -6
- package/src/components/drawer/COMPONENT.md +26 -10
- package/src/components/drawer/examples/header-actions.tsx +1 -1
- package/src/components/drawer/examples/loading.tsx +58 -0
- package/src/components/field-list/COMPONENT.md +78 -47
- package/src/components/field-list/examples/base-row.tsx +2 -1
- package/src/components/field-list/examples/locked.tsx +11 -11
- 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.module.css +1 -1
- package/src/components/field-list/examples/screen.tsx +169 -162
- package/src/components/field-list/examples/totals.tsx +9 -8
- package/src/components/field-list/examples/variants.tsx +51 -0
- package/src/components/gantt/COMPONENT.md +4 -4
- package/src/components/kanban/COMPONENT.md +6 -2
- package/src/components/kanban/examples/states.tsx +7 -5
- package/src/components/list/COMPONENT.md +21 -11
- package/src/components/list/examples/reorder.tsx +42 -7
- package/src/components/modal/COMPONENT.md +23 -10
- package/src/components/scroll-container/COMPONENT.md +1 -1
- package/src/components/segmented-control/COMPONENT.md +3 -2
- package/src/components/sheet/COMPONENT.md +18 -7
- package/src/components/smart-filter/COMPONENT.md +2 -2
- package/src/layout/page/COMPONENT.md +13 -17
- package/src/layout/page/examples/screen.module.css +1 -1
- package/src/layout/page/examples/screen.tsx +93 -104
- package/src/layout/page/examples/sections.tsx +2 -15
- package/src/styles/theme-dark.css +1 -1
- package/src/styles/theme-light.css +1 -1
- package/dist/components/data-table/context.d.ts +0 -14
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** A record card that opens before its data arrives: the title comes from the list row, the line under it and the body hold their place in skeletons, then the data fades in — `Drawer.Description`, `loading`. */
|
|
2
|
+
import { Button, Crossfade, DescriptionList, Drawer, Skeleton } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
const BIKE = {
|
|
6
|
+
title: "Велосипед №214",
|
|
7
|
+
summary: "Тур 2 · на линии с 6 октября",
|
|
8
|
+
facts: [
|
|
9
|
+
["Модель", "Tour 2, рама M"],
|
|
10
|
+
["Пробег", "1 840 км"],
|
|
11
|
+
["Последнее ТО", "28 сентября"],
|
|
12
|
+
["Арендатор", "Илья Морозов"],
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default function DrawerLoadingExample() {
|
|
17
|
+
const [loading, setLoading] = React.useState(true);
|
|
18
|
+
|
|
19
|
+
const openChange = (open: boolean) => {
|
|
20
|
+
if (!open) return;
|
|
21
|
+
setLoading(true);
|
|
22
|
+
window.setTimeout(() => setLoading(false), 1200);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Drawer.Root onOpenChange={openChange}>
|
|
27
|
+
<Drawer.Trigger>
|
|
28
|
+
<Button.Root variant="soft" tone="neutral">
|
|
29
|
+
Открыть велосипед
|
|
30
|
+
</Button.Root>
|
|
31
|
+
</Drawer.Trigger>
|
|
32
|
+
<Drawer.Content>
|
|
33
|
+
<Drawer.Header>
|
|
34
|
+
<Drawer.Title>{BIKE.title}</Drawer.Title>
|
|
35
|
+
<Drawer.Description loading={loading}>{BIKE.summary}</Drawer.Description>
|
|
36
|
+
</Drawer.Header>
|
|
37
|
+
<Drawer.Body>
|
|
38
|
+
<Crossfade state={loading ? "loading" : "data"} aria-busy={loading}>
|
|
39
|
+
<DescriptionList.Root>
|
|
40
|
+
{BIKE.facts.map(([term, value]) => (
|
|
41
|
+
<DescriptionList.Item key={term} label={term}>
|
|
42
|
+
{loading ? <Skeleton /> : value}
|
|
43
|
+
</DescriptionList.Item>
|
|
44
|
+
))}
|
|
45
|
+
</DescriptionList.Root>
|
|
46
|
+
</Crossfade>
|
|
47
|
+
</Drawer.Body>
|
|
48
|
+
<Drawer.Footer>
|
|
49
|
+
<Drawer.Close>
|
|
50
|
+
<Button.Root variant="outline" tone="neutral">
|
|
51
|
+
Закрыть
|
|
52
|
+
</Button.Root>
|
|
53
|
+
</Drawer.Close>
|
|
54
|
+
</Drawer.Footer>
|
|
55
|
+
</Drawer.Content>
|
|
56
|
+
</Drawer.Root>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
**Category:** inputs
|
|
4
4
|
**Kind:** composite
|
|
5
5
|
|
|
6
|
-
> An editable list of records of one shape — invoice lines, specs, contacts, prices by period: one row per record, the column headings once above the rows,
|
|
6
|
+
> An editable list of records of one shape — invoice lines, specs, contacts, prices by period: one row per record on one grid, the column headings once above the rows, the row's actions at its end and an add button under them.
|
|
7
7
|
|
|
8
8
|
## When to use
|
|
9
9
|
- A form part where a person adds, edits and removes several records of the same fields: invoice lines, contacts of a client, specs of a plan, recipients.
|
|
10
10
|
- A base value with overrides (a price and its changes by month): the base is the first row, pinned with `getLocked`, each override is a row.
|
|
11
|
-
- A list of short texts in the order a person sets (selling points, checklist items): one column, `reorderable
|
|
11
|
+
- A list of short texts in the order a person sets (selling points, checklist items): one column, `reorderable`; each row ends with its remove button and its handle.
|
|
12
|
+
- Dense rows (three or more columns), a touch-heavy screen, or rows that need more than remove and move → `rowActions="menu"`: one «⋯» per row.
|
|
12
13
|
- Small attributes of a record beside its text (an icon, a color): `width="compact"` columns with an icon-only `Select` (`Select.TriggerIcon` and no `Select.Value`) and a `ColorPresets` swatch.
|
|
13
14
|
|
|
14
15
|
## When not to use
|
|
@@ -26,36 +27,39 @@ import { FieldList, type FieldListColumn } from "prime-ui-kit";
|
|
|
26
27
|
## Anatomy
|
|
27
28
|
```
|
|
28
29
|
FieldList field frame: label · the list · hint / error
|
|
29
|
-
├─
|
|
30
|
-
├─
|
|
31
|
-
│
|
|
32
|
-
│
|
|
33
|
-
|
|
30
|
+
├─ sheet the rows on one grid (`variant="surface"`: a fill one step up the ladder)
|
|
31
|
+
│ ├─ heading row the column headings, once (aria-hidden; each field carries its heading as its label)
|
|
32
|
+
│ ├─ <ol> the rows (Dnd.Sortable with `reorderable` and the buttons)
|
|
33
|
+
│ │ └─ <li> row [a cell per column] [end: × | ⋯] [grip: handle | lock]
|
|
34
|
+
│ │ └─ cell one kit field without `label`, named by its column heading
|
|
35
|
+
│ └─ totals row `renderTotals`: a group «Итого», a total per column on the same tracks
|
|
34
36
|
└─ add row Button «addLabel» (+ `labels.maxReached` at `max`)
|
|
35
37
|
```
|
|
36
|
-
A leaf: rows come from `value` through `renderItem`, which returns one element per column (a fragment). Every framed kit field (Input, Select, Datepicker, NativeSelect, ColorField…) in a cell takes its column heading as its label: read by screen readers, not shown while the heading row is on screen, shown above the field once the rows stack in a narrow container. `ColorPresets` takes it as the name of its trigger.
|
|
38
|
+
A leaf: rows come from `value` through `renderItem`, which returns one element per column (a fragment). Every framed kit field (Input, Select, Datepicker, NativeSelect, ColorField…) in a cell takes its column heading as its label: read by screen readers, not shown while the heading row is on screen, shown above the field once the rows stack in a narrow container. `ColorPresets` takes it as the name of its trigger. Every row, a one-column row too, ends with the same slots: the remove «×» (or the «⋯» menu) and, with `reorderable` buttons, the grip after it — never a segment inside a field, where a «×» would read as «clear the text».
|
|
37
39
|
|
|
38
40
|
## API
|
|
39
41
|
|
|
40
42
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
41
43
|
|
|
42
44
|
### FieldList
|
|
43
|
-
Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editable list of records of one shape: column headings once, a row of fields per record (`<ol>`),
|
|
45
|
+
Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editable list of records of one shape: column headings once, a row of fields per record (`<ol>`) on one grid, the row's actions at its end, add under the rows; rows stack in a container under 30rem.
|
|
44
46
|
|
|
45
47
|
| Prop | Type | Default | Description |
|
|
46
48
|
|---|---|---|---|
|
|
47
49
|
| `columns` | `readonly FieldListColumn[]` | — (required) | The columns in cell order: `label` (the heading; it names the field of the column in every row), `width` (`fill` \| `compact` \| `xs` \| `s` \| `m`), `required` (a `*` after the heading). |
|
|
48
50
|
| `renderItem` | `(item: T, row: FieldListRow<T>) => ReactNode` | — (required) | The cells of a row, one element per column (a fragment): kit fields without `label`. `row` has `index`, `update(patch)` and `remove()`. |
|
|
49
|
-
| `renderTotals` | `(items: readonly T[]) => ReactNode` | — | The totals row under the rows: one node per column (a fragment), `null` where
|
|
51
|
+
| `renderTotals` | `(items: readonly T[]) => ReactNode` | — | The totals row under the rows: one node per column (a fragment), `null` where none; each aligned as its column's field text. The first cell without a total reads `labels.totals`. |
|
|
50
52
|
| `getId` | `(item: T) => string` | — (required) | Stable unique id of an item: the row key and the drag id. |
|
|
51
53
|
| `value` | `readonly T[]` | — | The rows (controlled). |
|
|
52
54
|
| `defaultValue` | `readonly T[]` | `[]` | The first rows (uncontrolled). |
|
|
53
55
|
| `onValueChange` | `(value: T[]) => void \| Promise<void>` | — | The new list after an edit (`row.update`), an add, a remove or a move. Return the save's promise: the new list shows while it is pending, the owner's again on a reject (say why with `error`). |
|
|
54
56
|
| `createItem` | `() => T` | — | A new empty item; with it the add button shows under the rows and focus goes to the new row's first field. |
|
|
55
57
|
| `addLabel` | `string` | — | Text of the add button: what is added («Добавить позицию»). |
|
|
56
|
-
| `getLabel` | `(item: T) => string` | — | The row's own text (its title field) for the remove button, the handle and
|
|
57
|
-
| `getLocked` | `(item: T) => boolean` | — | Pinned rows:
|
|
58
|
-
| `reorderable` | `boolean` | `false` | Rows move by a
|
|
58
|
+
| `getLabel` | `(item: T) => string` | — | The row's own text (its title field) for the remove button, the handle, the menu button and move announcements; else the row number. |
|
|
59
|
+
| `getLocked` | `(item: T) => boolean` | — | Pinned rows: the remove stays in its place, off, and says why (`labels.lockedReason`); with `reorderable` a lock takes the handle's place and the row holds it. In the menu every item is off. |
|
|
60
|
+
| `reorderable` | `boolean` | `false` | Rows move: with `rowActions="buttons"` by a handle that ends the row, after the remove button (drag, a touch hold, Alt + ↑ / ↓); with `"menu"` by its items «Выше» and «Ниже» and Alt + ↑ / ↓ on the menu button. |
|
|
61
|
+
| `rowActions` | `"buttons" \| "menu"` | `"buttons"` | How a row offers its actions: `buttons` — a remove button «×» and, with `reorderable`, the handle after it; `menu` — one «⋯» button whose menu moves the row up or down and removes it. |
|
|
62
|
+
| `variant` | `"plain" \| "surface"` | `"plain"` | Look of the list: `plain` — the rows on the host surface; `surface` — the rows on a fill of their own, one step up the surface ladder, with side padding. |
|
|
59
63
|
| `min` | `number` | `0` | The fewest rows: at this count the remove buttons are disabled. |
|
|
60
64
|
| `max` | `number` | — | The most rows: at this count the add button is disabled with `labels.maxReached` beside it. |
|
|
61
65
|
| `label` | `ReactNode` | — | Heading of the list; names the `<ol>`. Omit it inside a `Fieldset` whose legend names the list. |
|
|
@@ -63,18 +67,30 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
63
67
|
| `hint` | `ReactNode` | — | Help text under the list; hidden while `error` shows. |
|
|
64
68
|
| `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
|
|
65
69
|
| `error` | `ReactNode` | — | A problem of the list as a whole («Добавьте хотя бы одну позицию»); a field's own error goes on that field. |
|
|
66
|
-
| `disabled` | `boolean` | `false` | Disables every field, handle and button; a disabled `Fieldset` around it sets it too. |
|
|
70
|
+
| `disabled` | `boolean` | `false` | Disables every field, handle, button and menu button; a disabled `Fieldset` around it sets it too. |
|
|
67
71
|
| `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | host tier, else `"m"` | Tier of the headings, buttons and the kit fields that read the host tier. Without it the host tier, else `m`. |
|
|
68
72
|
| `labels` | `Partial<FieldListLabels>` | — | Built-in strings, see Labels. |
|
|
69
73
|
| `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "id" \| "children" \| "defaultValue" \| "defaultChecked" \| "onChange">` | — | `className`, `data-*` and the other attributes of the field frame `<div>` (field-root rule: `className`, `ref` and the rest → frame, `id` → control). |
|
|
70
74
|
|
|
71
75
|
## Variants
|
|
72
76
|
|
|
77
|
+
### variant
|
|
78
|
+
| Value | Looks like | Use when | Default |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| `plain` | the rows on the host surface, no side padding, faint lines between rows | the list is the content of its card or section | yes |
|
|
81
|
+
| `surface` | the rows on a fill one step up the surface ladder, radius `--prime-radius-l`, 12 of side padding and 12 of air under the last row | the list stands among other fields of a form or card and must read as one block | |
|
|
82
|
+
|
|
83
|
+
### rowActions
|
|
84
|
+
| Value | Looks like | Use when | Default |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `buttons` | a ghost «×» at the row's end and, with `reorderable`, the grip after it; a pinned row: «×» off, then the lock | short lists edited often, a desktop screen, moving rows is a main task | yes |
|
|
87
|
+
| `menu` | one ghost «⋯» per row; its Dropdown holds «Выше», «Ниже» (with `reorderable`), a separator and «Удалить» (danger) | dense rows (three or more columns), a touch-heavy screen, a row that needs more than remove and move | |
|
|
88
|
+
|
|
73
89
|
### width (column)
|
|
74
90
|
| Value | Looks like | Use when | Default |
|
|
75
91
|
|---|---|---|---|
|
|
76
92
|
| `fill` | shares the free width with the other `fill` columns | names, descriptions, any text | yes |
|
|
77
|
-
| `compact` |
|
|
93
|
+
| `compact` | as wide as the widest of its controls, heading and total — the gap after it is the column gap like every other | an icon-only `Select`, a `ColorPresets` swatch, a checkbox | |
|
|
78
94
|
| `xs` | 96 px | a number of a few digits: a share, a percent, a count | |
|
|
79
95
|
| `s` | 144 px | a quantity, a code, a number of days | |
|
|
80
96
|
| `m` | 192 px | an amount with its unit, a date, a phone | |
|
|
@@ -82,38 +98,41 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
82
98
|
### Flags
|
|
83
99
|
| Value | Looks like | Use when | Default |
|
|
84
100
|
|---|---|---|---|
|
|
85
|
-
| `reorderable` | a grip
|
|
86
|
-
| `getLocked` | a lock where the grip is
|
|
101
|
+
| `reorderable` | buttons: a grip at the very end of every row, after the remove button, Alt + ↑ / ↓ on it; menu: «Выше» and «Ниже» in the menu, Alt + ↑ / ↓ on «⋯» | the order of the records matters (a pricing page, a checklist, payment stages) | `false` |
|
|
102
|
+
| `getLocked` | the remove button in its place, off, with a tooltip saying why (`labels.lockedReason`); with `reorderable` a lock where the grip is and the row holds its place; in the menu every item off, «Удалить» says why | a base value, system records a person cannot drop | — |
|
|
87
103
|
| `createItem` | the add button under the rows, aligned with the first column | the person grows the list | — |
|
|
88
104
|
| `min` · `max` | remove buttons off at `min`; the add button off with «Не больше {max}» at `max` | the record count is bounded | `0` · — |
|
|
89
|
-
| `renderTotals` | a row of totals under the rows over a faint line, each on its column; «Итого» in the first cell without a total | the rows add up (shares, amounts, hours) | — |
|
|
105
|
+
| `renderTotals` | a row of totals under the rows over a faint line, each on its column, aligned as the column's field text; «Итого» in the first cell without a total | the rows add up (shares, amounts, hours) | — |
|
|
90
106
|
|
|
91
107
|
**Combinations**
|
|
92
108
|
- Recommended: `label` on the list (or a `Fieldset` legend around it), one heading per column, a `getLabel` from the title field, an `addLabel` that says what is added.
|
|
93
109
|
- Allowed but rare: one column — the heading row is not drawn, the list label names the rows.
|
|
94
|
-
- Pointless: `getLocked` on every row with `createItem` — nothing can be removed or moved.
|
|
110
|
+
- Pointless: `getLocked` on every row with `createItem` — nothing can be removed or moved; `variant="surface"` on a list that is alone in its card — a fill inside a fill.
|
|
95
111
|
- Forbidden: a `label` on every field of a row, a numbered label («Пункт 1», «Пункт 2»), a unit in a heading when the field shows it.
|
|
96
112
|
|
|
97
113
|
## States
|
|
98
114
|
| State | Driven by | DOM |
|
|
99
115
|
|---|---|---|
|
|
100
116
|
| empty | `value` with no items | no heading row; only the add button (with `createItem`) |
|
|
101
|
-
| at min | `value.length <= min` | remove buttons `disabled` |
|
|
117
|
+
| at min | `value.length <= min` | remove buttons (or «Удалить» items) `disabled` |
|
|
102
118
|
| at max | `value.length >= max` | add button `disabled`, `labels.maxReached` beside it |
|
|
103
|
-
| pinned row | `getLocked(item)` | `data-locked="true"` on the `<li>`; a
|
|
119
|
+
| pinned row | `getLocked(item)` | `data-locked="true"` on the `<li>`; the remove button stays in its slot, `disabled` behind a focusable Tooltip host, its tooltip `labels.lockedReason`; with `reorderable` Dnd's lock in the grip box; in the menu every item `disabled`, «Удалить» described by `labels.lockedReason` |
|
|
120
|
+
| hover | the pointer over a row of a `surface` list (`(hover: hover)` only; a `plain` row has no fill of its own) | the whole row takes the field fill (`--prime-color-fill-muted`) and its fields the surface fill (`--prime-color-layer-current`) |
|
|
104
121
|
| invalid | `error` (the list), a field's own `error` | the list's message under the add row; a field's under the field in its row |
|
|
105
|
-
| disabled | `disabled` (or a disabled Fieldset) | `data-disabled="true"` on the frame; fields, handles and buttons disabled through the kit's disabled context |
|
|
106
|
-
| stacked | container narrower than 30rem | heading row hidden,
|
|
122
|
+
| disabled | `disabled` (or a disabled Fieldset) | `data-disabled="true"` on the frame; fields, handles, buttons and menu buttons disabled through the kit's disabled context |
|
|
123
|
+
| stacked | container narrower than 30rem | heading row hidden; the compact columns, the first field and the actions on the first line, every other field on a line of its own with its label shown; `data-stack` on the body keeps the first field at least 160 wide: `actions` when it is too narrow beside the actions (they end the second line, after the next field), `main` when it is too narrow even without them (it takes the full width under the compact columns and the actions); each total on its own line after its column name |
|
|
107
124
|
| saving | a promise from `onValueChange` | the rows show the new list, `aria-busy="true"` on the `<ol>`; a reject brings the owner's list back |
|
|
108
|
-
| rows come and go | the add button, a remove
|
|
125
|
+
| rows come and go | the add button, a remove, a menu move, a new `value` | a row the data adds rises in once (`enterMotion`), a removed one closes up in place and the rows below glide up (`listPresence`); a row the menu moves glides to its place (`useFlipList`); the heading row rises in with the first row and closes up with the last (`partPresence`); the first render stays still |
|
|
109
126
|
|
|
110
127
|
## Layout & spacing
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
- The
|
|
116
|
-
-
|
|
128
|
+
- One grid for the list: every row, the heading row and the totals row share one track list `[columns] [end] [grip]`, so the columns line up like a table's; the end fits the row's «×» or «⋯» of the tier, the grip (with `reorderable` buttons) is one `xs` control wide (the handle or the lock) and ends the row on the inline-end side (on the left in RTL). Every row fills the same slots: a pinned row shows its remove off, never a hole.
|
|
129
|
+
- Rows stand edge to edge, 8 of padding above and below their fields; a faint line between two rows (`hairline`), inset by the side padding, none above the first row. Columns 12 apart (`--prime-space-3`).
|
|
130
|
+
- `plain`: side padding 0. `surface`: the sheet's fill (`--prime-color-layer-current` at its `data-depth`), radius `--prime-radius-l`, 12 of side padding on every row and 12 above the headings and under the last row.
|
|
131
|
+
- A `compact` column is measured: its track is the widest of its controls, its heading and its total, so the gaps between columns are all 12.
|
|
132
|
+
- The remove button, the menu button, the handle and the lock stand on the middle of the first line of fields, so a field's error under it grows the row downwards only.
|
|
133
|
+
- The totals row stands under the last row over a faint line; a total's text sits where its column's field text does: at the start, or at the end for a field that ends its number there (an `Input` with `inputMode="decimal"` and an end `Input.InlineAffix`). Stacked, every total ends its line.
|
|
134
|
+
- The add button lines up with the sheet's start, 12 under it.
|
|
135
|
+
- Under 30rem of its own width (a container query): rows stack — the first line holds the compact columns, the first field and the actions; the other fields take the full width under it, 16 apart; rows 12 of padding above and below. The first field keeps at least 160 (measured, so a name such as «Вход через SSO» stays whole at 320 and 360): too narrow beside the actions, they end the second line after the next field; too narrow even without them (a narrow card), the field takes the full width under the compact columns and the actions.
|
|
117
136
|
|
|
118
137
|
## Accessibility
|
|
119
138
|
|
|
@@ -122,9 +141,9 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
122
141
|
|
|
123
142
|
| Key | Action |
|
|
124
143
|
|---|---|
|
|
125
|
-
| `Tab` | Moves through the fields of a row, then to its remove button and on. |
|
|
126
|
-
| `Enter` · `Space` | On the add button adds a row and focuses its first field; on a remove button removes the row and focuses the next row's remove button. |
|
|
127
|
-
| `Alt + ↑` · `Alt + ↓` | With `reorderable`, moves the row up or down (from its handle). |
|
|
144
|
+
| `Tab` | Moves through the fields of a row, then to its remove button and its handle (or its menu button) and on; a pinned row's remove takes focus to show why it is off. |
|
|
145
|
+
| `Enter` · `Space` | On the add button adds a row and focuses its first field; on a remove button removes the row and focuses the next row's remove button; on a menu button opens the row's menu. |
|
|
146
|
+
| `Alt + ↑` · `Alt + ↓` | With `reorderable`, moves the row up or down (from its handle, or from its menu button with `rowActions="menu"`); focus stays on it. |
|
|
128
147
|
|
|
129
148
|
### ARIA
|
|
130
149
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
@@ -133,20 +152,26 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
133
152
|
- The heading row is `aria-hidden`: each field in a cell carries its column heading as its own label, read but not shown on wide rows.
|
|
134
153
|
- The `<ol>` is described by the list's `hint` and `error` (`aria-describedby`), which speak for every row.
|
|
135
154
|
- In a narrow container the heading row goes and each field's label shows above it.
|
|
155
|
+
- With `rowActions="menu"` the row's «⋯» button opens a `role="menu"` (Dropdown) named «Действия: {row}»; it lists Alt + ↑ / ↓ in `aria-keyshortcuts`, and a move says the row's new place in a polite live region.
|
|
136
156
|
- The totals row (`renderTotals`) is a `group` named by `labels.totals`; each total is read after its column heading.
|
|
137
|
-
- The remove button is
|
|
138
|
-
- A pinned row
|
|
157
|
+
- The remove button is «Удалить: {row}», the handle «Переместить: {row}»: the row's name from `getLabel`, else its number.
|
|
158
|
+
- A pinned row keeps its remove button: `aria-disabled`, described by `labels.lockedReason` (a tooltip on hover and focus); with `reorderable` Dnd's lock stands in place of the handle. In the menu its items are `disabled` and the remove item is described by `labels.lockedReason`.
|
|
139
159
|
|
|
140
160
|
### Labels
|
|
141
161
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
142
162
|
|
|
143
163
|
| Key | Default | Used for |
|
|
144
164
|
|---|---|---|
|
|
145
|
-
| `remove` | `"
|
|
146
|
-
| `reorder` | `"
|
|
147
|
-
| `
|
|
165
|
+
| `remove` | `"Удалить: {row}"` | `aria-label` of a row's remove button; `{row}` is the row's name. |
|
|
166
|
+
| `reorder` | `"Переместить: {row}"` | `aria-label` of a row's handle (`reorderable`); `{row}` is the row's name. |
|
|
167
|
+
| `actions` | `"Действия: {row}"` | `aria-label` of a row's menu button (`rowActions="menu"`); `{row}` is the row's name. |
|
|
168
|
+
| `moveUp` | `"Выше"` | Menu item that moves the row one place up. |
|
|
169
|
+
| `moveDown` | `"Ниже"` | Menu item that moves the row one place down. |
|
|
170
|
+
| `removeItem` | `"Удалить"` | Menu item that removes the row. |
|
|
171
|
+
| `moved` | `"{row}: позиция {position} из {total}"` | Announced after a move from the menu or its keys; `{row}` is the row's name, `{position}` its new place, `{total}` the row count. |
|
|
172
|
+
| `row` | `"строка {index}"` | Name of a row without `getLabel` text; `{index}` counts from 1. |
|
|
148
173
|
| `namedRow` | `"«{label}»"` | Name of a row with `getLabel` text; `{label}` is that text. |
|
|
149
|
-
| `
|
|
174
|
+
| `lockedReason` | `"Закреплённую строку нельзя удалить"` | Why a pinned row (`getLocked`) cannot be removed: the tooltip of its remove button, the description of its menu's remove item. |
|
|
150
175
|
| `maxReached` | `"Не больше {max}"` | Text beside the disabled add button once the list holds `max` rows. |
|
|
151
176
|
| `totals` | `"Итого"` | Name of the totals row (`renderTotals`), shown in its first cell when that has no total. |
|
|
152
177
|
|
|
@@ -154,18 +179,20 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
154
179
|
| Example | Shows |
|
|
155
180
|
|---|---|
|
|
156
181
|
| [overview.tsx](examples/overview.tsx) | Lines of an invoice: the column headings stand once above the rows, each row has a remove button and the add button sits under them — `columns`, `renderItem`, `createItem`, `addLabel`. |
|
|
182
|
+
| [variants.tsx](examples/variants.tsx) | The two looks of the same list: the rows on the host surface, or on a fill of their own with side padding — `variant`. |
|
|
157
183
|
| [sizes.tsx](examples/sizes.tsx) | Every tier: the headings, the fields, the handle and the buttons follow the list's size — `size`. |
|
|
158
|
-
| [reorder.tsx](examples/reorder.tsx) | Selling points of a plan in the order of the pricing page: a one-column list has no headings and no numbers, a point moves by
|
|
159
|
-
| [
|
|
160
|
-
| [
|
|
184
|
+
| [reorder.tsx](examples/reorder.tsx) | Selling points of a plan in the order of the pricing page: a one-column list has no headings and no numbers, each row ends with its remove button and then its handle, a point moves by the handle or Alt+arrows — `reorderable`, `getLabel`. |
|
|
185
|
+
| [row-menu.tsx](examples/row-menu.tsx) | Payment stages of a project in dense rows: one more-button per row holds move up, move down and remove, the moves are off at the ends and next to the pinned advance, and Alt+arrows on the button move the row too — `rowActions`, `reorderable`, `getLocked`. |
|
|
186
|
+
| [locked.tsx](examples/locked.tsx) | Specs of a plan on one line each: an icon picker and its color side by side in compact columns, the first three specs are pinned with a lock in the handle's place and a remove button that is off and says why, the rest reorder below them — `getLocked`, `width`, `reorderable`. |
|
|
187
|
+
| [base-row.tsx](examples/base-row.tsx) | A plan price and its changes by month in one table: the base price is the first, pinned row whose remove button is off and says why, each change is a row with its month and its price, and the unit sits in the field, not in a heading — `getLocked`, `labels`, `width`. |
|
|
161
188
|
| [limits.tsx](examples/limits.tsx) | Recipients of invoice reminders, from one to three: the last row cannot be removed and the add button says why it is off once the list is full — `min`, `max`, `hint`. |
|
|
162
189
|
| [errors.tsx](examples/errors.tsx) | Contacts of a client checked on save: a field's problem shows under that field in its row, a problem of the whole list under the list, and focus goes to the first wrong field; a `*` after a heading marks a required column — `error`, `required`. |
|
|
163
|
-
| [totals.tsx](examples/totals.tsx) | A deal bonus split between the team: the share takes a narrow column, and the totals row under the lines sums the shares and the amounts — `renderTotals`, `width`. |
|
|
190
|
+
| [totals.tsx](examples/totals.tsx) | A deal bonus split between the team: the share takes a narrow column, and the totals row under the lines sums the shares and the amounts, each ending where its fields end their numbers — `renderTotals`, `width`. |
|
|
164
191
|
| [saving.tsx](examples/saving.tsx) | Removing a recipient is saved on the server: the row leaves at once and comes back with the reason if the server refuses — `onValueChange`, `error`. |
|
|
165
192
|
| [states.tsx](examples/states.tsx) | The same list editable and disabled while the request saves it: fields, handles and buttons turn off together — `disabled`. |
|
|
166
193
|
| [controlled.tsx](examples/controlled.tsx) | The parent owns the lines: it sums them under the list and puts the saved lines back on reset — `value`, `onValueChange`. |
|
|
167
|
-
| [narrow.tsx](examples/narrow.tsx) |
|
|
168
|
-
| [screen.tsx](examples/screen.tsx) | A record editor as a document page, a long form whose title, description and save stand in the content header: a plan's name, its specs and prices
|
|
194
|
+
| [narrow.tsx](examples/narrow.tsx) | Specs of a plan in a 320 px column: the headings give way, the icon, its color and the name keep the first line, and the value takes the line under it with the remove button and the handle, each field with its own label — `columns`, `width`. |
|
|
195
|
+
| [screen.tsx](examples/screen.tsx) | A record editor as a document page, a long form whose title, description and save stand in the content header and whose blocks are titled cards one under another: a plan's name, its specs and prices; repeating records as FieldList rows with the headings once, an icon and its color side by side, a base price pinned above its changes, the price list on its own fill among the fields of its card, one value in three currencies in one row — `getLocked`, `compact`, `variant`. |
|
|
169
196
|
|
|
170
197
|
## Mistakes
|
|
171
198
|
- A `label` above every field of every row («Иконка», «Параметр», «Значение» four times) → a FieldList: the headings stand once, the fields take them as hidden labels.
|
|
@@ -173,11 +200,15 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
173
200
|
- One record split over two lines of half-width fields → one row; small attributes go to `compact` columns (icon-only `Select`, `ColorPresets`).
|
|
174
201
|
- A base value as a separate field above its overrides, in another geometry → the first row, pinned with `getLocked`.
|
|
175
202
|
- The unit in every heading («Цена, ₽ / день») and again in the field → the unit once, in `Input.InlineAffix`.
|
|
176
|
-
- A hint that explains what the lock already shows («Первые три всегда первыми») → no hint; the lock
|
|
203
|
+
- A hint that explains what the lock already shows («Первые три всегда первыми») → no hint; the lock and the remove button's reason say it.
|
|
204
|
+
- A pinned row with an empty place where the others have a remove button → none to fix by hand: FieldList draws it off with `labels.lockedReason`; say the domain reason there («Базовую цену нельзя удалить»).
|
|
205
|
+
- A remove «×» as an `Input.Action` inside a one-field row → none: a «×» in a field reads as «clear the text»; FieldList ends every row with its own remove.
|
|
206
|
+
- A «⋯» menu or a «−» drawn by hand next to the rows → `rowActions`; a Card drawn around the list to set it apart from the form → `variant="surface"`.
|
|
207
|
+
- `rowActions="menu"` on a two-column list a person reorders all day → the buttons: the handle and «×» in sight are one gesture, the menu is three.
|
|
177
208
|
- `aria-label` written on every field in a cell → none: the column heading names the field.
|
|
178
209
|
- A grip, a remove button and an add button drawn by hand around a `.map()` of fields → FieldList.
|
|
179
210
|
- A total under the list typed by hand in a line of text, or a DataTable of fields to get a totals row → `renderTotals`.
|
|
180
211
|
|
|
181
212
|
## Related
|
|
182
|
-
- **Built from:** [Button](../button/COMPONENT.md) (add, remove), [Dnd](../dnd/COMPONENT.md) (`reorderable`: `Dnd.Sortable`, `Dnd.Handle`; the lock of a pinned row), [Label](../label/COMPONENT.md) (headings), the field frame (label, [Hint](../hint/COMPONENT.md) for the hint and error), [VisuallyHidden](../visually-hidden/COMPONENT.md)
|
|
213
|
+
- **Built from:** [Button](../button/COMPONENT.md) (add, remove, the menu button), [Dnd](../dnd/COMPONENT.md) (`reorderable`: `Dnd.Sortable`, `Dnd.Handle`; the lock of a pinned row), [Dropdown](../dropdown/COMPONENT.md) (`rowActions="menu"`), [Label](../label/COMPONENT.md) (headings), [Tooltip](../tooltip/COMPONENT.md) (why a pinned row's remove is off), the field frame (label, [Hint](../hint/COMPONENT.md) for the hint and error), [VisuallyHidden](../visually-hidden/COMPONENT.md)
|
|
183
214
|
- **See also:** [Input](../input/COMPONENT.md), [Select](../select/COMPONENT.md) (icon-only trigger), [ColorPicker](../color-picker/COMPONENT.md) (`ColorPresets`), [Fieldset](../fieldset/COMPONENT.md), [DataTable](../data-table/COMPONENT.md)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A plan price and its changes by month in one table: the base price is the first, pinned row, each change is a row with its month and its price, and the unit sits in the field, not in a heading — `getLocked`, `width`. */
|
|
1
|
+
/** A plan price and its changes by month in one table: the base price is the first, pinned row whose remove button is off and says why, each change is a row with its month and its price, and the unit sits in the field, not in a heading — `getLocked`, `labels`, `width`. */
|
|
2
2
|
import { FieldList, type FieldListColumn, Input, Select } from "prime-ui-kit";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
@@ -30,6 +30,7 @@ export default function FieldListBaseRowExample() {
|
|
|
30
30
|
onValueChange={setPeriods}
|
|
31
31
|
getId={(period) => period.id}
|
|
32
32
|
getLocked={(period) => period.base ?? false}
|
|
33
|
+
labels={{ lockedReason: "Базовую цену нельзя удалить" }}
|
|
33
34
|
createItem={() => ({ id: crypto.randomUUID(), from: "", price: "" })}
|
|
34
35
|
addLabel="Добавить изменение цены"
|
|
35
36
|
renderItem={(period, row) => (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Specs of a plan on one line each: an icon picker and
|
|
1
|
+
/** Specs of a plan on one line each: an icon picker and its color side by side in compact columns, the first three specs are pinned with a lock in the handle's place and a remove button that is off and says why, the rest reorder below them — `getLocked`, `width`, `reorderable`. */
|
|
2
2
|
import {
|
|
3
3
|
ColorPresets,
|
|
4
4
|
FieldList,
|
|
@@ -21,9 +21,9 @@ type Spec = {
|
|
|
21
21
|
|
|
22
22
|
const COLUMNS: FieldListColumn[] = [
|
|
23
23
|
{ label: "Иконка", width: "compact" },
|
|
24
|
+
{ label: "Цвет", width: "compact" },
|
|
24
25
|
{ label: "Параметр" },
|
|
25
26
|
{ label: "Значение" },
|
|
26
|
-
{ label: "Цвет", width: "compact" },
|
|
27
27
|
];
|
|
28
28
|
|
|
29
29
|
const ICONS: { name: IconName; label: string }[] = [
|
|
@@ -107,6 +107,15 @@ export default function FieldListLockedExample() {
|
|
|
107
107
|
))}
|
|
108
108
|
</Select.Content>
|
|
109
109
|
</Select.Root>
|
|
110
|
+
<ColorPresets.Root
|
|
111
|
+
value={spec.color}
|
|
112
|
+
onValueChange={(color) => row.update({ color: color ?? spec.color })}
|
|
113
|
+
>
|
|
114
|
+
<ColorPresets.Trigger>
|
|
115
|
+
<ColorPresets.SwatchButton />
|
|
116
|
+
</ColorPresets.Trigger>
|
|
117
|
+
<ColorPresets.Content />
|
|
118
|
+
</ColorPresets.Root>
|
|
110
119
|
<Input.Root>
|
|
111
120
|
<Input.Wrapper>
|
|
112
121
|
<Input.Field
|
|
@@ -123,15 +132,6 @@ export default function FieldListLockedExample() {
|
|
|
123
132
|
/>
|
|
124
133
|
</Input.Wrapper>
|
|
125
134
|
</Input.Root>
|
|
126
|
-
<ColorPresets.Root
|
|
127
|
-
value={spec.color}
|
|
128
|
-
onValueChange={(color) => row.update({ color: color ?? spec.color })}
|
|
129
|
-
>
|
|
130
|
-
<ColorPresets.Trigger>
|
|
131
|
-
<ColorPresets.SwatchButton />
|
|
132
|
-
</ColorPresets.Trigger>
|
|
133
|
-
<ColorPresets.Content />
|
|
134
|
-
</ColorPresets.Root>
|
|
135
135
|
</>
|
|
136
136
|
)}
|
|
137
137
|
/>
|
|
@@ -1,63 +1,138 @@
|
|
|
1
|
-
/**
|
|
2
|
-
import {
|
|
1
|
+
/** Specs of a plan in a 320 px column: the headings give way, the icon, its color and the name keep the first line, and the value takes the line under it with the remove button and the handle, each field with its own label — `columns`, `width`. */
|
|
2
|
+
import {
|
|
3
|
+
ColorPresets,
|
|
4
|
+
FieldList,
|
|
5
|
+
type FieldListColumn,
|
|
6
|
+
Icon,
|
|
7
|
+
type IconName,
|
|
8
|
+
Input,
|
|
9
|
+
Select,
|
|
10
|
+
} from "prime-ui-kit";
|
|
3
11
|
import * as React from "react";
|
|
4
12
|
|
|
5
13
|
import styles from "./examples.module.css";
|
|
6
14
|
|
|
7
|
-
type
|
|
15
|
+
type Spec = {
|
|
16
|
+
id: string;
|
|
17
|
+
icon: IconName;
|
|
18
|
+
name: string;
|
|
19
|
+
value: string;
|
|
20
|
+
color: string;
|
|
21
|
+
pinned?: boolean;
|
|
22
|
+
};
|
|
8
23
|
|
|
9
24
|
const COLUMNS: FieldListColumn[] = [
|
|
10
|
-
{ label: "
|
|
11
|
-
{ label: "
|
|
12
|
-
{ label: "
|
|
25
|
+
{ label: "Иконка", width: "compact" },
|
|
26
|
+
{ label: "Цвет", width: "compact" },
|
|
27
|
+
{ label: "Параметр" },
|
|
28
|
+
{ label: "Значение" },
|
|
13
29
|
];
|
|
14
30
|
|
|
15
|
-
const
|
|
16
|
-
{
|
|
17
|
-
{
|
|
31
|
+
const ICONS: { name: IconName; label: string }[] = [
|
|
32
|
+
{ name: "object.users", label: "Пользователи" },
|
|
33
|
+
{ name: "object.storage", label: "Хранилище" },
|
|
34
|
+
{ name: "object.headset", label: "Поддержка" },
|
|
35
|
+
{ name: "object.chart", label: "Отчёты" },
|
|
36
|
+
{ name: "object.key", label: "Доступ" },
|
|
37
|
+
{ name: "object.globe", label: "Интеграции" },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const SPECS: Spec[] = [
|
|
41
|
+
{
|
|
42
|
+
id: "seats",
|
|
43
|
+
icon: "object.users",
|
|
44
|
+
name: "Сотрудники",
|
|
45
|
+
value: "до 50",
|
|
46
|
+
color: "#5068f5",
|
|
47
|
+
pinned: true,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "storage",
|
|
51
|
+
icon: "object.storage",
|
|
52
|
+
name: "Хранилище",
|
|
53
|
+
value: "200 ГБ",
|
|
54
|
+
color: "#22c55e",
|
|
55
|
+
pinned: true,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "support",
|
|
59
|
+
icon: "object.headset",
|
|
60
|
+
name: "Поддержка",
|
|
61
|
+
value: "8/5",
|
|
62
|
+
color: "#f97316",
|
|
63
|
+
pinned: true,
|
|
64
|
+
},
|
|
65
|
+
{ id: "reports", icon: "object.chart", name: "Отчёты", value: "12 шаблонов", color: "#a855f7" },
|
|
66
|
+
{ id: "sso", icon: "object.key", name: "Вход через SSO", value: "SAML, OIDC", color: "#14b8a6" },
|
|
18
67
|
];
|
|
19
68
|
|
|
20
69
|
export default function FieldListNarrowExample() {
|
|
21
|
-
const [
|
|
70
|
+
const [specs, setSpecs] = React.useState(SPECS);
|
|
22
71
|
|
|
23
72
|
return (
|
|
24
73
|
<div className={styles.narrow}>
|
|
25
74
|
<FieldList
|
|
26
|
-
label="
|
|
75
|
+
label="Характеристики тарифа"
|
|
27
76
|
columns={COLUMNS}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
77
|
+
reorderable
|
|
78
|
+
value={specs}
|
|
79
|
+
onValueChange={setSpecs}
|
|
80
|
+
getId={(spec) => spec.id}
|
|
81
|
+
getLabel={(spec) => spec.name}
|
|
82
|
+
getLocked={(spec) => spec.pinned ?? false}
|
|
83
|
+
createItem={(): Spec => ({
|
|
84
|
+
id: crypto.randomUUID(),
|
|
85
|
+
icon: "object.globe",
|
|
86
|
+
name: "",
|
|
87
|
+
value: "",
|
|
88
|
+
color: "#14b8a6",
|
|
89
|
+
})}
|
|
90
|
+
addLabel="Добавить характеристику"
|
|
91
|
+
renderItem={(spec, row) => (
|
|
35
92
|
<>
|
|
93
|
+
<Select.Root
|
|
94
|
+
value={spec.icon}
|
|
95
|
+
onValueChange={(icon) => row.update({ icon: icon as IconName })}
|
|
96
|
+
>
|
|
97
|
+
<Select.Trigger>
|
|
98
|
+
<Select.TriggerIcon>
|
|
99
|
+
<Icon name={spec.icon} />
|
|
100
|
+
</Select.TriggerIcon>
|
|
101
|
+
</Select.Trigger>
|
|
102
|
+
<Select.Content>
|
|
103
|
+
{ICONS.map((icon) => (
|
|
104
|
+
<Select.Item key={icon.name} value={icon.name}>
|
|
105
|
+
<Select.ItemIcon>
|
|
106
|
+
<Icon name={icon.name} />
|
|
107
|
+
</Select.ItemIcon>
|
|
108
|
+
{icon.label}
|
|
109
|
+
</Select.Item>
|
|
110
|
+
))}
|
|
111
|
+
</Select.Content>
|
|
112
|
+
</Select.Root>
|
|
113
|
+
<ColorPresets.Root
|
|
114
|
+
value={spec.color}
|
|
115
|
+
onValueChange={(color) => row.update({ color: color ?? spec.color })}
|
|
116
|
+
>
|
|
117
|
+
<ColorPresets.Trigger>
|
|
118
|
+
<ColorPresets.SwatchButton />
|
|
119
|
+
</ColorPresets.Trigger>
|
|
120
|
+
<ColorPresets.Content />
|
|
121
|
+
</ColorPresets.Root>
|
|
36
122
|
<Input.Root>
|
|
37
123
|
<Input.Wrapper>
|
|
38
124
|
<Input.Field
|
|
39
|
-
value={
|
|
40
|
-
onChange={(event) => row.update({
|
|
41
|
-
/>
|
|
42
|
-
</Input.Wrapper>
|
|
43
|
-
</Input.Root>
|
|
44
|
-
<Input.Root>
|
|
45
|
-
<Input.Wrapper>
|
|
46
|
-
<Input.Field
|
|
47
|
-
value={line.quantity}
|
|
48
|
-
inputMode="numeric"
|
|
49
|
-
onChange={(event) => row.update({ quantity: event.target.value })}
|
|
125
|
+
value={spec.name}
|
|
126
|
+
onChange={(event) => row.update({ name: event.target.value })}
|
|
50
127
|
/>
|
|
51
128
|
</Input.Wrapper>
|
|
52
129
|
</Input.Root>
|
|
53
130
|
<Input.Root>
|
|
54
131
|
<Input.Wrapper>
|
|
55
132
|
<Input.Field
|
|
56
|
-
value={
|
|
57
|
-
|
|
58
|
-
onChange={(event) => row.update({ price: event.target.value })}
|
|
133
|
+
value={spec.value}
|
|
134
|
+
onChange={(event) => row.update({ value: event.target.value })}
|
|
59
135
|
/>
|
|
60
|
-
<Input.InlineAffix side="end">₽</Input.InlineAffix>
|
|
61
136
|
</Input.Wrapper>
|
|
62
137
|
</Input.Root>
|
|
63
138
|
</>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Selling points of a plan in the order of the pricing page: a one-column list has no headings and no numbers, a point moves by
|
|
1
|
+
/** Selling points of a plan in the order of the pricing page: a one-column list has no headings and no numbers, each row ends with its remove button and then its handle, a point moves by the handle or Alt+arrows — `reorderable`, `getLabel`. */
|
|
2
2
|
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|