@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.
@@ -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–706
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–598
14
- - Step 7: Register routes · lines 599–609
15
- - Step 8: Add Playwright specs for all three screens · lines 610–688
16
- - Step 9: Register the nav entry and route names · lines 689–703
17
- - Step 10: `npm run verify`. · lines 704–706
18
- - [What not to copy](#what-not-to-copy) · lines 708–734
19
- - [After a commit, in the backend](#after-a-commit-in-the-backend) · lines 736–753
20
- - [Deleting](#deleting) · lines 755–784
21
- - [Fields only a script writes](#fields-only-a-script-writes) · lines 786–807
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 65–84
7
- - [1. Plain text](#1-plain-text) · lines 86–93
8
- - [2. Icon + label](#2-icon--label) · lines 95–118
9
- - [3. Icon in a badge](#3-icon-in-a-badge) · lines 120–133
10
- - [4. Icon only, label in a tooltip](#4-icon-only-label-in-a-tooltip) · lines 135–212
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 160–212
12
- - [5. Enum → tone-mapped badge](#5-enum--tone-mapped-badge) · lines 214–227
13
- - [6. Multi-value enum, clustered](#6-multi-value-enum-clustered) · lines 229–241
14
- - [7. Link as an outline button](#7-link-as-an-outline-button) · lines 243–264
15
- - [8. Date, relative](#8-date-relative) · lines 266–303
16
- - [9. Ordinal scale (bounded integer)](#9-ordinal-scale-bounded-integer) · lines 305–353
17
- - [Two variants the package draws: a step bar and a star](#two-variants-the-package-draws-a-step-bar-and-a-star) · lines 323–353
18
- - [10. Summary in the cell, detail in a popover](#10-summary-in-the-cell-detail-in-a-popover) · lines 355–405
19
- - [A second example: what a row depends on](#a-second-example-what-a-row-depends-on) · lines 373–405
20
- - [11. Number or currency — Unproven](#11-number-or-currency--unproven) · lines 407–447
21
- - [12. Boolean](#12-boolean) · lines 449–479
22
- - [13. Progress bar — Unproven](#13-progress-bar--unproven) · lines 481–517
23
- - [The compact form: `ProgressRing`](#the-compact-form-progressring) · lines 501–517
24
- - [14. Avatar and name — Unproven](#14-avatar-and-name--unproven) · lines 519–536
25
- - [15. Yes/no you flip in the row](#15-yesno-you-flip-in-the-row) · lines 538–647
26
- - [A variant: a pressed icon inside another value's cell](#a-variant-a-pressed-icon-inside-another-values-cell) · lines 564–647
27
- - [16. Title linking to the record's view](#16-title-linking-to-the-records-view) · lines 649–684
28
- - [17. Quick action on the view](#17-quick-action-on-the-view) · lines 686–759
29
- - [18. Editable value](#18-editable-value) · lines 761–1207
30
- - [A list of sub-items](#a-list-of-sub-items) · lines 1023–1108
31
- - [In a table's rows](#in-a-tables-rows) · lines 1110–1195
32
- - [A table of a few short columns: `width="content"`](#a-table-of-a-few-short-columns-widthcontent) · lines 1197–1207
33
- - [19. Stage circle for an ordered status](#19-stage-circle-for-an-ordered-status) · lines 1209–1275
34
- - [20. Count linking to the related records](#20-count-linking-to-the-related-records) · lines 1277–1341
35
- - [21. Rows grouped under headings, and a pill filter](#21-rows-grouped-under-headings-and-a-pill-filter) · lines 1343–1408
36
- - [22. A highlighted row, and rows that are done](#22-a-highlighted-row-and-rows-that-are-done) · lines 1410–1438
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
- - **Not built:** collapsing groups, grouping by more than one key.
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 32–60
7
- - [The three badge styles](#the-three-badge-styles) · lines 62–79
8
- - [Mapping an enum to tones](#mapping-an-enum-to-tones) · lines 81–110
9
- - [Glyphs and icons: 3:1](#glyphs-and-icons-31) · lines 112–125
10
- - [Logos and brand marks](#logos-and-brand-marks) · lines 127–171
11
- - [Categorical colour](#categorical-colour) · lines 173–243
12
- - [Typography](#typography) · lines 245–327
13
- - [Written text (notes)](#written-text-notes) · lines 276–327
14
- - [Density](#density) · lines 329–386
15
- - [Widget cards (hub pages)](#widget-cards-hub-pages) · lines 388–502
16
- - [Module cards (a hub's places)](#module-cards-a-hubs-places) · lines 504–562
17
- - [Notices](#notices) · lines 564–592
18
- - [A value over time](#a-value-over-time) · lines 594–627
19
- - [A list as a board](#a-list-as-a-board) · lines 629–680
20
- - [A list as cards](#a-list-as-cards) · lines 682–728
21
- - [Picking at random](#picking-at-random) · lines 730–761
22
- - [Filling in what's missing](#filling-in-whats-missing) · lines 763–813
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, and both at `compact`; its checks run axe and the token and
561
- glyph checks in both themes and at `large`, and measure the strip at a
562
- desktop and a phone width.
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 and `groupBy` headings
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"), from the group's
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;