@nxgt/material 3.101.0 → 3.104.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 (207) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +37 -5
  3. package/dist/{alert-dialog-DYzxchiS.js → alert-dialog-CK_47jfm.js} +1 -1
  4. package/dist/{async-slice-drJyPvuM.js → async-slice-1ZNMiwyJ.js} +1 -1
  5. package/dist/{bubble-input-CQwVSoU3.js → bubble-input-BxI1WmNs.js} +17 -5
  6. package/dist/{button-GeIsUekU.js → button-YfEMZbRO.js} +2 -2
  7. package/dist/{calculator-BS4PAm6l.js → calculator-BmYZId4c.js} +261 -460
  8. package/dist/calculator-field.js +7 -7
  9. package/dist/{calculator-segment-CEdwE24M.js → calculator-segment-DJ08DJ-n.js} +1 -1
  10. package/dist/calculator.js +1 -1
  11. package/dist/charts.js +819 -764
  12. package/dist/{chip-BdEtFQ85.js → chip-DPj6e-3v.js} +2 -2
  13. package/dist/{code-editor-BByXZOGy.js → code-editor-CZg3nLZO.js} +178 -177
  14. package/dist/code-editor.js +5 -5
  15. package/dist/{code-language-items-CSZX5zxD.js → code-language-items-Nq1iOGId.js} +14 -5
  16. package/dist/components/ui/actions/action-card/action-card-tile.css +47 -0
  17. package/dist/components/ui/display/chip/chip-soft.css +27 -2
  18. package/dist/components/ui/display/code/code-moonlight.css +10 -10
  19. package/dist/components/ui/primitives/collapsible/collapsible.css +9 -0
  20. package/dist/{components-DQ4MljCr.js → components-B2wrH_W0.js} +19302 -18583
  21. package/dist/components.js +38 -37
  22. package/dist/{conversion-variant-DTQqAyDt.js → conversion-variant-C-LPx5ek.js} +5 -5
  23. package/dist/{currency-field-Dnju7ss2.js → currency-field-DnsRIA80.js} +24 -23
  24. package/dist/{dialog-5_2__tu8.js → dialog-BKBsDGFc.js} +3 -3
  25. package/dist/dnd.js +1 -1
  26. package/dist/{dropdown-menu-FdGC5kIP.js → dropdown-menu-QMDbRidH.js} +1 -1
  27. package/dist/editor.js +9 -9
  28. package/dist/{empty-state-BoepPG5v.js → empty-state-BLIJ6znY.js} +1 -1
  29. package/dist/{field-blur-DkiZOVNV.js → field-blur-DMJwPLsg.js} +9 -9
  30. package/dist/field-blur-context-3rKussBQ.js +29 -0
  31. package/dist/{financial-variant-BhqJp5C-.js → financial-variant-Ws0Jp01H.js} +5 -5
  32. package/dist/{hooks-DjcbJZiO.js → hooks-C3vkQmzh.js} +106 -61
  33. package/dist/hooks.js +3 -3
  34. package/dist/{hover-card-CmHJeBeY.js → hover-card-Df649U6I.js} +1 -1
  35. package/dist/i18n.js +3 -3
  36. package/dist/{icon-badge-LGUPQh_c.js → icon-badge-Cma1qOOJ.js} +1 -1
  37. package/dist/{icon-button-BSVpO1jP.js → icon-button-CpKxV264.js} +1 -1
  38. package/dist/key-layout-D2goCDkD.js +31 -0
  39. package/dist/keypad.js +551 -0
  40. package/dist/label-DR-zZyGQ.js +47 -0
  41. package/dist/lib/components/ui/actions/index.d.ts +1 -0
  42. package/dist/lib/components/ui/actions/product-tile/index.d.ts +1 -0
  43. package/dist/lib/components/ui/actions/product-tile/product-tile-link.d.ts +16 -0
  44. package/dist/lib/components/ui/actions/product-tile/product-tile.d.ts +41 -0
  45. package/dist/lib/components/ui/collections/data-table/data-table-density.d.ts +1 -1
  46. package/dist/lib/components/ui/collections/data-table/partials/data-table-scroll-region.d.ts +4 -1
  47. package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-list-bar.d.ts +6 -3
  48. package/dist/lib/components/ui/collections/data-table/partials/layouts/list-bar-in-row.d.ts +2 -1
  49. package/dist/lib/components/ui/collections/data-table/types.d.ts +11 -8
  50. package/dist/lib/components/ui/collections/data-table/virtual-option.d.ts +9 -3
  51. package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +2 -0
  52. package/dist/lib/components/ui/collections/server-table/partials/server-table-passed-through.d.ts +1 -0
  53. package/dist/lib/components/ui/collections/server-table/types.d.ts +1 -1
  54. package/dist/lib/components/ui/display/chip/chip.variants.d.ts +5 -4
  55. package/dist/lib/components/ui/display/code/code-block-parts.d.ts +1 -1
  56. package/dist/lib/components/ui/display/code/code-block.d.ts +4 -1
  57. package/dist/lib/components/ui/display/code/languages.d.ts +2 -0
  58. package/dist/lib/components/ui/display/code/moonlight-tokens.d.ts +10 -10
  59. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-props.d.ts +73 -0
  60. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-rules.d.ts +12 -0
  61. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog.d.ts +5 -61
  62. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-footer.d.ts +12 -0
  63. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-phrase.d.ts +26 -0
  64. package/dist/lib/components/ui/feedback/confirm-dialog/use-confirm-action.d.ts +1 -1
  65. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  66. package/dist/lib/components/ui/fields/index.d.ts +1 -0
  67. package/dist/lib/components/ui/fields/quantity-field/types.d.ts +1 -1
  68. package/dist/lib/components/ui/fields/quantity-stepper/index.d.ts +2 -0
  69. package/dist/lib/components/ui/fields/quantity-stepper/quantity-stepper-model.d.ts +9 -0
  70. package/dist/lib/components/ui/fields/quantity-stepper/quantity-stepper-value.d.ts +15 -0
  71. package/dist/lib/components/ui/fields/quantity-stepper/quantity-stepper.d.ts +9 -0
  72. package/dist/lib/components/ui/fields/quantity-stepper/quantity-stepper.types.d.ts +35 -0
  73. package/dist/lib/components/ui/fields/quantity-stepper/use-quantity-stepper.d.ts +28 -0
  74. package/dist/lib/components/ui/layout/index.d.ts +1 -0
  75. package/dist/lib/components/ui/layout/pane-layout/pane-layout.d.ts +8 -3
  76. package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-classes.d.ts +2 -2
  77. package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-context.d.ts +9 -0
  78. package/dist/lib/components/ui/layout/pane-layout/partials/use-pane-layout-state.d.ts +7 -2
  79. package/dist/lib/components/ui/layout/panel-card/index.d.ts +1 -0
  80. package/dist/lib/components/ui/layout/panel-card/panel-card.d.ts +23 -0
  81. package/dist/lib/components/ui/primitives/collapsible/collapsible.d.ts +9 -1
  82. package/dist/lib/components/ui/primitives/tabs/partials/tabs-list.d.ts +6 -1
  83. package/dist/lib/components/ui/primitives/tabs/tabs-classes.d.ts +1 -0
  84. package/dist/lib/components/ui/selection/chip-group-field/chip-group-field.d.ts +8 -0
  85. package/dist/lib/components/ui/selection/chip-group-field/chip-group-field.types.d.ts +6 -0
  86. package/dist/lib/components/ui/selection/chip-group-field/partials/chip-group-reasons.d.ts +10 -0
  87. package/dist/lib/components/ui/selection/chip-group-field/partials/use-chip-rail.d.ts +12 -0
  88. package/dist/lib/components/ui/selection/choice-group/choice-group-root.d.ts +5 -2
  89. package/dist/lib/components/ui/selection/index.d.ts +1 -0
  90. package/dist/lib/components/ui/selection/status-menu/index.d.ts +3 -0
  91. package/dist/lib/components/ui/selection/status-menu/partials/status-confirm.d.ts +12 -0
  92. package/dist/lib/components/ui/selection/status-menu/partials/status-icon.d.ts +6 -0
  93. package/dist/lib/components/ui/selection/status-menu/partials/status-items.d.ts +18 -0
  94. package/dist/lib/components/ui/selection/status-menu/partials/status-menu-list.d.ts +14 -0
  95. package/dist/lib/components/ui/selection/status-menu/partials/status-sheet-row.d.ts +11 -0
  96. package/dist/lib/components/ui/selection/status-menu/partials/status-sheet.d.ts +21 -0
  97. package/dist/lib/components/ui/selection/status-menu/partials/status-trigger.d.ts +18 -0
  98. package/dist/lib/components/ui/selection/status-menu/status-menu-data.d.ts +12 -0
  99. package/dist/lib/components/ui/selection/status-menu/status-menu-form-field.d.ts +20 -0
  100. package/dist/lib/components/ui/selection/status-menu/status-menu.d.ts +13 -0
  101. package/dist/lib/components/ui/selection/status-menu/types.d.ts +76 -0
  102. package/dist/lib/components/ui/selection/status-menu/use-status-change.d.ts +18 -0
  103. package/dist/lib/components/ui/selection/status-menu/use-status-menu.d.ts +30 -0
  104. package/dist/lib/components/ui/widgets/calculator/partials/calculator-keypad.d.ts +1 -5
  105. package/dist/lib/components/ui/widgets/calculator/variants/key-layout.d.ts +3 -49
  106. package/dist/lib/components/ui/widgets/keypad/cash-model.d.ts +16 -0
  107. package/dist/lib/components/ui/widgets/keypad/index.d.ts +5 -0
  108. package/dist/lib/components/ui/widgets/keypad/key-layout.d.ts +56 -0
  109. package/dist/lib/components/ui/widgets/keypad/keypad-binding.d.ts +32 -0
  110. package/dist/lib/components/ui/widgets/keypad/keypad-cash.d.ts +28 -0
  111. package/dist/lib/components/ui/widgets/keypad/keypad-display.d.ts +11 -0
  112. package/dist/lib/components/ui/widgets/keypad/keypad-entry.d.ts +39 -0
  113. package/dist/lib/components/ui/widgets/keypad/keypad-grid.d.ts +27 -0
  114. package/dist/lib/components/ui/widgets/{calculator/partials/calculator-key.d.ts → keypad/keypad-key.d.ts} +6 -3
  115. package/dist/lib/components/ui/widgets/keypad/keypad-layout.d.ts +19 -0
  116. package/dist/lib/components/ui/widgets/{calculator/partials → keypad}/keypad-navigation.d.ts +1 -1
  117. package/dist/lib/components/ui/widgets/keypad/keypad.d.ts +9 -0
  118. package/dist/lib/components/ui/widgets/keypad/keypad.types.d.ts +91 -0
  119. package/dist/lib/components/ui/widgets/keypad/use-keypad-cash.d.ts +14 -0
  120. package/dist/lib/components/ui/widgets/keypad/use-keypad-keyboard.d.ts +7 -0
  121. package/dist/lib/components/ui/widgets/keypad/use-keypad-number.d.ts +10 -0
  122. package/dist/lib/components/ui/widgets/keypad/use-keypad-quantity.d.ts +16 -0
  123. package/dist/lib/components/ui/widgets/keypad/use-keypad-value.d.ts +17 -0
  124. package/dist/lib/components/ui/widgets/keypad/use-keypad.d.ts +23 -0
  125. package/dist/lib/components/ui/widgets/native-charts/core/label-placement.d.ts +25 -0
  126. package/dist/lib/components/ui/widgets/native-charts/core/reference-geometry.d.ts +3 -2
  127. package/dist/lib/hooks/index.d.ts +1 -0
  128. package/dist/lib/hooks/use-barcode-scanner.d.ts +39 -0
  129. package/dist/lib/i18n/resources/en/en.d.ts +28 -0
  130. package/dist/lib/i18n/resources/en/material.json.d.ts +30 -2
  131. package/dist/lib/i18n/resources/fr/fr.d.ts +28 -0
  132. package/dist/lib/i18n/resources/fr/material.json.d.ts +30 -2
  133. package/dist/lib/i18n/resources/resources.d.ts +56 -0
  134. package/dist/lib/i18n/translator.d.ts +1 -1
  135. package/dist/lib/keypad/index.d.ts +20 -0
  136. package/dist/lib/print/output/csv.d.ts +37 -0
  137. package/dist/lib/print/output/index.d.ts +1 -0
  138. package/dist/lib.js +1 -1
  139. package/dist/main.js +54 -53
  140. package/dist/map.js +9 -9
  141. package/dist/material.css +1 -1
  142. package/dist/pdf-reader.js +1 -1
  143. package/dist/{percent-field-DGlU_xii.js → percent-field-sAq7BaxN.js} +15 -14
  144. package/dist/{picker-surface-CGhqNR8a.js → picker-surface--6E-nvpU.js} +4 -4
  145. package/dist/{popover-ZxL7ktKk.js → popover-bEB8I_-w.js} +1 -1
  146. package/dist/{pos-types-BQ0QpYMp.js → pos-types-Cpdc4E33.js} +1 -1
  147. package/dist/{pos-variant-CKy8h_5i.js → pos-variant-ClMGu59I.js} +25 -25
  148. package/dist/print.js +146 -127
  149. package/dist/{programmer-variant-CbHstE28.js → programmer-variant-ChzgyA7x.js} +30 -30
  150. package/dist/{public-D-tdOz4b.js → public-BY8N5NDT.js} +9 -9
  151. package/dist/{rich-code-block-CTPj1LuD.js → rich-code-block-D2GiWuqD.js} +18 -18
  152. package/dist/{scientific-variant-B8U9h3II.js → scientific-variant-31_93gcZ.js} +8 -8
  153. package/dist/{sortable-provider-CSoWE7Ce.js → sortable-provider-DGiVn72t.js} +2 -2
  154. package/dist/{spinner-ffuwBXHl.js → spinner-CEVomQ5K.js} +1 -1
  155. package/dist/standard-layout-DTr9yy8Z.js +48 -0
  156. package/dist/{tabs-JgRq6ZJx.js → tabs-B8MXBbPW.js} +14 -14
  157. package/dist/{text-field-DURkIP-v.js → text-field-DN88qD0Q.js} +16 -15
  158. package/dist/theme-picker.js +13 -13
  159. package/dist/{theme-toggle-n_Zg7JAA.js → theme-toggle-C7pnu1Xh.js} +1 -1
  160. package/dist/{toolbar-items-IANZdcw4.js → toolbar-items-BDfoh8QB.js} +4 -4
  161. package/dist/{translate-material-CMHUH5Ha.js → translate-material-s_lxIV43.js} +1 -1
  162. package/dist/{translator-9zA-Um3W.js → translator-Cgql9srO.js} +56 -4
  163. package/dist/use-calculator-keyboard-DysnFK8m.js +222 -0
  164. package/dist/{use-chat-composer-BoIN7RCA.js → use-chat-composer-xCcp9fVc.js} +2250 -2195
  165. package/dist/{use-heading-spy-Ciw9GBHL.js → use-heading-spy-FIZlf9qj.js} +235 -154
  166. package/dist/{use-material-translation-CLNaMhUf.js → use-material-translation-xNzbSVgS.js} +1 -1
  167. package/dist/{use-return-focus-DWhbTGVe.js → use-return-focus-CW9KkDkp.js} +1 -1
  168. package/docs/README.md +2 -1
  169. package/docs/components/README.md +2 -0
  170. package/docs/components/buttons-and-actions.md +81 -2
  171. package/docs/components/data-display.md +6 -3
  172. package/docs/components/data-table-filter-and-lists.md +19 -6
  173. package/docs/components/feedback-and-status.md +20 -0
  174. package/docs/components/form-fields.md +60 -7
  175. package/docs/components/navigation-and-layout.md +48 -2
  176. package/docs/components/primitives.md +44 -2
  177. package/docs/components/rich-widgets.md +28 -0
  178. package/docs/components/selection-and-choice.md +176 -1
  179. package/docs/guides/calculator.md +1 -1
  180. package/docs/guides/cards-and-tiles.md +31 -24
  181. package/docs/guides/csp.md +4 -3
  182. package/docs/guides/data-grid.md +5 -4
  183. package/docs/guides/field-look-refined.md +2 -2
  184. package/docs/guides/forms.md +1 -0
  185. package/docs/guides/helpers.md +49 -0
  186. package/docs/guides/keypad.md +233 -0
  187. package/docs/guides/printing.md +51 -1
  188. package/docs/previews/buttons-actions-producttile.webp +0 -0
  189. package/docs/previews/data-display-codeblock.webp +0 -0
  190. package/docs/previews/data-display-codegroup.webp +0 -0
  191. package/docs/previews/data-display-jsonviewer.webp +0 -0
  192. package/docs/previews/form-fields-quantitystepper.webp +0 -0
  193. package/docs/previews/navigation-layout-panelcard.webp +0 -0
  194. package/docs/previews/rich-widgets-keypad.webp +0 -0
  195. package/docs/previews/selection-choice-statusmenu.webp +0 -0
  196. package/docs/roadmap.md +21 -0
  197. package/docs/troubleshooting.md +68 -4
  198. package/lib/components/ui/actions/action-card/action-card-tile.css +47 -0
  199. package/lib/components/ui/display/chip/chip-soft.css +27 -2
  200. package/lib/components/ui/display/code/code-moonlight.css +10 -10
  201. package/lib/components/ui/primitives/collapsible/collapsible.css +9 -0
  202. package/lib/components/ui/widgets/calculator/calculator-keys.css +3 -0
  203. package/lib/components/ui/widgets/calculator/calculator.css +118 -0
  204. package/package.json +7 -2
  205. package/dist/field-blur-context-BLGgf_3z.js +0 -73
  206. package/dist/key-layout-cR79xfGE.js +0 -38
  207. package/dist/standard-layout-BuSRr3TY.js +0 -40
