@nxgt/material 3.79.1 → 3.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +48 -8
  3. package/dist/{alert-dialog-653apk3f.js → alert-dialog-DOZ0SfQs.js} +1 -1
  4. package/dist/{async-slice-CXLbQuOc.js → async-slice-BfVokA46.js} +1 -1
  5. package/dist/{button-3op3rtZh.js → button-CSKUtMI3.js} +2 -2
  6. package/dist/{calculator-KqIMBXSh.js → calculator-Cv00UQgR.js} +10 -10
  7. package/dist/calculator-field.js +6 -6
  8. package/dist/{calculator-segment-BlJE1LaS.js → calculator-segment-CwnC2cV_.js} +1 -1
  9. package/dist/calculator.js +1 -1
  10. package/dist/charts.js +3 -3
  11. package/dist/{chip-MuKDwtkG.js → chip-BGn0V6Pt.js} +3 -3
  12. package/dist/{code-editor-Bp2RrdNx.js → code-editor-B25-mWhb.js} +4 -4
  13. package/dist/code-editor.js +4 -4
  14. package/dist/{code-language-items-B7WR5ua_.js → code-language-items-oRjtO3qV.js} +2 -2
  15. package/dist/components/base-content.css +11 -0
  16. package/dist/components/components.css +5 -0
  17. package/dist/components/ui/actions/fab/fab-dock.css +19 -6
  18. package/dist/components/ui/collections/data-table/data-table-items-tiles.css +27 -1
  19. package/dist/components/ui/display/tube-cap/tube-cap.css +87 -0
  20. package/dist/components/ui/layout/app-search/app-search.css +62 -0
  21. package/dist/components/ui/layout/app-shell/activity-backdrop.css +10 -2
  22. package/dist/components/ui/layout/app-shell/app-header.css +108 -0
  23. package/dist/components/ui/layout/navigation-toolbar/navigation-toolbar.css +43 -0
  24. package/dist/{components-CTyNC_zW.js → components-B00kDHzJ.js} +23101 -18734
  25. package/dist/components.js +37 -37
  26. package/dist/{conversion-variant-BDFAxqe9.js → conversion-variant-wZd-G6xR.js} +3 -3
  27. package/dist/{currency-field-DWHB5oms.js → currency-field-DizBTAat.js} +4 -4
  28. package/dist/{date.utils-DUvqcjan.js → date.utils-JTEIo3mM.js} +98 -98
  29. package/dist/{dialog-Bm5LvVlB.js → dialog-B-3s-oXt.js} +3 -3
  30. package/dist/dnd.js +2 -2
  31. package/dist/{dropdown-menu-xlZ6NYUA.js → dropdown-menu-C6rcYGB9.js} +1 -1
  32. package/dist/editor.js +19 -19
  33. package/dist/{empty-state-C0kviQdW.js → empty-state-CSkghnD9.js} +1 -1
  34. package/dist/{financial-variant-CggTpad2.js → financial-variant-igpGRbGw.js} +5 -5
  35. package/dist/{hooks-Dg94jPRq.js → hooks-DvIsom2p.js} +2 -2
  36. package/dist/hooks.js +9 -8
  37. package/dist/{hover-card-B1g55Hi6.js → hover-card-DuaVVKMb.js} +1 -1
  38. package/dist/i18n.js +3 -3
  39. package/dist/{icon-badge-BcW66bRk.js → icon-badge-3ThP6EOl.js} +1 -1
  40. package/dist/{icon-button-Cogxy1cy.js → icon-button-4PKlaI1g.js} +2 -2
  41. package/dist/lib/components/ui/actions/button/button-tones.d.ts +17 -16
  42. package/dist/lib/components/ui/collections/day-grouped-list/day-grouped-list.d.ts +6 -1
  43. package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +2 -0
  44. package/dist/lib/components/ui/collections/server-table/types-states.d.ts +5 -1
  45. package/dist/lib/components/ui/display/index.d.ts +1 -0
  46. package/dist/lib/components/ui/display/tube-cap/index.d.ts +2 -0
  47. package/dist/lib/components/ui/display/tube-cap/tube-cap.d.ts +27 -0
  48. package/dist/lib/components/ui/display/tube-cap/tube-cap.kinds.d.ts +7 -0
  49. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  50. package/dist/lib/components/ui/fields/repeatable-group/partials/repeatable-group-row.d.ts +3 -1
  51. package/dist/lib/components/ui/fields/repeatable-group/partials/repeatable-group-rows.d.ts +2 -1
  52. package/dist/lib/components/ui/fields/repeatable-group/repeatable-group.d.ts +1 -1
  53. package/dist/lib/components/ui/fields/repeatable-group/types.d.ts +7 -0
  54. package/dist/lib/components/ui/layout/app-bar/top-app-bar.d.ts +18 -3
  55. package/dist/lib/components/ui/layout/app-search/app-search-bar.d.ts +11 -0
  56. package/dist/lib/components/ui/layout/app-search/app-search.types.d.ts +47 -0
  57. package/dist/lib/components/ui/layout/app-search/index.d.ts +3 -0
  58. package/dist/lib/components/ui/layout/app-search/partials/on-phone.d.ts +2 -0
  59. package/dist/lib/components/ui/layout/app-search/partials/phone-search.d.ts +3 -0
  60. package/dist/lib/components/ui/layout/app-search/partials/pill-search.d.ts +3 -0
  61. package/dist/lib/components/ui/layout/app-search/pill-form.d.ts +18 -0
  62. package/dist/lib/components/ui/layout/app-search/search-panel.d.ts +24 -0
  63. package/dist/lib/components/ui/layout/app-search/search-results.d.ts +14 -0
  64. package/dist/lib/components/ui/layout/app-search/search-shortcut.d.ts +15 -0
  65. package/dist/lib/components/ui/layout/app-search/search-view.d.ts +22 -0
  66. package/dist/lib/components/ui/layout/app-search/use-pill-panel.d.ts +22 -0
  67. package/dist/lib/components/ui/layout/app-search/use-search-text.d.ts +8 -0
  68. package/dist/lib/components/ui/layout/app-shell/activity-app-bar.d.ts +35 -0
  69. package/dist/lib/components/ui/layout/app-shell/activity-chrome.d.ts +26 -3
  70. package/dist/lib/components/ui/layout/app-shell/activity-column-props.d.ts +22 -0
  71. package/dist/lib/components/ui/layout/app-shell/activity-column.d.ts +10 -2
  72. package/dist/lib/components/ui/layout/app-shell/activity-layout-navigation.types.d.ts +94 -0
  73. package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +8 -1
  74. package/dist/lib/components/ui/layout/app-shell/activity-layout.types.d.ts +2 -1
  75. package/dist/lib/components/ui/layout/app-shell/activity-rails.d.ts +16 -0
  76. package/dist/lib/components/ui/layout/app-shell/activity-sidebar-lane.d.ts +22 -0
  77. package/dist/lib/components/ui/layout/app-shell/activity-top-nav.d.ts +11 -0
  78. package/dist/lib/components/ui/layout/app-shell/activity-two-level-lane.d.ts +14 -0
  79. package/dist/lib/components/ui/layout/app-shell/app-back.d.ts +22 -0
  80. package/dist/lib/components/ui/layout/app-shell/app-header-row.d.ts +3 -0
  81. package/dist/lib/components/ui/layout/app-shell/app-header.d.ts +33 -1
  82. package/dist/lib/components/ui/layout/app-shell/app-shell.utils.d.ts +8 -0
  83. package/dist/lib/components/ui/layout/app-shell/app-sidebar-entries.d.ts +13 -0
  84. package/dist/lib/components/ui/layout/app-shell/app-sidebar-label-item.d.ts +3 -1
  85. package/dist/lib/components/ui/layout/app-shell/bottom-chrome.d.ts +13 -0
  86. package/dist/lib/components/ui/layout/app-shell/header-title.d.ts +18 -2
  87. package/dist/lib/components/ui/layout/app-shell/header.utils.d.ts +37 -0
  88. package/dist/lib/components/ui/layout/app-shell/index.d.ts +3 -0
  89. package/dist/lib/components/ui/layout/app-shell/navigation-parts.d.ts +38 -0
  90. package/dist/lib/components/ui/layout/app-shell/navigation.utils.d.ts +36 -0
  91. package/dist/lib/components/ui/layout/app-shell/page-title-scope.d.ts +14 -0
  92. package/dist/lib/components/ui/layout/app-shell/page-title.d.ts +12 -1
  93. package/dist/lib/components/ui/layout/app-shell/shell-scroll.d.ts +32 -0
  94. package/dist/lib/components/ui/layout/app-shell/sticky-offset.d.ts +8 -0
  95. package/dist/lib/components/ui/layout/app-shell/types.d.ts +14 -0
  96. package/dist/lib/components/ui/layout/app-shell/use-focus-while-hidden.d.ts +9 -0
  97. package/dist/lib/components/ui/layout/app-shell/use-header-height.d.ts +12 -0
  98. package/dist/lib/components/ui/layout/app-shell/use-header-scroll.d.ts +19 -0
  99. package/dist/lib/components/ui/layout/app-shell/use-shell-header.d.ts +20 -0
  100. package/dist/lib/components/ui/layout/auth-card/auth-card-split.d.ts +14 -0
  101. package/dist/lib/components/ui/layout/auth-card/auth-card.d.ts +13 -1
  102. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid-context.d.ts +21 -0
  103. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.d.ts +9 -0
  104. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.state.d.ts +30 -0
  105. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.tiles.d.ts +8 -0
  106. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.types.d.ts +50 -0
  107. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.utils.d.ts +70 -0
  108. package/dist/lib/components/ui/layout/dashboard-grid/dashboard-tile.d.ts +7 -0
  109. package/dist/lib/components/ui/layout/dashboard-grid/index.d.ts +4 -0
  110. package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-announcer.d.ts +6 -0
  111. package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-edit-bar.d.ts +11 -0
  112. package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-move-buttons.d.ts +9 -0
  113. package/dist/lib/components/ui/layout/dashboard-grid/partials/dashboard-toolbar.d.ts +7 -0
  114. package/dist/lib/components/ui/layout/dashboard-grid/use-dashboard-grid.d.ts +21 -0
  115. package/dist/lib/components/ui/layout/dashboard-grid/use-dashboard-moves.d.ts +22 -0
  116. package/dist/lib/components/ui/layout/doc/doc-context.d.ts +2 -0
  117. package/dist/lib/components/ui/layout/doc/doc.d.ts +5 -0
  118. package/dist/lib/components/ui/layout/doc/index.d.ts +1 -1
  119. package/dist/lib/components/ui/layout/doc/partials/doc-embedded-bar.d.ts +9 -0
  120. package/dist/lib/components/ui/layout/doc/partials/doc-embedded.d.ts +8 -0
  121. package/dist/lib/components/ui/layout/doc/use-doc.d.ts +9 -1
  122. package/dist/lib/components/ui/layout/editor-layout/editor-layout.d.ts +14 -0
  123. package/dist/lib/components/ui/layout/editor-layout/editor-layout.types.d.ts +31 -0
  124. package/dist/lib/components/ui/layout/editor-layout/index.d.ts +4 -0
  125. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-body.d.ts +11 -0
  126. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-canvas.d.ts +8 -0
  127. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-context.d.ts +12 -0
  128. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-start.d.ts +8 -0
  129. package/dist/lib/components/ui/layout/editor-layout/partials/editor-layout-triggers.d.ts +10 -0
  130. package/dist/lib/components/ui/layout/editor-layout/partials/use-editor-layout-names.d.ts +8 -0
  131. package/dist/lib/components/ui/layout/editor-layout/partials/use-editor-layout-state.d.ts +21 -0
  132. package/dist/lib/components/ui/layout/index.d.ts +9 -0
  133. package/dist/lib/components/ui/layout/landing/index.d.ts +6 -0
  134. package/dist/lib/components/ui/layout/landing/landing-layout.d.ts +15 -0
  135. package/dist/lib/components/ui/layout/landing/landing-layout.types.d.ts +27 -0
  136. package/dist/lib/components/ui/layout/landing/landing-section.d.ts +12 -0
  137. package/dist/lib/components/ui/layout/landing/landing-section.types.d.ts +32 -0
  138. package/dist/lib/components/ui/layout/landing/landing.classes.d.ts +20 -0
  139. package/dist/lib/components/ui/layout/landing/partials/landing-header.d.ts +11 -0
  140. package/dist/lib/components/ui/layout/landing/partials/landing-section-intro.d.ts +13 -0
  141. package/dist/lib/components/ui/layout/landing/partials/site-footer-group.d.ts +8 -0
  142. package/dist/lib/components/ui/layout/landing/site-footer.d.ts +13 -0
  143. package/dist/lib/components/ui/layout/landing/site-footer.types.d.ts +29 -0
  144. package/dist/lib/components/ui/layout/list-details-layout/internal.d.ts +2 -0
  145. package/dist/lib/components/ui/layout/list-details-layout/list-details-layout.d.ts +9 -17
  146. package/dist/lib/components/ui/layout/list-details-layout/partials/history-mark.d.ts +2 -0
  147. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-context.d.ts +17 -1
  148. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-history.d.ts +8 -0
  149. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-inspector.d.ts +21 -0
  150. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-layout.types.d.ts +47 -0
  151. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-resizer.d.ts +9 -0
  152. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-root.d.ts +3 -0
  153. package/dist/lib/components/ui/layout/list-details-layout/partials/list-details-trigger.d.ts +13 -4
  154. package/dist/lib/components/ui/layout/list-details-layout/partials/use-inspector-focus.d.ts +8 -0
  155. package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-details-param.d.ts +26 -0
  156. package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-details-state.d.ts +15 -0
  157. package/dist/lib/components/ui/layout/list-details-layout/partials/use-list-width.d.ts +16 -0
  158. package/dist/lib/components/ui/layout/list-details-layout/partials/use-pane-focus.d.ts +6 -4
  159. package/dist/lib/components/ui/layout/navigation-bar/index.d.ts +3 -0
  160. package/dist/lib/components/ui/layout/navigation-bar/internal.d.ts +4 -0
  161. package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.classes.d.ts +14 -0
  162. package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.d.ts +35 -0
  163. package/dist/lib/components/ui/layout/navigation-bar/navigation-bar.utils.d.ts +19 -0
  164. package/dist/lib/components/ui/layout/navigation-bar/partials/bar-badge.d.ts +5 -0
  165. package/dist/lib/components/ui/layout/navigation-bar/partials/bar-entry.d.ts +12 -0
  166. package/dist/lib/components/ui/layout/navigation-bar/partials/navigation-bar-item.d.ts +22 -0
  167. package/dist/lib/components/ui/layout/navigation-bar/partials/navigation-bar-more.d.ts +24 -0
  168. package/dist/lib/components/ui/layout/navigation-bar/use-nav-bar-inset.d.ts +12 -0
  169. package/dist/lib/components/ui/layout/navigation-drawer/index.d.ts +5 -0
  170. package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.classes.d.ts +9 -0
  171. package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.d.ts +45 -0
  172. package/dist/lib/components/ui/layout/navigation-drawer/navigation-drawer.utils.d.ts +14 -0
  173. package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-body.d.ts +3 -0
  174. package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-item.d.ts +20 -0
  175. package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-modal.d.ts +17 -0
  176. package/dist/lib/components/ui/layout/navigation-drawer/partials/navigation-drawer-section.d.ts +15 -0
  177. package/dist/lib/components/ui/layout/navigation-rail/index.d.ts +3 -0
  178. package/dist/lib/components/ui/layout/navigation-rail/navigation-rail-panel.d.ts +26 -0
  179. package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.classes.d.ts +23 -0
  180. package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.d.ts +42 -1
  181. package/dist/lib/components/ui/layout/navigation-rail/navigation-rail.utils.d.ts +4 -0
  182. package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-body.d.ts +3 -0
  183. package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-item.d.ts +20 -0
  184. package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-panel-item.d.ts +7 -0
  185. package/dist/lib/components/ui/layout/navigation-rail/partials/navigation-rail-toggle.d.ts +7 -0
  186. package/dist/lib/components/ui/layout/navigation-rail/use-rail-expanded.d.ts +13 -0
  187. package/dist/lib/components/ui/layout/navigation-toolbar/index.d.ts +2 -0
  188. package/dist/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.classes.d.ts +20 -0
  189. package/dist/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.d.ts +51 -0
  190. package/dist/lib/components/ui/layout/navigation-toolbar/partials/navigation-toolbar-item.d.ts +25 -0
  191. package/dist/lib/components/ui/layout/navigation-toolbar/partials/navigation-toolbar-more.d.ts +3 -0
  192. package/dist/lib/components/ui/layout/pane-layout/internal.d.ts +3 -0
  193. package/dist/lib/components/ui/layout/pane-layout/pane-layout.d.ts +16 -2
  194. package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-classes.d.ts +7 -0
  195. package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-context.d.ts +13 -0
  196. package/dist/lib/components/ui/layout/pane-layout/partials/use-pane-layout-state.d.ts +16 -0
  197. package/dist/lib/components/ui/layout/pane-layout/partials/use-pane-narrow.d.ts +6 -0
  198. package/dist/lib/components/ui/layout/responsive-grid/index.d.ts +4 -1
  199. package/dist/lib/components/ui/layout/responsive-grid/responsive-grid-item.d.ts +19 -0
  200. package/dist/lib/components/ui/layout/responsive-grid/responsive-grid-spans.d.ts +19 -0
  201. package/dist/lib/components/ui/layout/responsive-grid/responsive-grid.d.ts +24 -2
  202. package/dist/lib/components/ui/layout/responsive-grid/responsive-grid.utils.d.ts +12 -0
  203. package/dist/lib/components/ui/layout/settings-layout/index.d.ts +2 -0
  204. package/dist/lib/components/ui/layout/settings-layout/partials/settings-link.d.ts +7 -0
  205. package/dist/lib/components/ui/layout/settings-layout/partials/settings-list.d.ts +11 -0
  206. package/dist/lib/components/ui/layout/settings-layout/partials/settings-page.d.ts +5 -0
  207. package/dist/lib/components/ui/layout/settings-layout/settings-layout.d.ts +10 -0
  208. package/dist/lib/components/ui/layout/settings-layout/settings-layout.types.d.ts +33 -0
  209. package/dist/lib/components/ui/layout/settings-layout/settings-layout.utils.d.ts +13 -0
  210. package/dist/lib/components/ui/layout/thread-layout/thread-layout-classes.d.ts +17 -0
  211. package/dist/lib/components/ui/layout/thread-layout/thread-layout-parts.d.ts +14 -0
  212. package/dist/lib/components/ui/layout/thread-layout/thread-layout-stepper.d.ts +11 -0
  213. package/dist/lib/components/ui/layout/thread-layout/thread-layout.d.ts +30 -3
  214. package/dist/lib/components/ui/layout/top-nav/index.d.ts +5 -0
  215. package/dist/lib/components/ui/layout/top-nav/partials/top-nav-item.d.ts +23 -0
  216. package/dist/lib/components/ui/layout/top-nav/partials/top-nav-menu.d.ts +22 -0
  217. package/dist/lib/components/ui/layout/top-nav/top-nav.classes.d.ts +10 -0
  218. package/dist/lib/components/ui/layout/top-nav/top-nav.d.ts +34 -0
  219. package/dist/lib/components/ui/layout/top-nav/top-nav.utils.d.ts +10 -0
  220. package/dist/lib/components/ui/media/galleries/gallery.d.ts +1 -1
  221. package/dist/lib/components/ui/media/galleries/lightbox-gallery.d.ts +1 -1
  222. package/dist/lib/components/ui/media/galleries/mini-gallery.d.ts +1 -1
  223. package/dist/lib/components/ui/overlays/dialog/dialog-parts.d.ts +1 -1
  224. package/dist/lib/components/ui/overlays/sheet/sheet-parts.d.ts +5 -0
  225. package/dist/lib/components/ui/selection/choice-card-group/choice-card-balance.d.ts +4 -0
  226. package/dist/lib/components/ui/selection/choice-card-group/choice-card-group.d.ts +3 -1
  227. package/dist/lib/components/ui/selection/choice-card-group/choice-card-group.types.d.ts +7 -0
  228. package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-body.d.ts +5 -1
  229. package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-row-parts.d.ts +2 -1
  230. package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-row-props.d.ts +8 -5
  231. package/dist/lib/components/ui/selection/sortable-list/partials/use-owed-focus.d.ts +10 -0
  232. package/dist/lib/components/ui/selection/sortable-list/sortable-list-context.d.ts +2 -0
  233. package/dist/lib/components/ui/selection/sortable-list/sortable-list.keyboard.d.ts +1 -1
  234. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-context.d.ts +3 -0
  235. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-shell.d.ts +15 -0
  236. package/dist/lib/components/ui/widgets/event-calendar/event-calendar-template.d.ts +9 -0
  237. package/dist/lib/components/ui/widgets/event-calendar/event-calendar.d.ts +7 -3
  238. package/dist/lib/components/ui/widgets/event-calendar/event-calendar.template.d.ts +41 -0
  239. package/dist/lib/components/ui/widgets/event-calendar/index.d.ts +1 -0
  240. package/dist/lib/components/ui/widgets/event-calendar/partials/calendar-date-nav.d.ts +2 -0
  241. package/dist/lib/components/ui/widgets/event-calendar/partials/calendar-lane-toggle.d.ts +5 -0
  242. package/dist/lib/components/ui/widgets/event-calendar/partials/time-grid-header-template.d.ts +7 -0
  243. package/dist/lib/components/ui/widgets/event-calendar/template.types.d.ts +73 -0
  244. package/dist/lib/components/ui/widgets/event-calendar/types.d.ts +2 -0
  245. package/dist/lib/components/ui/widgets/event-calendar/use-chip-event.d.ts +1 -0
  246. package/dist/lib/components/ui/widgets/event-calendar/use-drawn-events.d.ts +4 -3
  247. package/dist/lib/components/ui/widgets/event-calendar/use-template-calendar.d.ts +18 -0
  248. package/dist/lib/components/ui/widgets/event-calendar/use-template-lane.d.ts +17 -0
  249. package/dist/lib/components/ui/widgets/kanban/index.d.ts +1 -1
  250. package/dist/lib/components/ui/widgets/kanban/kanban-context.d.ts +5 -2
  251. package/dist/lib/components/ui/widgets/kanban/kanban.d.ts +11 -2
  252. package/dist/lib/components/ui/widgets/kanban/kanban.slice.d.ts +32 -0
  253. package/dist/lib/components/ui/widgets/kanban/kanban.utils.d.ts +9 -0
  254. package/dist/lib/components/ui/widgets/kanban/partials/kanban-card-preview.d.ts +3 -1
  255. package/dist/lib/components/ui/widgets/kanban/partials/kanban-column.d.ts +6 -0
  256. package/dist/lib/components/ui/widgets/kanban/partials/kanban-move-to.d.ts +13 -0
  257. package/dist/lib/components/ui/widgets/kanban/partials/kanban-phone-tabs.d.ts +10 -0
  258. package/dist/lib/components/ui/widgets/kanban/types.d.ts +7 -0
  259. package/dist/lib/components/ui/widgets/kanban/use-kanban-board-keys.d.ts +7 -0
  260. package/dist/lib/components/ui/widgets/kanban/use-kanban-item-actions.d.ts +5 -0
  261. package/dist/lib/components/ui/widgets/kanban/use-kanban-phone-column.d.ts +18 -0
  262. package/dist/lib/components/ui/widgets/kanban/use-kanban.d.ts +6 -1
  263. package/dist/lib/hooks/index.d.ts +1 -0
  264. package/dist/lib/i18n/resources/en/en.d.ts +52 -0
  265. package/dist/lib/i18n/resources/en/material.json.d.ts +54 -2
  266. package/dist/lib/i18n/resources/fr/fr.d.ts +52 -0
  267. package/dist/lib/i18n/resources/fr/material.json.d.ts +54 -2
  268. package/dist/lib/i18n/resources/resources.d.ts +104 -0
  269. package/dist/lib/i18n/translator.d.ts +1 -1
  270. package/dist/lib.js +2 -2
  271. package/dist/main.js +53 -52
  272. package/dist/map.js +7 -7
  273. package/dist/material.css +1 -1
  274. package/dist/pdf-reader.js +1 -1
  275. package/dist/{percent-field--WIXV5dX.js → percent-field-QwU1l2Q4.js} +1 -1
  276. package/dist/{picker-surface-CV09H7Nb.js → picker-surface-CiTycWgV.js} +4 -4
  277. package/dist/{popover-Cd7DkND4.js → popover-YmbzbKQG.js} +1 -1
  278. package/dist/{pos-types-DKCkOTYy.js → pos-types-BFrapNmx.js} +1 -1
  279. package/dist/{pos-variant-GrUfbTTP.js → pos-variant-CctfqieO.js} +4 -4
  280. package/dist/print.js +1 -1
  281. package/dist/{programmer-variant-eKJNKENL.js → programmer-variant-Dd0YOixn.js} +2 -2
  282. package/dist/{public-y6g4ddxd.js → public-CshR_aW7.js} +10 -10
  283. package/dist/{rich-code-block-CRQ0iyLb.js → rich-code-block-BuJLDoBu.js} +5 -5
  284. package/dist/{scientific-variant-2y3UxAAU.js → scientific-variant-CjErQxuZ.js} +2 -2
  285. package/dist/{sortable-provider-DdiI3Fwk.js → sortable-provider-DU2nvpFc.js} +3 -3
  286. package/dist/{spinner-C1QIewWv.js → spinner-DH1mm8hH.js} +2 -2
  287. package/dist/{tabs-KGKvve3y.js → tabs-Um0SFyf1.js} +1 -1
  288. package/dist/theme-picker.js +2 -2
  289. package/dist/{theme-toggle-C_VeiiCm.js → theme-toggle-C-Ih4uQU.js} +1 -1
  290. package/dist/{toolbar-items-D310xDuM.js → toolbar-items-BoEY5bkt.js} +4 -4
  291. package/dist/{translate-material-BypJJ1l5.js → translate-material-D4rn5UaQ.js} +1 -1
  292. package/dist/{translator-DJkf4J7t.js → translator-CcEgQ-H7.js} +112 -4
  293. package/dist/{use-chat-composer-BFhRMHcX.js → use-chat-composer-BBF1BI_s.js} +47 -43
  294. package/dist/{use-click-lock-BZYtTmVt.js → use-click-lock-Bwg_caHO.js} +14 -14
  295. package/dist/{use-heading-spy-DJR8YYm6.js → use-heading-spy-BgyA6SJl.js} +19 -19
  296. package/dist/{use-material-translation-CD6QoVal.js → use-material-translation-DYrfV_JJ.js} +1 -1
  297. package/dist/{use-return-focus-Cevw_6yq.js → use-return-focus-C_Qz_yTk.js} +1 -1
  298. package/docs/README.md +1 -0
  299. package/docs/components/data-display.md +40 -8
  300. package/docs/components/data-table-filter-and-lists.md +7 -3
  301. package/docs/components/form-fields.md +1 -0
  302. package/docs/components/media-and-files.md +1 -0
  303. package/docs/components/navigation-and-layout.md +946 -28
  304. package/docs/components/overlays-and-menus.md +11 -5
  305. package/docs/components/rich-widgets.md +111 -1
  306. package/docs/components/selection-and-choice.md +49 -10
  307. package/docs/guides/app-shell.md +641 -2
  308. package/docs/guides/forms.md +7 -0
  309. package/docs/guides/helpers.md +23 -0
  310. package/docs/guides/i18n.md +13 -1
  311. package/docs/guides/layouts.md +215 -0
  312. package/docs/guides/ssr.md +1 -1
  313. package/docs/guides/theming.md +7 -7
  314. package/docs/previews/data-display-tubecap.webp +0 -0
  315. package/docs/previews/navigation-layout-appshell-page-ground.webp +0 -0
  316. package/docs/previews/navigation-layout-appshell.webp +0 -0
  317. package/docs/previews/navigation-layout-authcard-split.webp +0 -0
  318. package/docs/previews/navigation-layout-authcard.webp +0 -0
  319. package/docs/previews/navigation-layout-dashboardgrid.webp +0 -0
  320. package/docs/previews/navigation-layout-editorlayout.webp +0 -0
  321. package/docs/previews/navigation-layout-landinglayout.webp +0 -0
  322. package/docs/previews/navigation-layout-panelayout.webp +0 -0
  323. package/docs/previews/navigation-layout-settingslayout.webp +0 -0
  324. package/docs/previews/navigation-layout-threadlayout-pages.webp +0 -0
  325. package/docs/previews/navigation-layout-threadlayout.webp +0 -0
  326. package/docs/previews/selection-choice-sortablelist.webp +0 -0
  327. package/docs/roadmap.md +12 -2
  328. package/docs/troubleshooting.md +50 -2
  329. package/lib/components/base-content.css +11 -0
  330. package/lib/components/components.css +5 -0
  331. package/lib/components/ui/actions/fab/fab-dock.css +19 -6
  332. package/lib/components/ui/collections/data-table/data-table-items-tiles.css +27 -1
  333. package/lib/components/ui/display/tube-cap/tube-cap.css +87 -0
  334. package/lib/components/ui/layout/app-search/app-search.css +62 -0
  335. package/lib/components/ui/layout/app-shell/activity-backdrop.css +10 -2
  336. package/lib/components/ui/layout/app-shell/app-header.css +108 -0
  337. package/lib/components/ui/layout/navigation-toolbar/navigation-toolbar.css +43 -0
  338. package/lib/themes/ambre.css +1 -1
  339. package/lib/themes/ardoise.css +1 -1
  340. package/lib/themes/bleumono.css +1 -1
  341. package/lib/themes/bluemoon.css +1 -1
  342. package/lib/themes/corail.css +2 -2
  343. package/lib/themes/foret.css +1 -1
  344. package/lib/themes/lavande.css +1 -1
  345. package/lib/themes/menthe.css +2 -2
  346. package/lib/themes/ocean.css +1 -1
  347. package/lib/themes/pink.css +2 -2
  348. package/lib/themes/prune.css +1 -1
  349. package/lib/themes/sable.css +1 -1
  350. package/lib/themes/slate.css +1 -1
  351. package/lib/themes/teal.css +2 -2
  352. package/package.json +2 -2
