@nxgt/material 3.80.0 → 3.81.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 +46 -0
- package/README.md +3 -2
- package/dist/{alert-dialog-DgHtXZuB.js → alert-dialog-DOZ0SfQs.js} +1 -1
- package/dist/{async-slice-YBQsGtQT.js → async-slice-BfVokA46.js} +1 -1
- package/dist/{button-_vm-xSBU.js → button-CSKUtMI3.js} +2 -2
- package/dist/{calculator-CTd_S23A.js → calculator-Cv00UQgR.js} +10 -10
- package/dist/calculator-field.js +6 -6
- package/dist/{calculator-segment-OI1BT3bA.js → calculator-segment-CwnC2cV_.js} +1 -1
- package/dist/calculator.js +1 -1
- package/dist/charts.js +3 -3
- package/dist/{chip-DumcVzUO.js → chip-BGn0V6Pt.js} +3 -3
- package/dist/{code-editor-CiwgfSOK.js → code-editor-B25-mWhb.js} +4 -4
- package/dist/code-editor.js +4 -4
- package/dist/{code-language-items-Dip0xnmc.js → code-language-items-oRjtO3qV.js} +2 -2
- package/dist/components/base-content.css +11 -0
- package/dist/components/components.css +2 -0
- package/dist/components/ui/collections/data-table/data-table-items-tiles.css +27 -1
- package/dist/components/ui/display/tube-cap/tube-cap.css +87 -0
- package/dist/{components-CJFHhkZv.js → components-B00kDHzJ.js} +19168 -18815
- package/dist/components.js +37 -37
- package/dist/{conversion-variant-CqrPwPjC.js → conversion-variant-wZd-G6xR.js} +3 -3
- package/dist/{currency-field-CMFvctye.js → currency-field-DizBTAat.js} +4 -4
- package/dist/{date.utils-DUvqcjan.js → date.utils-JTEIo3mM.js} +98 -98
- package/dist/{dialog-B0iU3fmh.js → dialog-B-3s-oXt.js} +3 -3
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-CPOgcajv.js → dropdown-menu-C6rcYGB9.js} +1 -1
- package/dist/editor.js +19 -19
- package/dist/{empty-state-D3Jggupu.js → empty-state-CSkghnD9.js} +1 -1
- package/dist/{financial-variant-BdyfYhnp.js → financial-variant-igpGRbGw.js} +5 -5
- package/dist/{hooks-Z7KKbv4G.js → hooks-DvIsom2p.js} +2 -2
- package/dist/hooks.js +9 -8
- package/dist/{hover-card-CFGIX6kg.js → hover-card-DuaVVKMb.js} +1 -1
- package/dist/i18n.js +3 -3
- package/dist/{icon-badge-kxGWUvcO.js → icon-badge-3ThP6EOl.js} +1 -1
- package/dist/{icon-button-BamxMwZs.js → icon-button-4PKlaI1g.js} +2 -2
- package/dist/lib/components/ui/actions/button/button-tones.d.ts +17 -16
- package/dist/lib/components/ui/collections/day-grouped-list/day-grouped-list.d.ts +6 -1
- package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +2 -0
- package/dist/lib/components/ui/collections/server-table/types-states.d.ts +5 -1
- package/dist/lib/components/ui/display/index.d.ts +1 -0
- package/dist/lib/components/ui/display/tube-cap/index.d.ts +2 -0
- package/dist/lib/components/ui/display/tube-cap/tube-cap.d.ts +27 -0
- package/dist/lib/components/ui/display/tube-cap/tube-cap.kinds.d.ts +7 -0
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/fields/repeatable-group/partials/repeatable-group-row.d.ts +3 -1
- package/dist/lib/components/ui/fields/repeatable-group/partials/repeatable-group-rows.d.ts +2 -1
- package/dist/lib/components/ui/fields/repeatable-group/repeatable-group.d.ts +1 -1
- package/dist/lib/components/ui/fields/repeatable-group/types.d.ts +7 -0
- package/dist/lib/components/ui/media/galleries/gallery.d.ts +1 -1
- package/dist/lib/components/ui/media/galleries/lightbox-gallery.d.ts +1 -1
- package/dist/lib/components/ui/media/galleries/mini-gallery.d.ts +1 -1
- package/dist/lib/components/ui/overlays/dialog/dialog-parts.d.ts +1 -1
- package/dist/lib/components/ui/overlays/sheet/sheet-parts.d.ts +5 -0
- package/dist/lib/components/ui/selection/choice-card-group/choice-card-balance.d.ts +4 -0
- package/dist/lib/components/ui/selection/choice-card-group/choice-card-group.d.ts +3 -1
- package/dist/lib/components/ui/selection/choice-card-group/choice-card-group.types.d.ts +7 -0
- package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-body.d.ts +5 -1
- package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-row-parts.d.ts +2 -1
- package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-row-props.d.ts +8 -5
- package/dist/lib/components/ui/selection/sortable-list/partials/use-owed-focus.d.ts +10 -0
- package/dist/lib/components/ui/selection/sortable-list/sortable-list-context.d.ts +2 -0
- package/dist/lib/components/ui/selection/sortable-list/sortable-list.keyboard.d.ts +1 -1
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar-context.d.ts +3 -0
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar-shell.d.ts +15 -0
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar-template.d.ts +9 -0
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar.d.ts +7 -3
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar.template.d.ts +41 -0
- package/dist/lib/components/ui/widgets/event-calendar/index.d.ts +1 -0
- package/dist/lib/components/ui/widgets/event-calendar/partials/calendar-date-nav.d.ts +2 -0
- package/dist/lib/components/ui/widgets/event-calendar/partials/calendar-lane-toggle.d.ts +5 -0
- package/dist/lib/components/ui/widgets/event-calendar/partials/time-grid-header-template.d.ts +7 -0
- package/dist/lib/components/ui/widgets/event-calendar/template.types.d.ts +73 -0
- package/dist/lib/components/ui/widgets/event-calendar/types.d.ts +2 -0
- package/dist/lib/components/ui/widgets/event-calendar/use-chip-event.d.ts +1 -0
- package/dist/lib/components/ui/widgets/event-calendar/use-drawn-events.d.ts +4 -3
- package/dist/lib/components/ui/widgets/event-calendar/use-template-calendar.d.ts +18 -0
- package/dist/lib/components/ui/widgets/event-calendar/use-template-lane.d.ts +17 -0
- package/dist/lib/hooks/index.d.ts +1 -0
- package/dist/lib/i18n/resources/en/en.d.ts +2 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +2 -0
- package/dist/lib/i18n/resources/fr/fr.d.ts +2 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +2 -0
- package/dist/lib/i18n/resources/resources.d.ts +4 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib.js +2 -2
- package/dist/main.js +53 -52
- package/dist/map.js +7 -7
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/{percent-field-C2LNr8NA.js → percent-field-QwU1l2Q4.js} +1 -1
- package/dist/{picker-surface-BUCFMoAZ.js → picker-surface-CiTycWgV.js} +4 -4
- package/dist/{popover-DFRwQF12.js → popover-YmbzbKQG.js} +1 -1
- package/dist/{pos-types-CXnEdjdy.js → pos-types-BFrapNmx.js} +1 -1
- package/dist/{pos-variant-DMwn_7is.js → pos-variant-CctfqieO.js} +4 -4
- package/dist/print.js +1 -1
- package/dist/{programmer-variant-Bo4eiDFi.js → programmer-variant-Dd0YOixn.js} +2 -2
- package/dist/{public-CyQPkkSR.js → public-CshR_aW7.js} +10 -10
- package/dist/{rich-code-block-iK7UcZdc.js → rich-code-block-BuJLDoBu.js} +5 -5
- package/dist/{scientific-variant-B9ScTgfx.js → scientific-variant-CjErQxuZ.js} +2 -2
- package/dist/{sortable-provider-BZwVjSVU.js → sortable-provider-DU2nvpFc.js} +2 -2
- package/dist/{spinner-RITOxvpH.js → spinner-DH1mm8hH.js} +1 -1
- package/dist/{tabs-DSo4eCUn.js → tabs-Um0SFyf1.js} +1 -1
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle-3gbzQX0x.js → theme-toggle-C-Ih4uQU.js} +1 -1
- package/dist/{toolbar-items-Bt5RK-Nx.js → toolbar-items-BoEY5bkt.js} +4 -4
- package/dist/{translate-material-CZTGqgen.js → translate-material-D4rn5UaQ.js} +1 -1
- package/dist/{translator-DTlOlIoq.js → translator-CcEgQ-H7.js} +4 -0
- package/dist/{use-chat-composer-DBY01IpX.js → use-chat-composer-BBF1BI_s.js} +46 -42
- package/dist/{use-click-lock-BZYtTmVt.js → use-click-lock-Bwg_caHO.js} +14 -14
- package/dist/{use-heading-spy-Dt48bBts.js → use-heading-spy-BgyA6SJl.js} +19 -19
- package/dist/{use-material-translation-BJucC_xe.js → use-material-translation-DYrfV_JJ.js} +1 -1
- package/dist/{use-return-focus-BS1JLcnq.js → use-return-focus-C_Qz_yTk.js} +1 -1
- package/docs/components/data-display.md +36 -8
- package/docs/components/data-table-filter-and-lists.md +7 -3
- package/docs/components/form-fields.md +1 -0
- package/docs/components/media-and-files.md +1 -0
- package/docs/components/navigation-and-layout.md +2 -1
- package/docs/components/overlays-and-menus.md +11 -5
- package/docs/components/rich-widgets.md +85 -1
- package/docs/components/selection-and-choice.md +49 -10
- package/docs/guides/forms.md +7 -0
- package/docs/guides/helpers.md +23 -0
- package/docs/guides/layouts.md +2 -2
- package/docs/guides/theming.md +7 -7
- package/docs/previews/data-display-tubecap.webp +0 -0
- package/docs/previews/selection-choice-sortablelist.webp +0 -0
- package/docs/roadmap.md +3 -1
- package/docs/troubleshooting.md +50 -2
- package/lib/components/base-content.css +11 -0
- package/lib/components/components.css +2 -0
- package/lib/components/ui/collections/data-table/data-table-items-tiles.css +27 -1
- package/lib/components/ui/display/tube-cap/tube-cap.css +87 -0
- package/lib/themes/ambre.css +1 -1
- package/lib/themes/ardoise.css +1 -1
- package/lib/themes/bleumono.css +1 -1
- package/lib/themes/bluemoon.css +1 -1
- package/lib/themes/corail.css +2 -2
- package/lib/themes/foret.css +1 -1
- package/lib/themes/lavande.css +1 -1
- package/lib/themes/menthe.css +2 -2
- package/lib/themes/ocean.css +1 -1
- package/lib/themes/pink.css +2 -2
- package/lib/themes/prune.css +1 -1
- package/lib/themes/sable.css +1 -1
- package/lib/themes/slate.css +1 -1
- package/lib/themes/teal.css +2 -2
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { o as e, r as t } from "./dist-DVRQkaTK.js";
|
|
2
2
|
import { i as n, n as r, r as i, t as a } from "./dist-DWZXlCZR.js";
|
|
3
|
-
import { C as o, S as s, x as c } from "./spinner-
|
|
3
|
+
import { C as o, S as s, x as c } from "./spinner-DH1mm8hH.js";
|
|
4
4
|
import { a as l, i as u, r as d } from "./dist-DHdUUM1w.js";
|
|
5
5
|
import { c as f, i as p, l as m, o as h, r as g, s as ee } from "./in-dialog-D61Yeaol.js";
|
|
6
6
|
import { r as te } from "./escape-owner-DpaP0lm1.js";
|
|
@@ -21,6 +21,7 @@ from `@nxgt/material/components`.
|
|
|
21
21
|
| <img src="../previews/data-display-metric-cards.webp" width="96" alt=""> | [Metric cards](#metric-cards) | Sixteen dashboard tiles |
|
|
22
22
|
| <img src="../previews/data-display-card-presets.webp" width="96" alt=""> | [Card presets](#card-presets) | Twenty-seven ready-made cards |
|
|
23
23
|
| <img src="../previews/data-display-statusindicator.webp" width="96" alt=""> | [StatusIndicator](#statusindicator) | Coloured status dot with a label |
|
|
24
|
+
| <img src="../previews/data-display-tubecap.webp" width="96" alt=""> | [TubeCap](#tubecap) | A specimen tube's chip: a cap in its fixed convention colour, the container's name beside it, optional volume, accession and action |
|
|
24
25
|
| <img src="../previews/data-display-summarydata.webp" width="96" alt=""> | [SummaryData](#summarydata) | Label / value pairs as a grid or inline |
|
|
25
26
|
| <img src="../previews/data-display-description.webp" width="96" alt=""> | [Description](#description) | One read-only label / value pair |
|
|
26
27
|
| <img src="../previews/data-display-descriptionlist.webp" width="96" alt=""> | [DescriptionList](#descriptionlist) | A dense auto-fill grid of label / value pairs in a `dl`, with a panels variant |
|
|
@@ -193,7 +194,7 @@ no `style` attribute and a strict CSP is fine.
|
|
|
193
194
|
- Seat changes are not animated beyond a colour fade, which stops under
|
|
194
195
|
`prefers-reduced-motion`.
|
|
195
196
|
|
|
196
|
-
**Storybook:** `Data display/SeatRing` · **Vue:** not yet (#1931)
|
|
197
|
+
**Storybook:** `Data display/SeatRing` · **Vue:** not yet (#2470) (#1931)
|
|
197
198
|
|
|
198
199
|
---
|
|
199
200
|
|
|
@@ -268,7 +269,7 @@ const [open, setOpen] = useState(false);
|
|
|
268
269
|
the layout follows `dir` (RTL) and dark mode.
|
|
269
270
|
- Keep `front` and `description` free of the secret: they are always in the page.
|
|
270
271
|
|
|
271
|
-
**Storybook:** `Data display/SecretCard` · **Vue:** not yet (#1938)
|
|
272
|
+
**Storybook:** `Data display/SecretCard` · **Vue:** not yet (#2470) (#1938)
|
|
272
273
|
|
|
273
274
|
---
|
|
274
275
|
|
|
@@ -425,7 +426,7 @@ import { CountBadge, IconButton } from '@nxgt/material/components';
|
|
|
425
426
|
|
|
426
427
|

|
|
427
428
|
|
|
428
|
-
A string with the words of a search query in a themed `<mark>`. Case, accents and ligatures are folded (`œ` is `oe`, `æ` is `ae`, `ß` is `ss`), and a query word matches at the start of a word, as a search engine's normaliser does.
|
|
429
|
+
A string with the words of a search query in a themed `<mark>`. Case, accents and ligatures are folded (`œ` is `oe`, `æ` is `ae`, `ß` is `ss`), and a query word matches at the start of a word, as a search engine's normaliser does. The `Guides/Recipes/Search dialog and results page` story marks the matches in command rows and result cards with it.
|
|
429
430
|
|
|
430
431
|
```tsx
|
|
431
432
|
import { HighlightedText, highlightRanges } from '@nxgt/material/components';
|
|
@@ -790,6 +791,33 @@ import { StatusIndicator } from '@nxgt/material/components';
|
|
|
790
791
|
|
|
791
792
|
---
|
|
792
793
|
|
|
794
|
+
## TubeCap
|
|
795
|
+
|
|
796
|
+

|
|
797
|
+
|
|
798
|
+
A chip for a specimen tube. The cap colour comes from the international tube convention, so it is a fixed value: it does not follow the theme and is the same in light and dark. The container's name is written beside the cap (`label` is required), so colour never carries the meaning alone; the text uses the theme's foreground, which keeps the theme's 4.5:1 contrast.
|
|
799
|
+
|
|
800
|
+
```tsx
|
|
801
|
+
import { TubeCap } from '@nxgt/material/components';
|
|
802
|
+
|
|
803
|
+
<TubeCap kind="edta" label="EDTA" volume="4 mL" accession="L00000042-1" />
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
| Prop | Type | Default | Effect |
|
|
807
|
+
| --- | --- | --- | --- |
|
|
808
|
+
| `kind` | `'edta' \| 'heparin' \| 'citrate' \| 'esr' \| 'serum' \| 'gel' \| 'fluoride' \| 'trace' \| 'other'` | required | the cap colour: lavender, green, light blue, black, red, gold, grey, royal blue, plain |
|
|
809
|
+
| `label` | `ReactNode` | required | the container's name beside the cap; it also names the chip (`role="group"`) |
|
|
810
|
+
| `volume` | `ReactNode` | none | the volume, as text |
|
|
811
|
+
| `accession` | `ReactNode` | none | the tube's accession label |
|
|
812
|
+
| `action` | `ReactNode` | none | trailing slot, such as an `IconButton` |
|
|
813
|
+
| `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | cap of 20, 28 or 40px |
|
|
814
|
+
|
|
815
|
+
`TUBE_CAP_KINDS` lists the kinds. The look is CSS on `data-kind`, with no inline style (strict CSP). The cap is `aria-hidden`.
|
|
816
|
+
|
|
817
|
+
**Storybook:** `Data display/TubeCap` · **Vue:** not yet (#2470)
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
793
821
|
## SummaryData
|
|
794
822
|
|
|
795
823
|

|
|
@@ -1339,7 +1367,7 @@ and the user's zone) and both sides agree:
|
|
|
1339
1367
|
<ChangeHistory entries={entries} now={loaderData.now} timeZone={loaderData.timeZone} />
|
|
1340
1368
|
```
|
|
1341
1369
|
|
|
1342
|
-
**Storybook:** `Data display/ChangeHistory` · **Vue:** not yet (tracked as a `vue` issue)
|
|
1370
|
+
**Storybook:** `Data display/ChangeHistory` · **Vue:** not yet (#2470) (tracked as a `vue` issue)
|
|
1343
1371
|
|
|
1344
1372
|
---
|
|
1345
1373
|
|
|
@@ -1376,7 +1404,7 @@ The offset follows the instant (`Europe/Paris` is `UTC+1` in winter and
|
|
|
1376
1404
|
`UTC+2` in summer). `ChangeHistory` takes a `timeZone` and `RelativeTime` a `now`
|
|
1377
1405
|
the same way; pass the app's zone from the server so every screen agrees.
|
|
1378
1406
|
|
|
1379
|
-
**Storybook:** `Data display/ZonedTime` · **Vue:** not yet (tracked as a `vue` issue)
|
|
1407
|
+
**Storybook:** `Data display/ZonedTime` · **Vue:** not yet (#2470) (tracked as a `vue` issue)
|
|
1380
1408
|
|
|
1381
1409
|
---
|
|
1382
1410
|
|
|
@@ -1445,7 +1473,7 @@ own, so they add up to less.
|
|
|
1445
1473
|
- Bar widths are SVG attributes, so server markup has no `style` attribute and
|
|
1446
1474
|
a strict CSP is fine.
|
|
1447
1475
|
|
|
1448
|
-
**Storybook:** `Data display/VoteTally` · **Vue:** not yet (#1940)
|
|
1476
|
+
**Storybook:** `Data display/VoteTally` · **Vue:** not yet (#2470) (#1940)
|
|
1449
1477
|
|
|
1450
1478
|
---
|
|
1451
1479
|
|
|
@@ -1778,7 +1806,7 @@ lock set by another code form disables its field and leaves Cancel usable.
|
|
|
1778
1806
|
button by `getByRole('button', { name: 'Activate' })` and the lock by
|
|
1779
1807
|
`getByRole('alert')`.
|
|
1780
1808
|
|
|
1781
|
-
**Storybook:** `Data display/EnrolmentCard`, `…/Shared lock`, `Guides/Accessibility/Touch targets/Verification code` · **Vue:** not yet
|
|
1809
|
+
**Storybook:** `Data display/EnrolmentCard`, `…/Shared lock`, `Guides/Accessibility/Touch targets/Verification code` · **Vue:** not yet (#2470)
|
|
1782
1810
|
|
|
1783
1811
|
---
|
|
1784
1812
|
|
|
@@ -1921,4 +1949,4 @@ import { RecoveryCodesStatus, VerificationLockProvider } from '@nxgt/material/co
|
|
|
1921
1949
|
|
|
1922
1950
|
It reads the page-wide lock like [EnrolmentCard](#enrolmentcard) does. With `requireCode={false}` there is no code form, so `error` and `attemptsLeft` are not shown: the plain button follows `pending`, and is disabled by `locked` and by the page-wide lock; then the lock's text (the message of the answer that locked, or "Too many wrong codes. Try again later.") shows above it as a muted line that describes the button.
|
|
1923
1951
|
|
|
1924
|
-
**Storybook:** `Data display/RecoveryCodesStatus`, `Data display/EnrolmentCard/Shared lock` · **Vue:** not yet
|
|
1952
|
+
**Storybook:** `Data display/RecoveryCodesStatus`, `Data display/EnrolmentCard/Shared lock` · **Vue:** not yet (#2470)
|
|
@@ -864,8 +864,9 @@ export default function Students() {
|
|
|
864
864
|
search. When rows come back while the focus was on the empty page, it
|
|
865
865
|
goes to the search; rows that come back on their own leave the focus
|
|
866
866
|
alone. The empty page stays while the next answer loads (`data-busy`).
|
|
867
|
-
- **A failed fetch.** Pass `error` (`{ title?, description?, cause?, kind? }`,
|
|
868
|
-
`null` when there is none; `
|
|
867
|
+
- **A failed fetch.** Pass `error` (`{ title?, description?, cause?, kind?, illustration? }`,
|
|
868
|
+
`null` when there is none; `illustration` is a scene id, a node, `true` or
|
|
869
|
+
`false`, as on `ListEmptyPage`, and replaces the scene `kind` picks; `kind` takes `ErrorState`'s kinds, `generic`
|
|
869
870
|
(default), `offline` or `maintenance`; `title` and `description` default to
|
|
870
871
|
the two strings below, `cause` to none) and `onRetry`: the failure replaces the rows,
|
|
871
872
|
the header, the footer and the empty page, with a Retry (a returned promise,
|
|
@@ -1667,6 +1668,7 @@ import { DayGroupedList } from '@nxgt/material/components';
|
|
|
1667
1668
|
| `labels` | `Partial<Record<DayGroupKey, ReactNode>>` | — | replaces a heading |
|
|
1668
1669
|
| `sticky` | `boolean` | `true` | headings stick while their items scroll |
|
|
1669
1670
|
| `showCount` | `boolean` | `false` | the count beside each heading |
|
|
1671
|
+
| `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `3` | the level of each heading (`<h2>`…`<h6>`), to follow the heading above the list |
|
|
1670
1672
|
| `headerClassName` / `listClassName` | `string` | — | a heading's background on a tinted parent, the `<ul>`'s dividers |
|
|
1671
1673
|
|
|
1672
1674
|
**Storybook:** `Data table, filter & lists/DayGroupedList` · **Vue:** — (later)
|
|
@@ -2992,7 +2994,9 @@ import { Avatar, ListPane, ListPaneItem } from '@nxgt/material/components';
|
|
|
2992
2994
|
`trailing`, `selected`, `focused` (a ring while its `group/listbox` parent has
|
|
2993
2995
|
visible focus), `disabled`, `density` (`comfortable` or `compact`, one line),
|
|
2994
2996
|
`appearance` (`plain` or `card`) and `lifted` (raised, as under a drag). It is
|
|
2995
|
-
purely visual: give it the role and the events of the list it sits in.
|
|
2997
|
+
purely visual: give it the role and the events of the list it sits in. A
|
|
2998
|
+
`title` or `description` cut by the ellipsis keeps its full text in a `title`
|
|
2999
|
+
attribute (when it is a string). The
|
|
2996
3000
|
badge steps aside when the pane is narrower than 20rem.
|
|
2997
3001
|
|
|
2998
3002
|
The height reaches the body as a CSS custom property set from script, never
|
|
@@ -245,6 +245,7 @@ export function PlanForm({ onSave }: { onSave: (values: Values) => void }) {
|
|
|
245
245
|
| `reorderable` | `boolean` | `false` | move up / move down buttons on each row |
|
|
246
246
|
| `max` | `number` | — | at this many rows, Add is disabled and says why |
|
|
247
247
|
| `min` | `number` | — | at this many rows, Remove is disabled |
|
|
248
|
+
| `removeAtMin` | `'disable' \| 'hide'` | `'disable'` | at `min` rows, `'hide'` does not render Remove instead of disabling it |
|
|
248
249
|
| `removable` | `(row, index) => boolean` | every row | a row it refuses keeps its Remove button `aria-disabled` (focusable; activating it does nothing). `row` is the `useFieldArray` field (`RepeatableGroupFormField`) or your item |
|
|
249
250
|
| `removeDisabledReason` | `(row, index) => string \| undefined` | — | why a row cannot be removed: its Remove button's tooltip (on hover, focus, tap and click) and `aria-describedby` description |
|
|
250
251
|
| `emptyTitle`, `emptyDescription` | `ReactNode` | — | an `EmptyState` when there is no row |
|
|
@@ -624,6 +624,7 @@ with a `+N` tile).
|
|
|
624
624
|
| `selectable` | `boolean` | — | checkboxes on tiles; `onSelectionChange(ids)` |
|
|
625
625
|
| `open` / `defaultOpen` / `onOpenChange` | `boolean` | — | control the lightbox |
|
|
626
626
|
| `onDelete` | `(item) => void` | — | adds a delete button on each tile |
|
|
627
|
+
| `aria-label` / `aria-labelledby` | `string` | `'Gallery'` label | names the gallery (the root has `role="application"`); a given `aria-labelledby` replaces the default label. `MiniGallery` and `LightboxGallery` take both. Other `div` props (`id`, `data-*`, `onKeyDown`) reach the root element |
|
|
627
628
|
|
|
628
629
|
**States.** Tiles use the `md` corner and round to `lg-plus` on hover (an
|
|
629
630
|
effects easing, so the radius never overshoots) with a spring press scale. A
|
|
@@ -536,8 +536,9 @@ the page. It takes a `div`'s props.
|
|
|
536
536
|
`Button`; a second filled `Button` among the children warns once in
|
|
537
537
|
development, make the others `outlined` or `tonal`.
|
|
538
538
|
- **Surface.** Below `md` the bar paints `--background` (it is stuck over the content); from `md`, and with `sticky={false}`, it paints nothing and takes its parent's surface, such as a `Card`.
|
|
539
|
+
- **Edges.** The bar adds no horizontal padding: its leading and trailing edges are those of the column it sits in, sticky or inline, so its buttons line up with the cards and fields above. Pad the column, not the bar.
|
|
539
540
|
- **Safe areas.** Under `viewport-fit=cover` the bar pads clear of
|
|
540
|
-
`env(safe-area-inset-left | right | bottom)
|
|
541
|
+
`env(safe-area-inset-left | right | bottom)` (the sides only below `md`), as classes.
|
|
541
542
|
- **Keyboard.** From the visual viewport (`visualViewport`), the bar rises by
|
|
542
543
|
the height the on-screen keyboard covers (`--nxgt-keyboard-inset` on the bar,
|
|
543
544
|
`data-keyboard="open"` while it is open) and drops the bottom safe-area
|
|
@@ -157,8 +157,12 @@ The look lives in the stylesheet (`nxgt-dialog`, `nxgt-scrim`), under the
|
|
|
157
157
|
utilities: a class you pass wins. The flat look of 2.x before it is
|
|
158
158
|
`className="rounded-lg border bg-background shadow-lg"` on `DialogContent`
|
|
159
159
|
(and `className="bg-black/50"` on a `DialogOverlay` of your own).
|
|
160
|
-
`DialogFooter` stacks its actions full width on a phone
|
|
161
|
-
|
|
160
|
+
`DialogFooter` stacks its actions full width on a phone. **One order for
|
|
161
|
+
`DialogFooter`, `SheetFooter` and `AlertDialogFooter`:** write the actions in
|
|
162
|
+
reading order, the secondary first and the main one last (`Cancel`, `Save`).
|
|
163
|
+
On a wide screen the dialog puts them in a row with the main one on the right;
|
|
164
|
+
on a phone the stack puts the main one on top and the secondary below it, in a
|
|
165
|
+
dialog and in a sheet alike. `stackOnPhone={false}` keeps one row instead, the first
|
|
162
166
|
action on the left and the last on the right, for two short actions such as
|
|
163
167
|
Cancel and Confirm. With `prefers-reduced-motion`, nothing moves: it appears and goes.
|
|
164
168
|
|
|
@@ -271,7 +275,9 @@ Parts: `Sheet`, `SheetTrigger`, `SheetContent`, `SheetHeader`, `SheetTitle`,
|
|
|
271
275
|
|
|
272
276
|
`SheetContent` scrolls inside itself, and a top or bottom sheet is capped at
|
|
273
277
|
`100dvh - 3.5rem`. Override the cap with `max-h-*` through `className`.
|
|
274
|
-
`SheetFooter` makes its buttons full width
|
|
278
|
+
`SheetFooter` makes its buttons full width, stacked in the same order as
|
|
279
|
+
`DialogFooter` on a phone: write the secondary first and the main action last,
|
|
280
|
+
the main one is on top. The former look is
|
|
275
281
|
`className="rounded-none border-l bg-background shadow-lg"` (the border on the
|
|
276
282
|
side facing the page), and `className="w-3/4"` for the former width.
|
|
277
283
|
|
|
@@ -339,7 +345,7 @@ A bottom drawer spans a phone's width and is centred, at most 672 px wide,
|
|
|
339
345
|
from 640 px up; `className="mx-auto max-w-md"` on `DrawerContent` centres a
|
|
340
346
|
narrower one, `sm:max-w-none` spans the viewport again, and
|
|
341
347
|
`className="rounded-t-lg bg-background"` is the former look. `DrawerFooter`
|
|
342
|
-
makes its buttons full width. Prefer it to `Sheet` on touch screens.
|
|
348
|
+
makes its buttons full width and keeps source order (first written on top). Prefer it to `Sheet` on touch screens.
|
|
343
349
|
`DrawerContent` moves focus in on open and hands it back on close as
|
|
344
350
|
`DialogContent` does (`Drawer` turns Vaul's `autoFocus` on; `autoFocus={false}`
|
|
345
351
|
keeps focus where it was): back to the
|
|
@@ -641,7 +647,7 @@ in a desktop application.
|
|
|
641
647
|
|
|
642
648
|

|
|
643
649
|
|
|
644
|
-
Command palette with filtering, groups and shortcuts. `CommandDialog` shows it in a modal, with a visible close button (`showCloseButton`, on by default; `false` removes it) at the end of the input row: named "Close" (`material.buttons.close`) and 44px on a coarse pointer, since a phone has no Escape key.
|
|
650
|
+
Command palette with filtering, groups and shortcuts. `CommandDialog` shows it in a modal, with a visible close button (`showCloseButton`, on by default; `false` removes it) at the end of the input row: named "Close" (`material.buttons.close`) and 44px on a coarse pointer, since a phone has no Escape key. For results the server already filtered (`commandProps={{ shouldFilter: false }}`), with debounced suggestions grouped by type, see the `Guides/Recipes/Search dialog and results page` story.
|
|
645
651
|
|
|
646
652
|
```tsx
|
|
647
653
|
import {
|
|
@@ -252,6 +252,8 @@ cannot be dragged to another tab; use Move to or Ctrl/Cmd + arrows.
|
|
|
252
252
|
|
|
253
253
|
Month / week / day / agenda calendar with drag, resize and slot creation, a
|
|
254
254
|
now line, an optional mini-month side panel and a phone layout of its own.
|
|
255
|
+
With `mode="template"` it edits a recurring week instead (weekday columns, no
|
|
256
|
+
dates), see [Template week](#eventcalendar-template-week).
|
|
255
257
|
|
|
256
258
|
It reads its **own width**, not the window's, so it adapts inside a narrow
|
|
257
259
|
column too. From 1024px `sidebar` shows a [MiniMonth](date-and-time.md#minimonth)
|
|
@@ -307,6 +309,7 @@ function Schedule() {
|
|
|
307
309
|
| `scrollToHour` | `number` | `7` | the hour the week and day views open on when today is not shown |
|
|
308
310
|
| `resources` | `EventCalendarResource[]` | — | the day view draws one column per resource, see [Resources](#eventcalendar-resources) |
|
|
309
311
|
| `timeZone` | `string` | the machine's | an IANA zone the grid, today and the times follow, see [Time zone](#eventcalendar-time-zone) |
|
|
312
|
+
| `mode` | `'dated' \| 'template'` | `'dated'` | `'template'` is a recurring week with weekday columns and no dates, see [Template week](#eventcalendar-template-week) |
|
|
310
313
|
|
|
311
314
|
**Short events.** Events that do not overlap in time share a column and keep the
|
|
312
315
|
full width. An event is drawn at least 24px tall only where there is free space
|
|
@@ -372,6 +375,87 @@ const events: CalendarEvent[] = [
|
|
|
372
375
|
date (`Room A, Thursday, March 12th, 2026`), and an event's name includes
|
|
373
376
|
its resource (`Visit 1, Room A`).
|
|
374
377
|
|
|
378
|
+
### EventCalendar template week
|
|
379
|
+
|
|
380
|
+
`mode="template"` edits a **recurring week** instead of a dated one: a weekly
|
|
381
|
+
timetable, an opening-hours grid. The columns are named by weekday only (a
|
|
382
|
+
letter on a phone, the full name from 1024px), and there is no date
|
|
383
|
+
navigation, no Today button, no today marker and no now line. Events,
|
|
384
|
+
slots and changes are keyed by **ISO weekday** (1 Monday to 7 Sunday) and
|
|
385
|
+
`HH:mm` times, never by a date. Create (click or drag across free hours),
|
|
386
|
+
move, resize, the keyboard moves and the preview popover keep working.
|
|
387
|
+
The month, day and agenda views do not apply: `view` is `'week'` (the
|
|
388
|
+
default) or `'workweek'` (Monday to Friday), and the week always starts on
|
|
389
|
+
Monday.
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
import { useState } from 'react';
|
|
393
|
+
import { EventCalendar, type EventCalendarTemplateEvent } from '@nxgt/material/components';
|
|
394
|
+
|
|
395
|
+
function Timetable() {
|
|
396
|
+
const [events, setEvents] = useState<EventCalendarTemplateEvent[]>([
|
|
397
|
+
{ id: 'm', title: 'Maths', weekday: 1, start: '08:00', end: '09:00' },
|
|
398
|
+
{ id: 'p', title: 'Physics', weekday: 1, start: '09:00', end: '10:30', lane: 'A' },
|
|
399
|
+
{ id: 'c', title: 'Chemistry', weekday: 1, start: '09:00', end: '10:30', lane: 'B' },
|
|
400
|
+
]);
|
|
401
|
+
return (
|
|
402
|
+
<EventCalendar
|
|
403
|
+
mode="template"
|
|
404
|
+
events={events}
|
|
405
|
+
onEventsChange={setEvents}
|
|
406
|
+
lanes={[
|
|
407
|
+
{ id: 'A', label: 'Week A' },
|
|
408
|
+
{ id: 'B', label: 'Week B' },
|
|
409
|
+
]}
|
|
410
|
+
onSlotClick={({ weekday, start, end, lane }) =>
|
|
411
|
+
setEvents((current) => [
|
|
412
|
+
...current,
|
|
413
|
+
{ id: crypto.randomUUID(), title: 'New lesson', weekday, start, end, lane },
|
|
414
|
+
])
|
|
415
|
+
}
|
|
416
|
+
/>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
| Prop | Type | Default | Effect |
|
|
422
|
+
| --- | --- | --- | --- |
|
|
423
|
+
| `mode` | `'template'` | `'dated'` | the recurring week; `'dated'` (the default) is the calendar above |
|
|
424
|
+
| `events` / `defaultEvents` | `EventCalendarTemplateEvent[]` | — | `{ id, title, weekday, start, end, lane?, color?, description?, disabled? }`; `end` may be `'24:00'` |
|
|
425
|
+
| `onEventsChange` | `(events: EventCalendarTemplateEvent[]) => void` | — | the whole list after a move, a resize, an edit or a delete |
|
|
426
|
+
| `onChange` | `(change: EventCalendarTemplateChange) => void` | — | `{ events, event, previous }` after a move, a resize or an edit |
|
|
427
|
+
| `onEventClick` | `(event: EventCalendarTemplateEvent) => void` | — | an event was clicked |
|
|
428
|
+
| `onSlotClick` | `(slot: EventCalendarTemplateSlot) => void` | — | `{ weekday, start, end, lane? }` of the free slot clicked or dragged over; `lane` is the one picked in the switch |
|
|
429
|
+
| `view` / `defaultView` / `onViewChange` | `'week' \| 'workweek'` | `'week'` | the visible view |
|
|
430
|
+
| `renderEvent` | `(event: EventCalendarTemplateEvent) => ReactNode` | — | replaces how an event is drawn |
|
|
431
|
+
| `className` | `string` | — | on the calendar's root |
|
|
432
|
+
| `lanes` | `EventCalendarLane[]` | — | `{ id, label }` of the alternating weeks (`A`, `B`); adds a switch to the toolbar |
|
|
433
|
+
| `lane` / `defaultLane` / `onLaneChange` | `string \| null` | `null` (all) | the lane shown; `null` shows them all |
|
|
434
|
+
|
|
435
|
+
**Lanes.** An event with a `lane` belongs to that alternating week only; one
|
|
436
|
+
with none belongs to every week. Picking a lane in the switch hides the other
|
|
437
|
+
lanes' events, so one week is edited at a time; with **All weeks** every event shows,
|
|
438
|
+
and the ones with a lane carry its label (`A · Physics`), side by side where they
|
|
439
|
+
overlap. A slot made while a lane is picked reports it in `slot.lane`.
|
|
440
|
+
Moving and resizing keep an event's lane.
|
|
441
|
+
|
|
442
|
+
The shared options (`startHour`, `endHour`, `hourHeight`,
|
|
443
|
+
`slotDurationMinutes`, `density`, `eventVariant`, `scrollToHour`, `toolbar`,
|
|
444
|
+
`searchable`, `disabled`) work as in the dated calendar; `renderEvent` and
|
|
445
|
+
`className` are in the table above. `date`, `resources`, `timeZone`,
|
|
446
|
+
`sidebar`, `phoneView`, `views` and `weekStartsOn` do not apply. All-day
|
|
447
|
+
events do not exist in a template week.
|
|
448
|
+
|
|
449
|
+
Ctrl+Arrow moves the focused event within the visible days (Friday wraps to
|
|
450
|
+
Monday in a `workweek`) and stops at 00:00 and 24:00. `ComponentProps<typeof
|
|
451
|
+
EventCalendar>` is the union of the dated and template props: type a wrapper
|
|
452
|
+
with `EventCalendarProps` or `EventCalendarTemplateProps`.
|
|
453
|
+
|
|
454
|
+
The types are exported from `@nxgt/material/components`: `EventCalendarTemplateProps`
|
|
455
|
+
(the props of this mode), `EventCalendarTemplateEvent`, `EventCalendarTemplateSlot`,
|
|
456
|
+
`EventCalendarTemplateChange`, `EventCalendarLane` and `EventCalendarWeekday`
|
|
457
|
+
(`1 | 2 | … | 7`, the ISO weekday).
|
|
458
|
+
|
|
375
459
|
### EventCalendar time zone
|
|
376
460
|
|
|
377
461
|
`timeZone` (an IANA name such as `Africa/Lagos`) makes the calendar read that
|
|
@@ -409,7 +493,7 @@ zone's wall clock. Without it, nothing changes: the machine's clock.
|
|
|
409
493
|
<EventCalendar view="day" date="2026-03-12" timeZone="Asia/Tokyo" events={events} />
|
|
410
494
|
```
|
|
411
495
|
|
|
412
|
-
**Storybook:** `Rich widgets/EventCalendar`, `Rich widgets/EventCalendar/Layouts`, `Rich widgets/EventCalendar/Resources`, `Rich widgets/EventCalendar/Resources on a phone`, `Rich widgets/EventCalendar/Resources a11y`, `Rich widgets/EventCalendar/Time zone`, `Rich widgets/EventCalendar/Back to back`, `Rich widgets/EventCalendar/Resource tone`, `Rich widgets/EventCalendar/Hydration` · **Vue:** `EventCalendar` (the new props and layouts, `resources` and `
|
|
496
|
+
**Storybook:** `Rich widgets/EventCalendar`, `Rich widgets/EventCalendar/Layouts`, `Rich widgets/EventCalendar/Resources`, `Rich widgets/EventCalendar/Resources on a phone`, `Rich widgets/EventCalendar/Resources a11y`, `Rich widgets/EventCalendar/Time zone`, `Rich widgets/EventCalendar/Back to back`, `Rich widgets/EventCalendar/Resource tone`, `Rich widgets/EventCalendar/Hydration`, `Rich widgets/EventCalendar/Template week` · **Vue:** `EventCalendar` (the new props and layouts, `resources`, `timeZone` and `mode="template"`: React only for now, #892, #1780 and #2458)
|
|
413
497
|
|
|
414
498
|
---
|
|
415
499
|
|
|
@@ -839,6 +839,18 @@ export function Topics() {
|
|
|
839
839
|
}
|
|
840
840
|
```
|
|
841
841
|
|
|
842
|
+
Balanced by default, and the unbalanced fill:
|
|
843
|
+
|
|
844
|
+
```tsx
|
|
845
|
+
const four = ['a', 'b', 'c', 'd'].map((value) => ({ value, title: value.toUpperCase() }));
|
|
846
|
+
|
|
847
|
+
// Room for 3 columns, 4 cards: drawn 2+2, no orphan.
|
|
848
|
+
<ChoiceCardGroup label="Size" columns={3} options={four} value={value} onValueChange={setValue} />
|
|
849
|
+
|
|
850
|
+
// Every column that fits: 3+1.
|
|
851
|
+
<ChoiceCardGroup label="Size" columns={3} balance={false} options={four} value={value} onValueChange={setValue} />
|
|
852
|
+
```
|
|
853
|
+
|
|
842
854
|
| Prop | Type | Default | Effect |
|
|
843
855
|
| --- | --- | --- | --- |
|
|
844
856
|
| `type` | `'single' \| 'multiple'` | `'single'` | the value is `string \| null` or `string[]` |
|
|
@@ -945,8 +957,18 @@ check box at the end. `type="single"` (the default) is a radio group of
|
|
|
945
957
|
card has a soft tint and a bar on its start edge; the mark says it too, so
|
|
946
958
|
colour is never the only cue. The cards fill as many columns as fit (each at
|
|
947
959
|
least 13.5rem wide), up to `columns`, so the layout follows the space the field
|
|
948
|
-
has, not the window
|
|
949
|
-
|
|
960
|
+
has, not the window. The rows are balanced: when the last row would hold one
|
|
961
|
+
orphan card (4 cards in room for 3), the group draws fewer columns and sits 2+2;
|
|
962
|
+
`balance={false}` fills every column that fits (3+1).
|
|
963
|
+
|
|
964
|
+
When it balances (the default, horizontal), the group is an inline-size
|
|
965
|
+
container (`@container`), which is how it measures its own width. A container's
|
|
966
|
+
width never comes from its content, so give the group a parent with a width: in
|
|
967
|
+
a shrink-to-fit parent (an `inline-flex` box, a float, a centred `w-fit` column)
|
|
968
|
+
it collapses to nothing. Add `w-full`, a `max-w-*` or a fixed width to that
|
|
969
|
+
parent, or pass `balance={false}`: with it (and with `orientation="vertical"`)
|
|
970
|
+
there is no container, and the group behaves as before in a shrink-to-fit
|
|
971
|
+
parent.
|
|
950
972
|
|
|
951
973
|
```tsx
|
|
952
974
|
import { ChoiceCardGroup } from '@nxgt/material/components';
|
|
@@ -971,7 +993,8 @@ export function Plan() {
|
|
|
971
993
|
| `deselectable` | `boolean` | `false` | `single` only: pressing the chosen card clears it |
|
|
972
994
|
| `max` / `min` | `number` | — | `multiple` only: `max` blocks the other cards once reached and speaks "{count} of {max} chosen"; `min` is a hint only |
|
|
973
995
|
| `options` | `{ value; title; description?; meta?; icon?; disabled?; disabledReason? }[]` | — | the cards |
|
|
974
|
-
| `columns` | `1 \| 2 \| 3 \| 4` | `2` | the most columns; fewer when a card would be under 13.5rem (one on a phone) |
|
|
996
|
+
| `columns` | `1 \| 2 \| 3 \| 4` | `2` | the most columns; fewer when a card would be under 13.5rem (one on a phone). With `balance`, fewer also when it avoids an orphan: `columns={3}` and 4 cards draw 2+2 |
|
|
997
|
+
| `balance` | `boolean` | `true` | when the last row would hold one orphan card (4 cards in room for 3), draw fewer columns (2+2); by container width, CSS only. `false` fills every column that fits |
|
|
975
998
|
| `density` | `'comfortable' \| 'compact'` | `'comfortable'` | the cards' padding |
|
|
976
999
|
| `label`, `helperText`, `error`, `required`, `disabled`, `id` | as on `ChipGroupField` | — | the shared choice-group props: the group's name and help, its invalid and required states |
|
|
977
1000
|
| `name` | `string` | — | put on every card (`ChoiceCardGroupFormField` passes its field `name`), so `document.getElementsByName(name)` finds the group's cards in order |
|
|
@@ -1113,11 +1136,19 @@ English without one.
|
|
|
1113
1136
|
|
|
1114
1137
|
Searchable list you reorder by dragging or with the keyboard. The row under
|
|
1115
1138
|
the pointer lifts into a card with a deep shadow, and its place in the list
|
|
1116
|
-
stays as a dashed slot.
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1139
|
+
stays as a dashed slot. With reduced motion the drop does not animate.
|
|
1140
|
+
|
|
1141
|
+
The list is a `role="list"` of `listitem` rows, not a listbox: a row holds
|
|
1142
|
+
buttons of its own (grip, move, remove), which an `option` may not. Tab reaches
|
|
1143
|
+
the list once, on one row (a roving tab stop), and the arrows move the focus
|
|
1144
|
+
from row to row. A row of a disabled list is never a tab stop and carries
|
|
1145
|
+
`aria-disabled`. A long label keeps its full text in a `title`, and the grip is
|
|
1146
|
+
named after its item ("Drag: Ada Lovelace").
|
|
1147
|
+
|
|
1148
|
+
From the keyboard, Space (or Enter) on a row picks it up, the arrows move it,
|
|
1149
|
+
Space drops it and Escape puts it back; Ctrl/⌘ + arrow moves it at once,
|
|
1150
|
+
Home / End jump. Each step is read out by a live region ("Ada Lovelace moved to
|
|
1151
|
+
position 3 of 5."). A key pressed on a row's own button is that button's.
|
|
1121
1152
|
|
|
1122
1153
|
```tsx
|
|
1123
1154
|
import { SortableList, type SortableListItem } from '@nxgt/material/components';
|
|
@@ -1144,7 +1175,7 @@ export function Ranking() {
|
|
|
1144
1175
|
| `reorderable` | `boolean` | `false` | move up / down buttons |
|
|
1145
1176
|
| `searchable` | `boolean` | `true` | filter box |
|
|
1146
1177
|
| `height` | `number \| string` | `320` | list height |
|
|
1147
|
-
| `required` | `boolean` | — | the asterisk
|
|
1178
|
+
| `required` | `boolean` | — | the asterisk on the label (a `list` takes no `aria-required`) |
|
|
1148
1179
|
| `hideLabel` | `boolean` | `false` | `true` keeps `label` as the list's accessible name (`aria-labelledby`) but draws nothing, for a heading that already titles it |
|
|
1149
1180
|
| `numbered` | `boolean` | `false` | each row shows its rank in the whole order |
|
|
1150
1181
|
| `removable` | `boolean` | `false` | a remove button on each row, and Delete: the row leaves the value |
|
|
@@ -1156,8 +1187,16 @@ export function Ranking() {
|
|
|
1156
1187
|
where it is: no grip, not draggable, not movable from the keyboard, not
|
|
1157
1188
|
removable).
|
|
1158
1189
|
|
|
1190
|
+
In a test, query it by role and name:
|
|
1191
|
+
|
|
1192
|
+
```ts
|
|
1193
|
+
const list = screen.getByRole('list', { name: 'Ranking' });
|
|
1194
|
+
const rows = within(list).getAllByRole('listitem');
|
|
1195
|
+
await user.click(within(rows[0]).getByRole('button', { name: /^Move down/ }));
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1159
1198
|
**Emits:** `string[]`, in the new order.
|
|
1160
|
-
**Form wrapper:** `SortableListFormField` — `control` and `name` plus the same props, `hideLabel` included (the label's `required` asterisk is hidden with it; the `
|
|
1199
|
+
**Form wrapper:** `SortableListFormField` — `control` and `name` plus the same props, `hideLabel` included (the label's `required` asterisk is hidden with it; the asterisk only: a `list` takes no `aria-required`, so a required list is described by a visually hidden "This field is required."), see
|
|
1161
1200
|
[forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
|
|
1162
1201
|
**Storybook:** `Selection & choice/SortableList` · **Vue:** `SortableList`, `SortableListFormField`
|
|
1163
1202
|
|
package/docs/guides/forms.md
CHANGED
|
@@ -262,6 +262,13 @@ in `[data-slot="form-actions"]`) and put each group of fields in a
|
|
|
262
262
|
</form>
|
|
263
263
|
```
|
|
264
264
|
|
|
265
|
+
- The form grid insets its fields and its actions by 16px. In a container that
|
|
266
|
+
already pads its content (a settings page, a card, a sheet), set
|
|
267
|
+
`data-padding="none"` on the `.form-grid-layout`: the fields lose their
|
|
268
|
+
padding on all sides and the actions lose it left and right, so both sit
|
|
269
|
+
flush with that container's content edge:
|
|
270
|
+
`<form className="form-grid-layout" data-padding="none">`. The default is
|
|
271
|
+
unchanged.
|
|
265
272
|
- Each section's title is an `h2` (pass `titleAs="h3"` when the form sits
|
|
266
273
|
under an `h2` of your own), so a screen reader moves from section to
|
|
267
274
|
section, and each section is a group named by its title.
|
package/docs/guides/helpers.md
CHANGED
|
@@ -126,6 +126,7 @@ Vue: the Redux helpers are Pinia store factories: `defineLocalStore`
|
|
|
126
126
|
| `useIsClient()` | `false` on the server and first render, `true` after mount: a hydration guard. |
|
|
127
127
|
| `useCspNonce()` | The nonce of the nearest `NonceProvider`, for a `<style>` of your own (`<style nonce={useCspNonce()}>`); `undefined` outside one, on the server and on the client alike. See [CSP](csp.md#nonceprovider). |
|
|
128
128
|
| `useIsMobile()` | `true` below 768 px (`ResponsiveButton` no longer uses it: it collapses with CSS). `false` on the server and while hydrating, so SSR HTML is the desktop layout; a phone switches right after. |
|
|
129
|
+
| `useSsrMediaQuery(query, serverValue)` | Whether a media query matches, for a breakpoint `useIsMobile` (768 px) does not cover. The server and the hydrating client both render `serverValue` (`serverValue` is the answer the query gives on a desktop screen: `true` for `(min-width: …)`, `false` for `(max-width: …)`), and the client switches right after hydrating, so there is no hydration mismatch; a client-only render (SPA, client-side navigation) reads the real answer at once. Without `matchMedia` (jsdom) it answers `serverValue`. See [below](#usessrmediaquery). |
|
|
129
130
|
| `useHeadingSpy(ids, onActive, options?)` | IntersectionObserver over heading ids; reports the topmost visible one. |
|
|
130
131
|
| `useLocalizedDate()` | `{ language, locale, format }`: `DATE_UTILS.format` bound to the `I18nProvider` language (`en` without a provider). |
|
|
131
132
|
| `useNumberFormatter()` | `{ formatNumber, formatCurrency }`: `Intl.NumberFormat` with the options you pass, bound to the `I18nProvider` language (`en-US` without a provider). For what `formatAmount` does not cover: percentages, units, compact notation. |
|
|
@@ -207,6 +208,28 @@ export function Sent({ email }: { email: string }) {
|
|
|
207
208
|
`useFocusOnMount(open)` focuses again whenever `open` turns true, for content
|
|
208
209
|
that stays mounted and is only shown. Storybook: `Guides/Hooks/useFocusOnMount`.
|
|
209
210
|
|
|
211
|
+
### `useSsrMediaQuery`
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { useSsrMediaQuery } from '@nxgt/material/hooks';
|
|
215
|
+
|
|
216
|
+
// A panel that is open from 1280 px (xl, 80rem) and folded below.
|
|
217
|
+
function Inspector({ children }: { children: React.ReactNode }) {
|
|
218
|
+
const isXl = useSsrMediaQuery('(min-width: 80rem)', true);
|
|
219
|
+
return isXl ? <aside>{children}</aside> : null;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The second argument is what the server renders, and what a hydrating client
|
|
224
|
+
renders on its first pass: it is the answer the query gives on a desktop
|
|
225
|
+
screen (`true` for `(min-width: …)`, `false` for `(max-width: …)`). On an SSR
|
|
226
|
+
page a narrower screen therefore paints the desktop markup first and switches right
|
|
227
|
+
after hydrating (one flash, and no hydration mismatch). A client-only render
|
|
228
|
+
(an SPA, a client-side navigation) reads the real answer from its first
|
|
229
|
+
render, with no flash. Where `matchMedia` does not exist (jsdom) the hook
|
|
230
|
+
answers `serverValue` and never subscribes. When CSS alone can do it
|
|
231
|
+
(`hidden xl:block`), prefer CSS: it has no flash at all.
|
|
232
|
+
|
|
210
233
|
### `useCountdown`
|
|
211
234
|
|
|
212
235
|
The clock behind [`CountdownTimer`](../components/feedback-and-status.md#countdowntimer),
|
package/docs/guides/layouts.md
CHANGED
|
@@ -204,8 +204,8 @@ The props of each are in
|
|
|
204
204
|
The `Guides/Recipes` stories in Storybook are tested whole screens (the code
|
|
205
205
|
ships with the `screen-recipes` skill of the `nxgt-material` plugin): a home
|
|
206
206
|
dashboard, a list and detail, a form and dialog, a table and settings, a dense
|
|
207
|
-
detail page, a status board
|
|
208
|
-
replace the data.
|
|
207
|
+
detail page, a status board, a landing page and a search dialog with its
|
|
208
|
+
results page. Start from the closest one and replace the data.
|
|
209
209
|
|
|
210
210
|
## See also
|
|
211
211
|
|
package/docs/guides/theming.md
CHANGED
|
@@ -68,7 +68,7 @@ at the top are needed is in [getting started](../getting-started.md#styles).
|
|
|
68
68
|
| `--accent`, `--accent-foreground` | `bg-accent`, … | hover surfaces |
|
|
69
69
|
| `--nxgt-accent` | `[--nxgt-accent:…]` | the mark colour of the selection controls: the fill, handle, state layer and stop dots of `Slider`, the on state of `Switch`, the box and dot of `Checkbox` and `Radio`. It defaults to `--primary` (`--primary-text` in dark) and is set per control, so override it on the control itself, not on `:root` |
|
|
70
70
|
| `--error`, `--success`, `--info`, `--warning` (+ `-foreground`) | `bg-error`, `text-success`, … | the status colours of `Button`, `Alert`, `Badge`, toasts |
|
|
71
|
-
| `--primary-text` | `text-primary-text` | primary drawn as text or an icon on a page, card or popover surface: `Typography tone="primary"`, links, the `Alert` primary icon, selected labels. In light, `--primary` mixed toward black: 96 % at most, solved lower on a tinted page so a tonal Button label on a striped row reaches 4.6:1 (
|
|
71
|
+
| `--primary-text` | `text-primary-text` | primary drawn as text or an icon on a page, card or popover surface: `Typography tone="primary"`, links, the `Alert` primary icon, selected labels. In light, `--primary` mixed toward black: 96 % at most, solved lower on a tinted page so a tonal Button label on a striped row, or straight on the page ground, reaches 4.6:1 (92 % on teal, 88 % on corail, 92 % on pink, 95 % on menthe); in dark, `--primary` mixed 60 % with white (`color-mix(in oklab, var(--primary) 40%, white)`), well over 4.5:1 on `--background` and `--card` |
|
|
72
72
|
| `--primary-indicator` | `bg-primary-indicator`, `border-primary-indicator`, `ring-primary-indicator`, `stroke-primary-indicator` | primary drawn as a non-text mark that carries a state (WCAG 1.4.11, 3:1): an active bar or dot, a selected or focused border, a ring, a spinner arc. Equal to `--primary` in light; in dark, `--primary` mixed 25 % with white (`color-mix(in oklab, var(--primary) 75%, white)`), 4.8:1 on `--background`, 4.5:1 on `--card`, 4.2:1 on `--muted` (`--primary` itself gives 2.7, 2.5 and 2.3:1) |
|
|
73
73
|
| `--error-text` | `text-error-text` | error drawn as text: field messages, `HelperText` in its error state, form and dialog errors, `variant="error"` menu items |
|
|
74
74
|
| `--destructive` | `bg-destructive`, `text-destructive` | deprecated alias of `--error`: the light theme sets it to `var(--error)`, the dark theme keeps its own value; use `--error` |
|
|
@@ -936,14 +936,14 @@ ones also take 10 % on focus, as on hover, so their label keeps 4.5:1 over it
|
|
|
936
936
|
|
|
937
937
|
The `tonal`, `outlined` and `link` variants of `Button` and `IconButton` draw
|
|
938
938
|
their text and icon in one tone per colour, read on `--background`, on
|
|
939
|
-
`--card
|
|
940
|
-
the light theme `primary
|
|
941
|
-
|
|
942
|
-
|
|
939
|
+
`--card`, on the page ground (`--ground`, a tinted page) and on the variant's own tint over each (15 % at most, at rest or focused). In
|
|
940
|
+
the light theme `primary` uses `--primary-text` (solved per theme), `secondary`, `info` and `success`
|
|
941
|
+
are mixed with 10 % black, `warning` with 15 % black and `error` with 20 % black (4.6:1 or more
|
|
942
|
+
on the page ground in every preset). In the dark theme `primary` is
|
|
943
943
|
mixed with 60 % white, the `--primary-text` token (8.5:1 at its lowest: the token itself is a dark indigo,
|
|
944
944
|
2.3:1), `success` and `warning` with 15 % white (5.3 and 6.5:1), `error` with
|
|
945
945
|
30 % white (6.0:1), and `secondary` and `info` use the token (5.1 and 5.6:1).
|
|
946
|
-
The tones are mixed from the tokens (`color-mix(in oklab, var(--color-
|
|
946
|
+
The tones are mixed from the tokens (`color-mix(in oklab, var(--color-warning)
|
|
947
947
|
85%, black)`), so an override of a token moves its tone with it; a text colour
|
|
948
948
|
of your own on one of those buttons (`className="text-primary-text"`) replaces the tone
|
|
949
949
|
and is yours to check.
|
|
@@ -1012,7 +1012,7 @@ override on the root, or use a [theme zone](#themes), which re-declares them.
|
|
|
1012
1012
|
for the same reason: the brand colour is a dark fill (it keeps near-white text
|
|
1013
1013
|
readable on a button) and reads at only about 2:1 as text on the dark surfaces.
|
|
1014
1014
|
The light value is `color-mix(in oklab, var(--primary) 96%, black)` at most (primary
|
|
1015
|
-
4 % toward black, 7.9:1 on the white page; the share is solved per theme and drops when a tinted page would leave a tonal Button label under 4.6:1 on a striped row, to
|
|
1015
|
+
4 % toward black, 7.9:1 on the white page; the share is solved per theme and drops when a tinted page would leave a tonal Button label under 4.6:1 on a striped row or on the page ground, to 92 % on teal); the dark one is
|
|
1016
1016
|
`color-mix(in oklab, var(--primary) 40%, white)` (primary mixed 60 % with
|
|
1017
1017
|
white, the recipe of the tonal `Button` labels). `Typography tone="primary"`,
|
|
1018
1018
|
the `Alert` primary icon, links, selected labels and the other primary text of
|
|
Binary file
|
|
Binary file
|
package/docs/roadmap.md
CHANGED
|
@@ -383,7 +383,9 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
383
383
|
|
|
384
384
|
## Shipped
|
|
385
385
|
|
|
386
|
-
- **
|
|
386
|
+
- **Search dialog and results page recipe (next patch)** — a command dialog (Ctrl/Cmd+K and a header button), debounced grouped suggestions, type chips, a conditional filter and highlighted result cards, with its code in the plugin's `screen-recipes` skill ([#2333](https://github.com/softistx/nxgt-material/issues/2333)).
|
|
387
|
+
|
|
388
|
+
- **Layouts: adaptive navigation, headers and page layouts (3.80.0)** — `ActivityLayout` takes `navigation` (a bottom bar with More and badges, a rail with a FAB or expanded, a standard or modal drawer, a collapsible sidebar, a top navigation with dropdowns, two-level navigation, a floating toolbar or none, chosen for phone, tablet and desktop) and `header` (small, center-aligned, medium and large collapsing on scroll, hide on scroll, a search bar that opens full screen on a phone, sticky tabs), and a route adds a back arrow with `handle.back`; each piece is also exported. Page layouts grow with it: an inspector pane and the selection in the URL for `ListDetailsLayout`, phone modes and a container breakpoint for `PaneLayout`, a centred form and wizard for `ThreadLayout`, a split `AuthCard`, spans and masonry for `ResponsiveGrid`, tabs for `Kanban` on a phone, `Doc` drawn inside the shell, and new `SettingsLayout`, `DashboardGrid` (tiles the user rearranges), `EditorLayout` and `LandingLayout` (with `LandingSection` and `SiteFooter`). Everything is opt-in; the shell's default is unchanged. [Layouts guide](guides/layouts.md) ([#2103](https://github.com/softistx/nxgt-material/issues/2103)). Follow-ups: a resize handle for `EditorLayout`'s properties panel and a separate open state for its phone sheet.
|
|
387
389
|
|
|
388
390
|
- **Moment (3.74.0)** — a transient full-screen announcement for a one-off event (a player is eliminated, a role is revealed): a modal dialog named by its title, with an illustration, description, action and tone, dismissed by Escape, a tap, its action or a timer, and no motion under reduced motion ([#2220](https://github.com/softistx/nxgt-material/issues/2220), [#2247](https://github.com/softistx/nxgt-material/pull/2247)). A `modal={false}` variant for live events (focus stays, taps pass through, polite status) follows in the next minor ([#2313](https://github.com/softistx/nxgt-material/issues/2313)).
|
|
389
391
|
- **RecordActionsMenu (next minor)** — one menu for a record's lifecycle: archive with an Undo toast (or confirm first with `archivePolicy="confirm"`), restore, and delete that always confirms and shows the backend's refusal, with `ConfirmDialog` gaining `details` and `refusal` slots.
|