@@ -1,6 +1,6 @@
1
1
  import { o as e, r as t } from "./dist-DVRQkaTK.js";
2
2
  import { i as n, n as r, r as i, t as a } from "./dist-DWZXlCZR.js";
3
- import { C as o, S as s, x as c } from "./spinner-ffuwBXHl.js";
3
+ import { C as o, S as s, x as c } from "./spinner-CEVomQ5K.js";
4
4
  import { a as l, i as u, r as d } from "./dist-DHdUUM1w.js";
5
5
  import { c as f, i as p, l as m, o as h, r as g, s as ee } from "./in-dialog-D61Yeaol.js";
6
6
  import { r as te } from "./escape-owner-DpaP0lm1.js";
package/docs/README.md CHANGED
@@ -11,6 +11,7 @@ detail: every option, the second example, and the traps.
11
11
  | [Themes for your app](guides/themes.md) | You define your own themes: `defineTheme` modules, the Vite plugin's `themes` option, the `nxgt-theme` command and its CI check, the tone warnings |
12
12
  | [Barcodes and label sheets](guides/barcode.md) | You draw a Code 128 or Data Matrix code and print a sheet of labels with `@nxgt/material/barcode`: `Barcode`, `LabelSheet` and `Label`, label size, columns, rows, gaps and a start offset, dark mode, print and accessibility |