@@ -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)
@@ -375,8 +536,9 @@ the page. It takes a `div`'s props.
375
536
  `Button`; a second filled `Button` among the children warns once in
376
537
  development, make the others `outlined` or `tonal`.
377
538
  - **Surface.** Below `md` the bar paints `--background` (it is stuck over the content); from `md`, and with `sticky={false}`, it paints nothing and takes its parent's surface, such as a `Card`.
539
+ - **Edges.** The bar adds no horizontal padding: its leading and trailing edges are those of the column it sits in, sticky or inline, so its buttons line up with the cards and fields above. Pad the column, not the bar.
378
540
  - **Safe areas.** Under `viewport-fit=cover` the bar pads clear of
379
- `env(safe-area-inset-left | right | bottom)`, as classes.
541
+ `env(safe-area-inset-left | right | bottom)` (the sides only below `md`), as classes.
380
542
  - **Keyboard.** From the visual viewport (`visualViewport`), the bar rises by
381
543
  the height the on-screen keyboard covers (`--nxgt-keyboard-inset` on the bar,
382
544
  `data-keyboard="open"` while it is open) and drops the bottom safe-area
@@ -468,30 +630,259 @@ open the pane (`ListDetailsTrigger` takes the `view` to show), and their
468
630
  `children` / `className` can be functions of the layout state. A server, which
