@nxgt/material 3.79.1 → 3.80.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 +48 -0
- package/README.md +45 -6
- package/dist/{alert-dialog-653apk3f.js → alert-dialog-DgHtXZuB.js} +1 -1
- package/dist/{async-slice-CXLbQuOc.js → async-slice-YBQsGtQT.js} +1 -1
- package/dist/{button-3op3rtZh.js → button-_vm-xSBU.js} +1 -1
- package/dist/{calculator-KqIMBXSh.js → calculator-CTd_S23A.js} +10 -10
- package/dist/calculator-field.js +6 -6
- package/dist/{calculator-segment-BlJE1LaS.js → calculator-segment-OI1BT3bA.js} +1 -1
- package/dist/calculator.js +1 -1
- package/dist/charts.js +3 -3
- package/dist/{chip-MuKDwtkG.js → chip-DumcVzUO.js} +2 -2
- package/dist/{code-editor-Bp2RrdNx.js → code-editor-CiwgfSOK.js} +4 -4
- package/dist/code-editor.js +4 -4
- package/dist/{code-language-items-B7WR5ua_.js → code-language-items-Dip0xnmc.js} +2 -2
- package/dist/components/components.css +3 -0
- package/dist/components/ui/actions/fab/fab-dock.css +19 -6
- package/dist/components/ui/layout/app-search/app-search.css +62 -0
- package/dist/components/ui/layout/app-shell/activity-backdrop.css +10 -2
- package/dist/components/ui/layout/app-shell/app-header.css +108 -0
- package/dist/components/ui/layout/navigation-toolbar/navigation-toolbar.css +43 -0
- package/dist/{components-CTyNC_zW.js → components-CJFHhkZv.js} +21798 -17784
- package/dist/components.js +37 -37
- package/dist/{conversion-variant-BDFAxqe9.js → conversion-variant-CqrPwPjC.js} +3 -3
- package/dist/{currency-field-DWHB5oms.js → currency-field-CMFvctye.js} +4 -4
- package/dist/{dialog-Bm5LvVlB.js → dialog-B0iU3fmh.js} +3 -3
- package/dist/dnd.js +2 -2
- package/dist/{dropdown-menu-xlZ6NYUA.js → dropdown-menu-CPOgcajv.js} +1 -1
- package/dist/editor.js +7 -7
- package/dist/{empty-state-C0kviQdW.js → empty-state-D3Jggupu.js} +1 -1
- package/dist/{financial-variant-CggTpad2.js → financial-variant-BdyfYhnp.js} +5 -5
- package/dist/{hooks-Dg94jPRq.js → hooks-Z7KKbv4G.js} +2 -2
- package/dist/hooks.js +2 -2
- package/dist/{hover-card-B1g55Hi6.js → hover-card-CFGIX6kg.js} +1 -1
- package/dist/i18n.js +3 -3
- package/dist/{icon-badge-BcW66bRk.js → icon-badge-kxGWUvcO.js} +1 -1
- package/dist/{icon-button-Cogxy1cy.js → icon-button-BamxMwZs.js} +1 -1
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/layout/app-bar/top-app-bar.d.ts +18 -3
- package/dist/lib/components/ui/layout/app-search/app-search-bar.d.ts +11 -0
- package/dist/lib/components/ui/layout/app-search/app-search.types.d.ts +47 -0
- package/dist/lib/components/ui/layout/app-search/index.d.ts +3 -0
- package/dist/lib/components/ui/layout/app-search/partials/on-phone.d.ts +2 -0
- package/dist/lib/components/ui/layout/app-search/partials/phone-search.d.ts +3 -0
- package/dist/lib/components/ui/layout/app-search/partials/pill-search.d.ts +3 -0
- package/dist/lib/components/ui/layout/app-search/pill-form.d.ts +18 -0
- package/dist/lib/components/ui/layout/app-search/search-panel.d.ts +24 -0
- package/dist/lib/components/ui/layout/app-search/search-results.d.ts +14 -0
- package/dist/lib/components/ui/layout/app-search/search-shortcut.d.ts +15 -0
- package/dist/lib/components/ui/layout/app-search/search-view.d.ts +22 -0
- package/dist/lib/components/ui/layout/app-search/use-pill-panel.d.ts +22 -0
- package/dist/lib/components/ui/layout/app-search/use-search-text.d.ts +8 -0
- package/dist/lib/components/ui/layout/app-shell/activity-app-bar.d.ts +35 -0
- package/dist/lib/components/ui/layout/app-shell/activity-chrome.d.ts +26 -3
- package/dist/lib/components/ui/layout/app-shell/activity-column-props.d.ts +22 -0
- package/dist/lib/components/ui/layout/app-shell/activity-column.d.ts +10 -2
- package/dist/lib/components/ui/layout/app-shell/activity-layout-navigation.types.d.ts +94 -0
- package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +8 -1
- package/dist/lib/components/ui/layout/app-shell/activity-layout.types.d.ts +2 -1
- package/dist/lib/components/ui/layout/app-shell/activity-rails.d.ts +16 -0
- package/dist/lib/components/ui/layout/app-shell/activity-sidebar-lane.d.ts +22 -0
- package/dist/lib/components/ui/layout/app-shell/activity-top-nav.d.ts +11 -0
- package/dist/lib/components/ui/layout/app-shell/activity-two-level-lane.d.ts +14 -0
- package/dist/lib/components/ui/layout/app-shell/app-back.d.ts +22 -0
- package/dist/lib/components/ui/layout/app-shell/app-header-row.d.ts +3 -0
- package/dist/lib/components/ui/layout/app-shell/app-header.d.ts +33 -1
- package/dist/lib/components/ui/layout/app-shell/app-shell.utils.d.ts +8 -0
- package/dist/lib/components/ui/layout/app-shell/app-sidebar-entries.d.ts +13 -0
- package/dist/lib/components/ui/layout/app-shell/app-sidebar-label-item.d.ts +3 -1
- package/dist/lib/components/ui/layout/app-shell/bottom-chrome.d.ts +13 -0
- package/dist/lib/components/ui/layout/app-shell/header-title.d.ts +18 -2
- package/dist/lib/components/ui/layout/app-shell/header.utils.d.ts +37 -0
- package/dist/lib/components/ui/layout/app-shell/index.d.ts +3 -0
- package/dist/lib/components/ui/layout/app-shell/navigation-parts.d.ts +38 -0
- package/dist/lib/components/ui/layout/app-shell/navigation.utils.d.ts +36 -0
- package/dist/lib/components/ui/layout/app-shell/page-title-scope.d.ts +14 -0
- package/dist/lib/components/ui/layout/app-shell/page-title.d.ts +12 -1
- package/dist/lib/components/ui/layout/app-shell/shell-scroll.d.ts +32 -0
- package/dist/lib/components/ui/layout/app-shell/sticky-offset.d.ts +8 -0
- package/dist/lib/components/ui/layout/app-shell/types.d.ts +14 -0
- package/dist/lib/components/ui/layout/app-shell/use-focus-while-hidden.d.ts +9 -0
- package/dist/lib/components/ui/layout/app-shell/use-header-height.d.ts +12 -0
- package/dist/lib/components/ui/layout/app-shell/use-header-scroll.d.ts +19 -0
- package/dist/lib/components/ui/layout/app-shell/use-shell-header.d.ts +20 -0
- package/dist/lib/components/ui/layout/auth-card/auth-card-split.d.ts +14 -0
- package/dist/lib/components/ui/layout/auth-card/auth-card.d.ts +13 -1
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid-context.d.ts +21 -0
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.d.ts +9 -0
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.state.d.ts +30 -0
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.tiles.d.ts +8 -0
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.types.d.ts +50 -0
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.utils.d.ts +70 -0
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-tile.d.ts +7 -0
- package/dist/lib/components/ui/layout/dashboard-grid/index.d.ts +4 -0
- package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-announcer.d.ts +6 -0
- package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-edit-bar.d.ts +11 -0
- package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-move-buttons.d.ts +9 -0
- package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-toolbar.d.ts +7 -0
- package/dist/lib/components/ui/layout/dashboard-grid/use-dashboard-grid.d.ts +21 -0
- package/dist/lib/components/ui/layout/dashboard-grid/use-dashboard-moves.d.ts +22 -0
- package/dist/lib/components/ui/layout/doc/doc-context.d.ts +2 -0
- package/dist/lib/components/ui/layout/doc/doc.d.ts +5 -0
- package/dist/lib/components/ui/layout/doc/index.d.ts +1 -1
- package/dist/lib/components/ui/layout/doc/partials/doc-embedded-bar.d.ts +9 -0
- package/dist/lib/components/ui/layout/doc/partials/doc-embedded.d.ts +8 -0
- package/dist/lib/components/ui/layout/doc/use-doc.d.ts +9 -1
- package/dist/lib/components/ui/layout/editor-layout/editor-layout.d.ts +14 -0
- package/dist/lib/components/ui/layout/editor-layout/editor-layout.types.d.ts +31 -0
- package/dist/lib/components/ui/layout/editor-layout/index.d.ts +4 -0
- package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-body.d.ts +11 -0
- package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-canvas.d.ts +8 -0
- package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-context.d.ts +12 -0
- package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-start.d.ts +8 -0
- package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-triggers.d.ts +10 -0
- package/dist/lib/components/ui/layout/editor-layout/partials/use-editor-layout-names.d.ts +8 -0
- package/dist/lib/components/ui/layout/editor-layout/partials/use-editor-layout-state.d.ts +21 -0
- package/dist/lib/components/ui/layout/index.d.ts +9 -0
- package/dist/lib/components/ui/layout/landing/index.d.ts +6 -0
- package/dist/lib/components/ui/layout/landing/landing-layout.d.ts +15 -0
- package/dist/lib/components/ui/layout/landing/landing-layout.types.d.ts +27 -0
- package/dist/lib/components/ui/layout/landing/landing-section.d.ts +12 -0
- package/dist/lib/components/ui/layout/landing/landing-section.types.d.ts +32 -0
- package/dist/lib/components/ui/layout/landing/landing.classes.d.ts +20 -0
- package/dist/lib/components/ui/layout/landing/partials/landing-header.d.ts +11 -0
- package/dist/lib/components/ui/layout/landing/partials/landing-section-intro.d.ts +13 -0
- package/dist/lib/components/ui/layout/landing/partials/site-footer-group.d.ts +8 -0
- package/dist/lib/components/ui/layout/landing/site-footer.d.ts +13 -0
- package/dist/lib/components/ui/layout/landing/site-footer.types.d.ts +29 -0
- package/dist/lib/components/ui/layout/list-details-layout/internal.d.ts +2 -0
- package/dist/lib/components/ui/layout/list-details-layout/list-details-layout.d.ts +9 -17
- package/dist/lib/components/ui/layout/list-details-layout/partials/history-mark.d.ts +2 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-context.d.ts +17 -1
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-history.d.ts +8 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-inspector.d.ts +21 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-layout.types.d.ts +47 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-resizer.d.ts +9 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-root.d.ts +3 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-trigger.d.ts +13 -4
- package/dist/lib/components/ui/layout/list-details-layout/partials/use-inspector-focus.d.ts +8 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-details-param.d.ts +26 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-details-state.d.ts +15 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-width.d.ts +16 -0
- package/dist/lib/components/ui/layout/list-details-layout/partials/use-pane-focus.d.ts +6 -4
- package/dist/lib/components/ui/layout/navigation-bar/index.d.ts +3 -0
- package/dist/lib/components/ui/layout/navigation-bar/internal.d.ts +4 -0
- package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.classes.d.ts +14 -0
- package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.d.ts +35 -0
- package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.utils.d.ts +19 -0
- package/dist/lib/components/ui/layout/navigation-bar/partials/bar-badge.d.ts +5 -0
- package/dist/lib/components/ui/layout/navigation-bar/partials/bar-entry.d.ts +12 -0
- package/dist/lib/components/ui/layout/navigation-bar/partials/navigation-bar-item.d.ts +22 -0
- package/dist/lib/components/ui/layout/navigation-bar/partials/navigation-bar-more.d.ts +24 -0
- package/dist/lib/components/ui/layout/navigation-bar/use-nav-bar-inset.d.ts +12 -0
- package/dist/lib/components/ui/layout/navigation-drawer/index.d.ts +5 -0
- package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.classes.d.ts +9 -0
- package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.d.ts +45 -0
- package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.utils.d.ts +14 -0
- package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-body.d.ts +3 -0
- package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-item.d.ts +20 -0
- package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-modal.d.ts +17 -0
- package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-section.d.ts +15 -0
- package/dist/lib/components/ui/layout/navigation-rail/index.d.ts +3 -0
- package/dist/lib/components/ui/layout/navigation-rail/navigation-rail-panel.d.ts +26 -0
- package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.classes.d.ts +23 -0
- package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.d.ts +42 -1
- package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.utils.d.ts +4 -0
- package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-body.d.ts +3 -0
- package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-item.d.ts +20 -0
- package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-panel-item.d.ts +7 -0
- package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-toggle.d.ts +7 -0
- package/dist/lib/components/ui/layout/navigation-rail/use-rail-expanded.d.ts +13 -0
- package/dist/lib/components/ui/layout/navigation-toolbar/index.d.ts +2 -0
- package/dist/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.classes.d.ts +20 -0
- package/dist/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.d.ts +51 -0
- package/dist/lib/components/ui/layout/navigation-toolbar/partials/navigation-toolbar-item.d.ts +25 -0
- package/dist/lib/components/ui/layout/navigation-toolbar/partials/navigation-toolbar-more.d.ts +3 -0
- package/dist/lib/components/ui/layout/pane-layout/internal.d.ts +3 -0
- package/dist/lib/components/ui/layout/pane-layout/pane-layout.d.ts +16 -2
- package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-classes.d.ts +7 -0
- package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-context.d.ts +13 -0
- package/dist/lib/components/ui/layout/pane-layout/partials/use-pane-layout-state.d.ts +16 -0
- package/dist/lib/components/ui/layout/pane-layout/partials/use-pane-narrow.d.ts +6 -0
- package/dist/lib/components/ui/layout/responsive-grid/index.d.ts +4 -1
- package/dist/lib/components/ui/layout/responsive-grid/responsive-grid-item.d.ts +19 -0
- package/dist/lib/components/ui/layout/responsive-grid/responsive-grid-spans.d.ts +19 -0
- package/dist/lib/components/ui/layout/responsive-grid/responsive-grid.d.ts +24 -2
- package/dist/lib/components/ui/layout/responsive-grid/responsive-grid.utils.d.ts +12 -0
- package/dist/lib/components/ui/layout/settings-layout/index.d.ts +2 -0
- package/dist/lib/components/ui/layout/settings-layout/partials/settings-link.d.ts +7 -0
- package/dist/lib/components/ui/layout/settings-layout/partials/settings-list.d.ts +11 -0
- package/dist/lib/components/ui/layout/settings-layout/partials/settings-page.d.ts +5 -0
- package/dist/lib/components/ui/layout/settings-layout/settings-layout.d.ts +10 -0
- package/dist/lib/components/ui/layout/settings-layout/settings-layout.types.d.ts +33 -0
- package/dist/lib/components/ui/layout/settings-layout/settings-layout.utils.d.ts +13 -0
- package/dist/lib/components/ui/layout/thread-layout/thread-layout-classes.d.ts +17 -0
- package/dist/lib/components/ui/layout/thread-layout/thread-layout-parts.d.ts +14 -0
- package/dist/lib/components/ui/layout/thread-layout/thread-layout-stepper.d.ts +11 -0
- package/dist/lib/components/ui/layout/thread-layout/thread-layout.d.ts +30 -3
- package/dist/lib/components/ui/layout/top-nav/index.d.ts +5 -0
- package/dist/lib/components/ui/layout/top-nav/partials/top-nav-item.d.ts +23 -0
- package/dist/lib/components/ui/layout/top-nav/partials/top-nav-menu.d.ts +22 -0
- package/dist/lib/components/ui/layout/top-nav/top-nav.classes.d.ts +10 -0
- package/dist/lib/components/ui/layout/top-nav/top-nav.d.ts +34 -0
- package/dist/lib/components/ui/layout/top-nav/top-nav.utils.d.ts +10 -0
- package/dist/lib/components/ui/widgets/kanban/index.d.ts +1 -1
- package/dist/lib/components/ui/widgets/kanban/kanban-context.d.ts +5 -2
- package/dist/lib/components/ui/widgets/kanban/kanban.d.ts +11 -2
- package/dist/lib/components/ui/widgets/kanban/kanban.slice.d.ts +32 -0
- package/dist/lib/components/ui/widgets/kanban/kanban.utils.d.ts +9 -0
- package/dist/lib/components/ui/widgets/kanban/partials/kanban-card-preview.d.ts +3 -1
- package/dist/lib/components/ui/widgets/kanban/partials/kanban-column.d.ts +6 -0
- package/dist/lib/components/ui/widgets/kanban/partials/kanban-move-to.d.ts +13 -0
- package/dist/lib/components/ui/widgets/kanban/partials/kanban-phone-tabs.d.ts +10 -0
- package/dist/lib/components/ui/widgets/kanban/types.d.ts +7 -0
- package/dist/lib/components/ui/widgets/kanban/use-kanban-board-keys.d.ts +7 -0
- package/dist/lib/components/ui/widgets/kanban/use-kanban-item-actions.d.ts +5 -0
- package/dist/lib/components/ui/widgets/kanban/use-kanban-phone-column.d.ts +18 -0
- package/dist/lib/components/ui/widgets/kanban/use-kanban.d.ts +6 -1
- package/dist/lib/i18n/resources/en/en.d.ts +50 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +52 -2
- package/dist/lib/i18n/resources/fr/fr.d.ts +50 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +52 -2
- package/dist/lib/i18n/resources/resources.d.ts +100 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/main.js +52 -52
- package/dist/map.js +7 -7
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/{percent-field--WIXV5dX.js → percent-field-C2LNr8NA.js} +1 -1
- package/dist/{picker-surface-CV09H7Nb.js → picker-surface-BUCFMoAZ.js} +4 -4
- package/dist/{popover-Cd7DkND4.js → popover-DFRwQF12.js} +1 -1
- package/dist/{pos-types-DKCkOTYy.js → pos-types-CXnEdjdy.js} +1 -1
- package/dist/{pos-variant-GrUfbTTP.js → pos-variant-DMwn_7is.js} +4 -4
- package/dist/print.js +1 -1
- package/dist/{programmer-variant-eKJNKENL.js → programmer-variant-Bo4eiDFi.js} +2 -2
- package/dist/{public-y6g4ddxd.js → public-CyQPkkSR.js} +9 -9
- package/dist/{rich-code-block-CRQ0iyLb.js → rich-code-block-iK7UcZdc.js} +5 -5
- package/dist/{scientific-variant-2y3UxAAU.js → scientific-variant-B9ScTgfx.js} +2 -2
- package/dist/{sortable-provider-DdiI3Fwk.js → sortable-provider-BZwVjSVU.js} +3 -3
- package/dist/{spinner-C1QIewWv.js → spinner-RITOxvpH.js} +2 -2
- package/dist/{tabs-KGKvve3y.js → tabs-DSo4eCUn.js} +1 -1
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle-C_VeiiCm.js → theme-toggle-3gbzQX0x.js} +1 -1
- package/dist/{toolbar-items-D310xDuM.js → toolbar-items-Bt5RK-Nx.js} +4 -4
- package/dist/{translate-material-BypJJ1l5.js → translate-material-CZTGqgen.js} +1 -1
- package/dist/{translator-DJkf4J7t.js → translator-DTlOlIoq.js} +108 -4
- package/dist/{use-chat-composer-BFhRMHcX.js → use-chat-composer-DBY01IpX.js} +16 -16
- package/dist/{use-heading-spy-DJR8YYm6.js → use-heading-spy-Dt48bBts.js} +4 -4
- package/dist/{use-material-translation-CD6QoVal.js → use-material-translation-BJucC_xe.js} +1 -1
- package/dist/{use-return-focus-Cevw_6yq.js → use-return-focus-BS1JLcnq.js} +1 -1
- package/docs/README.md +1 -0
- package/docs/components/data-display.md +4 -0
- package/docs/components/navigation-and-layout.md +944 -27
- package/docs/components/rich-widgets.md +26 -0
- package/docs/guides/app-shell.md +641 -2
- package/docs/guides/i18n.md +13 -1
- package/docs/guides/layouts.md +215 -0
- package/docs/guides/ssr.md +1 -1
- package/docs/previews/navigation-layout-appshell-page-ground.webp +0 -0
- package/docs/previews/navigation-layout-appshell.webp +0 -0
- package/docs/previews/navigation-layout-authcard-split.webp +0 -0
- package/docs/previews/navigation-layout-authcard.webp +0 -0
- package/docs/previews/navigation-layout-dashboardgrid.webp +0 -0
- package/docs/previews/navigation-layout-editorlayout.webp +0 -0
- package/docs/previews/navigation-layout-landinglayout.webp +0 -0
- package/docs/previews/navigation-layout-panelayout.webp +0 -0
- package/docs/previews/navigation-layout-settingslayout.webp +0 -0
- package/docs/previews/navigation-layout-threadlayout-pages.webp +0 -0
- package/docs/previews/navigation-layout-threadlayout.webp +0 -0
- package/docs/roadmap.md +10 -2
- package/lib/components/components.css +3 -0
- package/lib/components/ui/actions/fab/fab-dock.css +19 -6
- package/lib/components/ui/layout/app-search/app-search.css +62 -0
- package/lib/components/ui/layout/app-shell/activity-backdrop.css +10 -2
- package/lib/components/ui/layout/app-shell/app-header.css +108 -0
- package/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.css +43 -0
- package/package.json +1 -1
|
@@ -7,15 +7,20 @@ documentation-site shell. Everything is imported from
|
|
|
7
7
|
|
|
8
8
|
| | Component | What it does |
|
|
9
9
|
| --- | --- | --- |
|
|
10
|
-
| <img src="../previews/navigation-layout-appshell.webp" width="96" alt=""> | [ActivityLayout](#activitylayout) | App shell: sidebar, header, breadcrumbs, titled page; a route's `handle.title` is the page's `h1` in the app bar (it labels `main`; `usePageTitleFocus()` focuses it after a navigation), `handle.titleAfter` / `titleAfter` a node right after it, outside the `h1` (a record's `Switcher`, or `<Switcher size="compact">` for an icon-only one), `headerContext` a small tenant line after it from md up, `breadcrumbRoot` a text root crumb or (`icon: true`) the home icon with its own target and name, `breadcrumb={false}` no trail, `context` the tenant line atop the phone's menu sheet; `menuOpen` / `onMenuOpenChange` control the phone's menu sheet, which closes itself on a followed link or any location change |
|
|
10
|
+
| <img src="../previews/navigation-layout-appshell.webp" width="96" alt=""> | [ActivityLayout](#activitylayout) | App shell: sidebar, header, breadcrumbs, titled page; `header` sets the app bar (small, center, medium or large collapsing on scroll, hide on scroll, a route's `handle.header`, tabs under it, a route's `handle.back` arrow); a route's `handle.title` is the page's `h1` in the app bar (it labels `main`; `usePageTitleFocus()` focuses it after a navigation), `handle.titleAfter` / `titleAfter` a node right after it, outside the `h1` (a record's `Switcher`, or `<Switcher size="compact">` for an icon-only one), `headerContext` a small tenant line after it from md up, `breadcrumbRoot` a text root crumb or (`icon: true`) the home icon with its own target and name, `breadcrumb={false}` no trail, `context` the tenant line atop the phone's menu sheet; `menuOpen` / `onMenuOpenChange` control the phone's menu sheet, which closes itself on a followed link or any location change |
|
|
11
11
|
| <img src="../previews/navigation-layout-accountmenu.webp" width="96" alt=""> | [AccountMenu](#accountmenu) | The app bar's avatar menu: the account, the app's links, Sign out |
|
|
12
12
|
| <img src="../previews/navigation-layout-authcard.webp" width="96" alt=""> | [AuthCard](#authcard) | Signed-out page frame: one `main`, a centred card, its title the `h1` |
|
|
13
13
|
| <img src="../previews/navigation-layout-sidebar.webp" width="96" alt=""> | [Sidebar](#sidebar) | Collapsible navigation sidebar |
|
|
14
|
-
| <img src="../previews/navigation-layout-topappbar.webp" width="96" alt=""> | [TopAppBar, BottomAppBar](#topappbar) | Top and bottom bars |
|
|
14
|
+
| <img src="../previews/navigation-layout-topappbar.webp" width="96" alt=""> | [TopAppBar, BottomAppBar](#topappbar) | Top (small, center, medium, large) and bottom bars |
|
|
15
|
+
| | [AppSearchBar, SearchView](#appsearchbar) | The app bar's search: a pill with a docked results panel from 768px, a full-screen search view on a phone |
|
|
15
16
|
| <img src="../previews/navigation-layout-stickyactionbar.webp" width="96" alt=""> | [StickyActionBar](#stickyactionbar) | A page's main decision, stuck to the bottom on a phone, inline from `md` |
|
|
16
17
|
| <img src="../previews/navigation-layout-breadcrumb.webp" width="96" alt=""> | [Breadcrumb](#breadcrumb) | Breadcrumb primitives |
|
|
17
18
|
| <img src="../previews/navigation-layout-listdetailslayout.webp" width="96" alt=""> | [ListDetailsLayout, PaneLayout, ThreadLayout](#listdetailslayout) | Page layouts that adapt to phones |
|
|
18
|
-
| <img src="../previews/navigation-layout-
|
|
19
|
+
| <img src="../previews/navigation-layout-settingslayout.webp" width="96" alt=""> | [SettingsLayout](#settingslayout) | Grouped settings sections beside the selected section's page; list then page on a phone, the section kept in the URL |
|
|
20
|
+
| <img src="../previews/navigation-layout-landinglayout.webp" width="96" alt=""> | [LandingLayout, LandingSection, SiteFooter](#landinglayout) | The frame of a public landing page outside the app shell: a top bar on `TopNav` (a drawer on a phone), a hero and bands on the theme's surfaces, a footer of link columns |
|
|
21
|
+
| <img src="../previews/navigation-layout-responsivegrid.webp" width="96" alt=""> | [ResponsiveGrid](#responsivegrid) | Container-query grid, 1 to 5 columns, or `auto-fill` columns by a minimum tile width (`minItem`); `ResponsiveGrid.Item` spans, `dense` packing, `flow="masonry"` |
|
|
22
|
+
| <img src="../previews/navigation-layout-dashboardgrid.webp" width="96" alt=""> | [DashboardGrid, DashboardTile](#dashboardgrid) | Tiles with spans that the user rearranges in an edit mode, by drag or keyboard; the order is the app's |
|
|
23
|
+
| <img src="../previews/navigation-layout-editorlayout.webp" width="96" alt=""> | [EditorLayout](#editorlayout) | The editor page: toolbar, canvas that scrolls alone, properties at the end (a bottom sheet on a phone), optional outline at the start |
|
|
19
24
|
| <img src="../previews/navigation-layout-sectioncolumns.webp" width="96" alt=""> | [SectionColumns](#sectioncolumns) | Short page sections side by side from a viewport breakpoint, stacked below it |
|
|
20
25
|
| <img src="../previews/navigation-layout-recordpage.webp" width="96" alt=""> | [RecordPage, RecordDetails](#recordpage) | The page of one record: header card, content, facts as a side panel from `xl` |
|
|
21
26
|
| <img src="../previews/navigation-layout-resizable.webp" width="96" alt=""> | [ResizableGroup](#resizablegroup) | Resizable panels |
|
|
@@ -26,9 +31,14 @@ documentation-site shell. Everything is imported from
|
|
|
26
31
|
| <img src="../previews/navigation-layout-stepper.webp" width="96" alt=""> | [Stepper](#stepper) | Clickable steps with progress: shaped indicators, optional steps, a phone header |
|
|
27
32
|
| <img src="../previews/navigation-layout-steps.webp" width="96" alt=""> | [Steps](#steps) | Numbered editorial steps |
|
|
28
33
|
| <img src="../previews/navigation-layout-hero.webp" width="96" alt=""> | [Hero](#hero) | Landing band, or a compact greeting band, with an optional `Background` |
|
|
29
|
-
| <img src="../previews/navigation-layout-doc.webp" width="96" alt=""> | [Doc](#doc) | Documentation site shell |
|
|
34
|
+
| <img src="../previews/navigation-layout-doc.webp" width="96" alt=""> | [Doc](#doc) | Documentation site shell, standalone or inside the app shell |
|
|
30
35
|
| <img src="../previews/navigation-layout-background.webp" width="96" alt=""> | [Background](#background) | Gradient backdrop with colour blobs; the `/backgrounds` one takes its unset props from the nearest theme |
|
|
31
|
-
| | [
|
|
36
|
+
| | [NavigationBar](#navigationbar) | Bottom bar of 3 to 5 destinations with badges and a More sheet |
|
|
37
|
+
| | [TopNav](#topnav) | Top bar of destinations with dropdowns |
|
|
38
|
+
| | [NavigationDrawer](#navigationdrawer) | Drawer in sections, standard or modal |
|
|
39
|
+
| | [NavigationRail](#navigationrail) | Rail with a FAB slot, badges and an expanded state |
|
|
40
|
+
| | [NavigationRailPanel](#navigationrailpanel) | Second-level panel beside the rail, listing the current entry's children |
|
|
41
|
+
| | [NavigationToolbar](#navigationtoolbar) | Floating pill of navigation links at the bottom centre, optionally sliding away on scroll |
|
|
32
42
|
|
|
33
43
|
## ActivityLayout
|
|
34
44
|
|
|
@@ -59,7 +69,11 @@ const items: MenuItem[] = [
|
|
|
59
69
|
|
|
60
70
|
Parts: `ActivityLayout` (the chrome), `ActivityContent` (one screen),
|
|
61
71
|
`AppSidebar`, `AppHeader` and `AppBreadcrumb`, which the layout composes and
|
|
62
|
-
which work alone. `
|
|
72
|
+
which work alone. `AppHeader` also takes `tabs` (`ReactNode`, default none: a
|
|
73
|
+
`TabsNav` as a row at the foot of the bar, pinned when the bar hides) and `back`
|
|
74
|
+
(`ShellBack`, default none: the back arrow, named "Back" or "Back to <label>"
|
|
75
|
+
and drawn before the menu button); a complete example with a `MemoryRouter` is in
|
|
76
|
+
the [app shell guide](../guides/app-shell.md#using-the-parts). `usePageTitleFocus()` returns a function that focuses the app bar's page title (the content region without one) after a navigation; see the guide.
|
|
63
77
|
|
|
64
78
|
Under `viewport-fit=cover` the frame, the sticky rail and header, and the
|
|
65
79
|
phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
|
|
@@ -67,7 +81,7 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
|
|
|
67
81
|
|
|
68
82
|
| Prop | Type | Default | Effect |
|
|
69
83
|
| --- | --- | --- | --- |
|
|
70
|
-
| `items` | `MenuItem[]` | — | `label` (already translated), `url`, `icon`, `footer`, `display` (`'icon'`: a round icon-only button, the label as tooltip and name; see the [app shell guide](../guides/app-shell.md)), `badge` (`true` a dot, a number a count up to "99+"), `badgeLabel` (see [Badges](../guides/app-shell.md#badges)) |
|
|
84
|
+
| `items` | `MenuItem[]` | — | `label` (already translated), `url`, `icon`, `footer`, `display` (`'icon'`: a round icon-only button, the label as tooltip and name; see the [app shell guide](../guides/app-shell.md)), `badge` (`true` a dot, a number a count up to "99+"), `badgeLabel` (see [Badges](../guides/app-shell.md#badges)), and `children` (read by `navigation="top"`, which opens them in a dropdown under a parent that is then only a trigger; by `navigation="two-level"`, which lists them in the panel beside the rail, one level deep; by the phone's menu sheet and the expanded sidebar, which nest them under the parent's own link), `group`, `bar`, `priority` for the navigation presets that read them (the drawer reads `group`, the bar `bar` and `priority`; the floating toolbar, not drawn yet, is the only preset still to read them) |
|
|
71
85
|
| `linkComponent` | `AppShellLinkComponent` | React Router `Link` | The link each menu entry renders, given `href`; see the [app shell guide](../guides/app-shell.md). Defaults to the [`LinkComponentProvider`](./primitives.md#linkcomponentprovider)'s link when one is set. |
|
|
72
86
|
| `logo`, `name` | `AppLogo` (`ReactNode \| string \| AppLogoImage`, `AppLogoImage` = `{ src, alt? }`), `string` | — | the app's logo alone in the collapsed rail, named by `name` (also its tooltip); logo and name side by side in the phone's menu sheet |
|
|
73
87
|
| `context` | `ReactNode` | — | a line at the top of the phone's menu sheet, above the brand: the current tenant's name; `name` stays the app's brand; `AppHeader` and `AppSidebar` take it too (see the [app shell guide](../guides/app-shell.md#the-brand-logo-and-name)) |
|
|
@@ -90,6 +104,10 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
|
|
|
90
104
|
| `notice` | `ReactNode` | — | `ActivityContent`: a notice above the column and the side panel, at their width, taking its own height; first on a phone |
|
|
91
105
|
| `aside`, `asideLabel` / `asideLabelledBy` | `ReactNode`, `string` | — | `ActivityContent`: a side panel, a named `complementary` landmark, beside the content from 1280px (CSS only), stacked below; sticky under the app bar |
|
|
92
106
|
| `asideSide` / `asideWidth` / `asideFrom` / `asideSticky` / `asideStack` | `'start' \| 'end'` / `'sm' \| 'md' \| 'lg' \| string` / `'lg' \| 'xl' \| '2xl'` / `boolean` / `'before' \| 'after'` | `'end'` / `'md'` / `'xl'` / `true` / `'after'` | `ActivityContent`: the panel's side, width (16, 20, 24rem), breakpoint, stickiness and stacking order (see the [app shell guide](../guides/app-shell.md#a-side-panel-and-a-readable-width)) |
|
|
107
|
+
| `navigation` | `ShellNavigation \| { phone?, tablet?, desktop?, hideOnScroll?, persistKey? }` | `{ phone: 'modal-drawer', tablet: 'rail', desktop: 'rail' }` | `ActivityLayout`: the navigation per window size (phone below 768px, tablet to 1279px, desktop from 1280px). Every `ShellNavigation` name is drawn: `'modal-drawer'`, `'rail'`, `'rail-expanded'`, `'drawer'`, `'sidebar'`, `'top'`, `'two-level'`, `'floating'`, `'bar'` and `'none'`. `hideOnScroll` (a field of this object, `false` by default) slides the `'floating'` toolbar away on scroll; no other preset reads it. `persistKey` (a field of this object) remembers the `'rail-expanded'` rail's, the `'sidebar'`'s and the `'two-level'` panel's state. A name alone (`navigation="sidebar"`, `"drawer"`, `"top"`, `"two-level"`) leaves a phone its default navigation; `{ phone: 'sidebar' }` draws it there. A phone never draws `'rail'` or `'rail-expanded'`: it keeps its menu sheet. `navigation="bar"` alone is phone-only (hidden from 768px, where the default rail shows; `{ tablet: 'bar' }` keeps it there). `'floating'` is drawn at every size, the phone included. See [`navigation` and `header`](../guides/app-shell.md#navigation-and-header) |
|
|
108
|
+
| `navigationFab` | `ReactNode \| ({ expanded }) => ReactNode` | — | `ActivityLayout`: the screen's primary action, at the top of the `'rail-expanded'` rail (a `Fab`; the function form receives the rail's state). The other presets do not draw it |
|
|
109
|
+
| `sidebarExpanded` / `defaultSidebarExpanded` / `onSidebarExpandedChange` | `boolean` / `boolean` / `(expanded: boolean) => void` | — / `false` / — | `ActivityLayout`: the `'sidebar'`'s expanded state (labels beside the icons; it starts collapsed), controlled or not; with `navigation.persistKey` the last choice is kept and read after hydration |
|
|
110
|
+
| `header` | `'small' \| 'center' \| 'medium' \| 'large' \| 'none' \| { variant, collapse?, hideOnScroll?, tabs?, search? }` | `'small'` | `ActivityLayout`: the app bar's size. The five variants and `hideOnScroll` are drawn; `search` draws an `AppSearchBar` (see [AppSearchBar](#appsearchbar)); `tabs` (a `TabsNav`) draws a row stuck under the bar, which stays pinned when the bar hides (see [Tabs under the app bar](../guides/app-shell.md#tabs-under-the-app-bar)); a route's `handle.back` (`ShellBack`: `{ to, label? }` or `true`) draws a back arrow before the title, the phone's menu button following it while the phone has the menu sheet (see [The back arrow](../guides/app-shell.md#the-back-arrow)). A route's `handle.header` overrides it; a `'medium'` or `'large'` header needs a `handle.title`, else it stays small; `'none'` also drops the phone's menu button, so pair it with `navigation="bar"`. See [The app bar variants](../guides/app-shell.md#the-app-bar-variants), [Scroll direction](../guides/app-shell.md#scroll-direction) and [Room at the bottom](../guides/app-shell.md#room-at-the-bottom) |
|
|
93
111
|
| `align` | `'start' \| 'center'` | `'start'` | `ActivityLayout`, `AppSidebar`: the collapsed rail's entries sit right under the brand, or `'center'` for centred in the height below it |
|
|
94
112
|
| `backdrop` | `ReactNode \| false` | — | `ActivityLayout`: an exception to the theme's background (drawn by default; Lavande's is the built-in gradient and blobs); `false` draws a plain page. A node is a decorative background behind the frame: a `Background` preset from `@nxgt/material/backgrounds`. Rendered in an `inert`, `aria-hidden` layer as wide as the content column and one viewport high, fixed in the viewport while the content scrolls; the chrome (app bar, breadcrumb, tabs strip) is lightly translucent and frosted over it (80%, 87% for the sidebar, breadcrumb and tabs strip, 92% and no blur under `prefers-reduced-transparency` or without `backdrop-filter`), and the lists, tables and cards stay opaque; `--nxgt-backdrop-chrome` sets the chrome's fill and `--nxgt-backdrop-surface` the lists'. See [Use a background preset behind the shell](../guides/app-shell.md#use-a-background-preset-behind-the-shell) |
|
|
95
113
|
| `routeTransition` | `boolean \| Omit<ViewTransitionProps, 'children' \| 'ref'>` | `true` | `ActivityLayout`: React's `<ViewTransition>` around the content region, so a navigation cross-fades it (by default only when the pathname changes, not on a search change, a fetcher or a revalidation). `false` renders none (your app has its own boundary); an object passes `default` / `update` classes, `name`, callbacks through. `ROUTE_TRANSITION_CLASS` (`'nxgt-route'`) is exported for nested boundaries. See [Route transitions](../guides/app-shell.md#route-transitions) |
|
|
@@ -212,6 +230,8 @@ import { AuthCard, Button, EmailField, LanguageSwitcher, PasswordField } from '@
|
|
|
212
230
|
| `banner` | `ReactNode` | — | above that row (an update notice) |
|
|
213
231
|
| `footer` | `ReactNode` | — | under the card |
|
|
214
232
|
| `backdrop` | `ReactNode \| false` | the theme's background | what is drawn behind the page: a `Background` preset or any node; `false` for none. Without a theme provider nothing is drawn |
|
|
233
|
+
| `layout` | `'card' \| 'split'` | `'card'` | `'split'`: two columns from 768px, the `visual` side and the form side; a phone shows the form alone under the brand row |
|
|
234
|
+
| `visual` | `ReactNode` | — | with `layout="split"`: the side beside the form (a `Background` preset with `className="absolute inset-0"`, a picture, a tagline); hidden below 768px |
|
|
215
235
|
| `keepClear` | `boolean \| 'gaps'` | `true` | glyphs (shapes or icons) of the backdrop that would meet the brand row, the card or the page's surfaces, text or controls are dropped (never moved); `false` lets them run under the card; `'gaps'` also keeps a glyph that fits in a gap of 24px or more between two surfaces |
|
|
216
236
|
|
|
217
237
|
```tsx
|
|
@@ -234,6 +254,28 @@ fade, a glyph crossing the layer's edge too; none is cut or moved. Where `Resize
|
|
|
234
254
|
is drawn as it is. The server renders the layer and no measurement, so there
|
|
235
255
|
is no hydration mismatch. Under `prefers-reduced-motion` the fade is off.
|
|
236
256
|
|
|
257
|
+
**Split page.** `layout="split"` (unset is the centred card) makes the `main`
|
|
258
|
+
a two-column grid from 768px by CSS classes only, so the server and the
|
|
259
|
+
client render the same markup. The `visual` side (`data-slot="auth-card-visual"`)
|
|
260
|
+
takes the first column, is hidden below 768px and is `bg-muted` with
|
|
261
|
+
`text-foreground`; put a `Background` preset or a picture in it, and mark
|
|
262
|
+
a purely decorative node `aria-hidden`. The form side
|
|
263
|
+
(`data-slot="auth-card-form-side"`) is opaque (`bg-background`) and carries
|
|
264
|
+
the safe-area padding, so text and controls never sit on a backdrop, in light
|
|
265
|
+
or in dark. The brand row, the `h1`, the form and the footer are all in the
|
|
266
|
+
form side: a phone loses nothing but the visual.
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
<AuthCard
|
|
270
|
+
layout="split"
|
|
271
|
+
title="Sign in"
|
|
272
|
+
brand={<Logo />}
|
|
273
|
+
visual={<Background className="absolute inset-0" aria-hidden="true" />}
|
|
274
|
+
>
|
|
275
|
+
{form}
|
|
276
|
+
</AuthCard>
|
|
277
|
+
```
|
|
278
|
+
|
|
237
279
|
A non-error notice goes into a `role="status"` region that is always
|
|
238
280
|
rendered (zero height while empty), so a screen reader announces a notice
|
|
239
281
|
that appears later; an error is a `role="alert"` inserted when it shows.
|
|
@@ -338,14 +380,133 @@ import { BottomAppBar, IconButton, TopAppBar, Typography } from '@nxgt/material/
|
|
|
338
380
|
</BottomAppBar>
|
|
339
381
|
```
|
|
340
382
|
|
|
341
|
-
|
|
342
|
-
`
|
|
343
|
-
the
|
|
383
|
+
`BottomAppBar` spaces its children and styles the one with
|
|
384
|
+
`data-state="active"`; both take a `div`'s props. `TopAppBar` is a bordered row
|
|
385
|
+
with a `variant`: `'small'` (the default), `'center'` (the row's content
|
|
386
|
+
centred), and `'medium'` or `'large'`, which draw the `headline` on a second
|
|
387
|
+
row in display type (without a `headline` they are small). It does not scroll
|
|
388
|
+
by itself: `ActivityLayout`'s `header` does that for the shell's own bar (see
|
|
389
|
+
the [app shell guide](../guides/app-shell.md#the-app-bar-variants)).
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
import { Menu } from 'lucide-react';
|
|
393
|
+
import { IconButton, TopAppBar } from '@nxgt/material/components';
|
|
394
|
+
|
|
395
|
+
<TopAppBar variant="large" headline={<h1>Projects</h1>}>
|
|
396
|
+
<IconButton variant="ghost" aria-label="Open menu"><Menu /></IconButton>
|
|
397
|
+
</TopAppBar>
|
|
398
|
+
```
|
|
344
399
|
|
|
345
400
|
**Storybook:** `Navigation & layout/TopAppBar`, `Navigation & layout/BottomAppBar` · **Vue:** `TopAppBar`, `BottomAppBar`
|
|
346
401
|
|
|
347
402
|
---
|
|
348
403
|
|
|
404
|
+
## AppSearchBar
|
|
405
|
+
|
|
406
|
+
The app bar's search. From 768px it is a pill (a filled, fully rounded search
|
|
407
|
+
field) with the `results` in a panel docked under it, as wide as the pill; on a
|
|
408
|
+
phone it is a search button that opens `SearchView`, a full-screen dialog with a
|
|
409
|
+
back arrow, the field and the results. Both are rendered, one hidden by a breakpoint class, so the server's and the
|
|
410
|
+
client's markup match; the shortcut acts on the visible one. The text is shared
|
|
411
|
+
by the two, so a window resize keeps it, and a dialog open on a phone closes when
|
|
412
|
+
the window grows to 768px. Draw it in the shell with `header.search`:
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
import { useState } from 'react';
|
|
416
|
+
import { ActivityLayout, ListTile } from '@nxgt/material/components';
|
|
417
|
+
|
|
418
|
+
function Shell() {
|
|
419
|
+
const [query, setQuery] = useState('');
|
|
420
|
+
const hits = search(query); // your own: returns { id, title }[]
|
|
421
|
+
return (
|
|
422
|
+
<ActivityLayout
|
|
423
|
+
items={items}
|
|
424
|
+
header={{
|
|
425
|
+
variant: 'small',
|
|
426
|
+
search: {
|
|
427
|
+
value: query,
|
|
428
|
+
onValueChange: setQuery,
|
|
429
|
+
shortcut: '/',
|
|
430
|
+
resultCount: hits.length,
|
|
431
|
+
onSubmit: (text) => goToResults(text),
|
|
432
|
+
results: ({ close }) => (
|
|
433
|
+
<ul className="m-0 list-none p-0">
|
|
434
|
+
{hits.map((hit) => (
|
|
435
|
+
<ListTile
|
|
436
|
+
key={hit.id}
|
|
437
|
+
as="li"
|
|
438
|
+
variant="divided"
|
|
439
|
+
title={hit.title}
|
|
440
|
+
href={`/records/${hit.id}`}
|
|
441
|
+
onClick={close}
|
|
442
|
+
/>
|
|
443
|
+
))}
|
|
444
|
+
</ul>
|
|
445
|
+
),
|
|
446
|
+
},
|
|
447
|
+
}}
|
|
448
|
+
/>
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
A route sets it with `handle.header`, like any header option (`search: false`
|
|
454
|
+
hides it there). `AppSearchBar` and `SearchView` also stand on their own, for a
|
|
455
|
+
bar of your own or a search button of your choice:
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
import { useState } from 'react';
|
|
459
|
+
import { Button, SearchView } from '@nxgt/material/components';
|
|
460
|
+
|
|
461
|
+
function Finder() {
|
|
462
|
+
const [open, setOpen] = useState(false);
|
|
463
|
+
return (
|
|
464
|
+
<>
|
|
465
|
+
<Button onClick={() => setOpen(true)}>Find</Button>
|
|
466
|
+
<SearchView open={open} onOpenChange={setOpen} label="Find a page" results={<Pages />} />
|
|
467
|
+
</>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
| Prop | Type | Default | Notes |
|
|
473
|
+
| --- | --- | --- | --- |
|
|
474
|
+
| `value` / `defaultValue` / `onValueChange` | `string` / `string` / `(value: string) => void` | — / `''` / — | The text, controlled or not. `onValueChange` is called for every change, not debounced: debounce the lookup yourself |
|
|
475
|
+
| `placeholder` | `string` | `'Search'` (`material.search-field.placeholder`) | The field's placeholder |
|
|
476
|
+
| `label` | `string` | `'Search'` (`material.app-header.search`) | The accessible name of the form, the field, the phone's button and the dialog |
|
|
477
|
+
| `onSubmit` | `(value: string) => void` | — | Enter in the field. The panel and the phone's view close after it |
|
|
478
|
+
| `results` | `ReactNode \| ({ value, close }) => ReactNode` | — | Under the pill in a panel (named `material.app-header.search-results`), below the field in the view. Unset: no panel on desktop, an empty view on a phone. Call `close` when a result is chosen |
|
|
479
|
+
| `resultCount` | `number` | — | Announced politely ("3 results", `material.app-header.result-count`) when it changes. Unset: nothing is announced |
|
|
480
|
+
| `loading` | `boolean` | `false` | A spinner in the field in place of the clear button |
|
|
481
|
+
| `shortcut` | `string` | — | A key such as `"/"` or `"Ctrl+K"` (`mod` is Ctrl or Cmd): shown as a hint while the field is empty, set as `aria-keyshortcuts`, and bound on the document. It focuses the pill, or opens the view on a phone. A bare key is ignored while the focus is in another field |
|
|
482
|
+
| `leading` / `trailing` | `ReactNode` | — | Controls before / after the pill (desktop). `trailing` also sits after the field in the phone's view |
|
|
483
|
+
| `className` | `string` | — | On the bar (desktop) or the phone's button |
|
|
484
|
+
| `open` / `onOpenChange` | `boolean` / `(open: boolean) => void` | — | `SearchView` only: required, it is controlled |
|
|
485
|
+
|
|
486
|
+
Behaviour, all of it stated so it can be relied on:
|
|
487
|
+
|
|
488
|
+
- The pill opens its panel when it is clicked, typed in or given ArrowDown, and
|
|
489
|
+
only when `results` is set; focus put there by the `shortcut` does not open
|
|
490
|
+
it. Focus stays in the field. ArrowDown moves into the panel (the portal is
|
|
491
|
+
not in the tab order after the field), Escape closes the panel and brings the
|
|
492
|
+
focus back to the field, a click outside closes it. Escape in a field that
|
|
493
|
+
holds text clears it and closes the panel in the same press.
|
|
494
|
+
Clearing the text (the button, Escape) does not reopen the panel.
|
|
495
|
+
- `SearchView` mounts only while open (the server draws nothing). It is a modal
|
|
496
|
+
`role="dialog"` named by `label`, the page behind it is inert and its focus
|
|
497
|
+
is trapped. Opening puts the focus in the field; closing (the back arrow, Escape,
|
|
498
|
+
Enter, a link; Escape in a field that holds text clears it and closes in the same press, as the field's own Escape does) returns it to the element that opened it. Targets are 48px,
|
|
499
|
+
the sizes come from `--nxgt-search-h` in `app-search.css` (2.75rem in the bar,
|
|
500
|
+
3rem in the view). It animates like any dialog and not under reduced motion.
|
|
501
|
+
- The pill is 44px high and takes up to 28rem, giving way to the title when the
|
|
502
|
+
bar is narrow. On a phone the button is 48px.
|
|
503
|
+
- Strings are `material.app-header.*` and `material.search-field.*`, in English
|
|
504
|
+
and French; the library is React only for this part.
|
|
505
|
+
|
|
506
|
+
**Storybook:** `Navigation & layout/AppSearchBar`, `Navigation & layout/AppShell` (header search) · **Vue:** not yet
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
349
510
|
## StickyActionBar
|
|
350
511
|
|
|
351
512
|

|
|
@@ -468,30 +629,259 @@ open the pane (`ListDetailsTrigger` takes the `view` to show), and their
|
|
|
468
629
|
`children` / `className` can be functions of the layout state. A server, which
|
|
469
630
|
has no screen width, renders the wide layout, as does a hydrating client; a
|
|
470
631
|
phone switches right after hydrating. `PaneLayout`'s inline pane is hidden by
|
|
471
|
-
CSS at 648 px and narrower, so a phone never sees it flash before the sheet
|
|
632
|
+
CSS at 648 px and narrower (768 px with `phone` set), so a phone never sees it flash before the sheet
|
|
472
633
|
takes over. A page rendered only in the browser starts on the real layout.
|
|
634
|
+
|
|
635
|
+
`PaneLayout` takes three opt-in props for how the pane behaves on a phone
|
|
636
|
+
(#2103); left unset, nothing above changes:
|
|
637
|
+
|
|
638
|
+
```tsx
|
|
639
|
+
<PaneLayout phone="bottom" breakpoint="container" paneWidth="lg" paneSide="right">
|
|
640
|
+
<PaneLayout.Content>
|
|
641
|
+
<PaneLayout.Trigger aria-label="Filters" className={({ isMobile }) => (isMobile ? '' : 'hidden')}>…</PaneLayout.Trigger>
|
|
642
|
+
{results}
|
|
643
|
+
</PaneLayout.Content>
|
|
644
|
+
<PaneLayout.Pane sheetTitle="Filters">{filters}</PaneLayout.Pane>
|
|
645
|
+
</PaneLayout>
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
- `phone`: `'sheet'` (side sheet), `'bottom'` (bottom sheet) or `'stack'` (the
|
|
649
|
+
pane sits under the content, no overlay; the trigger shows or hides it).
|
|
650
|
+
Once set, the switch is at 768 px (`md`) instead of 648 px. The sheets are
|
|
651
|
+
the library `Sheet`: focus moves in, Escape and the close button close it,
|
|
652
|
+
and focus returns to the trigger.
|
|
653
|
+
- `breakpoint`: `'viewport'` (default) or `'container'`, which measures the
|
|
654
|
+
`PaneLayout` itself (a CSS container, 48rem = 768 px), so a layout in a
|
|
655
|
+
narrow column of a wide screen gets the phone layout. The root becomes the
|
|
656
|
+
container and the flex row moves to an inner `data-slot="pane-layout-body"`.
|
|
657
|
+
- `paneWidth`: `'sm'` (16rem), `'md'` (20rem, default) or `'lg'` (24rem) for
|
|
658
|
+
the inline pane; no style attribute, so it holds under a strict CSP.
|
|
659
|
+
- With `phone` or `breakpoint` set, the inline pane is an `<aside>` named by
|
|
660
|
+
`sheetTitle` (default `Side Panel`), a landmark; the historical markup is a
|
|
661
|
+
`div`. `usePaneLayout()` and the function children also get `phone`,
|
|
662
|
+
`breakpoint` and `paneWidth`.
|
|
473
663
|
`ThreadLayout` is a centred column over the app
|
|
474
664
|
[background](#background) on the page ground (`bg-ground`, so a `bg-card` card stands apart) for focused flows (sign-in, onboarding), with no
|
|
475
|
-
app bar.
|
|
665
|
+
app bar. `width`, `stepperPlacement` and a sticky `footer` make it a centred form or a wizard with a side rail (see "A centred form or a wizard" below).
|
|
476
666
|
|
|
477
667
|
| Prop | Type | Default | Effect |
|
|
478
668
|
| --- | --- | --- | --- |
|
|
479
669
|
| `activeView` / `defaultActiveView` / `onActiveViewChange` | `'list' \| 'details'` | `'list'` | `ListDetailsLayout`: which view a phone shows |
|
|
670
|
+
| `phone` | `'sheet' \| 'bottom' \| 'stack'` | unset | `PaneLayout`: how the pane shows below 768 px (unset: side sheet at 648 px and narrower) |
|
|
671
|
+
| `paneWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | `PaneLayout`: width of the inline pane |
|
|
480
672
|
| `listPosition` | `'left' \| 'right'` | `'left'` | `ListDetailsLayout` |
|
|
481
673
|
| `breakpoint` | `'viewport' \| 'container'` | `'viewport'` | `ListDetailsLayout`: what the 648 px phone layout follows, the window or the layout's own width |
|
|
674
|
+
| `breakpoint` | `'viewport' \| 'container'` | `'viewport'` | `PaneLayout`: measure the viewport or the layout's own width |
|
|
675
|
+
| `inspector` | `boolean` | `false` | `ListDetailsLayout`: a third pane (`ListDetailsLayout.Inspector`); `activeView` then also takes `'inspector'`; `ListDetailsLayout.Trigger view="inspector"` needs `inspector` as well |
|
|
676
|
+
| `history` / `historyParam` | `'push' \| 'replace'` / `string` | — / `'view'` | `ListDetailsLayout`: keep the view in the URL (`?view=details`), through React Router |
|
|
677
|
+
| `listWidth` / `resizable` / `onListWidthChange` | `number` / `boolean` / `(width) => void` | `320` / `false` | `ListDetailsLayout`: the list pane's width in px; `resizable` adds a keyboard and pointer separator, 240 to 480 px |
|
|
482
678
|
| `back` | `boolean \| ReactNode` | — | `ListDetailsLayout.Details`: a bar with the way back to the list, on a phone only; `true` is the translated "Back to the list" (`material['list-details'].back`), a node is its label |
|
|
483
679
|
| `open` / `defaultOpen` / `onOpenChange` | `boolean` | `false` | `PaneLayout`: pane shown |
|
|
484
680
|
| `paneSide` | `'left' \| 'right'` | `'left'` | `PaneLayout` |
|
|
485
681
|
| `sheetTitle` | `string` | "Side Panel" (translated, `material['pane-layout']['sheet-title']`) | `PaneLayout.Pane`: screen-reader title of the sheet on phones |
|
|
486
|
-
| `header
|
|
682
|
+
| `header` | `ReactNode` | — | `ThreadLayout`: above the column |
|
|
683
|
+
| `stepper` | `ReactNode \| (placement) => ReactNode` | — | `ThreadLayout`: directly above the content; with `stepperPlacement` it is wrapped in a named `nav` and a function is called once with `'top'` or `'side'` |
|
|
684
|
+
| `width` | `'form' \| 'reading' \| 'wide'` | unset | `ThreadLayout`: a centred column of 32, 48 or 72 rem, its content stretched to fill it (unset: the 14-of-16-column track, children centred at their own width) |
|
|
685
|
+
| `footer` | `ReactNode` | — | `ThreadLayout`: a sticky action bar (`data-slot="thread-layout-footer"`) that stays in view and clear of `--nxgt-bottom-chrome` and the home bar |
|
|
686
|
+
| `stepperPlacement` | `'top' \| 'side'` | unset | `ThreadLayout`: with a `stepper`, wraps it in a `nav` named "Progress" (`material.navigation.steps`); `'side'` makes it a sticky rail beside the column from 1280 px, above it below |
|
|
687
|
+
|
|
688
|
+
`useListDetails()` and `usePaneLayout()` read the state from inside (`useListDetails()` also gives `inspectorOpen`, `listWidth` and `setListWidth`).
|
|
487
689
|
|
|
488
|
-
|
|
690
|
+
**A centred form or a wizard.** `ThreadLayout` is the page for both:
|
|
489
691
|
|
|
490
|
-
|
|
692
|
+
```tsx
|
|
693
|
+
<ThreadLayout
|
|
694
|
+
width="form"
|
|
695
|
+
stepperPlacement="side"
|
|
696
|
+
stepper={() => <Stepper steps={steps} active={step} orientation="vertical" />}
|
|
697
|
+
footer={<><Button variant="ghost">Back</Button><Button>Next</Button></>}
|
|
698
|
+
>
|
|
699
|
+
{stepForm}
|
|
700
|
+
</ThreadLayout>
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
The column, the rail and the footer are placed by CSS breakpoint classes only
|
|
704
|
+
(no hook, no style attribute), so the server and the client render the same
|
|
705
|
+
markup. `stepper` is a node or a function called once with the placement (`'side'` or `'top'`), so the caller picks the `Stepper`'s orientation; the element is placed by CSS, so with `'side'` a vertical `Stepper` is a rail from 1280px and sits above the form below it. With `stepperPlacement` unset a node is rendered as it always was. The footer's `bottom` offset and the page's bottom
|
|
706
|
+
padding use `max(var(--nxgt-bottom-chrome, …), env(safe-area-inset-bottom))`,
|
|
707
|
+
so a bottom navigation bar or a docked media bar never covers it. With none
|
|
708
|
+
of `width`, `footer` and `stepperPlacement` the markup is the historical one.
|
|
709
|
+
|
|
710
|
+
**The way back on a phone.** With `back` on `ListDetailsLayout.Details` the details open under a bar with a labelled back button (`ListDetailsLayout.Back`, also usable in your own header; it renders nothing on a wide screen or when the list shows). On a phone the layout also moves the focus: to the details when a row opens them, then back to that row when the way back is taken, also when `activeView` is controlled by the screen (a URL-driven list and detail). A wide screen shows both panes, with no bar and no focus moves. A heading (or any element) in the details marked `data-pane-focus` takes that focus instead of the pane; it needs `tabIndex={-1}`: `<h2 tabIndex={-1} data-pane-focus>Record</h2>`. `SettingsLayout` uses it for its page heading. Leave `back` out and `ListDetailsTrigger view="list"` still works as before.
|
|
711
|
+
|
|
712
|
+
**Inspector, a third pane.** `<ListDetailsLayout inspector>` with a `ListDetailsLayout.Inspector title="Properties"` after the details. It is open while the view is `'inspector'` (`setActiveView('inspector')`, a `<ListDetailsLayout.Trigger inspector view="inspector">`, or `activeView`); It renders nothing otherwise. `useListDetails()` keeps `activeView` to `'list' | 'details'` (`'details'` while it is open) and gives `inspectorOpen`. From 1280 px it sits beside the details (three panes); below 1280 px it overlays them (a CSS switch, so the server's markup is the right one and nothing flips after hydration), and Escape or its close button closes the overlay; on a phone it is one more step after the details, with a back button to them. Its `title` is a heading that also names the `complementary` landmark: the focus moves to it when the inspector opens, and goes back to the control that opened it when it closes. Without `inspector` the types stay `'list' | 'details'` and the markup is unchanged. `breakpoint="container"` makes the 1280 px threshold follow the layout's own width too (container queries).
|
|
713
|
+
|
|
714
|
+
**Selection and view in the URL.** `history="push"` keeps the view in `?view=details` / `?view=inspector` (the list is no param), so the browser's Back button goes from the details to the list on a phone, and a reload or a shared link opens the same pane; the layout's own Back and the inspector's close take that Back when the entry before is theirs, so the history does not pile up. `history="replace"` rewrites the entry instead. The layout then owns `activeView`; `onActiveViewChange` is still called. `useListDetailsParam({ name = 'item', viewName = 'view', history = 'push' })` is the same for the selected record: `const [id, select] = useListDetailsParam()`, and `select(id, { open: 'details' })` writes the id and the view in one `setSearchParams` call. Both need a React Router above them.
|
|
715
|
+
|
|
716
|
+
```tsx
|
|
717
|
+
const [id, select] = useListDetailsParam();
|
|
718
|
+
<ListDetailsLayout history="push" inspector resizable listWidth={300}>
|
|
719
|
+
<ListDetailsLayout.List>{rows.map((r) => (
|
|
720
|
+
<Row key={r.id} onClick={() => select(r.id, { open: 'details' })} />
|
|
721
|
+
))}</ListDetailsLayout.List>
|
|
722
|
+
<ListDetailsLayout.Details back><Record id={id} /></ListDetailsLayout.Details>
|
|
723
|
+
<ListDetailsLayout.Inspector title="Properties"><Fields id={id} /></ListDetailsLayout.Inspector>
|
|
724
|
+
</ListDetailsLayout>
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**Resizable list.** `listWidth` sets the list pane's width in px (default `w-80`, 320 px; a `resizable` list with no `listWidth` starts at 320 px) and `resizable` adds a separator between the panes (`role="separator"`, `aria-valuenow`, `aria-valuemin` 240, `aria-valuemax` 480, named "Resize the list"): Arrow keys move it by 16 px (Shift: 64), Home and End go to the narrowest and the widest, a pointer drag resizes (a 16 px grab area on coarse pointers), and `onListWidthChange` is told. The width reaches the CSS as a custom property set from script (the CSP allows no `style` attribute), so the server renders the default width and a set width applies right after hydrating. The separator shows on a wide screen only. Strings: `material['list-details']` (`inspector-back`, `inspector-close`, `resize`).
|
|
491
728
|
|
|
492
729
|
**Sizing.** The layout fills the box it is in: `h-full` and, in a flex column, `flex-1 min-h-0`, so a header above it and the layout under it share the height with no `h-dvh`. `breakpoint="container"` makes the single-view layout follow the layout's own width (a dialog, a column of a split view) rather than the window; the first render is the wide one, as on a server.
|
|
493
730
|
|
|
494
|
-
**Storybook:** `Navigation & layout/ListDetailsLayout`, `Navigation & layout/PaneLayout`, `Navigation & layout/ThreadLayout` · **Vue:** `ListDetailsLayout`, `PaneLayout`, `ThreadLayout`
|
|
731
|
+
**Storybook:** `Navigation & layout/ListDetailsLayout` (also `/Phone`, `/Inspector`), `Navigation & layout/PaneLayout`, `Navigation & layout/ThreadLayout` · **Vue:** `ListDetailsLayout`, `PaneLayout`, `ThreadLayout`
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## SettingsLayout
|
|
736
|
+
|
|
737
|
+

|
|
738
|
+
|
|
739
|
+
The canonical settings screen, built on `ListDetailsLayout`: sections grouped
|
|
740
|
+
in a list (an icon in a tinted well, a title, one line of description), and the
|
|
741
|
+
page of the selected one beside it, with its heading and description above the
|
|
742
|
+
rows. On a phone the list shows first; a tap opens the page, the focus moves to
|
|
743
|
+
its heading, and Back (the bar over the page, or the browser's) returns to the
|
|
744
|
+
tapped row. The selected section is the `?section=` search param, so a reload or
|
|
745
|
+
a shared link opens the same page (on a phone too), and the other params are
|
|
746
|
+
kept. It needs a router above it.
|
|
747
|
+
|
|
748
|
+
```tsx
|
|
749
|
+
import { ListTile, SettingsLayout } from '@nxgt/material/components';
|
|
750
|
+
|
|
751
|
+
<SettingsLayout
|
|
752
|
+
search
|
|
753
|
+
sections={[
|
|
754
|
+
{
|
|
755
|
+
id: 'profile',
|
|
756
|
+
group: 'Account',
|
|
757
|
+
title: 'Profile',
|
|
758
|
+
description: 'Name, photo and language',
|
|
759
|
+
icon: <User />,
|
|
760
|
+
content: <ProfileForm />,
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
id: 'notifications',
|
|
764
|
+
group: 'Preferences',
|
|
765
|
+
title: 'Notifications',
|
|
766
|
+
description: 'Email and push',
|
|
767
|
+
icon: <Bell />,
|
|
768
|
+
keywords: 'mute quiet',
|
|
769
|
+
content: (
|
|
770
|
+
<ul className="m-0 grid list-none gap-1 p-0">
|
|
771
|
+
<ListTile as="li" title="Email" subtitle="A digest each morning" toggle={{ defaultChecked: true }} />
|
|
772
|
+
</ul>
|
|
773
|
+
),
|
|
774
|
+
},
|
|
775
|
+
]}
|
|
776
|
+
/>
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
| Prop | Type | Default | Effect |
|
|
780
|
+
| --- | --- | --- | --- |
|
|
781
|
+
| `sections` | `SettingsSection[]` (exported type) | required | `id`, `title`, `description`, `icon`, `group`, `keywords`, `disabled`, `content`. Sections with the same `group` sit together under its heading, in the order the groups first appear. Only the selected section's `content` renders; a missing or disabled `?section=` falls back to the first usable section |
|
|
782
|
+
| `title` | `ReactNode` | "Settings" | The list's heading, the name of its `nav` landmark and the label of the phone's Back bar |
|
|
783
|
+
| `search` | `boolean` | `false` | A search field over the sections (string title, description, group and `keywords`; every word must match) |
|
|
784
|
+
| `param` / `viewParam` | `string` | `'section'` / `'view'` | The search params holding the section and the phone's view |
|
|
785
|
+
| `breakpoint` | `'viewport' \| 'container'` | `'viewport'` | What the one-pane phone layout (648px or less) follows |
|
|
786
|
+
| `listWidth` | `number` | 320 | The list pane's width in px |
|
|
787
|
+
|
|
788
|
+
The section links are real links (`aria-current="page"` on the current one),
|
|
789
|
+
all ending in `view=details`, beside the page too: a link must be the same on
|
|
790
|
+
the server and the client, and a URL shared to a phone opens the page:
|
|
791
|
+
beside the page a choice rewrites the history entry, on a phone it adds one.
|
|
792
|
+
Build the page from the library's rows: `ListTile` (`toggle` for a switch row,
|
|
793
|
+
`variant="divided"` in a card), the `*Field`s and `Banner`. The strings are
|
|
794
|
+
`material.settings-layout.*` (English and French).
|
|
795
|
+
|
|
796
|
+
## LandingLayout
|
|
797
|
+
|
|
798
|
+

|
|
799
|
+
|
|
800
|
+
The frame of a public marketing or landing page, outside the app shell: a top
|
|
801
|
+
bar, a `main` with the hero then the sections, and a footer. Three parts, all
|
|
802
|
+
imported from `@nxgt/material/components`:
|
|
803
|
+
|
|
804
|
+
- `LandingLayout`: the frame. The bar holds the brand (a link to `brandHref`),
|
|
805
|
+
your `items` as a `TopNav` from 768px (the same links open in a drawer from a
|
|
806
|
+
menu button below it, closing when one is followed) and your `actions` (a
|
|
807
|
+
sign-in link, a filled button) at every width. A skip link comes first and
|
|
808
|
+
`main` is the target. It needs a router above it, like `TopNav`.
|
|
809
|
+
- `LandingSection`: one band: `eyebrow`, `title`, `description` and `actions`
|
|
810
|
+
over its children, on a `tone`, with a `width` and an `align`.
|
|
811
|
+
- `SiteFooter`: the brand and a line about it, columns of links under headings,
|
|
812
|
+
a legal line and a slot beside it.
|
|
813
|
+
|
|
814
|
+
```tsx
|
|
815
|
+
import {
|
|
816
|
+
LandingLayout,
|
|
817
|
+
LandingSection,
|
|
818
|
+
LinkButton,
|
|
819
|
+
SiteFooter,
|
|
820
|
+
} from '@nxgt/material/components';
|
|
821
|
+
|
|
822
|
+
<LandingLayout
|
|
823
|
+
name="Acme"
|
|
824
|
+
items={[
|
|
825
|
+
{ label: 'Features', url: '/features', icon: 'sparkles' },
|
|
826
|
+
{ label: 'Pricing', url: '/pricing', icon: 'tag' },
|
|
827
|
+
]}
|
|
828
|
+
actions={<LinkButton to="/start" variant="filled">Get started</LinkButton>}
|
|
829
|
+
hero={
|
|
830
|
+
<LandingSection
|
|
831
|
+
variant="hero"
|
|
832
|
+
tone="ground"
|
|
833
|
+
eyebrow="New"
|
|
834
|
+
title="Plan the work"
|
|
835
|
+
description="One calm place for the plan and the people."
|
|
836
|
+
actions={<LinkButton to="/start" variant="filled" size="lg">Start for free</LinkButton>}
|
|
837
|
+
/>
|
|
838
|
+
}
|
|
839
|
+
footer={
|
|
840
|
+
<SiteFooter
|
|
841
|
+
brand="Acme"
|
|
842
|
+
groups={[{ title: 'Product', links: [{ label: 'Pricing', href: '/pricing' }] }]}
|
|
843
|
+
legal="© 2026 Acme"
|
|
844
|
+
/>
|
|
845
|
+
}
|
|
846
|
+
>
|
|
847
|
+
<LandingSection title="Everything in one place" align="center">…</LandingSection>
|
|
848
|
+
<LandingSection tone="muted" title="How it works">…</LandingSection>
|
|
849
|
+
</LandingLayout>
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
| `LandingLayout` prop | Type | Default | Effect |
|
|
853
|
+
| --- | --- | --- | --- |
|
|
854
|
+
| `items` | `MenuItem[]` | none | The destinations (top level only), in the bar and the phone's drawer |
|
|
855
|
+
| `logo` / `name` / `brand` | `AppLogo` / `string` / `ReactNode` | none | The brand; `brand` replaces the logo and name and needs its own accessible name |
|
|
856
|
+
| `brandHref` | `string` | `'/'` | Where the brand links |
|
|
857
|
+
| `actions` | `ReactNode` | none | The end of the bar, at every width |
|
|
858
|
+
| `hero` / `footer` | `ReactNode` | none | The first band and the footer |
|
|
859
|
+
| `stickyHeader` | `boolean` | `true` | Keeps the bar in view while the page scrolls |
|
|
860
|
+
| `navigationLabel` | `string` | "Navigation" | The bar's landmark name, already translated |
|
|
861
|
+
| `linkComponent` | `AppShellLinkComponent` | provider's, else React Router's | The links' component |
|
|
862
|
+
|
|
863
|
+
| `LandingSection` prop | Type | Default | Effect |
|
|
864
|
+
| --- | --- | --- | --- |
|
|
865
|
+
| `tone` | `'background' \| 'muted' \| 'ground' \| 'primary'` | `'background'` | The surface: the page, a calm tint, the page ground, the primary container |
|
|
866
|
+
| `width` | `'narrow' \| 'medium' \| 'default' \| 'full'` | `'default'` | The content's max width: 48rem, 64rem, 80rem, the whole band |
|
|
867
|
+
| `align` | `'start' \| 'center'` | `'start'` | Where the intro (and its actions) sit |
|
|
868
|
+
| `variant` | `'section' \| 'hero'` | `'section'` | `hero` is the page's first band: its title is the `h1`, set larger, with more room |
|
|
869
|
+
| `media` | `ReactNode` | none | A visual beside the intro from `lg` (an illustration, a product card), under it below; mark decorative content `aria-hidden` |
|
|
870
|
+
| `eyebrow`, `title`, `description`, `actions` | `ReactNode` | none | The intro; a `title` names the section as a region (`aria-labelledby`) |
|
|
871
|
+
|
|
872
|
+
| `SiteFooter` prop | Type | Effect |
|
|
873
|
+
| --- | --- | --- |
|
|
874
|
+
| `brand`, `description` | `ReactNode` | The brand block and a line under it |
|
|
875
|
+
| `groups` | `{ title: string; links: { label, href, external? }[] }[]` | Columns under `h2` headings, in one `nav` named by `label` (default "Footer"). A link to another site (`http(s):`) opens in a new tab with `rel="noopener noreferrer"`, a small arrow and a hidden "(opens in a new tab)" in its name; `mailto:` and `tel:` are plain links; the others use the link component. `external` overrides this automatic choice either way |
|
|
876
|
+
| `linkComponent` | `AppShellLinkComponent` | The internal links' component: the prop, else `LinkComponentProvider`'s, else React Router's |
|
|
877
|
+
| `legal`, `extra` | `ReactNode` | The legal line, and a slot beside it for social links, a language switcher or a theme toggle |
|
|
878
|
+
|
|
879
|
+
Chrome, light and dark: in light the bar uses the page's surface and the footer a
|
|
880
|
+
calm tint of it; in dark both take the card surface, one tone above the page. A
|
|
881
|
+
band's text stays `foreground` on every tone, so the contrast holds in any theme.
|
|
882
|
+
On a phone the footer's columns stack two by two, the brand first. The brand node (`brand`) is drawn twice, in the bar and in the phone's drawer, so keep it free of ids. The strings
|
|
883
|
+
are `material.landing.*` (English and French). A whole page built from these
|
|
884
|
+
parts is the `Guides/Recipes/Landing page` story.
|
|
495
885
|
|
|
496
886
|
---
|
|
497
887
|
|
|
@@ -529,12 +919,186 @@ their narrow width instead of stretching, and a wide screen gains columns
|
|
|
529
919
|
variable; a custom length sets that variable in a `style` attribute (the
|
|
530
920
|
variable only, never a grid style), so prefer a preset under a strict CSP
|
|
531
921
|
(`style-src` without `'unsafe-inline'`). The `ResponsiveGrid` props are the
|
|
532
|
-
`div`'s plus these
|
|
922
|
+
`div`'s plus these.
|
|
923
|
+
|
|
924
|
+
### Spans, dense packing and masonry
|
|
925
|
+
|
|
926
|
+
```tsx
|
|
927
|
+
<ResponsiveGrid minItem="sm" dense>
|
|
928
|
+
<ResponsiveGrid.Item span={{ base: 1, md: 2, xl: 3 }}>
|
|
929
|
+
<Chart />
|
|
930
|
+
</ResponsiveGrid.Item>
|
|
931
|
+
<ResponsiveGrid.Item rowSpan={2}>
|
|
932
|
+
<TaskList />
|
|
933
|
+
</ResponsiveGrid.Item>
|
|
934
|
+
<StatCard label="Orders" value="1,284" />
|
|
935
|
+
</ResponsiveGrid>
|
|
936
|
+
|
|
937
|
+
// A wall of notes of different heights, packed in columns.
|
|
938
|
+
<ResponsiveGrid flow="masonry" minItem="md" gap="md">
|
|
939
|
+
{notes.map((note) => <NoteCard key={note.id} note={note} />)}
|
|
940
|
+
</ResponsiveGrid>
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
| Prop | Type | Default | Effect |
|
|
944
|
+
| --- | --- | --- | --- |
|
|
945
|
+
| `flow` | `'grid' \| 'masonry'` | `'grid'` | `'masonry'` packs tiles of any height in CSS columns, with no gaps; the column count follows `minItem` or the width, as above |
|
|
946
|
+
| `dense` | `boolean` | `false` | `grid-auto-flow: dense`: later, smaller tiles move up into the holes a wide tile leaves |
|
|
947
|
+
| `Item` `span` | `1 \| … \| 6 \| 'full'`, or `{ base?, md?, xl? }` | 1 | Columns the tile covers; each width applies from there up |
|
|
948
|
+
| `Item` `rowSpan` | `1 \| … \| 4`, or `{ base?, md?, xl? }` | 1 | Rows the tile covers |
|
|
949
|
+
|
|
950
|
+
`ResponsiveGrid.Item` is a `div`; a plain child needs no wrapper, only a tile
|
|
951
|
+
with a span does. The widths are the grid's own, not the window's: `md` is a
|
|
952
|
+
768px-wide grid (`@3xl`) and `xl` a 1280px one (`@7xl`). A span wider than the
|
|
953
|
+
columns at that width adds implicit columns, so keep it within them
|
|
954
|
+
(`span={{ base: 1, md: 2 }}`, not `span={2}` in a one-column grid).
|
|
955
|
+
|
|
956
|
+
Two caveats are about reading order. With `dense` the eye reads the tiles in a
|
|
957
|
+
different order than the keyboard and a screen reader (the source order). With
|
|
958
|
+
`flow="masonry"` the tiles fill the first column top to bottom, then the next:
|
|
959
|
+
Tab and the screen reader run down each column while the eye reads across.
|
|
960
|
+
Use masonry for browsing (notes, a gallery), not for a sequence, and keep
|
|
961
|
+
anything with an order in the default grid. Masonry ignores `span`, `rowSpan`
|
|
962
|
+
and `dense`. It is CSS multi-column, which every browser supports; no
|
|
963
|
+
JavaScript measures the tiles.
|
|
533
964
|
|
|
534
965
|
**Storybook:** `Navigation & layout/ResponsiveGrid` · **Vue:** `ResponsiveGrid` (without `minItem` and `gap`)
|
|
535
966
|
|
|
536
967
|
---
|
|
537
968
|
|
|
969
|
+
## DashboardGrid
|
|
970
|
+
|
|
971
|
+

|
|
972
|
+
|
|
973
|
+
Tiles in a responsive grid that the user can rearrange. A button turns on an
|
|
974
|
+
edit mode; in it each tile shows a handle to drag with a pointer, or to pick up
|
|
975
|
+
with Space, move with the arrow keys and drop with Space. The order belongs to
|
|
976
|
+
the app: it stores it and passes it back.
|
|
977
|
+
|
|
978
|
+
```tsx
|
|
979
|
+
import { DashboardGrid, DashboardTile } from '@nxgt/material/components';
|
|
980
|
+
|
|
981
|
+
const [order, setOrder] = useState(savedLayout); // string[] of tile ids
|
|
982
|
+
|
|
983
|
+
<DashboardGrid aria-label="Dashboard" order={order} onOrderChange={setOrder}>
|
|
984
|
+
<DashboardTile id="revenue" title="Revenue" span={{ base: 1, md: 2 }}>
|
|
985
|
+
<RevenueChart />
|
|
986
|
+
</DashboardTile>
|
|
987
|
+
<DashboardTile id="orders" title="Orders">
|
|
988
|
+
<OrdersCount />
|
|
989
|
+
</DashboardTile>
|
|
990
|
+
<DashboardTile id="activity" title="Activity" span="full" rowSpan={2}>
|
|
991
|
+
<ActivityFeed />
|
|
992
|
+
</DashboardTile>
|
|
993
|
+
</DashboardGrid>
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
| Prop (`DashboardGrid`) | Type | Default | Effect |
|
|
997
|
+
| --- | --- | --- | --- |
|
|
998
|
+
| `order` / `defaultOrder` / `onOrderChange` | `string[]` / `string[]` / `(order) => void` | written order | The tiles' ids in display order. A tile the order does not list goes last; an id with no tile is ignored. `onOrderChange` gets the whole new order after a drop, a keyboard move or a move button |
|
|
999
|
+
| `editing` / `defaultEditing` / `onEditingChange` | `boolean` | `false` | Edit mode, controlled or not |
|
|
1000
|
+
| `editButton` | `boolean` | `true` | The "Edit layout" / "Done" button above the grid; `false` for an app that toggles `editing` from its own control |
|
|
1001
|
+
| `columns` | `{ base?: 1 \| 2; md?: 1..6; xl?: 1..6 }` | `{ 1, 2, 4 }` | Columns at the container widths 0, 768px and 1280px |
|
|
1002
|
+
| `gap` | `'sm' \| 'md' \| 'lg'` | `'md'` | The space between tiles |
|
|
1003
|
+
| `dense` | `boolean` | `false` | Pack later tiles into the holes a wide tile leaves |
|
|
1004
|
+
|
|
1005
|
+
| Prop (`DashboardTile`) | Type | Default | Effect |
|
|
1006
|
+
| --- | --- | --- | --- |
|
|
1007
|
+
| `id` | `string` | required | The tile's name in `order`; unique in the grid |
|
|
1008
|
+
| `title` | `ReactNode` | none | An `h3` at the top, the tile's accessible name and its handle's name |
|
|
1009
|
+
| `span` / `rowSpan` | as `ResponsiveGrid.Item` | 1 | Columns and rows covered, held to the grid's columns at each width |
|
|
1010
|
+
| `plain` | `boolean` | `false` | No card of its own (border, background, padding), for a child that is a card already |
|
|
1011
|
+
| `label` | `string` | the title, else the id | The name used by the handle and the announcements when `title` is not text |
|
|
1012
|
+
|
|
1013
|
+
Write the `DashboardTile`s directly inside the grid (not in a fragment or a
|
|
1014
|
+
wrapper component): the grid reads their `id`s to place them. Tile content keeps its
|
|
1015
|
+
state when a tile moves.
|
|
1016
|
+
|
|
1017
|
+
**Keyboard.** In edit mode each handle is a Tab stop. Space or Enter picks the
|
|
1018
|
+
tile up, the arrows move it (left and up earlier, right and down later, the
|
|
1019
|
+
reverse of left and right in a right-to-left page), Home and End send it first
|
|
1020
|
+
or last, Space, Enter or Tab drops it, Escape puts it back where it was. A
|
|
1021
|
+
live region says "Orders picked up, position 2 of 6", where it moved, and
|
|
1022
|
+
whether it was dropped or cancelled. A pointer drag is announced the same way.
|
|
1023
|
+
|
|
1024
|
+
**Phone.** Below 768px the tiles stack in one column and each shows "earlier"
|
|
1025
|
+
and "later" buttons beside its handle, so a touch user needs no drag.
|
|
1026
|
+
|
|
1027
|
+
The strings are `material.dashboard.*` in English and French (React only).
|
|
1028
|
+
|
|
1029
|
+
**Storybook:** `Navigation & layout/DashboardGrid` · **Vue:** none yet
|
|
1030
|
+
|
|
1031
|
+
---
|
|
1032
|
+
|
|
1033
|
+
## EditorLayout
|
|
1034
|
+
|
|
1035
|
+

|
|
1036
|
+
|
|
1037
|
+
The page of an editor (an image, a slide, a form builder): a toolbar, a canvas
|
|
1038
|
+
that scrolls on its own, a properties panel at the end and an optional panel
|
|
1039
|
+
at the start (layers, outline). It is built on [`PaneLayout`](#listdetailslayout) (documented with
|
|
1040
|
+
ListDetailsLayout) and follows the layout's own container width, not the window's: from 768px the properties
|
|
1041
|
+
are an inline panel; below it they open in a bottom sheet from a toolbar button.
|
|
1042
|
+
|
|
1043
|
+
```tsx
|
|
1044
|
+
import { EditorLayout } from '@nxgt/material/components';
|
|
1045
|
+
|
|
1046
|
+
// UndoRedoButtons, LayerList, SelectionForm, Artboard: your components
|
|
1047
|
+
<EditorLayout
|
|
1048
|
+
toolbar={
|
|
1049
|
+
<>
|
|
1050
|
+
<EditorLayout.StartTrigger />
|
|
1051
|
+
<UndoRedoButtons />
|
|
1052
|
+
<span className="flex-1" />
|
|
1053
|
+
<EditorLayout.PropertiesTrigger />
|
|
1054
|
+
</>
|
|
1055
|
+
}
|
|
1056
|
+
start={<LayerList />}
|
|
1057
|
+
properties={<SelectionForm />}
|
|
1058
|
+
ground="neutral"
|
|
1059
|
+
className="h-dvh"
|
|
1060
|
+
>
|
|
1061
|
+
<Artboard />
|
|
1062
|
+
</EditorLayout>
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
| Prop | Type | Default | Effect |
|
|
1066
|
+
| --- | --- | --- | --- |
|
|
1067
|
+
| `children` | `ReactNode` | required | The canvas, in a scrolling region (it takes focus, so arrows and Page keys scroll it) |
|
|
1068
|
+
| `toolbar` | `ReactNode` | none | The bar above the canvas and the outline; it stays in view while the canvas scrolls and scrolls sideways when it overflows |
|
|
1069
|
+
| `start` | `ReactNode` | none | Inline panel from 1280px; a side sheet below, opened by `EditorLayout.StartTrigger` |
|
|
1070
|
+
| `properties` | `ReactNode` | none | Inline panel at the end from 768px; a bottom sheet below |
|
|
1071
|
+
| `ground` | `'plain' \| 'tinted' \| 'neutral'` | `'plain'` | What the canvas sits on: the page, the tinted page ground (`bg-ground`) or a grey |
|
|
1072
|
+
| `propertiesWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | Inline panel width: 16, 20 or 24rem |
|
|
1073
|
+
| `propertiesOpen` / `defaultPropertiesOpen` / `onPropertiesOpenChange` | `boolean` | open | Whether the properties panel is shown, controlled or not. It applies from 768px of the layout's own width, where the panel is inline. Below that the properties live in a bottom sheet that starts closed, is opened by `EditorLayout.PropertiesTrigger` and ignores these props |
|
|
1074
|
+
| `toolbarLabel`, `startLabel`, `propertiesLabel`, `canvasLabel` | `string` | `material.editor-layout.*` | The names of the regions |
|
|
1075
|
+
|
|
1076
|
+
`EditorLayout.PropertiesTrigger` toggles the inline panel (a way to give the
|
|
1077
|
+
canvas the whole width) or the sheet; `EditorLayout.StartTrigger` shows only
|
|
1078
|
+
below 1280px. Both are `Button`s whose label is the icon alone on a phone, and
|
|
1079
|
+
take `Button` props and a `children` label. `useEditorLayout()` gives the start
|
|
1080
|
+
panel's state; the properties' is `usePaneLayout()`.
|
|
1081
|
+
|
|
1082
|
+
**Accessibility.** The toolbar is a named `group`, not `role="toolbar"`: it holds
|
|
1083
|
+
whatever the app puts there, and a toolbar role would promise arrow-key
|
|
1084
|
+
navigation between its buttons. The canvas is a `region`, the properties a
|
|
1085
|
+
`complementary` landmark (a labelled dialog in its sheet, with focus moved in
|
|
1086
|
+
and back to the button on close), the outline a `complementary` landmark too.
|
|
1087
|
+
In the source the canvas comes before the properties, so Tab reaches it first.
|
|
1088
|
+
The canvas is a tab stop because it is the keyboard route to scroll it; an app
|
|
1089
|
+
that wants a real arrow-key toolbar can put `<Toolbar>` in the `toolbar` slot.
|
|
1090
|
+
|
|
1091
|
+
**SSR.** The server renders the desktop markup and hides the panels with
|
|
1092
|
+
container-query classes; the phone sheet takes over right after hydrating, and
|
|
1093
|
+
no `style` attribute is written. The panel state is the app's to persist (read it
|
|
1094
|
+
after hydrating, then pass `defaultPropertiesOpen`).
|
|
1095
|
+
|
|
1096
|
+
The strings are `material.editor-layout.*` in English and French (React only).
|
|
1097
|
+
|
|
1098
|
+
**Storybook:** `Navigation & layout/EditorLayout` · **Vue:** none yet
|
|
1099
|
+
|
|
1100
|
+
---
|
|
1101
|
+
|
|
538
1102
|
## SectionColumns
|
|
539
1103
|
|
|
540
1104
|

|
|
@@ -934,7 +1498,7 @@ Tooltip or Link wrapping a Button is not covered.
|
|
|
934
1498
|
|
|
935
1499
|

|
|
936
1500
|
|
|
937
|
-
Documentation site shell: sidebar from `md`, TOC rail from `xl`. Reading bar, copy-page / markdown, child cards, see also, and a 404 slot. Storybook `UI/Doc/Api` composes an API page from `DocPage`, `AnchorHeading`, `ApiOperation`, `ParamTable` and `CodeGroup`.
|
|
1501
|
+
Documentation site shell: sidebar from `md`, TOC rail from `xl`, or drawn inside the app shell with `chrome="embedded"`. Reading bar, copy-page / markdown, child cards, see also, and a 404 slot. Storybook `UI/Doc/Api` composes an API page from `DocPage`, `AnchorHeading`, `ApiOperation`, `ParamTable` and `CodeGroup`.
|
|
938
1502
|
|
|
939
1503
|
<img src="../previews/navigation-layout-doc-api.webp" height="96" alt="Doc API page">
|
|
940
1504
|
<img src="../previews/navigation-layout-doc-blocks.webp" height="96" alt="Doc blocks">
|
|
@@ -971,7 +1535,7 @@ the pieces it composes — `DocTopbar`, `DocSidebar`, `DocToc`, `DocPager`,
|
|
|
971
1535
|
`DocSearch`, `DocSeeAlso`, `DocChildCards`, `DocProgress`, `DocSkipLink`,
|
|
972
1536
|
`DocHeader`, `DocCopyPage`, `DocCopyMarkdown` — and `DocEmpty` for a 404. `DocEmpty` is an `EmptyState` (`kind` defaults to `not-found`; `size` is `default` or `compact`): see [EmptyState](./feedback-and-status.md#emptystate).
|
|
973
1537
|
`useDoc` / `DocProvider` / `useDocContext` expose the state for a shell of
|
|
974
|
-
your own.
|
|
1538
|
+
your own. `useDoc` takes the same `chrome` and returns it as `frame` (type `DocChrome`, exported).
|
|
975
1539
|
|
|
976
1540
|
| Prop | Type | Default | Effect |
|
|
977
1541
|
| --- | --- | --- | --- |
|
|
@@ -979,15 +1543,28 @@ your own.
|
|
|
979
1543
|
| `pathname` | `string` | — | current page; drives active item, pager, see also |
|
|
980
1544
|
| `toc` | `DocTocItem[]` | `[]` | the page's outline |
|
|
981
1545
|
| `onNavigate` / `linkComponent` | `(href) => void` / link component | — / `<a>` | wire your router |
|
|
1546
|
+
| `chrome` | `DocChrome` (`'standalone' \| 'embedded'`) | `'standalone'` | `'embedded'` draws the page inside `ActivityLayout`: see below |
|
|
982
1547
|
| `searchable` | `boolean` | `true` | search field and ⌘K |
|
|
983
|
-
| `brand`, `actions` | `ReactNode` | — | top bar, left and right |
|
|
1548
|
+
| `brand`, `actions` | `ReactNode` | — | top bar, left and right (embedded: `actions` only, in the row under the shell's header) |
|
|
984
1549
|
| `previous`, `next` | `DocPagerLink \| null` | from `items` | override the pager; `null` hides one side |
|
|
985
1550
|
|
|
1551
|
+
**Inside the app shell.** `chrome="embedded"` is for a docs page that lives
|
|
1552
|
+
under an `ActivityLayout` route (its sidebar, app bar and `handle.title` stay).
|
|
1553
|
+
`Doc` then draws no top bar, no reading bar and no full-height frame; the
|
|
1554
|
+
shell's header is the page's. The page tree and the outline stick under that
|
|
1555
|
+
header (the same offset as an `ActivityContent` `aside`, so a taller header or
|
|
1556
|
+
a `scroll="container"` shell is followed), scroll with the shell, and stop above
|
|
1557
|
+
a bottom bar or floating toolbar (`--nxgt-bottom-chrome`). Below `md` the tree
|
|
1558
|
+
is a sheet opened by a « Browse docs » button, named apart from the shell's
|
|
1559
|
+
« Menu », with the search and `actions` beside it; `brand` shows at the top of
|
|
1560
|
+
that sheet. The default `chrome` is unchanged, markup included. See the
|
|
1561
|
+
[app shell guide](../guides/app-shell.md#a-docs-page-inside-the-shell).
|
|
1562
|
+
|
|
986
1563
|
On `DocPage`: `title`, `description`, `badge`, `crumbs`, `editHref`,
|
|
987
1564
|
`updatedAt`, and `copyPage` / `copyMarkdown` with `markdown` for the copy
|
|
988
1565
|
buttons.
|
|
989
1566
|
|
|
990
|
-
**Storybook:** `Navigation & layout/Doc`, `Navigation & layout/Doc/Api`, `Navigation & layout/Doc/Blocks` · **Vue:** `Doc`, `DocPage`, `DocTopbar`, `DocSidebar`, `DocNav`, `DocArticle`, `DocToc`, `DocPager`, `DocSearch`, `DocChildCards`, `DocSeeAlso`, `DocEmpty`, `DocProgress`, `DocLink`, `DefaultDocLink`, `DocCopyPage` (no `linkComponent`: `@navigate`)
|
|
1567
|
+
**Storybook:** `Navigation & layout/Doc`, `Navigation & layout/Doc/Api`, `Navigation & layout/Doc/Blocks`, `Navigation & layout/Doc embedded in ActivityLayout` · **Vue:** `Doc`, `DocPage`, `DocTopbar`, `DocSidebar`, `DocNav`, `DocArticle`, `DocToc`, `DocPager`, `DocSearch`, `DocChildCards`, `DocSeeAlso`, `DocEmpty`, `DocProgress`, `DocLink`, `DefaultDocLink`, `DocCopyPage` (no `linkComponent`: `@navigate`)
|
|
991
1568
|
|
|
992
1569
|
---
|
|
993
1570
|
|
|
@@ -1021,17 +1598,357 @@ in dark. `ActivityLayout` and `ThreadLayout` sit on it. The superset with `grain
|
|
|
1021
1598
|
|
|
1022
1599
|
---
|
|
1023
1600
|
|
|
1601
|
+
## NavigationBar
|
|
1602
|
+
|
|
1603
|
+
The bottom navigation bar of a phone: a `nav` fixed to the bottom edge with 3 to
|
|
1604
|
+
5 destinations (icon in an indicator pill, label always visible, `aria-current`,
|
|
1605
|
+
48px targets, optional badge) and a "More" sheet for the others. `ActivityLayout`
|
|
1606
|
+
draws it with `navigation="bar"` (alone it is phone-only: hidden from 768px,
|
|
1607
|
+
where the default rail shows) or `{ phone: 'bar' }`; see the
|
|
1608
|
+
[app shell guide](../guides/app-shell.md#the-bottom-bar-navigationbar).
|
|
1609
|
+
|
|
1610
|
+
No preview yet: see the stories `Navigation & layout/NavigationBar`.
|
|
1611
|
+
|
|
1612
|
+
```tsx
|
|
1613
|
+
import { NavigationBar } from '@nxgt/material/components';
|
|
1614
|
+
|
|
1615
|
+
<NavigationBar
|
|
1616
|
+
className="md:hidden"
|
|
1617
|
+
items={[
|
|
1618
|
+
{ label: 'Home', url: '/', icon: 'house' },
|
|
1619
|
+
{ label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3 },
|
|
1620
|
+
{ label: 'Files', url: '/files', icon: 'folder' },
|
|
1621
|
+
{ label: 'Settings', url: '/settings', icon: 'gear', priority: -1 },
|
|
1622
|
+
]}
|
|
1623
|
+
/>
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
| Prop | Type | Default | |
|
|
1627
|
+
| --- | --- | --- | --- |
|
|
1628
|
+
| `items` | `MenuItem[]` | `[]` | Entries; `bar` and `priority` choose those kept on the bar |
|
|
1629
|
+
| `max` | `number` | `5` | Destinations on the bar, More included; clamped to 3-5 |
|
|
1630
|
+
| `label` | `string` | "Navigation" | The landmark's name |
|
|
1631
|
+
| `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
|
|
1632
|
+
| `children` | `NavigationBar.Item` | | Entries composed by hand |
|
|
1633
|
+
| `className` | `string` | | On the `nav`; a breakpoint class such as `md:hidden` hides the bar |
|
|
1634
|
+
|
|
1635
|
+
It sets `--nxgt-nav-bar` on the document root while it shows, and needs a
|
|
1636
|
+
router context. `NavigationBar.Item` takes `href`, `icon`, `children` (the label), `active`
|
|
1637
|
+
(default: the location is `href` or below it), `badge`, `badgeLabel`,
|
|
1638
|
+
`linkComponent` and `className`.
|
|
1639
|
+
|
|
1640
|
+
Also exported: `splitBarItems(items, max)`, which returns a `BarSplit`
|
|
1641
|
+
(`{ bar, more }`), `BAR_MAX` (5) and the type `NavigationBarItemProps`.
|
|
1642
|
+
|
|
1643
|
+
**Storybook:** `Navigation & layout/NavigationBar` · **Vue:** not yet
|
|
1644
|
+
|
|
1645
|
+
---
|
|
1646
|
+
|
|
1024
1647
|
## NavigationRail
|
|
1025
1648
|
|
|
1026
|
-
|
|
1649
|
+
The navigation rail (Material 3 Expressive): a `nav` of destinations down the
|
|
1650
|
+
start edge, each an icon in an indicator pill with its label under it, an
|
|
1651
|
+
optional badge read in the link's name, a `fab` slot at the top for the
|
|
1652
|
+
screen's primary action and a menu button that expands it into a wide rail
|
|
1653
|
+
with the labels inline. `ActivityLayout` draws it with
|
|
1654
|
+
`navigation="rail-expanded"`; see the
|
|
1655
|
+
[app shell guide](../guides/app-shell.md#the-wide-rail-navigationrail-expanded).
|
|
1656
|
+
Without `items` or `children` it is still the empty `div` it was, and needs no
|
|
1657
|
+
router.
|
|
1658
|
+
|
|
1659
|
+
Not previewed yet: see the stories `Navigation & layout/NavigationRail`.
|
|
1660
|
+
|
|
1661
|
+
```tsx
|
|
1662
|
+
import { Plus } from 'lucide-react';
|
|
1663
|
+
import { Fab, NavigationRail } from '@nxgt/material/components';
|
|
1664
|
+
|
|
1665
|
+
<NavigationRail
|
|
1666
|
+
persistKey="app-rail"
|
|
1667
|
+
items={[
|
|
1668
|
+
{ label: 'Home', url: '/', icon: 'house' },
|
|
1669
|
+
{ label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3 },
|
|
1670
|
+
{ label: 'Settings', url: '/settings', icon: 'gear', footer: true },
|
|
1671
|
+
]}
|
|
1672
|
+
fab={({ expanded }) => (
|
|
1673
|
+
<Fab extended collapsed={!expanded} icon={<Plus />} label="Compose" />
|
|
1674
|
+
)}
|
|
1675
|
+
/>
|
|
1676
|
+
```
|
|
1677
|
+
|
|
1678
|
+
| Prop | Type | Default | |
|
|
1679
|
+
| --- | --- | --- | --- |
|
|
1680
|
+
| `items` | `MenuItem[]` | `[]` | Entries; `footer: true` ones sit at the bottom |
|
|
1681
|
+
| `fab` | `ReactNode` or `({ expanded }) => ReactNode` | | The primary action above the destinations (an action, not a link) |
|
|
1682
|
+
| `expanded` | `boolean` | | Controls the state |
|
|
1683
|
+
| `defaultExpanded` | `boolean` | `false` | The initial state when not controlled |
|
|
1684
|
+
| `onExpandedChange` | `(expanded: boolean) => void` | | Called by the menu button |
|
|
1685
|
+
| `persistKey` | `string` | | Remembers the state in the browser under this key, read after hydration |
|
|
1686
|
+
| `toggle` | `boolean` | `true` | Whether the menu button shows |
|
|
1687
|
+
| `label` | `string` | "Navigation" | The landmark's name |
|
|
1688
|
+
| `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
|
|
1689
|
+
| `children` | `NavigationRail.Item` | | Entries composed by hand |
|
|
1690
|
+
| `className` | `string` | | On the `nav`; give it a height (the rail fills it) and a breakpoint class to hide it |
|
|
1691
|
+
|
|
1692
|
+
A phone never draws `'rail-expanded'` (or `'rail'`): it keeps its menu sheet.
|
|
1693
|
+
A standalone `NavigationRail` starts collapsed (`defaultExpanded` is `false`); inside `ActivityLayout` the `rail-expanded` preset starts expanded.
|
|
1694
|
+
|
|
1695
|
+
The rail is 5rem wide collapsed and 15rem expanded (the width eases unless the
|
|
1696
|
+
user prefers reduced motion). Targets are 48px or taller, the menu button
|
|
1697
|
+
carries `aria-expanded` and `aria-controls`, and its name is
|
|
1698
|
+
`material.navigation.rail-toggle` ("Navigation menu"). `NavigationRail.Item`
|
|
1699
|
+
takes `href`, `icon`, `children` (the label), `active`, `badge`, `badgeLabel`,
|
|
1700
|
+
`linkComponent` and `className`. Also exported: the types `NavigationRailItemProps`
|
|
1701
|
+
and `RailExpandedOptions`.
|
|
1702
|
+
|
|
1703
|
+
**Storybook:** `Navigation & layout/NavigationRail` · **Vue:** placeholder only
|
|
1704
|
+
|
|
1705
|
+
## NavigationRailPanel
|
|
1706
|
+
|
|
1707
|
+
The second level of a two-level navigation (Material 3 Expressive): a panel
|
|
1708
|
+
beside the [`NavigationRail`](#navigationrail) that lists the `children` of the
|
|
1709
|
+
top-level entry the location belongs to, under that entry's label, in 48px pill
|
|
1710
|
+
rows with the current child tinted and marked `aria-current="page"` (a badge
|
|
1711
|
+
ends the row and is read in the link's name). It is a `nav` named by the
|
|
1712
|
+
section's label, which is also its visible heading; a button folds it to a
|
|
1713
|
+
narrow strip and back (`aria-expanded`, `aria-controls`, focus stays on it).
|
|
1714
|
+
Nothing opens it on hover. `ActivityLayout` draws the rail and the panel
|
|
1715
|
+
together with `navigation="two-level"`; see the
|
|
1716
|
+
[app shell guide](../guides/app-shell.md#the-two-level-navigation). It reads the
|
|
1717
|
+
location, so it needs a React Router context.
|
|
1718
|
+
|
|
1719
|
+
The panel is **open by default** (`defaultExpanded` is `true`), or controlled with
|
|
1720
|
+
`expanded`; with a `persistKey` the last choice is kept in the browser under
|
|
1721
|
+
`nxgt-panel:<persistKey>`, read after hydration (the server and the first client
|
|
1722
|
+
render are open). On a section without `children` the panel hides itself with
|
|
1723
|
+
CSS and keeps its markup, so the page does not change shape between routes. Only
|
|
1724
|
+
one level of `children` is listed (the children's own children are ignored).
|
|
1027
1725
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1726
|
+
```tsx
|
|
1727
|
+
import {
|
|
1728
|
+
type MenuItem,
|
|
1729
|
+
NavigationRail,
|
|
1730
|
+
NavigationRailPanel,
|
|
1731
|
+
} from '@nxgt/material/components';
|
|
1732
|
+
|
|
1733
|
+
const items: MenuItem[] = [
|
|
1734
|
+
{ label: 'Home', url: '/', icon: 'house' },
|
|
1735
|
+
{
|
|
1736
|
+
label: 'Projects',
|
|
1737
|
+
url: '/projects',
|
|
1738
|
+
icon: 'folder',
|
|
1739
|
+
children: [
|
|
1740
|
+
{ label: 'Overview', url: '/projects', icon: 'folder' },
|
|
1741
|
+
{ label: 'Active', url: '/projects/active', icon: 'folder' },
|
|
1742
|
+
],
|
|
1743
|
+
},
|
|
1744
|
+
];
|
|
1745
|
+
|
|
1746
|
+
<div className="flex h-dvh">
|
|
1747
|
+
<NavigationRail items={items} toggle={false} />
|
|
1748
|
+
<NavigationRailPanel items={items} persistKey="app-panel" />
|
|
1749
|
+
</div>
|
|
1750
|
+
```
|
|
1751
|
+
|
|
1752
|
+
| Prop | Type | Default | |
|
|
1753
|
+
| --- | --- | --- | --- |
|
|
1754
|
+
| `items` | `MenuItem[]` | `[]` | The rail's menu; the panel lists the active top-level entry's `children` |
|
|
1755
|
+
| `expanded` | `boolean` | | Controls the panel: open while `true` |
|
|
1756
|
+
| `defaultExpanded` | `boolean` | `true` | The state to start from, before a saved choice is read |
|
|
1757
|
+
| `onExpandedChange` | `(expanded: boolean) => void` | | Asked for every change, controlled or not |
|
|
1758
|
+
| `persistKey` | `string` | | Keeps the state under `nxgt-panel:<persistKey>` (ignored when controlled) |
|
|
1759
|
+
| `label` | `string` | the section's label | The landmark's accessible name, already translated |
|
|
1760
|
+
| `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
|
|
1761
|
+
| `className` | `string` | | On the panel's root |
|
|
1762
|
+
|
|
1763
|
+
The button's names are `material.navigation.panel-collapse` ("Collapse the panel")
|
|
1764
|
+
and `material.navigation.panel-expand` ("Expand the panel"). Also exported: the
|
|
1765
|
+
type `NavigationRailPanelProps`. The rail beside it lights the section when the
|
|
1766
|
+
current page is one of its children, even a child whose `url` is not under the
|
|
1767
|
+
parent's (`NavigationRail` matches the whole tree and marks the top-level
|
|
1768
|
+
entry).
|
|
1769
|
+
|
|
1770
|
+
**Storybook:** `Navigation & layout/NavigationRailPanel` · **Vue:** —
|
|
1771
|
+
|
|
1772
|
+
## NavigationToolbar
|
|
1773
|
+
|
|
1774
|
+
The floating navigation toolbar (Material 3 Expressive): a pill of round icon
|
|
1775
|
+
links over the content, at the bottom centre. It is a `nav` landmark of links
|
|
1776
|
+
(`aria-current="page"` on the current one, each named by a visually hidden label
|
|
1777
|
+
and a tooltip, 48px targets, a badge read in the link's name), not a
|
|
1778
|
+
`role="toolbar"`: `Toolbar variant="floating"` stays for *actions*. The two share
|
|
1779
|
+
the pill (the `--tb-*` tokens, the shadow and the slide away of
|
|
1780
|
+
`.nxgt-toolbar`, so a theme that restyles the toolbar restyles both) and nothing
|
|
1781
|
+
else. `ActivityLayout` draws it with `navigation="floating"`; see the
|
|
1782
|
+
[app shell guide](../guides/app-shell.md#the-floating-navigation-toolbar).
|
|
1783
|
+
|
|
1784
|
+
No preview yet: see the stories `Navigation & layout/NavigationToolbar`.
|
|
1030
1785
|
|
|
1031
1786
|
```tsx
|
|
1032
|
-
import {
|
|
1787
|
+
import { NavigationToolbar } from '@nxgt/material/components';
|
|
1033
1788
|
|
|
1034
|
-
<
|
|
1789
|
+
<NavigationToolbar
|
|
1790
|
+
hideOnScroll
|
|
1791
|
+
items={[
|
|
1792
|
+
{ label: 'Home', url: '/', icon: 'house' },
|
|
1793
|
+
{ label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3 },
|
|
1794
|
+
{ label: 'Files', url: '/files', icon: 'folder' },
|
|
1795
|
+
{ label: 'Settings', url: '/settings', icon: 'gear', priority: -1 },
|
|
1796
|
+
]}
|
|
1797
|
+
/>
|
|
1035
1798
|
```
|
|
1036
1799
|
|
|
1037
|
-
|
|
1800
|
+
| Prop | Type | Default | |
|
|
1801
|
+
| --- | --- | --- | --- |
|
|
1802
|
+
| `items` | `MenuItem[]` | `[]` | Entries; `bar` and `priority` choose those kept on the pill, as for `NavigationBar` |
|
|
1803
|
+
| `max` | `number` | `5` | Destinations on the pill, More included; clamped to 3-5 (3 to 5 recommended) |
|
|
1804
|
+
| `label` | `string` | "Navigation" | The landmark's name |
|
|
1805
|
+
| `hideOnScroll` | `boolean` | `false` | Slides away while the page scrolls down (past 64px) and back on the way up; reads the shell's scroll, so outside `ActivityLayout` (or a `ShellScrollProvider`) nothing scrolls it away. While away it is `inert` and its focus moves to the content region, then returns. Reduced motion: it snaps |
|
|
1806
|
+
| `hidden` | `boolean` | — | Forces it away or shown, whatever the scroll says |
|
|
1807
|
+
| `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
|
|
1808
|
+
| `children` | `NavigationToolbar.Item` | | Entries composed by hand |
|
|
1809
|
+
| `className` | `string` | | On the `nav`; a breakpoint class such as `md:hidden` hides it |
|
|
1810
|
+
|
|
1811
|
+
With more entries than fit, the last slot is « More »: the same bottom sheet as
|
|
1812
|
+
`NavigationBar`'s, lighting up when the current page is under it. The pill is
|
|
1813
|
+
`position: fixed`, `1rem` above the safe area, and shows at every width. A docked media bar sits above
|
|
1814
|
+
it (the dock rests at `var(--nxgt-nav-bar)`). While it shows it sets `--nxgt-nav-bar` to `calc(5rem + env(safe-area-inset-bottom))` (its
|
|
1815
|
+
4rem height, the gap and the safe area), also while it is slid away, so `FabDock`, the toaster,
|
|
1816
|
+
the mini player and `StickyActionBar` stay clear and nothing jumps when it
|
|
1817
|
+
returns. It does not rise with the on-screen keyboard. It needs a router context. `NavigationToolbar.Item` takes `href`,
|
|
1818
|
+
`icon`, `children` (the name), `active`, `badge`, `badgeLabel`, `linkComponent`
|
|
1819
|
+
and `className`; the type `NavigationToolbarItemProps` is exported too.
|
|
1820
|
+
|
|
1821
|
+
**Storybook:** `Navigation & layout/NavigationToolbar` · **Vue:** not yet
|
|
1822
|
+
|
|
1823
|
+
## TopNav
|
|
1824
|
+
|
|
1825
|
+
The top navigation (Material 3 Expressive): one `nav` of destinations as 48px
|
|
1826
|
+
pills in a row, the current one tinted with `aria-current="page"`. An entry
|
|
1827
|
+
with `children` is a trigger button that opens a dropdown (a rounded card) of
|
|
1828
|
+
its child links; when the current page is one of them, the trigger reads as
|
|
1829
|
+
active. A badge ends the pill and is read in the link's name. Built on
|
|
1830
|
+
Radix's navigation menu: arrow keys move along the bar, Escape closes the
|
|
1831
|
+
dropdown and returns focus to its trigger, and the motion is off for users who
|
|
1832
|
+
prefer reduced motion. `ActivityLayout` draws it in the app header with
|
|
1833
|
+
`navigation="top"`; see the
|
|
1834
|
+
[app shell guide](../guides/app-shell.md#the-top-navigation). It reads the
|
|
1835
|
+
location, so it needs a React Router context.
|
|
1836
|
+
|
|
1837
|
+
Not previewed yet: see the stories `Navigation & layout/TopNav`.
|
|
1838
|
+
|
|
1839
|
+
An entry with `children` is only a trigger: its `url` is never a link, so put an "all" link as the first child. Past about 5 entries the bar does not fit at 768px beside the brand and the title and pushes the header's actions out; it does not scroll (that would clip the dropdowns). Use `{ tablet: 'rail', desktop: 'top' }` for a longer menu.
|
|
1840
|
+
|
|
1841
|
+
```tsx
|
|
1842
|
+
import { TopNav } from '@nxgt/material/components';
|
|
1843
|
+
|
|
1844
|
+
<TopNav
|
|
1845
|
+
items={[
|
|
1846
|
+
{ label: 'Home', url: '/', icon: 'house' },
|
|
1847
|
+
{ label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3 },
|
|
1848
|
+
{
|
|
1849
|
+
label: 'Projects',
|
|
1850
|
+
url: '/projects',
|
|
1851
|
+
icon: 'folder',
|
|
1852
|
+
children: [
|
|
1853
|
+
{ label: 'All projects', url: '/projects/all', icon: 'folder' },
|
|
1854
|
+
{ label: 'Archived', url: '/projects/archived', icon: 'box-archive' },
|
|
1855
|
+
],
|
|
1856
|
+
},
|
|
1857
|
+
]}
|
|
1858
|
+
/>
|
|
1859
|
+
```
|
|
1860
|
+
|
|
1861
|
+
| Prop | Type | Default | |
|
|
1862
|
+
| --- | --- | --- | --- |
|
|
1863
|
+
| `items` | `MenuItem[]` | `[]` | Destinations in order; an entry with `children` opens a dropdown (its own `url` is never a link) |
|
|
1864
|
+
| `label` | `string` | "Navigation" | The landmark's name |
|
|
1865
|
+
| `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
|
|
1866
|
+
| `children` | `TopNav.Item`, `TopNav.Menu` | | Entries composed by hand, after those of `items` |
|
|
1867
|
+
| `className` | `string` | | On the `nav` |
|
|
1868
|
+
|
|
1869
|
+
By hand, with the pieces:
|
|
1870
|
+
|
|
1871
|
+
```tsx
|
|
1872
|
+
import { TopNav, TopNavItem, TopNavMenu } from '@nxgt/material/components';
|
|
1873
|
+
|
|
1874
|
+
<TopNav>
|
|
1875
|
+
<TopNavItem href="/" icon="house" active>Home</TopNavItem>
|
|
1876
|
+
<TopNavItem href="/inbox" badge={3}>Inbox</TopNavItem>
|
|
1877
|
+
<TopNavMenu
|
|
1878
|
+
label="Projects"
|
|
1879
|
+
icon="folder"
|
|
1880
|
+
current="/projects/all"
|
|
1881
|
+
items={[
|
|
1882
|
+
{ label: 'All projects', url: '/projects/all', icon: 'folder' },
|
|
1883
|
+
{ label: 'Archived', url: '/projects/archived', icon: 'box-archive' },
|
|
1884
|
+
]}
|
|
1885
|
+
/>
|
|
1886
|
+
</TopNav>
|
|
1887
|
+
```
|
|
1888
|
+
|
|
1889
|
+
`TopNavItem` (also `TopNav.Item`) takes `href`, `icon` (optional), `children`
|
|
1890
|
+
(the label), `active` (`false`), `badge` (none), `badgeLabel`, `linkComponent` and
|
|
1891
|
+
`className`.
|
|
1892
|
+
`TopNavMenu` (also `TopNav.Menu`) takes `label`, `icon`, `items` (`MenuItem[]`, the links),
|
|
1893
|
+
`current` (the `url` of the current one; unset, none is current), `badge`, `badgeLabel`,
|
|
1894
|
+
`linkComponent` and `className`. Also exported: the types `TopNavProps`,
|
|
1895
|
+
`TopNavItemProps` and `TopNavMenuProps`.
|
|
1896
|
+
|
|
1897
|
+
**Storybook:** `Navigation & layout/TopNav` · **Vue:** —
|
|
1898
|
+
|
|
1899
|
+
## NavigationDrawer
|
|
1900
|
+
|
|
1901
|
+
The navigation drawer (Material 3 Expressive): a `nav` of destinations in
|
|
1902
|
+
sections, each a 56px row with an icon, a label and an optional badge that
|
|
1903
|
+
ends the row and is read in the link's name. `aria-current="page"` marks the
|
|
1904
|
+
current one, and its row is tinted. Entries are listed in sections by their
|
|
1905
|
+
`group` (the ones without a group first, with no headline) and the entries
|
|
1906
|
+
marked `footer` sit at the bottom. `ActivityLayout` draws the standard drawer
|
|
1907
|
+
with `navigation="drawer"`; see the
|
|
1908
|
+
[app shell guide](../guides/app-shell.md#the-drawer-and-the-collapsible-sidebar).
|
|
1909
|
+
It reads the location, so it needs a React Router context.
|
|
1910
|
+
|
|
1911
|
+
Not previewed yet: see the stories `Navigation & layout/NavigationDrawer`.
|
|
1912
|
+
|
|
1913
|
+
```tsx
|
|
1914
|
+
import { IconButton, NavigationDrawer } from '@nxgt/material/components';
|
|
1915
|
+
|
|
1916
|
+
// permanent, 20rem wide
|
|
1917
|
+
<NavigationDrawer
|
|
1918
|
+
items={[
|
|
1919
|
+
{ label: 'Home', url: '/', icon: 'house' },
|
|
1920
|
+
{ label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3, group: 'Mail' },
|
|
1921
|
+
{ label: 'Settings', url: '/settings', icon: 'gear', footer: true },
|
|
1922
|
+
]}
|
|
1923
|
+
/>
|
|
1924
|
+
|
|
1925
|
+
// modal: a sheet from the start edge, closed by a followed link or Escape
|
|
1926
|
+
<NavigationDrawer
|
|
1927
|
+
variant="modal"
|
|
1928
|
+
items={items}
|
|
1929
|
+
trigger={<IconButton aria-label="Menu" variant="outlined">...</IconButton>}
|
|
1930
|
+
/>
|
|
1931
|
+
```
|
|
1932
|
+
|
|
1933
|
+
| Prop | Type | Default | |
|
|
1934
|
+
| --- | --- | --- | --- |
|
|
1935
|
+
| `variant` | `'standard' \| 'modal'` | `'standard'` | A permanent column, or the same column in a sheet |
|
|
1936
|
+
| `items` | `MenuItem[]` | `[]` | Entries, in sections by `group`; `footer: true` ones sit at the bottom |
|
|
1937
|
+
| `header` | `ReactNode` | | A block above the destinations (the brand) |
|
|
1938
|
+
| `label` | `string` | "Navigation" | The landmark's name |
|
|
1939
|
+
| `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
|
|
1940
|
+
| `children` | `NavigationDrawer.Section` | | Sections composed by hand, after those of `items` |
|
|
1941
|
+
| `open` / `onOpenChange` | `boolean` / `(open: boolean) => void` | | `variant="modal"`: controls the sheet |
|
|
1942
|
+
| `trigger` | `ReactNode` | | `variant="modal"`: the element that opens it |
|
|
1943
|
+
| `className` | `string` | | On the `nav`; give a standard drawer a height (it fills it, and scrolls) and a breakpoint class to hide it |
|
|
1944
|
+
|
|
1945
|
+
`NavigationDrawerSection` (also `NavigationDrawer.Section`) takes `headline`
|
|
1946
|
+
(names the group), `items`, `current` (the `url` of the current entry; unset,
|
|
1947
|
+
the one of its own `items` the location is in), `linkComponent` and children
|
|
1948
|
+
(`NavigationDrawer.Item`: `href`, `icon`, `children`, `active`, `badge`,
|
|
1949
|
+
`badgeLabel`, `linkComponent`, `className`). The modal variant shares the app
|
|
1950
|
+
shell's menu sheet logic: a followed link and a new location close it and focus
|
|
1951
|
+
goes to the page's title, else back to the trigger. Also exported: the types
|
|
1952
|
+
`NavigationDrawerItemProps` and `NavigationDrawerSectionProps`.
|
|
1953
|
+
|
|
1954
|
+
**Storybook:** `Navigation & layout/NavigationDrawer` · **Vue:** —
|