@nxgt/material 3.80.0 → 3.82.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 +66 -0
- package/README.md +4 -3
- package/dist/{alert-dialog-DgHtXZuB.js → alert-dialog-DXLJ4AZP.js} +75 -74
- package/dist/{async-slice-YBQsGtQT.js → async-slice-BfVokA46.js} +1 -1
- package/dist/{button-_vm-xSBU.js → button-CSKUtMI3.js} +2 -2
- package/dist/{calculator-CTd_S23A.js → calculator-Cv00UQgR.js} +10 -10
- package/dist/calculator-field.js +6 -6
- package/dist/{calculator-segment-OI1BT3bA.js → calculator-segment-CwnC2cV_.js} +1 -1
- package/dist/calculator.js +1 -1
- package/dist/charts.js +3 -3
- package/dist/{chip-DumcVzUO.js → chip-BGn0V6Pt.js} +3 -3
- package/dist/{code-editor-CiwgfSOK.js → code-editor-B25-mWhb.js} +4 -4
- package/dist/code-editor.js +4 -4
- package/dist/{code-language-items-Dip0xnmc.js → code-language-items-oRjtO3qV.js} +2 -2
- package/dist/components/base-content.css +11 -0
- package/dist/components/components.css +2 -0
- package/dist/components/ui/collections/data-table/data-table-items-tiles.css +27 -1
- package/dist/components/ui/collections/data-table/data-table-items.css +15 -0
- package/dist/components/ui/display/tube-cap/tube-cap.css +87 -0
- package/dist/{components-CJFHhkZv.js → components-BmZk-git.js} +19258 -18882
- package/dist/components.js +37 -37
- package/dist/{conversion-variant-CqrPwPjC.js → conversion-variant-wZd-G6xR.js} +3 -3
- package/dist/{currency-field-CMFvctye.js → currency-field-DizBTAat.js} +4 -4
- package/dist/{date.utils-DUvqcjan.js → date.utils-JTEIo3mM.js} +98 -98
- package/dist/{dialog-B0iU3fmh.js → dialog-B-3s-oXt.js} +3 -3
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-CPOgcajv.js → dropdown-menu-C6rcYGB9.js} +1 -1
- package/dist/editor.js +987 -980
- package/dist/{empty-state-D3Jggupu.js → empty-state-CSkghnD9.js} +1 -1
- package/dist/{financial-variant-BdyfYhnp.js → financial-variant-igpGRbGw.js} +5 -5
- package/dist/{hooks-Z7KKbv4G.js → hooks-k8_f3B0k.js} +2 -2
- package/dist/hooks.js +9 -8
- package/dist/{hover-card-CFGIX6kg.js → hover-card-DuaVVKMb.js} +1 -1
- package/dist/i18n.js +3 -3
- package/dist/{icon-badge-kxGWUvcO.js → icon-badge-3ThP6EOl.js} +1 -1
- package/dist/{icon-button-BamxMwZs.js → icon-button-4PKlaI1g.js} +2 -2
- package/dist/lib/components/ui/actions/button/button-tones.d.ts +17 -16
- package/dist/lib/components/ui/collections/day-grouped-list/day-grouped-list.d.ts +6 -1
- package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +2 -0
- package/dist/lib/components/ui/collections/server-table/types-states.d.ts +5 -1
- package/dist/lib/components/ui/display/index.d.ts +1 -0
- package/dist/lib/components/ui/display/tube-cap/index.d.ts +2 -0
- package/dist/lib/components/ui/display/tube-cap/tube-cap.d.ts +27 -0
- package/dist/lib/components/ui/display/tube-cap/tube-cap.kinds.d.ts +7 -0
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/fields/inline-edit/inline-edit-display.d.ts +5 -1
- package/dist/lib/components/ui/fields/inline-edit/inline-edit-editor.d.ts +6 -1
- package/dist/lib/components/ui/fields/inline-edit/inline-edit.d.ts +14 -1
- package/dist/lib/components/ui/fields/inline-edit/use-inline-edit.d.ts +4 -1
- package/dist/lib/components/ui/fields/repeatable-group/partials/repeatable-group-row.d.ts +3 -1
- package/dist/lib/components/ui/fields/repeatable-group/partials/repeatable-group-rows.d.ts +2 -1
- package/dist/lib/components/ui/fields/repeatable-group/repeatable-group.d.ts +1 -1
- package/dist/lib/components/ui/fields/repeatable-group/types.d.ts +7 -0
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-grid.types.d.ts +6 -1
- package/dist/lib/components/ui/layout/dashboard-grid/dashboard-tile.d.ts +1 -1
- package/dist/lib/components/ui/media/galleries/gallery.d.ts +1 -1
- package/dist/lib/components/ui/media/galleries/gallery.utils.d.ts +14 -0
- package/dist/lib/components/ui/media/galleries/lightbox-gallery.d.ts +1 -1
- package/dist/lib/components/ui/media/galleries/mini-gallery.d.ts +1 -1
- package/dist/lib/components/ui/media/galleries/partials/gallery-tile-button.d.ts +12 -0
- package/dist/lib/components/ui/media/galleries/partials/gallery-tile.d.ts +3 -1
- package/dist/lib/components/ui/overlays/dialog/dialog-parts.d.ts +1 -1
- package/dist/lib/components/ui/overlays/sheet/sheet-parts.d.ts +5 -0
- package/dist/lib/components/ui/selection/choice-card-group/choice-card-balance.d.ts +4 -0
- package/dist/lib/components/ui/selection/choice-card-group/choice-card-group.d.ts +3 -1
- package/dist/lib/components/ui/selection/choice-card-group/choice-card-group.types.d.ts +7 -0
- package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-body.d.ts +5 -1
- package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-row-parts.d.ts +2 -1
- package/dist/lib/components/ui/selection/sortable-list/partials/sortable-list-row-props.d.ts +8 -5
- package/dist/lib/components/ui/selection/sortable-list/partials/use-owed-focus.d.ts +10 -0
- package/dist/lib/components/ui/selection/sortable-list/sortable-list-context.d.ts +2 -0
- package/dist/lib/components/ui/selection/sortable-list/sortable-list.keyboard.d.ts +1 -1
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar-context.d.ts +3 -0
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar-shell.d.ts +15 -0
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar-template.d.ts +9 -0
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar.d.ts +7 -3
- package/dist/lib/components/ui/widgets/event-calendar/event-calendar.template.d.ts +41 -0
- package/dist/lib/components/ui/widgets/event-calendar/index.d.ts +1 -0
- package/dist/lib/components/ui/widgets/event-calendar/partials/calendar-date-nav.d.ts +2 -0
- package/dist/lib/components/ui/widgets/event-calendar/partials/calendar-lane-toggle.d.ts +5 -0
- package/dist/lib/components/ui/widgets/event-calendar/partials/time-grid-header-template.d.ts +7 -0
- package/dist/lib/components/ui/widgets/event-calendar/template.types.d.ts +73 -0
- package/dist/lib/components/ui/widgets/event-calendar/types.d.ts +2 -0
- package/dist/lib/components/ui/widgets/event-calendar/use-chip-event.d.ts +1 -0
- package/dist/lib/components/ui/widgets/event-calendar/use-drawn-events.d.ts +4 -3
- package/dist/lib/components/ui/widgets/event-calendar/use-template-calendar.d.ts +18 -0
- package/dist/lib/components/ui/widgets/event-calendar/use-template-lane.d.ts +17 -0
- package/dist/lib/hooks/index.d.ts +1 -0
- package/dist/lib/i18n/resources/en/en.d.ts +2 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +2 -0
- package/dist/lib/i18n/resources/fr/fr.d.ts +2 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +2 -0
- package/dist/lib/i18n/resources/resources.d.ts +4 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/illustrations-game/index.d.ts +4 -3
- package/dist/lib.js +2 -2
- package/dist/main.js +53 -52
- package/dist/map.js +7 -7
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/{percent-field-C2LNr8NA.js → percent-field-QwU1l2Q4.js} +1 -1
- package/dist/{picker-surface-BUCFMoAZ.js → picker-surface-CiTycWgV.js} +4 -4
- package/dist/{popover-DFRwQF12.js → popover-YmbzbKQG.js} +1 -1
- package/dist/{pos-types-CXnEdjdy.js → pos-types-BFrapNmx.js} +1 -1
- package/dist/{pos-variant-DMwn_7is.js → pos-variant-CctfqieO.js} +4 -4
- package/dist/print.js +1 -1
- package/dist/{programmer-variant-Bo4eiDFi.js → programmer-variant-Dd0YOixn.js} +2 -2
- package/dist/{public-CyQPkkSR.js → public-CshR_aW7.js} +10 -10
- package/dist/{rich-code-block-iK7UcZdc.js → rich-code-block-BuJLDoBu.js} +5 -5
- package/dist/{scientific-variant-B9ScTgfx.js → scientific-variant-CjErQxuZ.js} +2 -2
- package/dist/{sortable-provider-BZwVjSVU.js → sortable-provider-DU2nvpFc.js} +2 -2
- package/dist/{spinner-RITOxvpH.js → spinner-DH1mm8hH.js} +1 -1
- package/dist/{tabs-DSo4eCUn.js → tabs-Um0SFyf1.js} +1 -1
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle-3gbzQX0x.js → theme-toggle-C-Ih4uQU.js} +1 -1
- package/dist/{toolbar-items-Bt5RK-Nx.js → toolbar-items-BoEY5bkt.js} +4 -4
- package/dist/{translate-material-CZTGqgen.js → translate-material-D4rn5UaQ.js} +1 -1
- package/dist/{translator-DTlOlIoq.js → translator-CcEgQ-H7.js} +4 -0
- package/dist/{use-chat-composer-DBY01IpX.js → use-chat-composer-BfFcmDua.js} +1439 -1391
- package/dist/{use-click-lock-BZYtTmVt.js → use-click-lock-Bwg_caHO.js} +14 -14
- package/dist/{use-heading-spy-Dt48bBts.js → use-heading-spy-gDNtK1sU.js} +19 -19
- package/dist/{use-material-translation-BJucC_xe.js → use-material-translation-DYrfV_JJ.js} +1 -1
- package/dist/{use-return-focus-BS1JLcnq.js → use-return-focus-C_Qz_yTk.js} +1 -1
- package/docs/README.md +1 -1
- package/docs/components/data-display.md +36 -8
- package/docs/components/data-table-filter-and-lists.md +7 -3
- package/docs/components/form-fields.md +6 -0
- package/docs/components/media-and-files.md +1 -0
- package/docs/components/navigation-and-layout.md +3 -1
- package/docs/components/overlays-and-menus.md +11 -5
- package/docs/components/rich-widgets.md +85 -1
- package/docs/components/selection-and-choice.md +49 -10
- package/docs/guides/forms.md +7 -0
- package/docs/guides/helpers.md +23 -0
- package/docs/guides/icon-motifs.md +1 -1
- package/docs/guides/illustrations.md +5 -5
- package/docs/guides/layouts.md +2 -2
- package/docs/guides/theming.md +7 -7
- package/docs/previews/data-display-tubecap.webp +0 -0
- package/docs/previews/feedback-status-illustration-game.webp +0 -0
- package/docs/previews/selection-choice-sortablelist.webp +0 -0
- package/docs/roadmap.md +3 -1
- package/docs/troubleshooting.md +50 -2
- package/lib/components/base-content.css +11 -0
- package/lib/components/components.css +2 -0
- package/lib/components/ui/collections/data-table/data-table-items-tiles.css +27 -1
- package/lib/components/ui/collections/data-table/data-table-items.css +15 -0
- package/lib/components/ui/display/tube-cap/tube-cap.css +87 -0
- package/lib/themes/ambre.css +1 -1
- package/lib/themes/ardoise.css +1 -1
- package/lib/themes/bleumono.css +1 -1
- package/lib/themes/bluemoon.css +1 -1
- package/lib/themes/corail.css +2 -2
- package/lib/themes/foret.css +1 -1
- package/lib/themes/lavande.css +1 -1
- package/lib/themes/menthe.css +2 -2
- package/lib/themes/ocean.css +1 -1
- package/lib/themes/pink.css +2 -2
- package/lib/themes/prune.css +1 -1
- package/lib/themes/sable.css +1 -1
- package/lib/themes/slate.css +1 -1
- package/lib/themes/teal.css +2 -2
- package/package.json +2 -2
|
@@ -839,6 +839,18 @@ export function Topics() {
|
|
|
839
839
|
}
|
|
840
840
|
```
|
|
841
841
|
|
|
842
|
+
Balanced by default, and the unbalanced fill:
|
|
843
|
+
|
|
844
|
+
```tsx
|
|
845
|
+
const four = ['a', 'b', 'c', 'd'].map((value) => ({ value, title: value.toUpperCase() }));
|
|
846
|
+
|
|
847
|
+
// Room for 3 columns, 4 cards: drawn 2+2, no orphan.
|
|
848
|
+
<ChoiceCardGroup label="Size" columns={3} options={four} value={value} onValueChange={setValue} />
|
|
849
|
+
|
|
850
|
+
// Every column that fits: 3+1.
|
|
851
|
+
<ChoiceCardGroup label="Size" columns={3} balance={false} options={four} value={value} onValueChange={setValue} />
|
|
852
|
+
```
|
|
853
|
+
|
|
842
854
|
| Prop | Type | Default | Effect |
|
|
843
855
|
| --- | --- | --- | --- |
|
|
844
856
|
| `type` | `'single' \| 'multiple'` | `'single'` | the value is `string \| null` or `string[]` |
|
|
@@ -945,8 +957,18 @@ check box at the end. `type="single"` (the default) is a radio group of
|
|
|
945
957
|
card has a soft tint and a bar on its start edge; the mark says it too, so
|
|
946
958
|
colour is never the only cue. The cards fill as many columns as fit (each at
|
|
947
959
|
least 13.5rem wide), up to `columns`, so the layout follows the space the field
|
|
948
|
-
has, not the window
|
|
949
|
-
|
|
960
|
+
has, not the window. The rows are balanced: when the last row would hold one
|
|
961
|
+
orphan card (4 cards in room for 3), the group draws fewer columns and sits 2+2;
|
|
962
|
+
`balance={false}` fills every column that fits (3+1).
|
|
963
|
+
|
|
964
|
+
When it balances (the default, horizontal), the group is an inline-size
|
|
965
|
+
container (`@container`), which is how it measures its own width. A container's
|
|
966
|
+
width never comes from its content, so give the group a parent with a width: in
|
|
967
|
+
a shrink-to-fit parent (an `inline-flex` box, a float, a centred `w-fit` column)
|
|
968
|
+
it collapses to nothing. Add `w-full`, a `max-w-*` or a fixed width to that
|
|
969
|
+
parent, or pass `balance={false}`: with it (and with `orientation="vertical"`)
|
|
970
|
+
there is no container, and the group behaves as before in a shrink-to-fit
|
|
971
|
+
parent.
|
|
950
972
|
|
|
951
973
|
```tsx
|
|
952
974
|
import { ChoiceCardGroup } from '@nxgt/material/components';
|
|
@@ -971,7 +993,8 @@ export function Plan() {
|
|
|
971
993
|
| `deselectable` | `boolean` | `false` | `single` only: pressing the chosen card clears it |
|
|
972
994
|
| `max` / `min` | `number` | — | `multiple` only: `max` blocks the other cards once reached and speaks "{count} of {max} chosen"; `min` is a hint only |
|
|
973
995
|
| `options` | `{ value; title; description?; meta?; icon?; disabled?; disabledReason? }[]` | — | the cards |
|
|
974
|
-
| `columns` | `1 \| 2 \| 3 \| 4` | `2` | the most columns; fewer when a card would be under 13.5rem (one on a phone) |
|
|
996
|
+
| `columns` | `1 \| 2 \| 3 \| 4` | `2` | the most columns; fewer when a card would be under 13.5rem (one on a phone). With `balance`, fewer also when it avoids an orphan: `columns={3}` and 4 cards draw 2+2 |
|
|
997
|
+
| `balance` | `boolean` | `true` | when the last row would hold one orphan card (4 cards in room for 3), draw fewer columns (2+2); by container width, CSS only. `false` fills every column that fits |
|
|
975
998
|
| `density` | `'comfortable' \| 'compact'` | `'comfortable'` | the cards' padding |
|
|
976
999
|
| `label`, `helperText`, `error`, `required`, `disabled`, `id` | as on `ChipGroupField` | — | the shared choice-group props: the group's name and help, its invalid and required states |
|
|
977
1000
|
| `name` | `string` | — | put on every card (`ChoiceCardGroupFormField` passes its field `name`), so `document.getElementsByName(name)` finds the group's cards in order |
|
|
@@ -1113,11 +1136,19 @@ English without one.
|
|
|
1113
1136
|
|
|
1114
1137
|
Searchable list you reorder by dragging or with the keyboard. The row under
|
|
1115
1138
|
the pointer lifts into a card with a deep shadow, and its place in the list
|
|
1116
|
-
stays as a dashed slot.
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1139
|
+
stays as a dashed slot. With reduced motion the drop does not animate.
|
|
1140
|
+
|
|
1141
|
+
The list is a `role="list"` of `listitem` rows, not a listbox: a row holds
|
|
1142
|
+
buttons of its own (grip, move, remove), which an `option` may not. Tab reaches
|
|
1143
|
+
the list once, on one row (a roving tab stop), and the arrows move the focus
|
|
1144
|
+
from row to row. A row of a disabled list is never a tab stop and carries
|
|
1145
|
+
`aria-disabled`. A long label keeps its full text in a `title`, and the grip is
|
|
1146
|
+
named after its item ("Drag: Ada Lovelace").
|
|
1147
|
+
|
|
1148
|
+
From the keyboard, Space (or Enter) on a row picks it up, the arrows move it,
|
|
1149
|
+
Space drops it and Escape puts it back; Ctrl/⌘ + arrow moves it at once,
|
|
1150
|
+
Home / End jump. Each step is read out by a live region ("Ada Lovelace moved to
|
|
1151
|
+
position 3 of 5."). A key pressed on a row's own button is that button's.
|
|
1121
1152
|
|
|
1122
1153
|
```tsx
|
|
1123
1154
|
import { SortableList, type SortableListItem } from '@nxgt/material/components';
|
|
@@ -1144,7 +1175,7 @@ export function Ranking() {
|
|
|
1144
1175
|
| `reorderable` | `boolean` | `false` | move up / down buttons |
|
|
1145
1176
|
| `searchable` | `boolean` | `true` | filter box |
|
|
1146
1177
|
| `height` | `number \| string` | `320` | list height |
|
|
1147
|
-
| `required` | `boolean` | — | the asterisk
|
|
1178
|
+
| `required` | `boolean` | — | the asterisk on the label (a `list` takes no `aria-required`) |
|
|
1148
1179
|
| `hideLabel` | `boolean` | `false` | `true` keeps `label` as the list's accessible name (`aria-labelledby`) but draws nothing, for a heading that already titles it |
|
|
1149
1180
|
| `numbered` | `boolean` | `false` | each row shows its rank in the whole order |
|
|
1150
1181
|
| `removable` | `boolean` | `false` | a remove button on each row, and Delete: the row leaves the value |
|
|
@@ -1156,8 +1187,16 @@ export function Ranking() {
|
|
|
1156
1187
|
where it is: no grip, not draggable, not movable from the keyboard, not
|
|
1157
1188
|
removable).
|
|
1158
1189
|
|
|
1190
|
+
In a test, query it by role and name:
|
|
1191
|
+
|
|
1192
|
+
```ts
|
|
1193
|
+
const list = screen.getByRole('list', { name: 'Ranking' });
|
|
1194
|
+
const rows = within(list).getAllByRole('listitem');
|
|
1195
|
+
await user.click(within(rows[0]).getByRole('button', { name: /^Move down/ }));
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1159
1198
|
**Emits:** `string[]`, in the new order.
|
|
1160
|
-
**Form wrapper:** `SortableListFormField` — `control` and `name` plus the same props, `hideLabel` included (the label's `required` asterisk is hidden with it; the `
|
|
1199
|
+
**Form wrapper:** `SortableListFormField` — `control` and `name` plus the same props, `hideLabel` included (the label's `required` asterisk is hidden with it; the asterisk only: a `list` takes no `aria-required`, so a required list is described by a visually hidden "This field is required."), see
|
|
1161
1200
|
[forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
|
|
1162
1201
|
**Storybook:** `Selection & choice/SortableList` · **Vue:** `SortableList`, `SortableListFormField`
|
|
1163
1202
|
|
package/docs/guides/forms.md
CHANGED
|
@@ -262,6 +262,13 @@ in `[data-slot="form-actions"]`) and put each group of fields in a
|
|
|
262
262
|
</form>
|
|
263
263
|
```
|
|
264
264
|
|
|
265
|
+
- The form grid insets its fields and its actions by 16px. In a container that
|
|
266
|
+
already pads its content (a settings page, a card, a sheet), set
|
|
267
|
+
`data-padding="none"` on the `.form-grid-layout`: the fields lose their
|
|
268
|
+
padding on all sides and the actions lose it left and right, so both sit
|
|
269
|
+
flush with that container's content edge:
|
|
270
|
+
`<form className="form-grid-layout" data-padding="none">`. The default is
|
|
271
|
+
unchanged.
|
|
265
272
|
- Each section's title is an `h2` (pass `titleAs="h3"` when the form sits
|
|
266
273
|
under an `h2` of your own), so a screen reader moves from section to
|
|
267
274
|
section, and each section is a group named by its title.
|
package/docs/guides/helpers.md
CHANGED
|
@@ -126,6 +126,7 @@ Vue: the Redux helpers are Pinia store factories: `defineLocalStore`
|
|
|
126
126
|
| `useIsClient()` | `false` on the server and first render, `true` after mount: a hydration guard. |
|
|
127
127
|
| `useCspNonce()` | The nonce of the nearest `NonceProvider`, for a `<style>` of your own (`<style nonce={useCspNonce()}>`); `undefined` outside one, on the server and on the client alike. See [CSP](csp.md#nonceprovider). |
|
|
128
128
|
| `useIsMobile()` | `true` below 768 px (`ResponsiveButton` no longer uses it: it collapses with CSS). `false` on the server and while hydrating, so SSR HTML is the desktop layout; a phone switches right after. |
|
|
129
|
+
| `useSsrMediaQuery(query, serverValue)` | Whether a media query matches, for a breakpoint `useIsMobile` (768 px) does not cover. The server and the hydrating client both render `serverValue` (`serverValue` is the answer the query gives on a desktop screen: `true` for `(min-width: …)`, `false` for `(max-width: …)`), and the client switches right after hydrating, so there is no hydration mismatch; a client-only render (SPA, client-side navigation) reads the real answer at once. Without `matchMedia` (jsdom) it answers `serverValue`. See [below](#usessrmediaquery). |
|
|
129
130
|
| `useHeadingSpy(ids, onActive, options?)` | IntersectionObserver over heading ids; reports the topmost visible one. |
|
|
130
131
|
| `useLocalizedDate()` | `{ language, locale, format }`: `DATE_UTILS.format` bound to the `I18nProvider` language (`en` without a provider). |
|
|
131
132
|
| `useNumberFormatter()` | `{ formatNumber, formatCurrency }`: `Intl.NumberFormat` with the options you pass, bound to the `I18nProvider` language (`en-US` without a provider). For what `formatAmount` does not cover: percentages, units, compact notation. |
|
|
@@ -207,6 +208,28 @@ export function Sent({ email }: { email: string }) {
|
|
|
207
208
|
`useFocusOnMount(open)` focuses again whenever `open` turns true, for content
|
|
208
209
|
that stays mounted and is only shown. Storybook: `Guides/Hooks/useFocusOnMount`.
|
|
209
210
|
|
|
211
|
+
### `useSsrMediaQuery`
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { useSsrMediaQuery } from '@nxgt/material/hooks';
|
|
215
|
+
|
|
216
|
+
// A panel that is open from 1280 px (xl, 80rem) and folded below.
|
|
217
|
+
function Inspector({ children }: { children: React.ReactNode }) {
|
|
218
|
+
const isXl = useSsrMediaQuery('(min-width: 80rem)', true);
|
|
219
|
+
return isXl ? <aside>{children}</aside> : null;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The second argument is what the server renders, and what a hydrating client
|
|
224
|
+
renders on its first pass: it is the answer the query gives on a desktop
|
|
225
|
+
screen (`true` for `(min-width: …)`, `false` for `(max-width: …)`). On an SSR
|
|
226
|
+
page a narrower screen therefore paints the desktop markup first and switches right
|
|
227
|
+
after hydrating (one flash, and no hydration mismatch). A client-only render
|
|
228
|
+
(an SPA, a client-side navigation) reads the real answer from its first
|
|
229
|
+
render, with no flash. Where `matchMedia` does not exist (jsdom) the hook
|
|
230
|
+
answers `serverValue` and never subscribes. When CSS alone can do it
|
|
231
|
+
(`hidden xl:block`), prefer CSS: it has no flash at all.
|
|
232
|
+
|
|
210
233
|
### `useCountdown`
|
|
211
234
|
|
|
212
235
|
The clock behind [`CountdownTimer`](../components/feedback-and-status.md#countdowntimer),
|
|
@@ -138,7 +138,7 @@ An `Illustration` draws a hero icon on a Material 3 Expressive plate, with two
|
|
|
138
138
|
to four satellites. There are twelve base scenes (`empty`, `no-results`,
|
|
139
139
|
`not-found`, `error`, `offline`, `success`, `welcome`, `forbidden`,
|
|
140
140
|
`maintenance`, `invitation`, `sign-in`, `empty-inbox`) and two domain sets of
|
|
141
|
-
ten, **health** and **education**, plus **game** (
|
|
141
|
+
ten, **health** and **education**, plus **game** (twenty-five scenes for games and
|
|
142
142
|
events), each its own subpath so an app that does not import one does not carry it:
|
|
143
143
|
|
|
144
144
|
<img src="../previews/feedback-status-illustration.webp" width="480" alt="The twelve base illustration scenes">
|
|
@@ -5,7 +5,7 @@ on a Material 3 Expressive plate, two to four satellite icons around it, a
|
|
|
5
5
|
dashed ring and a few sparkles. There are twelve scenes: `empty`, `no-results`,
|
|
6
6
|
`not-found`, `error`, `offline`, `success`, `welcome`, `forbidden`,
|
|
7
7
|
`maintenance`, `invitation`, `sign-in` and `empty-inbox`, and two domain sets of
|
|
8
|
-
ten more each, health and education, in their own entries ([health](#scene-sets-the-health-scenes) and [education](#scene-sets-the-education-scenes)), and
|
|
8
|
+
ten more each, health and education, in their own entries ([health](#scene-sets-the-health-scenes) and [education](#scene-sets-the-education-scenes)), and twenty-five for games ([game](#scene-sets-the-game-scenes)). The icons are
|
|
9
9
|
[Font Awesome](https://fontawesome.com) sprites **your app serves** (see
|
|
10
10
|
[Icons](../getting-started.md#icons)): the package ships no icon artwork, so it
|
|
11
11
|
works with the sprites your licence covers. The [icon motifs guide](icon-motifs.md) shows them next to the icon backgrounds, the sky and the themes, and lists the suggested names that are Pro only, with Free neighbours.
|
|
@@ -148,12 +148,12 @@ icons: serve a Pro sprite, or set `edition="free"` on `IconSpritesProvider`, whi
|
|
|
148
148
|
|
|
149
149
|
## Scene sets: the game scenes
|
|
150
150
|
|
|
151
|
-
`@nxgt/material/illustrations/game` holds
|
|
151
|
+
`@nxgt/material/illustrations/game` holds twenty-five scenes for a game, a quiz, a
|
|
152
152
|
community or an event app: `night-sky`, `dawn`, `village` (night),
|
|
153
|
-
`day-village`, `graveyard`,
|
|
154
|
-
`archer`, `cupid`, `wolf`, `villager`, `witch`), `victory`, `empty-table`,
|
|
153
|
+
`day-village`, `graveyard`, sixteen character pictograms (`seer`, `healer`,
|
|
154
|
+
`archer`, `cupid`, `wolf`, `villager`, `witch`, `protector`, `elder`, `village-idiot`, `scapegoat`, `raven`, `fox`, `siblings`, `big-bad-wolf`, `wolf-father`), `victory`, `empty-table`,
|
|
155
155
|
`waiting-players` and `connection-lost`. It works exactly like the other sets,
|
|
156
|
-
and they register side by side. Use them where a screen is empty, waiting,
|
|
156
|
+
and they register side by side. The `fox` and `scapegoat` heroes are drawn as vector art, because the icon sprites have no fox or goat; they follow the tone and dark mode like the sprite icons. Use them where a screen is empty, waiting,
|
|
157
157
|
failed, celebrating or opening: a lobby with no players, a round that waits, a
|
|
158
158
|
dropped connection, a winner, a night-phase hero. Keep them away from behind
|
|
159
159
|
data.
|
package/docs/guides/layouts.md
CHANGED
|
@@ -204,8 +204,8 @@ 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
|
|
208
|
-
replace the data.
|
|
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.
|
|
209
209
|
|
|
210
210
|
## See also
|
|
211
211
|
|
package/docs/guides/theming.md
CHANGED
|
@@ -68,7 +68,7 @@ at the top are needed is in [getting started](../getting-started.md#styles).
|
|
|
68
68
|
| `--accent`, `--accent-foreground` | `bg-accent`, … | hover surfaces |
|
|
69
69
|
| `--nxgt-accent` | `[--nxgt-accent:…]` | the mark colour of the selection controls: the fill, handle, state layer and stop dots of `Slider`, the on state of `Switch`, the box and dot of `Checkbox` and `Radio`. It defaults to `--primary` (`--primary-text` in dark) and is set per control, so override it on the control itself, not on `:root` |
|
|
70
70
|
| `--error`, `--success`, `--info`, `--warning` (+ `-foreground`) | `bg-error`, `text-success`, … | the status colours of `Button`, `Alert`, `Badge`, toasts |
|
|
71
|
-
| `--primary-text` | `text-primary-text` | primary drawn as text or an icon on a page, card or popover surface: `Typography tone="primary"`, links, the `Alert` primary icon, selected labels. In light, `--primary` mixed toward black: 96 % at most, solved lower on a tinted page so a tonal Button label on a striped row reaches 4.6:1 (
|
|
71
|
+
| `--primary-text` | `text-primary-text` | primary drawn as text or an icon on a page, card or popover surface: `Typography tone="primary"`, links, the `Alert` primary icon, selected labels. In light, `--primary` mixed toward black: 96 % at most, solved lower on a tinted page so a tonal Button label on a striped row, or straight on the page ground, reaches 4.6:1 (92 % on teal, 88 % on corail, 92 % on pink, 95 % on menthe); in dark, `--primary` mixed 60 % with white (`color-mix(in oklab, var(--primary) 40%, white)`), well over 4.5:1 on `--background` and `--card` |
|
|
72
72
|
| `--primary-indicator` | `bg-primary-indicator`, `border-primary-indicator`, `ring-primary-indicator`, `stroke-primary-indicator` | primary drawn as a non-text mark that carries a state (WCAG 1.4.11, 3:1): an active bar or dot, a selected or focused border, a ring, a spinner arc. Equal to `--primary` in light; in dark, `--primary` mixed 25 % with white (`color-mix(in oklab, var(--primary) 75%, white)`), 4.8:1 on `--background`, 4.5:1 on `--card`, 4.2:1 on `--muted` (`--primary` itself gives 2.7, 2.5 and 2.3:1) |
|
|
73
73
|
| `--error-text` | `text-error-text` | error drawn as text: field messages, `HelperText` in its error state, form and dialog errors, `variant="error"` menu items |
|
|
74
74
|
| `--destructive` | `bg-destructive`, `text-destructive` | deprecated alias of `--error`: the light theme sets it to `var(--error)`, the dark theme keeps its own value; use `--error` |
|
|
@@ -936,14 +936,14 @@ ones also take 10 % on focus, as on hover, so their label keeps 4.5:1 over it
|
|
|
936
936
|
|
|
937
937
|
The `tonal`, `outlined` and `link` variants of `Button` and `IconButton` draw
|
|
938
938
|
their text and icon in one tone per colour, read on `--background`, on
|
|
939
|
-
`--card
|
|
940
|
-
the light theme `primary
|
|
941
|
-
|
|
942
|
-
|
|
939
|
+
`--card`, on the page ground (`--ground`, a tinted page) and on the variant's own tint over each (15 % at most, at rest or focused). In
|
|
940
|
+
the light theme `primary` uses `--primary-text` (solved per theme), `secondary`, `info` and `success`
|
|
941
|
+
are mixed with 10 % black, `warning` with 15 % black and `error` with 20 % black (4.6:1 or more
|
|
942
|
+
on the page ground in every preset). In the dark theme `primary` is
|
|
943
943
|
mixed with 60 % white, the `--primary-text` token (8.5:1 at its lowest: the token itself is a dark indigo,
|
|
944
944
|
2.3:1), `success` and `warning` with 15 % white (5.3 and 6.5:1), `error` with
|
|
945
945
|
30 % white (6.0:1), and `secondary` and `info` use the token (5.1 and 5.6:1).
|
|
946
|
-
The tones are mixed from the tokens (`color-mix(in oklab, var(--color-
|
|
946
|
+
The tones are mixed from the tokens (`color-mix(in oklab, var(--color-warning)
|
|
947
947
|
85%, black)`), so an override of a token moves its tone with it; a text colour
|
|
948
948
|
of your own on one of those buttons (`className="text-primary-text"`) replaces the tone
|
|
949
949
|
and is yours to check.
|
|
@@ -1012,7 +1012,7 @@ override on the root, or use a [theme zone](#themes), which re-declares them.
|
|
|
1012
1012
|
for the same reason: the brand colour is a dark fill (it keeps near-white text
|
|
1013
1013
|
readable on a button) and reads at only about 2:1 as text on the dark surfaces.
|
|
1014
1014
|
The light value is `color-mix(in oklab, var(--primary) 96%, black)` at most (primary
|
|
1015
|
-
4 % toward black, 7.9:1 on the white page; the share is solved per theme and drops when a tinted page would leave a tonal Button label under 4.6:1 on a striped row, to
|
|
1015
|
+
4 % toward black, 7.9:1 on the white page; the share is solved per theme and drops when a tinted page would leave a tonal Button label under 4.6:1 on a striped row or on the page ground, to 92 % on teal); the dark one is
|
|
1016
1016
|
`color-mix(in oklab, var(--primary) 40%, white)` (primary mixed 60 % with
|
|
1017
1017
|
white, the recipe of the tonal `Button` labels). `Typography tone="primary"`,
|
|
1018
1018
|
the `Alert` primary icon, links, selected labels and the other primary text of
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/roadmap.md
CHANGED
|
@@ -383,7 +383,9 @@ version they shipped in, and planned entries may name the major they target.
|
|
|
383
383
|
|
|
384
384
|
## Shipped
|
|
385
385
|
|
|
386
|
-
- **
|
|
386
|
+
- **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
|
+
|
|
388
|
+
- **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.
|
|
387
389
|
|
|
388
390
|
- **Moment (3.74.0)** — a transient full-screen announcement for a one-off event (a player is eliminated, a role is revealed): a modal dialog named by its title, with an illustration, description, action and tone, dismissed by Escape, a tap, its action or a timer, and no motion under reduced motion ([#2220](https://github.com/softistx/nxgt-material/issues/2220), [#2247](https://github.com/softistx/nxgt-material/pull/2247)). A `modal={false}` variant for live events (focus stays, taps pass through, polite status) follows in the next minor ([#2313](https://github.com/softistx/nxgt-material/issues/2313)).
|
|
389
391
|
- **RecordActionsMenu (next minor)** — one menu for a record's lifecycle: archive with an Undo toast (or confirm first with `archivePolicy="confirm"`), restore, and delete that always confirms and shows the backend's refusal, with `ConfirmDialog` gaining `details` and `refusal` slots.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -78,6 +78,8 @@ words in your error.
|
|
|
78
78
|
- [`ActivityLayout`'s `backdrop` shows nothing, or the blobs are still there](#activitylayouts-backdrop-shows-nothing-or-the-blobs-are-still-there)
|
|
79
79
|
- [Shapes of an `ActivityLayout` `backdrop` run behind the text of the chrome or a list](#shapes-of-an-activitylayout-backdrop-run-behind-the-text-of-the-chrome-or-a-list)
|
|
80
80
|
- [Shaped Gallery tiles are plain rounded squares](#shaped-gallery-tiles-are-plain-rounded-squares)
|
|
81
|
+
- [ChoiceCardGroup shows 2+2 instead of 3+1](#choicecardgroup-shows-22-instead-of-31)
|
|
82
|
+
- [A `ChoiceCardGroup` collapses to nothing in an inline-flex or centred parent](#a-choicecardgroup-collapses-to-nothing-in-an-inline-flex-or-centred-parent)
|
|
81
83
|
- [A component with my own tone (Shape, Fab, Avatar, StatCard, a metric card) shows no colour, or `tone` is a type error](#a-component-with-my-own-tone-shape-fab-avatar-statcard-a-metric-card-shows-no-colour-or-tone-is-a-type-error)
|
|
82
84
|
- [A test expects the old `bg-muted` Avatar fallback, or a filled fallback shows a grey ring](#a-test-expects-the-old-bg-muted-avatar-fallback-or-a-filled-fallback-shows-a-grey-ring)
|
|
83
85
|
|
|
@@ -341,6 +343,8 @@ words in your error.
|
|
|
341
343
|
- [An `Autocomplete` list covers its helper text, or a chip test no longer finds its markup](#an-autocomplete-list-covers-its-helper-text-or-a-chip-test-no-longer-finds-its-markup)
|
|
342
344
|
- [A `CountryField` lost its chip, or its `renderOption` or `filter` does nothing](#a-countryfield-lost-its-chip-or-its-renderoption-or-filter-does-nothing)
|
|
343
345
|
- [A `CountryField` or `TimezoneField` test no longer finds its field or its options](#a-countryfield-or-timezonefield-test-no-longer-finds-its-field-or-its-options)
|
|
346
|
+
- [A test that queries a `SortableList` by `listbox` or `option` finds nothing](#a-test-that-queries-a-sortablelist-by-listbox-or-option-finds-nothing)
|
|
347
|
+
- [axe reports `nested-interactive` on a list whose rows hold buttons](#axe-reports-nested-interactive-on-a-list-whose-rows-hold-buttons)
|
|
344
348
|
- [A `SearchField` lost the height, radius or fill set through `[&_input]`](#a-searchfield-lost-the-height-radius-or-fill-set-through-_input)
|
|
345
349
|
- [`ComboboxEmpty`'s text shows under the options of a `Combobox`](#comboboxemptys-text-shows-under-the-options-of-a-combobox)
|
|
346
350
|
- [`ConfirmDialog` buttons stay in English](#confirmdialog-buttons-stay-in-english)
|
|
@@ -1744,6 +1748,23 @@ page.locator('[data-slot=chip]').getByRole('button', { name: 'Remove Design' });
|
|
|
1744
1748
|
**Why:** the shapes live in an opt-in stylesheet, `@nxgt/material/galleries.css`, which `styles.css` does not import. Without it a shaped tile keeps the `xl` corner (`--nxgt-shape-corner-xl`, 28px). The same fallback shows in a browser without CSS [`shape()`](https://developer.mozilla.org/en-US/docs/Web/CSS/basic-shape/shape) (Chromium-based and Safari today), which drops the `clip-path`. Under `prefers-reduced-motion` the shapes show but do not morph, by design. Also check that nothing overrides `clip-path` on `[data-slot=gallery-tile-media]`.
|
|
1745
1749
|
**Fix:** import it once, next to `styles.css`: `@import "@nxgt/material/galleries.css";` (or `import '@nxgt/material/galleries.css'`). For a browser without `shape()`, accept the rounded fallback, or use a non-shaped preset and pass `shape` only where you want it.
|
|
1746
1750
|
|
|
1751
|
+
|
|
1752
|
+
### ChoiceCardGroup shows 2+2 instead of 3+1
|
|
1753
|
+
|
|
1754
|
+
**When:** a `ChoiceCardGroup` with `columns={3}` (or 4) and 4 cards (or any count that leaves one card alone on the last row) draws two rows of two, where it used to draw three and one.
|
|
1755
|
+
**Why:** rows are balanced by default (`balance`, `true`): when the last row would hold one orphan card and fewer columns avoid it, the group draws fewer columns. It is decided by CSS container queries on the group's own width, so the server and the client print the same markup.
|
|
1756
|
+
**Fix:** keep it, or restore the old fill with `balance={false}`:
|
|
1757
|
+
|
|
1758
|
+
```tsx
|
|
1759
|
+
<ChoiceCardGroup label="Size" columns={3} balance={false} options={options} />
|
|
1760
|
+
```
|
|
1761
|
+
|
|
1762
|
+
### A `ChoiceCardGroup` collapses to nothing in an inline-flex or centred parent
|
|
1763
|
+
|
|
1764
|
+
**When:** the group sits in an `inline-flex` box, a float or a `w-fit` column and its cards are squeezed to zero width or the label shows alone.
|
|
1765
|
+
**Why:** a balanced group (the default, horizontal) is an inline-size container (`@container`), so that it can balance its rows; a container's width does not come from its content, so a shrink-to-fit parent gives it none. With `balance={false}`, or `orientation="vertical"`, there is no container.
|
|
1766
|
+
**Fix:** give the parent a width (`w-full`, a `max-w-*` or a fixed width), centring it with margins rather than shrink-to-fit, or pass `balance={false}` to restore the previous layout.
|
|
1767
|
+
|
|
1747
1768
|
### A component with my own tone (Shape, Fab, Avatar, StatCard, a metric card) shows no colour, or `tone` is a type error
|
|
1748
1769
|
|
|
1749
1770
|
**When:** `<Shape tone="maths" />`, `<Fab tone="maths" />`, an `Avatar`, a `StatCard` or a metric card with `tone="maths"` draws no colour, or TypeScript reports:
|
|
@@ -3272,6 +3293,9 @@ only in the browser reads the real answer from its first render, as before.
|
|
|
3272
3293
|
`Stepper` logged this line until 2.2.0, and a desktop hydrating its server
|
|
3273
3294
|
HTML showed the mismatch (it asks whether the screen is wide); upgrade and
|
|
3274
3295
|
drop any `clientOnly(Stepper)` you added for it.
|
|
3296
|
+
For a breakpoint of your own, use
|
|
3297
|
+
[`useSsrMediaQuery(query, serverValue)`](guides/helpers.md#usessrmediaquery)
|
|
3298
|
+
instead of `useMedia` or a hand-written `useSyncExternalStore`.
|
|
3275
3299
|
|
|
3276
3300
|
### `useMatches must be used within a data router`
|
|
3277
3301
|
|
|
@@ -5105,7 +5129,7 @@ append({ name: '' }, { shouldFocus: false });
|
|
|
5105
5129
|
`aria-disabled`, still focusable, and does nothing by design; the reason, if
|
|
5106
5130
|
`removeDisabledReason` gave one, is its tooltip, shown on hover, focus, tap and
|
|
5107
5131
|
click, and its description), or `min` is
|
|
5108
|
-
reached (the button is `disabled`).
|
|
5132
|
+
reached (the button is `disabled`, or absent with `removeAtMin="hide"`).
|
|
5109
5133
|
**Fix:** return `true` from `removable` for a row that may go, and say why for
|
|
5110
5134
|
the others, so a user is told instead of left guessing:
|
|
5111
5135
|
|
|
@@ -5202,7 +5226,8 @@ The `*` is now drawn by a CSS pseudo-element with an empty `content`
|
|
|
5202
5226
|
field's own `required` / `aria-required` says it is required, and every field
|
|
5203
5227
|
of this library sets it on the element a screen reader lands on (the input, the
|
|
5204
5228
|
picker's combobox, the select's trigger, the slider thumbs, the rating's
|
|
5205
|
-
radiogroup, the editor's textbox, the
|
|
5229
|
+
radiogroup, the editor's textbox, the `TransferList`'s listbox (a `SortableList` is a `list`
|
|
5230
|
+
and only draws the asterisk, with no `aria-required`), or, for a file picker,
|
|
5206
5231
|
a group described as "This field is required."). A `SelectField`'s trigger is
|
|
5207
5232
|
now named by its label (`aria-labelledby`), so `getByRole('combobox', { name:
|
|
5208
5233
|
'Framework' })` finds it.
|
|
@@ -6353,6 +6378,29 @@ expect(screen.getByRole('combobox', { name: 'Time zone' })).toHaveValue('Paris')
|
|
|
6353
6378
|
screen.getByRole('option', { name: 'Paris GMT+2' });
|
|
6354
6379
|
```
|
|
6355
6380
|
|
|
6381
|
+
### A test that queries a `SortableList` by `listbox` or `option` finds nothing
|
|
6382
|
+
|
|
6383
|
+
**When:** a test queries a `SortableList` by `listbox`, `option` or `aria-selected`, after upgrading.
|
|
6384
|
+
**Why:** a row holds buttons of its own (grip, move, remove), which an `option`
|
|
6385
|
+
may not, so axe reported `nested-interactive`. The list is now a `role="list"`
|
|
6386
|
+
of `listitem` rows; `aria-selected`, `aria-activedescendant` and `aria-required`
|
|
6387
|
+
are gone (`aria-disabled` stays on a disabled row).
|
|
6388
|
+
**Fix:**
|
|
6389
|
+
|
|
6390
|
+
```ts
|
|
6391
|
+
const list = screen.getByRole('list', { name: 'Ranking' });
|
|
6392
|
+
const rows = within(list).getAllByRole('listitem');
|
|
6393
|
+
```
|
|
6394
|
+
|
|
6395
|
+
### axe reports `nested-interactive` on a list whose rows hold buttons
|
|
6396
|
+
|
|
6397
|
+
**When:** axe flags a list whose rows hold buttons or other controls.
|
|
6398
|
+
**Why:** a row with `role="option"` (or `button`, `tab`...) may not contain
|
|
6399
|
+
focusable controls: assistive technology reads its children as text.
|
|
6400
|
+
**Fix:** make the row a `listitem` of a `list` (a roving tab stop on the row,
|
|
6401
|
+
the buttons keep their own names), as `SortableList` does. Do not exclude the
|
|
6402
|
+
list from the axe run.
|
|
6403
|
+
|
|
6356
6404
|
### An input inside an `InputGroup` lost a class or a style of `Input`
|
|
6357
6405
|
|
|
6358
6406
|
**When:** a test or a style relies on a class of `Input` or `Textarea`
|
|
@@ -162,6 +162,17 @@
|
|
|
162
162
|
[data-slot="form-actions"] {
|
|
163
163
|
@apply sticky bottom-0 z-10 flex w-full justify-end gap-2 bg-background/80 border-t-8 border-transparent px-4 pb-[max(1rem,env(safe-area-inset-bottom))] backdrop-blur-sm max-sm:*:flex-1;
|
|
164
164
|
}
|
|
165
|
+
/* `data-padding="none"`: the form sits flush in a container that
|
|
166
|
+
already pads it (a page, a card, a sheet): no inset on the fields or
|
|
167
|
+
the actions, so both align with the container's content edge. */
|
|
168
|
+
&[data-padding="none"] {
|
|
169
|
+
[data-slot="form-fields"] {
|
|
170
|
+
@apply p-0;
|
|
171
|
+
}
|
|
172
|
+
[data-slot="form-actions"] {
|
|
173
|
+
@apply px-0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
165
176
|
}
|
|
166
177
|
|
|
167
178
|
/* No height cap and no scroll of its own: the page (or the overlay) is
|
|
@@ -144,6 +144,8 @@
|
|
|
144
144
|
@import "./ui/display/seat-ring/seat-ring-ring.css";
|
|
145
145
|
/* VoteTally: ranked bars with counts, shares and voters. */
|
|
146
146
|
@import "./ui/display/vote-tally/vote-tally.css";
|
|
147
|
+
/* TubeCap: a specimen tube chip, cap colours fixed by the tube convention. */
|
|
148
|
+
@import "./ui/display/tube-cap/tube-cap.css";
|
|
147
149
|
/* SecretCard: a face that is not rendered until it is revealed. */
|
|
148
150
|
@import "./ui/display/secret-card/secret-card.css";
|
|
149
151
|
/* Alert and Banner span the row in the form grids (#1497). */
|
|
@@ -65,8 +65,34 @@
|
|
|
65
65
|
) {
|
|
66
66
|
outline-offset: -2px;
|
|
67
67
|
}
|
|
68
|
-
/* The checkbox
|
|
68
|
+
/* The checkbox sits on the tile's title row (#2391): its centre on the
|
|
69
|
+
* title's first line (1rem at 1.4, so 0.7rem under the tile's padding), not
|
|
70
|
+
* the tile's middle, which a multi-line tile moves down to the body text.
|
|
71
|
+
* The 36px box (44px on a coarse pointer) then starts at the padding minus
|
|
72
|
+
* the half-box plus the half-line. The tile's content starts at the top so
|
|
73
|
+
* the title is where the box expects it. */
|
|
74
|
+
[data-slot="data-table-tiles"]
|
|
75
|
+
> [data-slot="data-table-item"]:not(:has([data-slot="data-table-record"])) {
|
|
76
|
+
align-content: start;
|
|
77
|
+
}
|
|
69
78
|
[data-slot="data-table-tiles"] [data-slot="data-table-item-select"] {
|
|
79
|
+
inset-block-start: 0.325rem;
|
|
80
|
+
}
|
|
81
|
+
[data-slot="data-table-tiles"][data-density="compact"]
|
|
82
|
+
[data-slot="data-table-item-select"] {
|
|
83
|
+
inset-block-start: 0.075rem;
|
|
84
|
+
}
|
|
85
|
+
@media (pointer: coarse) {
|
|
86
|
+
[data-slot="data-table-tiles"] [data-slot="data-table-item-select"],
|
|
87
|
+
[data-slot="data-table-tiles"][data-density="compact"]
|
|
88
|
+
[data-slot="data-table-item-select"] {
|
|
89
|
+
inset-block-start: 0.075rem;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/* A record row is one dense line: its box stays centred on it. */
|
|
93
|
+
[data-slot="data-table-tiles"]
|
|
94
|
+
> [data-slot="data-table-item"]:has([data-slot="data-table-record"])
|
|
95
|
+
[data-slot="data-table-item-select"] {
|
|
70
96
|
inset-block-start: 50%;
|
|
71
97
|
translate: 0 -50%;
|
|
72
98
|
}
|
|
@@ -48,6 +48,21 @@
|
|
|
48
48
|
gap: var(--nxgt-cards-gap, 0.75rem);
|
|
49
49
|
grid-template-columns: minmax(0, 1fr);
|
|
50
50
|
}
|
|
51
|
+
/* The list's 4px of ring room sits inside the scroll box, which clips: the
|
|
52
|
+
* box takes it back on each inline side, so the cards' edges (not the
|
|
53
|
+
* room's) meet the page's edge and the heading above (#2472); the parent needs 4px of inline room. */
|
|
54
|
+
[data-slot="table-wrapper"]:has(> [data-slot="data-table-cards"]) {
|
|
55
|
+
margin-inline: -4px;
|
|
56
|
+
/* `w-full` (utilities layer) would win over this layer. */
|
|
57
|
+
width: calc(100% + 8px) !important;
|
|
58
|
+
}
|
|
59
|
+
/* Tiles keep their own 4px margin, except on a phone (#2292). */
|
|
60
|
+
@media (min-width: 768px) {
|
|
61
|
+
[data-slot="table-wrapper"]:has(> [data-slot="data-table-tiles"]) {
|
|
62
|
+
margin-inline: -4px;
|
|
63
|
+
width: calc(100% + 8px) !important;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
51
66
|
@container (min-width: 36rem) {
|
|
52
67
|
[data-slot="data-table-cards"] {
|
|
53
68
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* TubeCap: a specimen tube's chip. The cap colours are the international tube
|
|
2
|
+
* convention, so they are fixed values, not theme tokens: the same in light
|
|
3
|
+
* and dark and in every theme. The swatch carries no text; the name beside it
|
|
4
|
+
* keeps the theme's foreground (AA by the theme), and a hairline in the
|
|
5
|
+
* foreground colour keeps a black or pale cap visible on either scheme. */
|
|
6
|
+
@layer components {
|
|
7
|
+
.nxgt-tube-cap {
|
|
8
|
+
--tube-cap: oklch(0.92 0 0);
|
|
9
|
+
--tube-cap-size: 1.75rem;
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
gap: 0.5rem;
|
|
13
|
+
max-inline-size: 100%;
|
|
14
|
+
min-inline-size: 0;
|
|
15
|
+
padding: 0.25rem 0.625rem 0.25rem 0.25rem;
|
|
16
|
+
border: 1px solid var(--border);
|
|
17
|
+
border-radius: 9999px;
|
|
18
|
+
background: var(--card, var(--background));
|
|
19
|
+
color: var(--foreground);
|
|
20
|
+
font-size: 0.875rem;
|
|
21
|
+
line-height: 1.25;
|
|
22
|
+
}
|
|
23
|
+
.nxgt-tube-cap[data-size="sm"] {
|
|
24
|
+
--tube-cap-size: 1.25rem;
|
|
25
|
+
font-size: 0.8125rem;
|
|
26
|
+
}
|
|
27
|
+
.nxgt-tube-cap[data-size="lg"] {
|
|
28
|
+
--tube-cap-size: 2.5rem;
|
|
29
|
+
font-size: 1rem;
|
|
30
|
+
}
|
|
31
|
+
.nxgt-tube-cap[data-kind="edta"] {
|
|
32
|
+
--tube-cap: oklch(0.62 0.13 300);
|
|
33
|
+
}
|
|
34
|
+
.nxgt-tube-cap[data-kind="heparin"] {
|
|
35
|
+
--tube-cap: oklch(0.62 0.17 145);
|
|
36
|
+
}
|
|
37
|
+
.nxgt-tube-cap[data-kind="citrate"] {
|
|
38
|
+
--tube-cap: oklch(0.8 0.1 230);
|
|
39
|
+
}
|
|
40
|
+
.nxgt-tube-cap[data-kind="esr"] {
|
|
41
|
+
--tube-cap: oklch(0.22 0 0);
|
|
42
|
+
}
|
|
43
|
+
.nxgt-tube-cap[data-kind="serum"] {
|
|
44
|
+
--tube-cap: oklch(0.55 0.21 27);
|
|
45
|
+
}
|
|
46
|
+
.nxgt-tube-cap[data-kind="gel"] {
|
|
47
|
+
--tube-cap: oklch(0.82 0.15 85);
|
|
48
|
+
}
|
|
49
|
+
.nxgt-tube-cap[data-kind="fluoride"] {
|
|
50
|
+
--tube-cap: oklch(0.68 0.01 260);
|
|
51
|
+
}
|
|
52
|
+
.nxgt-tube-cap[data-kind="trace"] {
|
|
53
|
+
--tube-cap: oklch(0.45 0.18 262);
|
|
54
|
+
}
|
|
55
|
+
.nxgt-tube-cap [data-slot="tube-cap-swatch"] {
|
|
56
|
+
flex: none;
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
inline-size: var(--tube-cap-size);
|
|
59
|
+
block-size: var(--tube-cap-size);
|
|
60
|
+
border-radius: 9999px;
|
|
61
|
+
background: var(--tube-cap);
|
|
62
|
+
box-shadow: 0 0 0 1px
|
|
63
|
+
color-mix(in oklab, var(--foreground) 45%, transparent);
|
|
64
|
+
forced-color-adjust: none;
|
|
65
|
+
}
|
|
66
|
+
.nxgt-tube-cap [data-slot="tube-cap-text"] {
|
|
67
|
+
display: inline-flex;
|
|
68
|
+
flex-wrap: wrap;
|
|
69
|
+
align-items: baseline;
|
|
70
|
+
column-gap: 0.5rem;
|
|
71
|
+
min-inline-size: 0;
|
|
72
|
+
overflow-wrap: anywhere;
|
|
73
|
+
}
|
|
74
|
+
.nxgt-tube-cap [data-slot="tube-cap-label"] {
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
}
|
|
77
|
+
.nxgt-tube-cap [data-slot="tube-cap-volume"],
|
|
78
|
+
.nxgt-tube-cap [data-slot="tube-cap-accession"] {
|
|
79
|
+
color: var(--muted-foreground);
|
|
80
|
+
font-variant-numeric: tabular-nums;
|
|
81
|
+
}
|
|
82
|
+
.nxgt-tube-cap [data-slot="tube-cap-action"] {
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
flex: none;
|
|
85
|
+
margin-inline-start: 0.125rem;
|
|
86
|
+
}
|
|
87
|
+
}
|
package/lib/themes/ambre.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Theme "ambre", generated.
|
|
2
2
|
* Seeds: primary oklch(0.52 0.13 48), secondary oklch(0.5 0.09 95), tertiary oklch(0.5 0.14 350), neutral hue 65, tint 0.85.
|
|
3
|
-
* Contrast:
|
|
3
|
+
* Contrast: 67 pairs, 0 failing.
|
|
4
4
|
* hue-near-status: The primary's hue 48 is 14° from warning (62): they may read alike. Keep an icon and a word with every status.
|
|
5
5
|
*/
|
|
6
6
|
[data-nxgt-theme="ambre"] {
|
package/lib/themes/ardoise.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Theme "ardoise", generated.
|
|
2
2
|
* Seeds: primary oklch(0.45 0.065 252), secondary oklch(0.48 0.05 215), tertiary oklch(0.48 0.1 290), neutral hue 250, tint 0.7.
|
|
3
|
-
* Contrast:
|
|
3
|
+
* Contrast: 67 pairs, 0 failing.
|
|
4
4
|
*/
|
|
5
5
|
[data-nxgt-theme="ardoise"] {
|
|
6
6
|
--radius: 0.375rem;
|
package/lib/themes/bleumono.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Theme "bleumono", generated.
|
|
2
2
|
* Seeds: primary oklch(0.46 0.13 255), secondary oklch(0.55 0.06 255), tertiary oklch(0.38 0.17 255), neutral hue 255, tint 1.1.
|
|
3
|
-
* Contrast:
|
|
3
|
+
* Contrast: 67 pairs, 0 failing.
|
|
4
4
|
* lightness-moved: secondary: lightness 0.550 to 0.520 for AA.
|
|
5
5
|
* chroma-lowered: tertiary: chroma 0.170 to 0.125 (lowered to fit sRGB or AA).
|
|
6
6
|
* hue-near-status: The primary's hue 255 is 0° from info (254.624): they may read alike. Keep an icon and a word with every status.
|
package/lib/themes/bluemoon.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Theme "bluemoon", generated.
|
|
2
2
|
* Seeds: primary oklch(0.45 0.12 265), secondary oklch(0.55 0.02 250), tertiary oklch(0.62 0.08 290), neutral hue 265, tint 1.
|
|
3
|
-
* Contrast:
|
|
3
|
+
* Contrast: 67 pairs, 0 failing.
|
|
4
4
|
* lightness-moved: secondary: lightness 0.550 to 0.520 for AA.
|
|
5
5
|
* lightness-moved: tertiary: lightness 0.620 to 0.563 for AA.
|
|
6
6
|
* hue-near-status: The primary's hue 265 is 10° from info (254.624): they may read alike. Keep an icon and a word with every status.
|
package/lib/themes/corail.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Theme "corail", generated.
|
|
2
2
|
* Seeds: primary oklch(0.58 0.17 35), secondary oklch(0.52 0.1 70), tertiary oklch(0.5 0.1 200), neutral hue 30, tint 0.8.
|
|
3
|
-
* Contrast:
|
|
3
|
+
* Contrast: 67 pairs, 0 failing.
|
|
4
4
|
* lightness-moved: primary: lightness 0.580 to 0.575 for AA.
|
|
5
5
|
* chroma-lowered: tertiary: chroma 0.100 to 0.085 (lowered to fit sRGB or AA).
|
|
6
6
|
* hue-near-status: The primary's hue 35 is 8° from error (27.325): they may read alike. Keep an icon and a word with every status.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
--popover-foreground: oklch(0.227 0.0136 30);
|
|
18
18
|
--primary: oklch(0.575 0.17 35);
|
|
19
19
|
--primary-foreground: oklch(0.984 0.003 30);
|
|
20
|
-
--primary-text: color-mix(in oklab, var(--primary)
|
|
20
|
+
--primary-text: color-mix(in oklab, var(--primary) 88%, black);
|
|
21
21
|
--primary-indicator: var(--primary);
|
|
22
22
|
--secondary: oklch(0.52 0.1 70);
|
|
23
23
|
--secondary-foreground: oklch(0.984 0.003 30);
|
package/lib/themes/foret.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Theme "foret", generated.
|
|
2
2
|
* Seeds: primary oklch(0.48 0.1 152), secondary oklch(0.5 0.08 118), tertiary oklch(0.5 0.1 55), neutral hue 140, tint 0.8.
|
|
3
|
-
* Contrast:
|
|
3
|
+
* Contrast: 67 pairs, 0 failing.
|
|
4
4
|
*/
|
|
5
5
|
[data-nxgt-theme="foret"] {
|
|
6
6
|
--radius: 0.5rem;
|
package/lib/themes/lavande.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Theme "lavande", generated.
|
|
2
2
|
* Seeds: primary oklch(46% 0.1135 276.35), secondary oklch(48% 0.1135 245.65), tertiary oklch(45.91% 0.1864 310.75), neutral hue 292, tint 1.
|
|
3
|
-
* Contrast:
|
|
3
|
+
* Contrast: 67 pairs, 0 failing.
|
|
4
4
|
*/
|
|
5
5
|
[data-nxgt-theme="lavande"] {
|
|
6
6
|
--radius: 0.625rem;
|