469
631
  has no screen width, renders the wide layout, as does a hydrating client; a
470
632
  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
633
+ CSS at 648 px and narrower (768 px with `phone` set), so a phone never sees it flash before the sheet
472
634
  takes over. A page rendered only in the browser starts on the real layout.
635
+
636
+ `PaneLayout` takes three opt-in props for how the pane behaves on a phone
637
+ (#2103); left unset, nothing above changes:
638
+
639
+ ```tsx
640
+ <PaneLayout phone="bottom" breakpoint="container" paneWidth="lg" paneSide="right">
641
+ <PaneLayout.Content>
642
+ <PaneLayout.Trigger aria-label="Filters" className={({ isMobile }) => (isMobile ? '' : 'hidden')}>…</PaneLayout.Trigger>
643
+ {results}
644
+ </PaneLayout.Content>
645
+ <PaneLayout.Pane sheetTitle="Filters">{filters}</PaneLayout.Pane>
646
+ </PaneLayout>
647
+ ```
648
+
649
+ - `phone`: `'sheet'` (side sheet), `'bottom'` (bottom sheet) or `'stack'` (the
650
+ pane sits under the content, no overlay; the trigger shows or hides it).
651
+ Once set, the switch is at 768 px (`md`) instead of 648 px. The sheets are
652
+ the library `Sheet`: focus moves in, Escape and the close button close it,
653
+ and focus returns to the trigger.
654
+ - `breakpoint`: `'viewport'` (default) or `'container'`, which measures the
655
+ `PaneLayout` itself (a CSS container, 48rem = 768 px), so a layout in a
656
+ narrow column of a wide screen gets the phone layout. The root becomes the
657
+ container and the flex row moves to an inner `data-slot="pane-layout-body"`.
658
+ - `paneWidth`: `'sm'` (16rem), `'md'` (20rem, default) or `'lg'` (24rem) for
659
+ the inline pane; no style attribute, so it holds under a strict CSP.
660
+ - With `phone` or `breakpoint` set, the inline pane is an `<aside>` named by
661
+ `sheetTitle` (default `Side Panel`), a landmark; the historical markup is a
662
+ `div`. `usePaneLayout()` and the function children also get `phone`,
663
+ `breakpoint` and `paneWidth`.
473
664
  `ThreadLayout` is a centred column over the app
474
665
  [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.
666
+ 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
667
 
477
668
  | Prop | Type | Default | Effect |
478
669
  | --- | --- | --- | --- |
479
670
  | `activeView` / `defaultActiveView` / `onActiveViewChange` | `'list' \| 'details'` | `'list'` | `ListDetailsLayout`: which view a phone shows |
671
+ | `phone` | `'sheet' \| 'bottom' \| 'stack'` | unset | `PaneLayout`: how the pane shows below 768 px (unset: side sheet at 648 px and narrower) |
672
+ | `paneWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | `PaneLayout`: width of the inline pane |
480
673
  | `listPosition` | `'left' \| 'right'` | `'left'` | `ListDetailsLayout` |
481
674
  | `breakpoint` | `'viewport' \| 'container'` | `'viewport'` | `ListDetailsLayout`: what the 648 px phone layout follows, the window or the layout's own width |
675
+ | `breakpoint` | `'viewport' \| 'container'` | `'viewport'` | `PaneLayout`: measure the viewport or the layout's own width |
676
+ | `inspector` | `boolean` | `false` | `ListDetailsLayout`: a third pane (`ListDetailsLayout.Inspector`); `activeView` then also takes `'inspector'`; `ListDetailsLayout.Trigger view="inspector"` needs `inspector` as well |
677
+ | `history` / `historyParam` | `'push' \| 'replace'` / `string` | — / `'view'` | `ListDetailsLayout`: keep the view in the URL (`?view=details`), through React Router |
678
+ | `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
679
  | `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
680
  | `open` / `defaultOpen` / `onOpenChange` | `boolean` | `false` | `PaneLayout`: pane shown |
484
681
  | `paneSide` | `'left' \| 'right'` | `'left'` | `PaneLayout` |
485
682
  | `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 |
683
+ | `header` | `ReactNode` | — | `ThreadLayout`: above the column |
684
+ | `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'` |
685
+ | `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) |
686
+ | `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 |
687
+ | `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 |
688
+
689
+ `useListDetails()` and `usePaneLayout()` read the state from inside (`useListDetails()` also gives `inspectorOpen`, `listWidth` and `setListWidth`).
487
690
 
488
- `useListDetails()` and `usePaneLayout()` read the state from inside.
691
+ **A centred form or a wizard.** `ThreadLayout` is the page for both:
489
692
 
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.
693
+ ```tsx
694
+ <ThreadLayout
695
+ width="form"
696
+ stepperPlacement="side"
697
+ stepper={() => <Stepper steps={steps} active={step} orientation="vertical" />}
698
+ footer={<><Button variant="ghost">Back</Button><Button>Next</Button></>}
699
+ >
700
+ {stepForm}
701
+ </ThreadLayout>
702
+ ```
703
+
704
+ The column, the rail and the footer are placed by CSS breakpoint classes only
705
+ (no hook, no style attribute), so the server and the client render the same
706
+ 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
707
+ padding use `max(var(--nxgt-bottom-chrome, …), env(safe-area-inset-bottom))`,
708
+ so a bottom navigation bar or a docked media bar never covers it. With none
709
+ of `width`, `footer` and `stepperPlacement` the markup is the historical one.
710
+
711
+ **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.
712
+
713
+ **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).
714
+
715
+ **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.
716
+
717
+ ```tsx
718
+ const [id, select] = useListDetailsParam();
719
+ <ListDetailsLayout history="push" inspector resizable listWidth={300}>
720
+ <ListDetailsLayout.List>{rows.map((r) => (
721
+ <Row key={r.id} onClick={() => select(r.id, { open: 'details' })} />
722
+ ))}</ListDetailsLayout.List>
723
+ <ListDetailsLayout.Details back><Record id={id} /></ListDetailsLayout.Details>
724
+ <ListDetailsLayout.Inspector title="Properties"><Fields id={id} /></ListDetailsLayout.Inspector>
725
+ </ListDetailsLayout>
726
+ ```
727
+
728
+ **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
729
 
492
730
  **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
731
 
494
- **Storybook:** `Navigation & layout/ListDetailsLayout`, `Navigation & layout/PaneLayout`, `Navigation & layout/ThreadLayout` · **Vue:** `ListDetailsLayout`, `PaneLayout`, `ThreadLayout`
732
+ **Storybook:** `Navigation & layout/ListDetailsLayout` (also `/Phone`, `/Inspector`), `Navigation & layout/PaneLayout`, `Navigation & layout/ThreadLayout` · **Vue:** `ListDetailsLayout`, `PaneLayout`, `ThreadLayout`
733
+
734
+ ---
735
+
736
+ ## SettingsLayout
737
+
738
+ ![SettingsLayout](../previews/navigation-layout-settingslayout.webp)
739
+
740
+ The canonical settings screen, built on `ListDetailsLayout`: sections grouped
741
+ in a list (an icon in a tinted well, a title, one line of description), and the
742
+ page of the selected one beside it, with its heading and description above the
743
+ rows. On a phone the list shows first; a tap opens the page, the focus moves to
744
+ its heading, and Back (the bar over the page, or the browser's) returns to the
745
+ tapped row. The selected section is the `?section=` search param, so a reload or
746
+ a shared link opens the same page (on a phone too), and the other params are
747
+ kept. It needs a router above it.
748
+
749
+ ```tsx
750
+ import { ListTile, SettingsLayout } from '@nxgt/material/components';
751
+
752
+ <SettingsLayout
753
+ search
754
+ sections={[
755
+ {
756
+ id: 'profile',
757
+ group: 'Account',
758
+ title: 'Profile',
759
+ description: 'Name, photo and language',
760
+ icon: <User />,
761
+ content: <ProfileForm />,
762
+ },
763
+ {
764
+ id: 'notifications',
765
+ group: 'Preferences',
766
+ title: 'Notifications',
767
+ description: 'Email and push',
768
+ icon: <Bell />,
769
+ keywords: 'mute quiet',
770
+ content: (
771
+ <ul className="m-0 grid list-none gap-1 p-0">
772
+ <ListTile as="li" title="Email" subtitle="A digest each morning" toggle={{ defaultChecked: true }} />
773
+ </ul>
774
+ ),
775
+ },
776
+ ]}
777
+ />
778
+ ```
779
+
780
+ | Prop | Type | Default | Effect |
781
+ | --- | --- | --- | --- |
782
+ | `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 |
783
+ | `title` | `ReactNode` | "Settings" | The list's heading, the name of its `nav` landmark and the label of the phone's Back bar |
784
+ | `search` | `boolean` | `false` | A search field over the sections (string title, description, group and `keywords`; every word must match) |
785
+ | `param` / `viewParam` | `string` | `'section'` / `'view'` | The search params holding the section and the phone's view |
786
+ | `breakpoint` | `'viewport' \| 'container'` | `'viewport'` | What the one-pane phone layout (648px or less) follows |
787
+ | `listWidth` | `number` | 320 | The list pane's width in px |
788
+
789
+ The section links are real links (`aria-current="page"` on the current one),
790
+ all ending in `view=details`, beside the page too: a link must be the same on
791
+ the server and the client, and a URL shared to a phone opens the page:
792
+ beside the page a choice rewrites the history entry, on a phone it adds one.
793
+ Build the page from the library's rows: `ListTile` (`toggle` for a switch row,
794
+ `variant="divided"` in a card), the `*Field`s and `Banner`. The strings are
795
+ `material.settings-layout.*` (English and French).
796
+
797
+ ## LandingLayout
798
+
799
+ ![LandingLayout](../previews/navigation-layout-landinglayout.webp)
800
+
801
+ The frame of a public marketing or landing page, outside the app shell: a top
802
+ bar, a `main` with the hero then the sections, and a footer. Three parts, all
803
+ imported from `@nxgt/material/components`:
804
+
805
+ - `LandingLayout`: the frame. The bar holds the brand (a link to `brandHref`),
806
+ your `items` as a `TopNav` from 768px (the same links open in a drawer from a
807
+ menu button below it, closing when one is followed) and your `actions` (a
808
+ sign-in link, a filled button) at every width. A skip link comes first and
809
+ `main` is the target. It needs a router above it, like `TopNav`.
810
+ - `LandingSection`: one band: `eyebrow`, `title`, `description` and `actions`
811
+ over its children, on a `tone`, with a `width` and an `align`.
812
+ - `SiteFooter`: the brand and a line about it, columns of links under headings,
813
+ a legal line and a slot beside it.
814
+
815
+ ```tsx
816
+ import {
817
+ LandingLayout,
818
+ LandingSection,
819
+ LinkButton,
820
+ SiteFooter,
821
+ } from '@nxgt/material/components';
822
+
823
+ <LandingLayout
824
+ name="Acme"
825
+ items={[
826
+ { label: 'Features', url: '/features', icon: 'sparkles' },
827
+ { label: 'Pricing', url: '/pricing', icon: 'tag' },
828
+ ]}
829
+ actions={<LinkButton to="/start" variant="filled">Get started</LinkButton>}
830
+ hero={
831
+ <LandingSection
832
+ variant="hero"
833
+ tone="ground"
834
+ eyebrow="New"
835
+ title="Plan the work"
836
+ description="One calm place for the plan and the people."
837
+ actions={<LinkButton to="/start" variant="filled" size="lg">Start for free</LinkButton>}
838
+ />
839
+ }
840
+ footer={
841
+ <SiteFooter
842
+ brand="Acme"
843
+ groups={[{ title: 'Product', links: [{ label: 'Pricing', href: '/pricing' }] }]}
844
+ legal="© 2026 Acme"
845
+ />
846
+ }
847
+ >
848
+ <LandingSection title="Everything in one place" align="center">…</LandingSection>
849
+ <LandingSection tone="muted" title="How it works">…</LandingSection>
850
+ </LandingLayout>
851
+ ```
852
+
853
+ | `LandingLayout` prop | Type | Default | Effect |
854
+ | --- | --- | --- | --- |
855
+ | `items` | `MenuItem[]` | none | The destinations (top level only), in the bar and the phone's drawer |
856
+ | `logo` / `name` / `brand` | `AppLogo` / `string` / `ReactNode` | none | The brand; `brand` replaces the logo and name and needs its own accessible name |
857
+ | `brandHref` | `string` | `'/'` | Where the brand links |
858
+ | `actions` | `ReactNode` | none | The end of the bar, at every width |
859
+ | `hero` / `footer` | `ReactNode` | none | The first band and the footer |
860
+ | `stickyHeader` | `boolean` | `true` | Keeps the bar in view while the page scrolls |
861
+ | `navigationLabel` | `string` | "Navigation" | The bar's landmark name, already translated |
862
+ | `linkComponent` | `AppShellLinkComponent` | provider's, else React Router's | The links' component |
863
+
864
+ | `LandingSection` prop | Type | Default | Effect |
865
+ | --- | --- | --- | --- |
866
+ | `tone` | `'background' \| 'muted' \| 'ground' \| 'primary'` | `'background'` | The surface: the page, a calm tint, the page ground, the primary container |
867
+ | `width` | `'narrow' \| 'medium' \| 'default' \| 'full'` | `'default'` | The content's max width: 48rem, 64rem, 80rem, the whole band |
868
+ | `align` | `'start' \| 'center'` | `'start'` | Where the intro (and its actions) sit |
869
+ | `variant` | `'section' \| 'hero'` | `'section'` | `hero` is the page's first band: its title is the `h1`, set larger, with more room |
870
+ | `media` | `ReactNode` | none | A visual beside the intro from `lg` (an illustration, a product card), under it below; mark decorative content `aria-hidden` |
871
+ | `eyebrow`, `title`, `description`, `actions` | `ReactNode` | none | The intro; a `title` names the section as a region (`aria-labelledby`) |
872
+
873
+ | `SiteFooter` prop | Type | Effect |
874
+ | --- | --- | --- |
875
+ | `brand`, `description` | `ReactNode` | The brand block and a line under it |
876
+ | `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 |
877
+ | `linkComponent` | `AppShellLinkComponent` | The internal links' component: the prop, else `LinkComponentProvider`'s, else React Router's |
878
+ | `legal`, `extra` | `ReactNode` | The legal line, and a slot beside it for social links, a language switcher or a theme toggle |
879
+
880
+ Chrome, light and dark: in light the bar uses the page's surface and the footer a
881
+ calm tint of it; in dark both take the card surface, one tone above the page. A
882
+ band's text stays `foreground` on every tone, so the contrast holds in any theme.
883
+ 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
884
+ are `material.landing.*` (English and French). A whole page built from these
885
+ parts is the `Guides/Recipes/Landing page` story.
495
886
 
496
887
  ---
497
888
 
@@ -529,12 +920,186 @@ their narrow width instead of stretching, and a wide screen gains columns
529
920
  variable; a custom length sets that variable in a `style` attribute (the
530
921
  variable only, never a grid style), so prefer a preset under a strict CSP
531
922
  (`style-src` without `'unsafe-inline'`). The `ResponsiveGrid` props are the
532
- `div`'s plus these two.
923
+ `div`'s plus these.
924
+
925
+ ### Spans, dense packing and masonry
926
+
927
+ ```tsx
928
+ <ResponsiveGrid minItem="sm" dense>
929
+ <ResponsiveGrid.Item span={{ base: 1, md: 2, xl: 3 }}>
930
+ <Chart />
931
+ </ResponsiveGrid.Item>
932
+ <ResponsiveGrid.Item rowSpan={2}>
933
+ <TaskList />
934
+ </ResponsiveGrid.Item>
935
+ <StatCard label="Orders" value="1,284" />
936
+ </ResponsiveGrid>
937
+
938
+ // A wall of notes of different heights, packed in columns.
939
+ <ResponsiveGrid flow="masonry" minItem="md" gap="md">
940
+ {notes.map((note) => <NoteCard key={note.id} note={note} />)}
941
+ </ResponsiveGrid>
942
+ ```
943
+
944
+ | Prop | Type | Default | Effect |
945
+ | --- | --- | --- | --- |
946
+ | `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 |
947
+ | `dense` | `boolean` | `false` | `grid-auto-flow: dense`: later, smaller tiles move up into the holes a wide tile leaves |
948
+ | `Item` `span` | `1 \| … \| 6 \| 'full'`, or `{ base?, md?, xl? }` | 1 | Columns the tile covers; each width applies from there up |
949
+ | `Item` `rowSpan` | `1 \| … \| 4`, or `{ base?, md?, xl? }` | 1 | Rows the tile covers |
950
+
951
+ `ResponsiveGrid.Item` is a `div`; a plain child needs no wrapper, only a tile
952
+ with a span does. The widths are the grid's own, not the window's: `md` is a
953
+ 768px-wide grid (`@3xl`) and `xl` a 1280px one (`@7xl`). A span wider than the
954
+ columns at that width adds implicit columns, so keep it within them
955
+ (`span={{ base: 1, md: 2 }}`, not `span={2}` in a one-column grid).
956
+
957
+ Two caveats are about reading order. With `dense` the eye reads the tiles in a
958
+ different order than the keyboard and a screen reader (the source order). With
959
+ `flow="masonry"` the tiles fill the first column top to bottom, then the next:
960
+ Tab and the screen reader run down each column while the eye reads across.
961
+ Use masonry for browsing (notes, a gallery), not for a sequence, and keep
962
+ anything with an order in the default grid. Masonry ignores `span`, `rowSpan`
963
+ and `dense`. It is CSS multi-column, which every browser supports; no
964
+ JavaScript measures the tiles.
533
965
 
534
966
  **Storybook:** `Navigation & layout/ResponsiveGrid` · **Vue:** `ResponsiveGrid` (without `minItem` and `gap`)
535
967
 
536
968
  ---
537
969
 
970
+ ## DashboardGrid
971
+
972
+ ![DashboardGrid](../previews/navigation-layout-dashboardgrid.webp)
973
+
974
+ Tiles in a responsive grid that the user can rearrange. A button turns on an
975
+ edit mode; in it each tile shows a handle to drag with a pointer, or to pick up
976
+ with Space, move with the arrow keys and drop with Space. The order belongs to
977
+ the app: it stores it and passes it back.
978
+
979
+ ```tsx
980
+ import { DashboardGrid, DashboardTile } from '@nxgt/material/components';
981
+
982
+ const [order, setOrder] = useState(savedLayout); // string[] of tile ids
983
+
984
+ <DashboardGrid aria-label="Dashboard" order={order} onOrderChange={setOrder}>
985
+ <DashboardTile id="revenue" title="Revenue" span={{ base: 1, md: 2 }}>
986
+ <RevenueChart />
987
+ </DashboardTile>
988
+ <DashboardTile id="orders" title="Orders">
989
+ <OrdersCount />
990
+ </DashboardTile>
991
+ <DashboardTile id="activity" title="Activity" span="full" rowSpan={2}>
992
+ <ActivityFeed />
993
+ </DashboardTile>
994
+ </DashboardGrid>
995
+ ```
996
+
997
+ | Prop (`DashboardGrid`) | Type | Default | Effect |
998
+ | --- | --- | --- | --- |
999
+ | `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 |
1000
+ | `editing` / `defaultEditing` / `onEditingChange` | `boolean` | `false` | Edit mode, controlled or not |
1001
+ | `editButton` | `boolean` | `true` | The "Edit layout" / "Done" button above the grid; `false` for an app that toggles `editing` from its own control |
1002
+ | `columns` | `{ base?: 1 \| 2; md?: 1..6; xl?: 1..6 }` | `{ 1, 2, 4 }` | Columns at the container widths 0, 768px and 1280px |
1003
+ | `gap` | `'sm' \| 'md' \| 'lg'` | `'md'` | The space between tiles |
1004
+ | `dense` | `boolean` | `false` | Pack later tiles into the holes a wide tile leaves |
1005
+
1006
+ | Prop (`DashboardTile`) | Type | Default | Effect |
1007
+ | --- | --- | --- | --- |
1008
+ | `id` | `string` | required | The tile's name in `order`; unique in the grid |
1009
+ | `title` | `ReactNode` | none | An `h3` at the top, the tile's accessible name and its handle's name |
1010
+ | `span` / `rowSpan` | as `ResponsiveGrid.Item` | 1 | Columns and rows covered, held to the grid's columns at each width |
1011
+ | `plain` | `boolean` | `false` | No card of its own (border, background, padding), for a child that is a card already |
1012
+ | `label` | `string` | the title, else the id | The name used by the handle and the announcements when `title` is not text |
1013
+
1014
+ Write the `DashboardTile`s directly inside the grid (not in a fragment or a
1015
+ wrapper component): the grid reads their `id`s to place them. Tile content keeps its
1016
+ state when a tile moves.
1017
+
1018
+ **Keyboard.** In edit mode each handle is a Tab stop. Space or Enter picks the
1019
+ tile up, the arrows move it (left and up earlier, right and down later, the
1020
+ reverse of left and right in a right-to-left page), Home and End send it first
1021
+ or last, Space, Enter or Tab drops it, Escape puts it back where it was. A
1022
+ live region says "Orders picked up, position 2 of 6", where it moved, and
1023
+ whether it was dropped or cancelled. A pointer drag is announced the same way.
1024
+
1025
+ **Phone.** Below 768px the tiles stack in one column and each shows "earlier"
1026
+ and "later" buttons beside its handle, so a touch user needs no drag.
1027
+
1028
+ The strings are `material.dashboard.*` in English and French (React only).
1029
+
1030
+ **Storybook:** `Navigation & layout/DashboardGrid` · **Vue:** none yet
1031
+
1032
+ ---
1033
+
1034
+ ## EditorLayout
1035
+
1036
+ ![EditorLayout](../previews/navigation-layout-editorlayout.webp)
1037
+
1038
+ The page of an editor (an image, a slide, a form builder): a toolbar, a canvas
1039
+ that scrolls on its own, a properties panel at the end and an optional panel
1040
+ at the start (layers, outline). It is built on [`PaneLayout`](#listdetailslayout) (documented with
1041
+ ListDetailsLayout) and follows the layout's own container width, not the window's: from 768px the properties
1042
+ are an inline panel; below it they open in a bottom sheet from a toolbar button.
1043
+
1044
+ ```tsx
1045
+ import { EditorLayout } from '@nxgt/material/components';
1046
+
1047
+ // UndoRedoButtons, LayerList, SelectionForm, Artboard: your components
1048
+ <EditorLayout
1049
+ toolbar={
1050
+ <>
1051
+ <EditorLayout.StartTrigger />
1052
+ <UndoRedoButtons />
1053
+ <span className="flex-1" />
1054
+ <EditorLayout.PropertiesTrigger />
1055
+ </>
1056
+ }
1057
+ start={<LayerList />}
1058
+ properties={<SelectionForm />}
1059
+ ground="neutral"
1060
+ className="h-dvh"
1061
+ >
1062
+ <Artboard />
1063
+ </EditorLayout>
1064
+ ```
1065
+
1066
+ | Prop | Type | Default | Effect |
1067
+ | --- | --- | --- | --- |
1068
+ | `children` | `ReactNode` | required | The canvas, in a scrolling region (it takes focus, so arrows and Page keys scroll it) |
1069
+ | `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 |
1070
+ | `start` | `ReactNode` | none | Inline panel from 1280px; a side sheet below, opened by `EditorLayout.StartTrigger` |
1071
+ | `properties` | `ReactNode` | none | Inline panel at the end from 768px; a bottom sheet below |
1072
+ | `ground` | `'plain' \| 'tinted' \| 'neutral'` | `'plain'` | What the canvas sits on: the page, the tinted page ground (`bg-ground`) or a grey |
1073
+ | `propertiesWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | Inline panel width: 16, 20 or 24rem |
1074
+ | `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 |
1075
+ | `toolbarLabel`, `startLabel`, `propertiesLabel`, `canvasLabel` | `string` | `material.editor-layout.*` | The names of the regions |
1076
+
1077
+ `EditorLayout.PropertiesTrigger` toggles the inline panel (a way to give the
1078
+ canvas the whole width) or the sheet; `EditorLayout.StartTrigger` shows only
1079
+ below 1280px. Both are `Button`s whose label is the icon alone on a phone, and
1080
+ take `Button` props and a `children` label. `useEditorLayout()` gives the start
1081
+ panel's state; the properties' is `usePaneLayout()`.
1082
+
1083
+ **Accessibility.** The toolbar is a named `group`, not `role="toolbar"`: it holds
1084
+ whatever the app puts there, and a toolbar role would promise arrow-key
1085
+ navigation between its buttons. The canvas is a `region`, the properties a
1086
+ `complementary` landmark (a labelled dialog in its sheet, with focus moved in
1087
+ and back to the button on close), the outline a `complementary` landmark too.
1088
+ In the source the canvas comes before the properties, so Tab reaches it first.
1089
+ The canvas is a tab stop because it is the keyboard route to scroll it; an app
1090
+ that wants a real arrow-key toolbar can put `<Toolbar>` in the `toolbar` slot.
1091
+
1092
+ **SSR.** The server renders the desktop markup and hides the panels with
1093
+ container-query classes; the phone sheet takes over right after hydrating, and
1094
+ no `style` attribute is written. The panel state is the app's to persist (read it
1095
+ after hydrating, then pass `defaultPropertiesOpen`).
1096
+
1097
+ The strings are `material.editor-layout.*` in English and French (React only).
1098
+
1099
+ **Storybook:** `Navigation & layout/EditorLayout` · **Vue:** none yet
1100
+
1101
+ ---
1102
+
538
1103
  ## SectionColumns
