@nxgt/material 3.52.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 +28 -0
- package/README.md +5 -4
- package/dist/{async-slice-BGtmF3K5.js → async-slice-DDSsekfm.js} +2 -2
- package/dist/{button-Ct0KRBsN.js → button-Ct8DSLVv.js} +1 -1
- package/dist/charts.js +1064 -932
- package/dist/{chip-Ca1SEd70.js → chip-BzoNsd-W.js} +2 -2
- package/dist/{code-editor-DtwRhK8B.js → code-editor-DsLQeiZc.js} +6 -6
- package/dist/code-editor.js +5 -5
- package/dist/{code-language-items-CEclGxhA.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-CzELzsOi.js → components-DZPMvc1w.js} +15976 -15821
- package/dist/components.js +26 -26
- package/dist/{dialog-BIy04Hpd.js → dialog-DwPmwmA3.js} +4 -4
- package/dist/dnd.js +1 -1
- package/dist/draw-animate.js +1 -1
- package/dist/{dropdown-menu-B0Kd8x3G.js → dropdown-menu-CDN6Nqx6.js} +1 -1
- package/dist/editor.js +9 -9
- package/dist/{empty-state-CKxORwFE.js → empty-state-Bb9W9did.js} +2 -2
- package/dist/{escape-owner-BGKjiglh.js → escape-owner-DI_lg2ID.js} +1 -1
- package/dist/{field-blur-Dz1zPaYK.js → field-blur-BcJH8H9B.js} +1 -1
- package/dist/{field-blur-context-1T6GkSNc.js → field-blur-context-Dg1nwWOV.js} +1 -1
- package/dist/hooks.js +2 -2
- package/dist/{hover-card-BwQ9IRxa.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-D5Ns7ndZ.js → icon-badge-DVPBQOKe.js} +3 -3
- package/dist/{icon-button-VzdqY_Tv.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/server-table-band-classes.d.ts +5 -0
- 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/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/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/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/translator.d.ts +1 -1
- 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 +2 -2
- package/dist/{public-D-J3nZFm.js → public-BoVE0ffh.js} +9 -9
- package/dist/{rich-code-block-Ig0ERAa2.js → rich-code-block-DAUM7Yh_.js} +5 -5
- package/dist/{sortable-provider-DfPkSN90.js → sortable-provider-BLXsGE_0.js} +2 -2
- package/dist/{spinner-KQb3YuR_.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-BY-SpsFV.js → theme-toggle-jVdxF_mc.js} +1 -1
- package/dist/{toolbar-items-30w9rG5h.js → toolbar-items-BFY_hns5.js} +4 -4
- package/dist/{translate-material-BPl8TQLg.js → translate-material-q-JNxP1m.js} +2 -2
- package/dist/{translator-BnQXtpSZ.js → translator-BU1o3_l9.js} +1 -1
- package/dist/{use-chat-composer-BSKJRYMB.js → use-chat-composer-DGbDuIUa.js} +3758 -3716
- package/dist/{use-heading-spy-BgXA8uz6.js → use-heading-spy-CP63sAcS.js} +1 -1
- package/dist/{use-material-translation-ByM3gD9R.js → use-material-translation-nNF8fUXz.js} +2 -2
- package/dist/use-shape-morph-DXdy2X5g.js +35 -0
- package/docs/components/buttons-and-actions.md +30 -1
- package/docs/components/data-table-filter-and-lists.md +2 -2
- package/docs/components/navigation-and-layout.md +1 -0
- 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/i18n.md +6 -2
- package/docs/roadmap.md +84 -684
- package/docs/troubleshooting.md +21 -0
- 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-BFNrM0Qu.js +0 -45
- 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";
|
|
@@ -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-DlVPS58N.js";
|
|
2
|
+
import { t } from "./translate-material-q-JNxP1m.js";
|
|
3
3
|
import { useCallback as n } from "react";
|
|
4
4
|
//#region lib/i18n/use-material-translation.ts
|
|
5
5
|
function r() {
|
|
@@ -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
|
|
|
@@ -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 |
|
|
@@ -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/i18n.md
CHANGED
|
@@ -534,8 +534,12 @@ is visible only through **your** translator, and defining `material.command.titl
|
|
|
534
534
|
does not change the title `CommandDialog` renders. Pass a stable object
|
|
535
535
|
(module scope), not one built in a render. A component's `labels` prop still
|
|
536
536
|
wins over both. Outside a component, `translateMaterial` takes the merged
|
|
537
|
-
resources as a fifth argument and `zodLocaleError` takes `translate
|
|
538
|
-
toast presets and the
|
|
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.
|
|
539
543
|
|
|
540
544
|
Each time `mergeResources` runs outside production, overlapping leaves are
|
|
541
545
|
reported, one line per language:
|