13
13
  | [Calculator](guides/calculator.md) | You add an exact-decimal calculator with `@nxgt/material/calculator` (and `CalculatorField` with `@nxgt/material/calculator-field`): `Calculator`, the keyboard, the tape and memory, copy and paste, what is stored, feedback, sizing and accessibility |
14
+ | [Keypad](guides/keypad.md) | You add a numeric keypad outside the calculator with `@nxgt/material/keypad`: quantity, price, percentage and cash-tendered entry, decimal or minor-unit digits, calculation keys, field bindings (`useKeypadNumber`, `useKeypadQuantity`), the cash variant with change and bill shortcuts, the keyboard and accessibility |
14
15
  | [Charts](guides/charts.md) | You draw charts with `@nxgt/material/charts`: each chart, the shared props, reference bands, thresholds and point tones, zoom and phone scroll, the keyboard, accessibility, theming the `--chart-1..5` tokens, and moving off the Nivo charts, removed in 3.0 |
15
16
  | [Cards and tiles](guides/cards-and-tiles.md) | You want a `DataTable` as a list of cards or tiles: `layout`, `renderCard`, `renderTile`, the card's one link, selection, the sort control, columns by the list's width |
16
17
  | [EventCalendar](guides/event-calendar.md) | You mark periods with `spans`, label days and weeks with `dayBadges` and `weekBadges`, colour events with `tone`, cancel them with `status`, or make them read-only with `readOnlyEvents` |
@@ -21,7 +22,7 @@ detail: every option, the second example, and the traps.
21
22
  | [Layouts](guides/layouts.md) | You are choosing a navigation preset, an app bar variant or a page layout, and want one working example of each |
22
23
  | [Animations](guides/animations.md) | You animate with `animate-*` classes: setup next to `tw-animate-css`, every animation with its defaults, the modifiers, stagger, text splitting, scroll-driven animation, appear/disappear, reduced motion, recipes and browser support |
23
24
  | [Theming](guides/theming.md) | You want to change colours or radius, use a shipped theme (`@nxgt/material/themes/<name>.css`) or a themed zone, add dark mode without a flash, use tones (the `data-tone` channel and app tones), or use the compiled CSS without Tailwind |
24
- | [Printing](guides/printing.md) | You render PDF documents with `@nxgt/material/print`, turn them into a download, a new tab or a server response, write them as plain data, or bundle them into an SSR server |
25
+ | [Printing](guides/printing.md) | You render PDF documents with `@nxgt/material/print`, turn them into a download, a new tab or a server response, write them as plain data, bundle them into an SSR server, or export a list as CSV with `toCsv` and `csvResponse` |
25
26
  | [PDF reader](guides/pdf-reader.md) | You render PDFs with `@nxgt/material/pdf-reader`: the pdf.js assets and worker, `configurePdfReader`, server rendering, the Content-Security-Policy and moving from the classic reader (removed in 3.0) |
26
27
  | [Media players](guides/media.md) | You play video or audio: embedded, audio cards, playlists, chapters and transcript, sound across pages with `MediaPlayerHost`, the lightbox, CSP and keyboard |
27
28
  | [Streaming (HLS)](guides/media-streaming.md) | You play `.m3u8` streams in `MediaPlayer` with `@nxgt/material/media-hls` |
