@nxgt/material 3.47.0 → 3.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +3 -3
  3. package/dist/{button-yaltbQmC.js → button-DJgaQbBD.js} +49 -49
  4. package/dist/charts.js +144 -144
  5. package/dist/{chip-BqWbbr1b.js → chip-DN44R1vW.js} +56 -56
  6. package/dist/{code-editor-C9qBNoYb.js → code-editor-BJ9ewxSC.js} +307 -307
  7. package/dist/code-editor.js +46 -46
  8. package/dist/{code-language-items-DAHtfQ7W.js → code-language-items-BJE3VuQg.js} +1 -1
  9. package/dist/components/components.css +1 -0
  10. package/dist/components/ui/actions/action-card/action-card-indicator.css +48 -0
  11. package/dist/components/ui/actions/action-card/action-card-modes.css +4 -1
  12. package/dist/components/ui/actions/action-card/action-card-tile.css +141 -0
  13. package/dist/components/ui/actions/action-card/action-card.css +33 -3
  14. package/dist/{components-DFPy96C3.js → components-AtBccCir.js} +15714 -15755
  15. package/dist/components.js +23 -23
  16. package/dist/{dialog-CCCcnTIx.js → dialog-ODql4vH7.js} +15 -15
  17. package/dist/{tooltip-CkttDIab.js → dist-B61_o9Mg.js} +69 -66
  18. package/dist/dnd.js +1 -1
  19. package/dist/{dropdown-menu-Df8785nt.js → dropdown-menu-DRXYP9ta.js} +274 -274
  20. package/dist/editor.js +1027 -1027
  21. package/dist/{empty-state-DcF4TBzi.js → empty-state-C9QqvBU1.js} +36 -36
  22. package/dist/{escape-owner-C4W_sOAU.js → escape-owner-DCrlERLM.js} +1 -1
  23. package/dist/{field-blur-CJHzVSh9.js → field-blur-DYGyIdfD.js} +1 -1
  24. package/dist/{field-blur-context-Cft9s7Hg.js → field-blur-context-B-fSpl1H.js} +1 -1
  25. package/dist/hooks.js +2 -2
  26. package/dist/{hover-card-C0fw2kLK.js → hover-card-EO_CckIx.js} +19 -19
  27. package/dist/{icon-badge-aJ22mKGh.js → icon-badge-BQYj_LE6.js} +3 -3
  28. package/dist/{icon-button-CN9r5TNE.js → icon-button-B481L2TP.js} +20 -20
  29. package/dist/lib/components/ui/actions/action-card/action-card-semantics.d.ts +25 -2
  30. package/dist/lib/components/ui/actions/action-card/action-card.d.ts +18 -2
  31. package/dist/lib/components/ui/actions/action-card/partials/action-card-icon.d.ts +6 -0
  32. package/dist/lib/components/ui/actions/action-card/partials/action-card-meta.d.ts +15 -0
  33. package/dist/lib/components/ui/actions/action-card/partials/action-card-status.d.ts +6 -0
  34. package/dist/lib/components/ui/actions/action-card/partials/action-card-text.d.ts +20 -0
  35. package/dist/lib/components/ui/actions/action-card/partials/action-card-title-button.d.ts +14 -0
  36. package/dist/lib/components/ui/actions/action-card/partials/action-card-trail.d.ts +6 -0
  37. package/dist/lib/components/ui/actions/action-card/use-action-card-names.d.ts +22 -0
  38. package/dist/lib/components/ui/actions/action-card/use-reveal.d.ts +23 -0
  39. package/dist/lib/components/ui/actions/button/button.variants.d.ts +1 -1
  40. package/dist/lib/components/ui/actions/icon-button/icon-button.variants.d.ts +1 -1
  41. package/dist/lib/components/ui/collections/filter/query/filter-query-context.d.ts +1 -1
  42. package/dist/lib/components/ui/collections/filter/query/filter-query-model.d.ts +49 -0
  43. package/dist/lib/components/ui/collections/filter/query/types.d.ts +8 -0
  44. package/dist/lib/components/ui/collections/filter/query/use-filter-query-options.d.ts +2 -1
  45. package/dist/lib/components/ui/collections/filter/query/use-filter-query-value.d.ts +1 -0
  46. package/dist/lib/components/ui/collections/filter/query/use-filter-query.d.ts +3 -47
  47. package/dist/lib/components/ui/collections/filter/sections/filter-form-error.d.ts +16 -4
  48. package/dist/lib/components/ui/collections/row-menu/row-menu.d.ts +1 -1
  49. package/dist/lib/components/ui/collections/server-table/types.d.ts +5 -0
  50. package/dist/lib/components/ui/dates/duration-field/partials/use-duration-box.d.ts +1 -1
  51. package/dist/lib/components/ui/display/badge/badge.d.ts +1 -1
  52. package/dist/lib/components/ui/display/day-separator/day-separator.d.ts +1 -1
  53. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  54. package/dist/lib/components/ui/fields/opening-hours/logic/status.d.ts +13 -2
  55. package/dist/lib/components/ui/fields/opening-hours/opening-hours-summary.d.ts +7 -3
  56. package/dist/lib/components/ui/fields/slider/split-slider-props.d.ts +20 -20
  57. package/dist/lib/components/ui/fields/slider/use-slider-control.d.ts +20 -20
  58. package/dist/lib/components/ui/layout/app-shell/route-transition.d.ts +1 -1
  59. package/dist/lib/components/ui/media/carousel/use-carousel-engine.d.ts +1 -1
  60. package/dist/lib/components/ui/media/media-player/engine/use-queue-actions.d.ts +1 -1
  61. package/dist/lib/components/ui/media/pdf-reader/pdf-reader.slice.d.ts +1 -1
  62. package/dist/lib/components/ui/selection/choice-group/use-choice-group-field.d.ts +34 -34
  63. package/dist/lib/components/ui/widgets/chat/chat-message.variants.d.ts +2 -2
  64. package/dist/lib/components/ui/widgets/event-calendar/use-event-calendar.d.ts +1 -1
  65. package/dist/lib/i18n/translator.d.ts +1 -1
  66. package/dist/main.js +30 -30
  67. package/dist/map.js +78 -78
  68. package/dist/material.css +1 -1
  69. package/dist/nxgt-theme-ivgNca3z.js +37 -0
  70. package/dist/pdf-reader.js +1 -1
  71. package/dist/{public-zVb_Nq_D.js → public-CZBz4JVu.js} +70 -70
  72. package/dist/{rich-code-block-DMWZZJma.js → rich-code-block-597e0_Qm.js} +51 -51
  73. package/dist/{sortable-provider-DstbgMVf.js → sortable-provider-CK_xAk2H.js} +217 -217
  74. package/dist/{spinner-C4CgDEYm.js → spinner-BhuZuHK1.js} +1 -1
  75. package/dist/theme-picker.js +45 -45
  76. package/dist/{theme-store-CSl51uNH.js → theme-store-BdPp43cV.js} +13 -13
  77. package/dist/{theme-toggle-CGW2_K54.js → theme-toggle-Dnjn83TJ.js} +15 -15
  78. package/dist/themes.js +17 -17
  79. package/dist/{toggle-group-BMJNcFAC.js → toggle-group-BkH23Jk4.js} +11 -11
  80. package/dist/{toolbar-items-CsLGJMEJ.js → toolbar-items-CCwIu7fE.js} +426 -426
  81. package/dist/{use-chat-composer-Brcb5f7r.js → use-chat-composer-Ot5XUbdn.js} +5138 -4908
  82. package/dist/{use-theme-DMcyJ52N.js → use-theme-BCLireTd.js} +1 -1
  83. package/docs/components/buttons-and-actions.md +62 -4
  84. package/docs/components/data-table-filter-and-lists.md +29 -2
  85. package/docs/components/form-fields.md +1 -1
  86. package/docs/guides/cards-and-tiles.md +60 -0
  87. package/docs/previews/data-table-filter-lists-filter.webp +0 -0
  88. package/docs/roadmap.md +21 -0
  89. package/docs/troubleshooting.md +24 -0
  90. package/lib/components/components.css +1 -0
  91. package/lib/components/ui/actions/action-card/action-card-indicator.css +48 -0
  92. package/lib/components/ui/actions/action-card/action-card-modes.css +4 -1
  93. package/lib/components/ui/actions/action-card/action-card-tile.css +141 -0
  94. package/lib/components/ui/actions/action-card/action-card.css +33 -3
  95. package/package.json +1 -1
  96. package/dist/lib/components/ui/fields/opening-hours/logic/zoned.d.ts +0 -10
  97. package/dist/nxgt-theme-ZS945iMP.js +0 -37
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./use-ssr-media-query-C6M5m8sf.js";
2
- import { i as t, n, o as r, r as i, s as a, t as o } from "./theme-store-CSl51uNH.js";
2
+ import { i as t, n, o as r, r as i, s as a, t as o } from "./theme-store-BdPp43cV.js";
3
3
  import { a as s } from "./theme-context-Cofk6vgs.js";
