@nxgt/material 2.7.0 → 2.8.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 +180 -0
- package/README.md +7 -4
- package/dist/{_Uint8Array-JRUmWcRr.js → _Uint8Array-CALbCR7W.js} +1 -1
- package/dist/{_overRest-DvC5rm5s.js → _overRest-BQ8agsDH.js} +1 -1
- package/dist/{abnfDiagram-O67JEVCF-Cl4ukf6a.js → abnfDiagram-O67JEVCF-D-rmT4BY.js} +4 -4
- package/dist/{architectureDiagram-NJMV4G6O-WmFlBQF5.js → architectureDiagram-NJMV4G6O-DOc_c_T9.js} +7 -7
- package/dist/{async-slice-OftEOtMA.js → async-slice-BHMdvnqm.js} +2 -2
- package/dist/{blockDiagram-BEXU5L5S-BM-gkWAO.js → blockDiagram-BEXU5L5S-D4gI6nqs.js} +11 -11
- package/dist/{c4Diagram-YGBWAQC7-D9xSt2QO.js → c4Diagram-YGBWAQC7-El4SkIzX.js} +8 -8
- package/dist/{channel-Dg92Hoym.js → channel-CnLB6790.js} +1 -1
- package/dist/{chunk-3FUC2YCW-Cf18L00V.js → chunk-3FUC2YCW-BLMCnCFE.js} +8 -8
- package/dist/{chunk-5DYCD2WN-Cja1INgd.js → chunk-5DYCD2WN-Cd1zazNp.js} +3 -3
- package/dist/{chunk-6AEJRKK7-3YWRP4aV.js → chunk-6AEJRKK7-BLBlw8ui.js} +2 -2
- package/dist/{chunk-742MDFTN-Cs1qGiTK.js → chunk-742MDFTN-CsUjFyzK.js} +2 -2
- package/dist/{chunk-7INBJB4K-BY5n77ka.js → chunk-7INBJB4K-k-I67NSP.js} +6 -6
- package/dist/{chunk-7M6MHVWA-C3x6E3yT.js → chunk-7M6MHVWA-CUtKEeuL.js} +9 -9
- package/dist/{chunk-7PRAP22T-_Av9vcXz.js → chunk-7PRAP22T-0C6_H8Li.js} +1 -1
- package/dist/{chunk-GNY47TPC-D1E6DHDV.js → chunk-GNY47TPC-CB_83G7J.js} +11 -11
- package/dist/{chunk-GTNCS2PH-BZ9WMD1v.js → chunk-GTNCS2PH-AJi9PVT7.js} +3 -3
- package/dist/{chunk-GWA4HPMP-Co-A6dtV.js → chunk-GWA4HPMP-B8jXI0Tl.js} +3 -3
- package/dist/{chunk-MBY4JIJT-C3RQP_Nk.js → chunk-MBY4JIJT-BFIqOe-8.js} +4 -4
- package/dist/{chunk-NETBCI7D-DJUuN4EY.js → chunk-NETBCI7D-Cywmx0rz.js} +1 -1
- package/dist/{chunk-O7XYJQB3-A66QSOPA.js → chunk-O7XYJQB3-BChweQ_V.js} +1 -1
- package/dist/{chunk-UA2S7LBM-BNeHeMx9.js → chunk-UA2S7LBM-ChuksCDg.js} +6 -6
- package/dist/{chunk-WEXAMYUT-CucJfehv.js → chunk-WEXAMYUT-CbFOhEve.js} +2 -2
- package/dist/{chunk-XXDRQBXY-FgXU14Bh.js → chunk-XXDRQBXY-dKXxu_aj.js} +1 -1
- package/dist/{chunk-Z7XXMR3K-DMTBxZxM.js → chunk-Z7XXMR3K-Ccb3Rfjl.js} +8 -8
- package/dist/{chunk-ZIGJFQKS-C3xUAXJq.js → chunk-ZIGJFQKS-9VXArDKF.js} +3 -3
- package/dist/{classDiagram-v2-NBCMYWYE-BDaxYvDy.js → classDiagram-v2-NBCMYWYE-CRgxPgLD.js} +14 -14
- package/dist/clsx-CbxlCXZ3.js +16 -0
- package/dist/components/base-content.css +139 -0
- package/dist/components/base.css +150 -0
- package/dist/components/components.css +2 -0
- package/dist/components/reduced-motion.css +56 -0
- package/dist/components/theme-tokens.css +116 -0
- package/dist/components/ui/actions/button-group/button-group.css +1 -1
- package/dist/components/ui/feedback/toast/toast.css +21 -0
- package/dist/components/ui/primitives/label/label.css +33 -0
- package/dist/{components-DrStgM4J.js → components-Bg1bqGbc.js} +34666 -34274
- package/dist/components.js +6 -6
- package/dist/{cose-bilkent-JH36ORCC-DRUT3qBb.js → cose-bilkent-JH36ORCC-P94vkHv_.js} +2 -2
- package/dist/{cynefinDiagram-VND7K2PF-B9HrdQW8.js → cynefinDiagram-VND7K2PF-EIpXbLXm.js} +4 -4
- package/dist/{dagre-6A5THRUB-DZF0nICj.js → dagre-6A5THRUB-c8yG3_vh.js} +20 -20
- package/dist/{diagram-22UHCM2B-CVZYpeev.js → diagram-22UHCM2B-C3Xcfy6D.js} +15 -15
- package/dist/{diagram-3UASUU5V-D7MmBJbM.js → diagram-3UASUU5V-maEO61Bh.js} +7 -7
- package/dist/{diagram-ATOU4E4O-CNY-8kl0.js → diagram-ATOU4E4O-isL7nwg2.js} +3 -3
- package/dist/{diagram-CDSNMT55-C8Mk1smi.js → diagram-CDSNMT55-Bnhlo5at.js} +5 -5
- package/dist/{diagram-MLGK6HIB-CpfeILwW.js → diagram-MLGK6HIB-DvBcyz8v.js} +4 -4
- package/dist/{diagram-MPIPVDR6-Do7bUnTd.js → diagram-MPIPVDR6-iTevwmNE.js} +4 -4
- package/dist/{dist-C3YK_ean.js → dist-B6_-RPZ3.js} +1 -1
- package/dist/{dist-CRbPtqwD.js → dist-BvUQHUs_.js} +4 -4
- package/dist/dnd.js +1 -1
- package/dist/{ebnfDiagram-ZINNZB2B-1GewX9Zq.js → ebnfDiagram-ZINNZB2B-RVuQIpm0.js} +4 -4
- package/dist/{elk-276RUBZZ-DI4eicFj.js → elk-276RUBZZ-BoWoJr6K.js} +26 -26
- package/dist/{erDiagram-OPXOYQCR-CUbpVns3.js → erDiagram-OPXOYQCR-CVvgZCSJ.js} +14 -14
- package/dist/field-blur-Bwa48QBk.js +3637 -0
- package/dist/flowDiagram-KWPJA3E3-RdU0csul.js +17 -0
- package/dist/{ganttDiagram-FUAMR5RP-D3vmspam.js → ganttDiagram-FUAMR5RP-DPLDPJtJ.js} +6 -6
- package/dist/{gitGraphDiagram-X574FWY7-80yOq8dE.js → gitGraphDiagram-X574FWY7-DerJL1Q-.js} +3 -3
- package/dist/{graphlib-DLJn7qS3.js → graphlib-DHgEBs83.js} +6 -6
- package/dist/hooks.js +3 -3
- package/dist/{i18n-provider-DBFRz5uH.js → i18n-provider-CA8YAlje.js} +1 -1
- package/dist/i18n.js +6 -6
- package/dist/icon-button-G9zdQ3Ix.js +2753 -0
- package/dist/{infoDiagram-VRGFBTTK-Cboh-B9n.js → infoDiagram-VRGFBTTK-CtVsLyCQ.js} +3 -3
- package/dist/{isArrayLikeObject-CO5xeuaJ.js → isArrayLikeObject-BSfDsA7T.js} +3 -3
- package/dist/{isEmpty-DOC38Ces.js → isEmpty-BEe-nNXw.js} +3 -3
- package/dist/{ishikawaDiagram-OU5B5YK6-DwTd1YxL.js → ishikawaDiagram-OU5B5YK6-D1n8vp8C.js} +4 -4
- package/dist/{journeyDiagram-ZHPQQLJL-Dxvl3oeN.js → journeyDiagram-ZHPQQLJL-CTGnr-JY.js} +4 -4
- package/dist/{kanban-definition-PNTS6WVX-S8plI4wn.js → kanban-definition-PNTS6WVX-D1-Owjil.js} +10 -10
- package/dist/lib/components/ui/actions/button/busy-click-capture.d.ts +11 -0
- package/dist/lib/components/ui/actions/button/button-tones.d.ts +36 -0
- package/dist/lib/components/ui/actions/button/button.d.ts +19 -0
- package/dist/lib/components/ui/actions/button/button.wrap.d.ts +8 -0
- package/dist/lib/components/ui/actions/button/label-text.d.ts +10 -0
- package/dist/lib/components/ui/actions/button/use-click-lock.d.ts +18 -0
- package/dist/lib/components/ui/actions/copy-button/copy-button.d.ts +5 -1
- package/dist/lib/components/ui/actions/icon-button/icon-button.compound-variants.d.ts +114 -0
- package/dist/lib/components/ui/collections/data-table/hooks.d.ts +13 -13
- package/dist/lib/components/ui/collections/data-table/partials/data-table-empty-row.d.ts +5 -1
- package/dist/lib/components/ui/collections/data-table/partials/data-table-scroll-region.d.ts +16 -0
- package/dist/lib/components/ui/collections/data-table/partials/data-table-toolbar.d.ts +24 -0
- package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +10 -0
- package/dist/lib/components/ui/collections/data-table/types.d.ts +9 -0
- package/dist/lib/components/ui/collections/data-table/use-data-table.d.ts +12 -1
- package/dist/lib/components/ui/collections/data-table/use-scroll-edges.d.ts +22 -0
- package/dist/lib/components/ui/dates/calendar/calendar-day.d.ts +8 -0
- package/dist/lib/components/ui/dates/calendar/calendar.d.ts +3 -3
- package/dist/lib/components/ui/dates/calendar/calendar.utils.d.ts +8 -0
- package/dist/lib/components/ui/dates/calendar/use-calendar-labels.d.ts +22 -0
- package/dist/lib/components/ui/display/description/description.d.ts +2 -1
- package/dist/lib/components/ui/display/list-tile/list-tile.d.ts +9 -1
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-body.d.ts +11 -0
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-classes.d.ts +10 -0
- package/dist/lib/components/ui/feedback/alert-dialog/alert-dialog.d.ts +1 -1
- package/dist/lib/components/ui/feedback/toast/partials/toast-actions.d.ts +3 -0
- package/dist/lib/components/ui/feedback/toast/toast.d.ts +27 -3
- package/dist/lib/components/ui/feedback/toast/toaster.d.ts +19 -0
- package/dist/lib/components/ui/feedback/toast/toasts.d.ts +3 -0
- package/dist/lib/components/ui/forms/field-blur.d.ts +7 -2
- package/dist/lib/components/ui/forms/use-field-blur.d.ts +8 -1
- package/dist/lib/components/ui/layout/app-shell/activity-content.d.ts +10 -1
- package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +26 -2
- package/dist/lib/components/ui/layout/app-shell/app-header.d.ts +8 -2
- package/dist/lib/components/ui/layout/app-shell/app-sidebar.d.ts +8 -2
- package/dist/lib/components/ui/layout/app-shell/types.d.ts +3 -0
- package/dist/lib/components/ui/layout/app-shell/use-content-scroll.d.ts +10 -0
- package/dist/lib/components/ui/layout/app-shell/use-navigation-sheet.d.ts +19 -0
- package/dist/lib/components/ui/layout/sidebar/index.d.ts +5 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-content.d.ts +2 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-context.d.ts +7 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-footer.d.ts +2 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-header.d.ts +2 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-menu-item-tooltip.d.ts +7 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-menu-item.d.ts +31 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-menu-label.d.ts +4 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar.d.ts +1 -12
- package/dist/lib/components/ui/overlays/context-menu/context-menu-items.d.ts +10 -0
- package/dist/lib/components/ui/overlays/context-menu/context-menu-sub.d.ts +8 -0
- package/dist/lib/components/ui/overlays/context-menu/context-menu.d.ts +4 -14
- package/dist/lib/components/ui/overlays/dialog/dialog-outside.d.ts +6 -0
- package/dist/lib/components/ui/overlays/dialog/dialog-scroll-content.d.ts +1 -1
- package/dist/lib/components/ui/overlays/dialog/dialog.d.ts +1 -1
- package/dist/lib/components/ui/overlays/dialog/use-return-focus.d.ts +3 -2
- package/dist/lib/components/ui/overlays/drawer/drawer.d.ts +1 -1
- package/dist/lib/components/ui/overlays/dropdown-menu/dropdown-menu-items.d.ts +11 -0
- package/dist/lib/components/ui/overlays/dropdown-menu/dropdown-menu-sub.d.ts +8 -0
- package/dist/lib/components/ui/overlays/dropdown-menu/dropdown-menu.d.ts +6 -21
- package/dist/lib/components/ui/overlays/popover/popover.d.ts +1 -1
- package/dist/lib/components/ui/overlays/sheet/sheet.d.ts +1 -1
- package/dist/lib/components/ui/primitives/card/card.d.ts +10 -1
- package/dist/lib/components/ui/primitives/link/link-component.d.ts +14 -0
- package/dist/lib/components/ui/selection/select/select.d.ts +1 -1
- package/dist/lib/components/ui/selection/select-chip-field/select-chip-field.d.ts +1 -1
- package/dist/lib/i18n/resources/en/en.d.ts +9 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +13 -4
- package/dist/lib/i18n/resources/fr/fr.d.ts +9 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +13 -4
- package/dist/lib/i18n/resources/resources.d.ts +18 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/lib/redux/status-slice.d.ts +1 -1
- package/dist/lib.js +39 -55
- package/dist/main.js +13 -13
- package/dist/map.js +35 -35
- package/dist/material.css +1 -1
- package/dist/{merge-CuDTNgpm.js → merge-CRXQ4NSv.js} +4 -4
- package/dist/{mermaid.core-O7mtb1-p.js → mermaid.core-C_JPRVNa.js} +49 -49
- package/dist/{mindmap-definition-NLK3R4M7-CO0E97EZ.js → mindmap-definition-NLK3R4M7-OTZIWZah.js} +13 -13
- package/dist/{pegDiagram-GJSIUBJH-CAO6aYvj.js → pegDiagram-GJSIUBJH-CWKobqLl.js} +4 -4
- package/dist/{pieDiagram-5QR66LMP-BXxVfv5K.js → pieDiagram-5QR66LMP-BpmwlCYh.js} +5 -5
- package/dist/print.js +2 -2
- package/dist/{quadrantDiagram-O4NWA36T-BbidZDM8.js → quadrantDiagram-O4NWA36T-CIFt4MgA.js} +3 -3
- package/dist/{railroadDiagram-XR7U4H2S-DmKDXg8V.js → railroadDiagram-XR7U4H2S-fsZ7oqPG.js} +4 -4
- package/dist/{requirementDiagram-PLB6GJNP-wFvgSWJP.js → requirementDiagram-PLB6GJNP-BJaiDsKj.js} +13 -13
- package/dist/rolldown-runtime-BFFgcFxR.js +30 -0
- package/dist/{sankeyDiagram-IPEJSGJF-Cka5t09q.js → sankeyDiagram-IPEJSGJF-XDAIB5as.js} +3 -3
- package/dist/{sequenceDiagram-PO4LG4MO-B2qb55A5.js → sequenceDiagram-PO4LG4MO-Bgx1kuGA.js} +5 -5
- package/dist/{sortable-provider-C5VYhY8u.js → sortable-provider-MCgOzfRp.js} +24 -24
- package/dist/{src-C9kQSo6K.js → src-CsLQInsN.js} +1 -1
- package/dist/{stateDiagram-v2-GCMORJYK-EMa7q3ju.js → stateDiagram-v2-GCMORJYK-CGo16q99.js} +14 -14
- package/dist/{stored-language-B0o4cBh8.js → stored-language-Cz89IYZX.js} +26 -8
- package/dist/styles.css +10 -368
- package/dist/{swimlanes-2SLR337P-BtZ7Di5u.js → swimlanes-2SLR337P-DXZt0zyd.js} +12 -12
- package/dist/swimlanesDiagram-TC7HE7FX-Cno6MSMH.js +28 -0
- package/dist/{timeline-definition-EJHVYXUP-2Z6UABw8.js → timeline-definition-EJHVYXUP-CbPIZcT7.js} +5 -5
- package/dist/{translate-material-BCwQrt2G.js → translate-material-Caxsy0bp.js} +2 -2
- package/dist/{translator-CwpmpI24.js → translator-B85Q_O55.js} +1 -1
- package/dist/{use-heading-spy-BtBJiU63.js → use-heading-spy-C4yWEr5c.js} +37 -37
- package/dist/{use-material-translation-DG_7z3Q7.js → use-material-translation-Cm3Ej3bT.js} +2 -2
- package/dist/{usecaseDiagram-POWQR4AR-DT56cp_B.js → usecaseDiagram-POWQR4AR-D_gyFA3m.js} +13 -13
- package/dist/{utils-UBn-46YY.js → utils-DC8bw9A_.js} +346 -360
- package/dist/{vennDiagram-UO4OBE2U-CO0yFCI8.js → vennDiagram-UO4OBE2U-BpgP__1T.js} +4 -4
- package/dist/{wardleyDiagram-VNRHLVJA-QkwghAYo.js → wardleyDiagram-VNRHLVJA-CYqKMb2f.js} +4 -4
- package/dist/{xychartDiagram-PMCCYNJV-B2aZlrno.js → xychartDiagram-PMCCYNJV-W-fXV6iN.js} +7 -7
- package/docs/components/buttons-and-actions.md +60 -10
- package/docs/components/data-display.md +23 -1
- package/docs/components/data-table-filter-and-lists.md +88 -10
- package/docs/components/date-and-time.md +24 -0
- package/docs/components/feedback-and-status.md +85 -8
- package/docs/components/form-fields.md +14 -5
- package/docs/components/media-and-files.md +7 -0
- package/docs/components/navigation-and-layout.md +21 -3
- package/docs/components/overlays-and-menus.md +21 -6
- package/docs/components/primitives.md +16 -2
- package/docs/components/selection-and-choice.md +13 -2
- package/docs/getting-started.md +4 -2
- package/docs/guides/app-shell.md +97 -4
- package/docs/guides/forms.md +54 -5
- package/docs/guides/helpers.md +2 -2
- package/docs/guides/i18n.md +5 -2
- package/docs/guides/ssr.md +1 -1
- package/docs/guides/theming.md +133 -5
- package/docs/previews/data-display-description.webp +0 -0
- package/docs/previews/feedback-status-toast.webp +0 -0
- package/docs/roadmap.md +22 -1
- package/docs/troubleshooting.md +963 -11
- package/lib/components/base-content.css +139 -0
- package/lib/components/base.css +150 -0
- package/lib/components/components.css +2 -0
- package/lib/components/reduced-motion.css +56 -0
- package/lib/components/theme-tokens.css +116 -0
- package/lib/components/ui/actions/button-group/button-group.css +1 -1
- package/lib/components/ui/feedback/toast/toast.css +21 -0
- package/lib/components/ui/primitives/label/label.css +33 -0
- package/lib/styles.css +10 -368
- package/package.json +2 -2
- package/dist/field-blur-DWQtxiAe.js +0 -3483
- package/dist/flowDiagram-KWPJA3E3-DZxumFfu.js +0 -17
- package/dist/icon-button-BxySFfrr.js +0 -2718
- package/dist/swimlanesDiagram-TC7HE7FX-CROh8GTF.js +0 -28
|
@@ -59,11 +59,14 @@ which work alone.
|
|
|
59
59
|
| Prop | Type | Default | Effect |
|
|
60
60
|
| --- | --- | --- | --- |
|
|
61
61
|
| `items` | `MenuItem[]` | — | `label` (already translated), `url`, `icon`, `footer` |
|
|
62
|
+
| `linkComponent` | `AppShellLinkComponent` | React Router `Link` | The link each menu entry renders, given `href`; see the [app shell guide](../guides/app-shell.md) |
|
|
62
63
|
| `brand` | `ReactNode` | — | sidebar header |
|
|
63
64
|
| `avatar` | `ReactNode` | — | an image URL or a node, in the header |
|
|
64
65
|
| `headerActions` | `ReactNode` | — | header, before the avatar |
|
|
65
66
|
| `title`, `subtitle`, `actions` | `ReactNode` | — | `ActivityContent`'s header card |
|
|
66
67
|
| `header` / `hideHeader` | `ReactNode` / `boolean` | — | `ActivityContent`: replace or drop the header card |
|
|
68
|
+
| `contentAs` / `scroll` | `'main' \| 'div'` / `'document' \| 'container'` | `'main'` / `'document'` | the content region is the page's `<main>`; the document scrolls (see the [app shell guide](../guides/app-shell.md#landmarks-headings-and-scrolling)) |
|
|
69
|
+
| `titleAs` | `'h1'` … `'h6'` \| `'div'` | `'h1'` | `ActivityContent`: the title's heading level |
|
|
67
70
|
|
|
68
71
|
The active entry is the longest match across the whole menu, segment-aware.
|
|
69
72
|
`AppBreadcrumb` reads `handle.breadcrumb` from `useMatches()`, so the shell
|
|
@@ -110,12 +113,27 @@ import {
|
|
|
110
113
|
```
|
|
111
114
|
|
|
112
115
|
Parts: `Sidebar`, `SidebarHeader`, `SidebarContent`, `SidebarFooter`,
|
|
113
|
-
`SidebarMenuLabel` (`actions` slot), `SidebarMenuItem` (a button
|
|
116
|
+
`SidebarMenuLabel` (`actions` slot), `SidebarMenuItem` (a button, or a link with `href` and `linkComponent`: `icon`,
|
|
114
117
|
`active`, `tooltip`).
|
|
115
118
|
|
|
119
|
+
A `SidebarMenuItem` stays a `<button>` without `href`. With `href` it renders a
|
|
120
|
+
real link, a plain `<a>` unless you pass `linkComponent` (given the `href`):
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<SidebarMenuItem href="/inbox" linkComponent={MyLink} icon={<Inbox />}>Inbox</SidebarMenuItem>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
An entry's tooltip is its label, shown by default **only while the
|
|
127
|
+
`Sidebar` is collapsed**: expanded, the label is on screen and no tooltip
|
|
128
|
+
opens. A `tooltip` you pass is always shown, collapsed or expanded:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<SidebarMenuItem icon={<Inbox />} tooltip="3 unread">Inbox</SidebarMenuItem>
|
|
132
|
+
```
|
|
133
|
+
|
|
116
134
|
| Prop | Type | Default | Effect |
|
|
117
135
|
| --- | --- | --- | --- |
|
|
118
|
-
| `expanded` | `boolean` | `false` | labels beside the icons; collapsed shows icons with tooltips |
|
|
136
|
+
| `expanded` | `boolean` | `false` | labels beside the icons; collapsed shows icons with their labels as tooltips |
|
|
119
137
|
| `onExpandedChange` | `(expanded: boolean) => void` | — | called by the toggle |
|
|
120
138
|
| `withToggleIcon` | `boolean` | `false` | built-in expand / collapse button, shown on hover or while focus is inside the sidebar, named "Toggle sidebar" (`material.sidebar.toggle`) with `aria-expanded` |
|
|
121
139
|
|
|
@@ -573,7 +591,7 @@ import { Background, Card } from '@nxgt/material/components';
|
|
|
573
591
|
</Background>
|
|
574
592
|
```
|
|
575
593
|
|
|
576
|
-
No props of its own. It fills the viewport (`
|
|
594
|
+
No props of its own. It fills the viewport (`w-lvw h-lvh`); pass a `className`
|
|
577
595
|
such as `h-96 w-full` to use it as a band. `ActivityLayout` and
|
|
578
596
|
`ThreadLayout` sit on it.
|
|
579
597
|
|
|
@@ -8,6 +8,19 @@ command palette) with this library's styling, and render into a portal on
|
|
|
8
8
|
[Feedback & status](feedback-and-status.md). Everything is imported from
|
|
9
9
|
`@nxgt/material/components`.
|
|
10
10
|
|
|
11
|
+
**The page behind a modal layer is inert.** While a `Dialog`, `Sheet`,
|
|
12
|
+
`Drawer`, `AlertDialog` or `CommandDialog` is open — and a `Select` list, a
|
|
13
|
+
`ContextMenu`, or a `DropdownMenu` or `Popover` given `modal` — everything else
|
|
14
|
+
on the page is `inert` as well as `aria-hidden`: its controls leave the tab
|
|
15
|
+
order and the accessibility tree, so a keyboard or screen-reader user stays in
|
|
16
|
+
the layer and axe's `aria-hidden-focus` passes. Live regions stay reachable:
|
|
17
|
+
a toast shown over a dialog is announced, and its action can be used. Layers
|
|
18
|
+
stack: a `Select` open in a `Dialog` makes the dialog inert too, until it
|
|
19
|
+
closes. Focus left on the page when the layer opens (an `onOpenAutoFocus`
|
|
20
|
+
that prevents the default and focuses nothing) goes to the layer's content.
|
|
21
|
+
Non-modal layers (`modal={false}`, and `DropdownMenu` and `Popover` by
|
|
22
|
+
default) leave the page as it is.
|
|
23
|
+
|
|
11
24
|
| | Component | What it does |
|
|
12
25
|
| --- | --- | --- |
|
|
13
26
|
| <img src="../previews/overlays-menus-dialog.webp" width="96" alt=""> | [Dialog](#dialog) | Modal dialog with an icon close button |
|
|
@@ -92,7 +105,7 @@ export function Terms() {
|
|
|
92
105
|
| --- | --- | --- | --- |
|
|
93
106
|
| `defaultOpen` / `open` (`Dialog`) | `boolean` | `false` | open state, uncontrolled / controlled |
|
|
94
107
|
| `onOpenChange` (`Dialog`) | `(open: boolean) => void` | — | called on trigger, Escape, outside click and close |
|
|
95
|
-
| `modal` (`Dialog`) | `boolean` | `true` | `false` drops the backdrop and lets the page stay interactive |
|
|
108
|
+
| `modal` (`Dialog`) | `boolean` | `true` | `false` drops the backdrop and lets the page stay interactive; `true` makes the page behind `inert` while it is open |
|
|
96
109
|
| `asChild` (`DialogTrigger`) | `boolean` | **`true`** | the trigger is its child — pass one element, usually a `Button` |
|
|
97
110
|
| `variant`, `color` (`DialogClose`) | as `IconButton` | `'ghost'` | a top-right X button, named "Close" (`material.buttons.close`, translated) while it shows only the icon; text children or an `aria-label` name it instead, and icon-only children leave it unnamed; children replace the X |
|
|
98
111
|
|
|
@@ -100,8 +113,9 @@ export function Terms() {
|
|
|
100
113
|
It ignores outside clicks that land on an open `Combobox` list, so a combobox
|
|
101
114
|
inside a dialog works. On open it moves focus into the dialog: to its first
|
|
102
115
|
control, or to the content itself when it has none. An `onOpenAutoFocus` of
|
|
103
|
-
yours that calls `event.preventDefault()` keeps focus
|
|
104
|
-
what you want yourself)
|
|
116
|
+
yours that calls `event.preventDefault()` keeps focus off that control (then
|
|
117
|
+
focus what you want yourself): a modal dialog then focuses its content, as the
|
|
118
|
+
page behind it is inert, and a non-modal one leaves focus where it was.
|
|
105
119
|
On close it hands focus back to the element that had it when the dialog
|
|
106
120
|
opened — its `DialogTrigger`, or the button of yours that set `open` on a
|
|
107
121
|
controlled `Dialog` — if that element is still on the page and focus has not
|
|
@@ -368,7 +382,7 @@ the fullscreen element shows.
|
|
|
368
382
|
|
|
369
383
|

|
|
370
384
|
|
|
371
|
-
Menu with checkbox and radio items, submenus, shortcuts and an error item.
|
|
385
|
+
Menu with checkbox and radio items, submenus, shortcuts and an error item. It is non-modal by default: Radix's modal menu marks the rest of the page `aria-hidden` while its buttons and links stay focusable, which accessibility audits (axe `aria-hidden-focus`) report; a non-modal menu hides nothing and still closes on Escape or an outside click (Tab stays in the menu). Pass `modal` for a modal one: the page behind it is then `inert` too, so its controls leave the tab order and axe passes.
|
|
372
386
|
|
|
373
387
|
```tsx
|
|
374
388
|
import {
|
|
@@ -408,11 +422,12 @@ Parts: `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`,
|
|
|
408
422
|
|
|
409
423
|
| Prop | Type | Default | Effect |
|
|
410
424
|
| --- | --- | --- | --- |
|
|
425
|
+
| `modal` (`DropdownMenu`) | `boolean` | `false` | `true` brings back Radix's modal menu: scroll lock, outside clicks swallowed, the rest of the page `aria-hidden` and `inert` |
|
|
411
426
|
| `variant` (`DropdownMenuItem`) | `'default' \| 'error'` | `'default'` | error colour for a destructive item |
|
|
412
|
-
| `active` (`DropdownMenuItem`) | `boolean` | — | highlights the current item (filled primary) |
|
|
427
|
+
| `active` (`DropdownMenuItem`) | `boolean` | — | highlights the current item (filled primary) and sets `data-active` on it; style or select it with `data-[active]:` or `[data-active]` |
|
|
413
428
|
| `inset` (item, label, sub-trigger) | `boolean` | — | indents to line up with checkbox and radio items |
|
|
414
429
|
| `onSelect` (`DropdownMenuItem`) | `(event: Event) => void` | — | the item's action; `event.preventDefault()` keeps the menu open |
|
|
415
|
-
| `defaultOpen` / `open
|
|
430
|
+
| `defaultOpen` / `open` (`DropdownMenu`) | `boolean` | — | open state; see `modal` above for the page behind the menu |
|
|
416
431
|
| `container` (`DropdownMenuContent`) | `Element \| DocumentFragment \| null` | `document.body` | where the menu is portalled; for a menu opened in fullscreen, pass `document.fullscreenElement`, read when the menu opens (see [troubleshooting](../troubleshooting.md#a-dropdownmenu-opened-in-fullscreen-does-not-show)) |
|
|
417
432
|
|
|
418
433
|
**Storybook:** `Overlays & menus/DropdownMenu` · **Vue:** `DropdownMenu` (same parts)
|
|
@@ -147,7 +147,9 @@ import {
|
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
Parts: `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`,
|
|
150
|
-
`CardContent`, `CardFooter` — each a styled `<div>` taking its element's props
|
|
150
|
+
`CardContent`, `CardFooter` — each a styled `<div>` taking its element's props,
|
|
151
|
+
except `CardTitle`, a heading: an `h3` by default, another level or a `div`
|
|
152
|
+
with `as` (`<CardTitle as="h2">`), so `getByRole('heading', { name })` finds it.
|
|
151
153
|
`CardAction` sits top-right of the header.
|
|
152
154
|
|
|
153
155
|
**Storybook:** `Primitives/Card` · **Vue:** `Card` (same parts)
|
|
@@ -224,12 +226,24 @@ import { Input, Label } from '@nxgt/material/components';
|
|
|
224
226
|
|
|
225
227
|
| Prop | Type | Default | Effect |
|
|
226
228
|
| --- | --- | --- | --- |
|
|
227
|
-
| `withAsterisk` | `boolean` | — |
|
|
229
|
+
| `withAsterisk` | `boolean` | — | draws a red `*` with CSS after the label, so it is in neither the label's text nor its accessible name (`getByLabel('Email', { exact: true })` and `getByLabelText` match "Email"): mark the field itself `required` (or `aria-required`), which every field of this library does for you |
|
|
228
230
|
| `htmlFor` | `string` | — | the control it labels; clicking the label focuses it |
|
|
229
231
|
|
|
230
232
|
Fields on the [Form fields](form-fields.md) page render it for you through
|
|
231
233
|
their `label` prop.
|
|
232
234
|
|
|
235
|
+
To restyle the mark, target `[data-slot="label-text"][data-required]::after`:
|
|
236
|
+
`background-color` sets its colour (default `var(--color-error)`), `width` and
|
|
237
|
+
`height` its size. It stays visible in forced-colors mode and in print.
|
|
238
|
+
|
|
239
|
+
```css
|
|
240
|
+
[data-slot="label-text"][data-required]::after {
|
|
241
|
+
background-color: #b45309;
|
|
242
|
+
width: 0.75em;
|
|
243
|
+
height: 0.75em;
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
233
247
|
**Storybook:** `Primitives/Label` · **Vue:** `Label`
|
|
234
248
|
|
|
235
249
|
---
|
|
@@ -30,7 +30,9 @@ value type each one writes. Everything is imported from
|
|
|
30
30
|
|
|
31
31
|

|
|
32
32
|
|
|
33
|
-
Select primitives with groups, labels and scroll buttons.
|
|
33
|
+
Select primitives with groups, labels and scroll buttons. Its list is modal:
|
|
34
|
+
while it is open the rest of the page is `inert` (a `Select` in a `Dialog`
|
|
35
|
+
makes the dialog inert too, until the list closes).
|
|
34
36
|
|
|
35
37
|
```tsx
|
|
36
38
|
import {
|
|
@@ -132,9 +134,13 @@ export function Framework() {
|
|
|
132
134
|
| `onValueChange` | `(value: string) => void` | — | Radix Select's |
|
|
133
135
|
| `renderOption` | `(option: SelectOption) => ReactNode` | — | custom item content |
|
|
134
136
|
| `placeholder` | `ReactNode` | `label` | the trigger's text while nothing is selected |
|
|
135
|
-
| `required` | `boolean` | — | an asterisk after the label, and Radix Select's `required
|
|
137
|
+
| `required` | `boolean` | — | an asterisk after the label, and Radix Select's `required`; the trigger is named by the label |
|
|
136
138
|
| `label`, `helperText`, `error` | | — | the field's text and state |
|
|
137
139
|
|
|
140
|
+
**Accessibility:** the trigger is named by `label`, described by the help or
|
|
141
|
+
error text (`aria-describedby`) and marked `aria-invalid` on error, so
|
|
142
|
+
`getByRole('combobox', { name: 'Framework' })` finds it and
|
|
143
|
+
`toHaveAccessibleDescription(…)` / `toBeInvalid()` match.
|
|
138
144
|
**Emits:** `string`.
|
|
139
145
|
**Form wrapper:** `SelectFormField` — `control` and `name`, see
|
|
140
146
|
[forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
|
|
@@ -530,6 +536,11 @@ export function Topics() {
|
|
|
530
536
|
| `onValueChange` | `(value: string[]) => void` | — | |
|
|
531
537
|
| `options` | `{ value, label, icon? }[]` | — | the chips |
|
|
532
538
|
|
|
539
|
+
**Accessibility:** the chips are toggle buttons in a `role="group"` named by
|
|
540
|
+
`label` and described by the help text: a chosen chip is
|
|
541
|
+
`aria-pressed="true"` (never `aria-selected`), in `single` mode too.
|
|
542
|
+
Query `getByRole('group', { name: 'Topics' })`, then
|
|
543
|
+
`getByRole('button', { name: 'Design' })`; there is no `option` role.
|
|
533
544
|
**Emits:** `string[]`. `SelectChipFormField` stores `string | null` in
|
|
534
545
|
`single` mode (`null` once the choice is cleared; `''` and `null` both show
|
|
535
546
|
nothing selected) and `string[]` in `multiple`. Options are matched by
|
package/docs/getting-started.md
CHANGED
|
@@ -20,7 +20,7 @@ hooks throw ("Invalid hook call") and design tokens come from two builds.
|
|
|
20
20
|
| `react-hook-form` | `^7` | every `*FormField` wrapper ([forms](guides/forms.md)) |
|
|
21
21
|
| `react-redux`, `@reduxjs/toolkit` | `^9`, `^2` | `DataTable`, filter, gallery, kanban, player, the slice helpers in `/lib` |
|
|
22
22
|
| `@tanstack/react-table` | `^9` | `DataTable` |
|
|
23
|
-
| `react-use` | `^17` | `useTheme`, `useIsMobile`, `ScrollProgress` (`useScroll` / `useWindowScroll`),
|
|
23
|
+
| `react-use` | `^17` | `useTheme`, `useIsMobile`, `ScrollProgress` (`useScroll` / `useWindowScroll`), other hooks |
|
|
24
24
|
|
|
25
25
|
**Optional peers** are only needed by the entry that uses them. An app that
|
|
26
26
|
does not import that entry never has them in its tree.
|
|
@@ -255,7 +255,9 @@ A `ConfirmationDialog` given `open` follows it, and closes only through
|
|
|
255
255
|
open (an alert dialog to its Cancel), and hand it back on close. A test that
|
|
256
256
|
types or presses a key right after opening one now acts inside it
|
|
257
257
|
([details](troubleshooting.md#keys-typed-right-after-opening-a-dialog-sheet-or-drawer-land-inside-it)).
|
|
258
|
-
To keep focus
|
|
258
|
+
To keep focus off the first control, prevent the default (on a modal it
|
|
259
|
+
lands on the content, as the page behind is inert;
|
|
260
|
+
[details](troubleshooting.md#focus-moves-into-a-dialog-although-onopenautofocus-prevented-it)):
|
|
259
261
|
|
|
260
262
|
```tsx
|
|
261
263
|
<DialogContent onOpenAutoFocus={(event) => event.preventDefault()}>{/* … */}</DialogContent>
|
package/docs/guides/app-shell.md
CHANGED
|
@@ -51,8 +51,8 @@ wraps. Each leaf route renders `ActivityContent`.
|
|
|
51
51
|
`useMatches()`. That hook only works under a **data router**:
|
|
52
52
|
`createBrowserRouter` / `createMemoryRouter` + `RouterProvider`, or a
|
|
53
53
|
framework-mode app. Under `<BrowserRouter>` or `<MemoryRouter>` it throws
|
|
54
|
-
`useMatches must be used within a data router.`. The sidebar
|
|
55
|
-
|
|
54
|
+
`useMatches must be used within a data router.`. The sidebar entries are React Router `Link`s too, so the shell cannot render
|
|
55
|
+
outside a router.
|
|
56
56
|
|
|
57
57
|
In a test or a story:
|
|
58
58
|
|
|
@@ -142,10 +142,36 @@ partitionMenu(items); // { content, footer }
|
|
|
142
142
|
| `items` | `MenuItem[]` | — | Sidebar entries, and the mobile menu sheet |
|
|
143
143
|
| `brand` | `ReactNode` | — | Replaces the sidebar header: a logo, a product name |
|
|
144
144
|
| `avatar` | `ReactNode` | — | End of the header. A `string` is used as the image URL of an `Avatar`; anything else is rendered as-is |
|
|
145
|
+
| `linkComponent` | `AppShellLinkComponent` | React Router `Link` | The link each menu entry renders, given `href` (see below) |
|
|
145
146
|
| `headerActions` | `ReactNode` | — | In the header, before the avatar: notifications, help, `ThemeToggle` |
|
|
146
147
|
| `children` | `ReactNode` | — | The routed content, usually `<Outlet />` |
|
|
148
|
+
| `contentAs` | `'main' \| 'div'` | `'main'` | The content region's element: the page's `<main>` landmark, or a `div` when your app renders its own `<main>` |
|
|
149
|
+
| `scroll` | `'document' \| 'container'` | `'document'` | Who scrolls: the page (header and sidebar sticky), or the content region inside a shell one screen high |
|
|
150
|
+
|
|
151
|
+
Each menu entry is a real link, `<a href="/invoices">`, so it works before
|
|
152
|
+
hydration on a server-rendered page, opens in a new tab on middle-click, and
|
|
153
|
+
is announced as a link, named by its label alone ("Identities": the icon beside
|
|
154
|
+
it is decorative); the current page's entry carries `aria-current="page"`.
|
|
155
|
+
By default it is a React Router `Link` (client-side navigation, the router's
|
|
156
|
+
`basename`), so leaving `linkComponent` out never reloads the page. Pass it only
|
|
157
|
+
for another router or a custom link: a component taking
|
|
158
|
+
`href`. It must spread **every other prop it receives** onto the element it
|
|
159
|
+
renders (`className`, `aria-current`, the tooltip's handlers, `data-state`) and
|
|
160
|
+
forward `ref`: the styling, the tooltip and the current-page mark arrive that
|
|
161
|
+
way. A link that forwards only a `DataTable`'s `DataTableLinkProps` is not
|
|
162
|
+
enough here.
|
|
147
163
|
|
|
148
|
-
|
|
164
|
+
```tsx
|
|
165
|
+
import { Link } from 'react-router';
|
|
166
|
+
import type { AppShellLinkComponent } from '@nxgt/material/components';
|
|
167
|
+
|
|
168
|
+
// Spread everything but `href`; `ref` is a prop in React 19.
|
|
169
|
+
const MyLink: AppShellLinkComponent = ({ href, ...props }) => <Link to={href} {...props} />;
|
|
170
|
+
// <ActivityLayout items={items} linkComponent={MyLink}>…</ActivityLayout>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`className` and the rest of the `div` props (`id`, `data-*`…) go to the outer
|
|
174
|
+
element, `[data-slot=activity-layout]`.
|
|
149
175
|
`avatar` and `headerActions` are props because the shell holds no session:
|
|
150
176
|
read the user wherever your app keeps it and pass the node.
|
|
151
177
|
|
|
@@ -180,7 +206,54 @@ export function Shell({ items, user }: { items: MenuItem[]; user: { name: string
|
|
|
180
206
|
```
|
|
181
207
|
|
|
182
208
|
On screens below 768 px the sidebar is hidden and the header shows a menu
|
|
183
|
-
button that opens the same entries in a sheet. Breadcrumbs are
|
|
209
|
+
button ("Open menu") that opens the same entries in a sheet. Breadcrumbs are
|
|
210
|
+
hidden there. The sheet is a dialog named "Navigation"
|
|
211
|
+
(`material.app-shell.navigation`) with a visible "Close" button. Following an
|
|
212
|
+
entry closes it — so does any change of location, Back included — and focus
|
|
213
|
+
goes to the page's `<main>`; closed with Escape, the close button or the
|
|
214
|
+
overlay, focus returns to the menu button. Its entries show their labels, so
|
|
215
|
+
they have no tooltip: none opens over the first entry as focus moves in.
|
|
216
|
+
|
|
217
|
+
## Landmarks, headings and scrolling
|
|
218
|
+
|
|
219
|
+
- **One `<main>`.** The content region is the page's `<main>` landmark
|
|
220
|
+
(`getByRole('main')`). If your app already renders a `<main>` around the
|
|
221
|
+
shell, pass `contentAs="div"`: a page has only one.
|
|
222
|
+
- **The screen's title is the `h1`.** `ActivityContent`'s `title` is a heading,
|
|
223
|
+
level 1 by default (`titleAs`); the shell itself renders no heading, so a
|
|
224
|
+
screen's title is the page's top one. A `CardTitle` is an `h3` (its `as`
|
|
225
|
+
changes the level).
|
|
226
|
+
- **The document scrolls.** The shell is `100%` wide and at least `100dvh`
|
|
227
|
+
high, so it never adds a scrollbar's width of horizontal scroll and its
|
|
228
|
+
bottom is never under a phone's toolbar. The page grows with the screen and
|
|
229
|
+
the browser scrolls it; the header and the desktop sidebar are sticky.
|
|
230
|
+
`scroll="container"` is the opt-in for a screen that manages its own height
|
|
231
|
+
(a full-height map, a board): the shell is then exactly `100dvh` high and
|
|
232
|
+
the content region scrolls inside it.
|
|
233
|
+
- **A new screen starts at the top.** With the document scrolling, React
|
|
234
|
+
Router's `<ScrollRestoration />` handles positions as for any page. With
|
|
235
|
+
`scroll="container"` the content region outlives navigations, and
|
|
236
|
+
`ScrollRestoration` only knows the window, so the shell does it for that
|
|
237
|
+
box: a new pathname opens at the top, and Back or Forward (a `POP`) returns
|
|
238
|
+
to where that entry was left (kept per `location.key`, for the session). A
|
|
239
|
+
change of query or hash only (a filter, a tab, an `#anchor`) keeps the
|
|
240
|
+
position.
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
import { Outlet } from 'react-router';
|
|
244
|
+
import { ActivityLayout, type MenuItem } from '@nxgt/material/components';
|
|
245
|
+
|
|
246
|
+
// The app renders its own <main>, and the board below needs a fixed height.
|
|
247
|
+
export function BoardShell({ items }: { items: MenuItem[] }) {
|
|
248
|
+
return (
|
|
249
|
+
<main>
|
|
250
|
+
<ActivityLayout items={items} contentAs="div" scroll="container">
|
|
251
|
+
<Outlet />
|
|
252
|
+
</ActivityLayout>
|
|
253
|
+
</main>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
184
257
|
|
|
185
258
|
## `ActivityContent`
|
|
186
259
|
|
|
@@ -191,6 +264,7 @@ button that opens the same entries in a sheet. Breadcrumbs are hidden there.
|
|
|
191
264
|
| `actions` | `ReactNode` | — | Right side of the header card: buttons |
|
|
192
265
|
| `header` | `ReactNode` | — | Replaces the header card entirely |
|
|
193
266
|
| `hideHeader` | `boolean` | `false` | No header card |
|
|
267
|
+
| `titleAs` | `'h1'` … `'h6'` \| `'div'` | `'h1'` | The title's element: the screen's heading, `'h2'` under a heading of yours, `'div'` for none |
|
|
194
268
|
|
|
195
269
|
```tsx
|
|
196
270
|
import { ActivityContent, Button } from '@nxgt/material/components';
|
|
@@ -208,6 +282,9 @@ export function InvoicesPage() {
|
|
|
208
282
|
}
|
|
209
283
|
```
|
|
210
284
|
|
|
285
|
+
The header card wraps: on a narrow screen the actions go under the title
|
|
286
|
+
instead of squeezing it, and a long title wraps rather than scrolling.
|
|
287
|
+
|
|
211
288
|
## Breadcrumbs
|
|
212
289
|
|
|
213
290
|
`AppBreadcrumb` (rendered by `ActivityLayout`) starts with a home icon linking
|
|
@@ -272,3 +349,19 @@ own. They take the same `items`; `AppHeader` takes `actions`, `avatar` and
|
|
|
272
349
|
`showMenuIcon` (default `true`). The lower-level `Sidebar`, `TopAppBar` and
|
|
273
350
|
`Breadcrumb` primitives are on the
|
|
274
351
|
[navigation & layout](../components/navigation-and-layout.md) page.
|
|
352
|
+
|
|
353
|
+
`AppHeader` needs a React Router context even on its own: its menu sheet reads
|
|
354
|
+
the location (`useLocation`) to close when it changes, and outside a router
|
|
355
|
+
that hook throws. Render it under your `RouterProvider` — or, in a test, inside
|
|
356
|
+
a `MemoryRouter`:
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
import { MemoryRouter } from 'react-router';
|
|
360
|
+
import { AppHeader } from '@nxgt/material/components';
|
|
361
|
+
|
|
362
|
+
render(
|
|
363
|
+
<MemoryRouter>
|
|
364
|
+
<AppHeader items={items} />
|
|
365
|
+
</MemoryRouter>,
|
|
366
|
+
);
|
|
367
|
+
```
|
package/docs/guides/forms.md
CHANGED
|
@@ -57,7 +57,7 @@ What a wrapper does for you, from the field state of `name`:
|
|
|
57
57
|
| `helperText` | `fieldState.error?.message`, or your own `helperText` when there is no error |
|
|
58
58
|
| `disabled` | your own `disabled`, or `field.disabled` from React Hook Form |
|
|
59
59
|
| `name` | the field `name` |
|
|
60
|
-
| `id` | yours when you pass one. Otherwise
|
|
60
|
+
| `id` | yours when you pass one. Otherwise every wrapper generates one (`useId`), never the field `name`, so two forms on one page sharing a field name (a sign-in and a sign-up form) keep their labels apart; a group's option *i* is `` `${id}-${i}` `` |
|
|
61
61
|
| `aria-labelledby` / `aria-describedby` | on `RadioGroupFormField` and `CheckboxGroupFormField`, the `radiogroup` / `group` is named by its `label` (`` `${id}-label` ``) and described by the helper or error text (`` `${id}-helper` ``), so a screen reader announces the question and the error with the group. The `radiogroup` also gets `aria-invalid`; the checkbox `group` does not (ARIA forbids it there), its error is its description — for an error with no message, a hidden "This value is not valid." inside your `helperText`. Your own `aria-label` / `aria-labelledby` wins; your `aria-describedby` ids are kept, first |
|
|
62
62
|
|
|
63
63
|
Every wrapper has the same shape: the React Hook Form half and the field's own
|
|
@@ -223,8 +223,18 @@ export function SubscribeForm({
|
|
|
223
223
|
schema's input type match what `NumberFormField` emits, and a cleared field
|
|
224
224
|
fails with "This field is required.".
|
|
225
225
|
- `Button` defaults to `type="button"`: the submit button needs
|
|
226
|
-
`type="submit"`. `loading`
|
|
227
|
-
|
|
226
|
+
`type="submit"`. With `loading={form.formState.isSubmitting}` it is busy
|
|
227
|
+
(`aria-busy`, `aria-disabled`, a spinner) from the first click until the
|
|
228
|
+
promise returned by `onSave` settles, and cancels every click meanwhile — a
|
|
229
|
+
double click, or Enter pressed twice in a field, submits once. It stays
|
|
230
|
+
focusable. For a `loading` that turns true only after an `await` (a
|
|
231
|
+
mutation's `isPending`), add `lockOnClick`
|
|
232
|
+
([Button](../components/buttons-and-actions.md#button)). The guard sees
|
|
233
|
+
clicks, Space and Enter on the button, and Enter in a field when it is the
|
|
234
|
+
form's first submit button; `form.requestSubmit()` and `form.submit()` fire
|
|
235
|
+
no click, so a submit your code starts (an OTP step that submits on its
|
|
236
|
+
last digit, a shortcut) checks `formState.isSubmitting` itself. A
|
|
237
|
+
`lockOnClick` whose `loading` never turns true releases after one second.
|
|
228
238
|
- Default values must have the type the field emits. See
|
|
229
239
|
[What each field emits](#what-each-field-emits).
|
|
230
240
|
|
|
@@ -247,8 +257,9 @@ Then, for the schema of [A complete form](#a-complete-form):
|
|
|
247
257
|
|
|
248
258
|
| Input | English | French |
|
|
249
259
|
| --- | --- | --- |
|
|
250
|
-
| `email: 'nope'` | `Invalid email address.` | `
|
|
251
|
-
| `name: 'ab'` for `z.string().min(3)` | `
|
|
260
|
+
| `email: 'nope'` | `Invalid email address.` | `Adresse e-mail invalide.` |
|
|
261
|
+
| `name: 'ab'` for `z.string().min(3)` | `Must contain at least 3 characters.` | `Doit contenir au moins 3 caractères.` |
|
|
262
|
+
| `bio` too long for `z.string().max(80)` | `Must contain at most 80 characters.` | `Doit contenir au plus 80 caractères.` |
|
|
252
263
|
| a missing key (`undefined`) | `This field is required.` | `Ce champ est requis.` |
|
|
253
264
|
|
|
254
265
|
A message set on the schema wins over the error map, so override one field
|
|
@@ -280,6 +291,13 @@ How it picks the language, and what that implies:
|
|
|
280
291
|
(`form.trigger()` re-runs it).
|
|
281
292
|
- It is safe on a server: with no `localStorage` (or when reading it throws)
|
|
282
293
|
it writes English, unless you pass `language`.
|
|
294
|
+
- A string length reads as a length (`Must contain at most 80 characters.`);
|
|
295
|
+
a number or a list keeps the size wording (`This value is too large: it
|
|
296
|
+
must be at most 2 items.`). The strings are `material.zod.too_big_string_*`
|
|
297
|
+
and `material.zod.too_small_string_*` (`inclusive` / `exclusive`): an app
|
|
298
|
+
that overrode `too_big_inclusive` for strings overrides these now.
|
|
299
|
+
- A format message that opens with its noun (`Invalid email address.`,
|
|
300
|
+
`Adresse e-mail invalide.`) starts with a capital letter in every language.
|
|
283
301
|
- `invalid_type` says what was expected (`z.number()` given a string says
|
|
284
302
|
`This value must be a number.`); a type with no string of its own says
|
|
285
303
|
`This value has the wrong type.`
|
|
@@ -520,6 +538,37 @@ own entry, so the main barrel cannot render it. Use `LocationFormField` from
|
|
|
520
538
|
`Field` is the same renderer without React Hook Form: `<Field config={…} />`,
|
|
521
539
|
where the config carries `value` and `onChange` / `onValueChange` itself.
|
|
522
540
|
|
|
541
|
+
## Focusing the first invalid field
|
|
542
|
+
|
|
543
|
+
React Hook Form moves focus to the first invalid field on a failed
|
|
544
|
+
`handleSubmit` (`shouldFocusError`, on by default), and to the field you name
|
|
545
|
+
in `setError(name, error, { shouldFocus: true })` or `setFocus(name)`. It does
|
|
546
|
+
that by calling `focus()` on the `ref` the field registered, which every
|
|
547
|
+
`*FormField` (and the config-driven `FormField`) hands over: an input takes
|
|
548
|
+
it directly; a field made of several controls (a select, a group of chips, a
|
|
549
|
+
date range, an upload, a dialog editor) hands over a handle that focuses the
|
|
550
|
+
first control a user can reach in it.
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
const form = useForm<Values>({ resolver: zodResolver(schema) });
|
|
554
|
+
|
|
555
|
+
// A failed submit focuses the first invalid field, nothing to wire:
|
|
556
|
+
<form onSubmit={form.handleSubmit(save)}>…</form>
|
|
557
|
+
|
|
558
|
+
// A server error on one field:
|
|
559
|
+
form.setError('email', { type: 'server', message: 'Already used' }, { shouldFocus: true });
|
|
560
|
+
form.setFocus('email'); // or without an error
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
- Only a **mounted** field takes focus: React Hook Form skips one in a closed
|
|
564
|
+
tab or a collapsed section, so open it before calling `setError`.
|
|
565
|
+
- `FilterFormField` is the exception: it holds criteria, not a value that can
|
|
566
|
+
be invalid.
|
|
567
|
+
- A field of your own passes `field.ref` to its control:
|
|
568
|
+
`<input {...field} />` (`{...field}` carries `ref`).
|
|
569
|
+
- If focus does not move, see
|
|
570
|
+
[`setError(…, { shouldFocus: true })` does nothing](../troubleshooting.md#seterror--shouldfocus-true--or-a-failed-submit-does-not-focus-the-field).
|
|
571
|
+
|
|
523
572
|
## Server errors
|
|
524
573
|
|
|
525
574
|
Map an API's field errors onto the form with `setError`, and put anything that
|
package/docs/guides/helpers.md
CHANGED
|
@@ -45,7 +45,7 @@ import { cn, DATE_UTILS, formatNumber, searchParam, splitMatch } from '@nxgt/mat
|
|
|
45
45
|
|
|
46
46
|
export function OrderRow({ order, query }: { order: { ref: string; total: number; createdAt: string; late: boolean }; query: string }) {
|
|
47
47
|
return (
|
|
48
|
-
<tr className={cn('border-b', order.late && 'text-error')}>
|
|
48
|
+
<tr className={cn('border-b', order.late && 'text-error-text')}>
|
|
49
49
|
<td>
|
|
50
50
|
{splitMatch(order.ref, query).map((part, i) =>
|
|
51
51
|
part.match ? <mark key={i}>{part.text}</mark> : <span key={i}>{part.text}</span>,
|
|
@@ -119,7 +119,7 @@ Vue: the Redux helpers are Pinia store factories: `defineLocalStore`
|
|
|
119
119
|
| `useTheme()` | `{ theme, preference, updateTheme }`: light / dark / system, stored in `localStorage`, applied as the `dark` class. See [theming](theming.md#usetheme). |
|
|
120
120
|
| `resolveTheme(stored, prefersDark)`, `applyResolvedTheme(theme)` | The pure theme logic behind `useTheme`, for a boot script. |
|
|
121
121
|
| `useIsClient()` | `false` on the server and first render, `true` after mount: a hydration guard. |
|
|
122
|
-
| `useIsMobile()` | `true` below 768 px. `false` on the server and while hydrating, so SSR HTML is the desktop layout; a phone switches right after. |
|
|
122
|
+
| `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. |
|
|
123
123
|
| `useHeadingSpy(ids, onActive, options?)` | IntersectionObserver over heading ids; reports the topmost visible one. |
|
|
124
124
|
| `useLocalizedDate()` | `{ language, locale, format }`: `DATE_UTILS.format` bound to the `I18nProvider` language (`en` without a provider). |
|
|
125
125
|
| `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. |
|
package/docs/guides/i18n.md
CHANGED
|
@@ -206,10 +206,13 @@ presets and `Min`/`Max` fields, the `variant="4"` spinner's label,
|
|
|
206
206
|
`withCloseButton`. A range `Slider`'s readout inputs read
|
|
207
207
|
`material.slider.minimum` / `maximum` ("Minimum", "Maximum"), and a
|
|
208
208
|
single value's input, when nothing names the slider, `material.slider.value`
|
|
209
|
-
("Value", "Valeur").
|
|
209
|
+
("Value", "Valeur"). `CopyButton` reads `material.copy.copy`, `copied` and `failed` ("Copy", "Copied",
|
|
210
|
+
"Copy failed"). The other icon-only buttons are named from it too: the
|
|
210
211
|
`Sidebar` toggle (`material.sidebar.toggle`), the data table's row expander
|
|
211
212
|
(`material.table.labels.toggle-row`), its loading status, "Loading data…" /
|
|
212
|
-
"Chargement des données…" (`material.table.loading`),
|
|
213
|
+
"Chargement des données…" (`material.table.loading`), its scroll region,
|
|
214
|
+
"Scrollable table" / "Tableau défilant"
|
|
215
|
+
(`material.table.labels.scroll-region`, or the `scrollLabel` prop), a chip's remove button — `Chip`,
|
|
213
216
|
`ComboboxChip` and the `Autocomplete` chips, "Remove {label}"
|
|
214
217
|
(`material.chip.dismiss-label`) — the `LocationPreview` trigger
|
|
215
218
|
(`material.map.preview` / `preview-named`), and the delete button of the
|
package/docs/guides/ssr.md
CHANGED
|
@@ -105,7 +105,7 @@ State the server cannot know must be resolved the same way on both sides:
|
|
|
105
105
|
| Language | no `localStorage`: `defaultLanguage` | Resolve it from the request and pass `initialLanguage` to `I18nProvider`. See [i18n](i18n.md#server-rendering) |
|
|
106
106
|
| Theme | no preference: light | An inline boot script in `<head>`, and `suppressHydrationWarning` on `<html>`. See [theming](theming.md#no-flash-on-load-a-boot-script) |
|
|
107
107
|
| `formatAmount`, `formatDate` | the server's locale and time zone, unless you pass them | Use `useFormatters({ timeZone })`, which follows the `I18nProvider` language, or pass `locale` and `timeZone` (`createFormatters` binds them). A date-only `YYYY-MM-DD` is the same everywhere. See [printing](printing.md#formatamount-and-formatdate) |
|
|
108
|
-
| Screen width, `prefers-color-scheme` (`useIsMobile`, `useTheme`, `AppHeader`, `DataTable`, `
|
|
108
|
+
| Screen width, `prefers-color-scheme` (`useIsMobile`, `useTheme`, `AppHeader`, `DataTable`, `TimeField`, `Stepper`, `PaneLayout`, `ListDetailsLayout`, the code blocks, …) | no screen: a desktop, light | Nothing: a hydrating client renders the same, then switches on a phone or a dark screen |
|
|
109
109
|
| Step form (`createStepFormSlice`) | no `localStorage`: step 0 | Nothing with `useSliceReducer`: a hydrating client renders step 0, then resumes. A slice in a Redux store: see [forms](forms.md#a-multi-step-form) |
|
|
110
110
|
| Zod messages | no `localStorage`: English | Pass the request's language, `zodLocaleError({ language })`, where the server formats messages. See [forms](forms.md#validation-messages) |
|
|
111
111
|
|