@nxgt/material 3.36.0 → 3.38.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/CHANGELOG.md +33 -0
- package/README.md +1 -1
- package/dist/{async-slice-BNI_F0jz.js → async-slice-DqjtOYlO.js} +2 -2
- package/dist/{button-EMWOFSDB.js → button-D52ron-a.js} +3 -2
- package/dist/charts.js +3 -3
- package/dist/{chip-B6m3yagf.js → chip-0XYwSp9o.js} +1 -1
- package/dist/{code-editor-BdiufnpC.js → code-editor-BetIXpJ6.js} +4 -4
- package/dist/code-editor.js +4 -4
- package/dist/{code-language-items-v5Rnq425.js → code-language-items-C7prxl_7.js} +2 -2
- package/dist/components/components.css +2 -0
- package/dist/components/ui/collections/server-table/server-table-phone-row.css +189 -0
- package/dist/{components-C0WjV2oy.js → components-NoCZ44Ri.js} +15551 -15316
- package/dist/components.js +24 -24
- package/dist/{dialog-DR9rACzY.js → dialog-BCsT_aLo.js} +1 -1
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-CpSnRXd-.js → dropdown-menu-Crt5QvTr.js} +1 -1
- package/dist/editor.js +908 -909
- package/dist/{empty-state-DMr94lsF.js → empty-state-DFWq2CWB.js} +1 -1
- package/dist/hooks.js +2 -2
- package/dist/{i18n-provider-A4mI4vc1.js → i18n-provider-C0RBM8rH.js} +1 -1
- package/dist/i18n.js +5 -5
- package/dist/{icon-badge-DXEBeS1y.js → icon-badge-XBoBIt_R.js} +1 -1
- package/dist/lib/components/ui/actions/button/button.d.ts +8 -0
- package/dist/lib/components/ui/collections/data-table/data-table-list-slots.d.ts +12 -0
- package/dist/lib/components/ui/collections/data-table/hooks.d.ts +6 -1
- package/dist/lib/components/ui/collections/data-table/internal.d.ts +1 -0
- package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-list-bar.d.ts +11 -1
- package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +9 -1
- package/dist/lib/components/ui/collections/filter/bar/filter-bar-view.d.ts +1 -1
- package/dist/lib/components/ui/collections/filter/facets/filter-facets.d.ts +1 -1
- package/dist/lib/components/ui/collections/filter/facets/partials/filter-facets-footer.d.ts +4 -1
- package/dist/lib/components/ui/collections/filter/facets/partials/filter-facets-panel.d.ts +3 -1
- package/dist/lib/components/ui/collections/filter/filter-presets.d.ts +1 -1
- package/dist/lib/components/ui/collections/filter/internal.d.ts +1 -0
- package/dist/lib/components/ui/collections/filter/query/types.d.ts +13 -0
- package/dist/lib/components/ui/collections/filter/sections/sheet/filter-sheet-footer.d.ts +3 -1
- package/dist/lib/components/ui/collections/filter/sections/sheet/filter-sheet.d.ts +3 -1
- package/dist/lib/components/ui/collections/filter/types-props.d.ts +3 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-band-classes.d.ts +10 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-band.d.ts +13 -2
- package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-buttons.d.ts +6 -2
- package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk.d.ts +10 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-phone-row.d.ts +17 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog.d.ts +7 -1
- package/dist/lib/components/ui/feedback/confirmation-dialog/confirmation-dialog.d.ts +7 -1
- package/dist/lib/components/ui/fields/attributes/attribute-dialog.d.ts +4 -1
- package/dist/lib/components/ui/fields/attributes/attributes-field/attributes-dialog-field.d.ts +4 -1
- package/dist/lib/components/ui/fields/attributes/attributes-field/attributes-inline-field.d.ts +1 -1
- package/dist/lib/components/ui/fields/contacts/contact-dialog.d.ts +4 -1
- package/dist/lib/components/ui/fields/contacts/contacts-dialog-form-field.d.ts +4 -1
- package/dist/lib/components/ui/fields/contacts/contacts-form-field.d.ts +4 -1
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/fields/opening-hours/opening-hours-dialog-form-field.d.ts +4 -1
- package/dist/lib/components/ui/fields/opening-hours/opening-hours-dialog.d.ts +4 -1
- package/dist/lib/components/ui/fields/opening-hours/opening-hours-form-field.d.ts +4 -1
- package/dist/lib/components/ui/fields/postal-address/postal-address-dialog.d.ts +4 -1
- package/dist/lib/components/ui/fields/postal-address/postal-address-form-field.d.ts +4 -1
- package/dist/lib/components/ui/layout/app-shell/activity-column.d.ts +2 -2
- package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +1 -1
- package/dist/lib/components/ui/layout/app-shell/activity-layout.types.d.ts +18 -1
- package/dist/lib/components/ui/layout/app-shell/app-breadcrumb.d.ts +20 -4
- package/dist/lib/components/ui/layout/app-shell/header-context.d.ts +10 -0
- package/dist/lib/components/ui/layout/app-shell/header-title.d.ts +5 -1
- package/dist/lib/components/ui/layout/app-shell/page-title.d.ts +21 -2
- package/dist/lib/components/ui/media/image-cropper/image-cropper.d.ts +4 -1
- package/dist/lib/components/ui/selection/chip-group-field/chip-group-columns.d.ts +3 -0
- package/dist/lib/components/ui/selection/chip-group-field/chip-group-field.d.ts +28 -5
- package/dist/lib/components/ui/selection/chip-group-field/chip-group-field.types.d.ts +17 -0
- package/dist/lib/components/ui/selection/chip-group-field/partials/chip-group-option.d.ts +7 -3
- package/dist/lib/components/ui/selection/chip-group-field/partials/chip-group-sections.d.ts +13 -0
- package/dist/lib/components/ui/selection/choice-group/choice-key-target.d.ts +1 -1
- package/dist/lib/components/ui/selection/choice-group/choice-press.d.ts +2 -0
- package/dist/lib/components/ui/selection/choice-group/use-choice-group-field.d.ts +3 -1
- package/dist/lib/components/ui/selection/choice-group/use-choice-group.d.ts +2 -0
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar-events.utils.d.ts +11 -1
- package/dist/lib/components/ui/widgets/rich-text-editor/types.d.ts +3 -0
- package/dist/lib/i18n/resources/en/en.d.ts +1 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +2 -1
- package/dist/lib/i18n/resources/fr/fr.d.ts +1 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +2 -1
- package/dist/lib/i18n/resources/resources.d.ts +2 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/main.js +41 -41
- package/dist/map.js +4 -4
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/print.js +2 -2
- package/dist/{public-f9CBkMZP.js → public-CWIpzLzO.js} +6 -6
- package/dist/{rich-code-block-Bwhfhu7J.js → rich-code-block-C4aBv4cA.js} +4 -4
- package/dist/{sortable-provider-BshacF8v.js → sortable-provider-0uBi0zcP.js} +3 -2
- package/dist/{spinner-CKlDba5I.js → spinner-CuxEwO0d.js} +1 -1
- package/dist/{stored-language-CfQGHLq9.js → stored-language-D8aemZhx.js} +4 -2
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle--Zcf5AM8.js → theme-toggle-ik5nV1P5.js} +1 -1
- package/dist/{toolbar-items-B1W-uVHS.js → toolbar-items-C97E5HRa.js} +2 -2
- package/dist/{translate-material-xDh10aWZ.js → translate-material-B84ufMSF.js} +2 -2
- package/dist/{translator-BbOZntEF.js → translator-Bq8UHmvu.js} +1 -1
- package/dist/{use-chat-composer-C5g_Va_g.js → use-chat-composer-DmDcEGp4.js} +2317 -2190
- package/dist/{use-heading-spy-DXYCeegh.js → use-heading-spy-BgG7Q47k.js} +1 -1
- package/dist/{use-material-translation-BK2YYCY3.js → use-material-translation-CiYhVc39.js} +2 -2
- package/docs/components/buttons-and-actions.md +19 -1
- package/docs/components/data-table-filter-and-lists.md +65 -3
- package/docs/components/feedback-and-status.md +3 -1
- package/docs/components/form-fields.md +10 -4
- package/docs/components/media-and-files.md +1 -0
- package/docs/components/navigation-and-layout.md +4 -2
- package/docs/components/primitives.md +1 -1
- package/docs/components/rich-widgets.md +2 -2
- package/docs/components/selection-and-choice.md +67 -3
- package/docs/guides/app-shell.md +91 -2
- package/docs/guides/cards-and-tiles.md +5 -0
- package/docs/guides/i18n.md +1 -1
- package/docs/roadmap.md +5 -1
- package/lib/components/components.css +2 -0
- package/lib/components/ui/collections/server-table/server-table-phone-row.css +189 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as e } from "./i18n-provider-
|
|
1
|
+
import { r as e } from "./i18n-provider-C0RBM8rH.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-
|
|
2
|
-
import { t } from "./translate-material-
|
|
1
|
+
import { r as e } from "./i18n-provider-C0RBM8rH.js";
|
|
2
|
+
import { t } from "./translate-material-B84ufMSF.js";
|
|
3
3
|
import { useCallback as n } from "react";
|
|
4
4
|
//#region lib/i18n/use-material-translation.ts
|
|
5
5
|
function r() {
|
|
@@ -65,7 +65,7 @@ import { Mail } from 'lucide-react';
|
|
|
65
65
|
| `asChild` | `boolean` | `false` | styles its only child instead of rendering a `<button>` |
|
|
66
66
|
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | not `submit` by default — pass it inside a form |
|
|
67
67
|
|
|
68
|
-
The classes are exported as `buttonVariants` for styling your own element (Pagination, AlertDialog and the calendar's date picker use them).
|
|
68
|
+
The `ButtonVariant` type (one `variant` value) is exported too, to type a `cancelVariant` you forward. The classes are exported as `buttonVariants` for styling your own element (Pagination, AlertDialog and the calendar's date picker use them).
|
|
69
69
|
|
|
70
70
|
**The look.** The label is set in the type scale and is heavier than before (weights 560 at `xs` to 700 at `2xl`). The focus is a 2 px outline in the button's colour, 2 px off the button (the error colour with `aria-invalid`). A disabled button has neutral text (40 % of the text colour) and a neutral 12 % fill, none on `ghost` and `link`. `outlined` is a 1.5 px line inside the box at 55 % of the colour, and keeps a `ButtonText` outline in forced colours, where the inset shadow is removed. A `ghost` button is neutral without a `color`, and takes the tone of one it is given. On a coarse pointer every size has a 44 px hit area.
|
|
71
71
|
|
|
@@ -79,6 +79,24 @@ const [on, setOn] = useState(false);
|
|
|
79
79
|
<Button size="2xl" shape="square" variant="tonal">Start</Button>;
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
**Footer buttons.** In a footer (a dialog, a sheet, a form, an inline editor),
|
|
83
|
+
the confirming action is `filled` and the Cancel, Close or Discard beside it
|
|
84
|
+
is `tonal`: two clear buttons, one weight apart, where a text or outlined
|
|
85
|
+
Cancel read poorly and left its hit area invisible. The library draws its own
|
|
86
|
+
Cancel buttons that way (`AlertDialogCancel`, `ConfirmDialog`,
|
|
87
|
+
`ConfirmationDialog`, the field dialogs, `ImageCropper`, the filter sheet…),
|
|
88
|
+
and each takes `cancelVariant` (a `Button` `variant`, default `'tonal'`) to
|
|
89
|
+
restore `'outlined'` or `'ghost'`. Write yours the same way. An icon-only ✕ in
|
|
90
|
+
a corner, a toolbar button and a text link stay as they are; a destructive
|
|
91
|
+
confirm keeps its own variant and `color="error"`.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<DialogFooter>
|
|
95
|
+
<Button variant="tonal" onClick={close}>Cancel</Button>
|
|
96
|
+
<Button type="submit">Save</Button>
|
|
97
|
+
</DialogFooter>;
|
|
98
|
+
```
|
|
99
|
+
|
|
82
100
|
**On a phone.** A button is never wider than its container, and keeps its
|
|
83
101
|
content's width in a column: a label too
|
|
84
102
|
long for it (a card on a phone) is cut with “…” on one line, and its
|
|
@@ -83,7 +83,9 @@ truncates with an ellipsis and carries its full text as a `title`); `DataTablePa
|
|
|
83
83
|
"Toggle row details" (`material.table.labels.toggle-row`) with `aria-expanded`, under a
|
|
84
84
|
visually hidden header "Expand", `material.table.labels.expand`);
|
|
85
85
|
`useDeleteRowsHandler(onDelete)` returns a `handleDelete(rows)` click
|
|
86
|
-
handler and the `confirmDialog` to render
|
|
86
|
+
handler and the `confirmDialog` to render; its Cancel button is `tonal`
|
|
87
|
+
(`useDeleteRowsHandler(onDelete, { cancelVariant: 'outlined' })` restores the
|
|
88
|
+
earlier look).
|
|
87
89
|
`dataTableFeatures` / `DataTableFeatures` are the TanStack v9 features the
|
|
88
90
|
table enables, for typing your own column defs.
|
|
89
91
|
|
|
@@ -922,7 +924,7 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
|
|
|
922
924
|
| `actions` | `ReactNode` | — | the page's actions, at the band's end |
|
|
923
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') |
|
|
924
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) |
|
|
925
|
-
| `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 |
|
|
926
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 |
|
|
927
929
|
| `sorting` | `createSorting(…)` | — | headers sorted from the URL |
|
|
928
930
|
| `filters` | `tableFilters(model)` | — | without `filter`: "nothing matches" and its clear |
|
|
@@ -942,6 +944,59 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
|
|
|
942
944
|
<ServerTable {...props} filter={{ model }} bandActions="inline" actions={<IconButton aria-label="New" tooltip="New"><Icon name="plus" /></IconButton>} />
|
|
943
945
|
```
|
|
944
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
|
+
|
|
945
1000
|
**Where the focus goes.** After "Clear" on an empty page, or the last chip
|
|
946
1001
|
removed, the focus returns to the Filter's search; a bar without one falls to
|
|
947
1002
|
its first shown control (a quick filter, "Add filter", or "Filters (N)" on a
|
|
@@ -988,7 +1043,7 @@ page's `title`.
|
|
|
988
1043
|
|
|
989
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)).
|
|
990
1045
|
|
|
991
|
-
**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`
|
|
992
1047
|
|
|
993
1048
|
---
|
|
994
1049
|
|
|
@@ -1402,6 +1457,13 @@ and `errors` shows a server's refusal of a condition (see
|
|
|
1402
1457
|
[A refused condition](#a-refused-condition)). A locked chip is a focusable group named by its text ("Year is 2026-2027"), with "Locked: <reason>" as its description. The other variants
|
|
1403
1458
|
and `FilterProps` are unchanged.
|
|
1404
1459
|
|
|
1460
|
+
#### The sheet's and the staged panel's secondary button
|
|
1461
|
+
|
|
1462
|
+
"Clear all" in the phone sheet's footer and "Reset" in the staged panel's are
|
|
1463
|
+
`tonal`, beside a filled "Show N results" / "Apply". `cancelVariant` on
|
|
1464
|
+
`Filter variant="bar"` or `"facets"` (a `Button` `variant`) restores
|
|
1465
|
+
`'outlined'`. `FilterPresets` takes the same prop for its save dialog's Cancel.
|
|
1466
|
+
|
|
1405
1467
|
#### A compact band for a list inside a page section
|
|
1406
1468
|
|
|
1407
1469
|
`bandDensity="compact"` is `ServerTable`'s prop, with the same values and the
|
|
@@ -167,7 +167,7 @@ Parts: `AlertDialog`, `AlertDialogTrigger`, `AlertDialogContent`,
|
|
|
167
167
|
`AlertDialogFooter`, `AlertDialogAction`, `AlertDialogCancel`,
|
|
168
168
|
`AlertDialogOverlay`, `AlertDialogPortal`. `AlertDialogAction` and
|
|
169
169
|
`AlertDialogCancel` take the `Button` `variant` and `color` (cancel defaults
|
|
170
|
-
to `outlined
|
|
170
|
+
to `tonal`, under a filled action; `variant="outlined"` restores the earlier look; a `ghost` one in a colour shows it, as a ghost `Button` does). Radix props (`open`, `defaultOpen`, `onOpenChange`) go on
|
|
171
171
|
`AlertDialog`.
|
|
172
172
|
On open, `AlertDialogContent` focuses `AlertDialogCancel`, the least
|
|
173
173
|
destructive action (its first control when there is no Cancel, the content
|
|
@@ -225,6 +225,7 @@ function DeleteProject({ onDelete }: { onDelete: () => Promise<void> }) {
|
|
|
225
225
|
| `confirmDisabled` | `boolean` | `false` | disables the confirm button, for example while an error of yours stands |
|
|
226
226
|
| `lockOnRefusal` | `boolean` | `false` | once `onConfirm` has refused (thrown), the confirm button stays disabled while its error shows, so a retry that cannot succeed is not invited; Cancel stays enabled. The error clears when the dialog closes |
|
|
227
227
|
| `confirmLabel` / `cancelLabel` | `string` | "Confirm" / "Cancel", translated (`material.confirm-dialog.confirm` / `.cancel`) | button text |
|
|
228
|
+
| `cancelVariant` | `ButtonVariant` | `'tonal'` | the Cancel button's variant; `'outlined'` restores the earlier look |
|
|
228
229
|
|
|
229
230
|
On a phone (below 640 px) the footer stacks. A `destructive` dialog puts Cancel first and the destructive action last; a default one keeps the action on top.
|
|
230
231
|
|
|
@@ -272,6 +273,7 @@ const schema = z.object({ reason: z.string().min(3) });
|
|
|
272
273
|
| `icon` | `ReactNode` | info icon | the glyph alone (not an `IconButton`): it is shown in a decorative, `aria-hidden` badge |
|
|
273
274
|
| `iconProps` | `IconButtonProps` | — | the badge's `variant`, `color`, `className` |
|
|
274
275
|
| `confirmText` / `cancelText` | `ReactNode` | "Confirm" / "Cancel", translated (`material.confirm-dialog.confirm` / `.cancel`) | button text |
|
|
276
|
+
| `cancelVariant` | `ButtonVariant` | `'tonal'` | the Cancel button's variant; `'outlined'` or `'ghost'` restores an earlier look |
|
|
275
277
|
|
|
276
278
|
**Several steps, one dialog.** Confirm closes it; to show the next step, change
|
|
277
279
|
its `title` (and `description`) and set `open` back to `true`, at once (from
|
|
@@ -1324,7 +1324,7 @@ Three parts, each for one job:
|
|
|
1324
1324
|
|
|
1325
1325
|
| Part | Use it for |
|
|
1326
1326
|
| --- | --- |
|
|
1327
|
-
| `AttributesField` · `AttributesFormField` | the field, and its React Hook Form wrapper. Inline by default; `editing="dialog"` keeps the list and dialog |
|
|
1327
|
+
| `AttributesField` · `AttributesFormField` | the field, and its React Hook Form wrapper. Inline by default; `editing="dialog"` keeps the list and dialog, whose Cancel button is `tonal` (`cancelVariant` restores `'outlined'`) |
|
|
1328
1328
|
| `AttributesEditor` | the same inline editor, controlled (`value`, `onValueChange`), outside a form |
|
|
1329
1329
|
| `AttributesSummary` | the read-only display: type icons, labels and default values |
|
|
1330
1330
|
|
|
@@ -1536,8 +1536,9 @@ only, and a value that fails its check is shown without a link.
|
|
|
1536
1536
|
`ContactsEditor` takes `value`, `onValueChange`, `label`, `headingLevel` (`3`
|
|
1537
1537
|
by default; the type headings sit one below it), `variant` and `disabled`. A
|
|
1538
1538
|
wrong format is said once the field was left, a duplicate at once. `ContactsFormField` takes `editing` (`'inline' \| 'dialog'`,
|
|
1539
|
-
default `'inline'`)
|
|
1540
|
-
|
|
1539
|
+
default `'inline'`), `variant` (inline only) and `cancelVariant` (dialog only,
|
|
1540
|
+
the Cancel button's `Button` variant, default `'tonal'`; `'outlined'` restores
|
|
1541
|
+
the earlier look). `Contacts` takes `data`, `onAdd`, `onEdit` and `disabled`.
|
|
1541
1542
|
|
|
1542
1543
|
`contactsListSchema()` is the zod schema of a whole list, with the editor's
|
|
1543
1544
|
own rules (the same functions): the value's format by type (an email, an
|
|
@@ -1592,6 +1593,10 @@ Parts: `PostalAddress` (the card), `PostalAddressDialog` (the edit form).
|
|
|
1592
1593
|
| `onEdit` | `() => void` | — | shows the edit button |
|
|
1593
1594
|
| `disabled` | `boolean` | — | hides it |
|
|
1594
1595
|
|
|
1596
|
+
`PostalAddressFormField` and `PostalAddressDialog` take `cancelVariant` (the
|
|
1597
|
+
dialog's Cancel button, a `Button` `variant`, default `'tonal'`; `'outlined'`
|
|
1598
|
+
restores the earlier look).
|
|
1599
|
+
|
|
1595
1600
|
**Emits:** `PostalAddressFormField` stores a `PostalAddressFieldValue`
|
|
1596
1601
|
object, and `{}` when the address is removed.
|
|
1597
1602
|
**Form wrapper:** `PostalAddressFormField` — `control` and `name`, see
|
|
@@ -1671,7 +1676,8 @@ summary shows a quiet "Hours not set" (`empty` replaces the text,
|
|
|
1671
1676
|
`OpeningHoursEditor` takes `value`, `onValueChange`, `label`, `headingLevel` (`3` by default), `weekStartsOn`,
|
|
1672
1677
|
`locale` and `disabled`. `OpeningHoursFormField` takes `editing`
|
|
1673
1678
|
(`'inline' \| 'dialog'`, default `'inline'`), `weekStartsOn` and `locale`
|
|
1674
|
-
(inline), and `showAddAllDays` (dialog, default `false`)
|
|
1679
|
+
(inline), and `showAddAllDays` (dialog, default `false`) and `cancelVariant`
|
|
1680
|
+
(dialog, the Cancel button's `Button` variant, default `'tonal'`).
|
|
1675
1681
|
|
|
1676
1682
|
`openingHoursSchema()` returns the zod schema of one `OpeningHour`
|
|
1677
1683
|
(`dayOfWeek` coerced from a string), built on the first call. It is unchanged.
|
|
@@ -429,6 +429,7 @@ after `onCrop`).
|
|
|
429
429
|
| `cropShape` | `'rect' \| 'round'` | `'rect'` | round mask for avatars |
|
|
430
430
|
| `fileName` | `string` | — | name of the produced `File` |
|
|
431
431
|
| `onCancel` | `() => void` | — | `ImageCropper`: shows a Cancel button |
|
|
432
|
+
| `cancelVariant` | `ButtonVariant` | `'tonal'` | `ImageCropper`: the Cancel button's variant; `'ghost'` restores the earlier look |
|
|
432
433
|
| `open` / `onOpenChange` / `trigger` | | — | `ImageCropperDialog`: controlled or trigger-opened |
|
|
433
434
|
|
|
434
435
|
**Storybook:** `Media & files/ImageCropper` · **Vue:** `ImageCropper`, `ImageCropperDialog`
|
|
@@ -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
|
|
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 |
|
|
@@ -151,7 +151,7 @@ import {
|
|
|
151
151
|
</CardHeader>
|
|
152
152
|
<CardContent>Pick a name and a framework.</CardContent>
|
|
153
153
|
<CardFooter className="justify-end gap-2">
|
|
154
|
-
<Button variant="
|
|
154
|
+
<Button variant="tonal">Cancel</Button>
|
|
155
155
|
<Button>Deploy</Button>
|
|
156
156
|
</CardFooter>
|
|
157
157
|
</Card>;
|
|
@@ -1728,7 +1728,7 @@ function Reply({ onSubmit }: { onSubmit: (html: string) => void }) {
|
|
|
1728
1728
|
mentions={['Ada', 'Grace']}
|
|
1729
1729
|
actions={
|
|
1730
1730
|
<>
|
|
1731
|
-
<Button variant="
|
|
1731
|
+
<Button variant="tonal" onClick={() => setHtml('')}>
|
|
1732
1732
|
Cancel
|
|
1733
1733
|
</Button>
|
|
1734
1734
|
<Button onClick={() => onSubmit(html)}>Reply</Button>
|
|
@@ -1965,7 +1965,7 @@ function Comment() {
|
|
|
1965
1965
|
| `editable` | `boolean` | `true` | read-only when `false` (toolbar hidden) |
|
|
1966
1966
|
| `options.features` | `RichTextEditorFeatures` | all `true` | turn off tables, images, YouTube, slash commands, custom blocks… |
|
|
1967
1967
|
| `options.mentions.suggestions` | `(string \| MentionItem)[]` | — | enables `@` mentions; a `MentionItem` (as `MentionList` / `ChatComposer` take) shows its avatar and handle in the list and inserts its `id` with its `label` as text |
|
|
1968
|
-
| `options.onSave` / `onCancel` | `() => void` | — | adds Save / Cancel buttons |
|
|
1968
|
+
| `options.onSave` / `onCancel` | `() => void` | — | adds Save / Cancel buttons; Cancel is `tonal`, `options.cancelVariant` restores `'ghost'` |
|
|
1969
1969
|
| `characterLimit` | `number` | — | caps the text and shows the count |
|
|
1970
1970
|
| `placeholder` | `string` | "Write something …" (translated, `material['rich-text-editor'].placeholder`) | shown in an empty editor; a change shows at once |
|
|
1971
1971
|
| `required` | `boolean` | — | the asterisk, and `aria-required` on the editable area |
|
|
@@ -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
|
|
804
|
-
`
|
|
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
|
package/docs/guides/app-shell.md
CHANGED
|
@@ -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' })];
|
package/docs/guides/i18n.md
CHANGED
|
@@ -542,7 +542,7 @@ places that use them read:
|
|
|
542
542
|
| `material.table.labels.no-data` | No data available | Données non disponibles | `DataTable`'s default for a table with no data |
|
|
543
543
|
| `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
544
|
| `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>") |
|
|
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 (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
546
|
| `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
547
|
| `material.table.labels.scroll-list` | Scrollable list | Liste défilante | The region of a cards or tiles list with `maxHeight` (`scrollLabel` overrides it) |
|
|
548
548
|
| `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
|
|
@@ -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";
|