539
1104
 
540
1105
  ![SectionColumns](../previews/navigation-layout-sectioncolumns.webp)
@@ -934,7 +1499,7 @@ Tooltip or Link wrapping a Button is not covered.
934
1499
 
935
1500
  ![Doc](../previews/navigation-layout-doc.webp)
936
1501
 
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`.
1502
+ 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
1503
 
939
1504
  <img src="../previews/navigation-layout-doc-api.webp" height="96" alt="Doc API page">
940
1505
  <img src="../previews/navigation-layout-doc-blocks.webp" height="96" alt="Doc blocks">
@@ -971,7 +1536,7 @@ the pieces it composes — `DocTopbar`, `DocSidebar`, `DocToc`, `DocPager`,
971
1536
  `DocSearch`, `DocSeeAlso`, `DocChildCards`, `DocProgress`, `DocSkipLink`,
972
1537
  `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
1538
  `useDoc` / `DocProvider` / `useDocContext` expose the state for a shell of
974
- your own.
1539
+ your own. `useDoc` takes the same `chrome` and returns it as `frame` (type `DocChrome`, exported).
975
1540
 
976
1541
  | Prop | Type | Default | Effect |
977
1542
  | --- | --- | --- | --- |
@@ -979,15 +1544,28 @@ your own.
979
1544
  | `pathname` | `string` | — | current page; drives active item, pager, see also |
