@nxgt/material 3.24.1 → 3.25.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 +14 -0
- package/README.md +7 -5
- package/dist/{components-CbuWrY21.js → components-BF6wCEzH.js} +9453 -9220
- package/dist/components.js +23 -23
- package/dist/lib/components/ui/collections/filter/bar/filter-bar.d.ts +1 -1
- package/dist/lib/components/ui/collections/filter/internal.d.ts +1 -0
- package/dist/lib/components/ui/collections/filter/query/types.d.ts +11 -0
- package/dist/lib/components/ui/collections/filter/sections/filter-band-classes.d.ts +5 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-band-classes.d.ts +3 -4
- package/dist/lib/components/ui/display/description/description.d.ts +26 -3
- package/dist/lib/components/ui/display/description/partials/description-plain.d.ts +6 -0
- package/dist/lib/components/ui/display/description-list/description-list.context.d.ts +15 -0
- package/dist/lib/components/ui/display/description-list/description-list.d.ts +35 -0
- package/dist/lib/components/ui/display/description-list/description-list.utils.d.ts +17 -0
- package/dist/lib/components/ui/display/description-list/index.d.ts +3 -0
- package/dist/lib/components/ui/display/description-list/partials/description-list-group.d.ts +13 -0
- package/dist/lib/components/ui/display/index.d.ts +1 -0
- package/dist/lib/components/ui/display/list-tile/list-tile-dense.sample.d.ts +25 -0
- package/dist/lib/components/ui/display/list-tile/list-tile.d.ts +26 -1
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-actions.d.ts +8 -0
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-body.d.ts +4 -1
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-classes.d.ts +5 -1
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-line.d.ts +1 -1
- package/dist/lib/components/ui/display/list-tile/partials/list-tile-toggle.d.ts +1 -1
- package/dist/lib/components/ui/display/stat-card/stat-card-group.d.ts +11 -3
- package/dist/lib/components/ui/layout/index.d.ts +1 -0
- package/dist/lib/components/ui/layout/responsive-grid/index.d.ts +1 -0
- package/dist/lib/components/ui/layout/responsive-grid/min-item.d.ts +13 -0
- package/dist/lib/components/ui/layout/responsive-grid/responsive-grid.d.ts +20 -1
- package/dist/lib/components/ui/layout/responsive-grid/responsive-grid.utils.d.ts +20 -0
- package/dist/lib/components/ui/layout/section-columns/index.d.ts +2 -0
- package/dist/lib/components/ui/layout/section-columns/section-columns.d.ts +25 -0
- package/dist/lib/components/ui/layout/section-columns/section-columns.utils.d.ts +9 -0
- package/dist/main.js +40 -40
- package/dist/material.css +1 -1
- package/docs/components/data-display.md +121 -8
- package/docs/components/data-table-filter-and-lists.md +20 -2
- package/docs/components/navigation-and-layout.md +63 -5
- package/docs/guides/app-shell.md +43 -0
- package/docs/previews/data-display-descriptionlist.webp +0 -0
- package/docs/previews/navigation-layout-sectioncolumns.webp +0 -0
- package/docs/roadmap.md +14 -0
- package/package.json +1 -1
|
@@ -20,8 +20,9 @@ from `@nxgt/material/components`.
|
|
|
20
20
|
| <img src="../previews/data-display-statusindicator.webp" width="96" alt=""> | [StatusIndicator](#statusindicator) | Coloured status dot with a label |
|
|
21
21
|
| <img src="../previews/data-display-summarydata.webp" width="96" alt=""> | [SummaryData](#summarydata) | Label / value pairs as a grid or inline |
|
|
22
22
|
| <img src="../previews/data-display-description.webp" width="96" alt=""> | [Description](#description) | One read-only label / value pair |
|
|
23
|
+
| <img src="../previews/data-display-descriptionlist.webp" width="96" alt=""> | [DescriptionList](#descriptionlist) | A dense auto-fill grid of label / value pairs in a `dl`, with a panels variant |
|
|
23
24
|
| <img src="../previews/data-display-entityheader.webp" width="96" alt=""> | [EntityHeader](#entityheader) | Page header with status, metadata and actions |
|
|
24
|
-
| <img src="../previews/data-display-listtile.webp" width="96" alt=""> | [ListTile](#listtile) | Row with leading / trailing slots
|
|
25
|
+
| <img src="../previews/data-display-listtile.webp" width="96" alt=""> | [ListTile](#listtile) | Row with leading / trailing slots, row actions, chips and a divided variant |
|
|
25
26
|
| <img src="../previews/data-display-accordioncard.webp" width="96" alt=""> | [AccordionCard](#accordioncard) | Accordion rendered from `items` |
|
|
26
27
|
| <img src="../previews/data-display-timeline.webp" width="96" alt=""> | [Timeline](#timeline) | Vertical event timeline with relative times |
|
|
27
28
|
| <img src="../previews/data-display-eventchip.webp" width="96" alt=""> | [EventChip](#eventchip) | Calendar event pill, soft, solid or outlined |
|
|
@@ -456,6 +457,18 @@ import { StatCard } from '@nxgt/material/components';
|
|
|
456
457
|
|
|
457
458
|
**`StatCardGroup`: an even grid of compact tiles.** Four hugging tiles leave an orphan at 360 px. Wrap the `size="sm"` tiles in a `StatCardGroup`: while the group is narrower than `md` (28 rem, its own width, not the window's) the tiles are two equal columns, 2×2 for four (an odd last tile takes the whole row); from there they are one row of tiles that hug their content, wrapping if they must. It takes the props of a `div`; the root is `data-slot="stat-card-group"` and `StatCardGroupProps` is exported. `EntityHeader`'s `stats` use it.
|
|
458
459
|
|
|
460
|
+
**Beside a title, in a flex row.** The group is a size container, which counts for nothing in a flex row: it used to collapse to a few pixels, its tiles a character wide. It now keeps at least 16 rem (or its parent's width, if less) and grows into the row's free room, so it needs no `flex-1`, `min-w-*` or `basis-*` from you; in a block parent nothing changes. `align` places the tiles once the group is wider than they are: `'start'` (default) or `'end'`, against the group's end edge, without a selector into its children (`data-align` marks it). In a column (as `EntityHeader`'s body) pass `className="grow-0"`, since growing there would be in height. To let the group shrink below the 16 rem floor in a flex row, pass `className="min-w-0"`.
|
|
461
|
+
|
|
462
|
+
```tsx
|
|
463
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
464
|
+
<h2>Class 6B</h2>
|
|
465
|
+
<StatCardGroup align="end">
|
|
466
|
+
<StatCard size="sm" label="Students" value={22} />
|
|
467
|
+
<StatCard size="sm" label="Classes" value={6} />
|
|
468
|
+
</StatCardGroup>
|
|
469
|
+
</div>
|
|
470
|
+
```
|
|
471
|
+
|
|
459
472
|
```tsx
|
|
460
473
|
import { StatCard, StatCardGroup } from '@nxgt/material/components';
|
|
461
474
|
|
|
@@ -471,7 +484,7 @@ Parts: the root carries `data-slot="stat-card"`, `data-variant` and, when a tone
|
|
|
471
484
|
|
|
472
485
|
In `filled`, the delta and its trend arrow take the on-colour too (the arrow and the sign still say up or down). The outline's share is `--stat-card-ring-share` (`68%`, or `--nxgt-tone-ring-share` for every ring); it draws the tonal card's border, the filled card's edge in a calm zone and the hairline round the icon chip. Known limit: the dark theme's primary `filled` card has an edge of about 2.3:1 on a `Card` and 2.5:1 on the page (its text is 7:1); a `tonal` card avoids it. See [App tones](../guides/theming.md#app-tones-on-shape-fab-avatar-statcard-and-the-metric-cards).
|
|
473
486
|
|
|
474
|
-
**Storybook:** `Data display/StatCard/Group` (`GroupOnPhone`, `GroupOnDesktop`), `Data display/StatCard/Tones` · **Vue:** `StatCard` (`StatCardGroup` is React only for now)
|
|
487
|
+
**Storybook:** `Data display/StatCard/Group` (`GroupOnPhone`, `GroupOnDesktop`), `Data display/StatCard/Group in a flex row`, `Data display/StatCard/Tones` · **Vue:** `StatCard` (`StatCardGroup` is React only for now)
|
|
475
488
|
|
|
476
489
|
---
|
|
477
490
|
|
|
@@ -620,15 +633,77 @@ import { Description } from '@nxgt/material/components';
|
|
|
620
633
|
</div>
|
|
621
634
|
```
|
|
622
635
|
|
|
623
|
-
`label` and `value` are its
|
|
624
|
-
`value` renders nothing, so optional fields need no guard. The label is a small
|
|
636
|
+
`label` and `value` are its main props; the rest go to the `div`. A `value` of `null`, `undefined`, `false` or `''` renders nothing (`0` is shown), so optional fields need no guard. Inside a `variant="panels"` list a `Description` must sit in a `DescriptionList.Group`; otherwise it renders its stand-alone markup. The label is a small
|
|
625
637
|
muted caption above the value (not a title). `Description` takes one cell of
|
|
626
|
-
the grid it is in; pass `className="col-start-full"` for a full-width row.
|
|
638
|
+
the grid it is in; pass `className="col-start-full"` for a full-width row. It takes `lines` anywhere; inside a [`DescriptionList`](#descriptionlist) also `span` and `labelPosition`.
|
|
627
639
|
|
|
628
640
|
**Storybook:** `Data display/Description` · **Vue:** `Description`
|
|
629
641
|
|
|
630
642
|
---
|
|
631
643
|
|
|
644
|
+
## DescriptionList
|
|
645
|
+
|
|
646
|
+

|
|
647
|
+
|
|
648
|
+
A record's fields as a dense grid of `Description`s in a `dl`: label on top, hairlines, as many columns as fit.
|
|
649
|
+
|
|
650
|
+
```tsx
|
|
651
|
+
import { Description, DescriptionList } from '@nxgt/material/components';
|
|
652
|
+
|
|
653
|
+
<DescriptionList meta="Added on 1 Mar 2026 · Modified on 2 Oct 2026">
|
|
654
|
+
<Description label="Full name" value={patient.name} />
|
|
655
|
+
<Description label="Birth date" value={patient.birthDate} />
|
|
656
|
+
<Description label="Address" lines={patient.addressLines} span="full" />
|
|
657
|
+
<Description label="Phone" value={patient.phone} />
|
|
658
|
+
</DescriptionList>
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
| Prop | Type | Default | Notes |
|
|
662
|
+
| --- | --- | --- | --- |
|
|
663
|
+
| `minItem` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` or a CSS length | `'md'` (14rem) | the narrowest a field gets: 10, 12, 14, 18 or 22rem |
|
|
664
|
+
| `maxColumns` | `1`–`6` | `4` (`panels`: `3`) | the most columns |
|
|
665
|
+
| `meta` | `ReactNode` | — | a muted line under the list |
|
|
666
|
+
| `variant` | `'default' \| 'panels'` | `'default'` | `panels`: tonal groups |
|
|
667
|
+
|
|
668
|
+
Columns are `auto-fill`, so three fields keep narrow, left-aligned columns on a
|
|
669
|
+
wide screen instead of stretching, and there is one column on a phone. The
|
|
670
|
+
layout is preset classes and CSS variables, so it works under a strict CSP; a
|
|
671
|
+
CSS-length `minItem` sets one variable in a `style` attribute, so prefer a
|
|
672
|
+
preset there. Fit three fields or thirty alike.
|
|
673
|
+
|
|
674
|
+
`Description` takes two more props inside a list. `span` is `'full'` (the whole
|
|
675
|
+
row) or a number of columns (used once that many fit; at most `maxColumns`, and
|
|
676
|
+
4; with a CSS-length `minItem` the thresholds are those of `md`). `labelPosition` is `'top'` or `'inline'` (label beside the value). A full-width
|
|
677
|
+
field uses dense packing: the fields after it fill the cells before it, so they
|
|
678
|
+
may be drawn above it while the DOM and reading order stay the source order.
|
|
679
|
+
Put the full-width field where that reads fine, or last. A multi-line value
|
|
680
|
+
(an address) takes `lines={[…]}` or `children`, never a string to split. Entries of `lines` that are `null`, `false` or `""` are skipped, and a `Description` whose `lines` are all empty and that has no `value` renders nothing.
|
|
681
|
+
|
|
682
|
+
```tsx
|
|
683
|
+
<DescriptionList variant="panels">
|
|
684
|
+
<DescriptionList.Group title="Contact">
|
|
685
|
+
<Description label="Address" lines={['12 Sample Street', '75001 Exampleville']} />
|
|
686
|
+
<Description label="Phone" value={patient.phone} />
|
|
687
|
+
</DescriptionList.Group>
|
|
688
|
+
<DescriptionList.Group title="Coverage" headingLevel={2}>
|
|
689
|
+
<Description label="Insurer" value={patient.insurer} />
|
|
690
|
+
</DescriptionList.Group>
|
|
691
|
+
</DescriptionList>
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
`variant="panels"` draws one tonal panel per `DescriptionList.Group` (a `div` with `role="group"`,
|
|
695
|
+
not a landmark, named by its `title`, an `h3` unless `headingLevel` says otherwise, over its
|
|
696
|
+
own `dl`) in a grid of at least 19rem and at most 3 columns. The label sits
|
|
697
|
+
beside the value from the panel's own width (`@xs`, 20rem); `labelPosition`
|
|
698
|
+
pins it. `title` is the heading and `headingLevel` (2 to 6, default 3) its level. `span` has no effect in a panel, and `maxColumns` is clamped to 1 to 6. In the default variant a `Group` only passes its fields through. In `panels`, put the fields in `DescriptionList.Group`s: a `Description` straight in a `panels` list (outside a Group) has no `dl` to sit in, so it falls back to its stand-alone markup.
|
|
699
|
+
|
|
700
|
+
Each pair is a `div` holding a `dt` and a `dd`, valid inside the `dl`; a
|
|
701
|
+
`Description` outside a `DescriptionList` keeps its plain markup.
|
|
702
|
+
|
|
703
|
+
**Storybook:** `Data display/DescriptionList` · **Vue:** later ([#1614](https://github.com/softistx/nxgt-material/issues/1614))
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
632
707
|
## EntityHeader
|
|
633
708
|
|
|
634
709
|

|
|
@@ -736,10 +811,13 @@ A tile with an `href` ends with a discreet chevron after `trailing`: decorative
|
|
|
736
811
|
| `linkComponent` | `AppShellLinkComponent` | plain `<a>` | the link element, as in the app shell: takes `href`, spreads every other prop, forwards `ref` |
|
|
737
812
|
| `onClick` | `MouseEventHandler` | — | renders a `<button>` |
|
|
738
813
|
| `selected` | `boolean` | `false` | the current row: drawn selected; a link gets `aria-current="page"` (your own `aria-current` replaces it), a button `aria-pressed` |
|
|
739
|
-
| `toggle` | `Switch` props | — | makes the tile a switch row (see below): the whole row toggles a Switch at its end; `href`, `onClick`, `actions`, `chevron` and `linkComponent` are refused beside it by type |
|
|
740
|
-
| `size` | `'sm' \| 'md'` | `'md'` | `sm` is the dense
|
|
814
|
+
| `toggle` | `Switch` props | — | makes the tile a switch row (see below): the whole row toggles a Switch at its end; `href`, `onClick`, `actions`, `chips`, `chevron` and `linkComponent` are refused beside it by type |
|
|
815
|
+
| `size` | `'sm' \| 'md'` | `'md'` | `sm` is the dense row (with `chips`, the record row, see below) |
|
|
741
816
|
| `leading`, `trailing`, `subtitle` | `ReactNode` | — | slots; `trailing` sits inside the link, so it holds decoration only |
|
|
742
817
|
| `actions` | `ReactNode` | — | buttons at the end of the tile, outside the link, vertically centred (`data-slot="item-actions"`) |
|
|
818
|
+
| `chips` | `ReactNode` | — | tags after the text, in the compact record row (see below); inside the link, so decoration only; part of the row's accessible name, keep labels short |
|
|
819
|
+
| `variant` | `'tonal' \| 'divided'` | `'tonal'` | `divided`: flat rows with no tonal surface, a hover tint and the record-row layout; the hairline comes with `as="li"` |
|
|
820
|
+
| `as` | `'li'` | — | renders the tile inside a `<li>`, for a `<ul>` you own |
|
|
743
821
|
| `disabled` | `boolean` | — | disables the link or button, and the `actions` with it |
|
|
744
822
|
|
|
745
823
|
With a router, pass `linkComponent` so a click navigates on the client instead of reloading the page (React Router's `Link` takes `to`, so wrap it):
|
|
@@ -776,6 +854,41 @@ export function SettingsRow() {
|
|
|
776
854
|
}
|
|
777
855
|
```
|
|
778
856
|
|
|
857
|
+
**A compact record row.** For a long list of records (patients, students), `chips` adds tags to the row and the tile lays itself out by its own width (a container query: the tile measures itself, so put it in a block, grid or `w-full` flex parent, not a box sized by its content):
|
|
858
|
+
|
|
859
|
+
| Tile width | Layout |
|
|
860
|
+
| --- | --- |
|
|
861
|
+
| under 28rem (`@md`) | title, subtitle under it, chips under the subtitle; `actions` at the end |
|
|
862
|
+
| 28rem and up | the subtitle sits on the title's line |
|
|
863
|
+
| 36rem (`@xl`) and up | the chips sit after the text, before the chevron and the `actions` |
|
|
864
|
+
|
|
865
|
+
`size="sm"` is the dense size, and `actions` stay last at every width. The chips sit inside the link (or button), so they are decoration, never buttons, and they form part of the row's accessible name: keep the chip labels short. On a coarse pointer, the buttons of `actions` at `size="sm"` keep the 44px hit area of an `IconButton` and are spaced so a neighbour never takes it. The 44px spacing applies to the record row (a tile with `chips` or `variant="divided"`); a tile without them renders the markup it always did. A switch row takes `variant` but not `chips`.
|
|
866
|
+
|
|
867
|
+
```tsx
|
|
868
|
+
import { MoreHorizontal } from 'lucide-react';
|
|
869
|
+
import { Avatar, Chip, IconButton, ListTile } from '@nxgt/material/components';
|
|
870
|
+
|
|
871
|
+
<ul aria-label="Patients" className="m-0 grid list-none gap-1 p-0">
|
|
872
|
+
<ListTile
|
|
873
|
+
as="li"
|
|
874
|
+
size="sm"
|
|
875
|
+
href="/patients/204-118"
|
|
876
|
+
title="Amara Okafor"
|
|
877
|
+
subtitle="54 y · MRN 204-118"
|
|
878
|
+
leading={<Avatar className="size-7 text-xs" tone="primary" fallback="AO" />}
|
|
879
|
+
chips={[
|
|
880
|
+
<Chip key="a" size="sm" color="error" label="Penicillin allergy" />,
|
|
881
|
+
<Chip key="d" size="sm" color="warning" label="Type 2 diabetes" />,
|
|
882
|
+
]}
|
|
883
|
+
actions={<IconButton size="sm" aria-label="More for Amara Okafor"><MoreHorizontal /></IconButton>}
|
|
884
|
+
/>
|
|
885
|
+
</ul>
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
`variant="divided"` is the flat version for a denser page: no tonal surface, a hover tint and a hairline under each row. Give the `<ul>` no gap (`m-0 list-none p-0`); the hairline is drawn on the `<li>` of an `as="li"` tile, and the last row has none.
|
|
889
|
+
|
|
890
|
+
**A list container: `as="li"`.** There is no `ListTileGroup`: `as="li"` on the tile and a `<ul>` of your own is the smaller API (the `<ul>` carries your `aria-label`, spacing and width, and the tile stays free of a context, so it still renders on the server alone). The `<li>` wraps the tile's own element, since a link or a button cannot be the `<li>`.
|
|
891
|
+
|
|
779
892
|
**A selected link, for list-detail navigation.** Give `selected` from the router's location: the open row is a link (a shareable URL), `aria-current="page"` and drawn selected. Pass `aria-current="true"` when the selection is not a page.
|
|
780
893
|
|
|
781
894
|
```tsx
|
|
@@ -805,7 +918,7 @@ See also [A `ListTile` link reloads the whole page](../troubleshooting.md#a-list
|
|
|
805
918
|
|
|
806
919
|
The root carries `data-slot="item-detail"` (a tile that is a button or a plain row too).
|
|
807
920
|
|
|
808
|
-
**Storybook:** `Data display/ListTile`, `Data display/ListTile links`, `Data display/ListTile current` · **Vue:** `ListTile`
|
|
921
|
+
**Storybook:** `Data display/ListTile`, `Data display/ListTile links`, `Data display/ListTile current`, `Data display/ListTile/Compact record row` · **Vue:** `ListTile`
|
|
809
922
|
|
|
810
923
|
---
|
|
811
924
|
|
|
@@ -883,7 +883,7 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
|
|
|
883
883
|
| `title` | `ReactNode` | — | the band's title (a string is an `<h2>`). Leave it out when the page's `h1` is elsewhere (the app bar's): the band then has no heading |
|
|
884
884
|
| `description` | `ReactNode` | — | a muted line under the `title`, or alone above the band's controls without one; the `actions` stay at the band's end |
|
|
885
885
|
| `actions` | `ReactNode` | — | the page's actions, at the band's end |
|
|
886
|
-
| `bandDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | `'compact'` halves the room above the band (16px to 8px) and under it (12px to 6px), for a list under a sticky app bar and a breadcrumb; the controls keep their size (the Filter's own `density` is the controls') |
|
|
886
|
+
| `bandDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | (the same prop as [`Filter`](#a-compact-band-for-a-list-inside-a-page-section)'s bar) `'compact'` halves the room above the band (16px to 8px) and under it (12px to 6px), for a list under a sticky app bar and a breadcrumb; the controls keep their size (the Filter's own `density` is the controls') |
|
|
887
887
|
| `pageControls` | `'labelled' \| 'icon'` | `'labelled'` | the footer's Previous / Next: `'icon'` is icon-only, with a tooltip and the names "Previous page" / "Next page" (`DataTable`'s default is unchanged); on a coarse pointer the icon controls are 44x44 boxes (still painted 32px) |
|
|
888
888
|
| `bandActions` | `'stacked' \| 'inline'` | `'stacked'` | with `filter`, where `actions` sit below 768px: `'stacked'` gives them a row above the search; `'inline'` keeps them on the search's row (the search takes the room left), for a lone icon action such as a "+". An action wider than 3.5rem wraps under the search. From 768px both are the same |
|
|
889
889
|
| `filterRef` | `Ref<ServerTableFilterHandle>` | — | with `filter`: `filterRef.current.focus()` focuses the Filter's first control, the search, else the first quick filter, "Add filter" or "Filters (N)" the width shows |
|
|
@@ -1251,10 +1251,28 @@ Its props are `FilterQueryProps`, its `variant` a `FilterQueryVariant`
|
|
|
1251
1251
|
overload per kind, so `<Filter variant="bar" />` does not compile).
|
|
1252
1252
|
`query` / `defaultQuery` + `onQueryChange` carry the user's query, never a
|
|
1253
1253
|
locked condition; `density` (a `FilterDensity`: `'comfortable'` or
|
|
1254
|
-
`'compact'
|
|
1254
|
+
`'compact'`, the controls' size and the gap between them), `bandDensity`
|
|
1255
|
+
(`'comfortable'` or `'compact'`, the bar's own vertical room: see below), `label`, `searchLabel`, `searchPlaceholder`, `liveDelayMs`
|
|
1255
1256
|
(200 ms), `searchApply` and `searchMinLength` tune it. A locked chip is a focusable group named by its text ("Year is 2026-2027"), with "Locked: <reason>" as its description. The other variants
|
|
1256
1257
|
and `FilterProps` are unchanged.
|
|
1257
1258
|
|
|
1259
|
+
#### A compact band for a list inside a page section
|
|
1260
|
+
|
|
1261
|
+
`bandDensity="compact"` is `ServerTable`'s prop, with the same values and the
|
|
1262
|
+
same spacing, on a standalone `Filter variant="bar"`: the bar takes 6px back
|
|
1263
|
+
under it (a `-mb-1.5`, the class `ServerTable`'s band uses), so a short list
|
|
1264
|
+
filtered with `matchQuery` inside a page section does not spend the room of a
|
|
1265
|
+
full page. The controls keep their size: `density` is theirs, and the two are
|
|
1266
|
+
independent. The default is unchanged, and a compact bar carries
|
|
1267
|
+
`data-band-density="compact"`. `variant="bar"` only; a `ServerTable` sets its
|
|
1268
|
+
own band with its own `bandDensity`.
|
|
1269
|
+
|
|
1270
|
+
```tsx
|
|
1271
|
+
<Filter variant="bar" bandDensity="compact" density="compact" model={model} query={query} onQueryChange={setQuery} />
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
**Storybook:** `Data table, filter & lists/Filter/Bar/Band density`.
|
|
1275
|
+
|
|
1258
1276
|
#### The search: apply on Enter, a minimum length
|
|
1259
1277
|
|
|
1260
1278
|
By default the search is live: the text reaches `onQueryChange` (and a
|
|
@@ -14,7 +14,8 @@ documentation-site shell. Everything is imported from
|
|
|
14
14
|
| <img src="../previews/navigation-layout-topappbar.webp" width="96" alt=""> | [TopAppBar, BottomAppBar](#topappbar) | Top and bottom bars |
|
|
15
15
|
| <img src="../previews/navigation-layout-breadcrumb.webp" width="96" alt=""> | [Breadcrumb](#breadcrumb) | Breadcrumb primitives |
|
|
16
16
|
| <img src="../previews/navigation-layout-listdetailslayout.webp" width="96" alt=""> | [ListDetailsLayout, PaneLayout, ThreadLayout](#listdetailslayout) | Page layouts that adapt to phones |
|
|
17
|
-
| <img src="../previews/navigation-layout-responsivegrid.webp" width="96" alt=""> | [ResponsiveGrid](#responsivegrid) | Container-query grid, 1 to 5 columns |
|
|
17
|
+
| <img src="../previews/navigation-layout-responsivegrid.webp" width="96" alt=""> | [ResponsiveGrid](#responsivegrid) | Container-query grid, 1 to 5 columns, or `auto-fill` columns by a minimum tile width (`minItem`) |
|
|
18
|
+
| <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 |
|
|
18
19
|
| <img src="../previews/navigation-layout-resizable.webp" width="96" alt=""> | [ResizableGroup](#resizablegroup) | Resizable panels |
|
|
19
20
|
| <img src="../previews/navigation-layout-skiplink.webp" width="96" alt=""> | [SkipLink](#skiplink) | Skip-to-content link |
|
|
20
21
|
| <img src="../previews/navigation-layout-pager.webp" width="96" alt=""> | [Pager](#pager) | Previous / next cards |
|
|
@@ -442,7 +443,7 @@ app bar.
|
|
|
442
443
|
|
|
443
444
|

|
|
444
445
|
|
|
445
|
-
|
|
446
|
+
Tiles in a grid that follows its own width: stepped columns from 1 to 5, or `auto-fill` columns by a minimum tile width.
|
|
446
447
|
|
|
447
448
|
```tsx
|
|
448
449
|
import { ResponsiveGrid, StatCard } from '@nxgt/material/components';
|
|
@@ -452,12 +453,69 @@ import { ResponsiveGrid, StatCard } from '@nxgt/material/components';
|
|
|
452
453
|
<StatCard label="Orders" value="1,284" />
|
|
453
454
|
<StatCard label="Customers" value="812" />
|
|
454
455
|
</ResponsiveGrid>
|
|
456
|
+
|
|
457
|
+
// A board of small tiles: as many 12rem columns as fit, 1rem apart.
|
|
458
|
+
<ResponsiveGrid minItem="sm" gap="md">
|
|
459
|
+
{rooms.map((room) => <RoomTile key={room.id} room={room} />)}
|
|
460
|
+
</ResponsiveGrid>
|
|
455
461
|
```
|
|
456
462
|
|
|
457
|
-
|
|
458
|
-
|
|
463
|
+
| Prop | Type | Default | Effect |
|
|
464
|
+
| --- | --- | --- | --- |
|
|
465
|
+
| `minItem` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| string` | none | Fill the row with tiles at least this wide: a preset (`xs` 10rem, `sm` 12rem, `md` 14rem, `lg` 18rem, `xl` 22rem, the same as `DescriptionList`) or a CSS length (`'9rem'`). Without it the columns step by width |
|
|
466
|
+
| `gap` | `'sm' \| 'md' \| 'lg'` | `'sm'` | The space between tiles: 0.5rem, 1rem, 1.5rem |
|
|
467
|
+
|
|
468
|
+
Without `minItem`, columns follow the grid's own width, not the window's: 2
|
|
469
|
+
from `@lg`, 3 from `@2xl`, 4 from `@4xl`, 5 from `@6xl`. With it, the tracks
|
|
470
|
+
are `repeat(auto-fill, minmax(min(<minItem>, 100%), 1fr))`: a few tiles keep
|
|
471
|
+
their narrow width instead of stretching, and a wide screen gains columns
|
|
472
|
+
(a 1920 px box holds eight `md` tiles). A preset is a class that sets a CSS
|
|
473
|
+
variable; a custom length sets that variable in a `style` attribute (the
|
|
474
|
+
variable only, never a grid style), so prefer a preset under a strict CSP
|
|
475
|
+
(`style-src` without `'unsafe-inline'`). The `ResponsiveGrid` props are the
|
|
476
|
+
`div`'s plus these two.
|
|
477
|
+
|
|
478
|
+
**Storybook:** `Navigation & layout/ResponsiveGrid` · **Vue:** `ResponsiveGrid` (without `minItem` and `gap`)
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## SectionColumns
|
|
483
|
+
|
|
484
|
+

|
|
485
|
+
|
|
486
|
+
Short sections of a page side by side from a viewport breakpoint, stacked in source order below it: a record's contact beside its identifiers, its related people beside its allergies.
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
import { SectionColumns } from '@nxgt/material/components';
|
|
490
|
+
|
|
491
|
+
<SectionColumns from="2xl">
|
|
492
|
+
<section aria-labelledby="contact">
|
|
493
|
+
<h2 id="contact">Contact</h2>
|
|
494
|
+
…
|
|
495
|
+
</section>
|
|
496
|
+
<section aria-labelledby="identifiers">
|
|
497
|
+
<h2 id="identifiers">Identifiers</h2>
|
|
498
|
+
…
|
|
499
|
+
</section>
|
|
500
|
+
</SectionColumns>
|
|
501
|
+
```
|
|
459
502
|
|
|
460
|
-
|
|
503
|
+
| Prop | Type | Default | Effect |
|
|
504
|
+
| --- | --- | --- | --- |
|
|
505
|
+
| `from` | `'lg' \| 'xl' \| '2xl'` | `'2xl'` | The viewport breakpoint the sections sit side by side from: 1024, 1280 or 1536 px. Below it they stack, in source order |
|
|
506
|
+
| `columns` | `2 \| 3` | `2` | Equal columns from `from`. A third column wants `2xl`: from `lg` it leaves each section about 20rem on a laptop |
|
|
507
|
+
|
|
508
|
+
The columns are equal (`minmax(0, 1fr)`, so a wide child cannot push the page
|
|
509
|
+
sideways) and the children keep their own height (`items-start`): a short
|
|
510
|
+
section is not stretched to its neighbour's. The gap is 1rem. It is a plain
|
|
511
|
+
`div`, **not a landmark**: each child keeps its own heading (a `section` with
|
|
512
|
+
`aria-labelledby`, or a heading in a `div`), and it adds no region of its own.
|
|
513
|
+
It follows the **viewport**, not its container: for tiles that follow their
|
|
514
|
+
box use [ResponsiveGrid](#responsivegrid) with `minItem`, and for one side
|
|
515
|
+
panel beside a page use `ActivityContent`'s `aside`. It takes the `div`'s
|
|
516
|
+
props, so `className` adds to it.
|
|
517
|
+
|
|
518
|
+
**Storybook:** `Navigation & layout/SectionColumns` · **Vue:** later ([#1614](https://github.com/softistx/nxgt-material/issues/1614))
|
|
461
519
|
|
|
462
520
|
---
|
|
463
521
|
|
package/docs/guides/app-shell.md
CHANGED
|
@@ -888,6 +888,49 @@ decides the layout.
|
|
|
888
888
|
- **Width token.** `--nxgt-aside-width` changes `asideWidth="md"` (20rem) app-wide;
|
|
889
889
|
any CSS length goes in `asideWidth` for one screen.
|
|
890
890
|
|
|
891
|
+
## Dense pages on large screens
|
|
892
|
+
|
|
893
|
+
On a 1440 or 1920 px screen a page should show more, not stretch the same four
|
|
894
|
+
fields wider. Four components do it together, each measuring its own space, and
|
|
895
|
+
the `Dense detail page` recipe (`Guides/Recipes` in Storybook, `screen-recipes`
|
|
896
|
+
in the plugin) assembles them for any record: a patient, a student, a project.
|
|
897
|
+
|
|
898
|
+
| The block is | Use | Behaviour |
|
|
899
|
+
| --- | --- | --- |
|
|
900
|
+
| a record's fields | [`DescriptionList`](../components/data-display.md#descriptionlist) | `auto-fill` columns of at least `minItem` (14rem), at most `maxColumns` (4); one column on a phone; `variant="panels"` for titled groups |
|
|
901
|
+
| short sections of a page | [`SectionColumns`](../components/navigation-and-layout.md#sectioncolumns) | side by side from `from` (`2xl`, 1536px, by default), stacked in source order below it |
|
|
902
|
+
| a board of small tiles | [`ResponsiveGrid minItem`](../components/navigation-and-layout.md#responsivegrid) | more columns as the box widens, no stretching |
|
|
903
|
+
| the rows of a long list | [`ListTile`](../components/data-display.md#listtile) `size="sm" as="li"` with `chips` | subtitle on the title's line from 28rem, chips after the text from 36rem |
|
|
904
|
+
| the filter above such a list | [`Filter`](../components/data-table-filter-and-lists.md#filter) `variant="bar" bandDensity="compact"` | 6px less under the bar; `density` stays the controls' size |
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
<ActivityContent hideHeader>
|
|
908
|
+
<EntityHeader title={record.name} stats={stats} metadata={facts} />
|
|
909
|
+
<DescriptionList meta="Added on … · Modified on …">
|
|
910
|
+
{fields.map((f) => <Description key={f.label} label={f.label} value={f.value} />)}
|
|
911
|
+
</DescriptionList>
|
|
912
|
+
<SectionColumns>
|
|
913
|
+
<section aria-labelledby="visits">
|
|
914
|
+
<h2 id="visits">Visits</h2>
|
|
915
|
+
<Filter variant="bar" bandDensity="compact" model={model} query={query} onQueryChange={setQuery} />
|
|
916
|
+
<ul className="m-0 grid list-none gap-1 p-0">
|
|
917
|
+
{rows.map((r) => <ListTile key={r.id} as="li" size="sm" title={r.title} chips={<Chip size="sm" label={r.kind} />} href={r.href} />)}
|
|
918
|
+
</ul>
|
|
919
|
+
</section>
|
|
920
|
+
<section aria-labelledby="notes">…</section>
|
|
921
|
+
</SectionColumns>
|
|
922
|
+
</ActivityContent>
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
- **One `h1`**: the `EntityHeader`'s title, or the shell's `handle.title` (then
|
|
926
|
+
leave `title` out). Each section keeps its own `h2`; `SectionColumns` is a
|
|
927
|
+
plain `div`, not a landmark.
|
|
928
|
+
- **Viewport against container**: `SectionColumns` and `asideFrom` follow the
|
|
929
|
+
window, while `DescriptionList`, `ListTile` and `ResponsiveGrid` follow their
|
|
930
|
+
own box, so the same list works in a column, a dialog or a side panel.
|
|
931
|
+
- **Check it** at 360, 768, 1440 and 1920 px, light and dark: no sideways
|
|
932
|
+
overflow, and no column of empty space beside a short list.
|
|
933
|
+
|
|
891
934
|
## Breadcrumbs
|
|
892
935
|
|
|
893
936
|
`AppBreadcrumb` (rendered by `ActivityLayout`) starts with a home icon linking
|
|
Binary file
|
|
Binary file
|
package/docs/roadmap.md
CHANGED
|
@@ -6,6 +6,20 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
6
6
|
|
|
7
7
|
## Now
|
|
8
8
|
|
|
9
|
+
- **Dense pages on large screens** — a record's page shows more on a 1440 or
|
|
10
|
+
1920 px screen instead of stretching the same fields. `DescriptionList` lays
|
|
11
|
+
a record's fields in `auto-fill` columns (`minItem`, `maxColumns`, a `meta`
|
|
12
|
+
line, `span`, `lines`, and `variant="panels"` for titled groups);
|
|
13
|
+
`SectionColumns` puts short sections side by side from a breakpoint (1536 px
|
|
14
|
+
by default) and stacks them below it; `ResponsiveGrid` takes `minItem` and
|
|
15
|
+
`gap` for boards of tiles; `ListTile` becomes a compact record row with
|
|
16
|
+
`chips`, `variant="divided"` and `as="li"`; `Filter` takes `bandDensity` like
|
|
17
|
+
`ServerTable`; `StatCardGroup` no longer collapses in a flex row and takes
|
|
18
|
+
`align`. The `Dense detail page` recipe puts them together for any subject,
|
|
19
|
+
tested at 360, 768, 1440 and 1920 px. React only for now; the Vue gap is
|
|
20
|
+
tracked in issue #1614.
|
|
21
|
+
([App shell guide](guides/app-shell.md#dense-pages-on-large-screens))
|
|
22
|
+
|
|
9
23
|
- **Vector drawings in code** (in the next prerelease) — `@nxgt/material/draw`
|
|
10
24
|
draws a scene of plain data, or JSX primitives (`Rect`, `Path`, `Text`...), as
|
|
11
25
|
SVG that follows the theme: colours are Material 3 roles and tones, with no
|