@nxgt/material 3.37.0 → 3.39.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +1 -1
  3. package/dist/{async-slice-BNI_F0jz.js → async-slice-BtUexP2l.js} +2 -2
  4. package/dist/{button-CB4nSAMd.js → button-CCAYSsgh.js} +1 -1
  5. package/dist/charts.js +3 -3
  6. package/dist/{chip-B6m3yagf.js → chip-Corr637n.js} +1 -1
  7. package/dist/{code-editor-BdiufnpC.js → code-editor-B8_ny_jT.js} +4 -4
  8. package/dist/code-editor.js +4 -4
  9. package/dist/{code-language-items-v5Rnq425.js → code-language-items-1E5Qmfzb.js} +2 -2
  10. package/dist/components/components.css +2 -0
  11. package/dist/components/ui/collections/server-table/server-table-phone-row.css +189 -0
  12. package/dist/{components-mqgqmfvj.js → components-CTKlqX8g.js} +20201 -19716
  13. package/dist/components.js +15 -15
  14. package/dist/{dialog-DR9rACzY.js → dialog-DGlnqBbX.js} +1 -1
  15. package/dist/dnd.js +1 -1
  16. package/dist/{dropdown-menu-CpSnRXd-.js → dropdown-menu-B-buMTGc.js} +1 -1
  17. package/dist/editor.js +7 -7
  18. package/dist/{empty-state-DMr94lsF.js → empty-state-DKgPt475.js} +1 -1
  19. package/dist/hooks.js +2 -2
  20. package/dist/{i18n-provider-A4mI4vc1.js → i18n-provider-Bc7bdIRW.js} +1 -1
  21. package/dist/i18n.js +5 -5
  22. package/dist/{icon-badge-DXEBeS1y.js → icon-badge-BzLsPW3c.js} +1 -1
  23. package/dist/lib/components/ui/collections/data-table/data-table-list-slots.d.ts +12 -0
  24. package/dist/lib/components/ui/collections/data-table/internal.d.ts +1 -0
  25. package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-list-bar.d.ts +11 -1
  26. package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +9 -1
  27. package/dist/lib/components/ui/collections/filter/internal.d.ts +1 -0
  28. package/dist/lib/components/ui/collections/server-table/partials/server-table-band-classes.d.ts +10 -0
  29. package/dist/lib/components/ui/collections/server-table/partials/server-table-band.d.ts +13 -2
  30. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-buttons.d.ts +6 -2
  31. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk.d.ts +10 -0
  32. package/dist/lib/components/ui/collections/server-table/partials/server-table-phone-row.d.ts +17 -0
  33. package/dist/lib/components/ui/dates/date-field/date-field-content.d.ts +1 -1
  34. package/dist/lib/components/ui/dates/date-field/date-field-popover.d.ts +3 -1
  35. package/dist/lib/components/ui/dates/date-field/date-field-sheet-entry.d.ts +3 -2
  36. package/dist/lib/components/ui/dates/date-field/date-field-surface.d.ts +1 -1
  37. package/dist/lib/components/ui/dates/date-field/date-field-typed.d.ts +3 -3
  38. package/dist/lib/components/ui/dates/date-field/date-field.d.ts +23 -2
  39. package/dist/lib/components/ui/dates/date-field/use-typed-date.d.ts +3 -1
  40. package/dist/lib/components/ui/dates/date-field/use-zoned-content.d.ts +36 -0
  41. package/dist/lib/components/ui/dates/date-range-field/date-range-field.types.d.ts +1 -1
  42. package/dist/lib/components/ui/dates/picker-popover/hero-text.d.ts +1 -0
  43. package/dist/lib/components/ui/dates/picker-trigger/floating-clock.d.ts +7 -0
  44. package/dist/lib/components/ui/dates/picker-trigger/typed-day.d.ts +8 -0
  45. package/dist/lib/components/ui/dates/picker-trigger/use-zoned-date-props.d.ts +38 -0
  46. package/dist/lib/components/ui/dates/picker-trigger/use-zoned-value.d.ts +20 -0
  47. package/dist/lib/components/ui/dates/picker-trigger/zoned-bounds.d.ts +16 -0
  48. package/dist/lib/components/ui/dates/picker-trigger/zoned-matchers.d.ts +8 -0
  49. package/dist/lib/components/ui/dates/time-field/time-field.d.ts +14 -1
  50. package/dist/lib/components/ui/dates/time-field/time-form-field.d.ts +5 -1
  51. package/dist/lib/components/ui/dates/time-field/time-string-field.d.ts +3 -2
  52. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  53. package/dist/lib/components/ui/layout/app-shell/activity-column.d.ts +2 -2
  54. package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +1 -1
  55. package/dist/lib/components/ui/layout/app-shell/activity-layout.types.d.ts +18 -1
  56. package/dist/lib/components/ui/layout/app-shell/app-breadcrumb.d.ts +20 -4
  57. package/dist/lib/components/ui/layout/app-shell/header-context.d.ts +10 -0
  58. package/dist/lib/components/ui/layout/app-shell/header-title.d.ts +5 -1
  59. package/dist/lib/components/ui/layout/app-shell/page-title.d.ts +21 -2
  60. package/dist/lib/components/ui/primitives/segmented-input/segment-date-time.d.ts +13 -0
  61. package/dist/lib/components/ui/primitives/segmented-input/segment-defs.d.ts +6 -0
  62. package/dist/lib/components/ui/primitives/segmented-input/segment-floating.d.ts +11 -0
  63. package/dist/lib/components/ui/primitives/segmented-input/segment-part.d.ts +19 -0
  64. package/dist/lib/components/ui/selection/chip-group-field/chip-group-columns.d.ts +3 -0
  65. package/dist/lib/components/ui/selection/chip-group-field/chip-group-field.d.ts +28 -5
  66. package/dist/lib/components/ui/selection/chip-group-field/chip-group-field.types.d.ts +17 -0
  67. package/dist/lib/components/ui/selection/chip-group-field/partials/chip-group-option.d.ts +7 -3
  68. package/dist/lib/components/ui/selection/chip-group-field/partials/chip-group-sections.d.ts +13 -0
  69. package/dist/lib/components/ui/selection/choice-group/choice-key-target.d.ts +1 -1
  70. package/dist/lib/components/ui/selection/choice-group/choice-press.d.ts +2 -0
  71. package/dist/lib/components/ui/selection/choice-group/use-choice-group-field.d.ts +3 -1
  72. package/dist/lib/components/ui/selection/choice-group/use-choice-group.d.ts +2 -0
  73. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-events.utils.d.ts +11 -1
  74. package/dist/lib/i18n/resources/en/en.d.ts +2 -0
  75. package/dist/lib/i18n/resources/en/material.json.d.ts +3 -1
  76. package/dist/lib/i18n/resources/fr/fr.d.ts +2 -0
  77. package/dist/lib/i18n/resources/fr/material.json.d.ts +3 -1
  78. package/dist/lib/i18n/resources/resources.d.ts +4 -0
  79. package/dist/lib/i18n/translator.d.ts +1 -1
  80. package/dist/lib/lib/zoned-floating.d.ts +30 -0
  81. package/dist/lib/lib/zoned-instant.d.ts +16 -0
  82. package/dist/lib/lib/zoned.d.ts +11 -4
  83. package/dist/lib.js +1 -1
  84. package/dist/main.js +21 -21
  85. package/dist/map.js +4 -4
  86. package/dist/material.css +1 -1
  87. package/dist/pdf-reader.js +1 -1
  88. package/dist/print.js +2 -2
  89. package/dist/{public-BdJCVnGS.js → public-CNlIuQOj.js} +6 -6
  90. package/dist/{rich-code-block-Bwhfhu7J.js → rich-code-block-Jo0OLy0J.js} +4 -4
  91. package/dist/{sortable-provider-BshacF8v.js → sortable-provider-DgIo378H.js} +3 -2
  92. package/dist/{spinner-CKlDba5I.js → spinner-BTR8rVN9.js} +1 -1
  93. package/dist/{stored-language-CfQGHLq9.js → stored-language-Bbwcy-Pv.js} +6 -2
  94. package/dist/theme-picker.js +2 -2
  95. package/dist/{theme-toggle--Zcf5AM8.js → theme-toggle-DMZ_3HiG.js} +1 -1
  96. package/dist/{toolbar-items-B1W-uVHS.js → toolbar-items-D3SPTgXE.js} +2 -2
  97. package/dist/{translate-material-xDh10aWZ.js → translate-material-CRF13PON.js} +2 -2
  98. package/dist/{translator-BbOZntEF.js → translator-D7AVZCfQ.js} +1 -1
  99. package/dist/{use-chat-composer-C82hvxm4.js → use-chat-composer-D3kvp8Fw.js} +2310 -2184
  100. package/dist/{use-heading-spy-DXYCeegh.js → use-heading-spy-uyhOQV2O.js} +1 -1
  101. package/dist/{use-material-translation-BK2YYCY3.js → use-material-translation-kISeB-fY.js} +2 -2
  102. package/docs/components/data-table-filter-and-lists.md +55 -2
  103. package/docs/components/date-and-time.md +77 -3
  104. package/docs/components/navigation-and-layout.md +4 -2
  105. package/docs/components/selection-and-choice.md +67 -3
  106. package/docs/guides/app-shell.md +91 -2
  107. package/docs/guides/cards-and-tiles.md +5 -0
  108. package/docs/guides/forms.md +2 -2
  109. package/docs/guides/i18n.md +3 -2
  110. package/docs/roadmap.md +5 -1
  111. package/docs/troubleshooting.md +9 -1
  112. package/lib/components/components.css +2 -0
  113. package/lib/components/ui/collections/server-table/server-table-phone-row.css +189 -0
  114. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { r as e } from "./i18n-provider-A4mI4vc1.js";
