@nxgt/material 3.104.0 → 3.106.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.
@@ -97,6 +97,7 @@ table enables, for typing your own column defs.
97
97
  | `data` | `T[]` | — | rows; sorting, filtering and pagination run client-side |
98
98
  | `layout` | `'table' \| 'cards' \| 'tiles' \| { phone, wide }` | `'table'` | `'cards'` lays the rows out as a list of cards, `'tiles'` as one column in a single surface ([Cards and tiles](../guides/cards-and-tiles.md)); both ignore `virtual`, `renderExpandedRow` and `showViewOptions` |
99
99
  | `cardDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | `compact` fits cards of at least `--nxgt-cards-min` (15rem), up to four a row, with a tighter padding; `layout="cards"` only ([Cards and tiles](../guides/cards-and-tiles.md#columns)) |
100
+ | `surface` | `'card' \| 'none'` | `'card'` | `'none'` drops the surface of a `cards` or `tiles` list so it sits on a card that already has one (no card in a card): the table's fill and padding go, tiles lose the list's border and corners (rows stay split by hairlines), cards lose their border and fill (one column stays split by hairlines) ([Cards and tiles](../guides/cards-and-tiles.md#inside-a-card)); the `table` layout ignores it |
100
101
  | `renderCard` | `(row, { Title, Actions, Row, selected, locked, reason }) => ReactNode` | — | a card's content, required with `layout="cards"`; `Title` is the card's one link and its name; `Actions` is the trailing action slot (outside the link, at least 44px); `Row` (the compact record row) is also passed, laid out inside the card's own padding, with no height of its own |
101
102
  | `renderTile` | `(row, { Title, Actions, Row, selected, locked, reason }) => ReactNode` | — | a tile's content, required with `layout="tiles"`; same `Title` as a card, or `Row`, the compact record row (title, subtitle, chips, trailing facts; see the cards and tiles guide) |
102
103
  | `renderExpandedRow` | `(row) => ReactNode` | — | makes rows expandable; pair with the `expand` column |
@@ -82,7 +82,7 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
82
82
 
83
83
  | Prop | Type | Default | Effect |
84
84
  | --- | --- | --- | --- |
85
- | `items` | `MenuItem[]` | — | `label` (already translated), `url`, `icon`, `footer`, `display` (`'icon'`: a round icon-only button, the label as tooltip and name, in the rail and the expanded sidebar; the phone's menu sheet always shows icon and label; see the [app shell guide](../guides/app-shell.md)), `badge` (`true` a dot, a number a count up to "99+"), `badgeLabel` (see [Badges](../guides/app-shell.md#badges)), and `children` (read by `navigation="top"`, which opens them in a dropdown under a parent that is then only a trigger; by `navigation="two-level"`, which lists them in the panel beside the rail, one level deep; by the phone's menu sheet and the expanded sidebar, which nest them under the parent's own link, unless the entry sets `childrenIn: 'sidebar'`, which keeps them to the sidebar and the top dropdown), `childrenIn`, `defaultExpanded` (the expanded sidebar folds the entry's children from a chevron button, open at first when `true`, folded when `false`; unset, they are always listed), `group`, `bar`, `priority` for the navigation presets that read them (the drawer reads `group`, the bar `bar` and `priority`; the floating toolbar, not drawn yet, is the only preset still to read them) |
85
+ | `items` | `MenuItem[]` | — | `label` (already translated), `url`, `icon`, `footer`, `display` (`'icon'`: a round icon-only button, the label as tooltip and name, in the rail and the expanded sidebar; the phone's menu sheet always shows icon and label; see the [app shell guide](../guides/app-shell.md)), `badge` (`true` a dot, a number a count up to "99+"), `badgeLabel` (see [Badges](../guides/app-shell.md#badges)), and `children` (read by `navigation="top"`, which opens them in a dropdown under a parent that is then only a trigger; by `navigation="two-level"`, which lists them in the panel beside the rail, one level deep; by the phone's menu sheet and the expanded sidebar, which nest them under the parent's own link, unless the entry sets `childrenIn: 'sidebar'`, which keeps them to the sidebar and the top dropdown), `childrenIn`, `defaultExpanded` (the expanded sidebar folds the entry's children from a chevron button, open at first when `true`, folded when `false`; unset, they are always listed), `persistKey` (remembers a collapsible group's fold state in this browser: a string key, or `true` to derive it from `navigation.persistKey` and the `url`), `badgeWhen` (`'collapsed'`: a collapsible group's own `badge` shows only while it is folded), `group`, `bar`, `priority` for the navigation presets that read them (the drawer reads `group`, the bar `bar` and `priority`; the floating toolbar, not drawn yet, is the only preset still to read them) |
86
86
  | `linkComponent` | `AppShellLinkComponent` | React Router `Link` | The link each menu entry renders, given `href`; see the [app shell guide](../guides/app-shell.md). Defaults to the [`LinkComponentProvider`](./primitives.md#linkcomponentprovider)'s link when one is set. |
87
87
  | `logo`, `name` | `AppLogo` (`ReactNode \| string \| AppLogoImage`, `AppLogoImage` = `{ src, alt? }`), `string` | — | the app's logo alone in the collapsed rail, named by `name` (also its tooltip); logo and name side by side in the phone's menu sheet |
88
88
  | `context` | `ReactNode` | — | a line at the top of the phone's menu sheet, above the brand: the current tenant's name; `name` stays the app's brand; `AppHeader` and `AppSidebar` take it too (see the [app shell guide](../guides/app-shell.md#the-brand-logo-and-name)) |
@@ -129,6 +129,8 @@ type MenuItem = {
129
129
  `badge` marks an entry that has something new (the Material 3 navigation
130
130
  badge):
131
131
 
132
+ (A collapsible sidebar group can show its own `badge` only while folded: see `badgeWhen` with `defaultExpanded`, below.)
133
+
132
134
  ```tsx
