@nxgt/material 3.79.0 → 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.
Files changed (279) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +45 -6
  3. package/dist/{alert-dialog-653apk3f.js → alert-dialog-DgHtXZuB.js} +1 -1
  4. package/dist/{async-slice-CXLbQuOc.js → async-slice-YBQsGtQT.js} +1 -1
  5. package/dist/{button-3op3rtZh.js → button-_vm-xSBU.js} +1 -1
  6. package/dist/{calculator-KqIMBXSh.js → calculator-CTd_S23A.js} +10 -10
  7. package/dist/calculator-field.js +6 -6
  8. package/dist/{calculator-segment-BlJE1LaS.js → calculator-segment-OI1BT3bA.js} +1 -1
  9. package/dist/calculator.js +1 -1
  10. package/dist/charts.js +3 -3
  11. package/dist/{chip-MuKDwtkG.js → chip-DumcVzUO.js} +2 -2
  12. package/dist/{code-editor-Bp2RrdNx.js → code-editor-CiwgfSOK.js} +4 -4
  13. package/dist/code-editor.js +4 -4
  14. package/dist/{code-language-items-B7WR5ua_.js → code-language-items-Dip0xnmc.js} +2 -2
  15. package/dist/components/components.css +3 -0
  16. package/dist/components/ui/actions/fab/fab-dock.css +19 -6
  17. package/dist/components/ui/layout/app-search/app-search.css +62 -0
  18. package/dist/components/ui/layout/app-shell/activity-backdrop.css +10 -2
  19. package/dist/components/ui/layout/app-shell/app-header.css +108 -0
  20. package/dist/components/ui/layout/navigation-toolbar/navigation-toolbar.css +43 -0
  21. package/dist/{components-ConMf2w-.js → components-CJFHhkZv.js} +21800 -17785
  22. package/dist/components.js +37 -37
  23. package/dist/{conversion-variant-BDFAxqe9.js → conversion-variant-CqrPwPjC.js} +3 -3
  24. package/dist/{currency-field-DWHB5oms.js → currency-field-CMFvctye.js} +4 -4
  25. package/dist/{dialog-Bm5LvVlB.js → dialog-B0iU3fmh.js} +3 -3
  26. package/dist/dnd.js +2 -2
  27. package/dist/{dropdown-menu-xlZ6NYUA.js → dropdown-menu-CPOgcajv.js} +1 -1
  28. package/dist/editor.js +7 -7
  29. package/dist/{empty-state-C0kviQdW.js → empty-state-D3Jggupu.js} +1 -1
  30. package/dist/{financial-variant-CggTpad2.js → financial-variant-BdyfYhnp.js} +5 -5
  31. package/dist/{hooks-Dg94jPRq.js → hooks-Z7KKbv4G.js} +2 -2
  32. package/dist/hooks.js +2 -2
  33. package/dist/{hover-card-B1g55Hi6.js → hover-card-CFGIX6kg.js} +1 -1
  34. package/dist/i18n.js +3 -3
  35. package/dist/{icon-badge-BcW66bRk.js → icon-badge-kxGWUvcO.js} +1 -1
  36. package/dist/{icon-button-Cogxy1cy.js → icon-button-BamxMwZs.js} +1 -1
  37. package/dist/lib/components/ui/collections/server-table/partials/server-table-band-classes.d.ts +13 -0
  38. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  39. package/dist/lib/components/ui/layout/app-bar/top-app-bar.d.ts +18 -3
  40. package/dist/lib/components/ui/layout/app-search/app-search-bar.d.ts +11 -0
  41. package/dist/lib/components/ui/layout/app-search/app-search.types.d.ts +47 -0
  42. package/dist/lib/components/ui/layout/app-search/index.d.ts +3 -0
  43. package/dist/lib/components/ui/layout/app-search/partials/on-phone.d.ts +2 -0
  44. package/dist/lib/components/ui/layout/app-search/partials/phone-search.d.ts +3 -0
  45. package/dist/lib/components/ui/layout/app-search/partials/pill-search.d.ts +3 -0
  46. package/dist/lib/components/ui/layout/app-search/pill-form.d.ts +18 -0
  47. package/dist/lib/components/ui/layout/app-search/search-panel.d.ts +24 -0
  48. package/dist/lib/components/ui/layout/app-search/search-results.d.ts +14 -0
  49. package/dist/lib/components/ui/layout/app-search/search-shortcut.d.ts +15 -0
  50. package/dist/lib/components/ui/layout/app-search/search-view.d.ts +22 -0
  51. package/dist/lib/components/ui/layout/app-search/use-pill-panel.d.ts +22 -0
  52. package/dist/lib/components/ui/layout/app-search/use-search-text.d.ts +8 -0
  53. package/dist/lib/components/ui/layout/app-shell/activity-app-bar.d.ts +35 -0
  54. package/dist/lib/components/ui/layout/app-shell/activity-chrome.d.ts +26 -3
  55. package/dist/lib/components/ui/layout/app-shell/activity-column-props.d.ts +22 -0
  56. package/dist/lib/components/ui/layout/app-shell/activity-column.d.ts +10 -2
  57. package/dist/lib/components/ui/layout/app-shell/activity-layout-navigation.types.d.ts +94 -0
  58. package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +8 -1
  59. package/dist/lib/components/ui/layout/app-shell/activity-layout.types.d.ts +2 -1
  60. package/dist/lib/components/ui/layout/app-shell/activity-rails.d.ts +16 -0
  61. package/dist/lib/components/ui/layout/app-shell/activity-sidebar-lane.d.ts +22 -0
  62. package/dist/lib/components/ui/layout/app-shell/activity-top-nav.d.ts +11 -0
  63. package/dist/lib/components/ui/layout/app-shell/activity-two-level-lane.d.ts +14 -0
  64. package/dist/lib/components/ui/layout/app-shell/app-back.d.ts +22 -0
  65. package/dist/lib/components/ui/layout/app-shell/app-header-row.d.ts +3 -0
  66. package/dist/lib/components/ui/layout/app-shell/app-header.d.ts +33 -1
  67. package/dist/lib/components/ui/layout/app-shell/app-shell.utils.d.ts +8 -0
  68. package/dist/lib/components/ui/layout/app-shell/app-sidebar-entries.d.ts +13 -0
  69. package/dist/lib/components/ui/layout/app-shell/app-sidebar-label-item.d.ts +3 -1
  70. package/dist/lib/components/ui/layout/app-shell/bottom-chrome.d.ts +13 -0
  71. package/dist/lib/components/ui/layout/app-shell/header-title.d.ts +18 -2
  72. package/dist/lib/components/ui/layout/app-shell/header.utils.d.ts +37 -0
  73. package/dist/lib/components/ui/layout/app-shell/index.d.ts +3 -0
  74. package/dist/lib/components/ui/layout/app-shell/navigation-parts.d.ts +38 -0
  75. package/dist/lib/components/ui/layout/app-shell/navigation.utils.d.ts +36 -0
  76. package/dist/lib/components/ui/layout/app-shell/page-title-scope.d.ts +14 -0
  77. package/dist/lib/components/ui/layout/app-shell/page-title.d.ts +12 -1
  78. package/dist/lib/components/ui/layout/app-shell/shell-scroll.d.ts +32 -0
  79. package/dist/lib/components/ui/layout/app-shell/sticky-offset.d.ts +8 -0
  80. package/dist/lib/components/ui/layout/app-shell/types.d.ts +14 -0
  81. package/dist/lib/components/ui/layout/app-shell/use-focus-while-hidden.d.ts +9 -0
  82. package/dist/lib/components/ui/layout/app-shell/use-header-height.d.ts +12 -0
  83. package/dist/lib/components/ui/layout/app-shell/use-header-scroll.d.ts +19 -0
  84. package/dist/lib/components/ui/layout/app-shell/use-shell-header.d.ts +20 -0
  85. package/dist/lib/components/ui/layout/auth-card/auth-card-split.d.ts +14 -0
  86. package/dist/lib/components/ui/layout/auth-card/auth-card.d.ts +13 -1
  87. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid-context.d.ts +21 -0
  88. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.d.ts +9 -0
  89. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.state.d.ts +30 -0
  90. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.tiles.d.ts +8 -0
  91. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.types.d.ts +50 -0
  92. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.utils.d.ts +70 -0
  93. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-tile.d.ts +7 -0
  94. package/dist/lib/components/ui/layout/dashboard-grid/index.d.ts +4 -0
  95. package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-announcer.d.ts +6 -0
  96. package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-edit-bar.d.ts +11 -0
  97. package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-move-buttons.d.ts +9 -0
  98. package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-toolbar.d.ts +7 -0
  99. package/dist/lib/components/ui/layout/dashboard-grid/use-dashboard-grid.d.ts +21 -0
  100. package/dist/lib/components/ui/layout/dashboard-grid/use-dashboard-moves.d.ts +22 -0
  101. package/dist/lib/components/ui/layout/doc/doc-context.d.ts +2 -0
  102. package/dist/lib/components/ui/layout/doc/doc.d.ts +5 -0
  103. package/dist/lib/components/ui/layout/doc/index.d.ts +1 -1
  104. package/dist/lib/components/ui/layout/doc/partials/doc-embedded-bar.d.ts +9 -0
  105. package/dist/lib/components/ui/layout/doc/partials/doc-embedded.d.ts +8 -0
  106. package/dist/lib/components/ui/layout/doc/use-doc.d.ts +9 -1
  107. package/dist/lib/components/ui/layout/editor-layout/editor-layout.d.ts +14 -0
  108. package/dist/lib/components/ui/layout/editor-layout/editor-layout.types.d.ts +31 -0
  109. package/dist/lib/components/ui/layout/editor-layout/index.d.ts +4 -0
  110. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-body.d.ts +11 -0
  111. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-canvas.d.ts +8 -0
  112. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-context.d.ts +12 -0
  113. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-start.d.ts +8 -0
  114. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-triggers.d.ts +10 -0
  115. package/dist/lib/components/ui/layout/editor-layout/partials/use-editor-layout-names.d.ts +8 -0
  116. package/dist/lib/components/ui/layout/editor-layout/partials/use-editor-layout-state.d.ts +21 -0
  117. package/dist/lib/components/ui/layout/index.d.ts +9 -0
  118. package/dist/lib/components/ui/layout/landing/index.d.ts +6 -0
  119. package/dist/lib/components/ui/layout/landing/landing-layout.d.ts +15 -0
  120. package/dist/lib/components/ui/layout/landing/landing-layout.types.d.ts +27 -0
  121. package/dist/lib/components/ui/layout/landing/landing-section.d.ts +12 -0
  122. package/dist/lib/components/ui/layout/landing/landing-section.types.d.ts +32 -0
  123. package/dist/lib/components/ui/layout/landing/landing.classes.d.ts +20 -0
  124. package/dist/lib/components/ui/layout/landing/partials/landing-header.d.ts +11 -0
  125. package/dist/lib/components/ui/layout/landing/partials/landing-section-intro.d.ts +13 -0
  126. package/dist/lib/components/ui/layout/landing/partials/site-footer-group.d.ts +8 -0
  127. package/dist/lib/components/ui/layout/landing/site-footer.d.ts +13 -0
  128. package/dist/lib/components/ui/layout/landing/site-footer.types.d.ts +29 -0
  129. package/dist/lib/components/ui/layout/list-details-layout/internal.d.ts +2 -0
  130. package/dist/lib/components/ui/layout/list-details-layout/list-details-layout.d.ts +9 -17
  131. package/dist/lib/components/ui/layout/list-details-layout/partials/history-mark.d.ts +2 -0
  132. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-context.d.ts +17 -1
  133. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-history.d.ts +8 -0
  134. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-inspector.d.ts +21 -0
  135. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-layout.types.d.ts +47 -0
  136. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-resizer.d.ts +9 -0
  137. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-root.d.ts +3 -0
  138. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-trigger.d.ts +13 -4
  139. package/dist/lib/components/ui/layout/list-details-layout/partials/use-inspector-focus.d.ts +8 -0
  140. package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-details-param.d.ts +26 -0
  141. package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-details-state.d.ts +15 -0
  142. package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-width.d.ts +16 -0
  143. package/dist/lib/components/ui/layout/list-details-layout/partials/use-pane-focus.d.ts +6 -4
  144. package/dist/lib/components/ui/layout/navigation-bar/index.d.ts +3 -0
  145. package/dist/lib/components/ui/layout/navigation-bar/internal.d.ts +4 -0
  146. package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.classes.d.ts +14 -0
  147. package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.d.ts +35 -0
  148. package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.utils.d.ts +19 -0
  149. package/dist/lib/components/ui/layout/navigation-bar/partials/bar-badge.d.ts +5 -0
  150. package/dist/lib/components/ui/layout/navigation-bar/partials/bar-entry.d.ts +12 -0
  151. package/dist/lib/components/ui/layout/navigation-bar/partials/navigation-bar-item.d.ts +22 -0
  152. package/dist/lib/components/ui/layout/navigation-bar/partials/navigation-bar-more.d.ts +24 -0
  153. package/dist/lib/components/ui/layout/navigation-bar/use-nav-bar-inset.d.ts +12 -0
  154. package/dist/lib/components/ui/layout/navigation-drawer/index.d.ts +5 -0
  155. package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.classes.d.ts +9 -0
  156. package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.d.ts +45 -0
  157. package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.utils.d.ts +14 -0
  158. package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-body.d.ts +3 -0
  159. package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-item.d.ts +20 -0
  160. package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-modal.d.ts +17 -0
  161. package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-section.d.ts +15 -0
  162. package/dist/lib/components/ui/layout/navigation-rail/index.d.ts +3 -0
  163. package/dist/lib/components/ui/layout/navigation-rail/navigation-rail-panel.d.ts +26 -0
  164. package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.classes.d.ts +23 -0
  165. package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.d.ts +42 -1
  166. package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.utils.d.ts +4 -0
  167. package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-body.d.ts +3 -0
  168. package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-item.d.ts +20 -0
  169. package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-panel-item.d.ts +7 -0
  170. package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-toggle.d.ts +7 -0
  171. package/dist/lib/components/ui/layout/navigation-rail/use-rail-expanded.d.ts +13 -0
  172. package/dist/lib/components/ui/layout/navigation-toolbar/index.d.ts +2 -0
  173. package/dist/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.classes.d.ts +20 -0
  174. package/dist/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.d.ts +51 -0
  175. package/dist/lib/components/ui/layout/navigation-toolbar/partials/navigation-toolbar-item.d.ts +25 -0
  176. package/dist/lib/components/ui/layout/navigation-toolbar/partials/navigation-toolbar-more.d.ts +3 -0
  177. package/dist/lib/components/ui/layout/pane-layout/internal.d.ts +3 -0
  178. package/dist/lib/components/ui/layout/pane-layout/pane-layout.d.ts +16 -2
  179. package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-classes.d.ts +7 -0
  180. package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-context.d.ts +13 -0
  181. package/dist/lib/components/ui/layout/pane-layout/partials/use-pane-layout-state.d.ts +16 -0
  182. package/dist/lib/components/ui/layout/pane-layout/partials/use-pane-narrow.d.ts +6 -0
  183. package/dist/lib/components/ui/layout/responsive-grid/index.d.ts +4 -1
  184. package/dist/lib/components/ui/layout/responsive-grid/responsive-grid-item.d.ts +19 -0
  185. package/dist/lib/components/ui/layout/responsive-grid/responsive-grid-spans.d.ts +19 -0
  186. package/dist/lib/components/ui/layout/responsive-grid/responsive-grid.d.ts +24 -2
  187. package/dist/lib/components/ui/layout/responsive-grid/responsive-grid.utils.d.ts +12 -0
  188. package/dist/lib/components/ui/layout/settings-layout/index.d.ts +2 -0
  189. package/dist/lib/components/ui/layout/settings-layout/partials/settings-link.d.ts +7 -0
  190. package/dist/lib/components/ui/layout/settings-layout/partials/settings-list.d.ts +11 -0
  191. package/dist/lib/components/ui/layout/settings-layout/partials/settings-page.d.ts +5 -0
  192. package/dist/lib/components/ui/layout/settings-layout/settings-layout.d.ts +10 -0
  193. package/dist/lib/components/ui/layout/settings-layout/settings-layout.types.d.ts +33 -0
  194. package/dist/lib/components/ui/layout/settings-layout/settings-layout.utils.d.ts +13 -0
  195. package/dist/lib/components/ui/layout/thread-layout/thread-layout-classes.d.ts +17 -0
  196. package/dist/lib/components/ui/layout/thread-layout/thread-layout-parts.d.ts +14 -0
  197. package/dist/lib/components/ui/layout/thread-layout/thread-layout-stepper.d.ts +11 -0
  198. package/dist/lib/components/ui/layout/thread-layout/thread-layout.d.ts +30 -3
  199. package/dist/lib/components/ui/layout/top-nav/index.d.ts +5 -0
  200. package/dist/lib/components/ui/layout/top-nav/partials/top-nav-item.d.ts +23 -0
  201. package/dist/lib/components/ui/layout/top-nav/partials/top-nav-menu.d.ts +22 -0
  202. package/dist/lib/components/ui/layout/top-nav/top-nav.classes.d.ts +10 -0
  203. package/dist/lib/components/ui/layout/top-nav/top-nav.d.ts +34 -0
  204. package/dist/lib/components/ui/layout/top-nav/top-nav.utils.d.ts +10 -0
  205. package/dist/lib/components/ui/widgets/kanban/index.d.ts +1 -1
  206. package/dist/lib/components/ui/widgets/kanban/kanban-context.d.ts +5 -2
  207. package/dist/lib/components/ui/widgets/kanban/kanban.d.ts +11 -2
  208. package/dist/lib/components/ui/widgets/kanban/kanban.slice.d.ts +32 -0
  209. package/dist/lib/components/ui/widgets/kanban/kanban.utils.d.ts +9 -0
  210. package/dist/lib/components/ui/widgets/kanban/partials/kanban-card-preview.d.ts +3 -1
  211. package/dist/lib/components/ui/widgets/kanban/partials/kanban-column.d.ts +6 -0
  212. package/dist/lib/components/ui/widgets/kanban/partials/kanban-move-to.d.ts +13 -0
  213. package/dist/lib/components/ui/widgets/kanban/partials/kanban-phone-tabs.d.ts +10 -0
  214. package/dist/lib/components/ui/widgets/kanban/types.d.ts +7 -0
  215. package/dist/lib/components/ui/widgets/kanban/use-kanban-board-keys.d.ts +7 -0
  216. package/dist/lib/components/ui/widgets/kanban/use-kanban-item-actions.d.ts +5 -0
  217. package/dist/lib/components/ui/widgets/kanban/use-kanban-phone-column.d.ts +18 -0
  218. package/dist/lib/components/ui/widgets/kanban/use-kanban.d.ts +6 -1
  219. package/dist/lib/i18n/resources/en/en.d.ts +50 -0
  220. package/dist/lib/i18n/resources/en/material.json.d.ts +52 -2
  221. package/dist/lib/i18n/resources/fr/fr.d.ts +50 -0
  222. package/dist/lib/i18n/resources/fr/material.json.d.ts +52 -2
  223. package/dist/lib/i18n/resources/resources.d.ts +100 -0
  224. package/dist/lib/i18n/translator.d.ts +1 -1
  225. package/dist/lib.js +1 -1
  226. package/dist/main.js +52 -52
  227. package/dist/map.js +7 -7
  228. package/dist/material.css +1 -1
  229. package/dist/pdf-reader.js +1 -1
  230. package/dist/{percent-field--WIXV5dX.js → percent-field-C2LNr8NA.js} +1 -1
  231. package/dist/{picker-surface-CV09H7Nb.js → picker-surface-BUCFMoAZ.js} +4 -4
  232. package/dist/{popover-Cd7DkND4.js → popover-DFRwQF12.js} +1 -1
  233. package/dist/{pos-types-DKCkOTYy.js → pos-types-CXnEdjdy.js} +1 -1
  234. package/dist/{pos-variant-GrUfbTTP.js → pos-variant-DMwn_7is.js} +4 -4
  235. package/dist/print.js +1 -1
  236. package/dist/{programmer-variant-eKJNKENL.js → programmer-variant-Bo4eiDFi.js} +2 -2
  237. package/dist/{public-y6g4ddxd.js → public-CyQPkkSR.js} +9 -9
  238. package/dist/{rich-code-block-CRQ0iyLb.js → rich-code-block-iK7UcZdc.js} +5 -5
  239. package/dist/{scientific-variant-2y3UxAAU.js → scientific-variant-B9ScTgfx.js} +2 -2
  240. package/dist/{sortable-provider-DdiI3Fwk.js → sortable-provider-BZwVjSVU.js} +3 -3
  241. package/dist/{spinner-C1QIewWv.js → spinner-RITOxvpH.js} +2 -2
  242. package/dist/{tabs-KGKvve3y.js → tabs-DSo4eCUn.js} +1 -1
  243. package/dist/theme-picker.js +2 -2
  244. package/dist/{theme-toggle-C_VeiiCm.js → theme-toggle-3gbzQX0x.js} +1 -1
  245. package/dist/{toolbar-items-D310xDuM.js → toolbar-items-Bt5RK-Nx.js} +4 -4
  246. package/dist/{translate-material-BypJJ1l5.js → translate-material-CZTGqgen.js} +1 -1
  247. package/dist/{translator-DJkf4J7t.js → translator-DTlOlIoq.js} +108 -4
  248. package/dist/{use-chat-composer-BFhRMHcX.js → use-chat-composer-DBY01IpX.js} +16 -16
  249. package/dist/{use-heading-spy-DJR8YYm6.js → use-heading-spy-Dt48bBts.js} +4 -4
  250. package/dist/{use-material-translation-CD6QoVal.js → use-material-translation-BJucC_xe.js} +1 -1
  251. package/dist/{use-return-focus-Cevw_6yq.js → use-return-focus-BS1JLcnq.js} +1 -1
  252. package/docs/README.md +1 -0
  253. package/docs/components/data-display.md +4 -0
  254. package/docs/components/data-table-filter-and-lists.md +2 -0
  255. package/docs/components/navigation-and-layout.md +944 -27
  256. package/docs/components/rich-widgets.md +26 -0
  257. package/docs/guides/app-shell.md +641 -2
  258. package/docs/guides/i18n.md +13 -1
  259. package/docs/guides/layouts.md +215 -0
  260. package/docs/guides/ssr.md +1 -1
  261. package/docs/previews/navigation-layout-appshell-page-ground.webp +0 -0
  262. package/docs/previews/navigation-layout-appshell.webp +0 -0
  263. package/docs/previews/navigation-layout-authcard-split.webp +0 -0
  264. package/docs/previews/navigation-layout-authcard.webp +0 -0
  265. package/docs/previews/navigation-layout-dashboardgrid.webp +0 -0
  266. package/docs/previews/navigation-layout-editorlayout.webp +0 -0
  267. package/docs/previews/navigation-layout-landinglayout.webp +0 -0
  268. package/docs/previews/navigation-layout-panelayout.webp +0 -0
  269. package/docs/previews/navigation-layout-settingslayout.webp +0 -0
  270. package/docs/previews/navigation-layout-threadlayout-pages.webp +0 -0
  271. package/docs/previews/navigation-layout-threadlayout.webp +0 -0
  272. package/docs/roadmap.md +10 -2
  273. package/lib/components/components.css +3 -0
  274. package/lib/components/ui/actions/fab/fab-dock.css +19 -6
  275. package/lib/components/ui/layout/app-search/app-search.css +62 -0
  276. package/lib/components/ui/layout/app-shell/activity-backdrop.css +10 -2
  277. package/lib/components/ui/layout/app-shell/app-header.css +108 -0
  278. package/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.css +43 -0
  279. 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-responsivegrid.webp" width="96" alt=""> | [ResponsiveGrid](#responsivegrid) | Container-query grid, 1 to 5 columns, or `auto-fill` columns by a minimum tile width (`minItem`) |
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
- | | [NavigationRail](#navigationrail) | Placeholder, no props yet |
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. `usePageTitleFocus()` returns a function that focuses the app bar's page title (the content region without one) after a navigation; see the guide.
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
- Both are layout surfaces with no props of their own (they take a `div`'s):
342
- `TopAppBar` is a bordered row, `BottomAppBar` spaces its children and styles
343
- the one with `data-state="active"`.
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
  ![StickyActionBar](../previews/navigation-layout-stickyactionbar.webp)
@@ -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`, `stepper` | `ReactNode` | — | `ThreadLayout`: above the column, and directly above the content |
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
- `useListDetails()` and `usePaneLayout()` read the state from inside.
690
+ **A centred form or a wizard.** `ThreadLayout` is the page for both:
489
691
 
490
- **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. Leave `back` out and `ListDetailsTrigger view="list"` still works as before.
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
+ ![SettingsLayout](../previews/navigation-layout-settingslayout.webp)
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
+ ![LandingLayout](../previews/navigation-layout-landinglayout.webp)
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 two.
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
+ ![DashboardGrid](../previews/navigation-layout-dashboardgrid.webp)
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
+ ![EditorLayout](../previews/navigation-layout-editorlayout.webp)
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
  ![SectionColumns](../previews/navigation-layout-sectioncolumns.webp)
@@ -934,7 +1498,7 @@ Tooltip or Link wrapping a Button is not covered.
934
1498
 
935
1499
  ![Doc](../previews/navigation-layout-doc.webp)
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
- Placeholder: renders an empty rail container and takes no props yet.
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
- No preview: it renders an empty container, so there is nothing to show and
1029
- no story. Use [`Sidebar`](#sidebar) collapsed for a rail of icons today.
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 { NavigationRail } from '@nxgt/material/components';
1787
+ import { NavigationToolbar } from '@nxgt/material/components';
1033
1788
 
1034
- <NavigationRail />
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
- **Storybook:** — · **Vue:** `NavigationRail`
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:** —