980
1545
  | `toc` | `DocTocItem[]` | `[]` | the page's outline |
981
1546
  | `onNavigate` / `linkComponent` | `(href) => void` / link component | — / `<a>` | wire your router |
1547
+ | `chrome` | `DocChrome` (`'standalone' \| 'embedded'`) | `'standalone'` | `'embedded'` draws the page inside `ActivityLayout`: see below |
982
1548
  | `searchable` | `boolean` | `true` | search field and ⌘K |
983
- | `brand`, `actions` | `ReactNode` | — | top bar, left and right |
1549
+ | `brand`, `actions` | `ReactNode` | — | top bar, left and right (embedded: `actions` only, in the row under the shell's header) |
984
1550
  | `previous`, `next` | `DocPagerLink \| null` | from `items` | override the pager; `null` hides one side |
985
1551
 
1552
+ **Inside the app shell.** `chrome="embedded"` is for a docs page that lives
1553
+ under an `ActivityLayout` route (its sidebar, app bar and `handle.title` stay).
1554
+ `Doc` then draws no top bar, no reading bar and no full-height frame; the
1555
+ shell's header is the page's. The page tree and the outline stick under that
1556
+ header (the same offset as an `ActivityContent` `aside`, so a taller header or
1557
+ a `scroll="container"` shell is followed), scroll with the shell, and stop above
1558
+ a bottom bar or floating toolbar (`--nxgt-bottom-chrome`). Below `md` the tree
1559
+ is a sheet opened by a « Browse docs » button, named apart from the shell's
1560
+ « Menu », with the search and `actions` beside it; `brand` shows at the top of
1561
+ that sheet. The default `chrome` is unchanged, markup included. See the
1562
+ [app shell guide](../guides/app-shell.md#a-docs-page-inside-the-shell).
1563
+
986
1564
  On `DocPage`: `title`, `description`, `badge`, `crumbs`, `editHref`,
987
1565
  `updatedAt`, and `copyPage` / `copyMarkdown` with `markdown` for the copy
988
1566
  buttons.
989
1567
 
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`)
1568
+ **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
1569
 
992
1570
  ---
993
1571
 
@@ -1021,17 +1599,357 @@ in dark. `ActivityLayout` and `ThreadLayout` sit on it. The superset with `grain
1021
1599
 
1022
1600
  ---
1023
1601
 
1602
+ ## NavigationBar
1603
+
1604
+ The bottom navigation bar of a phone: a `nav` fixed to the bottom edge with 3 to
1605
+ 5 destinations (icon in an indicator pill, label always visible, `aria-current`,
1606
+ 48px targets, optional badge) and a "More" sheet for the others. `ActivityLayout`
1607
+ draws it with `navigation="bar"` (alone it is phone-only: hidden from 768px,
1608
+ where the default rail shows) or `{ phone: 'bar' }`; see the
1609
+ [app shell guide](../guides/app-shell.md#the-bottom-bar-navigationbar).
1610
+
1611
+ No preview yet: see the stories `Navigation & layout/NavigationBar`.
1612
+
1613
+ ```tsx
1614
+ import { NavigationBar } from '@nxgt/material/components';
1615
+
1616
+ <NavigationBar
1617
+ className="md:hidden"
1618
+ items={[
1619
+ { label: 'Home', url: '/', icon: 'house' },
1620
+ { label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3 },
1621
+ { label: 'Files', url: '/files', icon: 'folder' },
1622
+ { label: 'Settings', url: '/settings', icon: 'gear', priority: -1 },
1623
+ ]}
1624
+ />
1625
+ ```
1626
+
1627
+ | Prop | Type | Default | |
1628
+ | --- | --- | --- | --- |
1629
+ | `items` | `MenuItem[]` | `[]` | Entries; `bar` and `priority` choose those kept on the bar |
1630
+ | `max` | `number` | `5` | Destinations on the bar, More included; clamped to 3-5 |
1631
+ | `label` | `string` | "Navigation" | The landmark's name |
1632
+ | `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
1633
+ | `children` | `NavigationBar.Item` | | Entries composed by hand |
1634
+ | `className` | `string` | | On the `nav`; a breakpoint class such as `md:hidden` hides the bar |
1635
+
1636
+ It sets `--nxgt-nav-bar` on the document root while it shows, and needs a
1637
+ router context. `NavigationBar.Item` takes `href`, `icon`, `children` (the label), `active`
1638
+ (default: the location is `href` or below it), `badge`, `badgeLabel`,
1639
+ `linkComponent` and `className`.
1640
+
1641
+ Also exported: `splitBarItems(items, max)`, which returns a `BarSplit`
1642
+ (`{ bar, more }`), `BAR_MAX` (5) and the type `NavigationBarItemProps`.
1643
+
1644
+ **Storybook:** `Navigation & layout/NavigationBar` · **Vue:** not yet
1645
+
1646
+ ---
1647
+
1024
1648
  ## NavigationRail
1025
1649
 
1026
- Placeholder: renders an empty rail container and takes no props yet.
1650
+ The navigation rail (Material 3 Expressive): a `nav` of destinations down the
1651
+ start edge, each an icon in an indicator pill with its label under it, an
1652
+ optional badge read in the link's name, a `fab` slot at the top for the
1653
+ screen's primary action and a menu button that expands it into a wide rail
1654
+ with the labels inline. `ActivityLayout` draws it with
1655
+ `navigation="rail-expanded"`; see the
1656
+ [app shell guide](../guides/app-shell.md#the-wide-rail-navigationrail-expanded).
1657
+ Without `items` or `children` it is still the empty `div` it was, and needs no
1658
+ router.
1659
+
1660
+ Not previewed yet: see the stories `Navigation & layout/NavigationRail`.
1661
+
1662
+ ```tsx
1663
+ import { Plus } from 'lucide-react';
1664
+ import { Fab, NavigationRail } from '@nxgt/material/components';
1665
+
1666
+ <NavigationRail
1667
+ persistKey="app-rail"
1668
+ items={[
1669
+ { label: 'Home', url: '/', icon: 'house' },
1670
+ { label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3 },
1671
+ { label: 'Settings', url: '/settings', icon: 'gear', footer: true },
1672
+ ]}
1673
+ fab={({ expanded }) => (
1674
+ <Fab extended collapsed={!expanded} icon={<Plus />} label="Compose" />
1675
+ )}
1676
+ />
1677
+ ```
1678
+
1679
+ | Prop | Type | Default | |
1680
+ | --- | --- | --- | --- |
1681
+ | `items` | `MenuItem[]` | `[]` | Entries; `footer: true` ones sit at the bottom |
1682
+ | `fab` | `ReactNode` or `({ expanded }) => ReactNode` | | The primary action above the destinations (an action, not a link) |
1683
+ | `expanded` | `boolean` | | Controls the state |
1684
+ | `defaultExpanded` | `boolean` | `false` | The initial state when not controlled |
1685
+ | `onExpandedChange` | `(expanded: boolean) => void` | | Called by the menu button |
1686
+ | `persistKey` | `string` | | Remembers the state in the browser under this key, read after hydration |
1687
+ | `toggle` | `boolean` | `true` | Whether the menu button shows |
1688
+ | `label` | `string` | "Navigation" | The landmark's name |
1689
+ | `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
1690
+ | `children` | `NavigationRail.Item` | | Entries composed by hand |
1691
+ | `className` | `string` | | On the `nav`; give it a height (the rail fills it) and a breakpoint class to hide it |
1692
+
1693
+ A phone never draws `'rail-expanded'` (or `'rail'`): it keeps its menu sheet.
1694
+ A standalone `NavigationRail` starts collapsed (`defaultExpanded` is `false`); inside `ActivityLayout` the `rail-expanded` preset starts expanded.
1695
+
1696
+ The rail is 5rem wide collapsed and 15rem expanded (the width eases unless the
1697
+ user prefers reduced motion). Targets are 48px or taller, the menu button
1698
+ carries `aria-expanded` and `aria-controls`, and its name is
1699
+ `material.navigation.rail-toggle` ("Navigation menu"). `NavigationRail.Item`
1700
+ takes `href`, `icon`, `children` (the label), `active`, `badge`, `badgeLabel`,
1701
+ `linkComponent` and `className`. Also exported: the types `NavigationRailItemProps`
1702
+ and `RailExpandedOptions`.
1703
+
1704
+ **Storybook:** `Navigation & layout/NavigationRail` · **Vue:** placeholder only
1705
+
1706
+ ## NavigationRailPanel
1707
+
1708
+ The second level of a two-level navigation (Material 3 Expressive): a panel
1709
+ beside the [`NavigationRail`](#navigationrail) that lists the `children` of the
1710
+ top-level entry the location belongs to, under that entry's label, in 48px pill
1711
+ rows with the current child tinted and marked `aria-current="page"` (a badge
1712
+ ends the row and is read in the link's name). It is a `nav` named by the
1713
+ section's label, which is also its visible heading; a button folds it to a
1714
+ narrow strip and back (`aria-expanded`, `aria-controls`, focus stays on it).
1715
+ Nothing opens it on hover. `ActivityLayout` draws the rail and the panel
1716
+ together with `navigation="two-level"`; see the
1717
+ [app shell guide](../guides/app-shell.md#the-two-level-navigation). It reads the
1718
+ location, so it needs a React Router context.
1719
+
1720
+ The panel is **open by default** (`defaultExpanded` is `true`), or controlled with
1721
+ `expanded`; with a `persistKey` the last choice is kept in the browser under
1722
+ `nxgt-panel:<persistKey>`, read after hydration (the server and the first client
1723
+ render are open). On a section without `children` the panel hides itself with
1724
+ CSS and keeps its markup, so the page does not change shape between routes. Only
1725
+ one level of `children` is listed (the children's own children are ignored).
1027
1726
 
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.
1727
+ ```tsx
1728
+ import {
1729
+ type MenuItem,
1730
+ NavigationRail,
1731
+ NavigationRailPanel,
1732
+ } from '@nxgt/material/components';
1733
+
1734
+ const items: MenuItem[] = [
1735
+ { label: 'Home', url: '/', icon: 'house' },
1736
+ {
1737
+ label: 'Projects',
1738
+ url: '/projects',
1739
+ icon: 'folder',
1740
+ children: [
1741
+ { label: 'Overview', url: '/projects', icon: 'folder' },
1742
+ { label: 'Active', url: '/projects/active', icon: 'folder' },
1743
+ ],
1744
+ },
1745
+ ];
1746
+
1747
+ <div className="flex h-dvh">
1748
+ <NavigationRail items={items} toggle={false} />
1749
+ <NavigationRailPanel items={items} persistKey="app-panel" />
1750
+ </div>
1751
+ ```
1752
+
1753
+ | Prop | Type | Default | |
1754
+ | --- | --- | --- | --- |
1755
+ | `items` | `MenuItem[]` | `[]` | The rail's menu; the panel lists the active top-level entry's `children` |
1756
+ | `expanded` | `boolean` | | Controls the panel: open while `true` |
1757
+ | `defaultExpanded` | `boolean` | `true` | The state to start from, before a saved choice is read |
1758
+ | `onExpandedChange` | `(expanded: boolean) => void` | | Asked for every change, controlled or not |
1759
+ | `persistKey` | `string` | | Keeps the state under `nxgt-panel:<persistKey>` (ignored when controlled) |
1760
+ | `label` | `string` | the section's label | The landmark's accessible name, already translated |
1761
+ | `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
1762
+ | `className` | `string` | | On the panel's root |
1763
+
1764
+ The button's names are `material.navigation.panel-collapse` ("Collapse the panel")
1765
+ and `material.navigation.panel-expand` ("Expand the panel"). Also exported: the
1766
+ type `NavigationRailPanelProps`. The rail beside it lights the section when the
1767
+ current page is one of its children, even a child whose `url` is not under the
1768
+ parent's (`NavigationRail` matches the whole tree and marks the top-level
1769
+ entry).
1770
+
1771
+ **Storybook:** `Navigation & layout/NavigationRailPanel` · **Vue:** —
1772
+
1773
+ ## NavigationToolbar
1774
+
1775
+ The floating navigation toolbar (Material 3 Expressive): a pill of round icon
1776
+ links over the content, at the bottom centre. It is a `nav` landmark of links
1777
+ (`aria-current="page"` on the current one, each named by a visually hidden label
1778
+ and a tooltip, 48px targets, a badge read in the link's name), not a
1779
+ `role="toolbar"`: `Toolbar variant="floating"` stays for *actions*. The two share
1780
+ the pill (the `--tb-*` tokens, the shadow and the slide away of
1781
+ `.nxgt-toolbar`, so a theme that restyles the toolbar restyles both) and nothing
1782
+ else. `ActivityLayout` draws it with `navigation="floating"`; see the
1783
+ [app shell guide](../guides/app-shell.md#the-floating-navigation-toolbar).
1784
+
1785
+ No preview yet: see the stories `Navigation & layout/NavigationToolbar`.
1030
1786
 
1031
1787
  ```tsx
1032
- import { NavigationRail } from '@nxgt/material/components';
1788
+ import { NavigationToolbar } from '@nxgt/material/components';
1033
1789
 
1034
- <NavigationRail />
1790
+ <NavigationToolbar
1791
+ hideOnScroll
1792
+ items={[
1793
+ { label: 'Home', url: '/', icon: 'house' },
1794
+ { label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3 },
1795
+ { label: 'Files', url: '/files', icon: 'folder' },
1796
+ { label: 'Settings', url: '/settings', icon: 'gear', priority: -1 },
1797
+ ]}
1798
+ />
1035
1799
  ```
1036
1800
 
1037
- **Storybook:** — · **Vue:** `NavigationRail`
1801
+ | Prop | Type | Default | |
1802
+ | --- | --- | --- | --- |
1803
+ | `items` | `MenuItem[]` | `[]` | Entries; `bar` and `priority` choose those kept on the pill, as for `NavigationBar` |
1804
+ | `max` | `number` | `5` | Destinations on the pill, More included; clamped to 3-5 (3 to 5 recommended) |
1805
+ | `label` | `string` | "Navigation" | The landmark's name |
1806
+ | `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 |
1807
+ | `hidden` | `boolean` | — | Forces it away or shown, whatever the scroll says |
1808
+ | `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
1809
+ | `children` | `NavigationToolbar.Item` | | Entries composed by hand |
1810
+ | `className` | `string` | | On the `nav`; a breakpoint class such as `md:hidden` hides it |
1811
+
1812
+ With more entries than fit, the last slot is « More »: the same bottom sheet as
1813
+ `NavigationBar`'s, lighting up when the current page is under it. The pill is
1814
+ `position: fixed`, `1rem` above the safe area, and shows at every width. A docked media bar sits above
1815
+ 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
1816
+ 4rem height, the gap and the safe area), also while it is slid away, so `FabDock`, the toaster,
1817
+ the mini player and `StickyActionBar` stay clear and nothing jumps when it
1818
+ returns. It does not rise with the on-screen keyboard. It needs a router context. `NavigationToolbar.Item` takes `href`,
1819
+ `icon`, `children` (the name), `active`, `badge`, `badgeLabel`, `linkComponent`
1820
+ and `className`; the type `NavigationToolbarItemProps` is exported too.
1821
+
1822
+ **Storybook:** `Navigation & layout/NavigationToolbar` · **Vue:** not yet
1823
+
1824
+ ## TopNav
1825
+
1826
+ The top navigation (Material 3 Expressive): one `nav` of destinations as 48px
1827
+ pills in a row, the current one tinted with `aria-current="page"`. An entry
1828
+ with `children` is a trigger button that opens a dropdown (a rounded card) of
1829
+ its child links; when the current page is one of them, the trigger reads as
1830
+ active. A badge ends the pill and is read in the link's name. Built on
1831
+ Radix's navigation menu: arrow keys move along the bar, Escape closes the
1832
+ dropdown and returns focus to its trigger, and the motion is off for users who
1833
+ prefer reduced motion. `ActivityLayout` draws it in the app header with
1834
+ `navigation="top"`; see the
1835
+ [app shell guide](../guides/app-shell.md#the-top-navigation). It reads the
1836
+ location, so it needs a React Router context.
1837
+
1838
+ Not previewed yet: see the stories `Navigation & layout/TopNav`.
1839
+
1840
+ 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.
1841
+
1842
+ ```tsx
1843
+ import { TopNav } from '@nxgt/material/components';
1844
+
1845
+ <TopNav
1846
+ items={[
1847
+ { label: 'Home', url: '/', icon: 'house' },
1848
+ { label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3 },
1849
+ {
1850
+ label: 'Projects',
1851
+ url: '/projects',
1852
+ icon: 'folder',
1853
+ children: [
1854
+ { label: 'All projects', url: '/projects/all', icon: 'folder' },
1855
+ { label: 'Archived', url: '/projects/archived', icon: 'box-archive' },
1856
+ ],
1857
+ },
1858
+ ]}
1859
+ />
1860
+ ```
1861
+
1862
+ | Prop | Type | Default | |
1863
+ | --- | --- | --- | --- |
1864
+ | `items` | `MenuItem[]` | `[]` | Destinations in order; an entry with `children` opens a dropdown (its own `url` is never a link) |
1865
+ | `label` | `string` | "Navigation" | The landmark's name |
1866
+ | `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
1867
+ | `children` | `TopNav.Item`, `TopNav.Menu` | | Entries composed by hand, after those of `items` |
1868
+ | `className` | `string` | | On the `nav` |
1869
+
1870
+ By hand, with the pieces:
1871
+
1872
+ ```tsx
1873
+ import { TopNav, TopNavItem, TopNavMenu } from '@nxgt/material/components';
1874
+
1875
+ <TopNav>
1876
+ <TopNavItem href="/" icon="house" active>Home</TopNavItem>
1877
+ <TopNavItem href="/inbox" badge={3}>Inbox</TopNavItem>
1878
+ <TopNavMenu
1879
+ label="Projects"
1880
+ icon="folder"
1881
+ current="/projects/all"
1882
+ items={[
1883
+ { label: 'All projects', url: '/projects/all', icon: 'folder' },
1884
+ { label: 'Archived', url: '/projects/archived', icon: 'box-archive' },
1885
+ ]}
1886
+ />
1887
+ </TopNav>
1888
+ ```
1889
+
1890
+ `TopNavItem` (also `TopNav.Item`) takes `href`, `icon` (optional), `children`
1891
+ (the label), `active` (`false`), `badge` (none), `badgeLabel`, `linkComponent` and
1892
+ `className`.
1893
+ `TopNavMenu` (also `TopNav.Menu`) takes `label`, `icon`, `items` (`MenuItem[]`, the links),
1894
+ `current` (the `url` of the current one; unset, none is current), `badge`, `badgeLabel`,
1895
+ `linkComponent` and `className`. Also exported: the types `TopNavProps`,
1896
+ `TopNavItemProps` and `TopNavMenuProps`.
1897
+
1898
+ **Storybook:** `Navigation & layout/TopNav` · **Vue:** —
1899
+
1900
+ ## NavigationDrawer
1901
+
1902
+ The navigation drawer (Material 3 Expressive): a `nav` of destinations in
1903
+ sections, each a 56px row with an icon, a label and an optional badge that
1904
+ ends the row and is read in the link's name. `aria-current="page"` marks the
1905
+ current one, and its row is tinted. Entries are listed in sections by their
1906
+ `group` (the ones without a group first, with no headline) and the entries
1907
+ marked `footer` sit at the bottom. `ActivityLayout` draws the standard drawer
1908
+ with `navigation="drawer"`; see the
1909
+ [app shell guide](../guides/app-shell.md#the-drawer-and-the-collapsible-sidebar).
1910
+ It reads the location, so it needs a React Router context.
1911
+
1912
+ Not previewed yet: see the stories `Navigation & layout/NavigationDrawer`.
1913
+
1914
+ ```tsx
1915
+ import { IconButton, NavigationDrawer } from '@nxgt/material/components';
1916
+
1917
+ // permanent, 20rem wide
1918
+ <NavigationDrawer
1919
+ items={[
1920
+ { label: 'Home', url: '/', icon: 'house' },
1921
+ { label: 'Inbox', url: '/inbox', icon: 'envelope', badge: 3, group: 'Mail' },
1922
+ { label: 'Settings', url: '/settings', icon: 'gear', footer: true },
1923
+ ]}
1924
+ />
1925
+
1926
+ // modal: a sheet from the start edge, closed by a followed link or Escape
1927
+ <NavigationDrawer
1928
+ variant="modal"
1929
+ items={items}
1930
+ trigger={<IconButton aria-label="Menu" variant="outlined">...</IconButton>}
1931
+ />
1932
+ ```
1933
+
1934
+ | Prop | Type | Default | |
1935
+ | --- | --- | --- | --- |
1936
+ | `variant` | `'standard' \| 'modal'` | `'standard'` | A permanent column, or the same column in a sheet |
1937
+ | `items` | `MenuItem[]` | `[]` | Entries, in sections by `group`; `footer: true` ones sit at the bottom |
1938
+ | `header` | `ReactNode` | | A block above the destinations (the brand) |
1939
+ | `label` | `string` | "Navigation" | The landmark's name |
1940
+ | `linkComponent` | `LinkComponent` | React Router `Link` | As for `AppSidebar` |
1941
+ | `children` | `NavigationDrawer.Section` | | Sections composed by hand, after those of `items` |
1942
+ | `open` / `onOpenChange` | `boolean` / `(open: boolean) => void` | | `variant="modal"`: controls the sheet |
1943
+ | `trigger` | `ReactNode` | | `variant="modal"`: the element that opens it |
1944
+ | `className` | `string` | | On the `nav`; give a standard drawer a height (it fills it, and scrolls) and a breakpoint class to hide it |
1945
+
1946
+ `NavigationDrawerSection` (also `NavigationDrawer.Section`) takes `headline`
1947
+ (names the group), `items`, `current` (the `url` of the current entry; unset,
1948
+ the one of its own `items` the location is in), `linkComponent` and children
1949
+ (`NavigationDrawer.Item`: `href`, `icon`, `children`, `active`, `badge`,
1950
+ `badgeLabel`, `linkComponent`, `className`). The modal variant shares the app
1951
+ shell's menu sheet logic: a followed link and a new location close it and focus
1952
+ goes to the page's title, else back to the trigger. Also exported: the types
1953
+ `NavigationDrawerItemProps` and `NavigationDrawerSectionProps`.
1954
+
1955
+ **Storybook:** `Navigation & layout/NavigationDrawer` · **Vue:** —