133
135
  const items: MenuItem[] = [
134
136
  { label: 'Home', url: '/', icon: 'house', badge: true }, // a dot
@@ -418,8 +420,26 @@ its `url`, with its badge, for a parent page that already lists them. Unset
418
420
  chevron button after the entry (named "Collapse the group" / "Expand the group",
419
421
  `material.navigation.group-collapse` and `group-expand`) shows and hides them,
420
422
  `true` opening them at first and `false` folding them (a folded group still opens at first when the current page is one of its children). It is ignored with `display: 'icon'`. Unset, they are always
421
- listed, as before. The entry stays a link to its `url`; the state is local (not
422
- remembered), and the phone's menu sheet and the other presets ignore it.
423
+ listed, as before. The entry stays a link to its `url`, and the phone's menu sheet and the other presets ignore it.
424
+
425
+ By default the fold state is local and resets on reload. `persistKey` remembers it
426
+ in this browser: a string is the storage key, `true` derives the key from
427
+ `navigation.persistKey` and the entry's `url` (nothing is saved without that key).
428
+ Like the sidebar's own collapse, the saved choice is read after hydration, so a
429
+ server render and the first client render both follow `defaultExpanded`; a saved
430
+ choice wins over the "current page is a child" opening. `badgeWhen: 'collapsed'`
431
+ draws the parent's `badge` only while the group is folded, so its children's own
432
+ badges are not signalled twice (default `'always'`).
433
+
434
+ ```tsx
435
+ {
436
+ label: 'Groups', url: '/groups', icon: 'users', badge: true,
437
+ children: [{ label: 'Item A', url: '/a', badge: 2 }],
438
+ defaultExpanded: true,
439
+ persistKey: true, // under navigation.persistKey + '/groups'
440
+ badgeWhen: 'collapsed', // the dot shows only while folded
441
+ }
442
+ ```
423
443
 
424
444
  ```tsx
425
445
  {
@@ -302,6 +302,26 @@ on the list or an ancestor to tune it. The default `comfortable` keeps the one,
302
302
  two, three columns above, whatever the token; tiles ignore `cardDensity`. It is
303
303
  independent of `density`, which also tightens a card's padding and gap.
304
304
 
305
+ ## Inside a card
306
+
307
+ A list draws its own surface: tiles are one bordered, filled box, cards each have
308
+ a border and a fill. Inside a page card or a `PanelCard` that is a card in a card.
309
+ `surface="none"` leaves it out:
310
+
311
+ ```tsx
312
+ <PanelCard title="Team">
313
+ <DataTable layout="tiles" surface="none" renderTile={…} … />
314
+ </PanelCard>
315
+ ```
316
+
317
+ The table's own fill and padding go, tiles lose the list's border and rounded
318
+ corners (the rows stay split by hairlines), and cards lose their border and fill (in
319
+ one column they are split by hairlines). Hover and selected tints, the padding and
320
+ the focus ring are unchanged, so the rows sit directly on the parent card. The
321
+ default (`'card'`) is unchanged, and the `table` layout ignores it. `ServerTable`
322
+ has its own `surface` (for its root, with the tiles keeping their border) and does
323
+ not pass this one on.
324
+
305
325
  ## States
306
326
 
307
327
  - **First load** (`loading` with no row): placeholder cards (or tiles), hidden from
@@ -13,6 +13,15 @@
13
13
  border: 1px solid var(--item-border);
14
14
  border-radius: var(--item-radius);
15
15
  }
16
+ /* `surface="none"`: no surface of its own, for a list inside a card. The
17
+ * rows keep their hairlines; the tints still mix over the parent. */
18
+ [data-slot="data-table-tiles"][data-surface="none"] {
19
+ --item-surface: transparent;
20
+ margin: 0;
21
+ background: none;
22
+ border: 0;
23
+ border-radius: 0;
24
+ }
16
25
  /* A ServerTable on a phone has no panel of its own: the tile list sits at
17
26
  * the page gutter, in line with the heading (#2292). */
18
27
  @media (max-width: 767.98px) {
@@ -52,6 +61,11 @@
52
61
  border-end-start-radius: calc(var(--item-radius) - 1px);
53
62
  border-end-end-radius: calc(var(--item-radius) - 1px);
54
63
  }
64
+ /* `surface="none"`: no rounded first and last row either. */
65
+ [data-slot="data-table-tiles"][data-surface="none"]
66
+ > [data-slot="data-table-item"] {
67
+ border-radius: 0;
68
+ }
55
69
  [data-slot="data-table-tiles"][data-selectable]
56
70
  > [data-slot="data-table-item"] {
57
71
  padding-inline-start: 3.25rem;
@@ -73,6 +73,12 @@
73
73
  grid-template-columns: repeat(3, minmax(0, 1fr));
74
74
  }
75
75
  }
76
+ /* `surface="none"`: the list sits on its parent, with no padding of the
77
+ * table's own to take the ring room's 4px back from. */
78
+ [data-slot="table-wrapper"]:has(> [data-surface="none"]) {
79
+ margin-inline: 0;
80
+ width: 100% !important;
81
+ }
76
82
  [data-slot="data-table-cards"][data-density="compact"] {
77
83
  gap: var(--nxgt-cards-gap, 0.5rem);
78
84
  }
@@ -120,6 +126,22 @@
120
126
  > [data-slot="data-table-item"] {
121
127
  padding: 0.75rem;
122
128
  }
129
+ /* `surface="none"`: a card draws no border or fill of its own, for a list
130
+ * inside a card. In one column the cards are split by hairlines. */
131
+ [data-slot="data-table-cards"][data-surface="none"] {
132
+ --item-surface: transparent;
133
+ }
134
+ [data-slot="data-table-cards"][data-surface="none"]
135
+ > [data-slot="data-table-item"] {
136
+ border-color: transparent;
137
+ }
138
+ @container (max-width: 35.999rem) {
139
+ [data-slot="data-table-cards"][data-surface="none"]
140
+ > [data-slot="data-table-item"]
141
+ + [data-slot="data-table-item"] {
142
+ border-top-color: var(--item-border);
143
+ }
144
+ }
123
145
  [data-slot="data-table-item"] a[data-slot="data-table-item-link"],
124
146
  [data-slot="data-table-item"] button[data-slot="data-table-item-link"] {
125
147
  color: inherit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.104.0",
3
+ "version": "3.106.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"