1
+ import { r as e } from "./i18n-provider-Bc7bdIRW.js";
2
2
  import { _ as t, g as n, h as r, n as i, v as a, y as o } from "./number.utils-BG_eEt5g.js";
3
3
  import { useCallback as s, useEffect as c, useMemo as l, useRef as u } from "react";
4
4
  //#region ../../node_modules/date-fns/locale/fr/_lib/formatDistance.js
@@ -1,5 +1,5 @@
1
- import { r as e } from "./i18n-provider-A4mI4vc1.js";
2
- import { t } from "./translate-material-xDh10aWZ.js";
1
+ import { r as e } from "./i18n-provider-Bc7bdIRW.js";
2
+ import { t } from "./translate-material-CRF13PON.js";
3
3
  import { useCallback as n } from "react";
4
4
  //#region lib/i18n/use-material-translation.ts
5
5
  function r() {
@@ -924,7 +924,7 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
924
924
  | `actions` | `ReactNode` | — | the page's actions, at the band's end |
925
925
  | `bandDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | (the same prop as [`Filter`](#a-compact-band-for-a-list-inside-a-page-section)'s bar) `'compact'` halves the room above the band (16px to 8px) and under it (12px to 6px), for a list under a sticky app bar and a breadcrumb; the controls keep their size (the Filter's own `density` is the controls') |
926
926
  | `pageControls` | `'labelled' \| 'icon'` | `'labelled'` | the footer's Previous / Next: `'icon'` is icon-only, with a tooltip and the names "Previous page" / "Next page" (`DataTable`'s default is unchanged); on a coarse pointer the icon controls are 44x44 boxes (still painted 32px) |
927
- | `bandActions` | `'stacked' \| 'inline'` | `'stacked'` | with `filter`, where `actions` sit below 768px: `'stacked'` gives them a row above the search; `'inline'` keeps them on the search's row (the search takes the room left), for a lone icon action such as a "+". An action wider than 3.5rem wraps under the search. From 768px both are the same |
927
+ | `bandActions` | `'stacked' \| 'inline'` | `'stacked'` | with `filter`, where `actions` sit below 768px: `'stacked'` gives them a row above the search; `'inline'` keeps them on the search's row (the search takes the room left), for a lone icon action such as a "+". An action wider than 3.5rem wraps under the search. From 768px both are the same. With a cards or tiles list on a phone, neither applies: [the two-row header](#the-phone-header-of-a-list-in-cards-or-tiles) puts the actions on its second row |
928
928
  | `filterRef` | `Ref<ServerTableFilterHandle>` | — | with `filter`: `filterRef.current.focus()` focuses the Filter's first control, the search, else the first quick filter, "Add filter" or "Filters (N)" the width shows |
929
929
  | `sorting` | `createSorting(…)` | — | headers sorted from the URL |
930
930
  | `filters` | `tableFilters(model)` | — | without `filter`: "nothing matches" and its clear |
@@ -944,6 +944,59 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
944
944
  <ServerTable {...props} filter={{ model }} bandActions="inline" actions={<IconButton aria-label="New" tooltip="New"><Icon name="plus" /></IconButton>} />
945
945
  ```
946
946
 
947
+ #### The phone header of a list in cards or tiles
948
+
949
+ Below 768px, with `filter`, `actions` and a list in `cards` or `tiles` (the
950
+ `layout`, or the phone side of `{ phone, wide }`), the header takes two rows,
951
+ not three:
952
+
953
+ 1. the search;
954
+ 2. at the start, the list's labelled "Select all" checkbox (its name stays
955
+ "Select all on this page"); at the end, the sort menu, "Filters (N)" and
956
+ the page's `actions`.
957
+
958
+ While rows are selected, row 2 becomes the selection bar: "N rows selected"
959
+ (`material.table.pagination.selected`) and "Clear selection" take the label's
960
+ place, and the `bulkActions` take the place of the sort, "Filters (N)" and the
961
+ actions, which come back once the selection is empty. The header stays at two
962
+ rows (the bulk bar is not drawn under it). With the declarative list form of
963
+ `bulkActions`, each action keeps its icon, a short label and its count ("Publish
964
+ (2)", named "Publish 2 rows"), hidden at 0 rows; the buttons wrap onto a second
965
+ line rather than scroll, and past the third action the rest go into a "More"
966
+ menu (`material.server-table.bulk-more`), each item keeping its count. From
967
+ 768px the buttons read "Label (n)" as before. When the focused button
968
+ disappears the focus goes to the next one, else to "Select all". The function
969
+ form (`bulkActions={(ids) => node}`) is drawn whole in the row, so keep it to
970
+ one or two compact buttons; the footer's polite status still tells the selection once. The cards'
971
+ own list bar row is gone in this layout; the names, the sort menu and the
972
+ Filters sheet are the same. A list that cannot be selected has no checkbox
973
+ (row 2 starts with the sort). The table layout, a list without `filter` or
974
+ without `actions`, `variant: 'facets'` and every width from 768px are
975
+ unchanged, and `bandActions` has no effect while the second row is there.
976
+
977
+ The row is narrow, so it compacts to the band's width, never wrapping to a
978
+ third row: below 30rem the sort shows its icon and arrow (its name, "Sort: Name,
979
+ Ascending", is unchanged); "Filters (N)" keeps its icon and count (and its
980
+ name) while rows are selected, and on a band narrower than 19rem; the count
981
+ may take two lines. A button of `actions` that has an icon (a lucide glyph or
982
+ an `Icon`) is drawn as the icon alone: its text stays its accessible name and,
983
+ unless you gave it a `tooltip`, becomes its `title`; an `IconButton` keeps its
984
+ own name and tooltip, and a button without an icon keeps its text. Only a button with a single icon is compacted; put an icon
985
+ in the primary action to get the compact form. With `filter.sheetTrigger: false`
986
+ the second row has no "Filters (N)": the one you place in `actions` goes there. Every control is a 44px box on a
987
+ coarse pointer.
988
+
989
+ ```tsx
990
+ <ServerTable
991
+ {...props}
992
+ filter={{ model }}
993
+ layout={{ phone: 'tiles', wide: 'table' }}
994
+ renderTile={renderTile}
995
+ bulkActions={(ids) => <Button>Archive</Button>}
996
+ actions={<Button><Icon name="plus" />New</Button>}
997
+ />
998
+ ```
999
+
947
1000
  **Where the focus goes.** After "Clear" on an empty page, or the last chip
948
1001
  removed, the focus returns to the Filter's search; a bar without one falls to
949
1002
  its first shown control (a quick filter, "Add filter", or "Filters (N)" on a
@@ -990,7 +1043,7 @@ page's `title`.
990
1043
 
991
1044
  `ServerTable` uses `DataTable`'s `focusable`, `focusRef` (the table, or the list of cards or tiles), `announceLoading`, `announceEmpty`, `filtered` and `pagination.live` (see [DataTable](#datatable)).
992
1045
 
993
- **Storybook:** `Data table, filter & lists/ServerTable` (`Error No Loading`, `Error Retry`), `…/ServerTable/Band and empty page`, `…/ServerTable/Phone`, `…/ServerTable/Cards`, `…/ServerTable/Tiles`, `…/ServerTable/Own controls` (`Past The Last Page`, `Slow Router Keeps Search`), `…/ServerTable/Locked rows`, `…/ServerTable/Compact band and icon paging` · **Vue:** `ServerTable`
1046
+ **Storybook:** `Data table, filter & lists/ServerTable` (`Error No Loading`, `Error Retry`), `…/ServerTable/Band and empty page`, `…/ServerTable/Phone`, `…/ServerTable/Phone header`, `…/ServerTable/Cards`, `…/ServerTable/Tiles`, `…/ServerTable/Own controls` (`Past The Last Page`, `Slow Router Keeps Search`), `…/ServerTable/Locked rows`, `…/ServerTable/Compact band and icon paging` · **Vue:** `ServerTable`
994
1047
 
995
1048
  ---
996
1049
 
@@ -177,7 +177,9 @@ export function Delivery() {
177
177
  | `onValueChange` | `(value?: Date) => void` | — | `undefined` when the day is unselected |
178
178
  | `entry` | `'typed'` | — | deprecated, no effect: the date is always typed in segments, with a button that opens the calendar (`'button'`, the 2.x trigger, was removed in 3.0) |
179
179
  | `clearable` | `boolean` | `!required` | a ✕ (named "Clear") that empties the typed field; shown only while it holds a date and is not `disabled` |
180
- | `showTimePicker` | `boolean` | — | adds a `TimePicker` and an OK button; the popover stays open. The time is picked there, not typed: the tiles, beside the calendar from 768px and under it in a sheet, with a Cancel button |
180
+ | `showTimePicker` | `boolean` | — | adds the time of day (a day click in the calendar keeps it): its hours and minutes (and AM / PM on a 12-hour clock) are typed in segments after the date, and a `TimePicker` with an OK button and a Cancel button sits beside the calendar from 768px and under it in a sheet; the popover stays open. Typed and picked are one value. The field has a value once the date and the time are both complete (see [Time zones and typed time](#time-zones-and-typed-time)) |
181
+ | `hourCycle` | `12 \| 24` | the language's | the clock of the typed time and of the picker (English 12, French 24); with `showTimePicker` |
182
+ | `timeZone` | IANA name (`'Europe/Paris'`) | the machine's zone | the field reads and writes the wall clock of that zone; `value` and `onValueChange` stay the true instant ([below](#time-zones-and-typed-time)) |
181
183
  | `placeholder`, `label`, `helperText`, `error`, `required` | | — | `label` and `helperText` sit beside the field; `error` and `required` its state. `placeholder` names a field with no label |
182
184
  | `disabled` | `boolean` | — | disables the field: no segment takes a key and the calendar cannot be opened |
183
185
  | `disabledDays` | react-day-picker's `Matcher \| Matcher[]` | — | days that cannot be picked (the calendar's `disabled`), e.g. `{ dayOfWeek: [0] }` |
@@ -257,6 +259,77 @@ resized, a phone turned) keeps the field as it is: a partial entry stays,
257
259
  and so do the "Open calendar" button and its focus; an open picker keeps
258
260
  the focus when it moves between the popover and the sheet.
259
261
 
262
+ ### Time zones and typed time
263
+
264
+ With `showTimePicker` the trigger reads `mm / dd / yyyy 09 : 30 AM` (the
265
+ locale's order and clock; `hourCycle` forces one). The time is typed like the
266
+ date: digits move on once a segment is full, a separator or an arrow moves
267
+ between segments, ArrowUp and ArrowDown step the focused one (wrapping), "a"
268
+ or "p" sets AM / PM, and a pasted "07/01/2026 9:30 PM" fills all of them. The
269
+ segments are in one `group` named by the label; "Hours", "Minutes" and
270
+ "AM / PM" are `spinbutton`s like the date's. On a phone, where the segments
271
+ are 44px wide, the time wraps whole under the date when the two do not fit.
272
+
273
+ A value needs the date and the time both complete: while one segment is empty
274
+ the field emits `undefined` (a partial entry stays on screen), so a form's
275
+ `required` rule refuses it, says so in the helper text and marks every segment
276
+ `aria-invalid`, exactly as for a half-typed date. The calendar, the tiles and
277
+ the segments edit the same value: a day picked in the calendar keeps the time
278
+ of day, and a time typed shows in the tiles when the picker opens. (Before,
279
+ with `showTimePicker`, a day picked in the calendar put the time back to
280
+ midnight.) On the phone's sheet the pencil's large field takes the time too
281
+ and is named `material.dates.type-date-time`.
282
+
283
+ `timeZone` (an IANA name such as `Europe/Paris`) makes the field a form for the
284
+ wall clock of that zone, whatever zone the browser runs in:
285
+
286
+ | | |
287
+ | --- | --- |
288
+ | `value`, `onValueChange` | the true instant, a `Date`, as without `timeZone` |
289
+ | Shown | the instant's wall clock in the zone: `2026-07-01T07:30:00Z` is `09:30` for `Europe/Paris` and `03:30` for `America/Toronto` |
290
+ | Typed or picked | read as the zone's wall clock: `09:30` typed with `Europe/Paris` emits `07:30Z` in summer and `08:30Z` in winter |
291
+ | `minDate`, `maxDate` | instants, compared as the days they fall on in the zone |
292
+ | `disablePast`, `disableFuture`, today's mark | today in the zone, not the machine's |
293
+ | `disabledDays` | `Date`s in a matcher are the days they fall on in the zone; a function sees the zone's days |
294
+ | Unknown name | read as `UTC` (as `ZonedTime` does) |
295
+ | No `timeZone` | the machine's zone, as before |
296
+
297
+ Daylight-saving changes have one rule each, the one of Temporal's
298
+ `compatible` mode:
299
+
300
+ - **A time that does not exist** (the clocks go forward: 02:30 on 29 March
301
+ 2026 in `Europe/Paris`) is read with the offset in force before the gap, so
302
+ it lands the length of the gap later: 03:30. The field shows 03:30 right
303
+ away.
304
+ - **A time that happens twice** (the clocks go back: 02:30 on 25 October 2026
305
+ in `Europe/Paris`) is the first occurrence, `00:30Z`. If the value being
306
+ edited already is the second occurrence (`01:30Z`), editing its minutes
307
+ keeps the second one, so the time never jumps an hour back under the typing.
308
+
309
+ The server renders the wall clock from `Intl` with the explicit zone, never
310
+ from the machine, so the HTML hydrates identically whatever zone the server
311
+ and the browser run in. Inside, the wall clock is a floating date (read
312
+ through UTC), so a time the browser's own clocks skip (02:30 Paris on the
313
+ day Toronto springs forward) is shown, typed and picked as it is. A
314
+ `disabledDays` matcher that holds a `Date` (`Date`, `{ before }`,
315
+ `{ after }`, `{ from, to }`, and lists of them) names the days those instants
316
+ fall on in the zone; a function matcher is given the zone's days. With no
317
+ value, a time picked first is dated today in the zone and must pass the
318
+ bounds (`disablePast`...).
319
+
320
+ ```tsx
321
+ <DateField
322
+ label="Starts"
323
+ timeZone="Europe/Paris"
324
+ showTimePicker
325
+ value={startsAt}
326
+ onValueChange={setStartsAt}
327
+ disablePast
328
+ />
329
+ ```
330
+
331
+ `DateFormField` takes the same props. `DateRangeField` has no `timeZone` (react-day-picker's own `timeZone` prop is no longer accepted by `DateField` or `DateRangeField`: use `DateField`'s `timeZone`, or drop it from a range).
332
+
260
333
  ### Pickers' surfaces
261
334
 
262
335
  The popover has 28px corners, a soft lift and 12px of
@@ -277,7 +350,7 @@ Storybook: `Date & time/Pickers surfaces/Phone sheet`, `/Popover` and `/Cancel`.
277
350
  **Emits:** `Date | undefined`.
278
351
  **Form wrapper:** `DateFormField` — `control` and `name`, see
279
352
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
280
- **Storybook:** `Date & time/DateField`, `Date & time/DateField/Typed entry`, `Date & time/DateField/Phone sheet` · **Vue:** `DateField`, `DateFormField` (`disabled-days`, same rules; the 2.x trigger only, no typed entry)
353
+ **Storybook:** `Date & time/DateField`, `Date & time/DateField/Typed entry`, `Date & time/DateField/Time zone`, `Date & time/DateField/Phone sheet` · **Vue:** `DateField`, `DateFormField` (`disabled-days`, same rules; the 2.x trigger only, no typed entry)
281
354
 
282
355
  ---
283
356
 
@@ -587,6 +660,7 @@ export function OpensAt() {
587
660
  | `value` | `Date` | — | only the hours and minutes are shown |
588
661
  | `onValueChange` | `(value?: Date) => void` | — | on every hour, minute or AM / PM change |
589
662
  | `size` | `'default' \| 'sm'` | `'default'` | `'sm'` is the compact 36px box (the field radius is unchanged) |
663
+ | `timeZone` | IANA name (`'Europe/Paris'`) | the machine's zone | the segments and the picker show the value's wall clock in that zone and what is typed or picked is read in it; `value` and `onValueChange` stay the true instant. The time lands on the value's day in the zone (today in the zone with no value). The same rules as `DateField`'s ([Time zones and typed time](#time-zones-and-typed-time)): a time skipped by the clocks moves to the end of the gap, a repeated one is the first, unless the value holds the second. `TimeFormField` takes it too; with `asString` the `'HH:mm'` string is the wall clock itself and `timeZone` has no effect, and `TimeStringField` has none |
590
664
  | `variant`, `hourCycle`, `minuteStep` | | `'tiles'`, the language's, `5` | passed to the `TimePicker` ([below](#timepicker)); `hourCycle` sets the segments' clock too |
591
665
  | `entry` | `'typed'` | — | deprecated, no effect: the time is always typed in segments, with a button that opens the picker (`'button'`, the 2.x trigger, was removed in 3.0) |
592
666
  | `clearable` | `boolean` | `!required` | a ✕ (named "Clear") that empties the typed field; shown only while it holds a time and is not `disabled` |
@@ -647,7 +721,7 @@ unchanged.
647
721
  state.
648
722
  **Form wrapper:** `TimeFormField` — `control` and `name` (plus `asString`), see
649
723
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
650
- **Storybook:** `Date & time/TimeField`, `Date & time/TimeField/Typed entry`, `Date & time/TimeField/Phone sheet` · **Vue:** `TimeField`, `TimeFormField` (the 2.x trigger only, no typed entry)
724
+ **Storybook:** `Date & time/TimeField`, `Date & time/TimeField/Typed entry`, `Date & time/TimeField/Time zone`, `Date & time/TimeField/Phone sheet` · **Vue:** `TimeField`, `TimeFormField` (the 2.x trigger only, no typed entry)
651
725
 
652
726
  ---
653
727
 
@@ -7,7 +7,7 @@ documentation-site shell. Everything is imported from
7
7
 
8
8
  | | Component | What it does |
9
9
  | --- | --- | --- |
10
- | <img src="../previews/navigation-layout-appshell.webp" width="96" alt=""> | [ActivityLayout](#activitylayout) | App shell: sidebar, header, breadcrumbs, titled page; a route's `handle.title` is the page's `h1` in the app bar (it labels `main`; `usePageTitleFocus()` focuses it after a navigation), `breadcrumbRoot` a text root crumb, `breadcrumb={false}` no trail, `context` the tenant line atop the phone's menu sheet; `menuOpen` / `onMenuOpenChange` control the phone's menu sheet, which closes itself on a followed link or any location change |
10
+ | <img src="../previews/navigation-layout-appshell.webp" width="96" alt=""> | [ActivityLayout](#activitylayout) | App shell: sidebar, header, breadcrumbs, titled page; a route's `handle.title` is the page's `h1` in the app bar (it labels `main`; `usePageTitleFocus()` focuses it after a navigation), `handle.titleAfter` / `titleAfter` a node right after it, outside the `h1` (a record's `Switcher`), `headerContext` a small tenant line after it from md up, `breadcrumbRoot` a text root crumb or (`icon: true`) the home icon with its own target and name, `breadcrumb={false}` no trail, `context` the tenant line atop the phone's menu sheet; `menuOpen` / `onMenuOpenChange` control the phone's menu sheet, which closes itself on a followed link or any location change |
11
11
  | <img src="../previews/navigation-layout-accountmenu.webp" width="96" alt=""> | [AccountMenu](#accountmenu) | The app bar's avatar menu: the account, the app's links, Sign out |
12
12
  | <img src="../previews/navigation-layout-authcard.webp" width="96" alt=""> | [AuthCard](#authcard) | Signed-out page frame: one `main`, a centred card, its title the `h1` |
13
13
  | <img src="../previews/navigation-layout-sidebar.webp" width="96" alt=""> | [Sidebar](#sidebar) | Collapsible navigation sidebar |
@@ -73,8 +73,10 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
73
73
  | `avatar` | `ReactNode` | — | an image URL or a node, in the header |
74
74
  | `headerTitle` | `ReactNode` | — | the header's leading title (the context: an organisation's name), after the phone's menu button, truncated when long; when it is cut off, its full text is the `title` tooltip (#629) |
75
75
  | `handle.title` | `ReactNode \| (match: UIMatch) => ReactNode` | — | on a route: the page's title in the app bar, its one `h1`, in the first server HTML; `ActivityContent` leaves its own title out while it is set (see the [app shell guide](../guides/app-shell.md#the-pages-title-in-the-app-bar)) |
76
+ | `titleAfter` / `handle.titleAfter` | `ReactNode \| (match: UIMatch) => ReactNode` | — | a node right after the page's title in the app bar, outside its `h1` (a record's `Switcher`); stays on screen on a phone, the title truncates first; the prop wins over the handle (see the [app shell guide](../guides/app-shell.md#a-slot-after-the-title)) |
77
+ | `headerContext` | `ReactNode` | — | a small muted context line (the tenant) after the page's title and its slot, from `md` up, outside the `h1`, truncated with its full text as the tooltip; hidden on a phone, where `context` fills the menu sheet (see the [app shell guide](../guides/app-shell.md#a-context-line-after-the-title)) |
76
78
  | `breadcrumb` | `boolean` | `true` | `false` hides the breadcrumb trail (an error page); it is also hidden when every matched `handle.breadcrumb` renders `null`; see the [app shell guide](../guides/app-shell.md#breadcrumbs) |
77
- | `breadcrumbRoot` | `{ label: ReactNode; to: string }` | — | the breadcrumb's first crumb, a text link in place of the home icon (`AppBreadcrumb`'s `root`); see the [app shell guide](../guides/app-shell.md#breadcrumbs) |
79
+ | `breadcrumbRoot` | `{ label: ReactNode; to: string }` or `{ label: string; to: string; icon: true }` | — | the breadcrumb's first crumb: a text link in place of the home icon, or with `icon: true` the home icon alone linked to `to`, named and tooltipped `label` (`AppBreadcrumb`'s `root`); see the [app shell guide](../guides/app-shell.md#breadcrumbs) |
78
80
  | `headerActions` | `ReactNode` | — | header, before the avatar |
79
81
  | `title`, `subtitle`, `actions` | `ReactNode` | — | `ActivityContent`'s header card; `title` is left out while a route's `handle.title` puts the page's title in the app bar |
80
82
  | `header` / `hideHeader` | `ReactNode` / `boolean` | — | `ActivityContent`: replace or drop the header card |
@@ -788,20 +788,84 @@ export function Topics() {
788
788
  | `value` / `onValueChange` | `string \| null` or `string[]` | — | the chosen value, by `type` |
789
789
  | `deselectable` | `boolean` | `false` | `single` only: pressing the chosen chip clears it |
790
790
  | `max` / `min` | `number` | — | `multiple` only: `max` blocks the other chips once reached and speaks "{count} of {max} chosen"; `min` is a hint only |
791
- | `options` | `{ value; label; icon?; count?; description?; disabled?; disabledReason? }[]` | — | the chips; `count`: see [option counts](#option-counts) |
791
+ | `options` | `{ value; label; icon?; count?; description?; disabled?; disabledReason? }[]` | — | the chips, in one flow; `count`: see [option counts](#option-counts) |
792
+ | `groups` | `{ label: string; options }[]` | — | titled sections instead of `options` (it wins when both are given): see [slot picker](#slot-picker-columns-groups-and-reasons) |
793
+ | `columns` | `number \| { base?; sm?; md?; lg? }` | — | equal-width columns (1 to 12; one count per breakpoint, each holding to the next); Up and Down then move by row. Unset, the chips flow and wrap |
794
+ | `showDisabledReason` | `boolean` | `true` | prints a disabled option's `disabledReason` under its chip; `false` leaves it to the tooltip and the accessible description |
792
795
  | `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | the chip's size: 28px, 24px, or `lg`, a 44px box at every pointer (a coarse pointer already gets a 44px box at any size) |
793
796
  | `name` | `string` | — | put on every chip (`ChipGroupFormField` passes its field `name`), so `document.getElementsByName(name)` finds the group's chips in order |
794
797
  | `label`, `helperText`, `error`, `required`, `disabled`, `id` | | — | the question and its help or error text, the required mark, disabling every chip, the group's `id` |
795
798
  | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | wrapping row or a column (each chip as wide as its label); in single mode it sets the `radiogroup`'s `aria-orientation` (ARIA allows none on a `group`), and every arrow key moves in both axes either way (the chips wrap) |
796
799
 
800
+ ### Slot picker: columns, groups and reasons
801
+
802
+ A day's slots, some taken: an even grid, a heading per period, and the reason
803
+ a slot is gone printed where a touch user can read it.
804
+
805
+ ```tsx
806
+ import { ChipGroupField } from '@nxgt/material/components';
807
+ import { useState } from 'react';
808
+
809
+ const groups = [
810
+ {
811
+ label: 'Morning',
812
+ options: [
813
+ { value: '09:00', label: '09:00' },
814
+ { value: '09:30', label: '09:30', disabled: true, disabledReason: 'Taken' },
815
+ { value: '10:00', label: '10:00' },
816
+ ],
817
+ },
818
+ {
819
+ label: 'Afternoon',
820
+ options: [
821
+ { value: '13:00', label: '13:00' },
822
+ { value: '13:30', label: '13:30' },
823
+ ],
824
+ },
825
+ ];
826
+
827
+ export function Slots() {
828
+ const [slot, setSlot] = useState<string | null>(null);
829
+ return (
830
+ <ChipGroupField
831
+ label="Time"
832
+ size="lg"
833
+ groups={groups}
834
+ columns={{ base: 3, lg: 6 }}
835
+ value={slot}
836
+ onValueChange={setSlot}
837
+ />
838
+ );
839
+ }
840
+ ```
841
+
842
+ - **Disabled reason.** The reason is a small muted line under the chip (not
843
+ only a tooltip), and the chip points to it with `aria-describedby`, so a
844
+ screen reader says "09:30, Taken". The chip stays `aria-disabled`, not
845
+ `disabled`: arrows still land on it and a press does nothing. The "up to N"
846
+ reason of a reached `max` stays a hidden text.
847
+ - **Columns.** The cells are equal (`minmax(0, 1fr)`), each chip fills its
848
+ cell, and the classes are static, so the server markup holds no inline
849
+ style. Unlike `ChoiceCardGroup`'s `columns` (a maximum, as many as fit), this
850
+ is the exact count at each width.
851
+ - **Groups.** Each section is a `role="group"` named by its visible heading
852
+ (`aria-labelledby`), inside the one `radiogroup` (or `group`). Chips keep one
853
+ index across sections: Left and Right run through all of them in reading
854
+ order, Up and Down move to the nearest chip on the row above or below,
855
+ crossing from the last row of Morning to the first of Afternoon, and Home
856
+ and End go to the first and last chip. With `columns` or `groups`, the
857
+ vertical arrows follow the layout; a plain flow keeps its linear order.
858
+ - A 44x44 box on a coarse pointer, and the grid follows `dir="rtl"`.
859
+
797
860
  **Accessibility:** `role="radiogroup"` / `radio` (single) or `role="group"` /
798
861
  `checkbox` (multiple), named by `label`. Arrows move the focus (and choose, in
799
862
  single mode), Home and End jump, and the arrows follow `dir="rtl"`; Space toggles in multiple
800
863
  mode; Enter does nothing, so it never submits a surrounding form. One chip holds
801
864
  the tab stop. A disabled option, or an unchosen one once `max` is reached, is
802
865
  `aria-disabled`: it stays focusable and an arrow stop, a press does nothing,
803
- and its `disabledReason` is its tooltip and its accessible description (as is
804
- `description`). The group's label shows the required mark when `required`; in
866
+ and its `disabledReason` is printed under the chip (unless
867
+ `showDisabledReason={false}`, then its tooltip) and is its accessible description
868
+ (as is `description`). The group's label shows the required mark when `required`; in
805
869
  multiple mode, where a `group` cannot carry `aria-required`, the group is
806
870
  described by "This field is required." instead. On a touch screen (coarse
807
871
  pointer) each chip is a real 44x44 box, 28px drawn with a mouse, and the
@@ -200,8 +200,10 @@ partitionMenu(items); // { content, footer }
200
200
  | `avatar` | `ReactNode` | — | End of the header. A `string` is used as the image URL of an `Avatar`; anything else is rendered as-is |
201
201
  | `linkComponent` | `AppShellLinkComponent` | React Router `Link` | The link each menu entry renders, given `href` (see below) |
202
202
  | `headerTitle` | `ReactNode` | — | The header's leading title: the context the app is in (the active organisation's name), right after the phone's menu button, truncated when long; when it is cut off, its full text is the `title` tooltip (#629) |
203
+ | `titleAfter` | `ReactNode` | — | A node right after the page's title in the app bar, outside its `h1` (a record's `Switcher`); wins over a route's `handle.titleAfter` (see [A slot after the title](#a-slot-after-the-title)) |
204
+ | `headerContext` | `ReactNode` | — | A small muted context line (the tenant's name) after the page's title and its slot, from `md` up: outside the `h1`, truncated, its full text the tooltip once cut off. Not shown on a phone, where the menu sheet's `context` carries it (see [A context line](#a-context-line-after-the-title)) |
203
205
  | `breadcrumb` | `boolean` | `true` | `false` hides the breadcrumb trail, for an error page whose matched routes would show a misleading one (see [Breadcrumbs](#breadcrumbs)) |
204
- | `breadcrumbRoot` | `AppBreadcrumbRoot` (`{ label: ReactNode; to: string }`) | — | The breadcrumb's first crumb, in place of the home icon: a text link to `to` (see [Breadcrumbs](#breadcrumbs)) |
206
+ | `breadcrumbRoot` | `AppBreadcrumbRoot` (`{ label: ReactNode; to: string }`, or `{ label: string; to: string; icon: true }`) | — | The breadcrumb's first crumb, in place of the default home icon: a text link to `to`, or with `icon: true` the home icon alone, linked to `to` and named `label` (see [Breadcrumbs](#breadcrumbs)) |
205
207
  | `headerActions` | `ReactNode` | — | In the header, before the avatar: notifications, help, `ThemeToggle` |
206
208
  | `children` | `ReactNode` | — | The routed content, usually `<Outlet />` |
207
209
  | `contentAs` | `'main' \| 'div'` | `'main'` | The content region's element: the page's `<main>` landmark, or a `div` when your app renders its own `<main>` |
@@ -377,10 +379,11 @@ export const invoiceRoute: RouteObject = {
377
379
  define none), truncated with an ellipsis; when it is cut off, its full text
378
380
  is the `title` tooltip, as for `headerTitle` (#629), and its accessible
379
381
  name stays the text itself. It sits after `headerTitle`, which gives it room.
380
- - **Typing it.** `AppShellHandle<Data>` (`{ breadcrumb?, title? }`) and
382
+ - **Typing it.** `AppShellHandle<Data>` (`{ breadcrumb?, title?, titleAfter? }`) and
381
383
  `PageTitle<Data>` are exported for a route's `handle`; `Data` types the
382
384
  match's `loaderData`. A custom `ActivityContent` `header` is
383
385
  yours: keep an `h1` out of it when the route sets `handle.title`.
386
+ - **A slot after the title** (`titleAfter`) and a tenant line (`headerContext`) go in the same row, see [below](#a-slot-after-the-title).
384
387
  - **`ActivityContent` leaves its own title out** while the bar has one, so the
385
388
  page has a single `h1`, whatever its `titleAs` (the bar already shows the
386
389
  title); its `subtitle` and `actions` stay, in the header
@@ -421,6 +424,72 @@ function FocusTitleOnNavigation() {
421
424
  // <ActivityLayout items={items}><FocusTitleOnNavigation /><Outlet /></ActivityLayout>
422
425
  ```
423
426
 
427
+ ### A slot after the title
428
+
429
+ `handle.titleAfter` (or the `titleAfter` prop of `ActivityLayout`) renders a node
430
+ right after the page's title in the app bar: a record page puts its `Switcher`
431
+ there, where the user reads the record's name. It takes the same forms as
432
+ `handle.title`: a node, or a function of the route's match, the deepest matched
433
+ route that declares one winning. The `titleAfter` prop wins over the handle, and
434
+ neither shows while the bar has no page title.
435
+
436
+ ```tsx
437
+ import type { AppShellHandle } from '@nxgt/material/components';
438
+ import { Switcher } from '@nxgt/material/components';
439
+
440
+ export const handle = {
441
+ title: 'Project A',
442
+ titleAfter: (
443
+ <Switcher
444
+ variant="expressive"
445
+ label="Project"
446
+ value="a"
447
+ options={projects}
448
+ getHref={(item) => `/projects/${item.id}`}
449
+ />
450
+ ),
451
+ } satisfies AppShellHandle;
452
+ ```
453
+
454
+ - **It is outside the `h1`**: a sibling in the same row
455
+ (`[data-slot=app-header-title-after]`), vertically centred with the title, so
456
+ the heading's accessible name stays the plain title.
457
+ - **On a phone it stays on screen** and shares the row with the title: the
458
+ title truncates first (ellipsis), the slot never shrinks.
459
+ - **Nothing changes without it**: the markup of a bar with no slot is the same
460
+ as before, and the slot is read from the matches like the title, so it is in
461
+ the server HTML and does not flash on a route change.
462
+
463
+ ### A context line after the title
464
+
465
+ A multi-tenant app keeps the current tenant visible on a desktop screen with
466
+ `headerContext`: a small, muted line after the page's title (and its
467
+ `titleAfter` slot) in the app bar.
468
+
469
+ ```tsx
470
+ <ActivityLayout
471
+ items={items}
472
+ context={tenant.name} // the phone's menu sheet
473
+ headerContext={tenant.name} // the app bar, from md up
474
+ >
475
+ <Outlet />
476
+ </ActivityLayout>
477
+ ```
478
+
479
+ - **It is outside the `h1`** (`[data-slot=app-header-context]`), so the
480
+ heading's name stays the page's title; it is truncated (at most 14rem), and
481
+ its full text is the `title` tooltip once it is cut off (#629).
482
+ - **From `md` up only.** On a phone it is hidden: the menu sheet's `context`
483
+ line covers it. The two props are independent, so setting `context` alone
484
+ adds nothing to the bar, and setting both never shows the name twice on one
485
+ screen.
486
+ - **The title truncates first**: with a long title, the slot and the line
487
+ together, the title gives way; the slot stays whole and the line keeps its cap.
488
+ - **It needs a page title**: while the bar shows none (no `handle.title`),
489
+ `headerTitle` is the context line and `headerContext` is ignored.
490
+
491
+ ### The account menu
492
+
424
493
  `AccountMenu` is the `avatar` most apps want: an avatar button named
425
494
  "Account menu, Ada Lovelace" (`material.account-menu.trigger`) that opens a
426
495
  menu aligned to its end, headed by the avatar, the name and `secondary`, then
@@ -1010,6 +1079,26 @@ crumbs; `label` is a node, already translated.
1010
1079
  </ActivityLayout>
1011
1080
  ```
1012
1081
 
1082
+ When a long tenant name would crowd the trail, keep the house icon alone with
1083
+ `icon: true`: it links to `to`, and `label` (a string) is its accessible name
1084
+ and its tooltip, shown on hover and on keyboard focus. It keeps the home
1085
+ icon's 44px target on a touch screen and its focus ring. The default icon
1086
+ (no `breadcrumbRoot`) shows the `material.app-shell.home` name as a tooltip
1087
+ too, and still links to the router's root.
1088
+
1089
+ ```tsx
1090
+ <ActivityLayout
1091
+ items={items}
1092
+ breadcrumbRoot={{
1093
+ label: `Home \u2014 ${school.name}`,
1094
+ to: `/schools/${school.slug}`,
1095
+ icon: true,
1096
+ }}
1097
+ >
1098
+ <Outlet />
1099
+ </ActivityLayout>
1100
+ ```
1101
+
1013
1102
  ```tsx
1014
1103
  import type { RouteObject, UIMatch } from 'react-router';
1015
1104
 
@@ -145,6 +145,11 @@ Clear shrinks to an icon button; its label stays its accessible name, and it has
145
145
  a tooltip. Do not add your own in `actions`: that one is the bulk bar's.
146
146
  Once a bulk action clears the selection and the focus is lost, the list takes it.
147
147
 
148
+ Inside a `ServerTable` with `filter` and `actions`, below 768px, the bar has no
149
+ row of its own: its "Select all" (with a short visible label, "Select all"),
150
+ the count and "Clear selection", and the sort move to the table's second header
151
+ row, beside "Filters (N)" and the actions ([ServerTable](../components/data-table-filter-and-lists.md#the-phone-header-of-a-list-in-cards-or-tiles)).
152
+
148
153
  ```tsx
149
154
  const { columnHelper, predefinedColumns } = useColumnHelpers<Person>();
150
155
  const columns = [predefinedColumns().select, columnHelper.accessor('name', { header: 'Name' })];
@@ -508,9 +508,9 @@ or fails validation.
508
508
 
509
509
  | Field · wrapper | Form value | Empty default |
510
510
  | --- | --- | --- |
511
- | `DateField` · `DateFormField` | `Date \| undefined` (not an ISO string) | `undefined` |
511
+ | `DateField` · `DateFormField` | `Date \| undefined` (not an ISO string). With `timeZone`, still the true instant (the field shows and edits that zone's wall clock); with `showTimePicker`, `undefined` until the date and the time are both complete | `undefined` |
512
512
  | `DateRangeField` · `DateRangeFormField` | `DateRange \| undefined`: `{ from: Date \| undefined; to?: Date }` | `undefined` |
513
- | `TimeField` · `TimeFormField` | `Date \| undefined`. With `asString`: `'HH:mm'`. While empty it shows its `placeholder` | `undefined` / `''` |
513
+ | `TimeField` · `TimeFormField` | `Date \| undefined` (with `timeZone`, the true instant). With `asString`: `'HH:mm'`. While empty it shows its `placeholder` | `undefined` / `''` |
514
514
  | `DurationField` · `DurationFormField` | `number \| null`: total minutes (hours × 60 + minutes) | `null` |
515
515
 
516
516
  ### Selection & choice
@@ -371,7 +371,8 @@ date") and `material.dates.apply` on its Apply button ("Apply",
371
371
  "Appliquer"); the range itself is written by `Intl.DateTimeFormat`'s
372
372
  `formatRange` in the field's locale. On a phone's sheet, the switch to the
373
373
  large typed field is named `material.dates.type-date` ("Type the date",
374
- "Saisir la date") on `DateField` and `material.dates.type-range` ("Type the
374
+ "Saisir la date") on `DateField` (`material.dates.type-date-time`, "Type the date and
375
+ time", "Saisir la date et l’heure", with `showTimePicker`) and `material.dates.type-range` ("Type the
375
376
  dates", "Saisir les dates") on `DateRangeField`. Its hero says
376
377
  `material.dates.select-date` ("Select date", "Choisir la date") while the date
377
378
  is empty, `material.dates.select-dates` on `DateRangeField`, and the Cancel of
@@ -542,7 +543,7 @@ places that use them read:
542
543
  | `material.table.labels.no-data` | No data available | Données non disponibles | `DataTable`'s default for a table with no data |
543
544
  | `material.table.labels.no-results` / `no-results-description` | No results / Try a different search or clear the filters. | Aucun résultat / Essayez une autre recherche ou retirez les filtres. | `DataTable`'s default when a filter hides every row, also announced by its status |
544
545
  | `material.table.labels.sort` / `sort-by` / `ascending` / `descending` | Sort / Sort by / Ascending / Descending | Trier / Trier par / Croissant / Décroissant | The cards list's sort menu button and its two radio groups |
545
- | `material.table.labels.select-page` / `select-item` | Select all on this page / Select | Tout sélectionner sur cette page / Sélectionner | The cards list bar's select-all, and a card's checkbox (named "Select <title>") |
546
+ | `material.table.labels.select-page` / `select-item` | Select all on this page / Select | Tout sélectionner sur cette page / Sélectionner | The cards list bar's select-all (its accessible name), and a card's checkbox (named "Select <title>"). Inside a `ServerTable`'s two-row phone header the select-all also shows `material.table.labels.select-all` ("Select all") as its short visible label |
546
547
  | `material.table.labels.clear-selection` | Clear selection | Effacer la sélection | The cards list bar's button, shown while rows are selected; on a narrow list it is an icon button, and this string is its accessible name and tooltip |
547
548
  | `material.table.labels.scroll-list` | Scrollable list | Liste défilante | The region of a cards or tiles list with `maxHeight` (`scrollLabel` overrides it) |
548
549
  | `material.command.empty` | No results | Aucun résultat | `CommandEmpty` with no children |
package/docs/roadmap.md CHANGED
@@ -491,7 +491,11 @@ version they shipped in, and planned entries may name the major they target.
491
491
  drops its own title while the bar has one (see [the page's title in the app
492
492
  bar](guides/app-shell.md#the-pages-title-in-the-app-bar)). The title has a
493
493
  stable `id` and `tabIndex={-1}`, `<main>` is labelled by it, and
494
- `usePageTitleFocus()` focuses it after a navigation.
494
+ `usePageTitleFocus()` focuses it after a navigation. `handle.titleAfter` (or the
495
+ `titleAfter` prop) puts a node, such as a record's `Switcher`, right after the
496
+ title outside its `h1`, and `headerContext` a small tenant line after it from
497
+ `md` up (see [a slot after the
498
+ title](guides/app-shell.md#a-slot-after-the-title)).
495
499
  `breadcrumbRoot` on `ActivityLayout` (`root` on `AppBreadcrumb`) puts a text
496
500
  root crumb, resolved through the router's basename, in place of the home
497
501
  icon; and `context` on `ActivityLayout`, `AppHeader` and `AppSidebar` shows
@@ -6358,9 +6358,17 @@ names "Start time" and "End time". Every pick is committed as it is made, and
6358
6358
  OK only closes the picker. Cancel puts back the value the picker opened with
6359
6359
  (it fires `onValueChange` only if something changed); closing the picker in
6360
6360
  another way (Escape, a drag, a click outside) keeps what was picked.
6361
- **Fix:** drive the tiles:
6361
+ A `DateField` with `showTimePicker` also types the time in the field itself
6362
+ (`spinbutton`s "Hours", "Minutes" and, on a 12-hour clock, "AM / PM", in the
6363
+ same group as the date's), so while the popover is open `getByRole('spinbutton',
6364
+ { name: 'Hours' })` can match two: the field's and the tile's. A day clicked in
6365
+ the calendar keeps the time of day.
6366
+ **Fix:** drive the tiles, scoped to the popover (or type in the field's group):
6362
6367
 
6363
6368
  ```ts
6369
+ const popover = screen.getByRole('dialog');
6370
+ await user.click(within(popover).getByRole('button', { name: 'Increase hours' }));
6371
+ // or type: within(screen.getByRole('group', { name: 'Starts' })).getByRole('spinbutton', { name: 'Hours' })
6364
6372
  const start = screen.getByRole('group', { name: 'Start time' });
6365
6373
  await user.click(within(start).getByRole('button', { name: 'Increase hours' }));
6366
6374
  await user.click(screen.getByRole('button', { name: 'OK' }));
@@ -63,6 +63,8 @@
63
63
  @import "./ui/collections/data-table/data-table-items-bar.css";
64
64
  @import "./ui/collections/data-table/data-table-items-forced.css";
65
65
  @import "./ui/collections/data-table/data-table-items-tiles.css";
66
+ /* ServerTable's second header row on a phone: select-all, sort, Filters and the actions. */
67
+ @import "./ui/collections/server-table/server-table-phone-row.css";
66
68
  /* Checkbox and Radio: the check that draws in, the dot that springs in, the state layer. */
67
69
  @import "./ui/selection/selection-controls.css";
68
70
  @import "./ui/selection/selection-controls-forced.css";