@nxgt/material 3.51.0 → 3.53.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 +44 -0
- package/README.md +5 -4
- package/dist/{async-slice-BGtmF3K5.js → async-slice-DDSsekfm.js} +2 -2
- package/dist/{button-BWcr2b49.js → button-Ct8DSLVv.js} +1 -1
- package/dist/charts.js +1064 -932
- package/dist/{chip-CmLW6ZrT.js → chip-BzoNsd-W.js} +2 -2
- package/dist/{code-editor-BKMaeIF6.js → code-editor-DsLQeiZc.js} +6 -6
- package/dist/code-editor.js +5 -5
- package/dist/{code-language-items-Cs1Pwd1h.js → code-language-items-4zXlIEZ0.js} +2 -2
- package/dist/components/components.css +1 -5
- package/dist/components/ui/actions/action-card/action-card-tile.css +11 -2
- package/dist/components/ui/collections/data-table/data-table-items-record.css +27 -0
- package/dist/components/ui/collections/data-table/index.css +14 -0
- package/dist/{components-CuwU9Sux.js → components-DZPMvc1w.js} +16110 -15921
- package/dist/components.js +26 -26
- package/dist/{dialog-TdT_5iUc.js → dialog-DwPmwmA3.js} +4 -4
- package/dist/dnd.js +1 -1
- package/dist/draw-animate.js +1 -1
- package/dist/{dropdown-menu-BozV6Ikw.js → dropdown-menu-CDN6Nqx6.js} +1 -1
- package/dist/editor.js +9 -9
- package/dist/{empty-state-gzBPimAo.js → empty-state-Bb9W9did.js} +2 -2
- package/dist/{escape-owner-Bu-aDEEO.js → escape-owner-DI_lg2ID.js} +1 -1
- package/dist/{field-blur-47zmuDkH.js → field-blur-BcJH8H9B.js} +1 -1
- package/dist/{field-blur-context-6McYOnM8.js → field-blur-context-Dg1nwWOV.js} +1 -1
- package/dist/hooks.js +2 -2
- package/dist/{hover-card-zHdZJpBG.js → hover-card-Qse4gnop.js} +2 -2
- package/dist/i18n-provider-DlVPS58N.js +79 -0
- package/dist/i18n.js +5 -5
- package/dist/{icon-badge-CRFGIQPX.js → icon-badge-DVPBQOKe.js} +3 -3
- package/dist/{icon-button-CtG3Snfm.js → icon-button-C3wDxsh6.js} +12 -12
- package/dist/lib/components/ui/actions/action-card/action-card-semantics.d.ts +17 -1
- package/dist/lib/components/ui/actions/action-card/action-card.d.ts +16 -2
- package/dist/lib/components/ui/actions/action-card/partials/action-card-text.d.ts +6 -2
- package/dist/lib/components/ui/actions/action-card/partials/action-card-title-button.d.ts +15 -1
- package/dist/lib/components/ui/actions/action-card/partials/action-card-trail.d.ts +2 -1
- package/dist/lib/components/ui/collections/data-grid/columns/column-codec.d.ts +3 -3
- package/dist/lib/components/ui/collections/data-grid/columns/column-edit.d.ts +5 -5
- package/dist/lib/components/ui/collections/data-grid/columns/row-meta.d.ts +22 -0
- package/dist/lib/components/ui/collections/data-grid/grid-meta.d.ts +10 -3
- package/dist/lib/components/ui/collections/data-table/data-table.d.ts +1 -1
- package/dist/lib/components/ui/collections/data-table/layout-types.d.ts +27 -0
- package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-item-context.d.ts +2 -0
- package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-item-row.d.ts +8 -0
- package/dist/lib/components/ui/collections/data-table/types.d.ts +1 -1
- package/dist/lib/components/ui/collections/filter/bar/filter-bar-classes.d.ts +7 -2
- package/dist/lib/components/ui/collections/server-table/partials/filtered-server-table.d.ts +1 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-band-classes.d.ts +5 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-band-view.d.ts +18 -0
- package/dist/lib/components/ui/collections/server-table/partials/use-server-table-list-root.d.ts +10 -0
- package/dist/lib/components/ui/collections/server-table/partials/use-server-table-phone-row.d.ts +3 -2
- package/dist/lib/components/ui/display/change-history/types.d.ts +2 -1
- package/dist/lib/components/ui/display/description/description.d.ts +2 -2
- package/dist/lib/components/ui/display/list-tile/internal.d.ts +1 -0
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-dense-text.d.ts +28 -0
- package/dist/lib/components/ui/feedback/toast/toast-text.d.ts +7 -0
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/fields/verification-code/use-resend-focus.d.ts +4 -1
- package/dist/lib/components/ui/forms/index.d.ts +2 -0
- package/dist/lib/components/ui/forms/use-fast-form.d.ts +25 -0
- package/dist/lib/components/ui/forms/use-focus-on-refusal.d.ts +14 -0
- package/dist/lib/components/ui/layout/app-shell/activity-content-aside.d.ts +3 -1
- package/dist/lib/components/ui/layout/app-shell/activity-content.d.ts +10 -1
- package/dist/lib/components/ui/primitives/tabs/tabs-overflow-classes.d.ts +8 -5
- package/dist/lib/components/ui/primitives/tabs/use-tabs-overflow.d.ts +4 -3
- package/dist/lib/components/ui/selection/switcher/partials/switcher-trigger.d.ts +3 -1
- package/dist/lib/components/ui/selection/switcher/switcher.d.ts +1 -1
- package/dist/lib/components/ui/selection/switcher/types.d.ts +7 -0
- package/dist/lib/components/ui/widgets/native-charts/core/reference-text.d.ts +14 -6
- package/dist/lib/components/ui/widgets/native-charts/core/reference.d.ts +57 -10
- package/dist/lib/components/ui/widgets/native-charts/core/types.d.ts +13 -5
- package/dist/lib/components/ui/widgets/native-charts/hooks/chart-model-types.d.ts +3 -2
- package/dist/lib/components/ui/widgets/native-charts/index.d.ts +1 -1
- package/dist/lib/components/ui/widgets/native-charts/line-chart/line-chart-types.d.ts +6 -1
- package/dist/lib/components/ui/widgets/native-charts/line-chart/partials/reference-marks.d.ts +1 -1
- package/dist/lib/i18n/i18n-provider.d.ts +14 -3
- package/dist/lib/i18n/material-resources-registry.d.ts +21 -0
- package/dist/lib/i18n/resources/en/en.d.ts +4 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +5 -1
- package/dist/lib/i18n/resources/fr/fr.d.ts +4 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +5 -1
- package/dist/lib/i18n/resources/resources.d.ts +8 -0
- package/dist/lib/i18n/translate-material.d.ts +7 -1
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/i18n/types.d.ts +6 -0
- package/dist/lib/i18n/use-material-translation.d.ts +4 -0
- package/dist/lib.js +1 -1
- package/dist/main.js +43 -43
- package/dist/map.js +8 -8
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/print.js +5 -4
- package/dist/{public-CTZ5RK_Y.js → public-BoVE0ffh.js} +9 -9
- package/dist/{rich-code-block-oTgL-s4W.js → rich-code-block-DAUM7Yh_.js} +5 -5
- package/dist/{sortable-provider-CpA5NYgD.js → sortable-provider-BLXsGE_0.js} +2 -2
- package/dist/{spinner-UvqlDsV8.js → spinner-Rg4PnBPH.js} +1 -1
- package/dist/{stored-language-Cb9FMEwk.js → stored-language-B1lJMPbR.js} +10 -2
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle-BZVz6O5q.js → theme-toggle-jVdxF_mc.js} +1 -1
- package/dist/{toolbar-items-C09bJphe.js → toolbar-items-BFY_hns5.js} +4 -4
- package/dist/translate-material-q-JNxP1m.js +20 -0
- package/dist/{translator-BnQXtpSZ.js → translator-BU1o3_l9.js} +1 -1
- package/dist/{use-chat-composer-AZAj4tFJ.js → use-chat-composer-DGbDuIUa.js} +3783 -3719
- package/dist/{use-heading-spy-C9-A2md2.js → use-heading-spy-CP63sAcS.js} +1 -1
- package/dist/use-material-translation-nNF8fUXz.js +14 -0
- package/dist/use-shape-morph-DXdy2X5g.js +35 -0
- package/docs/components/buttons-and-actions.md +30 -1
- package/docs/components/data-display.md +3 -3
- package/docs/components/data-table-filter-and-lists.md +2 -2
- package/docs/components/navigation-and-layout.md +1 -0
- package/docs/components/primitives.md +1 -1
- package/docs/components/rich-widgets.md +2 -1
- package/docs/components/selection-and-choice.md +20 -0
- package/docs/guides/app-shell.md +22 -0
- package/docs/guides/cards-and-tiles.md +40 -4
- package/docs/guides/charts.md +30 -2
- package/docs/guides/data-grid.md +42 -2
- package/docs/guides/forms.md +35 -0
- package/docs/guides/helpers.md +1 -1
- package/docs/guides/i18n.md +34 -8
- package/docs/guides/two-factor.md +2 -1
- package/docs/previews/guides-i18n-i18nprovider.webp +0 -0
- package/docs/previews/guides-i18n-translated-components.webp +0 -0
- package/docs/roadmap.md +84 -684
- package/docs/troubleshooting.md +24 -2
- package/lib/components/components.css +1 -5
- package/lib/components/ui/actions/action-card/action-card-tile.css +11 -2
- package/lib/components/ui/collections/data-table/data-table-items-record.css +27 -0
- package/lib/components/ui/collections/data-table/index.css +14 -0
- package/package.json +1 -1
- package/dist/i18n-provider-Ang2f31m.js +0 -43
- package/dist/translate-material-D6KCK21i.js +0 -12
- package/dist/use-material-translation-pLgSqqLw.js +0 -14
- package/dist/use-shape-morph-BKA_R5P5.js +0 -35
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as e } from "./i18n-provider-
|
|
1
|
+
import { r as e } from "./i18n-provider-DlVPS58N.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, useState as d } from "react";
|
|
4
4
|
import { busySettler as f, busySettler as p, createSubmitGuard as m, createSubmitGuard as h } from "@nxgt/material-core/submit-guard";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { r as e } from "./i18n-provider-DlVPS58N.js";
|
|
2
|
+
import { t } from "./translate-material-q-JNxP1m.js";
|
|
3
|
+
import { useCallback as n } from "react";
|
|
4
|
+
//#region lib/i18n/use-material-translation.ts
|
|
5
|
+
function r() {
|
|
6
|
+
let r = e(), i = r?.language, a = r?.resources;
|
|
7
|
+
return {
|
|
8
|
+
t: n((e, n, r) => t(i, e, n, r, a), [i, a]),
|
|
9
|
+
language: i,
|
|
10
|
+
hasProvider: !!r
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { r as t };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useLayoutEffect as e, useRef as t } from "react";
|
|
2
|
+
import { morph as n, morphAt as r, toSvgPath as i } from "@nxgt/material-core/shapes";
|
|
3
|
+
//#region lib/components/ui/display/shape/use-shape-morph.ts
|
|
4
|
+
var a = .8, o = 14, s = o * Math.sqrt(1 - a * a);
|
|
5
|
+
function c(e) {
|
|
6
|
+
let t = e / 1e3;
|
|
7
|
+
return 1 - Math.exp(-.8 * o * t) * (Math.cos(s * t) + a * o / s * Math.sin(s * t));
|
|
8
|
+
}
|
|
9
|
+
var l = () => typeof window.matchMedia == "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
10
|
+
function u(a, o, s) {
|
|
11
|
+
let u = t(o);
|
|
12
|
+
e(() => {
|
|
13
|
+
let e = a.current, t = u.current;
|
|
14
|
+
if (!e || t === o || !s || l()) {
|
|
15
|
+
e && t !== o && e.setAttribute("d", i(o)), u.current = o;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let d = n(t, o), f = i(o), p = 0, m = performance.now(), h = (t) => {
|
|
19
|
+
let n = t - m;
|
|
20
|
+
if (n >= 520) {
|
|
21
|
+
u.current = o, e.setAttribute("d", f);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let a = r(d, c(n));
|
|
25
|
+
u.current = a, e.setAttribute("d", i(a)), p = requestAnimationFrame(h);
|
|
26
|
+
};
|
|
27
|
+
return e.setAttribute("d", i(d.from)), p = requestAnimationFrame(h), () => cancelAnimationFrame(p);
|
|
28
|
+
}, [
|
|
29
|
+
a,
|
|
30
|
+
o,
|
|
31
|
+
s
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { u as n, c as t };
|
|
@@ -1138,9 +1138,38 @@ keyboard focus of the title button, only while it is clipped (Escape dismisses
|
|
|
1138
1138
|
it; moving the pointer across the line does not). Without `action` the DOM is as
|
|
1139
1139
|
before. Nothing is added for a Vue port yet (React first).
|
|
1140
1140
|
|
|
1141
|
+
#### A link title, several actions and a footer
|
|
1142
|
+
|
|
1143
|
+
For a tile whose title goes to a page (a hospital bed, a room), pass
|
|
1144
|
+
`titleLink`: an element (a router `<Link to=... />`, an `<a href=...>`) that
|
|
1145
|
+
receives the title as its children. It is stretched over the card exactly like
|
|
1146
|
+
the title button and named after the title, the status and the meta. The root
|
|
1147
|
+
stays a plain `div`, and `onClick`, `active` and `role` are not read. `action`
|
|
1148
|
+
takes several controls (two menus in a fragment); `footer` (`tile` only) is a
|
|
1149
|
+
last row for secondary links, such as the occupant or a device, above the
|
|
1150
|
+
stretched link and outside its accessible name. Nothing nests, so
|
|
1151
|
+
`nested-interactive` stays clean; keep each footer target 44px (`min-h-11`);
|
|
1152
|
+
the footer wraps inside the tile at 360px.
|
|
1153
|
+
|
|
1154
|
+
```tsx
|
|
1155
|
+
<ActionCard
|
|
1156
|
+
variant="tile"
|
|
1157
|
+
title="Bed 12A"
|
|
1158
|
+
titleLink={<Link to="/beds/12a" />}
|
|
1159
|
+
status={<Badge variant="soft" tone="warning">Occupied</Badge>}
|
|
1160
|
+
action={<><StatusMenu /><StayMenu /></>}
|
|
1161
|
+
footer={<Link to="/patients/9">DOE Jane</Link>}
|
|
1162
|
+
/>;
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
| Prop | Type | Effect |
|
|
1166
|
+
| --- | --- | --- |
|
|
1167
|
+
| `titleLink` | `ReactElement` | the title becomes this link (its `className` is kept); the card is one link tab stop, then the actions, then the footer links |
|
|
1168
|
+
| `footer` | `ReactNode` | `tile` only; secondary links under the status and meta |
|
|
1169
|
+
|
|
1141
1170
|
The root is `data-slot="action-card"` (it was the `Card` primitive's `data-slot="card"`): style it with `className`, not through `Card` classes or a `[data-slot=card]` selector.
|
|
1142
1171
|
|
|
1143
|
-
A whole board built on it, with sections and an occupancy table, is the `Guides/Recipes/Status board` recipe. **Storybook:** `Buttons & actions/ActionCard`, `Buttons & actions/ActionCard/Look`, `Buttons & actions/ActionCard/Tile`, `Buttons & actions/ActionCard/Checks` · **Vue:** `ActionCard`, with the earlier look and no button role yet (#1195)
|
|
1172
|
+
A whole board built on it, with sections (each its own h2 and free count) and, with `linked`, link titles and an occupant footer, and an occupancy table, is the `Guides/Recipes/Status board` recipe. **Storybook:** `Buttons & actions/ActionCard`, `Buttons & actions/ActionCard/Look`, `Buttons & actions/ActionCard/Tile`, `Buttons & actions/ActionCard/Link tile`, `Buttons & actions/ActionCard/Checks` · **Vue:** `ActionCard`, with the earlier look and no button role yet (#1195)
|
|
1144
1173
|
|
|
1145
1174
|
---
|
|
1146
1175
|
|
|
@@ -642,7 +642,7 @@ import { Description } from '@nxgt/material/components';
|
|
|
642
642
|
</div>
|
|
643
643
|
```
|
|
644
644
|
|
|
645
|
-
`label` and `value` are its main props; the rest go to the `div`. A `value` of `null`, `undefined`, `false` or `''` renders nothing (`0` is shown), so optional fields need no guard. Inside a `variant="panels"` list a `Description` must sit in a `DescriptionList.Group`; otherwise it renders its stand-alone markup. The label is a small
|
|
645
|
+
`label` and `value` are its main props; the rest go to the `div`. A `value` of `null`, `undefined`, `false` or `''` with no `lines` and no `children` renders nothing (`0` is shown), so optional fields need no guard. `children` alone are the value (`<Description label="Note">text</Description>`); after a `value` they follow it. Inside a `variant="panels"` list a `Description` must sit in a `DescriptionList.Group`; otherwise it renders its stand-alone markup. The label is a small
|
|
646
646
|
muted caption above the value (not a title). `Description` takes one cell of
|
|
647
647
|
the grid it is in; pass `className="col-start-full"` for a full-width row. It takes `lines` anywhere; inside a [`DescriptionList`](#descriptionlist) also `span` and `labelPosition`.
|
|
648
648
|
|
|
@@ -686,7 +686,7 @@ row) or a number of columns (used once that many fit; at most `maxColumns`, and
|
|
|
686
686
|
field uses dense packing: the fields after it fill the cells before it, so they
|
|
687
687
|
may be drawn above it while the DOM and reading order stay the source order.
|
|
688
688
|
Put the full-width field where that reads fine, or last. A multi-line value
|
|
689
|
-
(an address) takes `lines={[…]}` or `children`, never a string to split. Entries of `lines` that are `null`, `false` or `""` are skipped, and a `Description` whose `lines` are all empty and that has no `value` renders nothing.
|
|
689
|
+
(an address) takes `lines={[…]}` or `children`, never a string to split. Entries of `lines` that are `null`, `false` or `""` are skipped, and a `Description` whose `lines` are all empty and that has no `value` or `children` renders nothing.
|
|
690
690
|
|
|
691
691
|
```tsx
|
|
692
692
|
<DescriptionList variant="panels">
|
|
@@ -1087,7 +1087,7 @@ yours, the component names no domain.
|
|
|
1087
1087
|
|
|
1088
1088
|
| Prop | Type | Default | Effect |
|
|
1089
1089
|
| --- | --- | --- | --- |
|
|
1090
|
-
| `entries` | `ChangeHistoryEntry[]` | — | `id`, `kind`, `at` (`Date`, string or number), `author` (`name`, `src`), `changes` (`ChangeHistoryChange[]`: `field`, `before`, `after`, `redacted`) |
|
|
1090
|
+
| `entries` | `ChangeHistoryEntry[]` | — | `id`, `kind`, `at` (`Date`, string or number), `author` (`name`, `src`), `changes` (optional, `ChangeHistoryChange[]`: `field`, `before`, `after`, `redacted`; left out or `[]`, the entry shows its sentence, author and time with no change list) |
|
|
1091
1091
|
| `kinds` | `Record<string, ChangeHistoryKindOverride>` | built-ins | `label`, `verb`, `icon` (`null` removes it), `tone` per kind; `creation` (default true for `created`) makes an entry list values only |
|
|
1092
1092
|
| `variant` | `'rail' \| 'cards'` | `'rail'` | the look |
|
|
1093
1093
|
| `groupBy` | `'day' \| 'none'` | `'day'` | a heading per day (sticky on the rail), or one list in the order given |
|
|
@@ -95,8 +95,8 @@ table enables, for typing your own column defs.
|
|
|
95
95
|
| `data` | `T[]` | — | rows; sorting, filtering and pagination run client-side |
|
|
96
96
|
| `layout` | `'table' \| 'cards' \| 'tiles' \| { phone, wide }` | `'table'` | `'cards'` lays the rows out as a list of cards, `'tiles'` as one column in a single surface ([Cards and tiles](../guides/cards-and-tiles.md)); both ignore `virtual`, `renderExpandedRow` and `showViewOptions` |
|
|
97
97
|
| `cardDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | `compact` fits cards of at least `--nxgt-cards-min` (15rem), up to four a row, with a tighter padding; `layout="cards"` only ([Cards and tiles](../guides/cards-and-tiles.md#columns)) |
|
|
98
|
-
| `renderCard` | `(row, { Title, selected, locked, reason }) => ReactNode` | — | a card's content, required with `layout="cards"`; `Title` is the card's one link and its name |
|
|
99
|
-
| `renderTile` | `(row, { Title, selected, locked, reason }) => ReactNode` | — | a tile's content, required with `layout="tiles"`; same `Title` as a card |
|
|
98
|
+
| `renderCard` | `(row, { Title, Row, selected, locked, reason }) => ReactNode` | — | a card's content, required with `layout="cards"`; `Title` is the card's one link and its name; `Row` (the compact record row) is also passed, laid out inside the card's own padding, with no height of its own |
|
|
99
|
+
| `renderTile` | `(row, { Title, Row, selected, locked, reason }) => ReactNode` | — | a tile's content, required with `layout="tiles"`; same `Title` as a card, or `Row`, the compact record row (title, subtitle, chips, trailing facts; see the cards and tiles guide) |
|
|
100
100
|
| `renderExpandedRow` | `(row) => ReactNode` | — | makes rows expandable; pair with the `expand` column |
|
|
101
101
|
| `showViewOptions` | `boolean` | — | column visibility menu above the table |
|
|
102
102
|
| `actions` | `(table, controls) => ReactNode` | — | toolbar content with access to the table (selected rows…); disabled while `loading`. `controls.focusTable()` moves the focus to the table |
|
|
@@ -85,6 +85,7 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
|
|
|
85
85
|
| `titleAs` | `'h1'` … `'h6'` \| `'div'` | `'h1'` | `ActivityContent`: the title's heading level |
|
|
86
86
|
| `inset` | `boolean` | `false` | `ActivityContent`: pads the content 1rem on each side. Off, it lines up with the app bar and the cards |
|
|
87
87
|
| `width` | `'full' \| 'form' \| 'reading'` | `'full'` | `ActivityContent`: caps the main column at 48rem (`'form'`) or 72ch (`'reading'`), the side panel right beside it |
|
|
88
|
+
| `notice` | `ReactNode` | — | `ActivityContent`: a notice above the column and the side panel, at their width, taking its own height; first on a phone |
|
|
88
89
|
| `aside`, `asideLabel` / `asideLabelledBy` | `ReactNode`, `string` | — | `ActivityContent`: a side panel, a named `complementary` landmark, beside the content from 1280px (CSS only), stacked below; sticky under the app bar |
|
|
89
90
|
| `asideSide` / `asideWidth` / `asideFrom` / `asideSticky` / `asideStack` | `'start' \| 'end'` / `'sm' \| 'md' \| 'lg' \| string` / `'lg' \| 'xl' \| '2xl'` / `boolean` / `'before' \| 'after'` | `'end'` / `'md'` / `'xl'` / `true` / `'after'` | `ActivityContent`: the panel's side, width (16, 20, 24rem), breakpoint, stickiness and stacking order (see the [app shell guide](../guides/app-shell.md#a-side-panel-and-a-readable-width)) |
|
|
90
91
|
| `align` | `'start' \| 'center'` | `'start'` | `ActivityLayout`, `AppSidebar`: the collapsed rail's entries sit right under the brand, or `'center'` for centred in the height below it |
|
|
@@ -735,7 +735,7 @@ reached with the arrow keys. The list scrolls sideways when the tabs do not
|
|
|
735
735
|
fit (its scrollbar hidden), which suits a phone or a narrow popover. While it
|
|
736
736
|
overflows, each side with hidden tabs fades and gets a small scroll button
|
|
737
737
|
("Scroll tabs left" / "Scroll tabs right", from `material.tabs.*`; out of the
|
|
738
|
-
tab order
|
|
738
|
+
tab order; on a touch screen 44px wide and as tall as the list, so it never covers content); the selected and the focused tab are
|
|
739
739
|
scrolled into view, instantly under reduced motion. A list that fits has
|
|
740
740
|
neither, and neither is in the server markup. `segmented` does the same.
|
|
741
741
|
|
|
@@ -382,7 +382,8 @@ import { LineChart } from '@nxgt/material/charts';
|
|
|
382
382
|
```
|
|
383
383
|
|
|
384
384
|
A line chart can be read against a reference: `bands` shade a range of the
|
|
385
|
-
value axis (a normal range
|
|
385
|
+
value axis (a normal range, constant or stepping along x with a row field or
|
|
386
|
+
function), `thresholds` draw labelled lines and `pointTone`
|
|
386
387
|
rings the values that need a word, which the tooltip, the summary and the
|
|
387
388
|
"View data" table repeat
|
|
388
389
|
([guide](../guides/charts.md#reading-values-against-a-reference)).
|
|
@@ -1294,6 +1294,25 @@ const RouterLink: AppShellLinkComponent = ({ href, ...props }) => <Link to={href
|
|
|
1294
1294
|
/>;
|
|
1295
1295
|
```
|
|
1296
1296
|
|
|
1297
|
+
### Compact trigger for an app bar
|
|
1298
|
+
|
|
1299
|
+
In the app bar's `titleAfter` slot the title already names the chosen record, so the full trigger repeats it. `size="compact"` renders an icon-only round button instead (32px, a 44x44 box on a coarse pointer). It is named by the label and the chosen option (`Project: Quarterly plan`, read to screen readers), the tooltip shows the option, and the panel (menu, popover or sheet) and the keyboard are unchanged. `size="default"` is the full trigger. Compact works with both variants; to compact only below `md`, pick the size with your own breakpoint hook (`size={isNarrow ? 'compact' : 'default'}`).
|
|
1300
|
+
|
|
1301
|
+
```tsx
|
|
1302
|
+
export const handle = {
|
|
1303
|
+
title: 'Quarterly plan',
|
|
1304
|
+
titleAfter: (
|
|
1305
|
+
<Switcher
|
|
1306
|
+
size="compact"
|
|
1307
|
+
label="Project"
|
|
1308
|
+
value="a"
|
|
1309
|
+
options={projects}
|
|
1310
|
+
getHref={(item) => `/projects/${item.id}`}
|
|
1311
|
+
/>
|
|
1312
|
+
),
|
|
1313
|
+
};
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1297
1316
|
### Expressive
|
|
1298
1317
|
|
|
1299
1318
|
`variant="expressive"` is a pill that opens a larger panel (always with a search). `recent` lists up to 3 option ids shown as tiles above the groups while nothing is typed; `badge` draws a soft badge after the title. On a phone the sheet is full height with a close button.
|
|
@@ -1328,6 +1347,7 @@ To put a Switcher at the start of a page header, pass it as [EntityHeader](data-
|
|
|
1328
1347
|
| `onValueChange` | `(value: string) => void` | — | |
|
|
1329
1348
|
| `label` | `ReactNode` | — | what is switched: names the trigger, the panel and the list |
|
|
1330
1349
|
| `variant` | `'default' \| 'expressive'` | `'default'` | tile trigger and menu, or pill and large panel |
|
|
1350
|
+
| `size` | `'default' \| 'compact'` | `'default'` | full trigger, or an icon-only round button for the app bar's `titleAfter` slot |
|
|
1331
1351
|
| `searchable` | `boolean` | from 8 options (always, expressive) | the search field of the panel; `true` also replaces the menu with the panel |
|
|
1332
1352
|
| `recent` | `string[]` | — | up to 3 option ids shown as tiles (expressive) |
|
|
1333
1353
|
| `getHref` | `(item) => string \| undefined` | — | link mode; falls back to `item.href` |
|
package/docs/guides/app-shell.md
CHANGED
|
@@ -925,6 +925,7 @@ export function BoardShell({ items }: { items: MenuItem[] }) {
|
|
|
925
925
|
| `titleAs` | `'h1'` … `'h6'` \| `'div'` | `'h1'` | The title's element: the screen's heading, `'h2'` under a heading of yours, `'div'` for none |
|
|
926
926
|
| `inset` | `boolean` | `false` | Pads the content 1rem on each side. Off, it lines up with the app bar and the cards |
|
|
927
927
|
| `width` | `'full'` \| `'form'` \| `'reading'` | `'full'` | Caps the main column: `'form'` 48rem, `'reading'` 72ch. The header card keeps the full width |
|
|
928
|
+
| `notice` | `ReactNode` | — | A notice (an `Alert`, a `Banner`) above the main column and the side panel, as wide as the two together; it follows the header card and stacks first on a phone |
|
|
928
929
|
| `aside` | `ReactNode` | — | A side panel: beside the content from `asideFrom`, stacked below it |
|
|
929
930
|
| `asideLabel` / `asideLabelledBy` | `string` | — | The panel's accessible name (`aria-label`), or the id of a heading inside it (`aria-labelledby`). One of them is needed |
|
|
930
931
|
| `asideSide` | `'start'` \| `'end'` | `'end'` | Which side of the content the panel sits on, wide |
|
|
@@ -973,6 +974,20 @@ decides the layout.
|
|
|
973
974
|
</ActivityContent>
|
|
974
975
|
```
|
|
975
976
|
|
|
977
|
+
```tsx
|
|
978
|
+
import { ActivityContent, Alert } from '@nxgt/material/components';
|
|
979
|
+
|
|
980
|
+
<ActivityContent
|
|
981
|
+
title="New visit"
|
|
982
|
+
width="form"
|
|
983
|
+
notice={<Alert variant="warning" title="Confirm your address" />}
|
|
984
|
+
aside={<DuplicateHints />}
|
|
985
|
+
asideLabel="Possible duplicates"
|
|
986
|
+
>
|
|
987
|
+
<VisitForm />
|
|
988
|
+
</ActivityContent>
|
|
989
|
+
```
|
|
990
|
+
|
|
976
991
|
- **Landmark.** The panel is an `<aside>`, a `complementary` landmark: name it
|
|
977
992
|
with `asideLabel`, or with `asideLabelledBy` (the id of its heading). Its
|
|
978
993
|
content is the panel's own; give it one `h2` when it has a heading.
|
|
@@ -987,6 +1002,13 @@ decides the layout.
|
|
|
987
1002
|
the main column and the pair stays start-aligned: at 1920px the panel is 1rem
|
|
988
1003
|
from the fields, not at the window's far edge. The header card above keeps
|
|
989
1004
|
the full width. `width` also works without a panel.
|
|
1005
|
+
- **A notice above both.** `notice` renders an `Alert` or a `Banner` right
|
|
1006
|
+
under the header card, across the main column and the panel (with
|
|
1007
|
+
`width="form"`, exactly their width), and it takes its own height in the
|
|
1008
|
+
layout, so a page that centres its content needs no guessed offset. On a
|
|
1009
|
+
phone it comes first, then the column, then the panel (`asideStack`
|
|
1010
|
+
unchanged). Without `notice` the layout is unchanged. It works without a
|
|
1011
|
+
panel too.
|
|
990
1012
|
- **The breakpoint is the window's.** `asideFrom` follows the viewport, not the
|
|
991
1013
|
content column, which the sidebar and the gutters narrow a little: pick `'lg'`
|
|
992
1014
|
for a panel on a smaller window, `'2xl'` to keep it stacked on all but the
|
|
@@ -48,6 +48,41 @@ only the surface and the content differ. Let the text and the meta wrap
|
|
|
48
48
|
`renderTile` replaces `renderCard` there (`renderCard` is not read, and the
|
|
49
49
|
reverse), and `item.Title` is rendered once per tile.
|
|
50
50
|
|
|
51
|
+
### A compact record row in a tile
|
|
52
|
+
|
|
53
|
+
For a list of records (a name, a note, a few tags), `item.Row` lays the tile
|
|
54
|
+
out as the dense row of `ListTile` (`size="sm"` with `chips`), so you do not
|
|
55
|
+
hand-lay two lines. The tile keeps its checkbox, its link and its states; the
|
|
56
|
+
title is the one link and keeps the heading level you give it with `as`.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<ServerTable
|
|
60
|
+
layout={{ phone: 'tiles', wide: 'table' }}
|
|
61
|
+
getRowHref={(person) => `/people/${person.id}`}
|
|
62
|
+
renderTile={(person, { Row }) => (
|
|
63
|
+
<Row
|
|
64
|
+
as="h2"
|
|
65
|
+
title={person.name}
|
|
66
|
+
subtitle={person.role}
|
|
67
|
+
chips={<Chip size="sm" label={person.status} />}
|
|
68
|
+
trailing={<span>{person.room}</span>}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
// ...
|
|
72
|
+
/>;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`Row` takes `title`, `as` (`h2`, `h3`, `h4` or `span`, default `span`),
|
|
76
|
+
`subtitle`, `chips`, `trailing`, `leading` and `chevron`. It replaces
|
|
77
|
+
`Title`: render one or the other, once. The row measures its own width: the
|
|
78
|
+
subtitle sits on the title's line from 28rem and the chips after the text from
|
|
79
|
+
36rem, and below that they stack under the title. A one-line row is 36px tall
|
|
80
|
+
under a mouse and 44px on a coarse pointer, against 72px for a free tile.
|
|
81
|
+
`chips` and `trailing` are decoration, never buttons; a link or button row
|
|
82
|
+
(`getRowHref` or `onRowClick`) gets a decorative chevron (`chevron={false}`
|
|
83
|
+
removes it). `renderCard` is handed `Row` too: the same row inside the card's
|
|
84
|
+
own padding and border, with no height of its own.
|
|
85
|
+
|
|
51
86
|
## One layout on a phone, another from 768px
|
|
52
87
|
|
|
53
88
|
`layout` also takes `{ phone, wide }`: the layout below 768px and the one from
|
|
@@ -94,13 +129,14 @@ only the title is interactive.
|
|
|
94
129
|
On a coarse pointer the title link also keeps a 44px box of its own, clear of
|
|
95
130
|
the row-select checkbox, however short the title.
|
|
96
131
|
|
|
97
|
-
`item` also carries `
|
|
98
|
-
leaves out, and
|
|
132
|
+
`item` also carries `Row` (the compact record row above), `selected`,
|
|
133
|
+
`locked` and `reason` (a row `enableRowSelection` leaves out, and
|
|
134
|
+
`rowSelectionDisabledReason`'s answer).
|
|
99
135
|
|
|
100
136
|
The types, from `@nxgt/material/components`: `DataTableItem` (the `item`
|
|
101
137
|
argument), `DataTableLayout` (`'table' | 'cards' | 'tiles'`),
|
|
102
|
-
`
|
|
103
|
-
`layout` takes: either) and `DataTableItemTitleElement` (`Title`'s `as`).
|
|
138
|
+
`DataTableItemRowProps` (`Row`'s props), `DataTableResponsiveLayout`
|
|
139
|
+
(`{ phone, wide }`), `DataTableLayoutProp` (what `layout` takes: either) and `DataTableItemTitleElement` (`Title`'s `as`).
|
|
104
140
|
|
|
105
141
|
## In a `ServerTable`
|
|
106
142
|
|
package/docs/guides/charts.md
CHANGED
|
@@ -240,11 +240,39 @@ const toneOf = (point: ChartPoint) =>
|
|
|
240
240
|
|
|
241
241
|
| Prop | Effect |
|
|
242
242
|
| --- | --- |
|
|
243
|
-
| `bands` | `{ from, to, label, tone? }[]`: a wash across the plot behind the lines, with its `label` inside it at the left. `from` and `to` may come in either order. |
|
|
244
|
-
| `thresholds` | `{ value, label, tone?, dash? }[]`: a horizontal line behind the series with its `label` above it at the right end; dashed unless `dash: false`. |
|
|
243
|
+
| `bands` | `{ from, to, label, tone? }[]`: a wash across the plot behind the lines, with its `label` inside it at the left. `from` and `to` may come in either order, and are a number, a row field or a function of the row (a stepped band; typed `ChartRowValue`). |
|
|
244
|
+
| `thresholds` | `{ value, label, tone?, dash? }[]` (`value`: a number, a row field or a function of the row): a horizontal line behind the series with its `label` above it at the right end; dashed unless `dash: false`. |
|
|
245
245
|
| `pointTone` | `(point: ChartPoint, series: ChartSeries) => Tone \| undefined`: the tone of one value. It is given the point `onPointClick` gets, so `point.index` is the row of the full `data` even when the chart is zoomed. |
|
|
246
246
|
| `toneLabels` | `{ [tone]: word }`: the word of each tone, in your language and domain. Unset, the library's neutral word ("Error", translated by `material.charts.tone.*`); an app tone you declared falls back to its own name. |
|
|
247
247
|
|
|
248
|
+
**A band or a line that changes along x.** When each row carries its own
|
|
249
|
+
range (a normal range copied with a measurement, which changes with an age
|
|
250
|
+
band or a catalogue edit), give `from` and `to` as the name of a row field, or
|
|
251
|
+
as a function of the row; a threshold's `value` takes the same. The band then
|
|
252
|
+
steps along x: each row owns the stretch up to the midpoint to its neighbours,
|
|
253
|
+
rows with the same range merge into one block, and a row that gives no number
|
|
254
|
+
leaves a gap. A number keeps the constant band, and so does a field or function
|
|
255
|
+
that gives every row the same range.
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
// rows: { day: 'Day 1', reading: 48, low: 40, high: 60, limit: 70 }
|
|
259
|
+
<LineChart
|
|
260
|
+
title="Daily readings"
|
|
261
|
+
data={rows}
|
|
262
|
+
x="day"
|
|
263
|
+
series={[{ key: 'reading', label: 'Reading' }]}
|
|
264
|
+
bands={[{ from: 'low', to: 'high', label: 'Normal range', tone: 'success' }]}
|
|
265
|
+
thresholds={[{ value: (row) => row.limit as number, label: 'Upper limit', tone: 'error' }]}
|
|
266
|
+
/>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The text names every range with the x labels it covers, in the summary and
|
|
270
|
+
under "View data" (`Normal range: 40 to 60 (Day 1 to Day 6), 45 to 70 (Day 7 to
|
|
271
|
+
Day 12)`), and the count of values outside a band uses each row's own range
|
|
272
|
+
(a row with no range is not counted). The axis grows to the whole span, a
|
|
273
|
+
label is drawn once, in the widest stretch (a threshold's at the end of the
|
|
274
|
+
last one), and over a `zoom` window the blocks follow the window's rows.
|
|
275
|
+
|
|
248
276
|
**The value axis holds them.** Unless `yAxis.min` and `yAxis.max` pin it, the
|
|
249
277
|
axis grows to include every band end and threshold, so none is out of view.
|
|
250
278
|
With a pinned axis a band is clipped to the plot and a threshold outside it is
|
package/docs/guides/data-grid.md
CHANGED
|
@@ -15,6 +15,7 @@ import { DataGrid, type DataGridHandle } from '@nxgt/material/components';
|
|
|
15
15
|
## Contents
|
|
16
16
|
|
|
17
17
|
- [Columns and `meta`](#columns-and-meta)
|
|
18
|
+
- [One column, several kinds of row](#one-column-several-kinds-of-row)
|
|
18
19
|
- [Locked and read-only rows](#locked-and-read-only-rows)
|
|
19
20
|
- [Saving](#saving)
|
|
20
21
|
- [Versions and conflicts](#versions-and-conflicts)
|
|
@@ -42,9 +43,10 @@ Columns are DataTable's TanStack v9 column definitions. A column with no
|
|
|
42
43
|
| `frozen` | Keeps a leading column at the inline start while the rest scrolls |
|
|
43
44
|
| `lockPosition` | `'start'` or `'end'`: the column keeps its edge when columns are reordered ([columns](#columns-resize-hide-and-reorder)) |
|
|
44
45
|
| `editor` | `'text'`, `'number'`, `'select'`, `'date'`, `'checkbox'`, or your own component ([custom editor](#a-custom-editor)) |
|
|
45
|
-
| `
|
|
46
|
+
| `rowEditor(row)` | The built-in editor (`'text'`, `'number'`, `'select'`, `'date'`, `'checkbox'`) a row's cell has, for a column that mixes kinds ([one column, several kinds of row](#one-column-several-kinds-of-row)). It wins over `editor` |
|
|
47
|
+
| `number` | `{ min, max, decimals }` for the number editor; `min`, `max` and `decimals` may be functions of the row. A value outside them, or with too many decimals, is an error: it is never rounded or clamped |
|
|
46
48
|
| `codes` | Symbolic values the number editor also takes ("abs", "exempt"), with the label each language shows and the aliases it accepts |
|
|
47
|
-
| `options` | The select editor's `{ value, label }` list. The cell shows and copies the label; a typed or pasted text matches a label or a value, in any case and without accents |
|
|
49
|
+
| `options` | The select editor's `{ value, label }` list, or a function of the row returning it. The cell shows and copies the label; a typed or pasted text matches a label or a value, in any case and without accents |
|
|
48
50
|
| `date` | `{ min, max }` as ISO dates (`'2026-09-01'`). The value is an ISO date, shown and read in the page's locale |
|
|
49
51
|
| `parse(text, ctx)` | Text to value, replacing the editor's own: `{ value }` or `{ error }`. `ctx` has `lang`, `locale` and `row`. `{ value: null }` clears the cell, whatever the column's value type |
|
|
50
52
|
| `format(value, ctx)` | Value to text, shown in the cell and in the editor |
|
|
@@ -129,6 +131,44 @@ answered by then refuses the paste with the same message. Set `checkTimeout` to
|
|
|
129
131
|
<DataGrid checkTimeout={5000} /* … */ />
|
|
130
132
|
```
|
|
131
133
|
|
|
134
|
+
## One column, several kinds of row
|
|
135
|
+
|
|
136
|
+
A result-entry sheet has one « Result » column whose rows differ: a quantity
|
|
137
|
+
(a number, held to that row's own min, max and decimals), a coded value (a
|
|
138
|
+
pick among that row's own options) and a free text. Give the column
|
|
139
|
+
`rowEditor`, and let `number` and `options` read the row:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
type Result = {
|
|
143
|
+
id: string;
|
|
144
|
+
kind: 'quantity' | 'coded' | 'text';
|
|
145
|
+
value: number | string | null;
|
|
146
|
+
min?: number;
|
|
147
|
+
max?: number;
|
|
148
|
+
decimals?: number;
|
|
149
|
+
options?: { value: string; label: string }[];
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
helper.accessor('value', {
|
|
153
|
+
id: 'result',
|
|
154
|
+
header: 'Result',
|
|
155
|
+
meta: {
|
|
156
|
+
rowEditor: (row) =>
|
|
157
|
+
row.kind === 'quantity' ? 'number' : row.kind === 'coded' ? 'select' : 'text',
|
|
158
|
+
number: { min: (r) => r.min ?? 0, max: (r) => r.max ?? 0, decimals: (r) => r.decimals ?? 0 },
|
|
159
|
+
options: (row) => row.options ?? [],
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Everything reads the column for the row: typing and the editors, a pasted or
|
|
165
|
+
filled text, Delete, undo, copy, the phone's row panel and the conflict
|
|
166
|
+
panel. `rowEditor` returns a built-in name only; a custom component stays
|
|
167
|
+
`editor`'s. The column's `className`, `format`, `parse`, `validate` and
|
|
168
|
+
`schema` stay column-wide. A column with `rowEditor` copies on fill and never
|
|
169
|
+
continues a series, and its totals use the `decimals` given as a number. A row
|
|
170
|
+
that must not be edited at all is `readOnly` or `locked` as usual.
|
|
171
|
+
|
|
132
172
|
## Locked and read-only rows
|
|
133
173
|
|
|
134
174
|
`rowReadOnly(row)` and `rowLocked(row)` return `false`, `true` or
|
package/docs/guides/forms.md
CHANGED
|
@@ -783,6 +783,41 @@ form.setFocus('email'); // or without an error
|
|
|
783
783
|
- If focus does not move, see
|
|
784
784
|
[`setError(…, { shouldFocus: true })` does nothing](../troubleshooting.md#seterror--shouldfocus-true--or-a-failed-submit-does-not-focus-the-field).
|
|
785
785
|
|
|
786
|
+
## A form that is quick to fill
|
|
787
|
+
|
|
788
|
+
`useFastForm` and `useFocusOnRefusal` (`@nxgt/material/components`) are the
|
|
789
|
+
two habits of a record form, in one place. `useFastForm(props, first, active?)`
|
|
790
|
+
is `useForm` with `mode: 'onTouched'` (an error shows once the user leaves the
|
|
791
|
+
field, then follows each change; a `mode` in `props` wins) that also focuses
|
|
792
|
+
the `first` field when the form opens, so typing starts at once. It waits a
|
|
793
|
+
frame and retries for about half a second, so a dialog's focus trap has
|
|
794
|
+
settled; it stops as soon as the focus moved. `active` (default `true`) is for
|
|
795
|
+
a hook that stays mounted while its dialog is closed: it focuses each time
|
|
796
|
+
`active` turns true.
|
|
797
|
+
|
|
798
|
+
`useFocusOnRefusal<T>(message, busy)` returns a ref for the submit control: once
|
|
799
|
+
a refusal shows (`message` set) and nothing is running (`busy` false), the
|
|
800
|
+
control takes the focus back, so it never falls to the page after the button
|
|
801
|
+
was disabled during the request.
|
|
802
|
+
|
|
803
|
+
```tsx
|
|
804
|
+
import { Button, TextFormField, useFastForm, useFocusOnRefusal } from '@nxgt/material/components';
|
|
805
|
+
|
|
806
|
+
export function SignUp({ refusal, busy, save }: Props) {
|
|
807
|
+
const form = useFastForm<{ email: string }>({ defaultValues: { email: '' } }, 'email');
|
|
808
|
+
const submit = useFocusOnRefusal<HTMLButtonElement>(refusal, busy);
|
|
809
|
+
return (
|
|
810
|
+
<form onSubmit={form.handleSubmit(save)}>
|
|
811
|
+
<TextFormField control={form.control} name="email" label="Email" />
|
|
812
|
+
<Button ref={submit} type="submit" loading={busy}>Sign up</Button>
|
|
813
|
+
</form>
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
Storybook: `Guides/Forms/useFastForm`. If the focus does not move, see
|
|
819
|
+
[`useFastForm` does not focus the first field](../troubleshooting.md#usefastform-does-not-focus-the-first-field).
|
|
820
|
+
|
|
786
821
|
## Server errors
|
|
787
822
|
|
|
788
823
|
Map an API's field errors onto the form with `setError`, and put anything that
|
package/docs/guides/helpers.md
CHANGED
|
@@ -265,7 +265,7 @@ flag. Storybook: `Guides/Hooks/useSubmitOnce`.
|
|
|
265
265
|
| `formatMessage(message, language, values?, { onError }?)` | Formats one ICU message (`{name}`, plurals, selects) in any BCP 47 `language` with the translator's engine, for a message that is not in a bundle (a server-side catalogue). Cached per message; one that does not format is logged and returned unformatted, or passed to `onError` as a `FormatMessageError` (reason, key, source) without logging. |
|
|
266
266
|
| `createTypedTranslation(translate)` | Turns a translator into a typed `useTranslation()` hook. |
|
|
267
267
|
| `translate`, `useMaterialTranslation()` | Translator / hook bound to the package's own `material.*` keys. `useMaterialTranslation().t(key, fallback)` returns `fallback` without a provider. |
|
|
268
|
-
| `translateMaterial(language, key, fallback, values?)` | The same as `useMaterialTranslation().t`, with the language passed as a value: no provider, no hook. For a PDF `footer` function or a server. `'fr-CA'` reads `fr`; an unknown language formats `fallback`. |
|
|
268
|
+
| `translateMaterial(language, key, fallback, values?, resources?)` | The same as `useMaterialTranslation().t`, with the language passed as a value: no provider, no hook. `resources` is your merged bundle (stable object), read before the base one. For a PDF `footer` function or a server. `'fr-CA'` reads `fr`; an unknown language formats `fallback`. |
|
|
269
269
|
| `getLanguage()` | Language read from `localStorage`, else `FALLBACK_LANGUAGE`. |
|
|
270
270
|
| `LANGUAGE_KEY`, `FALLBACK_LANGUAGE`, `SUPPORTED_LANGUAGES` | Storage key (`'language'`), fallback (`'en'`) and supported languages (`en`, `fr`). |
|
|
271
271
|
|
package/docs/guides/i18n.md
CHANGED
|
@@ -63,6 +63,7 @@ function translateMaterial(
|
|
|
63
63
|
key: MaterialLocaleKey,
|
|
64
64
|
fallback: string,
|
|
65
65
|
values?: TranslationContext,
|
|
66
|
+
resources?: ResourceBundle, // your merged bundle, a stable object (module scope)
|
|
66
67
|
): string;
|
|
67
68
|
```
|
|
68
69
|
|
|
@@ -189,6 +190,7 @@ if (failure) {
|
|
|
189
190
|
| `initialLanguage` | `Language` | — | Used as-is for the first render when supported, and `localStorage` is not read. Set it in SSR. See [below](#server-rendering) |
|
|
190
191
|
| `storageKey` | `string` | `'language'` | `localStorage` key the choice is read from and written to |
|
|
191
192
|
| `supportedLanguages` | `Language[]` | `['en', 'fr']` | What `changeLanguage` accepts and `LanguageSwitcher` lists |
|
|
193
|
+
| `resources` | `ResourceBundle` | — | Your `mergeResources(...)` result: the components read their `material.*` words from it before the base bundle. Pass a stable object (module scope) |
|
|
192
194
|
| `onLanguageChange` | `(language) => void` | — | Called after a successful change: save it to a cookie, a profile, analytics |
|
|
193
195
|
|
|
194
196
|
`changeLanguage` with an unsupported value logs
|
|
@@ -208,7 +210,8 @@ const context = useOptionalI18nContext(); // null outside the provider
|
|
|
208
210
|
third language is not in the type today, so adding one to
|
|
209
211
|
`supportedLanguages` takes a cast. The runtime accepts it: your translator
|
|
210
212
|
uses your strings for it, and the components fall back to their English copy,
|
|
211
|
-
formatted.
|
|
213
|
+
formatted. A third language you define under `material.*` and give through
|
|
214
|
+
`resources` now reaches the components.
|
|
212
215
|
|
|
213
216
|
```tsx
|
|
214
217
|
const LANGUAGES = ['en', 'fr', 'de'] as Language[];
|
|
@@ -498,8 +501,8 @@ material.emoji.groups.flags; // "Flags" / "Drapeaux"
|
|
|
498
501
|
|
|
499
502
|
A group your `EmojiItem`s name that is not in this list (`hand_signs`) reads
|
|
500
503
|
as its words in English (`Hand signs`). As for every `material.*` key above,
|
|
501
|
-
|
|
502
|
-
the
|
|
504
|
+
your own translator sees a key you add; the components see it too once you hand
|
|
505
|
+
them the merged resources (see "Overriding a component's words" below).
|
|
503
506
|
|
|
504
507
|
```tsx
|
|
505
508
|
// Under <I18nProvider defaultLanguage="fr">
|
|
@@ -509,11 +512,34 @@ the tab does not.
|
|
|
509
512
|
<Spinner variant="4" /> // role="img" aria-label="Chargement..."
|
|
510
513
|
```
|
|
511
514
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
515
|
+
### Overriding a component's words
|
|
516
|
+
|
|
517
|
+
Give the provider the merged resources and every component translates through
|
|
518
|
+
them, so an override under `material.*` changes what it renders:
|
|
519
|
+
|
|
520
|
+
```tsx
|
|
521
|
+
import { mergeResources, resources as materialResources } from '@nxgt/material/i18n';
|
|
522
|
+
|
|
523
|
+
const resources = mergeResources(materialResources, {
|
|
524
|
+
en: { material: { 'recovery-codes-status': { title: 'Backup codes' } } },
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
<I18nProvider defaultLanguage="en" resources={resources}>
|
|
528
|
+
<RecoveryCodesStatus remaining={3} onRegenerate={regenerate} /> {/* title: "Backup codes" */}
|
|
529
|
+
</I18nProvider>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Without `resources`, the components read the package's own bundle: an override
|
|
533
|
+
is visible only through **your** translator, and defining `material.command.title`
|
|
534
|
+
does not change the title `CommandDialog` renders. Pass a stable object
|
|
535
|
+
(module scope), not one built in a render. A component's `labels` prop still
|
|
536
|
+
wins over both. Outside a component, `translateMaterial` takes the merged
|
|
537
|
+
resources as a fifth argument and `zodLocaleError` takes `translate`. Toasts
|
|
538
|
+
follow `resources` too: `toast()`, its presets and the close label run outside
|
|
539
|
+
React, so the mounted `I18nProvider` hands them its `resources` and language
|
|
540
|
+
(with several providers, the last one mounted wins). They follow that
|
|
541
|
+
provider's language as well as its resources, which can differ from the
|
|
542
|
+
language of an inner subtree.
|
|
517
543
|
|
|
518
544
|
Each time `mergeResources` runs outside production, overlapping leaves are
|
|
519
545
|
reported, one line per language:
|
|
@@ -62,7 +62,8 @@ in `@nxgt/material-core/two-factor`.
|
|
|
62
62
|
What stays in the app: every request, the mapping of a server error to the
|
|
63
63
|
props above, where the challenge is kept, the replay of the action a step-up
|
|
64
64
|
unblocked, the leave guard around the new recovery codes, and all wording
|
|
65
|
-
(`labels`, or your translations under `material
|
|
65
|
+
(`labels`, or your translations under `material.*`, given to
|
|
66
|
+
`I18nProvider` as `resources`, see [i18n](i18n.md#overriding-a-components-words)).
|
|
66
67
|
|
|
67
68
|
## On a phone and for assistive technology
|
|
68
69
|
|
|
Binary file
|
|
Binary file
|