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
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
Crossfade,
|
|
20
20
|
DEFAULT_DATEPICKER_PRESETS,
|
|
21
21
|
DataTable,
|
|
22
|
+
DataTableColumnsMenu,
|
|
22
23
|
Datepicker,
|
|
23
24
|
DescriptionList,
|
|
24
25
|
DigitInput,
|
|
@@ -87,7 +88,7 @@ import {
|
|
|
87
88
|
useSidebar,
|
|
88
89
|
useTokenValue,
|
|
89
90
|
useUploadQueue
|
|
90
|
-
} from "./chunks/chunk-
|
|
91
|
+
} from "./chunks/chunk-4374EBWV.js";
|
|
91
92
|
import {
|
|
92
93
|
Avatar,
|
|
93
94
|
Badge,
|
|
@@ -122,7 +123,7 @@ import {
|
|
|
122
123
|
tierMappings,
|
|
123
124
|
tones,
|
|
124
125
|
variants
|
|
125
|
-
} from "./chunks/chunk-
|
|
126
|
+
} from "./chunks/chunk-3PY52JP3.js";
|
|
126
127
|
import {
|
|
127
128
|
ControlSizeProvider,
|
|
128
129
|
useOptionalControlSize
|
|
@@ -153,6 +154,7 @@ export {
|
|
|
153
154
|
Crossfade,
|
|
154
155
|
DEFAULT_DATEPICKER_PRESETS,
|
|
155
156
|
DataTable,
|
|
157
|
+
DataTableColumnsMenu,
|
|
156
158
|
Datepicker,
|
|
157
159
|
DescriptionList,
|
|
158
160
|
DigitInput,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type DivProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
3
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
4
|
+
};
|
|
5
|
+
type ToolGroupProps = DivProps;
|
|
6
|
+
/**
|
|
7
|
+
* A host's own items kept together in one cell that takes the free room (the Gantt's legend or
|
|
8
|
+
* title before its zoom): they wrap inside it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ToolGroup({ className, ...rest }: ToolGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
type ToolRowProps = DivProps & {
|
|
12
|
+
/** The row has a line of its own: it wraps and its cells fill it by kind. Off — one line, every cell at its natural width. */
|
|
13
|
+
fill?: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A row of tools as written, each in a cell of its kind: a search grows, switchers and pickers
|
|
17
|
+
* keep their content width beside it and fill a short line, buttons keep their width at the end.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ToolRow({ fill, className, children, ...rest }: ToolRowProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
type ToolChipsProps = DivProps;
|
|
21
|
+
/** The applied filters (`SmartFilter.Chips`) on a line of their own under a tool row; gone while empty. */
|
|
22
|
+
export declare function ToolChips({ className, ...rest }: ToolChipsProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export {};
|
|
@@ -17,12 +17,6 @@ export type FieldCell = {
|
|
|
17
17
|
hidden: "wide" | "always";
|
|
18
18
|
/** The column is required: the field in the cell is required too (`useFieldFrame`). */
|
|
19
19
|
required: boolean;
|
|
20
|
-
/**
|
|
21
|
-
* The row's action the field holds at its end: a one-column list puts each row's remove button
|
|
22
|
-
* inside its field. A text field (`Input.Wrapper`, `Textarea`) renders it as its last action
|
|
23
|
-
* segment; a list whose field holds none keeps the remove in a column of its own.
|
|
24
|
-
*/
|
|
25
|
-
action?: React.ReactNode;
|
|
26
20
|
};
|
|
27
21
|
export declare const FieldCellContext: React.Context<FieldCell | null>;
|
|
28
22
|
export declare function useFieldCell(): FieldCell | null;
|
|
@@ -128,14 +128,14 @@ declare namespace PageActions {
|
|
|
128
128
|
}
|
|
129
129
|
export type PageToolsProps = DivProps;
|
|
130
130
|
/**
|
|
131
|
-
* The page's tools — sections, search and filter, view — as kit controls in the order written
|
|
132
|
-
*
|
|
133
|
-
* a
|
|
134
|
-
* the
|
|
135
|
-
*
|
|
136
|
-
* a `group`.
|
|
131
|
+
* The page's tools — sections, search and filter, view — as kit controls in the order written, laid
|
|
132
|
+
* out by the one tool row (`ToolRow`, as the DataTable `toolbar`). On the bar's first line each keeps
|
|
133
|
+
* its natural width; on a line of their own a search takes the free room, switchers and pickers
|
|
134
|
+
* keep their width at the end (a `SegmentedControl.Root` and a `Datepicker.Root` are `fullWidth`
|
|
135
|
+
* unless set, so they fill a short line), and they wrap when one line is not enough. Named
|
|
136
|
+
* (`aria-label`), it is a `group`.
|
|
137
137
|
*/
|
|
138
|
-
declare function PageTools({ className,
|
|
138
|
+
declare function PageTools({ className, ...rest }: PageToolsProps): import("react/jsx-runtime").JSX.Element;
|
|
139
139
|
declare namespace PageTools {
|
|
140
140
|
var displayName: string;
|
|
141
141
|
}
|
package/dist/skill/anti-slop.md
CHANGED
|
@@ -242,7 +242,8 @@ The same for one value in several units — one heading, the fields in a row, th
|
|
|
242
242
|
| `<Button.Root onClick={() => location.assign(url)}>` | `<Button.Root asChild><a href={url}>…</a></Button.Root>` or `LinkButton` |
|
|
243
243
|
| a hand-built spinner, shimmer, grey placeholder box or «Loading…» text | `loading` on Button / Select / DataTable; a region: `Skeleton` of its content inside `Crossfade`; no shape to hold: `Spinner` |
|
|
244
244
|
| `{loading ? <Spinner /> : <List />}` — a state that flips | `<Crossfade state={status}>` around the region, `Skeleton` while loading |
|
|
245
|
-
| a search Input + a row of filter Selects above a table | `SmartFilter`
|
|
245
|
+
| a search Input + a row of filter Selects above a table | `SmartFilter` next to the table it filters: its toolbar and chips in the table's `toolbar` / `toolbarChips` beside KPI cards, in `Page.Tools` / `Page.Chips` when the table is the page ([where the filters go](composition.md#where-the-filters-go)) |
|
|
246
|
+
| the table's search, filter or «Колонки» in the page's bar above KPI cards | the bar keeps the title, the period that drives every block and the primary action; the table's tools go in its own `toolbar` ([where the filters go](composition.md#where-the-filters-go)) |
|
|
246
247
|
| a grid of «label: value» text pairs for one record | `DescriptionList.Root` + `DescriptionList.Item` |
|
|
247
248
|
| a Modal hand-built as «Удалить?» with your own pending flag | `ConfirmDialog.Root` with an async `onConfirm` |
|
|
248
249
|
| a `<fieldset>` reset by hand, or `disabled` on every field while saving | `<Fieldset label disabled={saving}>` |
|
|
@@ -300,19 +301,49 @@ the page's actions ([controls of a table](composition.md#controls-of-a-table)):
|
|
|
300
301
|
<DataTable columns={columns} rows={orders} toolbar={<Button.Root>Колонки</Button.Root>} />
|
|
301
302
|
```
|
|
302
303
|
|
|
304
|
+
A chooser cell in the header of a table that has no row actions is a column of nothing — an empty
|
|
305
|
+
cell in every row:
|
|
306
|
+
|
|
307
|
+
```tsx partial
|
|
308
|
+
// bad — columnsMenu with no row-actions column to stand over
|
|
309
|
+
<DataTable columns={columns} rows={orders} columnsMenu />
|
|
310
|
+
```
|
|
311
|
+
|
|
303
312
|
```tsx good
|
|
304
|
-
// good —
|
|
313
|
+
// good — a page that is the table, no row actions: the chooser is in the bar, on the state the table reads
|
|
305
314
|
// declare: const orders: { id: string; client: string; total: number }[]
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
315
|
+
// declare: const hidden: string[]
|
|
316
|
+
// declare: const setHidden: (hidden: string[]) => void
|
|
317
|
+
<Page.Root>
|
|
318
|
+
<Page.TopBar>
|
|
319
|
+
<Page.Heading>
|
|
320
|
+
<Page.Title>Заказы</Page.Title>
|
|
321
|
+
</Page.Heading>
|
|
322
|
+
<Page.Tools>
|
|
323
|
+
<DataTableColumnsMenu
|
|
324
|
+
columns={[
|
|
325
|
+
{ id: "id", header: "Заказ", accessor: "id", hideable: false },
|
|
326
|
+
{ id: "client", header: "Клиент", accessor: "client" },
|
|
327
|
+
{ id: "total", header: "Сумма", accessor: "total", numeric: true },
|
|
328
|
+
]}
|
|
329
|
+
hiddenColumns={hidden}
|
|
330
|
+
onHiddenColumnsChange={setHidden}
|
|
331
|
+
/>
|
|
332
|
+
</Page.Tools>
|
|
333
|
+
</Page.TopBar>
|
|
334
|
+
<Page.Body>
|
|
335
|
+
<DataTable
|
|
336
|
+
columns={[
|
|
337
|
+
{ id: "id", header: "Заказ", accessor: "id", hideable: false },
|
|
338
|
+
{ id: "client", header: "Клиент", accessor: "client" },
|
|
339
|
+
{ id: "total", header: "Сумма", accessor: "total", numeric: true },
|
|
340
|
+
]}
|
|
341
|
+
hiddenColumns={hidden}
|
|
342
|
+
rows={orders}
|
|
343
|
+
getRowKey={(row) => row.id}
|
|
344
|
+
/>
|
|
345
|
+
</Page.Body>
|
|
346
|
+
</Page.Root>
|
|
316
347
|
```
|
|
317
348
|
|
|
318
349
|
## 7b. A top bar out of its groups
|
|
@@ -23,7 +23,7 @@ import type { ControlSize, PaletteColor, TextTone, Tone, Variant } from "prime-u
|
|
|
23
23
|
| Validation | `invalid`, `hint`, `error` | Props, never parts. A non-empty `error` implies `invalid`; sets `aria-invalid`, `data-invalid`. |
|
|
24
24
|
| Value | `value` / `defaultValue` / `onValueChange(value)` | Accordion, Chat.Pinned, ChatList, ColorField, ColorPicker, ColorPresets, ColorSwatches, Composer, Datepicker, Datepicker.Panel, DigitInput, FieldList, FileUpload, FileUpload.Queue, Kanban, NativeSelect, Pagination, Radio.Group, SegmentedControl, Select, Slider, SmartFilter, Stepper, Tabs, TagSelect. |
|
|
25
25
|
| Text value | native `value` / `onChange` + `onValueChange(string)` | Input (`Input.Field`), Textarea. |
|
|
26
|
-
| Other state | `x` / `defaultX` / `onXChange(x)` | Badge `pressed`; Button `pressed`; ChartFrame `hiddenSeries`; Chat `selected`, `search`; ChatList `search`; ColorPicker `format`; CommandMenu `search`; Composer `recordMode`; DataTable `hiddenColumns`, `page`, `sort`, `selected`, `expanded`, `collapsedGroups`; ExampleFrame `viewport`, `colorScheme`; Gallery `index`; Gantt `scale`; Kanban `columnOrder`; MediaViewer `index`; Message.Media `index`; Select `search`; SmartFilter `search`; VideoNote `playing`, `transcriptExpanded`; VoicePlayer `rate`, `playing`, `transcriptExpanded`; Sidebar `mode`. |
|
|
26
|
+
| Other state | `x` / `defaultX` / `onXChange(x)` | Badge `pressed`; Button `pressed`; ChartFrame `hiddenSeries`; Chat `selected`, `search`; ChatList `search`; ColorPicker `format`; CommandMenu `search`; Composer `recordMode`; DataTable `hiddenColumns`, `columnOrder`, `page`, `sort`, `selected`, `expanded`, `collapsedGroups`; ExampleFrame `viewport`, `colorScheme`; Gallery `index`; Gantt `scale`; Kanban `columnOrder`; MediaViewer `index`; Message.Media `index`; Select `search`; SmartFilter `search`; VideoNote `playing`, `transcriptExpanded`; VoicePlayer `rate`, `playing`, `transcriptExpanded`; Sidebar `mode`. |
|
|
27
27
|
| Checked | `checked` / `defaultChecked` / `onCheckedChange(checked)` | Checkbox, Dropdown.CheckboxItem, Switch, Thumbnail. |
|
|
28
28
|
| Open | `open` / `defaultOpen` / `onOpenChange(open)` | ColorPresets, CommandMenu, ConfirmDialog, Datepicker, Drawer, Dropdown, Gallery, MediaViewer, Message.Media, Modal, Popover, Select, Sheet, TagSelect, Tooltip, Page.Split, Sidebar, Sidebar.Group, Sidebar.Sub. |
|
|
29
29
|
| Dismiss | `closeOnOutsideClick` (default `true`), `closeOnEscape` (default `true`) | Both on every overlay that opens a layer: the dialogs, the floating panels, the panels of Select, TagSelect, Datepicker and ColorPresets, the `suggestions` list of Input, the off-canvas Sidebar and the reaction bar of a message. `ConfirmDialog` takes both with `closeOnOutsideClick` default `false` (Escape is held while `onConfirm` runs). Tooltip and Notification take neither. A form holds closing with `Modal.Guard` / `Drawer.Guard` / `Sheet.Guard`. |
|
package/dist/skill/cheatsheet.md
CHANGED
|
@@ -44,6 +44,7 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
44
44
|
- Copy or call next to a value: `Input.CopyButton`, `Input.Action` inside `Input.Wrapper` — not a Button beside the field; a password: `Input.RevealButton`.
|
|
45
45
|
- Records of one shape a person adds and removes (invoice lines, contacts, specs, prices by period): `<FieldList columns={[{ label: "Услуга" }, { label: "Цена", width: "m" }]} value onValueChange getId createItem addLabel renderItem={(item, row) => <>…fields without label…</>} />` — the headings stand once, every field takes its column heading as its label; not a `.map()` of fields with a `label` in every row, not «Позиция 1», «Позиция 2».
|
|
46
46
|
- Small attributes in a row of fields: an icon-only `Select` (`<Select.Trigger><Select.TriggerIcon>…</Select.TriggerIcon></Select.Trigger>`, no `Select.Value`) and a `ColorPresets` swatch in `width: "compact"` columns — not a full-width Select and a ColorField on a line of their own.
|
|
47
|
+
- FieldList row actions: `rowActions="menu"` (one «⋯») for dense rows or touch, the default «×» + handle for short lists reordered often; `variant="surface"` when the list sits among other fields, `plain` when it is the card's content — not a hand-made «⋯» or a Card around the list.
|
|
47
48
|
- A base value with overrides (a price and its changes by month): one `FieldList`, the base as the first row pinned by `getLocked` — not a separate field above a list in another geometry.
|
|
48
49
|
- Rows that add up: `FieldList renderTotals={(items) => <>…one node per column…</>}` draws «Итого» under the rows on the same tracks — not a line of text typed under the list, not a DataTable of fields.
|
|
49
50
|
- One value in several units (a deposit in ₽, $, ฿): the fields side by side in one row under one heading, each unit in `Input.InlineAffix` — not a label per field, not the unit in the label and in the field.
|
|
@@ -84,7 +85,7 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
84
85
|
|
|
85
86
|
- The app is navigation and the page: everything at the top belongs to the page's `<Page.Root>` — not an app-wide header, a global search or notifications above every page.
|
|
86
87
|
- A work screen (a list, a board, a chat, a record) titles itself in `<Page.TopBar>`: `<Page.Heading>` with `<Page.Title>` + `<Page.Description>` carrying a line of data («128 в работе · 6 ждут оплаты») — not a sentence about the page.
|
|
87
|
-
- The bar holds only its four groups: `<Page.Heading>`, `<Page.Tools aria-label>` (sections, `<SmartFilter.Toolbar
|
|
88
|
+
- The bar holds only its four groups: `<Page.Heading>`, `<Page.Tools aria-label>` (sections, the view, a shared period; `<SmartFilter.Toolbar />` of a one-list page — in reading order), `<Page.Actions>`, `<Page.Chips>` (`<SmartFilter.Chips />`) — not a hand-made flex row of title, tabs, search and buttons ([the top bar](composition.md#the-top-bar)).
|
|
88
89
|
- A nested record: `<Breadcrumb.Root>` first in `<Page.Heading>`, then `<Page.Title>` with the record's name — the path ends in the title on one line; not a title row under the path, not a breadcrumb in the body. With a router each crumb is `<Breadcrumb.Item asChild>` around its `Link`.
|
|
89
90
|
- A line of data that loads after the page opens: `<Page.Description loading={!summary}>` from the first render (`Drawer.Description` / `Modal.Description` alike) — not a Description mounted when the data arrives, which moves the title, nor a `Skeleton` placed in it by hand.
|
|
90
91
|
- A page that edits a record: `<Page.Guard dirty={changed} busy={saving} />` inside `Page.Root` — the Sidebar, BottomNav and path links ask before the changes are lost; not your own `beforeunload` or a confirm wired to each link.
|
|
@@ -109,7 +110,8 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
109
110
|
## Tables
|
|
110
111
|
|
|
111
112
|
- A list page is a `<DataTable columns rows getRowKey>` under the page's `<SmartFilter.Root fields value onValueChange>` with `<SmartFilter.Toolbar />` in its `Page.Tools` and `<SmartFilter.Chips />` in its `Page.Chips` ([where the filters go](composition.md#where-the-filters-go)); filter rows with `matchesSmartFilter` — not a search Input and a row of Selects above the table.
|
|
112
|
-
-
|
|
113
|
+
- A table beside KPI cards or a chart: `<SmartFilter.Root>` around the `<DataTable toolbar={<><SmartFilter.Toolbar /><DataTableColumnsMenu … /></>} toolbarChips={<SmartFilter.Chips />}>` — not its search, filter and «Колонки» in the page's bar above blocks they do not filter ([where the filters go](composition.md#where-the-filters-go)).
|
|
114
|
+
- The column chooser is `<DataTable columnsMenu>` over the row actions, else `<DataTableColumnsMenu>` beside the table's search on the same state; `toolbar` takes two or more controls of the table as they are — no wrapper ([controls of a table](composition.md#controls-of-a-table)) — never a toolbar for one button.
|
|
113
115
|
- Search over a table or a list page is always SmartFilter, even with no filter fields yet (`fields={[]}` is a plain search that grows into filters). A search `Input` only outside a filtered list: inside a picker, a settings section, a panel.
|
|
114
116
|
- Paging is built in: `paging="pages"` (default) with `pageSize`, `paging="infinite"` for feeds, `paging="none"` for short lists — not a separate `<Pagination>` under a DataTable.
|
|
115
117
|
- Long lists are virtualized inside the kit: pass the whole array to `DataTable` (with `scrollHeight` or `paging="infinite"`; over 100 rows — grouped, `onReorder` and `renderExpanded` bodies render every row, keep those short or paged), `TagSelect options`, `Chat.Messages items`, `Gantt rows` — never slice it by hand, never wrap a kit list in react-window / react-virtual or your own windowing. Big remote data is still paged or loaded on scroll by the server (`source="server"`, `onLoadMore`).
|
|
@@ -121,6 +123,8 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
121
123
|
- A column header that needs a word more («Маржа»): the column's `description` (an info glyph with a tooltip) — not the explanation squeezed into the header, not your own icon and Tooltip in `header`.
|
|
122
124
|
- A list row with controls of its own (a Switch, a ⋯ menu): `List.ItemActions` inside `List.Item`, a stage colour as `Badge.Dot color` in `List.ItemIcon` — not a control inside the row's button, not a row rebuilt as a `div`.
|
|
123
125
|
- Rows people order by hand: `onReorder(id, beforeId)` (apply it with `moveBefore`, return the save's Promise) — not a Dnd list rebuilt next to the table.
|
|
126
|
+
- A DataTable's grip stays at the end of the row (`reorderHandle` default); `start` or `row` only by the rule ([choosing](choosing.md#3-pairs-that-are-easy-to-confuse)) — never a grip column of your own.
|
|
127
|
+
- Columns people arrange: `columnOrder` + `onColumnOrderChange` next to `hiddenColumns` and the column chooser, both saved by the app — not up / down buttons, a settings dialog or a header drag of your own.
|
|
124
128
|
- A short list of records people order by hand (stages, priorities) is `<List.Root onReorder>` with `List.Item value` — not up / down buttons or a hand-made `Dnd.Sortable`.
|
|
125
129
|
- A toggle in a reorderable row leads it (`List.ItemActions side="start"`) and a plain settings row ends with it ([list rows](composition.md#list-rows)) — never a Switch between the label and the grip.
|
|
126
130
|
- The grip ends its row: a `Dnd.Handle` is the last child of its item, after the row's own controls, and a locked row's lock takes the same place (List, FieldList and Kanban place theirs there) — not before the row's text.
|
|
@@ -208,7 +212,7 @@ Full rules: [responsive.md](responsive.md).
|
|
|
208
212
|
|
|
209
213
|
- Rearrange, never hide: every desktop function is on the phone, in another place or a menu — not `display: none` below a breakpoint.
|
|
210
214
|
- Breakpoints only in [their scope](responsive.md#breakpoints); inside a page — [intrinsic grids](responsive.md#intrinsic-grids) and container queries.
|
|
211
|
-
- A wide table scrolls inside itself; offer the column chooser
|
|
215
|
+
- A wide table scrolls inside itself; offer the column chooser (`columnsMenu` or `DataTableColumnsMenu`) — never drop columns by breakpoint, never hand-build the chooser.
|
|
212
216
|
- A row that opens a record page is `getRowHref` (a real link: ⌘-click, new tab), a row that opens a drawer is `onRowClick` — never `navigate()` in `onRowClick`, never your own `event.target` checks for buttons in the row.
|
|
213
217
|
- A list + detail screen is `Page.Split` (`Page.List`, `Page.Detail`) in a `Page.Root fill` page, its records a `List` (`current` on the open one) — never ghost Buttons with a soft current; a full-height table is `scrollHeight="fill"` — never a height computed from `100vh`.
|
|
214
218
|
- App sections on phones: `BottomNav` in `AppShell.Footer` — not in Main, not `position: fixed`, no own media query: the shell hides the footer while the Sidebar rail is on screen; a short task from the bottom edge: `Sheet` — Select, Dropdown, Popover and Modal become sheets below 640 by themselves.
|
package/dist/skill/checklist.md
CHANGED
|
@@ -10,13 +10,16 @@ checks.
|
|
|
10
10
|
- [ ] A nested record's path ends in its title on one line inside `Page.Heading`; no title row under the path ([the top bar](composition.md#the-top-bar)).
|
|
11
11
|
- [ ] Every button of the page is in one `Page.Actions`, the primary last, each with a `Button.Icon` ([the top bar](composition.md#the-top-bar)).
|
|
12
12
|
- [ ] `SmartFilter.Root` wraps `Page.Root`; its toolbar is in `Page.Tools`, its chips in `Page.Chips` ([the top bar](composition.md#the-top-bar)).
|
|
13
|
+
- [ ] A filter stands next to what it filters: beside KPI cards or a chart, the table's search, filter, chips and chooser are in its `toolbar` / `toolbarChips`; the bar keeps the title, the shared period and the primary action ([where the filters go](composition.md#where-the-filters-go)).
|
|
13
14
|
- [ ] A Card only for a standalone titled block — never around a table, a single field or the page ([surfaces](composition.md#4-surfaces)).
|
|
14
15
|
- [ ] A titled panel carries its heading in its `Card.Header` and stacks in the flow; no heading column beside a panel ([surfaces](composition.md#4-surfaces)).
|
|
15
|
-
- [ ]
|
|
16
|
+
- [ ] A DataTable's row grip and column order follow the rule: grip at the end, columns by `columnOrder` saved with `hiddenColumns` ([choosing](choosing.md#3-pairs-that-are-easy-to-confuse)).
|
|
17
|
+
- [ ] The column chooser is `columnsMenu` only over a row-actions column, else `DataTableColumnsMenu` beside the table's search; a DataTable `toolbar` holds two or more table controls, unwrapped, or does not exist ([controls of a table](composition.md#controls-of-a-table)).
|
|
16
18
|
|
|
17
19
|
## UX ([ux.md](ux.md))
|
|
18
20
|
- [ ] I wrote the five-line brief before the JSX ([brief](ux.md#1-brief-before-code)).
|
|
19
21
|
- [ ] Every entity is laid out by its shape: a record is one row or one block ([data shape](ux.md#2-the-shape-of-the-data-decides-the-layout)).
|
|
22
|
+
- [ ] A FieldList picks its `rowActions` (buttons or menu) and its `variant` (plain or surface) by the rules ([forms](composition.md#7-forms)).
|
|
20
23
|
- [ ] Labels on a form of rows ≤ columns + the list label; none repeated, numbered or repeating its heading ([label economy](ux.md#3-label-economy)).
|
|
21
24
|
- [ ] No text explains the layout; every hint says what the person cannot see ([label economy](ux.md#3-label-economy)).
|
|
22
25
|
- [ ] Buttons say the verb and the object, errors say how to fix it, empty states offer the next step ([words](ux.md#4-words)).
|
package/dist/skill/choosing.md
CHANGED
|
@@ -171,6 +171,7 @@ the records (tickets, letters, invoices; `current` on the open one; a row's own
|
|
|
171
171
|
| Accordion / Tabs | Accordion when the user may need several sections open or reads them in order (FAQ, settings groups). Tabs when sections are alternatives viewed one at a time. |
|
|
172
172
|
| Divider / gap | Divider inside a block between rows or a form and its footer. Between blocks and groups use air (`gap`), not lines. |
|
|
173
173
|
| Breadcrumb / back button | Breadcrumb on pages below a top-level section (Счета → № 2026-0418) and deeper; not on top-level pages. A single “Back” without hierarchy → `LinkButton` or `Button.Root variant="ghost"`. |
|
|
174
|
+
| DataTable grip `end` / `start` / `row`, column order | Rows whose order is data (stages, a price list): `onReorder` with the grip at the end of the row (`reorderHandle="end"`, the default — after the row actions); `start` only when the order is the list's main point and nothing is selected; `row` (the whole row carries) only for short rows with nothing to select, copy or follow as a link. Sorted by a column, rows do not reorder — say so, never hide the grip. Columns people arrange for themselves: `columnOrder` with `hiddenColumns` and the column chooser — a header drag (the header moves alone), Alt + ← / → and the chooser's grips; the key column `reorderable: false` (or pinned), the actions column `hideHeader`; the app saves both lists. |
|
|
174
175
|
| Pagination / infinite scroll | Pages for tables people return to or reference: DataTable `paging="pages"` (default, Pagination in the footer). `paging="infinite"` for feeds. Standalone Pagination only for lists that are not a DataTable. |
|
|
175
176
|
| Stepper / Tabs | Stepper for a sequential process with completion status; Tabs for non-sequential views. |
|
|
176
177
|
| Gantt / Timeline / Kanban / DataTable | Gantt: records with a start and an end compared across time (who is busy when, a plan with phases and milestones), moved, stretched and drawn by hand (`onBarChange`, `onBarCreate`). Timeline: a history of events at points in time, read top to bottom. Kanban: work items moved between statuses, time does not matter. DataTable: many records compared by many attributes, sorted, filtered and selected — dates are only columns. |
|
package/dist/skill/components.md
CHANGED
|
@@ -40,7 +40,7 @@ Type a value, and the field anatomy: label, hint, error.
|
|
|
40
40
|
| Label | Field label (native `<label>`) with required and optional markers. | [COMPONENT.md](reference/components/label/COMPONENT.md) · [examples](reference/components/label/examples/) |
|
|
41
41
|
| Hint | Help text or a validation error under a field. | [COMPONENT.md](reference/components/hint/COMPONENT.md) · [examples](reference/components/hint/examples/) |
|
|
42
42
|
| Fieldset | A frameless group of related form fields: a native `<fieldset>` with a `title-s` legend, the form rhythm between its fields and one `disabled` for all of them. | [COMPONENT.md](reference/components/fieldset/COMPONENT.md) · [examples](reference/components/fieldset/examples/) |
|
|
43
|
-
| FieldList | 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,
|
|
43
|
+
| FieldList | 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. | [COMPONENT.md](reference/components/field-list/COMPONENT.md) · [examples](reference/components/field-list/examples/) |
|
|
44
44
|
|
|
45
45
|
## Selection (`selection`)
|
|
46
46
|
|
|
@@ -66,7 +66,7 @@ AppShell.Root once per app (layouts.md#app-frame-once-per-a
|
|
|
66
66
|
│ │ ├─ Breadcrumb.Root only on a nested record: the levels above this page
|
|
67
67
|
│ │ ├─ Page.Title the one h1, the path's last level
|
|
68
68
|
│ │ └─ Page.Description a line of data
|
|
69
|
-
│ ├─ Page.Tools sections,
|
|
69
|
+
│ ├─ Page.Tools what acts on the whole page — sections, a period, view; a one-list page's search
|
|
70
70
|
│ ├─ Page.Actions every button of the page, each with an icon; the primary last
|
|
71
71
|
│ └─ Page.Chips SmartFilter.Chips: a line under everything, gone while empty
|
|
72
72
|
└─ Page.Body blocks 24 apart, the first 24 under the bar's controls
|
|
@@ -98,9 +98,11 @@ order is Heading, Tools, Actions, Chips whatever the JSX order.
|
|
|
98
98
|
then `Page.Title` with its name, on one line (the heading draws the chevron between them), and
|
|
99
99
|
`Page.Description` under the whole line. The title is never a row of its own under the path, and
|
|
100
100
|
the path never repeats the title as its last item.
|
|
101
|
-
- **`Page.Tools`** — the page
|
|
102
|
-
(a `SegmentedControl` with counts),
|
|
103
|
-
|
|
101
|
+
- **`Page.Tools`** — what acts on the whole page, as kit controls in the order a person reads them:
|
|
102
|
+
sections (a `SegmentedControl` with counts), the view, a period that drives several blocks; the
|
|
103
|
+
search and filter (`SmartFilter.Toolbar`) only when the page is one list
|
|
104
|
+
([where the filters go](#where-the-filters-go)). Named with `aria-label`; no buttons and no
|
|
105
|
+
wrappers inside.
|
|
104
106
|
- **`Page.Actions`** — the one place for the page's buttons: secondary ones (`soft` + `neutral`)
|
|
105
107
|
first, the primary (`solid`) last. Every Button carries a `Button.Icon` beside its label: the bar
|
|
106
108
|
shows it as the icon alone when the line runs short.
|
|
@@ -306,8 +308,15 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
306
308
|
- Records of one shape that a person adds, edits and removes are a `FieldList`: one row per record,
|
|
307
309
|
the column headings once above the rows, small attributes (an icon, then its color) in `compact`
|
|
308
310
|
columns. Never a label in every row, never numbered labels, never one record split over lines.
|
|
309
|
-
FieldList draws every row's actions itself — the remove
|
|
310
|
-
|
|
311
|
+
FieldList draws every row's actions itself — the remove «×» and the handle after it, a pinned
|
|
312
|
+
row's remove off with its reason (`labels.lockedReason`) — so never add your own.
|
|
313
|
+
- The row's actions (`rowActions`): `"buttons"` (default) for short lists edited often on a desktop,
|
|
314
|
+
where moving rows is a main task — remove and handle in sight on every row; `"menu"` — one «⋯» with
|
|
315
|
+
«Выше», «Ниже», «Удалить» — for dense rows (three or more columns), touch-heavy screens, or a row
|
|
316
|
+
that would carry more than remove and move.
|
|
317
|
+
- The look (`variant`): `"plain"` (default) when the list is the content of its card or section;
|
|
318
|
+
`"surface"` when the list stands among other fields of a form or card, so its rows read as one
|
|
319
|
+
block apart from the fields around it.
|
|
311
320
|
- One value in several units is one row of fields under one heading, each unit in its field's
|
|
312
321
|
`Input.InlineAffix`; a base value with overrides is one `FieldList` with the base pinned first.
|
|
313
322
|
The shape of the data decides the layout: [ux.md](ux.md#2-the-shape-of-the-data-decides-the-layout).
|
|
@@ -336,22 +345,130 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
336
345
|
|
|
337
346
|
### Where the filters go
|
|
338
347
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
348
|
+
A filter stands next to what it filters.
|
|
349
|
+
|
|
350
|
+
- `Page.TopBar` holds only what acts on the whole page: the heading with its line of data, a period
|
|
351
|
+
or a date range that drives several blocks (the KPI cards and the table), the page's sections or
|
|
352
|
+
view, the primary action.
|
|
353
|
+
- A page that holds more than its table — KPI cards, a chart, another block: the table's own
|
|
354
|
+
controls (search and filter, the chips, the column chooser, a table / cards view, bulk actions)
|
|
355
|
+
stand in the table's tools row. `SmartFilter.Root` wraps the `DataTable`; `SmartFilter.Toolbar`
|
|
356
|
+
and `DataTableColumnsMenu` go in its `toolbar`, `SmartFilter.Chips` in its `toolbarChips`
|
|
357
|
+
([server.tsx](reference/components/data-table/examples/server.tsx)).
|
|
358
|
+
- A page that is one list — the table, the board or the schedule is the whole page: «next to the
|
|
359
|
+
list» and «on the page» are one place, so its search and filter stay in the bar. `SmartFilter.Root`
|
|
360
|
+
wraps `Page.Root`, `SmartFilter.Toolbar` in `Page.Tools`, `SmartFilter.Chips` in `Page.Chips`
|
|
361
|
+
([the list page](#list-page)). A search alone is the same SmartFilter with `fields={[]}`.
|
|
362
|
+
- What picks the place: does the control change everything under the bar? A period that recomputes
|
|
363
|
+
the cards and the table does — the bar. A search that narrows only the rows does not — the
|
|
364
|
+
table's row.
|
|
365
|
+
- Both rows are one tool row of the kit: written in reading order, a search takes the free room,
|
|
366
|
+
switchers and buttons keep their width at the end, and a short row wraps by itself — pass the
|
|
367
|
+
controls as they are, never inside a flex wrapper of your own.
|
|
368
|
+
|
|
369
|
+
```tsx partial
|
|
370
|
+
// bad — the table's search, filter, chips and chooser in the bar, above KPI cards they do not filter
|
|
371
|
+
<SmartFilter.Root fields={FIELDS}>
|
|
372
|
+
<Page.Root>
|
|
373
|
+
<Page.TopBar>
|
|
374
|
+
<Page.Heading>
|
|
375
|
+
<Page.Title>Модели</Page.Title>
|
|
376
|
+
</Page.Heading>
|
|
377
|
+
<Page.Tools aria-label="Модели">
|
|
378
|
+
<SmartFilter.Toolbar />
|
|
379
|
+
<DataTableColumnsMenu columns={COLUMNS} hiddenColumns={hidden} onHiddenColumnsChange={setHidden} />
|
|
380
|
+
</Page.Tools>
|
|
381
|
+
<Page.Chips>
|
|
382
|
+
<SmartFilter.Chips />
|
|
383
|
+
</Page.Chips>
|
|
384
|
+
</Page.TopBar>
|
|
385
|
+
<Page.Body>
|
|
386
|
+
<Page.Grid>{/* four KPI cards */}</Page.Grid>
|
|
387
|
+
<DataTable columns={COLUMNS} rows={models} hiddenColumns={hidden} />
|
|
388
|
+
</Page.Body>
|
|
389
|
+
</Page.Root>
|
|
390
|
+
</SmartFilter.Root>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
```tsx good
|
|
394
|
+
// good — the bar keeps the title, the period that drives the cards and the table, the primary
|
|
395
|
+
// action; the table's search, filter, chips and chooser stand in the table's own tools row
|
|
396
|
+
// declare: const FIELDS: Parameters<typeof SmartFilter.Root>[0]["fields"]
|
|
397
|
+
// declare: const models: { id: string; name: string; rides: number }[]
|
|
398
|
+
// declare: const kpis: { label: string; value: string }[]
|
|
399
|
+
// declare: const hidden: string[]
|
|
400
|
+
// declare: const setHidden: (hidden: string[]) => void
|
|
401
|
+
<Page.Root>
|
|
402
|
+
<Page.TopBar>
|
|
403
|
+
<Page.Heading>
|
|
404
|
+
<Page.Title>Модели</Page.Title>
|
|
405
|
+
</Page.Heading>
|
|
406
|
+
<Page.Tools aria-label="Модели">
|
|
407
|
+
<Datepicker.Root mode="range" aria-label="Период" />
|
|
408
|
+
</Page.Tools>
|
|
409
|
+
<Page.Actions>
|
|
410
|
+
<Button.Root>
|
|
411
|
+
<Button.Icon>
|
|
412
|
+
<Icon name="action.add" />
|
|
413
|
+
</Button.Icon>
|
|
414
|
+
Добавить модель
|
|
415
|
+
</Button.Root>
|
|
416
|
+
</Page.Actions>
|
|
417
|
+
</Page.TopBar>
|
|
418
|
+
<Page.Body>
|
|
419
|
+
<Page.Grid>
|
|
420
|
+
{kpis.map((kpi) => (
|
|
421
|
+
<Page.Cell key={kpi.label} span={3}>
|
|
422
|
+
<Card.Root variant="stat-trend">
|
|
423
|
+
<Card.Label>{kpi.label}</Card.Label>
|
|
424
|
+
<Card.Value>{kpi.value}</Card.Value>
|
|
425
|
+
</Card.Root>
|
|
426
|
+
</Page.Cell>
|
|
427
|
+
))}
|
|
428
|
+
</Page.Grid>
|
|
429
|
+
<SmartFilter.Root fields={FIELDS}>
|
|
430
|
+
<DataTable
|
|
431
|
+
columns={[
|
|
432
|
+
{ id: "name", header: "Модель", accessor: "name", hideable: false },
|
|
433
|
+
{ id: "rides", header: "Поездки", accessor: "rides", numeric: true },
|
|
434
|
+
]}
|
|
435
|
+
rows={models}
|
|
436
|
+
getRowKey={(row) => row.id}
|
|
437
|
+
hiddenColumns={hidden}
|
|
438
|
+
toolbar={
|
|
439
|
+
<>
|
|
440
|
+
<SmartFilter.Toolbar />
|
|
441
|
+
<DataTableColumnsMenu
|
|
442
|
+
columns={[
|
|
443
|
+
{ id: "name", header: "Модель", accessor: "name", hideable: false },
|
|
444
|
+
{ id: "rides", header: "Поездки", accessor: "rides", numeric: true },
|
|
445
|
+
]}
|
|
446
|
+
hiddenColumns={hidden}
|
|
447
|
+
onHiddenColumnsChange={setHidden}
|
|
448
|
+
/>
|
|
449
|
+
</>
|
|
450
|
+
}
|
|
451
|
+
toolbarChips={<SmartFilter.Chips />}
|
|
452
|
+
/>
|
|
453
|
+
</SmartFilter.Root>
|
|
454
|
+
</Page.Body>
|
|
455
|
+
</Page.Root>
|
|
456
|
+
```
|
|
343
457
|
|
|
344
458
|
### Controls of a table
|
|
345
459
|
|
|
346
|
-
- The column chooser
|
|
347
|
-
the head cell of
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
-
|
|
353
|
-
|
|
354
|
-
|
|
460
|
+
- The column chooser has one place per table. Over the row actions: `columnsMenu` puts the icon
|
|
461
|
+
trigger in the head cell of the row-actions column (the last column, `hideHeader`), and it sticks
|
|
462
|
+
with `stickyLastColumn`. A table without row actions gets no chooser cell in its header: the
|
|
463
|
+
chooser is `<DataTableColumnsMenu>` beside the table's search ([where the filters go](#where-the-filters-go)),
|
|
464
|
+
bound to the same `hiddenColumns` / `columnOrder` state the table reads. Give the key column and
|
|
465
|
+
the actions column `hideable: false`.
|
|
466
|
+
- The DataTable `toolbar` holds two or more controls of the table itself — its search and filter,
|
|
467
|
+
the chooser, a view, a bulk bar of the selected rows — as they are, in reading order; its chips go
|
|
468
|
+
to `toolbarChips`.
|
|
469
|
+
- One control never gets a row of its own. A lone «Экспорт» or «Создать» is the page's
|
|
470
|
+
(`Page.Actions`); a lone chooser joins the table's search or the page's tools; a count or a total
|
|
471
|
+
of the list is data in `Page.Description`.
|
|
355
472
|
|
|
356
473
|
### Tables
|
|
357
474
|
|
|
@@ -513,7 +630,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
|
|
|
513
630
|
[data-table/examples/screen.tsx](reference/components/data-table/examples/screen.tsx)
|
|
514
631
|
|
|
515
632
|
- A work screen titles itself in its `Page.TopBar`: `Page.Title` is the only heading, its `Page.Description` is one line of figures («10 счетов · просрочено 2 на …»), not a sentence about the page; there is no second header in the flow.
|
|
516
|
-
- `SmartFilter.Root` wraps the page
|
|
633
|
+
- The table is the whole page, so its filters are the page's: `SmartFilter.Root` wraps the page, `SmartFilter.Toolbar` in `Page.Tools`, `SmartFilter.Chips` in `Page.Chips`; export (`soft` + `neutral`, an icon beside its label) and the one primary action (`solid`, last) in `Page.Actions`. The table itself has no `toolbar`; add a KPI row above it and the search, filter and chips move into the table's `toolbar` / `toolbarChips`.
|
|
517
634
|
- A status is a `Badge` with one `tone` per meaning across the product (`STATUS` is one map); the text carries the meaning, the tone repeats it.
|
|
518
635
|
- Numbers and dates are `numeric` columns with `headerAlign="end"` (tabular figures, values and heading on the end edge). On a phone the table scrolls inside itself and `stickyFirstColumn` keeps the number in place — the page never scrolls sideways.
|
|
519
636
|
- Row actions are a `Dropdown` behind an `action.more` icon of size `s` in the last column (`hideHeader`), pinned to the end edge with `stickyLastColumn` so they stay in reach while the columns scroll: controls in cells are one tier below the table. Delete is `tone="danger"` at the end of the menu.
|