4
4
  import { useCallback as c, useEffect as l, useState as u, useSyncExternalStore as d } from "react";
5
5
  import { PRESET_SCHEMES as f } from "@nxgt/material-core/theme/boot";
@@ -20,7 +20,7 @@ and language switches. Everything is imported from `@nxgt/material/components`.
20
20
  | <img src="../previews/buttons-actions-loadmorebutton.webp" width="96" alt=""> | [LoadMoreButton](#loadmorebutton) | "Load more" for cursor pagination |
21
21
  | <img src="../previews/buttons-actions-scrolltotop.webp" width="96" alt=""> | [ScrollToTop](#scrolltotop) | Appears past a scroll threshold |
22
22
  | <img src="../previews/buttons-actions-scrolltolatestbutton.webp" width="96" alt=""> | [ScrollToLatestButton](#scrolltolatestbutton) | "Jump to latest" over a scroll box, counting what arrived |
23
- | <img src="../previews/buttons-actions-actioncard.webp" width="96" alt=""> | [ActionCard](#actioncard) | Clickable card, a button (`aria-pressed`) when it chooses or reacts, with an active indicator |
23
+ | <img src="../previews/buttons-actions-actioncard.webp" width="96" alt=""> | [ActionCard](#actioncard) | Clickable card, a button (`aria-pressed`) when it chooses or reacts, with an active indicator; `status`, `meta`, a trailing `action` and a `tile` variant |
24
24
  | <img src="../previews/buttons-actions-themetoggle.webp" width="96" alt=""> | [ThemeToggle](#themetoggle) | Light / dark / system switch |
25
25
  | <img src="../previews/buttons-actions-languageswitcher.webp" width="96" alt=""> | [LanguageSwitcher](#languageswitcher) | Menu of the `I18nProvider` languages |
26
26
 
@@ -1063,9 +1063,12 @@ import { Banknote, CreditCard } from 'lucide-react';
1063
1063
  | `active` | `boolean` | — | the chosen look: a primary container with its edge, a primary title, the icon's circle morphs to a filled cookie and the indicator fills; also `aria-pressed` |
1064
1064
  | `onClick` | `(event) => void` | — | makes the card a button |
1065
1065
  | `disabled` | `boolean` | `false` | `aria-disabled`, out of the Tab order, no click and no key, muted look |
1066
- | `variant` | `'sm' \| 'md'` | `'sm'` | one row, or icon and indicator above the text |
1067
- | `withIndicator` | `boolean` | `true` | the radio-like ring that fills with a check |
1066
+ | `variant` | `'sm' \| 'md' \| 'tile'` | `'sm'` | one row, icon and indicator above the text, or a compact [tile](#actioncard-tile) |
1067
+ | `withIndicator` | `boolean` | `true` (`false` for `tile`) | the radio-like ring that fills with a check |
1068
1068
  | `title`, `description`, `icon` | `ReactNode` | — | the content |
1069
+ | `status` | `ReactNode` | — | a line under the title; a string or number becomes a soft `Badge`, a node is shown as given |
1070
+ | `meta` | `ReactNode` | — | a secondary line, one line with an ellipsis |
1071
+ | `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 |
1069
1072
 
1070
1073
  A card with `active`, `onClick`, `disabled` or your own `role` is `role="button"` (unless you set another): it takes the Tab stop, and
1071
1074
  Enter and Space press it. `active` sets `aria-pressed`; pass your own `role`
@@ -1079,9 +1082,64 @@ the card, forced colours in system colours, and no motion under reduced motion.
1079
1082
  For a card choice bound to a form value, see `ChoiceCardGroup` on
1080
1083
  [Selection & choice](selection-and-choice.md).
1081
1084
 
1085
+ ### ActionCard tile
1086
+
1087
+ `variant="tile"` is a compact grid cell for boards: no minimum width (it fills
1088
+ its grid track), the title and the `action` on the first row, then `status`,
1089
+ `description` and `meta`, and no indicator unless `withIndicator`. `status` and
1090
+ `meta` work on every variant.
1091
+
1092
+ ```tsx
1093
+ import {
1094
+ ActionCard,
1095
+ Badge,
1096
+ DropdownMenuRadioGroup,
1097
+ DropdownMenuRadioItem,
1098
+ RowMenu,
1099
+ } from '@nxgt/material/components';
1100
+
1101
+ <ActionCard
1102
+ variant="tile"
1103
+ active={selected === unit.id}
1104
+ onClick={() => setSelected(unit.id)}
1105
+ title={unit.name}
1106
+ status={<Badge variant="soft" tone="success">Free</Badge>}
1107
+ meta="Zone B, second floor"
1108
+ action={
1109
+ <RowMenu
1110
+ label={unit.name}
1111
+ triggerProps={{ 'aria-label': `Change status of ${unit.name}` }}
1112
+ >
1113
+ <DropdownMenuRadioGroup value={unit.status} onValueChange={setStatus}>
1114
+ <DropdownMenuRadioItem value="free">Free</DropdownMenuRadioItem>
1115
+ <DropdownMenuRadioItem value="occupied">Occupied</DropdownMenuRadioItem>
1116
+ </DropdownMenuRadioGroup>
1117
+ </RowMenu>
1118
+ }
1119
+ />;
1120
+ ```
1121
+
1122
+ With an `action`, a card that chooses or reacts is a plain `div` and its
1123
+ **title is the one button**, stretched over the card, so the whole card presses
1124
+ it and the action never nests inside it (no `nested-interactive`). The button
1125
+ is named after the title, the status and the meta together ("Unit 21 Free Zone
1126
+ B"); the action keeps its own name. The card is one Tab stop and the action a
1127
+ second; Enter and Space press the button. `disabled` disables the button
1128
+ natively; disable the action's control too. **`title` is required with
1129
+ `action`**: it is the button's text (without one the button is still there, named
1130
+ after the status and the meta line, and development builds warn). The
1131
+ `description` is the button's description (`aria-describedby`). A `role`
1132
+ (`switch`, `checkbox`, `radio`) and its `aria-checked` move to the button, where
1133
+ they are valid; the root stays a plain `div` and takes no `aria-checked` or
1134
+ `aria-label`. Hover and press answer only the title button (its stretched area covers the
1135
+ card, padding included), not the action. The meta line shows its full text in a tooltip, on hover or
1136
+ keyboard focus of the title button, only while it is clipped (Escape dismisses
1137
+ it; moving the pointer across the line does not). Without `action` the DOM is as
1138
+ before. Nothing is added for a Vue port yet (React first).
1139
+
1082
1140
  The root is `data-slot="action-card"` (it was the `Card` primitive's `data-slot="card"`): style it with `className`, not through `Card` classes or a `[data-slot=card]` selector.
1083
1141
 
1084
- **Storybook:** `Buttons & actions/ActionCard`, `Buttons & actions/ActionCard/Look` · **Vue:** `ActionCard`, with the earlier look and no button role yet (#1195)
1142
+ A whole board built on it, with sections and an occupancy table, is the `Guides/Recipes/Status board` recipe. **Storybook:** `Buttons & actions/ActionCard`, `Buttons & actions/ActionCard/Look`, `Buttons & actions/ActionCard/Tile`, `Buttons & actions/ActionCard/Checks` · **Vue:** `ActionCard`, with the earlier look and no button role yet (#1195)
1085
1143
 
1086
1144
  ---
1087
1145
 
@@ -1169,7 +1169,7 @@ and `ref`, so it is the child of a `DropdownMenuTrigger asChild`.
1169
1169
  | --- | --- | --- | --- |
1170
1170
  | `label` (`RowMenu`, `RowMenuTrigger`) | `string` | — | the row's name; the button's name is "Actions for {label}" (translated). An `aria-label` of your own wins |
1171
1171
  | `items` (`RowMenu`) | `{ label, icon?, onSelect?, disabled?, destructive?, id? }[]` | `[]` | the actions; `destructive` ones are drawn in the error colour and moved last, after a separator |
1172
- | `children` (`RowMenu`) | `ReactNode` | — | menu content of your own, above `items` (put a destructive item last yourself) |
1172
+ | `children` (`RowMenu`) | `ReactNode` | — | menu content of your own, above `items` (put a destructive item last yourself); its menu items are 44px high on a coarse pointer too |
1173
1173
  | `triggerProps` (`RowMenu`) | button props | — | `disabled`, `className`, `data-*` on the button |
1174
1174
  | `open`, `onOpenChange` (`RowMenu`) | as `DropdownMenu` | — | controlled state |
1175
1175
  | `label`, `items`, `menu` (`rowMenuColumn`) | `(row) => string` / `(row) => RowMenuItem[]` / `(row) => ReactNode` | — | the row's name, its actions as data, or its menu content as nodes |
@@ -1890,6 +1890,32 @@ one. The page clears it when the condition changes, by passing no error. The
1890
1890
  words are `material.filter.error.*`; `fieldErrors(errors, model, locked)` in
1891
1891
  the core lists what the bar shows.
1892
1892
 
1893
+ #### The refused fields' words under the bar
1894
+
1895
+ The pill only reads "Invalid", so when a refused query leaves the list empty
1896
+ the page would show a red pill and nothing that says what to fix (WCAG 3.3.1,
1897
+ 3.3.3). By default the bar also says the words, in the place `formError` uses:
1898
+ one error `Alert` under the bar with a line per refused field that names it,
1899
+ "Period: choose 366 days or fewer." `formError` comes first when both are
1900
+ set. It is one Alert, not one per field, and it is no live region: the bar's
1901
+ one tells the refusal once, as before. The lines are plain text.
1902
+
1903
+ ```tsx
1904
+ <FilterBar model={model} params={params} onParamsChange={setParams}
1905
+ errors={{ period: 'Choose 366 days or fewer.' }} /> // pill + the line under the bar
1906
+ <FilterBar ... errors={...} errorSummary={false} /> // the pills only, as before this release
1907
+ ```
1908
+
1909
+ In the facets panel each refused facet already shows its message in its
1910
+ section, so there the Alert holds `formError` and the lines of the refused
1911
+ fields that have no section (a plain chip would read "Invalid" alone), never
1912
+ a field line twice; the bar and the facets on a phone (whose sections sit in
1913
+ the sheet) keep the full summary.
1914
+
1915
+ `errorSummary` (default `true`) is on `Filter`, `FilterBar` and
1916
+ `ServerTable`'s `filter`: `<ServerTable filter={{ model, errors, errorSummary: false }} />`
1917
+ keeps the pills only.
1918
+
1893
1919
  #### A refused search, and a refusal of no field
1894
1920
 
1895
1921
  `errors` is keyed by field id, so it cannot name the search box nor carry a
@@ -1913,7 +1939,8 @@ refusal that belongs to no field. Two more props, on the same three surfaces
1913
1939
  - **`formError`** (a node) is a refusal that belongs to no field: one error
1914
1940
  `Alert` under the bar, in place (under the bar's row in `FilterBar`, between
1915
1941
  the band and the chips in `ServerTable`, under the search in the facets
1916
- panel). It is no live region of its own.
1942
+ panel). It is no live region of its own. The refused fields' lines join it
1943
+ (`errorSummary`, above).
1917
1944
 
1918
1945
  Both are told by the bar's one polite live region, the same refusal path as a
1919
1946
  field's, once and after the count: "Search: Not a valid item." and the
@@ -1830,7 +1830,7 @@ summary shows a quiet "Hours not set" (`empty` replaces the text,
1830
1830
  | --- | --- | --- | --- |
1831
1831
  | `data` | `OpeningHour[] \| null` | `[]` | the hours |
1832
1832
  | `now` | `Date` | the clock | the moment read as now |
1833
- | `timeZone` | `string` | the viewer's | the IANA zone of the place, resolved with `Intl.DateTimeFormat` |
1833
+ | `timeZone` | `string` | the viewer's | the IANA zone of the place, read with the shared `@nxgt/material-core/zone` helpers. A name `Intl` does not know is read as `UTC`, like `DateField`'s `timeZone`, never as the viewer's clock (so the status agrees with the date fields for the same string) |
1834
1834
  | `locale` | `string` | the `I18nProvider` language, else `en` | day and time names |
1835
1835
  | `weekStartsOn` | `0`-`6` | the locale's | first day of the week |
1836
1836
  | `label` | `string` | "Opening Hours" | names the region |
@@ -240,6 +240,66 @@ navigation to learn.
240
240
 
241
241
  A `linkComponent` must forward `onClick` (with `className`): it is how `onRowClick` runs before the card's link navigates. A click that opens the link elsewhere (Ctrl, Cmd, Shift, Alt, the middle button) only opens it: `onRowClick` does not run. A title that is a button (`onRowClick` and no href) runs it on every click.
242
242
 
243
+ ## Status tiles and boards
244
+
245
+ A board of resources (beds, rooms, vehicles) is not a list of records: it is a
246
+ grid of compact cells you read at a glance and change in place. Use
247
+ [`ActionCard variant="tile"`](../components/buttons-and-actions.md#actioncard-tile)
248
+ in a `ResponsiveGrid minItem="xs"`, one grid per section under its own `h2`:
249
+
250
+ - **`status`** is a soft `Badge` with its word and an icon, so a status is
251
+ never told by colour alone.
252
+ - **`meta`** is one line, the occupant only while occupied; a long one is
253
+ clipped and shown in full in a tooltip.
254
+ - **`action`** is a `RowMenu` with a `DropdownMenuRadioGroup` of statuses,
255
+ named "Change status of {name}". The title is the one button (it opens the
256
+ detail), so the card and its menu are two tab stops and nothing nests.
257
+
258
+ ```tsx
259
+ import {
260
+ ActionCard,
261
+ Badge,
262
+ DropdownMenuRadioGroup,
263
+ DropdownMenuRadioItem,
264
+ ResponsiveGrid,
265
+ RowMenu,
266
+ } from '@nxgt/material/components';
267
+
268
+ <ResponsiveGrid minItem="xs">
269
+ <ActionCard
270
+ variant="tile"
271
+ title="Bed A1"
272
+ status={<Badge variant="soft" tone="warning" icon="user">Occupied</Badge>}
273
+ meta="Amara Diallo"
274
+ onClick={() => openDetail('a1')}
275
+ action={
276
+ <RowMenu
277
+ label="Bed A1"
278
+ triggerProps={{ 'aria-label': 'Change status of Bed A1' }}
279
+ >
280
+ <DropdownMenuRadioGroup value={status} onValueChange={setStatus}>
281
+ <DropdownMenuRadioItem value="free">Free</DropdownMenuRadioItem>
282
+ <DropdownMenuRadioItem value="occupied">Occupied</DropdownMenuRadioItem>
283
+ <DropdownMenuRadioItem value="cleaning">Cleaning</DropdownMenuRadioItem>
284
+ </DropdownMenuRadioGroup>
285
+ </RowMenu>
286
+ }
287
+ />
288
+ </ResponsiveGrid>;
289
+ ```
290
+
291
+ `title` is required with `action`: it is the card's button, named after the
292
+ title, the status and the meta line.
293
+
294
+ For the figures of the whole board, put a `StatCardGroup` above a `DataTable`
295
+ with one row per section, one numeric column per status (they add up to the
296
+ total) (`meta: { numeric: true }`) and a
297
+ rate cell (a `Progress` named "{section} occupancy" beside the visible
298
+ percent); `layout={{ phone: 'tiles', wide: 'table' }}` turns the rows into
299
+ tiles on a phone. Both screens, tested at 360 and 1440 px, light and dark, are
300
+ the `Guides/Recipes/Status board` story, and the Claude Code plugin's
301
+ `screen-recipes` skill ships their code.
302
+
243
303
  ## Troubleshooting
244
304
 
245
305
  **The sort control is missing, or an option reads like the column id.** The control needs a column that can be sorted (an accessor, `enableSorting` not `false`); an option is the column's `meta.label`, else a string `header`. Set `meta.label`.
package/docs/roadmap.md CHANGED
@@ -330,6 +330,27 @@ version they shipped in, and planned entries may name the major they target.
330
330
 
331
331
  ## Shipped
332
332
 
333
+ - **A status board of tiles** (3.49.0) — `ActionCard` gains `status` (a soft badge line),
334
+ `meta` (one line, its tooltip only while clipped), a trailing `action` (a
335
+ `RowMenu`, an `IconButton`) outside the press surface, and `variant="tile"`
336
+ for grids of compact cells. With an `action` the title is the card's one
337
+ button, so the card and its menu are two tab stops and nothing nests. The
338
+ `Guides/Recipes/Status board` recipe (tiles in sections with a menu that
339
+ changes the status, and an occupancy table with a rate per section) shows
340
+ the whole pattern, with its code in the plugin's `screen-recipes` skill.
341
+ React only for now; the Vue gap is a `vue` issue.
342
+ ([ActionCard tile](components/buttons-and-actions.md#actioncard-tile))
343
+
344
+ - **A refused field says why** (3.48.0) — the filter bar shows the words of a
345
+ refused field under it, in one error `Alert`: "Period: choose 366 days or
346
+ fewer.", after `formError`, so a refused, empty list no longer shows a red
347
+ "Invalid" pill and no reason. `errorSummary` (on by default) is on `Filter`,
348
+ `FilterBar` and `ServerTable`'s `filter`; `false` keeps the pills only. The
349
+ facets panel keeps each message inline and adds a line only for a field with
350
+ no section. React only for now; the Vue twin is a `vue` issue.
351
+ [Filter errors](components/data-table-filter-and-lists.md#filter-bar) ·
352
+ [Troubleshooting](troubleshooting.md)
353
+
333
354
  - **Stepper and Timeline, expressive** — both have a new look by default, with
334
355
  the same props. `Stepper` shows the current step as a cookie, a completed one
335
356
  as a circle with a check and a failed one in the error container, joined by
@@ -138,6 +138,7 @@ words in your error.
138
138
  - [`errors` shows nothing on the filter bar](#errors-shows-nothing-on-the-filter-bar)
139
139
  - [`@nxgt/material-core/filter: unknown time zone "…", using the runtime's`](#nxgtmaterial-corefilter-unknown-time-zone--using-the-runtimes)
140
140
  - [`` [@nxgt/material] EventCalendar: unknown timeZone "Mars/Olympus", reading UTC instead. ``](#nxgtmaterial-eventcalendar-unknown-timezone-marsolympus-reading-utc-instead)
141
+ - [`` [@nxgt/material] OpeningHoursSummary: unknown timeZone "Mars/Olympus", reading UTC instead. ``](#nxgtmaterial-openinghourssummary-unknown-timezone-marsolympus-reading-utc-instead)
141
142
  - [``Filter: the autocomplete field(s) … have neither `options` nor a `search` in `fieldProps`: their editor has nothing to offer.``](#filter-the-autocomplete-fields--have-neither-options-nor-a-search-in-fieldprops-their-editor-has-nothing-to-offer)
142
143
  - [`Filter bar parts must be used inside <Filter variant="bar">`](#filter-bar-parts-must-be-used-inside-filter-variantbar)
143
144
  - [A date preset's count differs between the server and the page](#a-date-presets-count-differs-between-the-server-and-the-page)
@@ -2540,6 +2541,16 @@ the field out of `locked`. `fieldErrors(errors, model, locked)` from
2540
2541
  <ServerTable filter={{ model, locked, errors: { period: 'The period cannot exceed 366 days.' } }} {...table} />
2541
2542
  ```
2542
2543
 
2544
+ ### A field error reads "Invalid" on the pill and nothing else shows
2545
+
2546
+ **When:** with 3.47 or earlier or `errorSummary={false}`, a refused,
2547
+ empty list shows a red "Invalid" pill and no visible reason.
2548
+ **Why:** the message sits in the pill's description and its editor; the words
2549
+ under the bar came with `errorSummary`, on by default.
2550
+ **Fix:** upgrade to 3.48.0 or later, and leave `errorSummary` unset (or `true`): the bar shows
2551
+ "Period: choose 366 days or fewer." in the Alert under it, after `formError`.
2552
+ `errorSummary={false}` brings back the pills only.
2553
+
2543
2554
  ### A refused search or a refusal of no field shows nothing
2544
2555
 
2545
2556
  **When:** the server refuses the typed search, or refuses with a message that
@@ -2591,6 +2602,19 @@ hours are then off by the zone's offset.
2591
2602
  <EventCalendar view="day" timeZone="Europe/Paris" events={events} />
2592
2603
  ```
2593
2604
 
2605
+ ### `` [@nxgt/material] OpeningHoursSummary: unknown timeZone "Mars/Olympus", reading UTC instead. ``
2606
+
2607
+ **When:** in development, once per name, when `OpeningHoursSummary`'s `timeZone`
2608
+ is not an IANA name `Intl` knows (`'Paris'`, `'UTC+2'`, a typo).
2609
+ **Why:** the open-now status reads `UTC` instead, as `DateField` and
2610
+ `@nxgt/material-core/zone` do (it no longer falls back to the viewer's clock),
2611
+ so the status is off by the zone's offset.
2612
+ **Fix:** pass an IANA name (`'America/Toronto'`):
2613
+
2614
+ ```tsx
2615
+ <OpeningHoursSummary data={hours} timeZone="America/Toronto" />
2616
+ ```
2617
+
2594
2618
  ### ``Filter: the autocomplete field(s) … have neither `options` nor a `search` in `fieldProps`: their editor has nothing to offer.``
2595
2619
 
2596
2620
  **When:** in the console in development, when `Filter variant="bar"` or
@@ -191,6 +191,7 @@
191
191
  @import "./ui/feedback/toast/toast-tokens.css";
192
192
  @import "./ui/actions/action-card/action-card.css";
193
193
  @import "./ui/actions/action-card/action-card-indicator.css";
194
+ @import "./ui/actions/action-card/action-card-tile.css";
194
195
  @import "./ui/actions/action-card/action-card-modes.css";
195
196
  @import "./ui/media/galleries/gallery-image-lists.css";
196
197
  /* Gallery: the View Transition from a tile to its lightbox. */
@@ -37,4 +37,52 @@
37
37
  .nxgt-action-card[data-active] .nxgt-action-card-indicator svg {
38
38
  stroke-dashoffset: 0;
39
39
  }
40
+
41
+ /* The indicator takes its own column, after the action, only when shown. */
42
+ .nxgt-action-card[data-size="tile"]:has(> .nxgt-action-card-indicator) {
43
+ grid-template-columns: minmax(0, 1fr) auto;
44
+ grid-template-areas: "title ind" "status status" "desc desc" "meta meta";
45
+ }
46
+ .nxgt-action-card[data-size="tile"][data-icon]:has(
47
+ > .nxgt-action-card-indicator
48
+ ) {
49
+ grid-template-columns: auto minmax(0, 1fr) auto;
50
+ grid-template-areas:
51
+ "icon title ind"
52
+ ". status status"
53
+ ". desc desc"
54
+ ". meta meta";
55
+ }
56
+ .nxgt-action-card[data-size="tile"][data-has-action]:has(
57
+ > .nxgt-action-card-indicator
58
+ ) {
59
+ grid-template-columns: minmax(0, 1fr) auto auto;
60
+ grid-template-areas:
61
+ "title act ind"
62
+ "status status status"
63
+ "desc desc desc"
64
+ "meta meta meta";
65
+ }
66
+ .nxgt-action-card[data-size="tile"][data-has-action][data-icon]:has(
67
+ > .nxgt-action-card-indicator
68
+ ) {
69
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
70
+ grid-template-areas:
71
+ "icon title act ind"
72
+ ". status status status"
73
+ ". desc desc desc"
74
+ ". meta meta meta";
75
+ }
76
+
77
+ /* The indicator is centred on the title row (36px high). */
78
+ .nxgt-action-card[data-size="tile"] .nxgt-action-card-indicator {
79
+ align-self: start;
80
+ margin-block: 5px;
81
+ }
82
+
83
+ /* The action is no longer at the card's edge: no optical offset. */
84
+ .nxgt-action-card[data-size="tile"]:has(> .nxgt-action-card-indicator)
85
+ .nxgt-action-card-action {
86
+ margin-inline-end: 0;
87
+ }
40
88
  }
@@ -7,7 +7,10 @@
7
7
  .nxgt-action-card * {
8
8
  transition: none;
9
9
  }
10
- .nxgt-action-card[data-interactive]:not([data-disabled]):active {
10
+ .nxgt-action-card[data-interactive]:not([data-disabled]):active,
11
+ .nxgt-action-card[data-has-action][data-interactive]:not(
12
+ [data-disabled]
13
+ ):has(.nxgt-action-card-button:active) {
11
14
  scale: none;
12
15
  --ac-r: 12px;
13
16
  }
@@ -0,0 +1,141 @@
1
+ /* biome-ignore-all lint/style/noDescendingSpecificity: states are grouped by meaning, not by specificity */
2
+ /* ActionCard status, meta, action and the tile variant. Loaded after
3
+ * action-card.css and the indicator. With an `action` the title is the card's
4
+ * one button: its ::after covers the card, and the action sits above it. */
5
+ @layer components {
6
+ .nxgt-action-card-status {
7
+ display: flex;
8
+ margin-top: 4px;
9
+ min-width: 0;
10
+ }
11
+ .nxgt-action-card-meta {
12
+ display: block;
13
+ margin-top: 2px;
14
+ min-width: 0;
15
+ overflow: hidden;
16
+ white-space: nowrap;
17
+ text-overflow: ellipsis;
18
+ font-size: 0.8125rem;
19
+ line-height: 1.4;
20
+ color: color-mix(in oklab, var(--card-foreground) 70%, var(--card));
21
+ }
22
+
23
+ /* The title button: the card's press surface. */
24
+ .nxgt-action-card-button {
25
+ display: inline;
26
+ margin: 0;
27
+ padding: 0;
28
+ border: 0;
29
+ background: none;
30
+ color: inherit;
31
+ font: inherit;
32
+ letter-spacing: inherit;
33
+ text-align: start;
34
+ cursor: pointer;
35
+ outline: none;
36
+ }
37
+ .nxgt-action-card-button::after {
38
+ content: "";
39
+ position: absolute;
40
+ inset: 0;
41
+ border-radius: inherit;
42
+ z-index: 0;
43
+ }
44
+ .nxgt-action-card-button:disabled {
45
+ cursor: not-allowed;
46
+ }
47
+ .nxgt-action-card:has(.nxgt-action-card-button:focus-visible) {
48
+ outline: 3px solid var(--ac-ring);
49
+ outline-offset: -3px;
50
+ }
51
+
52
+ /* The action: its own grid area, above the stretched button. */
53
+ .nxgt-action-card-action {
54
+ grid-area: act;
55
+ position: relative;
56
+ z-index: 1;
57
+ justify-self: end;
58
+ display: inline-flex;
59
+ margin-inline-end: -8px;
60
+ }
61
+ .nxgt-action-card[data-has-action] {
62
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
63
+ grid-template-areas: "icon text act ind";
64
+ }
65
+ .nxgt-action-card[data-has-action][data-size="md"] {
66
+ grid-template-columns: auto minmax(0, 1fr) auto;
67
+ grid-template-areas: "icon act ind" "text text text";
68
+ }
69
+ .nxgt-action-card[data-has-action][data-size="md"]:not([data-icon]) {
70
+ grid-template-columns: minmax(0, 1fr) auto auto;
71
+ grid-template-areas: "text act ind";
72
+ }
73
+
74
+ /* Tile: a compact grid cell, title and action on the first row, then the
75
+ * status, the description and the meta line, each full width. The text
76
+ * wrapper dissolves so its children take the areas. */
77
+ .nxgt-action-card[data-size="tile"] {
78
+ grid-template-columns: minmax(0, 1fr) auto;
79
+ grid-template-areas: "title act" "status status" "desc desc" "meta meta";
80
+ align-items: start;
81
+ /* A taller neighbour stretches the card: keep the rows at the top. */
82
+ align-content: start;
83
+ row-gap: 0;
84
+ column-gap: 8px;
85
+ min-height: 0;
86
+ min-width: 0;
87
+ padding-block: 12px;
88
+ padding-inline: 16px 12px;
89
+ }
90
+ .nxgt-action-card[data-size="tile"][data-icon] {
91
+ grid-template-columns: auto minmax(0, 1fr) auto;
92
+ grid-template-areas:
93
+ "icon title act"
94
+ ". status status"
95
+ ". desc desc"
96
+ ". meta meta";
97
+ }
98
+ .nxgt-action-card[data-size="tile"] .nxgt-action-card-icon {
99
+ width: 36px;
100
+ height: 36px;
101
+ }
102
+ .nxgt-action-card[data-size="tile"] .nxgt-action-card-text {
103
+ display: contents;
104
+ }
105
+ .nxgt-action-card[data-size="tile"] .nxgt-action-card-title {
106
+ grid-area: title;
107
+ align-self: center;
108
+ min-height: 36px;
109
+ display: flex;
110
+ align-items: center;
111
+ }
112
+ .nxgt-action-card[data-size="tile"] .nxgt-action-card-description {
113
+ grid-area: desc;
114
+ margin-top: 2px;
115
+ }
116
+ .nxgt-action-card[data-size="tile"] .nxgt-action-card-status {
117
+ grid-area: status;
118
+ }
119
+ .nxgt-action-card[data-size="tile"] .nxgt-action-card-meta {
120
+ grid-area: meta;
121
+ }
122
+ .nxgt-action-card[data-size="tile"][data-has-action]
123
+ .nxgt-action-card-action {
124
+ align-self: start;
125
+ }
126
+
127
+ @media (forced-colors: active) {
128
+ .nxgt-action-card:has(.nxgt-action-card-button:focus-visible) {
129
+ outline: 3px solid CanvasText;
130
+ outline-offset: -5px;
131
+ }
132
+ .nxgt-action-card-meta {
133
+ color: CanvasText;
134
+ }
135
+ /* a chosen tile's border is 2px here: the content keeps its place */
136
+ .nxgt-action-card[data-size="tile"][data-active] {
137
+ padding-block: 11px;
138
+ padding-inline: 15px 11px;
139
+ }
140
+ }
141
+ }
@@ -125,13 +125,43 @@
125
125
  .nxgt-action-card[data-interactive]:not([data-disabled]) {
126
126
  cursor: pointer;
127
127
  }
128
- .nxgt-action-card[data-interactive]:not([data-disabled]):hover {
128
+ .nxgt-action-card[data-interactive]:not(
129
+ [data-has-action],
130
+ [data-disabled]
131
+ ):hover {
129
132
  --ac-tint: 11%;
130
133
  }
131
- .nxgt-action-card[data-interactive][data-active]:not([data-disabled]):hover {
134
+ .nxgt-action-card[data-interactive][data-active]:not(
135
+ [data-has-action],
136
+ [data-disabled]
137
+ ):hover {
132
138
  --ac-tint: 21%;
133
139
  }
134
- .nxgt-action-card[data-interactive]:not([data-disabled]):active {
140
+ .nxgt-action-card[data-interactive]:not(
141
+ [data-has-action],
142
+ [data-disabled]
143
+ ):active {
144
+ --ac-tint: 15%;
145
+ --ac-r: 16px;
146
+ scale: 0.97;
147
+ }
148
+
149
+ /* With an action, only the title button answers hover and press (its
150
+ * stretched ::after covers the card, padding included); the action does
151
+ * not. The rules above leave these cards to the ones below. */
152
+ .nxgt-action-card[data-has-action][data-interactive]:not([data-disabled]):has(
153
+ .nxgt-action-card-button:hover
154
+ ) {
155
+ --ac-tint: 11%;
156
+ }
157
+ .nxgt-action-card[data-has-action][data-interactive][data-active]:not(
158
+ [data-disabled]
159
+ ):has(.nxgt-action-card-button:hover) {
160
+ --ac-tint: 21%;
161
+ }
162
+ .nxgt-action-card[data-has-action][data-interactive]:not([data-disabled]):has(
163
+ .nxgt-action-card-button:active
164
+ ) {
135
165
  --ac-tint: 15%;
136
166
  --ac-r: 16px;
137
167
  scale: 0.97;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.47.0",
3
+ "version": "3.49.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -1,10 +0,0 @@
1
- /** A wall-clock moment: the weekday (`0` Sunday) and minutes since midnight. */
2
- export type Wall = {
3
- day: number;
4
- minutes: number;
5
- };
6
- /**
7
- * The weekday and time of day `now` has in `timeZone` (an IANA name), or on
8
- * the viewer's clock without one. An unknown zone falls back to the viewer's.
9
- */
10
- export declare function wallClock(now: Date, timeZone?: string): Wall;
@@ -1,37 +0,0 @@
1
- import { t as e } from "./dist-DVRQkaTK.js";
2
- import { t } from "./is-production-GLw59WU8.js";
3
- import { n, r, t as i } from "./theme-context-Cofk6vgs.js";
4
- import { t as a } from "./warn-once-C_e47JM8.js";
5
- import { useContext as o, useMemo as s } from "react";
6
- import { jsx as c } from "react/jsx-runtime";
7
- //#region lib/themes/theme-value.ts
8
- function l(e, t, n) {
9
- return {
10
- name: e,
11
- theme: t,
12
- background: t?.background === !1 ? !1 : t?.background ?? i,
13
- defaultMode: n
14
- };
15
- }
16
- //#endregion
17
- //#region lib/themes/nxgt-theme.tsx
18
- function u({ theme: i, mode: u, asChild: d = !1, children: f, ...p }) {
19
- let m = o(r), h = typeof i == "string" ? m?.find((e) => e.name === i) : i, g = typeof i == "string" ? i : i.name;
20
- !t() && m && !h && a(`theme "${g}" is not in the provider's themes; its background is the default.`, "NxgtTheme");
21
- let _ = o(n)?.defaultMode, v = s(() => l(g, h, _), [
22
- g,
23
- h,
24
- _
25
- ]), y = d ? e : "div";
26
- return /* @__PURE__ */ c(n.Provider, {
27
- value: v,
28
- children: /* @__PURE__ */ c(y, {
29
- ...p,
30
- "data-nxgt-theme": g,
31
- ...u ? { "data-theme": u } : {},
32
- children: f
33
- })
34
- });
35
- }
36
- //#endregion
37
- export { l as n, u as t };