prime-ui-kit 0.18.0 → 0.19.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/dist/chunks/{chunk-OAXJWF3L.js → chunk-RZNGLELR.js} +120 -114
- package/dist/chunks/{chunk-MB4CXSLL.js → chunk-WYCTXD63.js} +832 -619
- package/dist/chunks/{chunk-X3JT5CLR.js → chunk-ZTZ2ML2B.js} +1 -1
- package/dist/color-picker.js +2 -2
- package/dist/components/data-table/ColumnsMenu.d.ts +16 -20
- package/dist/components/data-table/DataTable.d.ts +14 -11
- package/dist/components/data-table/Head.d.ts +8 -1
- package/dist/components/data-table/Row.d.ts +7 -1
- package/dist/components/data-table/StateRows.d.ts +3 -1
- package/dist/components/field-list/FieldList.d.ts +8 -8
- package/dist/components/index.d.ts +1 -1
- package/dist/components/kanban/Kanban.d.ts +4 -0
- package/dist/components/list/List.d.ts +9 -4
- package/dist/index.css +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/fieldCell.d.ts +6 -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 +1 -7
- package/dist/skill/anti-slop.md +23 -0
- package/dist/skill/api-contract.md +1 -1
- package/dist/skill/cheatsheet.md +7 -3
- package/dist/skill/checklist.md +4 -0
- package/dist/skill/composition.md +70 -12
- 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 +40 -40
- package/dist/skill/reference/components/data-table/examples/actions-column.tsx +154 -0
- package/dist/skill/reference/components/data-table/examples/columns-visibility.tsx +3 -12
- package/dist/skill/reference/components/data-table/examples/controlled.tsx +1 -1
- package/dist/skill/reference/components/data-table/examples/narrow.tsx +4 -11
- 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/toolbar.tsx +26 -32
- package/dist/skill/reference/components/dnd/COMPONENT.md +4 -3
- 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.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 +29 -25
- 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/reorder.tsx +1 -1
- package/dist/skill/reference/components/field-list/examples/screen.module.css +1 -1
- package/dist/skill/reference/components/field-list/examples/screen.tsx +168 -162
- package/dist/skill/reference/components/field-list/examples/totals.tsx +9 -8
- 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/segmented-control/COMPONENT.md +3 -2
- package/dist/skill/reference/components/sheet/COMPONENT.md +18 -7
- package/dist/skill/reference/layout/page/COMPONENT.md +5 -12
- 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 +7 -5
- package/dist/tokens/semantic.d.ts +1 -1
- 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 +40 -40
- package/src/components/data-table/examples/actions-column.tsx +154 -0
- package/src/components/data-table/examples/columns-visibility.tsx +3 -12
- package/src/components/data-table/examples/controlled.tsx +1 -1
- package/src/components/data-table/examples/narrow.tsx +4 -11
- 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/toolbar.tsx +26 -32
- package/src/components/dnd/COMPONENT.md +4 -3
- 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.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 +29 -25
- 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/reorder.tsx +1 -1
- package/src/components/field-list/examples/screen.module.css +1 -1
- package/src/components/field-list/examples/screen.tsx +168 -162
- package/src/components/field-list/examples/totals.tsx +9 -8
- 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/segmented-control/COMPONENT.md +3 -2
- package/src/components/sheet/COMPONENT.md +18 -7
- package/src/layout/page/COMPONENT.md +5 -12
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A settings page as a document: the path in the top bar that repeats the title once it scrolls away, the large title in the flow,
|
|
1
|
+
/** A settings page as a document: the path in the top bar that repeats the title once it scrolls away, the large title in the flow, each section a card with its own heading, a form saved by its own button, switches that apply at once and a danger zone with a confirm — `collapse`, `maxWidth`, `Card.Header`. */
|
|
2
2
|
import {
|
|
3
3
|
Breadcrumb,
|
|
4
4
|
Button,
|
|
@@ -88,118 +88,107 @@ export default function PageScreenExample() {
|
|
|
88
88
|
</Page.Heading>
|
|
89
89
|
</Page.Header>
|
|
90
90
|
<Page.Body>
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
<Card.
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
<Input.
|
|
91
|
+
<form noValidate onSubmit={save} onReset={() => setInnError(undefined)}>
|
|
92
|
+
<Card.Root variant="panel">
|
|
93
|
+
<Card.Header>
|
|
94
|
+
<Card.Title as="h2">Компания</Card.Title>
|
|
95
|
+
<Card.Description>Попадают в счета, акты и письма клиентам.</Card.Description>
|
|
96
|
+
</Card.Header>
|
|
97
|
+
<Card.Body>
|
|
98
|
+
<Fieldset disabled={saving}>
|
|
99
|
+
<Input.Root label="Название">
|
|
100
|
+
<Input.Wrapper>
|
|
101
|
+
<Input.Field name="name" defaultValue="ООО «Прайм Софт»" />
|
|
102
|
+
</Input.Wrapper>
|
|
103
|
+
</Input.Root>
|
|
104
|
+
<div className={styles.pair}>
|
|
105
|
+
<Input.Root label="ИНН" error={innError} hint="10 или 12 цифр" reserveSupportRow>
|
|
101
106
|
<Input.Wrapper>
|
|
102
|
-
<Input.Field name="
|
|
107
|
+
<Input.Field name="inn" inputMode="numeric" defaultValue="7704512908" />
|
|
103
108
|
</Input.Wrapper>
|
|
104
109
|
</Input.Root>
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
</
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
))}
|
|
126
|
-
</Select.Content>
|
|
127
|
-
</Select.Root>
|
|
128
|
-
</div>
|
|
129
|
-
<Textarea.Root
|
|
130
|
-
name="address"
|
|
131
|
-
label="Юридический адрес"
|
|
132
|
-
optional
|
|
133
|
-
rows={2}
|
|
134
|
-
defaultValue="125009, Москва, ул. Тверская, д. 7, офис 412"
|
|
135
|
-
/>
|
|
136
|
-
</Fieldset>
|
|
137
|
-
</Card.Body>
|
|
138
|
-
<Card.Footer>
|
|
139
|
-
<Button.Root type="reset" variant="ghost" tone="neutral" disabled={saving}>
|
|
140
|
-
Отменить изменения
|
|
141
|
-
</Button.Root>
|
|
142
|
-
<Button.Root type="submit" loading={saving}>
|
|
143
|
-
{saved ? "Сохранено" : "Сохранить"}
|
|
144
|
-
</Button.Root>
|
|
145
|
-
</Card.Footer>
|
|
146
|
-
</Card.Root>
|
|
147
|
-
</form>
|
|
148
|
-
</Page.Section>
|
|
149
|
-
|
|
150
|
-
<Page.Section
|
|
151
|
-
layout="aside"
|
|
152
|
-
label="Уведомления"
|
|
153
|
-
description="Включаются сразу, без сохранения."
|
|
154
|
-
>
|
|
155
|
-
<Card.Root variant="panel">
|
|
156
|
-
<Card.Body>
|
|
157
|
-
<Fieldset aria-label="Уведомления">
|
|
158
|
-
{ALERTS.map((alert) => (
|
|
159
|
-
<Switch.Root
|
|
160
|
-
key={alert.name}
|
|
161
|
-
name={alert.name}
|
|
162
|
-
defaultChecked={alert.defaultChecked}
|
|
163
|
-
hint={alert.hint}
|
|
164
|
-
>
|
|
165
|
-
<Switch.Label>{alert.label}</Switch.Label>
|
|
166
|
-
</Switch.Root>
|
|
167
|
-
))}
|
|
110
|
+
<Select.Root label="Часовой пояс" defaultValue="Europe/Moscow">
|
|
111
|
+
<Select.Trigger>
|
|
112
|
+
<Select.Value />
|
|
113
|
+
</Select.Trigger>
|
|
114
|
+
<Select.Content searchable>
|
|
115
|
+
{TIMEZONES.map((zone) => (
|
|
116
|
+
<Select.Item key={zone.value} value={zone.value}>
|
|
117
|
+
{zone.label}
|
|
118
|
+
</Select.Item>
|
|
119
|
+
))}
|
|
120
|
+
</Select.Content>
|
|
121
|
+
</Select.Root>
|
|
122
|
+
</div>
|
|
123
|
+
<Textarea.Root
|
|
124
|
+
name="address"
|
|
125
|
+
label="Юридический адрес"
|
|
126
|
+
optional
|
|
127
|
+
rows={2}
|
|
128
|
+
defaultValue="125009, Москва, ул. Тверская, д. 7, офис 412"
|
|
129
|
+
/>
|
|
168
130
|
</Fieldset>
|
|
169
131
|
</Card.Body>
|
|
132
|
+
<Card.Footer>
|
|
133
|
+
<Button.Root type="reset" variant="ghost" tone="neutral" disabled={saving}>
|
|
134
|
+
Отменить изменения
|
|
135
|
+
</Button.Root>
|
|
136
|
+
<Button.Root type="submit" loading={saving}>
|
|
137
|
+
{saved ? "Сохранено" : "Сохранить"}
|
|
138
|
+
</Button.Root>
|
|
139
|
+
</Card.Footer>
|
|
170
140
|
</Card.Root>
|
|
171
|
-
</
|
|
141
|
+
</form>
|
|
172
142
|
|
|
173
|
-
<
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<Card.
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
</div>
|
|
187
|
-
<ConfirmDialog.Root
|
|
188
|
-
title="Удалить «Прайм Софт»?"
|
|
189
|
-
description="312 счетов, 48 клиентов и вся история удалятся без возможности восстановления."
|
|
190
|
-
confirmLabel="Удалить пространство"
|
|
191
|
-
onConfirm={removeWorkspace}
|
|
143
|
+
<Card.Root variant="panel">
|
|
144
|
+
<Card.Header>
|
|
145
|
+
<Card.Title as="h2">Уведомления</Card.Title>
|
|
146
|
+
<Card.Description>Включаются сразу, без сохранения.</Card.Description>
|
|
147
|
+
</Card.Header>
|
|
148
|
+
<Card.Body>
|
|
149
|
+
<Fieldset aria-label="Уведомления">
|
|
150
|
+
{ALERTS.map((alert) => (
|
|
151
|
+
<Switch.Root
|
|
152
|
+
key={alert.name}
|
|
153
|
+
name={alert.name}
|
|
154
|
+
defaultChecked={alert.defaultChecked}
|
|
155
|
+
hint={alert.hint}
|
|
192
156
|
>
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
157
|
+
<Switch.Label>{alert.label}</Switch.Label>
|
|
158
|
+
</Switch.Root>
|
|
159
|
+
))}
|
|
160
|
+
</Fieldset>
|
|
161
|
+
</Card.Body>
|
|
162
|
+
</Card.Root>
|
|
163
|
+
|
|
164
|
+
<Card.Root variant="panel">
|
|
165
|
+
<Card.Header>
|
|
166
|
+
<Card.Title as="h2">Рабочее пространство</Card.Title>
|
|
167
|
+
<Card.Description>Действия, которые нельзя отменить.</Card.Description>
|
|
168
|
+
</Card.Header>
|
|
169
|
+
<Card.Body>
|
|
170
|
+
<div className={styles.danger}>
|
|
171
|
+
<div className={styles.dangerText}>
|
|
172
|
+
<Typography variant="title-s">Удалить пространство</Typography>
|
|
173
|
+
<Typography variant="body-s" tone="secondary">
|
|
174
|
+
Счета, клиенты и история будут удалены для всех участников.
|
|
175
|
+
</Typography>
|
|
199
176
|
</div>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
177
|
+
<ConfirmDialog.Root
|
|
178
|
+
title="Удалить «Прайм Софт»?"
|
|
179
|
+
description="312 счетов, 48 клиентов и вся история удалятся без возможности восстановления."
|
|
180
|
+
confirmLabel="Удалить пространство"
|
|
181
|
+
onConfirm={removeWorkspace}
|
|
182
|
+
>
|
|
183
|
+
<ConfirmDialog.Trigger>
|
|
184
|
+
<Button.Root variant="outline" tone="danger">
|
|
185
|
+
Удалить
|
|
186
|
+
</Button.Root>
|
|
187
|
+
</ConfirmDialog.Trigger>
|
|
188
|
+
</ConfirmDialog.Root>
|
|
189
|
+
</div>
|
|
190
|
+
</Card.Body>
|
|
191
|
+
</Card.Root>
|
|
203
192
|
</Page.Body>
|
|
204
193
|
</Page.Root>
|
|
205
194
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** A page with titled sections: tiles of one screen close together, a new topic set off by air, a link at the end of the heading
|
|
2
|
-
import { Card, DataTable, type DataTableColumn, LinkButton, Page
|
|
1
|
+
/** A page with titled sections: tiles of one screen close together, a new topic set off by air, a link at the end of the heading — `Page.Section`, `label`, `description`, `Page.Actions`. */
|
|
2
|
+
import { Card, DataTable, type DataTableColumn, LinkButton, Page } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
import styles from "./examples.module.css";
|
|
5
5
|
|
|
@@ -55,19 +55,6 @@ export default function PageSectionsExample() {
|
|
|
55
55
|
</Page.Actions>
|
|
56
56
|
<DataTable columns={COLUMNS} rows={INVOICES} getRowKey={(row) => row.id} paging="none" />
|
|
57
57
|
</Page.Section>
|
|
58
|
-
<Page.Section
|
|
59
|
-
layout="aside"
|
|
60
|
-
label="Реквизиты в счетах"
|
|
61
|
-
description="Попадают в каждый новый счёт."
|
|
62
|
-
>
|
|
63
|
-
<Card.Root variant="panel">
|
|
64
|
-
<Card.Body>
|
|
65
|
-
<Typography variant="body-m" tone="secondary">
|
|
66
|
-
ООО «Прайм Софт», ИНН 7701234567, р/с в Т-Банке.
|
|
67
|
-
</Typography>
|
|
68
|
-
</Card.Body>
|
|
69
|
-
</Card.Root>
|
|
70
|
-
</Page.Section>
|
|
71
58
|
</Page.Body>
|
|
72
59
|
</div>
|
|
73
60
|
);
|
package/dist/skill/responsive.md
CHANGED
|
@@ -66,7 +66,7 @@ Use these as they are; never wrap them in your own breakpoints.
|
|
|
66
66
|
| app navigation | Sidebar: on-canvas from 768px, an off-canvas panel below (`offCanvas="auto"`) opened by the menu button `Page.TopBar` draws by itself while the Sidebar is off-canvas; `BottomNav` in `AppShell.Footer` for 2–6 app sections on phones — flat or a floating glass capsule (`floating`) with an optional round `BottomNav.Action`, icons with or without labels (`iconOnly`); the shell hides it by itself while the Sidebar rail is on screen (from 768px with `offCanvas="auto"`) |
|
|
67
67
|
| page top bar | `Page.TopBar` lays out its groups from their natural widths: one line while they fit, then the tools on a line of their own, then icon-only buttons, then «⋯», and the heading gives last ([below](#the-pages-top-bar--pagetopbar)). `Page.Header` in the flow: actions wrap under the title |
|
|
68
68
|
| switchers | [Switchers](#switchers) never wrap or truncate: they scroll inside themselves |
|
|
69
|
-
| tables | DataTable scrolls inside itself, edge shadows show hidden columns, `stickyFirstColumn
|
|
69
|
+
| tables | DataTable scrolls inside itself, edge shadows show hidden columns, `stickyFirstColumn` and `stickyLastColumn` (row actions), `columnsMenu` for a column chooser in the header row, the toolbar stacks below a 30rem table, sticky head off on short screens |
|
|
70
70
|
| floating layers | Popover (opened from a Trigger), Dropdown, Select (and Datepicker, ColorPresets) never wider than the viewport − 16, 8 from the edges, flip when out of room; below 640 they open as a bottom sheet — scrim, grab handle, swipe down to close, page scroll locked; Tooltip and TagSelect stay anchored |
|
|
71
71
|
| dialogs | Modal is a bottom sheet with a handle below 640 (swipe down closes it while `closeOnOutsideClick` allows); footer actions stack on phones and in a dialog narrower than 360px; `Sheet` is a bottom sheet for a short task at every width (centred at 560 on a tablet or desktop) and closes with a swipe down from its handle or header; a Drawer has no swipe — buttons, Escape and the scrim close it |
|
|
72
72
|
| date picking | Datepicker shows two months and side presets only when they fit; otherwise one month and presets above |
|
|
@@ -204,8 +204,10 @@ export function OrdersPage() {
|
|
|
204
204
|
- Every switcher — Tabs, SegmentedControl, a horizontal ButtonGroup and BottomNav — scrolls inside
|
|
205
205
|
itself when its items do not fit: never a second line, never a truncated item. Its hidden edges
|
|
206
206
|
fade, a vertical mouse wheel scrolls it sideways, touch pans it, the current item is scrolled into
|
|
207
|
-
view, and the indicator moves only after a person's choice. A SegmentedControl
|
|
208
|
-
icons collapses to icons (each named by its
|
|
207
|
+
view, and the indicator moves only after a person's choice. A SegmentedControl first tightens its
|
|
208
|
+
segments' padding, and one whose items all have icons then collapses to icons (each named by its
|
|
209
|
+
tooltip) before it scrolls. On a 320px phone two sections with counts fit a line of their own;
|
|
210
|
+
more, or long labels, scroll — keep the section a person needs first at the start.
|
|
209
211
|
- Your duty as its host: give the switcher a bounded width. A flex or grid child that holds one sets
|
|
210
212
|
`min-width: 0`, otherwise the switcher grows the row instead of scrolling
|
|
211
213
|
([foundation §9](reference/foundation.md#9-layout-and-responsiveness)).
|
|
@@ -223,8 +225,8 @@ export function OrdersPage() {
|
|
|
223
225
|
|
|
224
226
|
## Components on narrow widths — what you still decide
|
|
225
227
|
|
|
226
|
-
**Tables.** Let DataTable scroll inside itself; offer a column chooser (`
|
|
227
|
-
|
|
228
|
+
**Tables.** Let DataTable scroll inside itself; offer a column chooser (`columnsMenu`, in the header
|
|
229
|
+
row; the key column `hideable: false`); `stickyFirstColumn` for the name. On phones a table / cards switch in `Page.Tools` is fine — silently dropping columns
|
|
228
230
|
by breakpoint is not. Never ask to rotate the phone.
|
|
229
231
|
|
|
230
232
|
**Card grids.** [Intrinsic grids](#intrinsic-grids), never a breakpoint. Tiles of a row share one
|
|
@@ -22,7 +22,7 @@ export declare const semanticTokens: {
|
|
|
22
22
|
readonly inverse: "{color.gray.925}";
|
|
23
23
|
readonly scrim: "color-mix(in srgb, {color.gray.925} 44%, transparent)";
|
|
24
24
|
/** Soft shade over an edge that hides scrolled content (DataTable columns). */
|
|
25
|
-
readonly edgeShadow: "color-mix(in srgb, {color.gray.925}
|
|
25
|
+
readonly edgeShadow: "color-mix(in srgb, {color.gray.925} 8%, transparent)";
|
|
26
26
|
/**
|
|
27
27
|
* Translucent glass over content (floating BottomNav), always with a strong backdrop blur
|
|
28
28
|
* (`--prime-glass-saturation` lifts the colors under it): dense, as the iOS tab bar — the
|
|
@@ -11,7 +11,7 @@ export declare const darkThemeOverrides: {
|
|
|
11
11
|
readonly inverse: "{color.gray.100}";
|
|
12
12
|
readonly scrim: "color-mix(in srgb, {color.black} 60%, transparent)";
|
|
13
13
|
/** Darker than the scrim's share: a shade on a 900 surface needs more ink to read. */
|
|
14
|
-
readonly edgeShadow: "color-mix(in srgb, {color.black}
|
|
14
|
+
readonly edgeShadow: "color-mix(in srgb, {color.black} 28%, transparent)";
|
|
15
15
|
readonly glass: "color-mix(in srgb, {color.gray.875} 76%, transparent)";
|
|
16
16
|
readonly glassEdge: "color-mix(in srgb, {color.white} 28%, transparent)";
|
|
17
17
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prime-ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"packageManager": "bun@1.3.13",
|
|
5
5
|
"description": "React 19 UI kit for product interfaces: 79 composable components on one design contract — forms, selects, tables, overlays, navigation, app shell. CSS Modules, --prime-* tokens, light and dark themes, TypeScript, a11y, Next.js App Router ready. Ships COMPONENT.md docs, copyable examples and an agent skill.",
|
|
6
6
|
"bin": {
|
|
@@ -134,5 +134,5 @@ The dialog is a Modal `s` with the floating surface, a header and a footer; ther
|
|
|
134
134
|
- `tone="accent"` for a delete → destructive is always `danger`.
|
|
135
135
|
|
|
136
136
|
## Related
|
|
137
|
-
- **Built from:** the dialog Modal stands on (`internal/overlay/ModalDialog` at `s`, the header, body and footer of `internal/overlay/DialogParts`), [ScrollContainer](../scroll-container/COMPONENT.md) (the body), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (pictures the shared icon slot recognises), [Button](../button/COMPONENT.md) (Cancel, the confirm with `loading`), [Banner](../banner/COMPONENT.md) (the failure message), `Icon` (`status.danger` in the Banner)
|
|
137
|
+
- **Built from:** the dialog Modal stands on (`internal/overlay/ModalDialog` at `s`, the header, body and footer of `internal/overlay/DialogParts`), [ScrollContainer](../scroll-container/COMPONENT.md) (the body), [Avatar](../avatar/COMPONENT.md) and [Thumbnail](../thumbnail/COMPONENT.md) (pictures the shared icon slot recognises), [Skeleton](../skeleton/COMPONENT.md) (the shared description's loading line), [Button](../button/COMPONENT.md) (Cancel, the confirm with `loading`), [Banner](../banner/COMPONENT.md) (the failure message), `Icon` (`status.danger` in the Banner)
|
|
138
138
|
- **See also:** [Modal](../modal/COMPONENT.md), [Drawer](../drawer/COMPONENT.md), [Notification](../notification/COMPONENT.md)
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
## When to use
|
|
9
9
|
- Lists of records with several attributes to compare: orders, invoices, users, campaigns.
|
|
10
|
-
- Data that needs sorting, paging
|
|
10
|
+
- Data that needs sorting, paging or bulk selection.
|
|
11
11
|
- Thousands of records the server sorts and pages (`source="server"` + `rowCount`): the table shows the one page the server returned.
|
|
12
12
|
- Rows that open a record: a page (`getRowHref`, a real link) or a drawer in place (`onRowClick`).
|
|
13
13
|
- Rows grouped by a key with a header and totals per group (`getRowGroup`).
|
|
14
14
|
- Records whose order is data the user sets: pipeline stages, a price list, priorities (`onReorder`).
|
|
15
15
|
- Hierarchical rows (`getRowChildren`) or rows with a detail panel (`renderExpanded`).
|
|
16
16
|
- Wide numeric reports with a sticky head and first column.
|
|
17
|
-
- Wide records on narrow screens: let people choose the columns (`
|
|
17
|
+
- Wide records on narrow screens: let people choose the columns (`columnsMenu`) instead of dropping them.
|
|
18
18
|
- A log or a register that takes the rest of the page height (`scrollHeight="fill"`).
|
|
19
19
|
|
|
20
20
|
## When not to use
|
|
@@ -30,22 +30,22 @@
|
|
|
30
30
|
```tsx
|
|
31
31
|
import { DataTable, type DataTableColumn } from "prime-ui-kit";
|
|
32
32
|
```
|
|
33
|
-
Exported types: `DataTableColumn<Row>`, `DataTableProps<Row>`, `DataTableSortState`, `DataTableOrder`, `DataTableGroup<Row>`, `DataTableRowClickEvent`, `
|
|
33
|
+
Exported types: `DataTableColumn<Row>`, `DataTableProps<Row>`, `DataTableSortState`, `DataTableOrder`, `DataTableGroup<Row>`, `DataTableRowClickEvent`, `DataTableLabels`.
|
|
34
34
|
|
|
35
35
|
## Anatomy
|
|
36
36
|
```
|
|
37
37
|
DataTable card-fill block, radius 12, clips its content
|
|
38
|
-
├── toolbar optional panel
|
|
39
|
-
│ └── DataTable.ColumnsMenu the column chooser: a Button and a Dropdown of check items
|
|
38
|
+
├── toolbar optional panel for several table controls (bulk actions, own search + filters)
|
|
40
39
|
├── scroll viewport ScrollContainer, both axes, in a frame that holds the edge shadows
|
|
41
40
|
│ └── table
|
|
42
|
-
│ ├── thead [handle] [select all] [toggle] head cells; sortable heads hold a button
|
|
41
|
+
│ ├── thead [handle] [select all] [toggle] head cells [column chooser]; sortable heads hold a button
|
|
42
|
+
│ │ columnsMenu: an icon Button + Dropdown in the row-actions head cell, else a trailing cell
|
|
43
43
|
│ └── tbody rows · sub-rows · detail rows · skeleton / empty / error row
|
|
44
44
|
│ (one per group) with getRowGroup: a <th scope="rowgroup"> header row, then its rows
|
|
45
45
|
│ with onReorder: a Dnd.Sortable tbody, each row led by a Dnd.Handle
|
|
46
46
|
└── footer range «Показано 1–5 из 23» · Pagination · infinite-scroll status
|
|
47
47
|
```
|
|
48
|
-
Columns are data (`DataTableColumn<Row>[]`), not child parts. The
|
|
48
|
+
Columns are data (`DataTableColumn<Row>[]`), not child parts; the table has no parts. The column chooser is the `columnsMenu` flag: it lives in the header row, never in `toolbar`.
|
|
49
49
|
|
|
50
50
|
## API
|
|
51
51
|
|
|
@@ -57,9 +57,10 @@ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an
|
|
|
57
57
|
| Prop | Type | Default | Description |
|
|
58
58
|
|---|---|---|---|
|
|
59
59
|
| `columns` | `DataTableColumn<Row>[]` | — (required) | Column definitions, see `DataTableColumn<Row>`. |
|
|
60
|
-
| `hiddenColumns` | `string[]` | — | Ids of columns that are not rendered, controlled. A column with `hideable: false` stays.
|
|
60
|
+
| `hiddenColumns` | `string[]` | — | Ids of columns that are not rendered, controlled. A column with `hideable: false` stays. The column chooser (`columnsMenu`) changes it. |
|
|
61
61
|
| `defaultHiddenColumns` | `string[]` | `[]` | Initially hidden column ids, uncontrolled. |
|
|
62
|
-
| `onHiddenColumnsChange` | `(hiddenColumns: string[]) => void` | — | The hidden ids changed (`
|
|
62
|
+
| `onHiddenColumnsChange` | `(hiddenColumns: string[]) => void` | — | The hidden ids changed (`columnsMenu`). The kit stores nothing: keep the list (local storage, the user's settings) and pass it back to restore the choice. |
|
|
63
|
+
| `columnsMenu` | `boolean` | `false` | The column chooser at the end of the header row: in the head cell of a trailing row-actions column (`hideHeader`), else in a cell of its own; sticks with `stickyLastColumn`. Needs `showHeader`. |
|
|
63
64
|
| `rows` | `Row[]` | — (required) | Data; sorted in memory when a sort is set. With `source="server"` — the page (or the loaded batches) as the server returned it. |
|
|
64
65
|
| `source` | `"client" \| "server"` | `"client"` | Who sorts and pages the rows. `client`: the table sorts `rows` and slices pages. `server`: `rows` is one page, already sorted; `sort` / `page` changes are the request, `rowCount` drives Pagination. |
|
|
65
66
|
| `rowCount` | `number` | `rows.length` | Total rows on the server (`source="server"`): the page count and «Показано {from}–{to} из {total}». |
|
|
@@ -78,7 +79,7 @@ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an
|
|
|
78
79
|
| `loadingRows` | `number` | `min(pageSize, 5)` | Number of skeleton rows. |
|
|
79
80
|
| `empty` | `ReactNode` | `labels.empty` | Empty-state content: a string goes into a compact EmptyPage, a node (e.g. a full `EmptyPage`) is rendered as is. |
|
|
80
81
|
| `loadError` | `ReactNode` | — | Load error: replaces the body with the message in `role="alert"`. |
|
|
81
|
-
| `toolbar` | `ReactNode` | — | Panel above the table (
|
|
82
|
+
| `toolbar` | `ReactNode` | — | Panel above the table for several controls of the table itself (bulk actions on the selected rows, a table-local search with filters); stacks below 30rem of table width. |
|
|
82
83
|
| `selectable` | `boolean` | `false` | Leading checkbox column with «select all», Shift range, press-and-drag and Space. |
|
|
83
84
|
| `selected` | `Key[]` | — | Selected row ids, controlled. |
|
|
84
85
|
| `defaultSelected` | `Key[]` | `[]` | Initially selected ids, uncontrolled. |
|
|
@@ -97,6 +98,7 @@ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an
|
|
|
97
98
|
| `scrollHeight` | `number \| string \| "fill"` | `360` with `paging="infinite"`, else none | Max height of the scroll viewport (number = px); `"fill"` — the table takes the height of its flex / grid parent (or a sized block), the rows scroll inside, infinite paging follows it. |
|
|
98
99
|
| `stickyHeader` | `boolean` | `false` | The head sticks while the body scrolls (with `scrollHeight`); on screens lower than 480px it scrolls with the rows. |
|
|
99
100
|
| `stickyFirstColumn` | `boolean` | `false` | The first visible column (with the selection and toggle columns) sticks while scrolling sideways; the start edge shadow falls from its end edge. |
|
|
101
|
+
| `stickyLastColumn` | `boolean` | `false` | The last visible column (row actions) sticks to the end edge while scrolling sideways; the end edge shadow falls from its start edge. |
|
|
100
102
|
| `showHeader` | `boolean` | `true` | Render `<thead>`. |
|
|
101
103
|
| `fullWidth` | `boolean` | `true` | The table spans its container; `false` sizes the table and its surface by its content (up to the container's width, then it scrolls). |
|
|
102
104
|
| `rowDividers` | `boolean` | `true` | Hairlines between rows. |
|
|
@@ -117,15 +119,6 @@ Generic `DataTable<Row>`; `ref` → `HTMLDivElement`. A card-fill block with an
|
|
|
117
119
|
| `className` | `string` | — | Class on the root `<div>`. |
|
|
118
120
|
| `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "children">` | — | The other attributes of the root `<div>`. |
|
|
119
121
|
|
|
120
|
-
### DataTable.ColumnsMenu
|
|
121
|
-
`ref` → `HTMLButtonElement` (the trigger). The column chooser of the table it sits in: a Button opening a Dropdown with a check item per column. Writes the table's hidden columns; throws outside a DataTable `toolbar`.
|
|
122
|
-
|
|
123
|
-
| Prop | Type | Default | Description |
|
|
124
|
-
|---|---|---|---|
|
|
125
|
-
| `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Side of the trigger the menu opens on. |
|
|
126
|
-
| `align` | `"auto" \| "start" \| "center" \| "end"` | `"auto"` | Alignment to the trigger. `auto` lines the panel up with the trigger edge that has more room beyond it; `start`, `center` or `end` fix it. |
|
|
127
|
-
| `…rest` | `Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" \| "children">` | — | `className`, `aria-label` and the other attributes of the trigger button. |
|
|
128
|
-
|
|
129
122
|
### Types
|
|
130
123
|
|
|
131
124
|
#### `DataTableColumn<Row>`
|
|
@@ -147,7 +140,7 @@ One column of `columns`: data, not a part.
|
|
|
147
140
|
| `numeric` | `boolean` | — | Tabular figures, no wrapping, end alignment unless `align` is set. |
|
|
148
141
|
| `truncate` | `boolean` | — | One line with an ellipsis; width from `maxWidth` (or `width`); string values get a `title`. |
|
|
149
142
|
| `grow` | `boolean` | — | Takes the free width and wraps its text; the table then fills its container. With `minWidth` it never gets narrower: below that the table scrolls instead of cells overlapping. |
|
|
150
|
-
| `hideable` | `boolean` | `true` | The column can be hidden through `hiddenColumns` and `
|
|
143
|
+
| `hideable` | `boolean` | `true` | The column can be hidden through `hiddenColumns` and `columnsMenu`; `false` for the key column that names the row and for the row-actions column. |
|
|
151
144
|
| `scale` | `boolean \| { min?: number; max?: number }` | — | Heatmap: each cell is shaded by its numeric value on the five-step chart scale. `true` spans the column's minimum to maximum over all rows; `{ min, max }` fixes either end. |
|
|
152
145
|
| `width · minWidth · maxWidth` | `string` | — | CSS sizes of the column (`"14rem"`). |
|
|
153
146
|
|
|
@@ -223,9 +216,11 @@ Heads keep `start` whatever the cell alignment, with the sort icon at the end ed
|
|
|
223
216
|
| `highlightRowOnHover` | `fill-subtle-active` wash on the hovered row (stronger than a zebra step, so a hovered row never takes a zebra row's color) | interactive lists | `true` |
|
|
224
217
|
| `highlightColumnOnHover` | `fill-subtle-active` wash on the hovered column | comparison tables | `false` |
|
|
225
218
|
| `stickyHeader` · `stickyFirstColumn` | opaque sticky head / first column with a hairline edge; on screens lower than 480px the head scrolls with the rows | long / wide tables in a `scrollHeight` window | `false` |
|
|
219
|
+
| `stickyLastColumn` | the last visible column sticks to the end edge, opaque in the row's fill (hover, zebra, selected, expanded), with a hairline on its start edge | a row actions column (⋯ menu, an icon Button) of a table wider than its container | `false` |
|
|
226
220
|
| `stickyGroupHeaders` | group header rows stick under the head while their rows scroll | long grouped tables in a `scrollHeight` window | `false` |
|
|
227
221
|
| `collapsedGroups` · `defaultCollapsedGroups` · `onCollapsedGroupsChange` | any of the three makes groups fold: a chevron toggle leads each group header, a collapsed group shows its header alone | long grouped tables where people work one group at a time; `defaultCollapsedGroups={[]}` starts with every group open | off |
|
|
228
|
-
|
|
|
222
|
+
| `columnsMenu` | an icon-only ghost Button (`view.columns`) at the end of the header row: in the head cell of a trailing row-actions column (`hideHeader`), else in a narrow cell of its own; it opens the column chooser | every table with more columns than a narrow screen holds; the default home of the chooser | `false` |
|
|
223
|
+
| column `hideable` | `false`: the column stays even when listed in `hiddenColumns`; checked and disabled in the column chooser | the key column that names the row, the row-actions column | `true` |
|
|
229
224
|
| `showHeader` | head row shown | off only for self-explanatory key/value tables | `true` |
|
|
230
225
|
| `fullWidth` | spans the container; `false` = content width | `false` for short lookup tables in wide layouts | `true` |
|
|
231
226
|
| column `numeric` | tabular figures, end-aligned, no wrap | numbers, money | — |
|
|
@@ -249,8 +244,8 @@ Heads keep `start` whatever the cell alignment, with the sort icon at the end ed
|
|
|
249
244
|
| expanded | `expanded` / `defaultExpanded` | `data-expanded` on the parent row (one step darker); toggle `aria-expanded`, chevron turns 90° |
|
|
250
245
|
| new rows | rows new in `rows` (by `getRowKey`) | `data-animate`: top-level cells drop in from above (`enterMotion`) |
|
|
251
246
|
| sub-rows and detail panels | an expand / a collapse | both fold open and shut through the kit's Disclosure (`0fr ↔ 1fr`, the fade and a short slide on the same `base` clock; a detail opens on the enter curve); the row height and padding live inside the fold, so nothing is left to jump. Sub-rows: `data-fold` on the row (`open` · `closed`), every cell's content a Disclosure; a collapsed row's sub-rows stay mounted until the fold ends. Detail row: `data-state`; it unmounts after the close |
|
|
252
|
-
| overflowing sideways | the table is wider than its viewport | `data-overflow-start` (scrolled from the start) and `data-overflow-end` (more to the end) on the root: a soft shadow over that edge, from the end edge of the sticky first column for the start; fades in and out over `fast` |
|
|
253
|
-
| hidden columns | `hiddenColumns` / `defaultHiddenColumns`, `
|
|
247
|
+
| overflowing sideways | the table is wider than its viewport | `data-overflow-start` (scrolled from the start) and `data-overflow-end` (more to the end) on the root: a soft shadow over that edge, from the end edge of the sticky first column for the start and from the start edge of the sticky last column for the end; fades in and out over `fast` |
|
|
248
|
+
| hidden columns | `hiddenColumns` / `defaultHiddenColumns`, `columnsMenu` | the listed columns (except `hideable: false`) are not rendered in the head, rows, skeleton or state rows; widths are measured again; a sort by a hidden column keeps the row order |
|
|
254
249
|
| opening rows | `getRowHref` / `onRowClick`, `isRowClickable` | `data-clickable` (pointer, pressed wash) on rows that open; link rows: `data-link`, an `<a data-row-link>` in every content cell; in-place rows: `tabindex="0"`. Focus (the link, or the row) rings the whole row with the hover wash |
|
|
255
250
|
| grouped | `getRowGroup` | one `<tbody>` per group run on screen, opened by a `<th scope="rowgroup">` row one ladder step off the table; `data-sticky` on it with `stickyGroupHeaders` |
|
|
256
251
|
| collapsed group | `collapsedGroups` / `defaultCollapsedGroups` | `data-collapsed` on the group header row, its toggle `aria-expanded="false"` with the chevron at rest; the group's `<tbody>` holds the header alone, its rows only lay out the column widths in the hidden measuring body. Its rows fold away and back like sub-rows (`data-fold`, `base`), whether a toggle or the owner changed the keys. «Select all» still takes the rows of collapsed groups; paging still counts them |
|
|
@@ -261,25 +256,26 @@ Heads keep `start` whatever the cell alignment, with the sort icon at the end ed
|
|
|
261
256
|
| filling the parent | `scrollHeight="fill"` | `data-scroll-height="fill"` on the root; the viewport takes the free height |
|
|
262
257
|
| state swap | the body moves between loading, rows, empty and error | the body fades in over `base` (opacity only, the same swap motion as [Crossfade](../crossfade/COMPONENT.md)); the head stays still; nothing moves on the first render |
|
|
263
258
|
|
|
264
|
-
Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data-sticky-header`, `data-sticky-first-column`, `data-table-width` (`fill` · `auto` · `grow`), `data-highlight-row`, `data-highlight-column`, `data-striped`, `data-loading`, `data-reorderable`, `data-selectable`, `data-expandable`, `data-dragging` (while drag-selecting), `data-overflow-start` · `data-overflow-end` (edges that hide columns), `data-scroll-height` (`fill`). Sorting and paging swap rows instantly; everything is still under `prefers-reduced-motion`.
|
|
259
|
+
Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data-sticky-header`, `data-sticky-first-column`, `data-sticky-last-column`, `data-menu-cell` (the column chooser in a trailing cell of its own), `data-table-width` (`fill` · `auto` · `grow`), `data-highlight-row`, `data-highlight-column`, `data-striped`, `data-loading`, `data-reorderable`, `data-selectable`, `data-expandable`, `data-dragging` (while drag-selecting), `data-overflow-start` · `data-overflow-end` (edges that hide columns), `data-scroll-height` (`fill`). Sorting and paging swap rows instantly; everything is still under `prefers-reduced-motion`.
|
|
265
260
|
|
|
266
261
|
## Layout & spacing
|
|
267
262
|
- The root is a layer of the surface ladder (`data-depth`, one above its host): it fills its container on `--prime-color-layer-current`, radius 12, and clips full-bleed head and rows; zebra rows and the sticky column are one ladder step off it, the head two. On a card or in a modal it is the next layer by itself — never wrap it in a Card.
|
|
263
|
+
- `toolbar` holds several controls of the table itself: bulk actions on the selected rows, or the search and filters of a table that is not the page's main region (a section of a record, a Drawer). One control never gets a bar: the column chooser is `columnsMenu` in the header row; the page's search, filters, period, view, figures and actions go to its top bar (`Page.Tools`, `Page.Actions`, `Page.Description`).
|
|
268
264
|
- Toolbar padding `--prime-space-3` × cell padding, items `gap: --prime-space-2`; below 30rem of table width toolbar and footer stack.
|
|
269
265
|
- The footer has a hairline on top: range on the left, Pagination on the right. The range shows only with more than one page or with infinite scroll.
|
|
270
|
-
- Columns scroll sideways inside the table, never the page; pin the identifying column with `stickyFirstColumn
|
|
271
|
-
- While columns are hidden past an edge, that edge shows a `--prime-space-4` shadow (`--prime-color-bg-edge-shadow`) over the content; it takes no space and follows RTL. With `stickyFirstColumn` the start shadow falls from the pinned column's end edge.
|
|
272
|
-
- Fewer columns, less scrolling:
|
|
273
|
-
-
|
|
266
|
+
- Columns scroll sideways inside the table, never the page; pin the identifying column with `stickyFirstColumn` and the row actions column with `stickyLastColumn` (it pins the last visible column, so hiding it pins the one before). A 320 px column holds the table without breaking it.
|
|
267
|
+
- While columns are hidden past an edge, that edge shows a `--prime-space-4` shadow (`--prime-color-bg-edge-shadow`) over the content; it takes no space and follows RTL. With `stickyFirstColumn` the start shadow falls from the pinned column's end edge; with `stickyLastColumn` the end shadow falls from the pinned column's start edge. With `stickyHeader` the pinned head cells hold both corners.
|
|
268
|
+
- Fewer columns, less scrolling: turn on `columnsMenu` (the key column and the row-actions column `hideable: false`). The kit stores nothing: keep `onHiddenColumnsChange`'s list where the app keeps settings and pass it back as `hiddenColumns`. Never hide columns silently by width.
|
|
269
|
+
- The column chooser is an icon-only ghost neutral Button one tier below the table (the `view.columns` icon; `labels.columns` is its name and its Tooltip) at the end of the header row. With a trailing row-actions column (the last visible column has `hideHeader`) it stands in that column's head cell, lined up with the ⋯ buttons under it; otherwise it takes a narrow trailing cell (the trigger plus the end padding, no column divider) that every row repeats empty. With `stickyLastColumn` it sticks: in the actions head cell with that column, or its own cell at the end edge with the last column pinned right before it. Its Dropdown opens below, lined up with the trigger's end edge, lists every column and ends with `labels.showAllColumns`; a `hideable: false` column stays checked and disabled, and the last visible column cannot be hidden.
|
|
274
270
|
- Row height per `size` is public: `--prime-table-row-height-xs` · `-s` · `-m` · `-l` · `-xl` (36 · 36 · 44 · 52 · 52). A placeholder outside the table that stands in its geometry (a heatmap, a matrix skeleton) uses the same token.
|
|
275
271
|
- A row that opens keeps controls out of its key cell (that cell is the link); controls in other cells stand above the row's link. Mark a custom widget with `data-row-click-ignore`.
|
|
276
272
|
- Group headers: the name at the start, totals at the end (`renderGroupHeader` returns both, the kit lays them out across the visible width), head-tier text in `text-secondary`, row height = the head height. With collapsible groups the chevron toggle (the expand toggle's ghost Button) leads the header at the cell padding.
|
|
277
273
|
- The empty and error content stands in a sticky box of the visible width: a wide table centres it in the viewport, and it stays there while the table scrolls sideways.
|
|
278
274
|
- A trend per row: a compact [Sparkline](../sparkline/COMPONENT.md) (`layout="compact"`) in a cell fits the row height; its pointer and keys never open the row.
|
|
279
|
-
- Long data needs no slicing: past 100 rows a body in a bounded viewport (`scrollHeight`, `"fill"`, `paging="infinite"`) renders only the rows around the view; spacer rows keep the scrollbar exact, the focused row stays rendered, rows carry `aria-rowindex` and the table `aria-rowcount`. Grouped (`getRowGroup`), reorderable (`onReorder`) and `renderExpanded` bodies, and a table the page scrolls, render every row. The browser's find-in-page sees only rendered rows: give a long table a search
|
|
275
|
+
- Long data needs no slicing: past 100 rows a body in a bounded viewport (`scrollHeight`, `"fill"`, `paging="infinite"`) renders only the rows around the view; spacer rows keep the scrollbar exact, the focused row stays rendered, rows carry `aria-rowindex` and the table `aria-rowcount`. Grouped (`getRowGroup`), reorderable (`onReorder`) and `renderExpanded` bodies, and a table the page scrolls, render every row. The browser's find-in-page sees only rendered rows: give a long table a search (the page's `SmartFilter`).
|
|
280
276
|
- `scrollHeight="fill"` needs a parent with a height: a flex or grid column with `min-height: 0`, `Page.Root fill` in an `AppShell.Root fillViewport`, or a sized pane.
|
|
281
277
|
- The sticky head stops sticking on screens lower than 480px (landscape phones), so it never takes a large share of the height.
|
|
282
|
-
- Cell content: text, two-line text (title + `caption`), an Avatar with text, a Badge, or a control one tier down (`s` in an `m` table). Toolbar controls use `s` in an `m` table.
|
|
278
|
+
- Cell content: text, two-line text (title + `caption`), an Avatar with text, a Badge, or a control one tier down (`s` in an `m` table). Toolbar controls and the column chooser use `s` in an `m` table.
|
|
283
279
|
- Column widths: `width` / `minWidth` / `maxWidth` strings; never size cells with custom CSS.
|
|
284
280
|
- Sub-rows indent by `--dt-indent` per level (avatar of the tier + gap); override it on the root for other leading content.
|
|
285
281
|
|
|
@@ -290,7 +286,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
|
|
|
290
286
|
|
|
291
287
|
| Key | Action |
|
|
292
288
|
|---|---|
|
|
293
|
-
| `Tab` | Moves through the toolbar (`
|
|
289
|
+
| `Tab` | Moves through the toolbar, sort buttons, the column chooser (`columnsMenu`), row handles, checkboxes, expand toggles, row links, rows that open in place and Pagination. |
|
|
294
290
|
| `Enter` · `Space` | Sorts by the column, toggles the row or the group, or opens the focused row (`onRowClick`); Enter on a row link follows it. |
|
|
295
291
|
| `Space` | Toggles the focused row checkbox. |
|
|
296
292
|
| `Shift + Space` | Selects the range from the last toggled row. |
|
|
@@ -304,7 +300,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
|
|
|
304
300
|
- A link row has one link for assistive tech: the `<a>` in the key cell, named by its text. Its copies over the other cells are `aria-hidden` and `tabindex="-1"`, so the pointer can use the whole row. An in-place row is focusable itself (`tabindex="0"`) and reads as its cells. Either way the focused row shows a ring around all its cells.
|
|
305
301
|
- A row never opens from a control inside it (links, buttons, fields, `label`, `summary`, ARIA widgets, `data-row-click-ignore`), from a text selection, or from a React event that bubbled out of a portal (a ⋯ menu item).
|
|
306
302
|
- A group header is a `<th scope="rowgroup">` across every column, first in its own `<tbody>`.
|
|
307
|
-
- `
|
|
303
|
+
- The column chooser (`columnsMenu`) is a menu button named by `labels.columns` (its tooltip says the same): `menuitemcheckbox` items with `aria-checked`; the key column is `aria-disabled`. In the row-actions head cell the column keeps its own name (`aria-labelledby`); its own trailing cell is a `<td>`.
|
|
308
304
|
- Row checkboxes are named by `labels.selectRow` with `{label}` from `getRowLabel` (pass it so the names are unique); the head checkbox by `labels.selectAll`, `indeterminate` on a partial selection.
|
|
309
305
|
- After a selection change a polite live region announces `labels.selectedCount`; selected rows carry `aria-selected`.
|
|
310
306
|
- A row handle is a button named by `labels.reorderRow` with `aria-keyshortcuts`; a pick-up, a drop and a keyboard move are announced in a polite live region («Договор: позиция 2 из 6»). While its move is saved the row is `aria-busy`; while the table is sorted the handles are disabled and a tooltip gives `labels.reorderSorted`.
|
|
@@ -328,8 +324,8 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
|
|
|
328
324
|
| `selectedCount` | `"Выбрано: {count}"` | Polite announcement after the selection changes. |
|
|
329
325
|
| `expand` | `"Развернуть строку {label}"` | Name of the toggle of a collapsed row. |
|
|
330
326
|
| `collapse` | `"Свернуть строку {label}"` | Name of the toggle of an expanded row. |
|
|
331
|
-
| `columns` | `"Колонки"` |
|
|
332
|
-
| `showAllColumns` | `"Показать все"` | The
|
|
327
|
+
| `columns` | `"Колонки"` | Name and tooltip of the column chooser trigger (`columnsMenu`); `{shown}` and `{total}` are column counts. |
|
|
328
|
+
| `showAllColumns` | `"Показать все"` | The column chooser item that shows every column again. |
|
|
333
329
|
| `reorderRow` | `"Переместить строку {label}"` | `aria-label` of a row's drag handle (`onReorder`); `{label}` is `getRowLabel(row)`. |
|
|
334
330
|
| `reorderSorted` | `"Порядок задан сортировкой. Сбросьте её, чтобы перемещать строки"` | Tooltip of the disabled handles while the table is sorted by a column. |
|
|
335
331
|
| `expandGroup` | `"Развернуть группу {group}"` | Name of the toggle of a collapsed group; `{group}` is the group key. |
|
|
@@ -346,22 +342,23 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
|
|
|
346
342
|
| [trend.tsx](examples/trend.tsx) | Clients with revenue by month as a compact Sparkline in a cell of each row, next to the half-year total — `cell`, `width`. |
|
|
347
343
|
| [content-width.tsx](examples/content-width.tsx) | A short lookup table sized by its content with a centered column, and a `grow` column that takes the free width and wraps — `fullWidth`, `align`, `grow`. |
|
|
348
344
|
| [appearance.tsx](examples/appearance.tsx) | Row styling: zebra rows without lines, a column wash under the pointer, a headless key/value list — `striped`, `rowDividers`, `highlightColumnOnHover`, `columnDividers`, `showHeader`. |
|
|
349
|
-
| [toolbar.tsx](examples/toolbar.tsx) | A campaign
|
|
345
|
+
| [toolbar.tsx](examples/toolbar.tsx) | A campaign table with its own search and status filter above it — two table-scoped controls; the empty text when nothing matches — `toolbar`, `empty`. |
|
|
350
346
|
| [selection.tsx](examples/selection.tsx) | Team members with checkboxes: Shift+click range, press-and-drag, «select all» and bulk actions in the toolbar — `selectable`, `selected`, `onSelectedChange`, `getRowLabel`. |
|
|
351
347
|
| [nested-rows.tsx](examples/nested-rows.tsx) | Partners with their expense lines: sub-rows indented under the parent's name, a chevron toggle, together with selection and sorting — `getRowChildren`, `expanded`, `onExpandedChange`. |
|
|
352
348
|
| [detail-panel.tsx](examples/detail-panel.tsx) | Order details in a full-width row under an expanded order, aligned with the first content column; a cancelled order has nothing to show and gets no toggle — `renderExpanded`, `defaultExpanded`, `isRowExpandable`. |
|
|
353
349
|
| [sticky.tsx](examples/sticky.tsx) | Monthly sales by region in a 280 px window: the head and the region column stay while scrolling both ways — `stickyHeader`, `stickyFirstColumn`, `scrollHeight`. |
|
|
350
|
+
| [actions-column.tsx](examples/actions-column.tsx) | Orders wider than the table: the order number stays at the start, the ⋯ row actions at the end with the column chooser in their header cell, and the columns between scroll sideways — `stickyFirstColumn`, `stickyLastColumn`, `columnsMenu`. |
|
|
354
351
|
| [row-link.tsx](examples/row-link.tsx) | Invoices whose rows are links: ⌘-click or a middle click opens a new tab, a plain click routes inside the app, the totals row and the ⋯ menu never open a row — `getRowHref`, `onRowClick`, `isRowClickable`. |
|
|
355
352
|
| [row-open.tsx](examples/row-open.tsx) | Deals that open in a side panel: a click, or Enter and Space on the focused row; the panel keeps the last deal while it slides away — `onRowClick`. |
|
|
356
353
|
| [reorder.tsx](examples/reorder.tsx) | Pipeline stages put in order by the handle or Alt + ↑ / ↓; each move is saved with a spinner in its handle, and sorting by a column turns the handles off — `onReorder`, `getRowLabel`. |
|
|
357
354
|
| [groups.tsx](examples/groups.tsx) | Payments grouped by month with the month's total in its header row; headers stay on top while their rows scroll, a past month folds away — `getRowGroup`, `renderGroupHeader`, `stickyGroupHeaders`, `defaultCollapsedGroups`. |
|
|
358
|
-
| [columns-visibility.tsx](examples/columns-visibility.tsx) | Orders with the column chooser
|
|
355
|
+
| [columns-visibility.tsx](examples/columns-visibility.tsx) | Orders with the column chooser at the end of the header row; the choice outlives a reload in local storage, the order number always stays — `columnsMenu`, `hiddenColumns`, `onHiddenColumnsChange`, `hideable`. |
|
|
359
356
|
| [infinite-scroll.tsx](examples/infinite-scroll.tsx) | An audit log that reveals loaded rows in batches and then asks the server for more — `paging`, `infiniteBatchSize`, `hasMore`, `loadingMore`, `onLoadMore`. |
|
|
360
357
|
| [server.tsx](examples/server.tsx) | A CRM deal list the server sorts, pages and searches; a new search returns to page 1 — `source`, `rowCount`, `sort`, `page`, `loading`. |
|
|
361
358
|
| [fill-height.tsx](examples/fill-height.tsx) | An audit log that fills the height of its panel and loads older entries while scrolling; the status shows while the request is pending — `scrollHeight`, `paging`, `onLoadMore`. |
|
|
362
359
|
| [states.tsx](examples/states.tsx) | Loading skeleton, an empty period and a load error with a retry: the head stays, only the body changes — `loading`, `loadingRows`, `empty`, `loadError`. |
|
|
363
360
|
| [controlled.tsx](examples/controlled.tsx) | Sort, page and the column choice owned by the parent (a URL or a store): a header click goes asc → desc → none, a new sort returns to page 1 — `sort`, `onSortChange`, `page`, `onPageChange`, `hiddenColumns`, `onHiddenColumnsChange`. |
|
|
364
|
-
| [narrow.tsx](examples/narrow.tsx) | A 320 px support queue: columns scroll inside the table with the first one pinned, the
|
|
361
|
+
| [narrow.tsx](examples/narrow.tsx) | A 320 px support queue: columns scroll inside the table with the first one pinned, the column chooser stays at the end of the header row, the footer reflows and the pager turns compact — `stickyFirstColumn`, `columnsMenu`, `size`. |
|
|
365
362
|
| [screen.tsx](examples/screen.tsx) | A list page: the title, a line of figures, search, filters and the one primary action in its top bar, a DataTable of invoices with statuses, row actions, pages and a filtered empty state — `empty`, `pageSize`, `stickyFirstColumn`. |
|
|
366
363
|
|
|
367
364
|
## Mistakes
|
|
@@ -376,10 +373,13 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
|
|
|
376
373
|
- Applying the new order before the server answers and undoing it by hand → return the request's promise from `onReorder` and apply after it resolves: the table keeps the row in place meanwhile and moves it back on a rejection.
|
|
377
374
|
- A long table in a `scrollHeight` window without `stickyHeader` → the head scrolls away.
|
|
378
375
|
- `paging="none"` for hundreds of rows → use `pages` or `infinite`.
|
|
379
|
-
- Dropping columns below a breakpoint → people lose data without knowing it;
|
|
380
|
-
- A hand-built column chooser from Dropdown items → `
|
|
376
|
+
- Dropping columns below a breakpoint → people lose data without knowing it; turn on `columnsMenu`.
|
|
377
|
+
- A hand-built column chooser from Dropdown items → `columnsMenu` (key column, last visible column, «Показать все» built in).
|
|
378
|
+
- A toolbar with one button (the column chooser, «Экспорт», «Создать») → a whole bar above the table for one control; the chooser is `columnsMenu` in the header row, the page's actions go to `Page.Actions`.
|
|
379
|
+
- The page's search, filters, period, view or figures in `toolbar` → they belong to the page's top bar (`Page.Tools`, `Page.Actions`, `Page.Description`); `toolbar` keeps several controls of the table itself (bulk actions, the search and filters of a table in a section or a Drawer).
|
|
381
380
|
- Letting the key column be hidden → set `hideable: false` on it, so every row keeps its name.
|
|
382
381
|
- Wrapping the table in an `overflow-x` box of your own → it already scrolls inside and shows the edge shadows.
|
|
382
|
+
- A row actions column that scrolls out of view in a wide table, or a hand-made `position: sticky` on it → make it the last column and set `stickyLastColumn`.
|
|
383
383
|
- `onRowClick` with `navigate()` for a record page → use `getRowHref` (⌘-click, new tab, status-bar URL) and route the plain click in `onRowClick` with `event.preventDefault()`.
|
|
384
384
|
- Checking `event.target` in `onRowClick` to skip a button or a menu in the row → the table already ignores controls and portals; mark a custom widget with `data-row-click-ignore`.
|
|
385
385
|
- A totals row that opens like a record → `isRowClickable`.
|
|
@@ -393,5 +393,5 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
|
|
|
393
393
|
- A new search that keeps the current page → set `page` to 1 together with the search.
|
|
394
394
|
|
|
395
395
|
## Related
|
|
396
|
-
- **Built from:** [Checkbox](../checkbox/COMPONENT.md), [Button](../button/COMPONENT.md), [Dropdown](../dropdown/COMPONENT.md) (
|
|
396
|
+
- **Built from:** [Checkbox](../checkbox/COMPONENT.md), [Button](../button/COMPONENT.md), [Dropdown](../dropdown/COMPONENT.md) (the column chooser), [Pagination](../pagination/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [VisuallyHidden](../visually-hidden/COMPONENT.md) (loading status, selection announcement), [Skeleton](../skeleton/COMPONENT.md) (loading rows, in the table tier), [Dnd](../dnd/COMPONENT.md) (`onReorder`: `Dnd.Sortable as="tbody"`, `Dnd.Handle`), [Tooltip](../tooltip/COMPONENT.md) (handle hint, a column `description`, the column chooser), Icon (sort arrows, the info glyph of a column `description`, the expand chevron, the column chooser trigger); `useVirtualRows` (the window of a long body)
|
|
397
397
|
- **See also:** [Sparkline](../sparkline/COMPONENT.md) (`layout="compact"` in a cell), [SmartFilter](../smart-filter/COMPONENT.md), [Page](../../layout/page/COMPONENT.md) (`Page.Tools`, `Page.Chips`), [Gantt](../gantt/COMPONENT.md), [Kanban](../kanban/COMPONENT.md), [Badge](../badge/COMPONENT.md), [Avatar](../avatar/COMPONENT.md), [Card](../card/COMPONENT.md)
|