@@ -34,6 +34,7 @@ Anything you press that is not a field: buttons, split buttons, floating action
34
34
  [<img src="../previews/buttons-actions-toolbar.webp" height="72" alt="Toolbar">](buttons-and-actions.md#toolbar)
35
35
  [<img src="../previews/buttons-actions-iconbutton.webp" height="72" alt="IconButton">](buttons-and-actions.md#iconbutton)
36
36
  [<img src="../previews/buttons-actions-actioncard.webp" height="72" alt="ActionCard">](buttons-and-actions.md#actioncard)
37
+ [<img src="../previews/buttons-actions-producttile.webp" height="72" alt="ProductTile">](buttons-and-actions.md#producttile)
37
38
 
38
39
  ## [Form fields](form-fields.md)
39
40
 
@@ -65,6 +66,7 @@ Picking among options: select, combobox, autocomplete, checkbox, radio, card and
65
66
  [<img src="../previews/selection-choice-autocomplete.webp" height="72" alt="Autocomplete">](selection-and-choice.md#autocomplete)
66
67
  [<img src="../previews/selection-choice-choicecardgroup.webp" height="72" alt="ChoiceCardGroup">](selection-and-choice.md#choicecardgroup)
67
68
  [<img src="../previews/selection-choice-transferlist.webp" height="72" alt="TransferList">](selection-and-choice.md#transferlist)
69
+ [<img src="../previews/selection-choice-statusmenu.webp" height="72" alt="StatusMenu">](selection-and-choice.md#statusmenu)
68
70
 
69
71
  ## [Data display](data-display.md)
70
72
 
@@ -24,6 +24,7 @@ and language switches. Everything is imported from `@nxgt/material/components`.
24
24
  | <img src="../previews/buttons-actions-scrolltotop.webp" width="96" alt=""> | [ScrollToTop](#scrolltotop) | Appears past a scroll threshold |
25
25
  | <img src="../previews/buttons-actions-scrolltolatestbutton.webp" width="96" alt=""> | [ScrollToLatestButton](#scrolltolatestbutton) | "Jump to latest" over a scroll box, counting what arrived |
26
26
  | <img src="../previews/buttons-actions-actioncard.webp" width="96" alt=""> | [ActionCard](#actioncard) | Clickable card, a button (`aria-pressed`) when it chooses or reacts, with an active indicator; `status`, `meta`, a trailing `action` and a `tile` variant |
27
+ | <img src="../previews/buttons-actions-producttile.webp" width="96" alt=""> | [ProductTile](#producttile) | Large tap tile for a till or kiosk catalogue: picture or icon, name, price, stock state |
27
28
  | <img src="../previews/buttons-actions-themetoggle.webp" width="96" alt=""> | [ThemeToggle](#themetoggle) | Light / dark / system switch |
28
29
  | <img src="../previews/buttons-actions-languageswitcher.webp" width="96" alt=""> | [LanguageSwitcher](#languageswitcher) | Menu of the `I18nProvider` languages |
29
30
 
@@ -1199,7 +1200,7 @@ import { Banknote, CreditCard } from 'lucide-react';
1199
1200
  | `title`, `description`, `icon` | `ReactNode` | — | the content |
1200
1201
  | `status` | `ReactNode` | — | a line under the title; a string or number becomes a soft `Badge`, a node is shown as given |
1201
1202
  | `meta` | `ReactNode` | — | a secondary line, one line with an ellipsis |
1202
- | `action` | `ReactNode` | — | a trailing control outside the press surface, see [tile](#actioncard-tile); `title` is then required, a `role` moves to the title button; the card's `disabled` does not reach it, disable its control too |
1203
+ | `action` | `ReactNode` | — | a trailing control outside the press surface, see [tile](#actioncard-tile); `title` is then required, a `role` moves to the title button; the card's `disabled` does not reach it, disable its control too; in a `tile` it wraps under a narrow title |
1203
1204
 
1204
1205
  A card with `active`, `onClick`, `disabled` or your own `role` is `role="button"` (unless you set another): it takes the Tab stop, and
1205
1206
  Enter and Space press it. `active` sets `aria-pressed`; pass your own `role`
@@ -1218,7 +1219,39 @@ For a card choice bound to a form value, see `ChoiceCardGroup` on
1218
1219
  `variant="tile"` is a compact grid cell for boards: no minimum width (it fills
1219
1220
  its grid track), the title and the `action` on the first row, then `status`,
1220
1221
  `description` and `meta`, and no indicator unless `withIndicator`. `status` and
1221
- `meta` work on every variant.
1222
+ `meta` work on every variant. The `action` wraps under a narrow title instead of
1223
+ overflowing the tile.
1224
+
1225
+ A status that changes in place is a [`StatusMenu`](selection-and-choice.md#statusmenu)
1226
+ in `action`: the chip shows the status (so the card needs no `status` badge) and
1227
+ opens the choices, in a bottom sheet on a phone. Its button is named "Status of
1228
+ Unit 21: Free". The chip is 6 to 10rem wide (a long status word is wider). A tile
1229
+ with an `action` is a wrapping line: the title may shrink to 4.5rem
1230
+ (`--ac-title-min`), and an action that does not fit beside it drops under the
1231
+ title, so a chip fits in a two-per-row `xs` grid on a 360px phone and an icon
1232
+ button such as `RowMenu` stays beside the title. A wrapped action starts at the tile's line start (under the icon when the tile
1233
+ has one), not under the title text.
1234
+
1235
+ ```tsx
1236
+ import { ActionCard, StatusMenu } from '@nxgt/material/components';
1237
+
1238
+ <ActionCard
1239
+ variant="tile"
1240
+ title="Unit 21"
1241
+ meta="Zone B, second floor"
1242
+ onClick={() => openDetail(unit.id)}
1243
+ action={
1244
+ <StatusMenu
1245
+ label="Unit 21"
1246
+ value={unit.status}
1247
+ choices={statuses}
1248
+ onSelect={(next) => saveStatus(unit.id, next)} // a promise keeps the old status with a spinner
1249
+ />
1250
+ }
1251
+ />;
1252
+ ```
1253
+
1254
+ A compact icon-button action, with a `status` badge, uses `RowMenu`:
1222
1255
 
1223
1256
  ```tsx
1224
1257
  import {
@@ -1303,6 +1336,52 @@ A whole board built on it, with sections (each its own h2 and free count) and, w
1303
1336
 
1304
1337
  ---
1305
1338
 
1339
+ ## ProductTile
1340
+
1341
+ ![ProductTile](../previews/buttons-actions-producttile.webp)
1342
+
1343
+ A large, even tap tile for a point-of-sale or kiosk catalogue: a picture (or a toned shape with an icon), the item's name, a prominent price and a stock state on its own line. `ActionCard` has no media or price slot; this is the tile for a catalogue.
1344
+
1345
+ ```tsx
1346
+ import { Badge, ProductTile, ResponsiveGrid } from '@nxgt/material/components';
1347
+
1348
+ <ResponsiveGrid minItem="9rem" gap="md">
1349
+ <ProductTile
1350
+ title="Orange juice 1 L"
1351
+ price="1,500"
1352
+ icon="bottle-water"
1353
+ selected={basket.has(id)}
1354
+ onClick={() => add(id)}
1355
+ />
1356
+ <ProductTile
1357
+ title="Free-range eggs, tray of thirty"
1358
+ price="4,800"
1359
+ image="/eggs.jpg"
1360
+ disabled
1361
+ status={<Badge variant="soft" tone="error">Out of stock</Badge>}
1362
+ />
1363
+ </ResponsiveGrid>
1364
+ ```
1365
+
1366
+ | Prop | Type | Default | Effect |
1367
+ | --- | --- | --- | --- |
1368
+ | `title` | `ReactNode` | required | The name, up to two lines |
1369
+ | `price` | `ReactNode` | none | Large and tabular (`tabular-nums`), at the foot of the tile; format it with the app's locale |
1370
+ | `status` | `ReactNode` | none | The stock state, on its own line under the price (a `Badge`) |
1371
+ | `image` | `string` | none | The photo's URL (a square one fits best), in a tinted 4:3 well, lazy loaded, with an empty `alt` (the title names the tile). If it fails to load, the toned `icon` shape takes its place |
1372
+ | `imageFit` | `'contain' \| 'cover'` | `contain` | `contain` for a cut-out PNG or a packshot on white, `cover` for a full-bleed photo |
1373
+ | `icon`, `tone`, `shape` | `IconName`, `Tone`, `ShapeName` | `primary`, `squircle` | Without an `image` (or when it fails), a toned shape with this icon, in the same 4:3 well, so tiles with and without a photo share one height |
1374
+ | `media` | `ReactNode` | none | Your own media in place of the box; replaces `image` and `icon` |
1375
+ | `selected` | `boolean` | `false` | `aria-pressed`, a tint and a bar at the start (the item is in the basket) |
1376
+ | `disabled` | `boolean` | `false` | Dimmed and not tappable; its text, status included, is still read |
1377
+ | `asChild` | `boolean` | `false` | Render inside the one child (a router `Link`): `<ProductTile asChild title="…"><Link to="…" /></ProductTile>`. `selected` is then `aria-current`, `disabled` is `aria-disabled`, `tabIndex={-1}` and a cancelled click, so a link leaves the Tab order and Enter does not follow it; `onClick` is not called |
1378
+
1379
+ The tile is one `button` named by its text: `getByRole('button', { name: /Orange juice.*1,500/ })`. It is a hairline card, at least 44 px high under a coarse pointer, and fills its grid cell (`h-full`): the name takes two lines and the status line is always reserved, so tiles of a row share one height. `minItem="9rem"` gives two columns at 360 px. It has a pressed state, a focus ring and a dark mode. It takes the `button`'s props, so `className` adds to it.
1380
+
1381
+ **Storybook:** `Buttons & actions/ProductTile` · **Vue:** later
1382
+
1383
+ ---
1384
+
1306
1385
  ## ThemeToggle
1307
1386
 
1308
1387
  ![ThemeToggle](../previews/buttons-actions-themetoggle.webp)
@@ -601,7 +601,7 @@ text colour, 4.5:1 or more on the page and in a card; the `outlined` hairline
601
601
  and the `tag` dot still read `--chip-tone`, not the tone channel); `tag`
602
602
  a smaller tag led by a colour dot; `dashed` a muted "add" pill. 28px tall,
603
603
  24px with `size="sm"`, 44px with `size="lg"`. Selected (`active`, or `aria-pressed` on a toggle
604
- chip) takes a primary tint and a primary edge, and a pressed toggle with
604
+ chip) takes a primary tint and a primary edge (a pressed toggle chip with a `color` shows the filled tone under its on-colour instead), and a pressed toggle with
605
605
  nothing leading draws a check. An unselected neutral toggle chip keeps a
606
606
  boundary of at least 3:1 against the page and the card. The label is truncated as one piece; the ✕
607
607
  and `trailing` stay whole.
@@ -631,7 +631,7 @@ import { Avatar, Chip } from '@nxgt/material/components';
631
631
  | `active` | `boolean` | — | selected: primary tint and edge |
632
632
  | `aria-pressed` | `boolean` | — | a toggle chip: selected while pressed, with a check when nothing leads |
633
633
  | `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | 28px, 24px, or `lg`, a 44px box at every pointer (the touch target); every target stays at least 24px |
634
- | `color` | `'default' \| 'primary' \| 'secondary' \| 'info' \| 'success' \| 'warning' \| 'error'` | neutral | tone: the edge (outlined), the fill and label (tonal, `filled`: a status look) or the dot and tint (tag). A `tag` without `color` (or with `default`) draws no dot |
634
+ | `color` | `'default'` or any `Tone` (the eight named tones, or one declared in `AppTones`) | neutral | tone: the edge (outlined), the fill and label (tonal, `filled`: a status look) or the dot and tint (tag). A `tag` without `color` (or with `default`) draws no dot |
635
635
  | `onDismiss` | `() => void` | — | adds the ✕ button, named "Remove {label}" (`material.chip.dismiss-label`), `{label}` being the text `label` shows, a node's included; "Remove" when it shows none; the ✕ is drawn 18px (16px small) in its own 24x24 box (44x44 on a coarse pointer) |
636
636
  | `dismissLabel` | `string \| ((label: string) => string)` | — | names the ✕ instead of "Remove {label}": a string as is, or a function of the text the chip shows |
637
637
  | `onClick` | `MouseEventHandler` | — | makes the chip (or its body, when dismissible) a button; without it the chip is a `<span>` |
@@ -1652,7 +1652,7 @@ The colour shows as a thin accent bar inset from the chip's edges (never a borde
1652
1652
 
1653
1653
  ![CodeBlock](../previews/data-display-codeblock.webp)
1654
1654
 
1655
- Highlighted code with line numbers and copy, a collapsible JSON tree, and tabbed samples (`Tabs`, underlined).
1655
+ Highlighted code (or plain text) with line numbers and copy, a collapsible JSON tree, and tabbed samples (`Tabs`, underlined).
1656
1656
 
1657
1657
  <img src="../previews/data-display-codegroup.webp" height="96" alt="CodeGroup">
1658
1658
  <img src="../previews/data-display-jsonviewer.webp" height="96" alt="JsonViewer">
@@ -1661,6 +1661,7 @@ Highlighted code with line numbers and copy, a collapsible JSON tree, and tabbed
1661
1661
  import { CodeBlock, CodeGroup, JsonViewer } from '@nxgt/material/components';
1662
1662
 
1663
1663
  <CodeBlock code={source} language="typescript" filename="greet.ts" highlightLines={[2]} />
1664
+ <CodeBlock code={dnsRecord} language="text" label={false} />
1664
1665
  <CodeGroup tabs={[
1665
1666
  { label: 'bun', language: 'bash', code: 'bun add @nxgt/material' },
1666
1667
  { label: 'npm', language: 'bash', code: 'npm i @nxgt/material' },
@@ -1671,6 +1672,8 @@ import { CodeBlock, CodeGroup, JsonViewer } from '@nxgt/material/components';
1671
1672
  | Prop | Type | Default | Effect |
1672
1673
  | --- | --- | --- | --- |
1673
1674
  | `scheme` | `'dark' \| 'light'` | follows the theme | forces the palette (all three) |
1675
+ | `language` | `string` | `typescript` | `CodeBlock`: one of 16 ids or aliases, or `text` (also `plaintext`, `plain`, `txt`) to show the code as is, labelled "Text". An unknown id still falls back to `typescript` |
1676
+ | `label` | `ReactNode \| false` | the language's name | `CodeBlock`: replaces the language label; `false` hides it |
1674
1677
  | `showLineNumbers` | `boolean` | `true` | `CodeBlock`, `CodeGroup` |
1675
1678
  | `highlightLines` | `number[]` | — | `CodeBlock`: marked lines |
1676
1679
  | `maxHeight` | `number \| string` | — | `CodeBlock`, `JsonViewer`: scrolls past it |
@@ -107,9 +107,11 @@ table enables, for typing your own column defs.
107
107
  | `onLoadMore` / `isLoadingMore` | `() => void` / `boolean` | — | adds a "load more" button under the rows |
108
108
  | `options` | `{ showPage?: boolean; showSelected?: boolean }` | both `true` | parts of the footer |
109
109
  | `showHeader` | `boolean` | shown | header row (column names, sorting, "Select all") at every width; `false` hides it |
110
+ | `showSort` | `boolean` | shown | `false` drops the sort control of the `cards` and `tiles` list bar (on a phone too); columns stay sortable and a `table` keeps its header sorting ([Cards and tiles](../guides/cards-and-tiles.md#sorting)) |
110
111
  | `maxHeight` | `number \| string` | none | caps the table's height (`400`, `'60vh'`); the rows scroll inside it |
112
+ | `maxHeightClassName` | class | none | the same cap as a Tailwind class (`'max-h-[28rem]'`), with no inline `style` on the scroll region, server included; wins over `maxHeight` |
111
113
  | `scrollLabel` | `string` | "Scrollable table" | the name of the scroll region the table becomes while it overflows |
112
- | `virtual` | `boolean \| { estimateSize?, overscan?, ssrRows? }` | off | renders only the rows in view, for long lists; needs `maxHeight`. Client mode shows every filtered and sorted row with no paging and no pagination footer; `manual` virtualizes the rows given. Defaults: `estimateSize` 36 (px, a comfortable row without the gap between rows), `overscan` 8, `ssrRows` 50. `renderExpandedRow` is ignored while it is on ([long lists](#long-lists)) |
114
+ | `virtual` | `boolean \| { estimateSize?, overscan?, ssrRows? }` | off | renders only the rows in view, for long lists; needs `maxHeight` or `maxHeightClassName`. Client mode shows every filtered and sorted row with no paging and no pagination footer; `manual` virtualizes the rows given. Defaults: `estimateSize` 36 (px, a comfortable row without the gap between rows), `overscan` 8, `ssrRows` 50. `renderExpandedRow` is ignored while it is on ([long lists](#long-lists)) |
113
115
  | `loading` | `boolean` | `false` | skeleton rows, or the current rows dimmed; `aria-busy`; controls disabled |
114
116
  | `loadingRows` | `number` | the page size | how many skeleton rows |
115
117
  | `density` | `'comfortable' \| 'compact'` | `'comfortable'` | `compact` tightens the cell padding, the row and header heights and the gap between rows, the selection column included; it is the table's `data-density` attribute, styled with classes (no inline style). The toolbar band and the empty state keep their size; the print variant is unchanged |
@@ -527,12 +529,22 @@ Rows stay rows on a phone unless you ask for `layout="cards"`, `layout="tiles"`
527
529
  virtual
528
530
  maxHeight="70vh"
529
531
  />
532
+
533
+ // Under a strict CSP (`style-src-attr 'none'`): a class, no inline style.
534
+ <DataTable
535
+ data={people}
536
+ columns={columns}
537
+ getRowId={(person) => person.id}
538
+ virtual
539
+ maxHeightClassName="max-h-[28rem]"
540
+ />
530
541
  ```
531
542
 
532
543
  `virtual` renders only the rows in view and a few around them (`overscan`),
533
544
  between two `aria-hidden` spacer rows that stand for the rest. It needs
534
- `maxHeight`, since the rows scroll inside the table: without it, `virtual`
535
- is off and a development warning says so. In client mode every filtered and
545
+ `maxHeight` or `maxHeightClassName`, since the rows scroll inside the table
546
+ (the virtualizer measures the scroll region, so the class needs no number):
547
+ without either, `virtual` is off and a development warning says so. In client mode every filtered and
536
548
  sorted row is in one scrolling list: no paging and no pagination footer
537
549
  (`onLoadMore` still shows its button). In `manual` mode the rows given are
538
550
  virtualized as they are, with the server footer.
@@ -556,8 +568,9 @@ virtualized as they are, with the server footer.
556
568
  takes over after mount. The spacers' heights are custom properties
557
569
  (`--vr-before`, `--vr-after`) written on the `<tbody>` through CSSOM, which
558
570
  `style-src-attr` does not govern: the rows add no `style` attribute to the
559
- server markup. The scroll region's own `maxHeight` is still an inline
560
- `style` there ([csp.md](../guides/csp.md)).
571
+ server markup. The scroll region's `maxHeight` is an inline
572
+ `style` there; pass `maxHeightClassName` instead and it has none
573
+ ([csp.md](../guides/csp.md)).
561
574
 
562
575
  `@nxgt/material-vue`'s `DataTable` has no `virtual` yet.
563
576
 
@@ -1070,7 +1083,7 @@ export default function Students() {
1070
1083
  and its "Clear search and filters" button
1071
1084
  (`onParamsChange(withoutFilters(params))`, then `onClearFilters`, or the
1072
1085
  table, or the list of cards or tiles, takes the focus). Your search box and switches stay outside.
1073
- - **Passed through** to `DataTable`: `columns`, `maxHeight`, `scrollLabel`,
1086
+ - **Passed through** to `DataTable`: `columns`, `maxHeight`, `maxHeightClassName`, `scrollLabel`,
1074
1087
  `showViewOptions`, `renderExpandedRow`, `options`, `loading`,
1075
1088
  `loadingRows`, `getRowHref`, `onRowClick`, `linkComponent`, `density`,
1076
1089
  `layout`, `enableRowSelection`, `rowSelectionDisabledReason`,
@@ -250,6 +250,8 @@ function DeleteProject({ onDelete }: { onDelete: () => Promise<void> }) {
250
250
  | `details` | `ReactNode` | — | a note under the description, read with it by assistive technology, for what else is affected or cannot be undone |
251
251
  | `refusal` | `(refusal: ConfirmRefusalContext) => ReactNode` | the message; an `ActionRefusal`'s details and action too | renders the refusal inside the error alert (still `role="alert"`, a `div`) instead of the default, so it can carry a link. Receives `{ message, cause, close }`: `message` is the thrown error's message or the translated `material.confirm-dialog.failed` text, `cause` is what `onConfirm` threw, `close()` closes the dialog |
252
252
  | `confirmLabel` / `cancelLabel` | `string` | "Confirm" / "Cancel", translated (`material.confirm-dialog.confirm` / `.cancel`) | button text |
253
+ | `confirmPhrase` | `string` | — | asks the user to type this text before Confirm enables, for a deletion worth a second thought. A field labelled by the translated `material.confirm-dialog.type-phrase` ("Type {phrase} to confirm", the phrase in code style) takes the focus on open instead of Cancel and starts empty each time the dialog opens. Paste is allowed; Enter in the field confirms only once it matches. It combines with `confirmDisabled` (both must allow). Empty or unset: no field |
254
+ | `confirmPhraseMatch` | `'exact' \| 'trim' \| 'trim-case-insensitive'` | `'exact'` | how the typed text is compared: character for character, ignoring spaces around it, or ignoring those and the letter case |
253
255
  | `cancelVariant` | `ButtonVariant` | `'tonal'` | the Cancel button's variant; `'outlined'` restores the earlier look |
254
256
  | `onCloseAutoFocus` | `(event: Event) => void` | — | called as the dialog closes, before the focus goes back; `event.preventDefault()` places it yourself, e.g. `ctx.focusTrigger()` of a `ServerTable` bulk action whose button a redraw replaced |
255
257
 
@@ -269,6 +271,24 @@ function DeleteProject({ onDelete }: { onDelete: () => Promise<void> }) {
269
271
  />
270
272
  ```
271
273
 
274
+ **Type the value to confirm.** For a deletion that should not happen by habit (a domain, a mailbox), ask for the name:
275
+
276
+ ```tsx
277
+ <ConfirmDialog
278
+ open={open}
279
+ onOpenChange={setOpen}
280
+ variant="destructive"
281
+ title="Delete example.com?"
282
+ description="Its mailboxes are deleted with it."
283
+ confirmLabel="Delete"
284
+ confirmPhrase="example.com"
285
+ confirmPhraseMatch="trim-case-insensitive"
286
+ onConfirm={remove}
287
+ />
288
+ ```
289
+
290
+ `ConfirmDialog` has no form; `ConfirmationDialog` (with `formOptions`) does not take `confirmPhrase`. The Vue twin does not have it yet.
291
+
272
292
  **The text stays while the dialog fades out.** A confirm is usually driven by a nullable subject, and closing clears it: `open={item !== null}`. `ConfirmDialog` keeps the `title`, `description`, `details`, button labels and `variant` of its last open render until it has closed, so the title does not read "Delete ?" during the exit animation. Nothing to add in the app:
273
293
 
274
294
  ```tsx
@@ -27,6 +27,7 @@ order and roles) and the tokens that tune it.
27
27
  | <img src="../previews/form-fields-numberfield.webp" width="96" alt=""> | [NumberField](#numberfield) | `number \| null` with clamping and +/− buttons |
28
28
  | <img src="../previews/form-fields-percentfield.webp" width="96" alt=""> | [PercentField](#percentfield) | `NumberField` with a `%` suffix |
29
29
  | <img src="../previews/form-fields-quantityfield.webp" width="96" alt=""> | [QuantityField](#quantityfield) | One labelled field holding an amount and its unit |
30
+ | <img src="../previews/form-fields-quantitystepper.webp" width="96" alt=""> | [QuantityStepper](#quantitystepper) | Inline −/value/+ control for a cart or ticket line |
30
31
  | <img src="../previews/form-fields-currencyfield.webp" width="96" alt=""> | [CurrencyField](#currencyfield) | Locale-formatted amount with a currency suffix |
31
32
  | <img src="../previews/form-fields-phonefield.webp" width="96" alt=""> | [PhoneField](#phonefield) | International number, emits E.164 |
32
33
  | <img src="../previews/form-fields-urlfield.webp" width="96" alt=""> | [UrlField](#urlfield) | Validates on blur, open and copy buttons |
@@ -802,7 +803,7 @@ phone: the chip truncates before the value does (see The unit chip below).
802
803
  | `control`, `name` | `Control<T>`, `Path<T>` | — | the form and its `{ value, unit }` (`QuantityFormField`) |
803
804
  | `value`, `defaultValue`, `onChange` | `QuantityValue`, `(value) => void` | empty | the amount and its unit; `onChange` gets both on each change |
804
805
  | `label` | `ReactNode` | none | the one label; it names the group |
805
- | `units` | `{ value, label }[]` | none | the units offered |
806
+ | `units` | `{ value, label }[]` | none | the units offered; without `units` and `allowCustomUnit` there is no unit to pick, so no chip is drawn (a plain count) |
806
807
  | `allowCustomUnit` | `boolean` | `false` | the unit menu gets a "Search or type a unit" input and a "Use “x”" item, so a unit outside `units` can be typed; otherwise it is a plain listbox of `units` |
807
808
  | `min`, `max`, `step`, `hideSteppers` | | `step` 1 | as `NumberField` |
808
809
  | `unitLabel`, `unitPlaceholder` | `string` | "<label> unit" | `unitLabel` names the unit part (the menu's name; the chip reads "<unitLabel>: mg"); `unitPlaceholder` shows while no unit is chosen |
@@ -812,15 +813,16 @@ phone: the chip truncates before the value does (see The unit chip below).
812
813
  | `required`, `disabled`, `placeholder`, `id`, `ref` | | | `required` applies to the number only; to require a unit, validate `unit` in the form schema. `placeholder`, `id` and `ref` reach the number input (React Hook Form's `field.ref` focuses it on an error) |
813
814
 
814
815
  - **Semantics:** a `<fieldset>` (role `group`) named by the label; the number
815
- is a textbox named after the label, the unit chip a button named "Unit: mg"
816
- (`aria-expanded`; it opens a popover), the menu a listbox named
817
- "Dose unit". The message describes the group, the number and the chip, and
818
- the number is `aria-invalid` on an error.
819
- - **Keyboard:** Tab goes value, unit chip, Decrease, Increase. Enter, Space or
816
+ is a textbox named after the label, the unit chip (when drawn) a button
817
+ named "Unit: mg" (`aria-expanded`; it opens a popover), the menu a listbox
818
+ named "Dose unit". The message describes the group, the number and the chip,
819
+ and the number is `aria-invalid` on an error. Without a chip there is no
820
+ unit button or listbox.
821
+ - **Keyboard:** Tab goes value, unit chip (when drawn), Decrease, Increase. Enter, Space or
820
822
  ArrowDown on the chip opens the menu (focus lands on the list, or on the
821
823
  search with `allowCustomUnit`); the arrows move, Enter picks, Esc closes and
822
824
  returns focus to the chip.
823
- - **The unit chip:** one focus ring for the whole box, whichever part has
825
+ - **The unit chip** (drawn only with `units` or `allowCustomUnit`; a plain `<QuantityField label="Count" />` has none, and Tab goes value, Decrease, Increase): one focus ring for the whole box, whichever part has
824
826
  focus. The chip is capped at 52% of the box and truncates a long label with
825
827
  an ellipsis (the full label is its `title`); the value keeps room for about
826
828
  4 digits at 360px. A coarse pointer gets 44px targets. Disabled, the chip
@@ -836,6 +838,57 @@ phone: the chip truncates before the value does (see The unit chip below).
836
838
 
837
839
  ---
838
840
 
841
+ ## QuantityStepper
842
+
843
+ ![QuantityStepper](../previews/form-fields-quantitystepper.webp)
844
+
845
+ A compact inline −/value/+ control for a cart or ticket line, where a
846
+ [`NumberField`](#numberfield) or [`QuantityField`](#quantityfield) (a labelled
847
+ box) is too heavy. It has no label and no border: a `group` named by your
848
+ `aria-label` ("Quantity of Orange juice") holding a minus button, the value and
849
+ a plus button. It is an uncontrolled or controlled number, not a form field.
850
+
851
+ ```tsx
852
+ import { QuantityStepper } from '@nxgt/material/components';
853
+ import { useState } from 'react';
854
+
855
+ export function Line() {
856
+ const [qty, setQty] = useState(2);
857
+ return (
858
+ <QuantityStepper
859
+ aria-label="Quantity of Orange juice"
860
+ value={qty}
861
+ onValueChange={setQty}
862
+ min={1}
863
+ max={20}
864
+ onRemove={() => removeLine()}
865
+ />
866
+ );
867
+ }
868
+ ```
869
+
870
+ | Prop | Type | Default | What it does |
871
+ | --- | --- | --- | --- |
872
+ | `aria-label` | `string` | required | Names the group and the value; the buttons are "Decrease …", "Increase …" and "Remove …" followed by it |
873
+ | `value` / `defaultValue` | `number` | `min` | Controlled / uncontrolled value |
874
+ | `onValueChange` | `(value: number) => void` | none | Called with the new value, always within `min` and `max` |
875
+ | `min`, `max` | `number` | `0`, none | The limits; the minus (or plus) button is `aria-disabled` at its limit |
876
+ | `step` | `number` | `1` | What a button or an arrow key adds; Page Up/Down use ten steps, Home/End jump to `min`/`max` |
877
+ | `size` | `'sm' \| 'md'` | `'md'` | 32px or 36px buttons; both are 44px under a coarse pointer |
878
+ | `editable` | `boolean` | `false` | The value is a text input: committed on Enter or blur (clamped; text that is not a number is dropped), Escape cancels; only Up/Down and Page Up/Down step it, Left/Right/Home/End move the caret |
879
+ | `onRemove` | `() => void` | none | At `min`, the minus button becomes a trash button named "Remove …" that calls this instead of stepping |
880
+ | `disabled` | `boolean` | `false` | Disables every control |
881
+
882
+ The value is a `spinbutton` (`aria-valuenow`, `aria-valuemin`, `aria-valuemax`)
883
+ in tabular figures, so the row does not shift as digits change. The stepper is
884
+ inline (`inline-flex`): put it in a `ListTile`'s `trailing` or `content`. The
885
+ text is translated through `material.number-field.*` and
886
+ `material.quantity-stepper.remove`.
887
+
888
+ **Storybook:** `Form fields/QuantityStepper` · **Vue:** later
889
+
890
+ ---
891
+
839
892
  ## CurrencyField
840
893
 
841
894
  ![CurrencyField](../previews/form-fields-currencyfield.webp)
@@ -22,6 +22,7 @@ documentation-site shell. Everything is imported from
22
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
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 |
24
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 |
25
+ | <img src="../previews/navigation-layout-panelcard.webp" width="96" alt=""> | [PanelCard](#panelcard) | Titled card for a side panel: heading, description, header actions |
25
26
  | <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` |
26
27
  | <img src="../previews/navigation-layout-resizable.webp" width="96" alt=""> | [ResizableGroup](#resizablegroup) | Resizable panels |
27
28
  | <img src="../previews/navigation-layout-skiplink.webp" width="96" alt=""> | [SkipLink](#skiplink) | Skip-to-content link |
@@ -669,7 +670,7 @@ import { ListDetailsLayout, PaneLayout, ThreadLayout } from '@nxgt/material/comp
669
670
  </ListDetailsLayout.Details>
670
671
  </ListDetailsLayout>
671
672
 
672
- <PaneLayout defaultOpen paneSide="right">
673
+ <PaneLayout defaultOpen="desktop" paneSide="right">
673
674
  <PaneLayout.Content>
674
675
  <PaneLayout.Trigger>Folders</PaneLayout.Trigger>
675
676
  {messages}
@@ -715,10 +716,25 @@ takes over. A page rendered only in the browser starts on the real layout.
715
716
  container and the flex row moves to an inner `data-slot="pane-layout-body"`.
716
717
  - `paneWidth`: `'sm'` (16rem), `'md'` (20rem, default) or `'lg'` (24rem) for
717
718
  the inline pane; no style attribute, so it holds under a strict CSP.
719
+ - `defaultOpen`: `true` / `false` for every viewport, `'desktop'` (open wide,
720
+ shut on a phone) or `{ desktop, phone }` (a missing key is shut). The state
721
+ follows the viewport until the user opens or closes the pane, so a cart that
722
+ starts open on desktop never opens its sheet over a phone, with no effect
723
+ and no flash: the server renders the desktop value and the inline pane is
724
+ hidden by CSS on a phone. With `phone="stack"` the pane has no overlay to
725
+ hide, so a stacked pane that is open on desktop only shows for one frame
726
+ before closing on a phone.
727
+ - `paneSide`: `'left'` (default) or `'right'`. The pane takes its side with a
728
+ CSS `order`, never by reversing the row, so the DOM and tab order are the
729
+ ones you write: put `PaneLayout.Content` first and the pane after it and
730
+ Tab reaches the content first, whichever side the pane is on.
731
+ Before this change a right pane written after the content sat on the left; check
732
+ screens that relied on it.
718
733
  - With `phone` or `breakpoint` set, the inline pane is an `<aside>` named by
719
734
  `sheetTitle` (default `Side Panel`), a landmark; the historical markup is a
720
735
  `div`. `usePaneLayout()` and the function children also get `phone`,
721
736
  `breakpoint` and `paneWidth`.
737
+
722
738
  `ThreadLayout` is a centred column over the app
723
739
  [background](#background) on the page ground (`bg-ground`, so a `bg-card` card stands apart) for focused flows (sign-in, onboarding), with no
724
740
  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).
@@ -735,7 +751,8 @@ app bar. `width`, `stepperPlacement` and a sticky `footer` make it a centred for
735
751
  | `history` / `historyParam` | `'push' \| 'replace'` / `string` | — / `'view'` | `ListDetailsLayout`: keep the view in the URL (`?view=details`), through React Router |
736
752
  | `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 |
737
753
  | `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 |
738
- | `open` / `defaultOpen` / `onOpenChange` | `boolean` | `false` | `PaneLayout`: pane shown |
754
+ | `open` / `onOpenChange` | `boolean` | `false` | `PaneLayout`: pane shown |
755
+ | `defaultOpen` | `boolean \| 'desktop' \| { desktop?, phone? }` | `false` | `PaneLayout`: initial pane state per viewport |
739
756
  | `paneSide` | `'left' \| 'right'` | `'left'` | `PaneLayout` |
740
757
  | `sheetTitle` | `string` | "Side Panel" (translated, `material['pane-layout']['sheet-title']`) | `PaneLayout.Pane`: screen-reader title of the sheet on phones |
741
758
  | `header` | `ReactNode` | — | `ThreadLayout`: above the column |
@@ -1197,6 +1214,35 @@ props, so `className` adds to it.
1197
1214
 
1198
1215
  **Storybook:** `Navigation & layout/SectionColumns` · **Vue:** later ([#1614](https://github.com/softistx/nxgt-material/issues/1614))
1199
1216
 
1217
+ ## PanelCard
1218
+
1219
+ A titled card for the side of a page (an `ActivityContent` aside) or its sections: a heading, an optional one-line description and header actions over the content. It replaces the `Card` + `CardHeader` + `CardTitle as="h2"` + `CardDescription` + `CardContent` wrapper apps wrote themselves.
1220
+
1221
+ ```tsx
1222
+ import { PanelCard } from '@nxgt/material/components';
1223
+
1224
+ <PanelCard
1225
+ title="Recent activity"
1226
+ description="The last five changes to this record."
1227
+ actions={<Button size="sm" variant="outlined">Refresh</Button>}
1228
+ >
1229
+ …
1230
+ </PanelCard>
1231
+ ```
1232
+
1233
+ | Prop | Type | Default | Effect |
1234
+ | --- | --- | --- | --- |
1235
+ | `title` | `ReactNode` | required | The heading, and the accessible name of the card |
1236
+ | `description` | `ReactNode` | none | One line under the title |
1237
+ | `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `2` | The title's heading level; use `3` inside a section that already has an `h2` |
1238
+ | `actions` | `ReactNode` | none | A slot at the end of the title row (a button, a `RowMenu`) |
1239
+
1240
+ The card is a `role="region"` labelled by its title, so `getByRole('region', { name })` finds it. Children are the card's content (`CardContent`); with none, the card is its header only. It takes the `div`'s props, so `className` adds to it.
1241
+
1242
+ The title is set in the card-title step (`title-sm` emphasized) whatever its heading level, one step below an `ActivityContent` page title (`title-md` emphasized), so an aside never outranks the page. The level only sets the document outline, not the size.
1243
+
1244
+ **Storybook:** `Navigation & layout/PanelCard` · **Vue:** later, [#2866](https://github.com/softistx/nxgt-material/issues/2866)
1245
+
1200
1246
  ---
1201
1247
 
1202
1248
  ## RecordPage
@@ -83,7 +83,7 @@ opens at once.
83
83
 
84
84
  ![Collapsible](../previews/primitives-collapsible.webp)
85
85
 
86
- A single show / hide region and its trigger. The content springs open to its height and folds away (clipped only while it moves, so a focus ring inside is never cut). Give `CollapsibleTrigger` a `variant` for a styled trigger with a turning chevron: `tonal`, a pill that squares its corners while open, or `text`.
86
+ A single show / hide region and its trigger. The content springs open to its height and folds away (clipped only while it moves, so a focus ring inside is never cut). Give `CollapsibleTrigger` a `variant` for a styled trigger with a turning chevron: `tonal`, a pill that squares its corners while open, or `text`. Add `flush` to a `text` trigger to drop its inline padding and take the parent's font, so a section disclosure inside `<Typography as="h3">` lines up with the headings around it.
87
87
 
88
88
  ```tsx
89
89
  import {
@@ -110,6 +110,33 @@ import {
110
110
  </Collapsible>;
111
111
  ```
112
112
 
113
+ A flush trigger inside a heading, aligned with the headings around it:
114
+
115
+ ```tsx
116
+ import {
117
+ Collapsible,
118
+ CollapsibleContent,
119
+ CollapsibleTrigger,
120
+ Typography,
121
+ } from '@nxgt/material/components';
122
+
123
+ <div className="flex w-80 flex-col gap-3">
124
+ <Typography as="h3" variant="title-sm" emphasized>
125
+ Question
126
+ </Typography>
127
+ <Collapsible>
128
+ <Typography as="h3" variant="title-sm" emphasized>
129
+ <CollapsibleTrigger variant="text" flush>
130
+ Details
131
+ </CollapsibleTrigger>
132
+ </Typography>
133
+ <CollapsibleContent className="text-muted-foreground text-sm">
134
+ Hidden text that opens under the heading.
135
+ </CollapsibleContent>
136
+ </Collapsible>
137
+ </div>;
138
+ ```
139
+
113
140
  Parts: `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`.
114
141
 
115
142
  | Prop | Type | Default | Effect |
@@ -117,6 +144,7 @@ Parts: `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent`.
117
144
  | `defaultOpen` / `open` | `boolean` | `false` | whether the content shows, uncontrolled / controlled |
118
145
  | `onOpenChange` | `(open: boolean) => void` | — | called when the trigger toggles it |
119
146
  | `disabled` | `boolean` | `false` | the trigger does nothing |
147
+ | `flush` (`CollapsibleTrigger`) | `boolean` | `false` | with `variant="text"`, no inline padding and the parent's font: the label starts at the left edge and can sit in a heading; ignored on other variants |
120
148
  | `variant` (`CollapsibleTrigger`) | `'tonal' \| 'text'` | — | a styled trigger with a chevron; unset, it is unstyled as before (with `asChild`, the child takes the look and brings its own icon) |
121
149
 
122
150
  To keep the former instant toggle, pass `className="animate-none"` to
@@ -705,7 +733,7 @@ For sorting, paging and selection use `DataTable` on the
705
733
 
706
734
  ![Tabs](../previews/primitives-tabs.webp)
707
735
 
708
- Tabs whose triggers can take an icon, in two looks: `underline` (the default) and the `segmented` pill.
736
+ Tabs whose triggers can take an icon, in two looks: `underline` (the default) and the `segmented` pill. `<TabsList fullWidth>` on a `segmented` list fills its container and shares the width between the tabs in equal parts (a payment-method switch); `underline` ignores it.
709
737
 
710
738
  ```tsx
711
739
  import {
@@ -767,6 +795,19 @@ few short choices, such as a view switch.
767
795
  </Tabs>
768
796
  ```
769
797
 
798
+ **`fullWidth`** on a `segmented` `TabsList` makes the track fill its container,
799
+ the tabs sharing the width in equal parts, whatever their labels. It suits a
800
+ two-way switch such as a payment method.
801
+
802
+ ```tsx
803
+ <Tabs defaultValue="cash" variant="segmented">
804
+ <TabsList fullWidth aria-label="Payment method">
805
+ <TabsTrigger value="cash">Cash</TabsTrigger>
806
+ <TabsTrigger value="card">Card</TabsTrigger>
807
+ </TabsList>
808
+ </Tabs>
809
+ ```
810
+
770
811
  `orientation="vertical"` lays the tabs in a column, the indicator on their
771
812
  left side; the arrow keys become Up and Down.
772
813
 
@@ -825,6 +866,7 @@ as `segmented`. Both still type-check and render; write `underline` or
825
866
  | `variant` (`Tabs`) | `'underline' \| 'segmented' \| 'bar' \| 'line'` | `'underline'` | the look; `bar` and `line` are deprecated aliases of `underline` and `segmented` |
826
867
  | `defaultValue` / `value` (`Tabs`) | `string` | — | the selected tab, uncontrolled / controlled |
827
868
  | `onValueChange` (`Tabs`) | `(value: string) => void` | — | called when a trigger is selected |
869
+ | `fullWidth` (`TabsList`) | `boolean` | `false` | `segmented` only: the track fills its container and the tabs share the width in equal parts; `underline` ignores it |
828
870
  | `orientation` (`Tabs`) | `'horizontal' \| 'vertical'` | `'horizontal'` | a row or a column, and the arrow keys' direction |
829
871
  | `dir` (`Tabs`) | `'ltr' \| 'rtl'` | `'ltr'` | the reading direction: which way the tabs run and the arrow keys move |
830
872
  | `icon` (`TabsTrigger`) | `ReactNode` | — | shown before the label |
@@ -13,6 +13,7 @@ the app owns the data.
13
13
  | <img src="../previews/rich-widgets-drawing.webp" width="96" alt=""> | [Drawing](#drawing) | Vector drawing from a scene of plain data or JSX primitives: theme colours, accessible, focusable nodes, strict-CSP safe |
14
14
  | <img src="../previews/rich-widgets-barcode.webp" width="96" alt=""> | [Barcode](#barcode) | Code 128 and Data Matrix as accessible SVG, themed and dark-aware, black on white in print, with `LabelSheet` for a printed sheet of labels |
15
15
  | <img src="../previews/rich-widgets-calculator.webp" width="96" alt=""> | [Calculator](#calculator) | Exact decimal calculator (Standard, Scientific, a point of sale, Financial, Conversion and Programmer) with the usual precedence, a shaped keypad, tape, memory, keyboard, copy and paste, and a number field whose button opens it (`CalculatorField`, in `@nxgt/material/calculator-field`), in its own entry; variants other than Standard load on demand |
16
+ | <img src="../previews/rich-widgets-keypad.webp" width="96" alt=""> | [Keypad](#keypad) | A touch and keyboard numeric keypad for a quantity, a price, a percentage or a cash tender with change (`@nxgt/material/keypad`), decimal or minor-unit entry, field bindings, in its own entry, styled by `calculator.css` |
16
17
  | <img src="../previews/rich-widgets-emojipicker.webp" width="96" alt=""> | [EmojiPicker](#emojipicker) | Shared emoji picker and list |
17
18
  | <img src="../previews/rich-widgets-mentionlist.webp" width="96" alt=""> | [MentionList](#mentionlist) | `@` mention list with local search |
18
19
  | <img src="../previews/rich-widgets-documenteditor.webp" width="96" alt=""> | [DocumentEditor](#documenteditor) | Rich text editor for long-form writing, built on a headless core |
@@ -1012,6 +1013,33 @@ import { CalculatorField, CalculatorFormField } from '@nxgt/material/calculator-
1012
1013
 
1013
1014
  See the [calculator guide](../guides/calculator.md).
1014
1015
 
1016
+ ## Keypad
1017
+
1018
+ A numeric keypad from `@nxgt/material/keypad`, usable outside the Calculator:
1019
+ quantity, price, percentage and cash-tendered entry. The value is a canonical
1020
+ decimal string and the arithmetic is the calculator's exact engine. Digits go in
1021
+ as typed (`entry="decimal"`) or like a till (`entry="minor"`, `1 2 5` is `1.25`);
1022
+ `operations` adds calculation keys; `min` and `max` hold the value; the keyboard
1023
+ works when focus is in the keypad (`keyboard="global"` for a till). Its own entry,
1024
+ server-safe, styled by `@nxgt/material/calculator.css` (import it after
1025
+ `styles.css`; there is no keypad stylesheet).
1026
+
1027
+ ```tsx
1028
+ import { Keypad, useKeypadNumber } from '@nxgt/material/keypad';
1029
+
1030
+ <Keypad value={qty} onValueChange={setQty} onCommit={(value) => addLine(value)} />
1031
+ const binding = useKeypadNumber({ value: qty, onChange: setQty, min: 1 });
1032
+ <Keypad binding={binding} />
1033
+ <Keypad variant="cash" total="17.35" denominations={[5, 10, 20, 50]} currency="EUR" onTender={pay} />
1034
+ ```
1035
+
1036
+ `useKeypadNumber` binds it to a `NumberField`, `PercentField` or `CurrencyField`
1037
+ (also with `minorUnits`), `useKeypadQuantity` to a `QuantityField`; the bound
1038
+ keypad hides its own display unless `operations` are offered. The `cash` variant shows the total, the change or
1039
+ the amount still due, bill shortcuts from `denominations`, and reports
1040
+ `onTender(tendered, settlement)`; your app owns the charge. See the
1041
+ [keypad guide](../guides/keypad.md).
1042
+
1015
1043
  ## EmojiPicker
1016
1044
 
1017
1045
  ![EmojiPicker](../previews/rich-widgets-emojipicker.webp)