@nxgt/material 3.82.0 → 3.83.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.
@@ -20005,18 +20005,22 @@ function RN({ className: e }) {
20005
20005
  //#endregion
20006
20006
  //#region lib/components/ui/media/galleries/partials/gallery-selection-bar.tsx
20007
20007
  function zN() {
20008
- let { state: e, selectable: t, clearSelection: n } = KC(), { t: r } = L();
20008
+ let { state: e, selectable: t, selectionActions: n, clearSelection: r } = KC(), { t: i } = L();
20009
20009
  return !t || e.selectedIds.length === 0 ? null : /* @__PURE__ */ K("div", {
20010
20010
  "data-slot": "gallery-selection-bar",
20011
20011
  className: "mb-2 flex flex-wrap items-center justify-between gap-2 rounded-(--nxgt-shape-corner-lg) border border-border bg-background px-3 py-2",
20012
20012
  children: [/* @__PURE__ */ G(Ot, {
20013
20013
  className: "text-sm",
20014
- children: r("material.gallery.selected", "{count} selected", { count: e.selectedIds.length })
20015
- }), /* @__PURE__ */ G(St, {
20016
- variant: "ghost",
20017
- color: "default",
20018
- onClick: n,
20019
- children: r("material.gallery.clear-selection", "Clear selection")
20014
+ children: i("material.gallery.selected", "{count} selected", { count: e.selectedIds.length })
20015
+ }), /* @__PURE__ */ K("div", {
20016
+ "data-slot": "gallery-selection-actions",
20017
+ className: "flex flex-wrap items-center gap-2",
20018
+ children: [typeof n == "function" ? n(e.selectedIds) : n, /* @__PURE__ */ G(St, {
20019
+ variant: "ghost",
20020
+ color: "default",
20021
+ onClick: r,
20022
+ children: i("material.gallery.clear-selection", "Clear selection")
20023
+ })]
20020
20024
  })]
20021
20025
  });
20022
20026
  }
@@ -20342,68 +20346,69 @@ function oP({ open: e, close: t, currentIndex: n, lightboxOpen: r }) {
20342
20346
  //#endregion
20343
20347
  //#region lib/components/ui/media/galleries/use-gallery.ts
20344
20348
  function sP(e) {
20345
- let { items: t, defaultIndex: n = 0, defaultOpen: r = !1, onDelete: i, trigger: a, selectable: o = !1 } = e, s = eP(e), [c, l] = Vt(UM, {
20349
+ let { items: t, defaultIndex: n = 0, defaultOpen: r = !1, onDelete: i, trigger: a, selectable: o = !1, selectionActions: s } = e, c = eP(e), [l, u] = Vt(UM, {
20346
20350
  currentIndex: n,
20347
- loop: s.loop ?? !1,
20351
+ loop: c.loop ?? !1,
20348
20352
  lightboxOpen: r
20349
20353
  });
20350
- nP(e, c, l);
20351
- let { next: u, prev: d, select: f, downloadCurrent: p } = XN(t, c, l), m = oP({
20352
- lightboxOpen: c.lightboxOpen,
20353
- open: l.openLightbox,
20354
- close: l.closeLightbox,
20355
- currentIndex: c.currentIndex
20356
- }), h = B((e, t) => {
20357
- s.lightbox ? m.openWith(e, t) : l.setIndex(e);
20354
+ nP(e, l, u);
20355
+ let { next: d, prev: f, select: p, downloadCurrent: m } = XN(t, l, u), h = oP({
20356
+ lightboxOpen: l.lightboxOpen,
20357
+ open: u.openLightbox,
20358
+ close: u.closeLightbox,
20359
+ currentIndex: l.currentIndex
20360
+ }), g = B((e, t) => {
20361
+ c.lightbox ? h.openWith(e, t) : u.setIndex(e);
20358
20362
  }, [
20359
- s.lightbox,
20360
- l,
20361
- m.openWith
20362
- ]), g = qN({
20363
+ c.lightbox,
20364
+ u,
20365
+ h.openWith
20366
+ ]), _ = qN({
20363
20367
  items: t,
20364
- state: c,
20365
- sliceActions: l,
20366
- resolved: s,
20367
- next: u,
20368
- prev: d,
20369
- close: m.closeWith,
20370
- downloadCurrent: p,
20371
- zoomable: t[c.currentIndex]?.kind !== "video"
20368
+ state: l,
20369
+ sliceActions: u,
20370
+ resolved: c,
20371
+ next: d,
20372
+ prev: f,
20373
+ close: h.closeWith,
20374
+ downloadCurrent: m,
20375
+ zoomable: t[l.currentIndex]?.kind !== "video"
20372
20376
  });
20373
20377
  return {
20374
20378
  items: t,
20375
- resolved: s,
20376
- state: c,
20377
- current: WM.selectCurrent(c, t),
20378
- canNext: WM.selectCanNext(c, t.length),
20379
- canPrev: WM.selectCanPrev(c, t.length),
20379
+ resolved: c,
20380
+ state: l,
20381
+ current: WM.selectCurrent(l, t),
20382
+ canNext: WM.selectCanNext(l, t.length),
20383
+ canPrev: WM.selectCanPrev(l, t.length),
20380
20384
  onDelete: i,
20381
20385
  trigger: a,
20382
- openAt: h,
20383
- select: f,
20384
- next: u,
20385
- prev: d,
20386
- closeLightbox: m.closeWith,
20387
- setZoom: l.setZoom,
20388
- setPan: l.setPan,
20389
- morph: m.morph,
20390
- toggleLoop: l.toggleLoop,
20391
- goToFirst: () => l.setIndex(0),
20392
- goToLast: () => l.setIndex(Math.max(0, t.length - 1)),
20393
- downloadCurrent: p,
20386
+ openAt: g,
20387
+ select: p,
20388
+ next: d,
20389
+ prev: f,
20390
+ closeLightbox: h.closeWith,
20391
+ setZoom: u.setZoom,
20392
+ setPan: u.setPan,
20393
+ morph: h.morph,
20394
+ toggleLoop: u.toggleLoop,
20395
+ goToFirst: () => u.setIndex(0),
20396
+ goToLast: () => u.setIndex(Math.max(0, t.length - 1)),
20397
+ downloadCurrent: m,
20394
20398
  selectable: o,
20395
- toggleSelected: l.toggleSelected,
20396
- clearSelection: l.clearSelection,
20397
- onKeyDown: g
20399
+ selectionActions: s,
20400
+ toggleSelected: u.toggleSelected,
20401
+ clearSelection: u.clearSelection,
20402
+ onKeyDown: _
20398
20403
  };
20399
20404
  }
20400
20405
  //#endregion
20401
20406
  //#region lib/components/ui/media/galleries/gallery.tsx
20402
20407
  function cP(e) {
20403
- let t = sP(e), { items: n, preset: r, layout: i, columns: a, aspect: o, loop: s, maxItems: c, lightbox: l, captions: u, shape: d, carousel: f, carouselAlign: p, carouselOrientation: m, defaultIndex: h, currentIndex: g, onIndexChange: _, open: v, defaultOpen: y, onOpenChange: b, onDelete: x, selectable: S, onSelectionChange: C, trigger: w, ...T } = e;
20408
+ let t = sP(e), { items: n, preset: r, layout: i, columns: a, aspect: o, loop: s, maxItems: c, lightbox: l, captions: u, shape: d, carousel: f, carouselAlign: p, carouselOrientation: m, defaultIndex: h, currentIndex: g, onIndexChange: _, open: v, defaultOpen: y, onOpenChange: b, onDelete: x, selectable: S, onSelectionChange: C, selectionActions: w, trigger: T, ...E } = e;
20404
20409
  return /* @__PURE__ */ G(GC, {
20405
20410
  value: t,
20406
- children: /* @__PURE__ */ G(lP, { ...T })
20411
+ children: /* @__PURE__ */ G(lP, { ...E })
20407
20412
  });
20408
20413
  }
20409
20414
  function lP({ className: e, onKeyDown: t, ...r }) {
@@ -18,6 +18,7 @@ from `@nxgt/material/components`.
18
18
  | <img src="../previews/data-display-periodcards.webp" width="96" alt=""> | [PeriodCards](#periodcards) | Consecutive date ranges (terms, sprints) as cards, with a current marker and an optional link per card |
19
19
  | <img src="../previews/data-display-chip.webp" width="96" alt=""> | [Chip](#chip) | Three looks and a dashed add pill, with slots, a selected state and a dismiss button |
20
20
  | <img src="../previews/data-display-statcard.webp" width="96" alt=""> | [StatCard](#statcard) | KPI tile with value, hint, icon and toned delta; `href` makes it one link |
21
+ | | [TotalsList](#totalslist) | Labelled figures in a `dl`, amounts right-aligned, a ruled grand-total row; the screen twin of `PrintTotals` |
21
22
  | <img src="../previews/data-display-metric-cards.webp" width="96" alt=""> | [Metric cards](#metric-cards) | Sixteen dashboard tiles |
22
23
  | <img src="../previews/data-display-card-presets.webp" width="96" alt=""> | [Card presets](#card-presets) | Twenty-seven ready-made cards |
23
24
  | <img src="../previews/data-display-statusindicator.webp" width="96" alt=""> | [StatusIndicator](#statusindicator) | Coloured status dot with a label |
@@ -194,7 +195,7 @@ no `style` attribute and a strict CSP is fine.
194
195
  - Seat changes are not animated beyond a colour fade, which stops under
195
196
  `prefers-reduced-motion`.
196
197
 
197
- **Storybook:** `Data display/SeatRing` · **Vue:** not yet (#2470) (#1931)
198
+ **Storybook:** `Data display/SeatRing` · **Vue:** not yet (#1931)
198
199
 
199
200
  ---
200
201
 
@@ -269,7 +270,7 @@ const [open, setOpen] = useState(false);
269
270
  the layout follows `dir` (RTL) and dark mode.
270
271
  - Keep `front` and `description` free of the secret: they are always in the page.
271
272
 
272
- **Storybook:** `Data display/SecretCard` · **Vue:** not yet (#2470) (#1938)
273
+ **Storybook:** `Data display/SecretCard` · **Vue:** not yet (#1938)
273
274
 
274
275
  ---
275
276
 
@@ -603,6 +604,35 @@ pass `disabled` with a `Spinner` in `leading`, or use a `Button`.
603
604
 
604
605
  ---
605
606
 
607
+ ## TotalsList
608
+
609
+ On-screen counterpart of `PrintTotals`: a `dl` of labelled figures, labels at the start, amounts right-aligned in tabular figures, one column at any width (a side rail, a phone). Rows have the shape of `PrintTotalRow` (`label`, `value`, `last`), so one array feeds the screen and its print.
610
+
611
+ ```tsx
612
+ import { TotalsList } from '@nxgt/material/components';
613
+
614
+ <TotalsList
615
+ label="Order totals"
616
+ rows={[
617
+ { label: 'Subtotal', value: '1,000.00' },
618
+ { label: 'Tax (19%)', value: '190.00' },
619
+ { label: 'Total', value: '1,190.00', last: true },
620
+ ]}
621
+ />
622
+ ```
623
+
624
+ | Prop | Type | Default | Effect |
625
+ | --- | --- | --- | --- |
626
+ | `rows` | `{ label, value, last?, strong?, tone? }[]` | required | `last` makes the grand total larger and bold with a rule above; `strong` emphasises a row with no rule; `tone` colours the amount (`error` for a due or negative amount, `success` for a credit; an `AppTones` tone works too) |
627
+ | `label` | `string` | — | accessible name: the block becomes a `group` named by it |
628
+ | `variant` | `'card' \| 'plain'` | `'card'` | `plain` has no border or padding, to sit inside a card you already have |
629
+
630
+ Format the amounts for the locale before passing them (`Intl.NumberFormat`); a long label wraps and the amount stays whole.
631
+
632
+ **Storybook:** `Data display/TotalsList`
633
+
634
+ ---
635
+
606
636
  ## StatCard
607
637
 
608
638
  ![StatCard](../previews/data-display-statcard.webp)
@@ -812,7 +842,7 @@ import { TubeCap } from '@nxgt/material/components';
812
842
  | `action` | `ReactNode` | none | trailing slot, such as an `IconButton` |
813
843
  | `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | cap of 20, 28 or 40px |
814
844
 
815
- `TUBE_CAP_KINDS` lists the kinds. The look is CSS on `data-kind`, with no inline style (strict CSP). The cap is `aria-hidden`.
845
+ `TUBE_CAP_KINDS` lists the kinds, and `TubeCapKind` is the exported type of `kind`. The look is CSS on `data-kind`, with no inline style (strict CSP). The cap is `aria-hidden`.
816
846
 
817
847
  **Storybook:** `Data display/TubeCap` · **Vue:** `TubeCap` (`@nxgt/material-vue/components`)
818
848
 
@@ -1367,7 +1397,7 @@ and the user's zone) and both sides agree:
1367
1397
  <ChangeHistory entries={entries} now={loaderData.now} timeZone={loaderData.timeZone} />
1368
1398
  ```
1369
1399
 
1370
- **Storybook:** `Data display/ChangeHistory` · **Vue:** not yet (#2470) (tracked as a `vue` issue)
1400
+ **Storybook:** `Data display/ChangeHistory` · **Vue:** not yet (tracked as a `vue` issue)
1371
1401
 
1372
1402
  ---
1373
1403
 
@@ -1404,7 +1434,7 @@ The offset follows the instant (`Europe/Paris` is `UTC+1` in winter and
1404
1434
  `UTC+2` in summer). `ChangeHistory` takes a `timeZone` and `RelativeTime` a `now`
1405
1435
  the same way; pass the app's zone from the server so every screen agrees.
1406
1436
 
1407
- **Storybook:** `Data display/ZonedTime` · **Vue:** not yet (#2470) (tracked as a `vue` issue)
1437
+ **Storybook:** `Data display/ZonedTime` · **Vue:** not yet (tracked as a `vue` issue)
1408
1438
 
1409
1439
  ---
1410
1440
 
@@ -1473,7 +1503,7 @@ own, so they add up to less.
1473
1503
  - Bar widths are SVG attributes, so server markup has no `style` attribute and
1474
1504
  a strict CSP is fine.
1475
1505
 
1476
- **Storybook:** `Data display/VoteTally` · **Vue:** not yet (#2470) (#1940)
1506
+ **Storybook:** `Data display/VoteTally` · **Vue:** not yet (#1940)
1477
1507
 
1478
1508
  ---
1479
1509
 
@@ -1806,7 +1836,7 @@ lock set by another code form disables its field and leaves Cancel usable.
1806
1836
  button by `getByRole('button', { name: 'Activate' })` and the lock by
1807
1837
  `getByRole('alert')`.
1808
1838
 
1809
- **Storybook:** `Data display/EnrolmentCard`, `…/Shared lock`, `Guides/Accessibility/Touch targets/Verification code` · **Vue:** not yet (#2470)
1839
+ **Storybook:** `Data display/EnrolmentCard`, `…/Shared lock`, `Guides/Accessibility/Touch targets/Verification code` · **Vue:** not yet
1810
1840
 
1811
1841
  ---
1812
1842
 
@@ -1949,4 +1979,4 @@ import { RecoveryCodesStatus, VerificationLockProvider } from '@nxgt/material/co
1949
1979
 
1950
1980
  It reads the page-wide lock like [EnrolmentCard](#enrolmentcard) does. With `requireCode={false}` there is no code form, so `error` and `attemptsLeft` are not shown: the plain button follows `pending`, and is disabled by `locked` and by the page-wide lock; then the lock's text (the message of the answer that locked, or "Too many wrong codes. Try again later.") shows above it as a muted line that describes the button.
1951
1981
 
1952
- **Storybook:** `Data display/RecoveryCodesStatus`, `Data display/EnrolmentCard/Shared lock` · **Vue:** not yet (#2470)
1982
+ **Storybook:** `Data display/RecoveryCodesStatus`, `Data display/EnrolmentCard/Shared lock` · **Vue:** not yet
@@ -602,6 +602,14 @@ const items: GalleryItem[] = [
602
602
 
603
603
  <Gallery items={items} preset="grid" selectable onSelectionChange={console.log} />;
604
604
 
605
+ // Bulk actions on the selection: they appear in the "n selected" bar.
606
+ <Gallery
607
+ items={items}
608
+ preset="grid"
609
+ selectable
610
+ selectionActions={(ids) => <Button onClick={() => removeAll(ids)}>Delete</Button>}
611
+ />;
612
+
605
613
  <LightboxGallery items={items} trigger={<button type="button">Open gallery</button>} />;
606
614
 
607
615
  <MiniGallery items={items} maxItems={5} />;
@@ -622,6 +630,7 @@ with a `+N` tile).
622
630
  | `carouselAlign` / `carouselOrientation` | `'start' \| 'center'` / `'horizontal' \| 'vertical'` | `'start'` / `'horizontal'` | where the large tile of `hero` sits; the scroll axis |
623
631
  | `lightbox` | `boolean` | from `preset` | click opens the full-screen viewer (gestures, zoom, filmstrip, download, keyboard) |
624
632
  | `selectable` | `boolean` | — | checkboxes on tiles; `onSelectionChange(ids)` |
633
+ | `selectionActions` | `ReactNode \| (selectedIds: string[]) => ReactNode` | — | bulk actions (download, move, delete) at the end of the "n selected" bar of a `selectable` gallery; a render function receives the selected ids. Hidden while nothing is selected; wraps under the count on a phone |
625
634
  | `open` / `defaultOpen` / `onOpenChange` | `boolean` | — | control the lightbox |
626
635
  | `onDelete` | `(item) => void` | — | adds a delete button on each tile |
627
636
  | `aria-label` / `aria-labelledby` | `string` | `'Gallery'` label | names the gallery (the root has `role="application"`); a given `aria-labelledby` replaces the default label. `MiniGallery` and `LightboxGallery` take both. Other `div` props (`id`, `data-*`, `onKeyDown`) reach the root element |
@@ -1913,6 +1922,8 @@ Parts: `PrintDocument`, `PrintHeader`, `PrintSection`, `PrintInfoRow`,
1913
1922
  `definePrintColumns` and the helpers `resolvePrintTable`,
1914
1923
  `resolvePrintFields`, `toPrintValue` and `formatPrintValue`.
1915
1924
 
1925
+ For the same rows on screen, use [`TotalsList`](data-display.md#totalslist).
1926
+
1916
1927
  **To get the file**, pass the document to one function: `downloadPdf`
1917
1928
  saves it, `openPdf` opens it in a new tab (a download when a popup blocker
1918
1929
  stops the tab), `usePdfFile` keeps a preview current, and `pdfResponse`
@@ -204,8 +204,11 @@ The props of each are in
204
204
  The `Guides/Recipes` stories in Storybook are tested whole screens (the code
205
205
  ships with the `screen-recipes` skill of the `nxgt-material` plugin): a home
206
206
  dashboard, a list and detail, a form and dialog, a table and settings, a dense
207
- detail page, a status board, a landing page and a search dialog with its
208
- results page. Start from the closest one and replace the data.
207
+ detail page, a status board, a landing page, a search dialog with its results
208
+ page, a team feed, the sign-in, sign-up and forgotten-password screens, a
209
+ gallery of pictures, settings with sections, a messages thread, an overview of
210
+ rearrangeable widgets on `DashboardGrid` and a document editor. Start from the
211
+ closest one and replace the data.
209
212
 
210
213
  ## See also
211
214
 
package/docs/roadmap.md CHANGED
@@ -166,13 +166,10 @@ version they shipped in, and planned entries may name the major they target.
166
166
  descriptions, a small empty state, loading rows and an in-list search
167
167
  pill. What changes on upgrade:
168
168
  [The field look (2.x)](guides/field-look-2-x.md)
169
+ - **`TotalsList`** — the on-screen `PrintTotals` (#2490): React only for now; Vue twin is #2492.
169
170
 
170
171
  ## Next
171
172
 
172
- - **Seven more layout recipes** — Feed, Gallery, Auth, Thread, settings,
173
- dashboard and editor screens, each a tested Storybook recipe under
174
- Guides/Recipes with its code beside it, on the layouts the shell now ships
175
- ([#2413](https://github.com/softistx/nxgt-material/issues/2413)).
176
173
  - **FilterBar "Add a filter" stays on its row** — the button no longer wraps
177
174
  alone onto a second row on a desktop screen ([#1752](https://github.com/softistx/nxgt-material/issues/1752)).
178
175
 
@@ -383,6 +380,8 @@ version they shipped in, and planned entries may name the major they target.
383
380
 
384
381
  ## Shipped
385
382
 
383
+ - **Seven more layout recipes (next minor)** — whole screens to start from under Guides/Recipes, each a tested Storybook story with its code in the plugin's `screen-recipes` skill: a team feed, a gallery of pictures by day, sign-in, sign-up and forgotten-password screens, a messages thread, settings with sections, an overview of rearrangeable widgets and a document editor, on the layouts the shell ships ([#2413](https://github.com/softistx/nxgt-material/issues/2413)).
384
+
386
385
  - **Search dialog and results page recipe (next patch)** — a command dialog (Ctrl/Cmd+K and a header button), debounced grouped suggestions, type chips, a conditional filter and highlighted result cards, with its code in the plugin's `screen-recipes` skill ([#2333](https://github.com/softistx/nxgt-material/issues/2333)).
387
386
 
388
387
  - **Layouts: adaptive navigation, headers and page layouts (3.80.0)** — `ActivityLayout` takes `navigation` (a bottom bar with More and badges, a rail with a FAB or expanded, a standard or modal drawer, a collapsible sidebar, a top navigation with dropdowns, two-level navigation, a floating toolbar or none, chosen for phone, tablet and desktop) and `header` (small, center-aligned, medium and large collapsing on scroll, hide on scroll, a search bar that opens full screen on a phone, sticky tabs), and a route adds a back arrow with `handle.back`; each piece is also exported. Page layouts grow with it: an inspector pane and the selection in the URL for `ListDetailsLayout`, phone modes and a container breakpoint for `PaneLayout`, a centred form and wizard for `ThreadLayout`, a split `AuthCard`, spans and masonry for `ResponsiveGrid`, tabs for `Kanban` on a phone, `Doc` drawn inside the shell, and new `SettingsLayout`, `DashboardGrid` (tiles the user rearranges), `EditorLayout` and `LandingLayout` (with `LandingSection` and `SiteFooter`). Everything is opt-in; the shell's default is unchanged. [Layouts guide](guides/layouts.md) ([#2103](https://github.com/softistx/nxgt-material/issues/2103)). Follow-ups: a resize handle for `EditorLayout`'s properties panel and a separate open state for its phone sheet.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.82.0",
3
+ "version": "3.83.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"