@nxgt/material 3.100.1 → 3.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +32 -2
- package/dist/{alert-dialog-DYzxchiS.js → alert-dialog-NFyAiSqc.js} +1 -1
- package/dist/{async-slice-drJyPvuM.js → async-slice-D35bX79S.js} +1 -1
- package/dist/{bubble-input-CQwVSoU3.js → bubble-input-BxI1WmNs.js} +17 -5
- package/dist/{button-GeIsUekU.js → button-Be7GZzF9.js} +2 -2
- package/dist/{calculator-BS4PAm6l.js → calculator-BGy1Q7rn.js} +261 -460
- package/dist/calculator-field.js +7 -7
- package/dist/{calculator-segment-CEdwE24M.js → calculator-segment-uixk6lBg.js} +1 -1
- package/dist/calculator.js +1 -1
- package/dist/charts.js +3 -3
- package/dist/{chip-BdEtFQ85.js → chip-3rWebZEM.js} +2 -2
- package/dist/{code-editor-BByXZOGy.js → code-editor-BqSVMfy-.js} +178 -177
- package/dist/code-editor.js +5 -5
- package/dist/{code-language-items-CSZX5zxD.js → code-language-items-Cf2LJVg2.js} +14 -5
- package/dist/components/ui/actions/action-card/action-card-tile.css +47 -0
- package/dist/components/ui/display/code/code-moonlight.css +10 -10
- package/dist/{components-BrZkSs3O.js → components-D2liJ9Yu.js} +17725 -17287
- package/dist/components.js +38 -37
- package/dist/{conversion-variant-DTQqAyDt.js → conversion-variant-clFZ6ZMJ.js} +5 -5
- package/dist/{currency-field-Dnju7ss2.js → currency-field-CBFEVNM1.js} +24 -23
- package/dist/{dialog-5_2__tu8.js → dialog-CrIIINTk.js} +3 -3
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-FdGC5kIP.js → dropdown-menu-BnfLWu8n.js} +1 -1
- package/dist/editor.js +9 -9
- package/dist/{empty-state-BoepPG5v.js → empty-state-CuUaCPY7.js} +1 -1
- package/dist/{field-blur-DkiZOVNV.js → field-blur-DMJwPLsg.js} +9 -9
- package/dist/field-blur-context-3rKussBQ.js +29 -0
- package/dist/{financial-variant-BhqJp5C-.js → financial-variant-BY_VCUuk.js} +5 -5
- package/dist/{hooks-C8MhL6hs.js → hooks-hHoZbL-4.js} +106 -61
- package/dist/hooks.js +6 -6
- package/dist/{hover-card-CmHJeBeY.js → hover-card-Do2-uzlF.js} +1 -1
- package/dist/i18n.js +3 -3
- package/dist/{icon-badge-LGUPQh_c.js → icon-badge-CHaVNEmb.js} +1 -1
- package/dist/{icon-button-BSVpO1jP.js → icon-button-BIAubLeJ.js} +1 -1
- package/dist/key-layout-D2goCDkD.js +31 -0
- package/dist/keypad.js +551 -0
- package/dist/label-DR-zZyGQ.js +47 -0
- package/dist/lib/components/ui/display/code/code-block-parts.d.ts +1 -1
- package/dist/lib/components/ui/display/code/code-block.d.ts +4 -1
- package/dist/lib/components/ui/display/code/languages.d.ts +2 -0
- package/dist/lib/components/ui/display/code/moonlight-tokens.d.ts +10 -10
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-props.d.ts +73 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-rules.d.ts +12 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog.d.ts +5 -61
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-footer.d.ts +12 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-phrase.d.ts +26 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/use-confirm-action.d.ts +1 -1
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/layout/index.d.ts +1 -0
- package/dist/lib/components/ui/layout/panel-card/index.d.ts +1 -0
- package/dist/lib/components/ui/layout/panel-card/panel-card.d.ts +21 -0
- package/dist/lib/components/ui/selection/index.d.ts +1 -0
- package/dist/lib/components/ui/selection/status-menu/index.d.ts +3 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-confirm.d.ts +12 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-icon.d.ts +6 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-items.d.ts +18 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-menu-list.d.ts +14 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-sheet-row.d.ts +11 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-sheet.d.ts +21 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-trigger.d.ts +18 -0
- package/dist/lib/components/ui/selection/status-menu/status-menu-data.d.ts +12 -0
- package/dist/lib/components/ui/selection/status-menu/status-menu-form-field.d.ts +20 -0
- package/dist/lib/components/ui/selection/status-menu/status-menu.d.ts +13 -0
- package/dist/lib/components/ui/selection/status-menu/types.d.ts +76 -0
- package/dist/lib/components/ui/selection/status-menu/use-status-change.d.ts +18 -0
- package/dist/lib/components/ui/selection/status-menu/use-status-menu.d.ts +30 -0
- package/dist/lib/components/ui/widgets/calculator/partials/calculator-keypad.d.ts +1 -5
- package/dist/lib/components/ui/widgets/calculator/variants/key-layout.d.ts +3 -49
- package/dist/lib/components/ui/widgets/keypad/cash-model.d.ts +16 -0
- package/dist/lib/components/ui/widgets/keypad/index.d.ts +5 -0
- package/dist/lib/components/ui/widgets/keypad/key-layout.d.ts +56 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-binding.d.ts +32 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-cash.d.ts +28 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-display.d.ts +11 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-entry.d.ts +39 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-grid.d.ts +27 -0
- package/dist/lib/components/ui/widgets/{calculator/partials/calculator-key.d.ts → keypad/keypad-key.d.ts} +6 -3
- package/dist/lib/components/ui/widgets/keypad/keypad-layout.d.ts +19 -0
- package/dist/lib/components/ui/widgets/{calculator/partials → keypad}/keypad-navigation.d.ts +1 -1
- package/dist/lib/components/ui/widgets/keypad/keypad.d.ts +9 -0
- package/dist/lib/components/ui/widgets/keypad/keypad.types.d.ts +91 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-cash.d.ts +14 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-keyboard.d.ts +7 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-number.d.ts +10 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-quantity.d.ts +16 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-value.d.ts +17 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad.d.ts +23 -0
- package/dist/lib/hooks/index.d.ts +2 -0
- package/dist/lib/hooks/use-barcode-scanner.d.ts +39 -0
- package/dist/lib/hooks/use-latched.d.ts +18 -0
- package/dist/lib/i18n/resources/en/en.d.ts +25 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +26 -1
- package/dist/lib/i18n/resources/fr/fr.d.ts +25 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +26 -1
- package/dist/lib/i18n/resources/resources.d.ts +50 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/keypad/index.d.ts +20 -0
- package/dist/lib.js +1 -1
- package/dist/main.js +54 -53
- package/dist/map.js +9 -9
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/{percent-field-DGlU_xii.js → percent-field-DU8eIMD3.js} +15 -14
- package/dist/{picker-surface-CGhqNR8a.js → picker-surface-XmA--JVI.js} +4 -4
- package/dist/{popover-ZxL7ktKk.js → popover-D8iHiAbl.js} +1 -1
- package/dist/{pos-types-BQ0QpYMp.js → pos-types-BEAY27Rm.js} +1 -1
- package/dist/{pos-variant-CKy8h_5i.js → pos-variant-ClGI92Lt.js} +25 -25
- package/dist/print.js +1 -1
- package/dist/{programmer-variant-CbHstE28.js → programmer-variant-Boq2O8hB.js} +30 -30
- package/dist/{public-D-tdOz4b.js → public-DOrXKmcL.js} +9 -9
- package/dist/{rich-code-block-CTPj1LuD.js → rich-code-block-Djj2XE_u.js} +18 -18
- package/dist/{scientific-variant-B8U9h3II.js → scientific-variant-BzF_aMQP.js} +8 -8
- package/dist/{sortable-provider-CSoWE7Ce.js → sortable-provider-DCZjsIIH.js} +2 -2
- package/dist/{spinner-ffuwBXHl.js → spinner-Cg0y_E_K.js} +1 -1
- package/dist/standard-layout-DTr9yy8Z.js +48 -0
- package/dist/{tabs-JgRq6ZJx.js → tabs-DpessV5s.js} +1 -1
- package/dist/{text-field-DURkIP-v.js → text-field-Dxk9lAKM.js} +16 -15
- package/dist/theme-picker.js +13 -13
- package/dist/{theme-toggle-n_Zg7JAA.js → theme-toggle-B6Jl5Kfn.js} +1 -1
- package/dist/{toolbar-items-IANZdcw4.js → toolbar-items-Cs1XrZsH.js} +4 -4
- package/dist/{translate-material-CMHUH5Ha.js → translate-material-Du0j0t97.js} +1 -1
- package/dist/{translator-9zA-Um3W.js → translator-BVs4n0Fz.js} +52 -2
- package/dist/use-calculator-keyboard-BZ1CA4Ut.js +222 -0
- package/dist/{use-chat-composer-BoIN7RCA.js → use-chat-composer-B4m3OEm1.js} +241 -240
- package/dist/{use-heading-spy-8o20PEZH.js → use-heading-spy-CO3_AtY9.js} +240 -142
- package/dist/{use-material-translation-CLNaMhUf.js → use-material-translation-uNc1FyC8.js} +1 -1
- package/dist/{use-return-focus-DWhbTGVe.js → use-return-focus-AfLRrUm-.js} +1 -1
- package/docs/README.md +1 -0
- package/docs/components/README.md +1 -0
- package/docs/components/buttons-and-actions.md +34 -2
- package/docs/components/data-display.md +4 -1
- package/docs/components/feedback-and-status.md +34 -0
- package/docs/components/navigation-and-layout.md +28 -0
- package/docs/components/rich-widgets.md +28 -0
- package/docs/components/selection-and-choice.md +120 -0
- package/docs/guides/calculator.md +1 -1
- package/docs/guides/cards-and-tiles.md +21 -24
- package/docs/guides/forms.md +1 -0
- package/docs/guides/helpers.md +78 -0
- package/docs/guides/keypad.md +225 -0
- package/docs/previews/data-display-codeblock.webp +0 -0
- package/docs/previews/data-display-codegroup.webp +0 -0
- package/docs/previews/data-display-jsonviewer.webp +0 -0
- package/docs/previews/navigation-layout-panelcard.webp +0 -0
- package/docs/previews/rich-widgets-keypad.webp +0 -0
- package/docs/previews/selection-choice-statusmenu.webp +0 -0
- package/docs/roadmap.md +21 -0
- package/docs/troubleshooting.md +63 -0
- package/lib/components/ui/actions/action-card/action-card-tile.css +47 -0
- package/lib/components/ui/display/code/code-moonlight.css +10 -10
- package/lib/components/ui/widgets/calculator/calculator.css +87 -0
- package/package.json +7 -2
- package/dist/field-blur-context-BLGgf_3z.js +0 -73
- package/dist/key-layout-cR79xfGE.js +0 -38
- package/dist/standard-layout-BuSRr3TY.js +0 -40
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` |
|
|
@@ -65,6 +65,7 @@ Picking among options: select, combobox, autocomplete, checkbox, radio, card and
|
|
|
65
65
|
[<img src="../previews/selection-choice-autocomplete.webp" height="72" alt="Autocomplete">](selection-and-choice.md#autocomplete)
|
|
66
66
|
[<img src="../previews/selection-choice-choicecardgroup.webp" height="72" alt="ChoiceCardGroup">](selection-and-choice.md#choicecardgroup)
|
|
67
67
|
[<img src="../previews/selection-choice-transferlist.webp" height="72" alt="TransferList">](selection-and-choice.md#transferlist)
|
|
68
|
+
[<img src="../previews/selection-choice-statusmenu.webp" height="72" alt="StatusMenu">](selection-and-choice.md#statusmenu)
|
|
68
69
|
|
|
69
70
|
## [Data display](data-display.md)
|
|
70
71
|
|
|
@@ -1199,7 +1199,7 @@ import { Banknote, CreditCard } from 'lucide-react';
|
|
|
1199
1199
|
| `title`, `description`, `icon` | `ReactNode` | — | the content |
|
|
1200
1200
|
| `status` | `ReactNode` | — | a line under the title; a string or number becomes a soft `Badge`, a node is shown as given |
|
|
1201
1201
|
| `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 |
|
|
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; in a `tile` it wraps under a narrow title |
|
|
1203
1203
|
|
|
1204
1204
|
A card with `active`, `onClick`, `disabled` or your own `role` is `role="button"` (unless you set another): it takes the Tab stop, and
|
|
1205
1205
|
Enter and Space press it. `active` sets `aria-pressed`; pass your own `role`
|
|
@@ -1218,7 +1218,39 @@ For a card choice bound to a form value, see `ChoiceCardGroup` on
|
|
|
1218
1218
|
`variant="tile"` is a compact grid cell for boards: no minimum width (it fills
|
|
1219
1219
|
its grid track), the title and the `action` on the first row, then `status`,
|
|
1220
1220
|
`description` and `meta`, and no indicator unless `withIndicator`. `status` and
|
|
1221
|
-
`meta` work on every variant.
|
|
1221
|
+
`meta` work on every variant. The `action` wraps under a narrow title instead of
|
|
1222
|
+
overflowing the tile.
|
|
1223
|
+
|
|
1224
|
+
A status that changes in place is a [`StatusMenu`](selection-and-choice.md#statusmenu)
|
|
1225
|
+
in `action`: the chip shows the status (so the card needs no `status` badge) and
|
|
1226
|
+
opens the choices, in a bottom sheet on a phone. Its button is named "Status of
|
|
1227
|
+
Unit 21: Free". The chip is 6 to 10rem wide (a long status word is wider). A tile
|
|
1228
|
+
with an `action` is a wrapping line: the title may shrink to 4.5rem
|
|
1229
|
+
(`--ac-title-min`), and an action that does not fit beside it drops under the
|
|
1230
|
+
title, so a chip fits in a two-per-row `xs` grid on a 360px phone and an icon
|
|
1231
|
+
button such as `RowMenu` stays beside the title. A wrapped action starts at the tile's line start (under the icon when the tile
|
|
1232
|
+
has one), not under the title text.
|
|
1233
|
+
|
|
1234
|
+
```tsx
|
|
1235
|
+
import { ActionCard, StatusMenu } from '@nxgt/material/components';
|
|
1236
|
+
|
|
1237
|
+
<ActionCard
|
|
1238
|
+
variant="tile"
|
|
1239
|
+
title="Unit 21"
|
|
1240
|
+
meta="Zone B, second floor"
|
|
1241
|
+
onClick={() => openDetail(unit.id)}
|
|
1242
|
+
action={
|
|
1243
|
+
<StatusMenu
|
|
1244
|
+
label="Unit 21"
|
|
1245
|
+
value={unit.status}
|
|
1246
|
+
choices={statuses}
|
|
1247
|
+
onSelect={(next) => saveStatus(unit.id, next)} // a promise keeps the old status with a spinner
|
|
1248
|
+
/>
|
|
1249
|
+
}
|
|
1250
|
+
/>;
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
A compact icon-button action, with a `status` badge, uses `RowMenu`:
|
|
1222
1254
|
|
|
1223
1255
|
```tsx
|
|
1224
1256
|
import {
|
|
@@ -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
|

|
|
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 |
|
|
@@ -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,38 @@ 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
|
+
|
|
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:
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
<ConfirmDialog
|
|
296
|
+
open={item !== null}
|
|
297
|
+
onOpenChange={(o) => !o && setItem(null)}
|
|
298
|
+
title={`Delete ${item?.name ?? ''}?`}
|
|
299
|
+
description="This cannot be undone."
|
|
300
|
+
onConfirm={remove}
|
|
301
|
+
/>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
For a dialog you build yourself (`Dialog`, `CustomDialog`, `ConfirmationDialog` children), read the subject through [`useLatched`](../guides/helpers.md#uselatched).
|
|
305
|
+
|
|
272
306
|
**`ActionRefusal`** is the refusal that carries an action. Throw `new ActionRefusal(message, { action, details? })` from `onConfirm`, with `action` `{ label, href }` (a link, a full page load) or `{ label, onAction }` (a handler, for client-side navigation; it wins when both are given) and `details` a note. The alert then shows the message, the details and the action, and the action closes the dialog; any other error shows its message only. `isActionRefusal(error)` is the guard (it also accepts a copy from another install of the package), and `RecordAction` is the action's type. `RecordActionsMenu` reuses all of this.
|
|
273
307
|
|
|
274
308
|
```tsx
|
|
@@ -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 |
|
|
@@ -1197,6 +1198,33 @@ props, so `className` adds to it.
|
|
|
1197
1198
|
|
|
1198
1199
|
**Storybook:** `Navigation & layout/SectionColumns` · **Vue:** later ([#1614](https://github.com/softistx/nxgt-material/issues/1614))
|
|
1199
1200
|
|
|
1201
|
+
## PanelCard
|
|
1202
|
+
|
|
1203
|
+
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.
|
|
1204
|
+
|
|
1205
|
+
```tsx
|
|
1206
|
+
import { PanelCard } from '@nxgt/material/components';
|
|
1207
|
+
|
|
1208
|
+
<PanelCard
|
|
1209
|
+
title="Recent activity"
|
|
1210
|
+
description="The last five changes to this record."
|
|
1211
|
+
actions={<Button size="sm" variant="outlined">Refresh</Button>}
|
|
1212
|
+
>
|
|
1213
|
+
…
|
|
1214
|
+
</PanelCard>
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
| Prop | Type | Default | Effect |
|
|
1218
|
+
| --- | --- | --- | --- |
|
|
1219
|
+
| `title` | `ReactNode` | required | The heading, and the accessible name of the card |
|
|
1220
|
+
| `description` | `ReactNode` | none | One line under the title |
|
|
1221
|
+
| `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `2` | The title's heading level; use `3` inside a section that already has an `h2` |
|
|
1222
|
+
| `actions` | `ReactNode` | none | A slot at the end of the title row (a button, a `RowMenu`) |
|
|
1223
|
+
|
|
1224
|
+
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.
|
|
1225
|
+
|
|
1226
|
+
**Storybook:** `Navigation & layout/PanelCard` · **Vue:** later, [#2866](https://github.com/softistx/nxgt-material/issues/2866)
|
|
1227
|
+
|
|
1200
1228
|
---
|
|
1201
1229
|
|
|
1202
1230
|
## RecordPage
|
|
@@ -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
|

|
|
@@ -29,6 +29,7 @@ value type each one writes. Everything is imported from
|
|
|
29
29
|
| <img src="../previews/selection-choice-sortablelist.webp" width="96" alt=""> | [SortableList](#sortablelist) | Reorderable list: drag or keyboard, announced; two-line labels, row menu |
|
|
30
30
|
| <img src="../previews/selection-choice-treeselectfield.webp" width="96" alt=""> | [TreeSelectField](#treeselectfield) | Pick from a tree, single or multiple |
|
|
31
31
|
| <img src="../previews/selection-choice-switcher.webp" width="96" alt=""> | [Switcher](#switcher) | Workspace, establishment or class switcher: menu, search, groups, links; `size="compact"` an icon-only round trigger for the app bar's `titleAfter` slot |
|
|
32
|
+
| <img src="../previews/selection-choice-statusmenu.webp" width="96" alt=""> | [StatusMenu](#statusmenu) | A record's status as a soft chip that opens a menu (a bottom sheet on a phone) of statuses: transitions, locked, busy, read-only; `StatusMenuFormField` for forms |
|
|
32
33
|
|
|
33
34
|
## Select
|
|
34
35
|
|
|
@@ -1020,6 +1021,125 @@ on a touch screen.
|
|
|
1020
1021
|
|
|
1021
1022
|
---
|
|
1022
1023
|
|
|
1024
|
+
## StatusMenu
|
|
1025
|
+
|
|
1026
|
+

|
|
1027
|
+
|
|
1028
|
+
A record's status (a ticket, an order, a patient, a device) as a chip that
|
|
1029
|
+
changes it in one tap: a soft Badge pill (the tone's container, the tone's
|
|
1030
|
+
icon, the words, a chevron) that opens a menu of radio items. The held status
|
|
1031
|
+
is checked and inert. The button is named "Status of Ticket 42: Open", and
|
|
1032
|
+
`onSelect` never runs for the held status, for a blocked one, or when locked or
|
|
1033
|
+
busy.
|
|
1034
|
+
|
|
1035
|
+
- `transitions` greys the statuses it does not reach, each saying why. A
|
|
1036
|
+
terminal status needs `done: []`; unlike `useAllowedTransitions`, a missing
|
|
1037
|
+
key allows every move.
|
|
1038
|
+
- `locked` picks nothing and shows its `reason` as a tooltip and as the menu's
|
|
1039
|
+
first line.
|
|
1040
|
+
- `busy` keeps the old status and swaps the chevron for a spinner. A returned
|
|
1041
|
+
promise does the same until it settles; a rejection is announced and sent to
|
|
1042
|
+
`onError`.
|
|
1043
|
+
- `confirm` on a choice asks first, in a dialog.
|
|
1044
|
+
- `readOnly` draws a plain label, no button.
|
|
1045
|
+
|
|
1046
|
+
On a phone (under 768px) the statuses open in a bottom sheet of 44px rows
|
|
1047
|
+
instead of the menu; a status that cannot be picked says why in its own row
|
|
1048
|
+
(touch has no tooltip). `presentation` is `'auto'` (default), `'menu'` or
|
|
1049
|
+
`'sheet'`.
|
|
1050
|
+
|
|
1051
|
+
```tsx
|
|
1052
|
+
import { StatusMenu, type StatusChoice } from '@nxgt/material/components';
|
|
1053
|
+
|
|
1054
|
+
const statuses: StatusChoice<'todo' | 'progress' | 'done'>[] = [
|
|
1055
|
+
{ value: 'todo', label: 'To do', tone: 'neutral', icon: 'circle' },
|
|
1056
|
+
{ value: 'progress', label: 'In progress', tone: 'info', icon: 'circle-half-stroke' },
|
|
1057
|
+
{ value: 'done', label: 'Done', tone: 'success', icon: 'circle-check' },
|
|
1058
|
+
];
|
|
1059
|
+
|
|
1060
|
+
<StatusMenu
|
|
1061
|
+
label="Ticket 42"
|
|
1062
|
+
value={status}
|
|
1063
|
+
choices={statuses}
|
|
1064
|
+
transitions={{ done: ['progress'] }}
|
|
1065
|
+
presentation="auto" // 'menu' | 'sheet' to force one
|
|
1066
|
+
onSelect={(next) => setStatus(next)}
|
|
1067
|
+
/>;
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
Saving through a server, with a status that asks first:
|
|
1071
|
+
|
|
1072
|
+
```tsx
|
|
1073
|
+
const statuses: StatusChoice<'open' | 'cancelled'>[] = [
|
|
1074
|
+
{ value: 'open', label: 'Open', tone: 'info' },
|
|
1075
|
+
{
|
|
1076
|
+
value: 'cancelled',
|
|
1077
|
+
label: 'Cancelled',
|
|
1078
|
+
tone: 'error',
|
|
1079
|
+
confirm: { title: 'Cancel this ticket?', confirmLabel: 'Cancel ticket', destructive: true },
|
|
1080
|
+
},
|
|
1081
|
+
];
|
|
1082
|
+
|
|
1083
|
+
<StatusMenu
|
|
1084
|
+
label="Ticket 42"
|
|
1085
|
+
value={status}
|
|
1086
|
+
choices={statuses}
|
|
1087
|
+
onSelect={async (next) => {
|
|
1088
|
+
await saveStatus(ticketId, next); // pending until it settles; a throw is announced
|
|
1089
|
+
setStatus(next);
|
|
1090
|
+
}}
|
|
1091
|
+
onError={(error) => toast.error(String(error))}
|
|
1092
|
+
/>;
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
`confirm` defaults: `confirmLabel` "Confirm", `destructive` `false`, `description`
|
|
1096
|
+
"Change the status to {label}?" (`material.status-menu.confirm-default`). The menu
|
|
1097
|
+
marks such a choice "Asks first" (`material.status-menu.confirms`). A confirmed
|
|
1098
|
+
save shows its progress in the dialog (busy buttons, a failure kept in it), not on the chip.
|
|
1099
|
+
|
|
1100
|
+
| Prop | Type | Default | Effect |
|
|
1101
|
+
| --- | --- | --- | --- |
|
|
1102
|
+
| `label` | `string` | — | what the status belongs to; names the button "Status of {label}: {status}" |
|
|
1103
|
+
| `value` / `choices` | `V` / `readonly StatusChoice<V>[]` | — | the held status, and every status: `value`, `label`, `tone`, `icon`, `description`, `disabled`, `disabledReason`, `confirm` (`title`, `description`, `confirmLabel`, `destructive`: a `ConfirmDialog` runs `onSelect` on confirm, a failure stays in the dialog and reaches `onError`) |
|
|
1104
|
+
| `onSelect` | `(next, prev) => void \| Promise<void>` | — | a different status was picked; a returned promise keeps the chip pending (menu closed, focus on the chip, spinner, `aria-busy`, old status shown) until it settles |
|
|
1105
|
+
| `onError` | `(error) => void` | — | the promise rejected (or threw): the failure is announced and the old status stays |
|
|
1106
|
+
| `transitions` | `Partial<Record<V, readonly V[]>>` | — | the statuses reachable from each status (no key: every move; a terminal status is `[]`) |
|
|
1107
|
+
| `unavailable` | `'disabled' \| 'hidden'` | `'disabled'` | an unreachable status is greyed with "Not available from {status}", or dropped |
|
|
1108
|
+
| `locked` / `reason` | `boolean` / `string` | — | nothing can be picked; `reason` is the tooltip, the button's description and the menu's first line |
|
|
1109
|
+
| `busy` | `boolean` | `false` | `aria-busy` and `aria-disabled`, a spinner, the old status kept, the menu does not open |
|
|
1110
|
+
| `readOnly` | `boolean` | `false` | a plain label |
|
|
1111
|
+
| `size` | `'sm' \| 'md'` | `'md'` | chip height |
|
|
1112
|
+
| `presentation` | `'auto' \| 'menu' \| 'sheet'` | `'auto'` | a dropdown menu, a bottom sheet of 44px rows, or the sheet under 768px |
|
|
1113
|
+
| `open` / `onOpenChange` | `boolean` / `(open) => void` | — | controlled menu |
|
|
1114
|
+
| `triggerProps` / `className` | | | on the button / on the menu |
|
|
1115
|
+
|
|
1116
|
+
Texts come from `material.status-menu.*` (English and French). The rules
|
|
1117
|
+
(which statuses can be picked, and why not) are `@nxgt/material-core/status-menu`.
|
|
1118
|
+
|
|
1119
|
+
**Form wrapper:** `StatusMenuFormField` takes `control`, `name`, `rules` and the
|
|
1120
|
+
StatusMenu props except `value` (the form holds it). A pick runs
|
|
1121
|
+
`onSelect` first; the form takes the new status once `onSelect` resolves (a
|
|
1122
|
+
promise keeps the pending state and the old status, and a rejection keeps the old
|
|
1123
|
+
value). `field.onBlur` fires when focus leaves the chip and its menu or sheet. An
|
|
1124
|
+
empty value (`''`) matches no choice, so the chip shows an empty value: give an
|
|
1125
|
+
initial status. The error message (or `helperText`) is written under the chip and linked to
|
|
1126
|
+
it with `aria-describedby` and `aria-invalid`, a refused submit focuses the chip,
|
|
1127
|
+
and `disabled` (or the form's) locks it.
|
|
1128
|
+
|
|
1129
|
+
```tsx
|
|
1130
|
+
<StatusMenuFormField
|
|
1131
|
+
control={form.control}
|
|
1132
|
+
name="status"
|
|
1133
|
+
label="Ticket 42"
|
|
1134
|
+
choices={statuses}
|
|
1135
|
+
rules={{ required: 'Pick a status.' }}
|
|
1136
|
+
/>
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
**Storybook:** `Selection & choice/StatusMenu` (and `/In a form`) · **Vue:** React only for now (#2816)
|
|
1140
|
+
|
|
1141
|
+
---
|
|
1142
|
+
|
|
1023
1143
|
## OptionTile
|
|
1024
1144
|
|
|
1025
1145
|

|
|
@@ -123,7 +123,7 @@ numbers: `sqrt(-1)` is "Undefined result".
|
|
|
123
123
|
/>
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
The props below are read only by this variant (`currency` is shared). A point of sale till for a counter, on `@nxgt/material-core/calculator/pos
|
|
126
|
+
The props below are read only by this variant (`currency` is shared). A point of sale till for a counter, on `@nxgt/material-core/calculator/pos` (the engine; apps import `@nxgt/material/calculator`). The
|
|
127
127
|
tab is named "Point of sale". The keys type an amount the way a till does:
|
|
128
128
|
`1`, `2`, `5` is `1.25`, `00` adds two zeros, backspace drops the last digit,
|
|
129
129
|
`C` clears it. `+` adds it to the ticket. `×` types a quantity first (`2.50 ×
|
|
@@ -326,49 +326,46 @@ grid of compact cells you read at a glance and change in place. Use
|
|
|
326
326
|
[`ActionCard variant="tile"`](../components/buttons-and-actions.md#actioncard-tile)
|
|
327
327
|
in a `ResponsiveGrid minItem="xs"`, one grid per section under its own `h2`:
|
|
328
328
|
|
|
329
|
-
- **`
|
|
330
|
-
|
|
329
|
+
- **`action`** is a [`StatusMenu`](../components/selection-and-choice.md#statusmenu)
|
|
330
|
+
chip: it shows the status as a word, an icon and a tone (never colour alone),
|
|
331
|
+
so the tile needs no `status` badge, and it changes the status in place (a
|
|
332
|
+
bottom sheet on a phone). Its button is named "Status of {name}: {status}".
|
|
333
|
+
The title is the one button (it opens the detail), so the card and its chip
|
|
334
|
+
are two tab stops and nothing nests. When the tile is too narrow for the title
|
|
335
|
+
and the chip side by side (two per row at 360px), the chip wraps under the
|
|
336
|
+
title.
|
|
331
337
|
- **`meta`** is one line, the occupant only while occupied; a long one is
|
|
332
338
|
clipped and shown in full in a tooltip.
|
|
333
|
-
- **`action`** is a `RowMenu` with a `DropdownMenuRadioGroup` of statuses,
|
|
334
|
-
named "Change status of {name}". The title is the one button (it opens the
|
|
335
|
-
detail), so the card and its menu are two tab stops and nothing nests.
|
|
336
339
|
|
|
337
340
|
```tsx
|
|
338
|
-
import {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
} from '@nxgt/material/components';
|
|
341
|
+
import { ActionCard, ResponsiveGrid, StatusMenu } from '@nxgt/material/components';
|
|
342
|
+
|
|
343
|
+
const choices = [
|
|
344
|
+
{ value: 'free', label: 'Free', tone: 'success', icon: 'check' },
|
|
345
|
+
{ value: 'occupied', label: 'Occupied', tone: 'warning', icon: 'user' },
|
|
346
|
+
{ value: 'out', label: 'Out of service', tone: 'error', icon: 'ban' },
|
|
347
|
+
];
|
|
346
348
|
|
|
347
349
|
<ResponsiveGrid minItem="xs">
|
|
348
350
|
<ActionCard
|
|
349
351
|
variant="tile"
|
|
350
352
|
title="Bed A1"
|
|
351
|
-
status={<Badge variant="soft" tone="warning" icon="user">Occupied</Badge>}
|
|
352
353
|
meta="Amara Diallo"
|
|
353
354
|
onClick={() => openDetail('a1')}
|
|
354
355
|
action={
|
|
355
|
-
<
|
|
356
|
+
<StatusMenu
|
|
356
357
|
label="Bed A1"
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
<DropdownMenuRadioItem value="occupied">Occupied</DropdownMenuRadioItem>
|
|
362
|
-
<DropdownMenuRadioItem value="cleaning">Cleaning</DropdownMenuRadioItem>
|
|
363
|
-
</DropdownMenuRadioGroup>
|
|
364
|
-
</RowMenu>
|
|
358
|
+
value={status}
|
|
359
|
+
choices={choices}
|
|
360
|
+
onSelect={setStatus}
|
|
361
|
+
/>
|
|
365
362
|
}
|
|
366
363
|
/>
|
|
367
364
|
</ResponsiveGrid>;
|
|
368
365
|
```
|
|
369
366
|
|
|
370
367
|
`title` is required with `action`: it is the card's button, named after the
|
|
371
|
-
title
|
|
368
|
+
title and the meta line.
|
|
372
369
|
|
|
373
370
|
For the figures of the whole board, put a `StatCardGroup` above a `DataTable`
|
|
374
371
|
with one row per section, one numeric column per status (they add up to the
|
package/docs/guides/forms.md
CHANGED
|
@@ -537,6 +537,7 @@ or fails validation.
|
|
|
537
537
|
| `Checkbox` · `CheckboxFormField` | `boolean` | `false` (see [Traps](#traps)) |
|
|
538
538
|
| `CheckboxGroup` · `CheckboxGroupFormField` | `string[]` | `[]` |
|
|
539
539
|
| `RadioGroup` · `RadioGroupFormField` | `string` | `''` |
|
|
540
|
+
| `StatusMenu` · `StatusMenuFormField` | `string`: the picked `choices` value | `''` matches no choice, so the chip shows an empty value: give an initial status |
|
|
540
541
|
| `CheckCardField` · `CheckCardFormField` | `boolean`: `true` for the first card, `false` for the second | `false` |
|
|
541
542
|
| `ChipGroupField` · `ChipGroupFormField` | `type="single"` (default): `string \| null`. `type="multiple"`: `string[]` | `null` / `[]` |
|
|
542
543
|
| `ChoiceCardGroup` · `ChoiceCardGroupFormField` | same as `ChipGroupField` | `null` / `[]` |
|
package/docs/guides/helpers.md
CHANGED
|
@@ -138,6 +138,8 @@ Vue: the Redux helpers are Pinia store factories: `defineLocalStore`
|
|
|
138
138
|
| `useAllowedTransitions(status, transitions, configs)` | Status-machine actions (icon, colour, label, dialog copy) allowed from the current status. |
|
|
139
139
|
| `useFocusOnMount<T>(focus = true)` | A ref whose element takes focus once mounted, and again whenever `focus` turns true: the heading of what replaced the content the user acted on. See [below](#usefocusonmount). |
|
|
140
140
|
| `useScrollAway(ref?, { threshold? })` | True while the reader scrolls down (by `threshold`, 24 px, since the scroll last turned); false scrolling up, at the top or at the end. Folds an extended `Fab`. Without `ref`, the window; false on the server. See [Fab](../components/buttons-and-actions.md#fab). |
|
|
141
|
+
| `useLatched(value)`, `useLatchedWhileClosed(open, value)` | The last value that was not `null` / `undefined`, and the value from the last open render while `open` is false: keep a dialog's text while it fades out after the app cleared its subject. `ConfirmDialog` does it itself. See [below](#uselatched). |
|
|
142
|
+
| `useBarcodeScanner({ onScan, enabled?, acceptIn?, maxGap?, minLength?, onKey? })`, `createScanner()`, `attachBarcodeScanner(target, options)` | Hears a keyboard-wedge barcode reader anywhere on the page: a run of keys each within `maxGap` ms (50), at least `minLength` (4) long, closed by Enter, reaches `onScan(code)`; slower or shorter runs are typing. `createScanner` is the framework-free detector (`@nxgt/material-core/barcode-scanner`, also re-exported by `@nxgt/material/hooks`). See [below](#usebarcodescanner). |
|
|
141
143
|
| `useCountdown(duration, { paused?, onExpire?, key? })` | Seconds left of `duration` (the clock `CountdownTimer` draws), for a button or a message that waits: ticks on the client only, `duration` on the server and first render. A new `duration` **or a new `key`** starts over, so the same `retryAfter` twice restarts it. See [below](#usecountdown). |
|
|
142
144
|
| `useSubmitOnce(busy?)` | One submit in flight: wraps a submit handler so a double click or a second Enter is dropped until the request ends (the handler's promise, or `busy` back to false). See [below](#usesubmitonce). |
|
|
143
145
|
| `useDrafts<T>(storageKey, { serialize?, parse?, storage? })`, `clearDrafts(storageKey, storage?)` | Unsent values kept by key in `sessionStorage`: `{ get, set, drop, clear, keys, loaded }`. Read after the first render (no hydration mismatch), storage errors swallowed, `drop(key)` once saved; `clearDrafts` empties a store key without a hook (sign-out). See [below](#usedrafts). |
|
|
@@ -231,6 +233,82 @@ render, with no flash. Where `matchMedia` does not exist (jsdom) the hook
|
|
|
231
233
|
answers `serverValue` and never subscribes. When CSS alone can do it
|
|
232
234
|
(`hidden xl:block`), prefer CSS: it has no flash at all.
|
|
233
235
|
|
|
236
|
+
### `useLatched`
|
|
237
|
+
|
|
238
|
+
A dialog driven by a nullable subject loses the subject the instant it closes,
|
|
239
|
+
while its exit animation still plays. Read the subject through `useLatched` and
|
|
240
|
+
the last one stays on screen until the dialog has gone. [`ConfirmDialog`](../components/feedback-and-status.md#confirmdialog)
|
|
241
|
+
does this for its own props; use the hook for a `Dialog`, `CustomDialog` or
|
|
242
|
+
`ConfirmationDialog` of your own.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { Dialog, DialogContent, DialogTitle } from '@nxgt/material/components';
|
|
246
|
+
import { useLatched } from '@nxgt/material/hooks';
|
|
247
|
+
|
|
248
|
+
export function ItemDialog({ item, onClose }: { item: Item | null; onClose: () => void }) {
|
|
249
|
+
const shown = useLatched(item); // still the last item while the dialog fades out
|
|
250
|
+
return (
|
|
251
|
+
<Dialog open={item !== null} onOpenChange={(o) => !o && onClose()}>
|
|
252
|
+
<DialogContent>
|
|
253
|
+
<DialogTitle>{shown?.name}</DialogTitle>
|
|
254
|
+
</DialogContent>
|
|
255
|
+
</Dialog>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`useLatchedWhileClosed(open, value)` is the same for a value built from several
|
|
261
|
+
things: it returns `value` while `open`, and the value of the last open render
|
|
262
|
+
once `open` is false.
|
|
263
|
+
|
|
264
|
+
### `useBarcodeScanner`
|
|
265
|
+
|
|
266
|
+
A USB or Bluetooth barcode reader is a keyboard: it types the whole code in a
|
|
267
|
+
few milliseconds a key and ends with Enter. The hook tells that run from a
|
|
268
|
+
person typing by timing alone, with one capture-phase `keydown` listener on
|
|
269
|
+
the document, for a till, a stock count or a check-in screen. It does not draw
|
|
270
|
+
barcodes: that is [`Barecode`](../components/data-display.md#barecode).
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import { useState } from 'react';
|
|
274
|
+
import { useBarcodeScanner } from '@nxgt/material/hooks';
|
|
275
|
+
|
|
276
|
+
export function Till({ add }: { add: (code: string) => void }) {
|
|
277
|
+
const [query, setQuery] = useState('');
|
|
278
|
+
useBarcodeScanner({ onScan: add, acceptIn: '[data-till-search]' });
|
|
279
|
+
return (
|
|
280
|
+
<input
|
|
281
|
+
data-till-search=""
|
|
282
|
+
aria-label="Search an item"
|
|
283
|
+
value={query}
|
|
284
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
285
|
+
/>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
| Option | Default | Effect |
|
|
291
|
+
| --- | --- | --- |
|
|
292
|
+
| `onScan` | required | called with the code when a scan ends; the Enter that closed it is swallowed (`preventDefault`, `stopPropagation`) |
|
|
293
|
+
| `enabled` | `true` | while `false` nothing is listened to and nothing is swallowed |
|
|
294
|
+
| `acceptIn` | none | a CSS selector of the form fields a scan is also accepted in (the search field); every other `input`, `textarea`, `select` and editable element is typed in. Outside form fields a scan is always accepted. In an accepted field the scan's characters are already typed in when Enter arrives: `onScan` clears the field itself |
|
|
295
|
+
| `maxGap` | `50` | the longest pause between two keys of one scan, in ms |
|
|
296
|
+
| `minLength` | `4` | the shortest code taken for a scan |
|
|
297
|
+
| `onKey` | none | `(event, { result, pending })` for each key the scanner looked at: a screen with its own shortcuts skips them while `pending.length > 1`, since those keys are a scan's |
|
|
298
|
+
|
|
299
|
+
Ctrl, Meta and Alt chords and key repeat are ignored. `onScan` and `onKey` are
|
|
300
|
+
read from a ref, so a new function each render never re-binds the listener.
|
|
301
|
+
The hook does nothing on the server. A reader that types slower than 50 ms a
|
|
302
|
+
key (some Bluetooth modes) needs a larger `maxGap`.
|
|
303
|
+
|
|
304
|
+
Outside React, `createScanner({ maxGap?, minLength? })` from
|
|
305
|
+
`@nxgt/material-core/barcode-scanner` (or `@nxgt/material/hooks`, which re-exports it) is the pure detector:
|
|
306
|
+
`feed(key, time)` answers `{ kind: 'pass' }`, `{ kind: 'buffering' }` or
|
|
307
|
+
`{ kind: 'scan', code }`; `reset()` and `pending` complete it. The timing is
|
|
308
|
+
the caller's (`event.timeStamp`), so it is tested without a clock.
|
|
309
|
+
`attachBarcodeScanner(target, options)` is the DOM binding without React and
|
|
310
|
+
returns what removes the listener.
|
|
311
|
+
|
|
234
312
|
### `useCountdown`
|
|
235
313
|
|
|
236
314
|
The clock behind [`CountdownTimer`](../components/feedback-and-status.md#countdowntimer),
|