@tristan2828/ui-foundation 4.45.0 → 4.47.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/conventions/docs/add-an-entity.md +25 -11
- package/conventions/docs/cell-patterns.md +111 -32
- package/conventions/docs/design-language.md +58 -22
- package/dist/components/app/data-table.d.ts +61 -4
- package/dist/components/app/data-table.js +217 -17
- package/dist/components/app/entity-view.d.ts +27 -1
- package/dist/components/app/entity-view.js +16 -9
- package/dist/components/app/key-stats.d.ts +14 -0
- package/dist/components/app/key-stats.js +6 -0
- package/dist/components/app/module-card.d.ts +11 -2
- package/dist/components/app/module-card.js +45 -5
- package/dist/components/app/table-groups.d.ts +12 -0
- package/dist/components/app/table-groups.js +37 -0
- package/dist/hooks/table-url-changes.d.ts +7 -0
- package/dist/hooks/table-url-changes.js +1 -0
- package/dist/hooks/use-table-layout.d.ts +1 -0
- package/dist/hooks/use-table-layout.js +2 -1
- package/dist/hooks/use-table-url-state.d.ts +11 -1
- package/dist/hooks/use-table-url-state.js +26 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
## Index
|
|
5
5
|
|
|
6
6
|
- [The reference files](#the-reference-files) · lines 37–59
|
|
7
|
-
- [Before anything: the entity plan](#before-anything-the-entity-plan) · lines 61–
|
|
7
|
+
- [Before anything: the entity plan](#before-anything-the-entity-plan) · lines 61–720
|
|
8
8
|
- Step 1: Add `<Entity>` to `openapi.yaml` · lines 99–106
|
|
9
9
|
- Step 2: `npm run gen:api` · lines 107–108
|
|
10
10
|
- Step 3: Add gateway tests in `tests/gateway/<entity>.test.ts`, derived from the spec · lines 109–120
|
|
11
11
|
- Step 4: Add MSW handlers in `src/mocks/<entity>.ts` · lines 121–130
|
|
12
12
|
- Step 5: Add a gateway module in `src/api/gateway/<entity>.ts` · lines 131–135
|
|
13
|
-
- Step 6: Copy the reference files, one for one, not just the two screens · lines 136–
|
|
14
|
-
- Step 7: Register routes · lines
|
|
15
|
-
- Step 8: Add Playwright specs for all three screens · lines
|
|
16
|
-
- Step 9: Register the nav entry and route names · lines
|
|
17
|
-
- Step 10: `npm run verify`. · lines
|
|
18
|
-
- [What not to copy](#what-not-to-copy) · lines
|
|
19
|
-
- [After a commit, in the backend](#after-a-commit-in-the-backend) · lines
|
|
20
|
-
- [Deleting](#deleting) · lines
|
|
21
|
-
- [Fields only a script writes](#fields-only-a-script-writes) · lines
|
|
13
|
+
- Step 6: Copy the reference files, one for one, not just the two screens · lines 136–612
|
|
14
|
+
- Step 7: Register routes · lines 613–623
|
|
15
|
+
- Step 8: Add Playwright specs for all three screens · lines 624–702
|
|
16
|
+
- Step 9: Register the nav entry and route names · lines 703–717
|
|
17
|
+
- Step 10: `npm run verify`. · lines 718–720
|
|
18
|
+
- [What not to copy](#what-not-to-copy) · lines 722–748
|
|
19
|
+
- [After a commit, in the backend](#after-a-commit-in-the-backend) · lines 750–767
|
|
20
|
+
- [Deleting](#deleting) · lines 769–798
|
|
21
|
+
- [Fields only a script writes](#fields-only-a-script-writes) · lines 800–821
|
|
22
22
|
|
|
23
23
|
<!-- /index -->
|
|
24
24
|
|
|
@@ -409,7 +409,21 @@ is a filled-in example while the Widgets demo is still here).
|
|
|
409
409
|
and every `placement` from the copied view (one column). Widget's
|
|
410
410
|
view is `rail`, with Details in the rail. The rail's sections come
|
|
411
411
|
first in the page (header, rail, main) at every width, so the
|
|
412
|
-
view's specs find them first too.
|
|
412
|
+
view's specs find them first too. When the plan says the main
|
|
413
|
+
column comes first on a phone (the record is opened for its notes
|
|
414
|
+
or its checklist), pass `mobileOrder="main-first"`: header, main,
|
|
415
|
+
rail where the columns stack, and in the DOM at every width, with
|
|
416
|
+
the rail still on the right two columns wide. Never re-order
|
|
417
|
+
`sections` by the window's width yourself.
|
|
418
|
+
- **A subtitle** (`subtitle`), only when the plan names a line that
|
|
419
|
+
says what the record is for (a goal, a one-line summary): under the
|
|
420
|
+
title in muted body type. When the plan marks it editable, wrap it
|
|
421
|
+
in `<EditableValue label=… edit={editInPlace(…)} layout="inline">`.
|
|
422
|
+
- **Key numbers** (`strip`), only when the plan names a few figures
|
|
423
|
+
read before anything else (done of total, time left, the next
|
|
424
|
+
date): `<KeyStats stats={[{ label, value, hint? }]} />`, under the
|
|
425
|
+
header across the page, so on a phone it comes before the rail and
|
|
426
|
+
the main column. Three to five; more belong in a section.
|
|
413
427
|
- **A notice** (`notice`), only when the plan names a state that
|
|
414
428
|
decides what you do with the record (held, overdue, archived and
|
|
415
429
|
read-only): a `<Notice tone=…>` saying what it means and what to do,
|
|
@@ -3,37 +3,38 @@
|
|
|
3
3
|
<!-- index: written by `npm run docs:index` in the package from the headings below; never edit it by hand -->
|
|
4
4
|
## Index
|
|
5
5
|
|
|
6
|
-
- [The rules that apply to every cell](#the-rules-that-apply-to-every-cell) · lines
|
|
7
|
-
- [1. Plain text](#1-plain-text) · lines
|
|
8
|
-
- [2. Icon + label](#2-icon--label) · lines
|
|
9
|
-
- [3. Icon in a badge](#3-icon-in-a-badge) · lines
|
|
10
|
-
- [4. Icon only, label in a tooltip](#4-icon-only-label-in-a-tooltip) · lines
|
|
11
|
-
- [Several values: the icons side by side, every name in one tooltip](#several-values-the-icons-side-by-side-every-name-in-one-tooltip) · lines
|
|
12
|
-
- [5. Enum → tone-mapped badge](#5-enum--tone-mapped-badge) · lines
|
|
13
|
-
- [6. Multi-value enum, clustered](#6-multi-value-enum-clustered) · lines
|
|
14
|
-
- [7. Link as an outline button](#7-link-as-an-outline-button) · lines
|
|
15
|
-
- [8. Date, relative](#8-date-relative) · lines
|
|
16
|
-
- [9. Ordinal scale (bounded integer)](#9-ordinal-scale-bounded-integer) · lines
|
|
17
|
-
- [Two variants the package draws: a step bar and a star](#two-variants-the-package-draws-a-step-bar-and-a-star) · lines
|
|
18
|
-
- [10. Summary in the cell, detail in a popover](#10-summary-in-the-cell-detail-in-a-popover) · lines
|
|
19
|
-
- [A second example: what a row depends on](#a-second-example-what-a-row-depends-on) · lines
|
|
20
|
-
- [11. Number or currency — Unproven](#11-number-or-currency--unproven) · lines
|
|
21
|
-
- [12. Boolean](#12-boolean) · lines
|
|
22
|
-
- [13. Progress bar — Unproven](#13-progress-bar--unproven) · lines
|
|
23
|
-
- [The compact form: `ProgressRing`](#the-compact-form-progressring) · lines
|
|
24
|
-
- [14. Avatar and name — Unproven](#14-avatar-and-name--unproven) · lines
|
|
25
|
-
- [15. Yes/no you flip in the row](#15-yesno-you-flip-in-the-row) · lines
|
|
26
|
-
- [A variant: a pressed icon inside another value's cell](#a-variant-a-pressed-icon-inside-another-values-cell) · lines
|
|
27
|
-
- [16. Title linking to the record's view](#16-title-linking-to-the-records-view) · lines
|
|
28
|
-
- [17. Quick action on the view](#17-quick-action-on-the-view) · lines
|
|
29
|
-
- [18. Editable value](#18-editable-value) · lines
|
|
30
|
-
- [A list of sub-items](#a-list-of-sub-items) · lines
|
|
31
|
-
- [In a table's rows](#in-a-tables-rows) · lines
|
|
32
|
-
- [A table of a few short columns: `width="content"`](#a-table-of-a-few-short-columns-widthcontent) · lines
|
|
33
|
-
- [19. Stage circle for an ordered status](#19-stage-circle-for-an-ordered-status) · lines
|
|
34
|
-
- [20. Count linking to the related records](#20-count-linking-to-the-related-records) · lines
|
|
35
|
-
- [21. Rows grouped under headings, and a pill filter](#21-rows-grouped-under-headings-and-a-pill-filter) · lines
|
|
36
|
-
- [22. A highlighted row, and rows that are done](#22-a-highlighted-row-and-rows-that-are-done) · lines
|
|
6
|
+
- [The rules that apply to every cell](#the-rules-that-apply-to-every-cell) · lines 66–85
|
|
7
|
+
- [1. Plain text](#1-plain-text) · lines 87–94
|
|
8
|
+
- [2. Icon + label](#2-icon--label) · lines 96–119
|
|
9
|
+
- [3. Icon in a badge](#3-icon-in-a-badge) · lines 121–134
|
|
10
|
+
- [4. Icon only, label in a tooltip](#4-icon-only-label-in-a-tooltip) · lines 136–213
|
|
11
|
+
- [Several values: the icons side by side, every name in one tooltip](#several-values-the-icons-side-by-side-every-name-in-one-tooltip) · lines 161–213
|
|
12
|
+
- [5. Enum → tone-mapped badge](#5-enum--tone-mapped-badge) · lines 215–228
|
|
13
|
+
- [6. Multi-value enum, clustered](#6-multi-value-enum-clustered) · lines 230–242
|
|
14
|
+
- [7. Link as an outline button](#7-link-as-an-outline-button) · lines 244–265
|
|
15
|
+
- [8. Date, relative](#8-date-relative) · lines 267–304
|
|
16
|
+
- [9. Ordinal scale (bounded integer)](#9-ordinal-scale-bounded-integer) · lines 306–354
|
|
17
|
+
- [Two variants the package draws: a step bar and a star](#two-variants-the-package-draws-a-step-bar-and-a-star) · lines 324–354
|
|
18
|
+
- [10. Summary in the cell, detail in a popover](#10-summary-in-the-cell-detail-in-a-popover) · lines 356–406
|
|
19
|
+
- [A second example: what a row depends on](#a-second-example-what-a-row-depends-on) · lines 374–406
|
|
20
|
+
- [11. Number or currency — Unproven](#11-number-or-currency--unproven) · lines 408–448
|
|
21
|
+
- [12. Boolean](#12-boolean) · lines 450–480
|
|
22
|
+
- [13. Progress bar — Unproven](#13-progress-bar--unproven) · lines 482–518
|
|
23
|
+
- [The compact form: `ProgressRing`](#the-compact-form-progressring) · lines 502–518
|
|
24
|
+
- [14. Avatar and name — Unproven](#14-avatar-and-name--unproven) · lines 520–537
|
|
25
|
+
- [15. Yes/no you flip in the row](#15-yesno-you-flip-in-the-row) · lines 539–648
|
|
26
|
+
- [A variant: a pressed icon inside another value's cell](#a-variant-a-pressed-icon-inside-another-values-cell) · lines 565–648
|
|
27
|
+
- [16. Title linking to the record's view](#16-title-linking-to-the-records-view) · lines 650–685
|
|
28
|
+
- [17. Quick action on the view](#17-quick-action-on-the-view) · lines 687–760
|
|
29
|
+
- [18. Editable value](#18-editable-value) · lines 762–1208
|
|
30
|
+
- [A list of sub-items](#a-list-of-sub-items) · lines 1024–1109
|
|
31
|
+
- [In a table's rows](#in-a-tables-rows) · lines 1111–1196
|
|
32
|
+
- [A table of a few short columns: `width="content"`](#a-table-of-a-few-short-columns-widthcontent) · lines 1198–1208
|
|
33
|
+
- [19. Stage circle for an ordered status](#19-stage-circle-for-an-ordered-status) · lines 1210–1276
|
|
34
|
+
- [20. Count linking to the related records](#20-count-linking-to-the-related-records) · lines 1278–1342
|
|
35
|
+
- [21. Rows grouped under headings, and a pill filter](#21-rows-grouped-under-headings-and-a-pill-filter) · lines 1344–1412
|
|
36
|
+
- [22. A highlighted row, and rows that are done](#22-a-highlighted-row-and-rows-that-are-done) · lines 1414–1442
|
|
37
|
+
- [23. A Group menu, and a record read beside the list](#23-a-group-menu-and-a-record-read-beside-the-list) · lines 1444–1518
|
|
37
38
|
|
|
38
39
|
<!-- /index -->
|
|
39
40
|
|
|
@@ -1405,7 +1406,10 @@ const { page, filters, setFilter, setPage } = useTableUrlState(['kind'])
|
|
|
1405
1406
|
- **No rows for a filter** is the table's own empty state, saying which
|
|
1406
1407
|
filter, with a button to take it off. An empty group never renders:
|
|
1407
1408
|
groups come from the rows.
|
|
1408
|
-
- **
|
|
1409
|
+
- **Headings that fold, picked by the reader** are pattern 23's
|
|
1410
|
+
`groupings`: a Group menu over choice fields. `groupBy` stays the
|
|
1411
|
+
app's one fixed grouping (a feed's days). **Not built:** grouping by
|
|
1412
|
+
more than one key.
|
|
1409
1413
|
|
|
1410
1414
|
## 22. A highlighted row, and rows that are done
|
|
1411
1415
|
|
|
@@ -1437,3 +1441,78 @@ current one. `DataTable`'s `highlightRow` returns true for it.
|
|
|
1437
1441
|
the token check in both themes; `e2e/data-table.spec.ts` checks every
|
|
1438
1442
|
cell wears the wash.
|
|
1439
1443
|
|
|
1444
|
+
## 23. A Group menu, and a record read beside the list
|
|
1445
|
+
|
|
1446
|
+
A working list (tasks, chores, a backlog) is read two ways besides its
|
|
1447
|
+
columns: **grouped by one of its choice fields**, picked by the reader
|
|
1448
|
+
(by status to see what's moving, by area to plan a trip round the
|
|
1449
|
+
house), and **one record at a time without leaving the list**, its notes
|
|
1450
|
+
and key facts in a panel beside the rows. `DataTable`'s `groupings` and
|
|
1451
|
+
`detailPanel` (4.46) do both. Raised for an app's task list; the
|
|
1452
|
+
package's Storybook renders it (`app/DataTable grouping`), with axe in
|
|
1453
|
+
both themes and its specs in a real browser.
|
|
1454
|
+
|
|
1455
|
+
```tsx
|
|
1456
|
+
const GROUPINGS: DataTableGrouping<Task>[] = [ // module level
|
|
1457
|
+
{ id: 'status', label: 'Status', options: STATUS_OPTIONS, value: (task) => task.status },
|
|
1458
|
+
{ id: 'categories', label: 'Category', options: CATEGORY_OPTIONS, value: (task) => task.categories, emptyLabel: 'No category' },
|
|
1459
|
+
{ id: 'canDoNow', label: 'Can do now', options: [{ value: 'yes', label: 'Can do now' }, { value: 'no', label: 'Not yet' }],
|
|
1460
|
+
value: (task) => (task.canDoNow ? 'yes' : 'no') },
|
|
1461
|
+
]
|
|
1462
|
+
|
|
1463
|
+
const { grouping, setGrouping, ...table } = useTableUrlState(FILTERS, MULTI, VIEWS, { groupStorageKey: 'my-app:tasks:group' })
|
|
1464
|
+
|
|
1465
|
+
<DataTable
|
|
1466
|
+
groupings={GROUPINGS}
|
|
1467
|
+
grouping={grouping}
|
|
1468
|
+
onGroupingChange={setGrouping}
|
|
1469
|
+
groupSummary={(tasks) => `${tasks.filter(isReady).length} ready · top ${Math.max(...tasks.map((task) => task.score))}`}
|
|
1470
|
+
detailLabel="Task"
|
|
1471
|
+
detailPanel={(task) => <TaskPreview task={task} />} // its own "Open page" link
|
|
1472
|
+
… />
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
- **None is the default**, and the table is exactly as it was until a
|
|
1476
|
+
grouping is picked. The Group menu sits at the end of the toolbar
|
|
1477
|
+
(before a layout switch): None, then each grouping. While grouped a
|
|
1478
|
+
Fold all button joins it, and the menu says which ("Group: Status").
|
|
1479
|
+
- **Only choice fields**: each group is one of the field's options, in
|
|
1480
|
+
the options' order (the order the form lists them), so give the same
|
|
1481
|
+
options list the column and the form use. A multi-value field (an
|
|
1482
|
+
array) shows the row under each of its values; a row with none goes
|
|
1483
|
+
last, under `emptyLabel` ("Not set" unless set). A yes/no field is two
|
|
1484
|
+
options, worded as headings ("Can do now", "Not yet").
|
|
1485
|
+
- **The grouped column is hidden** while grouped (its value is the
|
|
1486
|
+
heading), so a grouping's `id` is its column's id.
|
|
1487
|
+
- **A heading is a button** (`aria-expanded`) that folds its rows away:
|
|
1488
|
+
a chevron, the label, the count, then `groupSummary`'s line, given the
|
|
1489
|
+
group's rows *on this page* and the group itself. Folds reset when the
|
|
1490
|
+
grouping changes.
|
|
1491
|
+
- **Remembered like the layout**: `useTableUrlState`'s `grouping` is
|
|
1492
|
+
`?group=` in the URL, then the one remembered under `groupStorageKey`
|
|
1493
|
+
on this device, then None. A saved view can name a `group` (''
|
|
1494
|
+
for None) to turn one on when pressed; a view that names none keeps
|
|
1495
|
+
the grouping that's on. Grouping doesn't change which rows are shown,
|
|
1496
|
+
so it keeps the page and doesn't count toward the active view.
|
|
1497
|
+
- **It groups the page's rows**: paging pages rows, not groups, so a
|
|
1498
|
+
list that groups wants a page size that holds it (a working list of a
|
|
1499
|
+
few dozen), and a summary that must count a whole group across pages
|
|
1500
|
+
comes from the API. A grouping, while on, takes the place of
|
|
1501
|
+
`groupBy`'s headings.
|
|
1502
|
+
- **The detail panel** opens on a click on a row, or Enter on it (its
|
|
1503
|
+
title link included: a plain click picks the row, a modified one still
|
|
1504
|
+
opens the link in a new tab, and the panel's own "Open page" link
|
|
1505
|
+
leaves). A control in a cell (a button, a switch, an editable value)
|
|
1506
|
+
stays the control's. The picked row is marked (`aria-selected`, the
|
|
1507
|
+
muted fill and a bar in `--primary` at its start); ↑ and ↓ move the pick
|
|
1508
|
+
while the list has focus, and Escape or Close closes it, focus back on
|
|
1509
|
+
the row. On a wide screen (64rem and up) the panel sits beside the
|
|
1510
|
+
table and stays in view as the page scrolls; below that it is a
|
|
1511
|
+
bottom sheet.
|
|
1512
|
+
- **The panel is the app's**: a heading-sized title, a few badges, a
|
|
1513
|
+
short notes preview (a few lines of the record's notes, not the whole
|
|
1514
|
+
editor), and an "Open page" link (a `<Link>` in `text-link`, so it's
|
|
1515
|
+
announced as the link it is). Without `detailPanel` rows behave as they
|
|
1516
|
+
always have: the title link opens the record's page.
|
|
1517
|
+
- **The table layout's alone:** a card layout's cards don't open the
|
|
1518
|
+
panel.
|
|
@@ -3,23 +3,24 @@
|
|
|
3
3
|
<!-- index: written by `npm run docs:index` in the package from the headings below; never edit it by hand -->
|
|
4
4
|
## Index
|
|
5
5
|
|
|
6
|
-
- [Semantic tones](#semantic-tones) · lines
|
|
7
|
-
- [The three badge styles](#the-three-badge-styles) · lines
|
|
8
|
-
- [Mapping an enum to tones](#mapping-an-enum-to-tones) · lines
|
|
9
|
-
- [Glyphs and icons: 3:1](#glyphs-and-icons-31) · lines
|
|
10
|
-
- [Logos and brand marks](#logos-and-brand-marks) · lines
|
|
11
|
-
- [Categorical colour](#categorical-colour) · lines
|
|
12
|
-
- [Typography](#typography) · lines
|
|
13
|
-
- [Written text (notes)](#written-text-notes) · lines
|
|
14
|
-
- [Density](#density) · lines
|
|
15
|
-
- [Widget cards (hub pages)](#widget-cards-hub-pages) · lines
|
|
16
|
-
- [Module cards (a hub's places)](#module-cards-a-hubs-places) · lines
|
|
17
|
-
- [Notices](#notices) · lines
|
|
18
|
-
- [A value over time](#a-value-over-time) · lines
|
|
19
|
-
- [A list as a board](#a-list-as-a-board) · lines
|
|
20
|
-
- [A list as cards](#a-list-as-cards) · lines
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
6
|
+
- [Semantic tones](#semantic-tones) · lines 33–61
|
|
7
|
+
- [The three badge styles](#the-three-badge-styles) · lines 63–80
|
|
8
|
+
- [Mapping an enum to tones](#mapping-an-enum-to-tones) · lines 82–111
|
|
9
|
+
- [Glyphs and icons: 3:1](#glyphs-and-icons-31) · lines 113–126
|
|
10
|
+
- [Logos and brand marks](#logos-and-brand-marks) · lines 128–172
|
|
11
|
+
- [Categorical colour](#categorical-colour) · lines 174–244
|
|
12
|
+
- [Typography](#typography) · lines 246–328
|
|
13
|
+
- [Written text (notes)](#written-text-notes) · lines 277–328
|
|
14
|
+
- [Density](#density) · lines 330–387
|
|
15
|
+
- [Widget cards (hub pages)](#widget-cards-hub-pages) · lines 389–503
|
|
16
|
+
- [Module cards (a hub's places)](#module-cards-a-hubs-places) · lines 505–574
|
|
17
|
+
- [Notices](#notices) · lines 576–604
|
|
18
|
+
- [A value over time](#a-value-over-time) · lines 606–639
|
|
19
|
+
- [A list as a board](#a-list-as-a-board) · lines 641–692
|
|
20
|
+
- [A list as cards](#a-list-as-cards) · lines 694–740
|
|
21
|
+
- [Key numbers on a record](#key-numbers-on-a-record) · lines 742–764
|
|
22
|
+
- [Picking at random](#picking-at-random) · lines 766–797
|
|
23
|
+
- [Filling in what's missing](#filling-in-whats-missing) · lines 799–849
|
|
23
24
|
|
|
24
25
|
<!-- /index -->
|
|
25
26
|
|
|
@@ -556,10 +557,21 @@ const { data, isLoading, error, refetch } = useProjectsSummaryQuery()
|
|
|
556
557
|
each tile one link to its place (at least 44px tall); Retry stays its
|
|
557
558
|
own button above it. Six places make the phone's 3 × 2 grid; past
|
|
558
559
|
that, put the rest behind one tile ("More") rather than a third row.
|
|
560
|
+
- **A row of chips on a phone** (`<ModuleStrip mobile="row">`), when the
|
|
561
|
+
hub's widgets should start on the first screen, or there are more
|
|
562
|
+
places than six tiles: below the sidebar's breakpoint the strip is one
|
|
563
|
+
row that scrolls sideways, a chip per place with its icon in its slot's
|
|
564
|
+
colour, its name and its number (no note, no action). While the number
|
|
565
|
+
loads, a small skeleton; on an error, a muted dash ("Couldn't load" to
|
|
566
|
+
a screen reader), with Retry left to the desktop card and the place's
|
|
567
|
+
own page. The row scrolls inside itself and the page never does. A
|
|
568
|
+
desktop shows the cards either way, so don't render your own row
|
|
569
|
+
beside a strip hidden on a phone.
|
|
559
570
|
- Storybook's `app/ModuleCard` shows a hub with a strip and a widget,
|
|
560
|
-
every state,
|
|
561
|
-
|
|
562
|
-
desktop and a phone
|
|
571
|
+
every state, both at `compact`, and both again as a row of chips (Row,
|
|
572
|
+
Row states); its checks run axe and the token and glyph checks in both
|
|
573
|
+
themes and at `large`, measure the strip at a desktop and a phone
|
|
574
|
+
width, and run axe on the chips at a phone's.
|
|
563
575
|
|
|
564
576
|
## Notices
|
|
565
577
|
|
|
@@ -684,8 +696,8 @@ the table's.
|
|
|
684
696
|
`DataTable`'s `layouts` (4.39) draw the same rows as cards beside the
|
|
685
697
|
table: a thumbnail grid for a feed, covers for a collection, a dense line
|
|
686
698
|
each for clearing a backlog. They are layouts of one list, not other
|
|
687
|
-
pages: the toolbar, filters, saved views, paging
|
|
688
|
-
are the table's in every one, and so are its loading, empty and error
|
|
699
|
+
pages: the toolbar, filters, saved views, paging, `groupBy` headings
|
|
700
|
+
and a picked grouping's folding ones (cell pattern 23) are the table's in every one, and so are its loading, empty and error
|
|
689
701
|
states.
|
|
690
702
|
|
|
691
703
|
```tsx
|
|
@@ -727,6 +739,30 @@ const { layout, setLayout } = useTableLayout({
|
|
|
727
739
|
- `totals` and `highlightRow` are the table's alone. Sorting is the
|
|
728
740
|
toolbar's in a card layout (there is no header to click).
|
|
729
741
|
|
|
742
|
+
## Key numbers on a record
|
|
743
|
+
|
|
744
|
+
`KeyStats` (4.46), in `EntityView`'s `strip`: the few figures a record's
|
|
745
|
+
page is read for first (done of total, time left, the next date), in a
|
|
746
|
+
row under the header, across the page at every width.
|
|
747
|
+
|
|
748
|
+
```tsx
|
|
749
|
+
<EntityView
|
|
750
|
+
subtitle={<EditableValue label="Goal" edit={goals} layout="inline">{goal}</EditableValue>}
|
|
751
|
+
strip={<KeyStats stats={[{ label: 'Done', value: '7', hint: 'of 12' }, { label: 'Next step', value: formatPlainDate(next) }]} />}
|
|
752
|
+
… />
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
- **A label over its value**: the label in caption type and muted, the
|
|
756
|
+
value in the section title's size and tabular figures, a `hint`
|
|
757
|
+
under it in caption type. One bordered card, the figures side by side,
|
|
758
|
+
wrapping on a phone. A `<dl>`, so each label is read with its value.
|
|
759
|
+
- **An empty value reads as `emptyLabel`** ("Not set"), in body type and
|
|
760
|
+
muted, never a blank or a dash.
|
|
761
|
+
- **Three to five.** It's what the page is for at a glance; anything
|
|
762
|
+
more is a section's label/value rows.
|
|
763
|
+
- **`subtitle`** is the line under the title: what the record is for,
|
|
764
|
+
in muted body type, editable in place like any value.
|
|
765
|
+
|
|
730
766
|
## Picking at random
|
|
731
767
|
|
|
732
768
|
`SpinWheel` (4.42) picks one of a few candidates by chance, for "what do
|
|
@@ -82,10 +82,43 @@ export type DataTableProps<TData extends Record<string, unknown>> = {
|
|
|
82
82
|
*/
|
|
83
83
|
groupBy?: (row: TData) => RowGroup;
|
|
84
84
|
/**
|
|
85
|
-
* What a heading says after its label ("4 · 1 h 12 m"
|
|
86
|
-
* rows on this page
|
|
85
|
+
* What a heading says after its label ("4 · 1 h 12 m", "3 ready · top
|
|
86
|
+
* 42"), from the group's rows on this page, for `groupBy`'s headings and
|
|
87
|
+
* a grouping's alike. `group` says which group it is.
|
|
87
88
|
*/
|
|
88
|
-
groupSummary?: (rows: TData[]) => ReactNode;
|
|
89
|
+
groupSummary?: (rows: TData[], group: RowGroup) => ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* The fields the rows can be grouped by, picked in a Group menu at the
|
|
92
|
+
* end of the toolbar: None (the default, the table as it is without
|
|
93
|
+
* this), then each of these. Choice fields: each group is one of the
|
|
94
|
+
* field's options, in the options' order. While grouped, the field's
|
|
95
|
+
* column is hidden (its value is the heading), each heading folds its
|
|
96
|
+
* rows away and says how many there are, with `groupSummary` after it,
|
|
97
|
+
* and a Fold all button joins the menu. A grouping, while on, takes the
|
|
98
|
+
* place of `groupBy`. It groups this page's rows: paging still pages
|
|
99
|
+
* rows.
|
|
100
|
+
*/
|
|
101
|
+
groupings?: readonly DataTableGrouping<TData>[];
|
|
102
|
+
/**
|
|
103
|
+
* The grouping on: a grouping's id, or '' for None, from
|
|
104
|
+
* useTableUrlState's `grouping` (the URL's `?group=`, then the one
|
|
105
|
+
* remembered for this table). An id the table doesn't have is None.
|
|
106
|
+
* Left out, the menu keeps its own state, starting on None.
|
|
107
|
+
*/
|
|
108
|
+
grouping?: string;
|
|
109
|
+
onGroupingChange?: (grouping: string) => void;
|
|
110
|
+
/**
|
|
111
|
+
* A record read beside the list without leaving it. Clicking a row (its
|
|
112
|
+
* title link too; a modified click still opens the link) or Enter on it
|
|
113
|
+
* selects it and opens this panel: beside the table on a wide screen
|
|
114
|
+
* (64rem and up), a bottom sheet below that. ↑ and ↓ move the selection
|
|
115
|
+
* while the list has focus; Escape or Close closes it. Everything in
|
|
116
|
+
* the panel is the app's, an "Open page" link included. The table
|
|
117
|
+
* layout's alone: a card layout's cards are as they are.
|
|
118
|
+
*/
|
|
119
|
+
detailPanel?: (row: TData) => ReactNode;
|
|
120
|
+
/** The panel's heading and landmark name: "Details" unless set. */
|
|
121
|
+
detailLabel?: string;
|
|
89
122
|
/**
|
|
90
123
|
* False hides the column header row, for a list-like table whose columns
|
|
91
124
|
* explain themselves (a thumbnail, a title block, actions). Screen readers
|
|
@@ -132,6 +165,30 @@ export type DataTableProps<TData extends Record<string, unknown>> = {
|
|
|
132
165
|
icon?: IconComponent;
|
|
133
166
|
};
|
|
134
167
|
};
|
|
168
|
+
/** A field DataTable's rows can be grouped by (`groupings`). */
|
|
169
|
+
export type DataTableGrouping<TData> = {
|
|
170
|
+
/**
|
|
171
|
+
* The field's column id, hidden while grouped, and the grouping's id in
|
|
172
|
+
* the URL (`?group=status`) and in a saved view's `group`.
|
|
173
|
+
*/
|
|
174
|
+
id: string;
|
|
175
|
+
/** The menu's word for it ("Status"), and the menu button's while it's on ("Group: Status"). */
|
|
176
|
+
label: string;
|
|
177
|
+
/** The field's options, in order: the groups, their order and their headings. */
|
|
178
|
+
options: readonly {
|
|
179
|
+
value: string;
|
|
180
|
+
label: string;
|
|
181
|
+
}[];
|
|
182
|
+
/**
|
|
183
|
+
* The row's value: one option's value, or a list of them for a
|
|
184
|
+
* multi-value field, whose row then shows under each. A value the
|
|
185
|
+
* options don't name gets a group of its own after theirs, headed by
|
|
186
|
+
* the value itself.
|
|
187
|
+
*/
|
|
188
|
+
value: (row: TData) => string | readonly string[] | null | undefined;
|
|
189
|
+
/** The heading over rows with no value, last: "Not set" unless set ("No category"). */
|
|
190
|
+
emptyLabel?: string;
|
|
191
|
+
};
|
|
135
192
|
/** One of DataTable's card layouts (`layouts`). */
|
|
136
193
|
export type DataTableCardLayout<TData> = {
|
|
137
194
|
/** In the URL (`?layout=covers`) and remembered, so keep it short and stable. */
|
|
@@ -167,4 +224,4 @@ export type DataTableTotals = {
|
|
|
167
224
|
*/
|
|
168
225
|
cells?: Partial<Record<string, ReactNode>>;
|
|
169
226
|
};
|
|
170
|
-
export declare function DataTable<TData extends Record<string, unknown>>({ columns, data, total, page, pageSize, onPageChange, sorting, onSortingChange, isLoading, error, onRetry, emptyTitle, emptyDescription, emptyAction, toolbar, getRowId, pinFirstColumn, pinLastColumn, enableSortingRemoval, visibleColumns, width, groupBy, groupSummary, header: showHeader, highlightRow, totals, layouts, layout: layoutProp, onLayoutChange, tableLayout, }: DataTableProps<TData>): import("react").JSX.Element;
|
|
227
|
+
export declare function DataTable<TData extends Record<string, unknown>>({ columns, data, total, page, pageSize, onPageChange, sorting, onSortingChange, isLoading, error, onRetry, emptyTitle, emptyDescription, emptyAction, toolbar, getRowId, pinFirstColumn, pinLastColumn, enableSortingRemoval, visibleColumns, width, groupBy, groupSummary, header: showHeader, highlightRow, totals, layouts, layout: layoutProp, onLayoutChange, tableLayout, groupings, grouping: groupingProp, onGroupingChange, detailPanel, detailLabel, }: DataTableProps<TData>): import("react").JSX.Element;
|