@nxgt/material-vue 0.26.1 → 0.28.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 +202 -0
- package/README.md +10 -7
- package/dist/Button-D1uzdbOJ.js +728 -0
- package/dist/{DropdownMenuTrigger-BHAhXGUO.js → DropdownMenuTrigger-BsuyNVzV.js} +48 -41
- package/dist/HoverCardTrigger-DaM5d4hE.js +510 -0
- package/dist/{camera-DvKuqT4L.js → camera-CxXijySa.js} +4 -4
- package/dist/components/base-content.css +139 -0
- package/dist/components/base.css +150 -0
- package/dist/components/components.css +2 -0
- package/dist/components/reduced-motion.css +56 -0
- package/dist/components/theme-tokens.css +116 -0
- package/dist/components/ui/actions/button-group/button-group.css +1 -1
- package/dist/components/ui/feedback/toast/toast.css +21 -0
- package/dist/components/ui/primitives/label/label.css +33 -0
- package/dist/{components-CCWod7cJ.js → components-Db9OdMfQ.js} +19053 -18364
- package/dist/components.js +7 -7
- package/dist/composables.js +3 -3
- package/dist/dnd.js +1 -1
- package/dist/field-name-attribute-Bl7LSVK0.js +4 -0
- package/dist/{form-scope-x-7TYn2s.js → form-scope-BILi3ke-.js} +1 -1
- package/dist/{i18n-provider-CaaZCmaR.js → i18n-provider-BPK68eqM.js} +1 -1
- package/dist/i18n.js +4 -4
- package/dist/icon-button-D60G5B_Z.js +262 -0
- package/dist/lib/components/ui/actions/button/Button.vue.d.ts +22 -4
- package/dist/lib/components/ui/actions/button/button-tones.d.ts +36 -0
- package/dist/lib/components/ui/actions/button/button.variants.d.ts +1 -1
- package/dist/lib/components/ui/actions/button/button.wrap.d.ts +8 -0
- package/dist/lib/components/ui/actions/button/label-text.d.ts +9 -0
- package/dist/lib/components/ui/actions/button/prepend-to-child.d.ts +18 -0
- package/dist/lib/components/ui/actions/button/use-click-lock.d.ts +14 -0
- package/dist/lib/components/ui/actions/copy-button/copy-button.vue.d.ts +6 -2
- package/dist/lib/components/ui/actions/icon-button/icon-button.compound-variants.d.ts +28 -0
- package/dist/lib/components/ui/actions/icon-button/icon-button.vue.d.ts +1 -1
- package/dist/lib/components/ui/actions/responsive-button/responsive-button.vue.d.ts +1 -3
- package/dist/lib/components/ui/collections/data-table/partials/data-table-empty.vue.d.ts +5 -1
- package/dist/lib/components/ui/collections/data-table/partials/data-table-scroll-region.vue.d.ts +28 -0
- package/dist/lib/components/ui/collections/data-table/partials/data-table-toolbar.d.ts +60 -0
- package/dist/lib/components/ui/collections/data-table/types.d.ts +9 -0
- package/dist/lib/components/ui/collections/data-table/use-scroll-edges.d.ts +22 -0
- package/dist/lib/components/ui/collections/pagination/PaginationFirst.vue.d.ts +1 -1
- package/dist/lib/components/ui/collections/pagination/PaginationItem.vue.d.ts +1 -1
- package/dist/lib/components/ui/collections/pagination/PaginationLast.vue.d.ts +1 -1
- package/dist/lib/components/ui/collections/pagination/PaginationNext.vue.d.ts +1 -1
- package/dist/lib/components/ui/collections/pagination/PaginationPrevious.vue.d.ts +1 -1
- package/dist/lib/components/ui/dates/calendar/Calendar.vue.d.ts +4 -4
- package/dist/lib/components/ui/dates/calendar/calendar-day-label.d.ts +17 -0
- package/dist/lib/components/ui/dates/calendar/calendar-years.d.ts +11 -0
- package/dist/lib/components/ui/dates/calendar/partials/calendar-month-select.vue.d.ts +18 -0
- package/dist/lib/components/ui/dates/calendar/partials/calendar-year-select.vue.d.ts +19 -0
- package/dist/lib/components/ui/dates/calendar/use-calendar-locale.d.ts +7 -0
- package/dist/lib/components/ui/dates/calendar/use-calendar-selected-hint.d.ts +17 -0
- package/dist/lib/components/ui/dates/date-range-field/partials/date-range-day.vue.d.ts +8 -0
- package/dist/lib/components/ui/display/cards/cards.gallery.d.ts +3 -3
- package/dist/lib/components/ui/display/list-tile/list-tile.vue.d.ts +10 -6
- package/dist/lib/components/ui/feedback/alert-dialog/AlertDialogContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/feedback/sonner/index.d.ts +1 -0
- package/dist/lib/components/ui/feedback/toast/toast.d.ts +7 -0
- package/dist/lib/components/ui/feedback/toast/toast.vue.d.ts +18 -2
- package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
- package/dist/lib/components/ui/forms/use-field-blur.d.ts +3 -0
- package/dist/lib/components/ui/layout/app-shell/activity-content.vue.d.ts +9 -3
- package/dist/lib/components/ui/layout/app-shell/activity-layout.vue.d.ts +21 -4
- package/dist/lib/components/ui/layout/app-shell/app-header.vue.d.ts +7 -5
- package/dist/lib/components/ui/layout/app-shell/app-sidebar.vue.d.ts +4 -1
- package/dist/lib/components/ui/layout/app-shell/index.d.ts +2 -2
- package/dist/lib/components/ui/layout/app-shell/router-menu-link.d.ts +7 -0
- package/dist/lib/components/ui/layout/app-shell/types.d.ts +8 -0
- package/dist/lib/components/ui/layout/app-shell/use-content-scroll.d.ts +13 -0
- package/dist/lib/components/ui/layout/app-shell/use-navigation-sheet.d.ts +17 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-context.d.ts +15 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-menu-item.vue.d.ts +13 -4
- package/dist/lib/components/ui/media/carousel/carousel-next.vue.d.ts +1 -1
- package/dist/lib/components/ui/media/carousel/carousel-previous.vue.d.ts +1 -1
- package/dist/lib/components/ui/menu-item-variant.d.ts +4 -4
- package/dist/lib/components/ui/overlays/context-menu/ContextMenuContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/dialog/DialogContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/dialog/DialogScrollContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/dialog/use-escape-forward.d.ts +17 -0
- package/dist/lib/components/ui/overlays/dialog/use-inert-others.d.ts +17 -0
- package/dist/lib/components/ui/overlays/dialog/use-modal-content.d.ts +32 -0
- package/dist/lib/components/ui/overlays/drawer/DrawerContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/dropdown-menu/DropdownMenu.vue.d.ts +3 -1
- package/dist/lib/components/ui/overlays/dropdown-menu/DropdownMenuContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/popover/PopoverContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/sheet/SheetContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/primitives/card/CardTitle.vue.d.ts +11 -4
- package/dist/lib/components/ui/primitives/card/index.d.ts +1 -0
- package/dist/lib/components/ui/primitives/tabs/Tabs.vue.d.ts +6 -2
- package/dist/lib/components/ui/primitives/tabs/TabsList.vue.d.ts +2 -2
- package/dist/lib/components/ui/primitives/tabs/TabsTrigger.vue.d.ts +6 -4
- package/dist/lib/components/ui/primitives/tabs/tabs-classes.d.ts +27 -0
- package/dist/lib/components/ui/primitives/tabs/tabs-indicator.vue.d.ts +25 -0
- package/dist/lib/components/ui/primitives/tabs/tabs-variant.d.ts +5 -8
- package/dist/lib/components/ui/primitives/toggle/Toggle.vue.d.ts +1 -1
- package/dist/lib/components/ui/primitives/toggle/index.d.ts +1 -1
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-box.vue.d.ts +2 -0
- package/dist/lib/components/ui/selection/select/SelectContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/selection/select-chip-field/select-chip-field.vue.d.ts +1 -0
- package/dist/lib/components/ui/widgets/emoji/types.d.ts +1 -6
- package/dist/lib/i18n/resources/en/en.d.ts +23 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +28 -5
- package/dist/lib/i18n/resources/fr/fr.d.ts +23 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +28 -5
- package/dist/lib/i18n/resources/resources.d.ts +46 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/lib/field-name-attribute.d.ts +7 -0
- package/dist/lib/lib/focus-field.d.ts +17 -0
- package/dist/lib/lib/index.d.ts +1 -0
- package/dist/lib.js +76 -74
- package/dist/main.js +13 -13
- package/dist/map.js +4 -4
- package/dist/material-vue.css +1 -1
- package/dist/{pdf-outline-sidebar-D3vq2qfS.js → pdf-outline-sidebar-BXGTDISI.js} +2 -2
- package/dist/{pdf-page-BrJbyjQj.js → pdf-page-BR4G03kN.js} +1 -1
- package/dist/{pdf-page-list-BCgYEszV.js → pdf-page-list-bjQm6_kf.js} +2 -2
- package/dist/{pdf-reader-BAzevSJU.js → pdf-reader-DGVEFNc0.js} +9 -9
- package/dist/{pdf-sidebar-DSh2Mend.js → pdf-sidebar-CiM6dwAm.js} +3 -3
- package/dist/{pdf-thumbnail-sidebar-D_EMcbKF.js → pdf-thumbnail-sidebar-fM3DGJzD.js} +3 -3
- package/dist/{pdf-toolbar-BcL7HlfS.js → pdf-toolbar-mWlGTWU1.js} +5 -5
- package/dist/pdf.js +2 -2
- package/dist/{sortable-provider-DMpmM7Ke.js → sortable-provider-Dml11nQY.js} +2 -2
- package/dist/{stored-language-Bxhsx4iB.js → stored-language-UnInBB6T.js} +56 -10
- package/dist/{stx-player-MldK_ZAZ.js → stx-player-BS7-CXFx.js} +2 -2
- package/dist/styles.css +10 -368
- package/dist/{translator-CLI_7Crg.js → translator-J3_vaV_A.js} +1 -1
- package/dist/{use-heading-spy-VPuFj2Gt.js → use-heading-spy-_dk8kGwL.js} +19 -19
- package/dist/use-inert-others-CtGqZKR8.js +22 -0
- package/dist/{use-material-translation-CONqD9wJ.js → use-material-translation-Do6ztdtV.js} +3 -3
- package/docs/components/buttons-and-actions.md +71 -16
- package/docs/components/data-display.md +13 -2
- package/docs/components/data-table-filter-and-lists.md +85 -5
- package/docs/components/date-and-time.md +25 -1
- package/docs/components/feedback-and-status.md +86 -11
- package/docs/components/form-fields.md +13 -4
- package/docs/components/media-and-files.md +7 -0
- package/docs/components/navigation-and-layout.md +19 -1
- package/docs/components/overlays-and-menus.md +20 -4
- package/docs/components/primitives.md +97 -20
- package/docs/components/rich-widgets.md +10 -0
- package/docs/components/selection-and-choice.md +12 -1
- package/docs/getting-started.md +2 -2
- package/docs/guides/app-shell.md +102 -6
- package/docs/guides/forms.md +58 -5
- package/docs/guides/helpers.md +2 -1
- package/docs/guides/i18n.md +24 -4
- package/docs/guides/ssr.md +1 -1
- package/docs/guides/theming.md +148 -6
- package/docs/previews/data-display-codegroup.webp +0 -0
- package/docs/previews/data-display-description.webp +0 -0
- package/docs/previews/feedback-status-toast.webp +0 -0
- package/docs/previews/primitives-tabs.webp +0 -0
- package/docs/previews/rich-widgets-emojipicker.webp +0 -0
- package/docs/roadmap.md +30 -1
- package/docs/troubleshooting.md +889 -12
- package/lib/components/base-content.css +139 -0
- package/lib/components/base.css +150 -0
- package/lib/components/components.css +2 -0
- package/lib/components/reduced-motion.css +56 -0
- package/lib/components/theme-tokens.css +116 -0
- package/lib/components/ui/actions/button-group/button-group.css +1 -1
- package/lib/components/ui/feedback/toast/toast.css +21 -0
- package/lib/components/ui/primitives/label/label.css +33 -0
- package/lib/styles.css +10 -368
- package/package.json +2 -2
- package/dist/Button-16J7vTpE.js +0 -665
- package/dist/HoverCardTrigger-C41002JS.js +0 -455
- package/dist/icon-button-CDdNsZuZ.js +0 -227
- package/dist/lib/components/ui/widgets/emoji/emoji.groups.d.ts +0 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,207 @@
|
|
|
1
1
|
# @nxgt/material-vue
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#468](https://github.com/softistx/nxgt-material/pull/468) [`5d57954`](https://github.com/softistx/nxgt-material/commit/5d57954d477f81d02872382b8ba54a97168dd191) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `Tabs` has two looks, the same as `@nxgt/material`'s, class for class: `underline`, the new default (tabs over a border, the active one in the primary colour with a bar under it that slides to the tab clicked or reached with the arrow keys), and `segmented` (a neutral pill track whose raised surface slides to the active tab, whose label also turns foreground and semibold). `TabsVariant` is `'underline' | 'segmented' | 'bar' | 'line'`; `bar` is a deprecated alias of `underline` and `line` of `segmented`, so no call site or type breaks. The sliding indicator is reka's `TabsIndicator`; the list scrolls sideways when the tabs overflow (its scrollbar hidden), `orientation="vertical"` puts the indicator on the side, and the focus ring is a primary outline inside the tab. Server HTML and pages without JavaScript show a static indicator under the active tab; once mounted, the list carries `data-indicator="ready"` and the sliding one takes over, with no hydration mismatch. `TabsTrigger` gains `@nxgt/material`'s icon: an `icon` prop (a component) or an `#icon` slot, before the label, which now renders in a `<span>`. `Tabs` always sets `data-variant` (`underline` when none is given). `TabsContent` is `w-full`, as in React. `CodeGroup` and the emoji lists use `underline` (the emoji list was `bar`, React's `line`: both libraries now match).
|
|
8
|
+
|
|
9
|
+
The indicator jumps instead of sliding under `prefers-reduced-motion: reduce`, and `dir="rtl"` runs the tabs and the indicator from the right. The `segmented` track scrolls sideways on a phone too, rather than widening the page. The emoji list's icon-only group tabs are named (`aria-label` and `title`, from the new `material.emoji.groups.*` keys, in English and French); an icon-only `TabsTrigger` of your own needs an `aria-label` the same way.
|
|
10
|
+
|
|
11
|
+
`TabsList` accepts `asChild` (it threw in React with two children; the sliding indicator now sits inside the child, and in Vue a list with `asChild` keeps the static indicator), and the indicator follows a tab inserted or removed before the active one.
|
|
12
|
+
|
|
13
|
+
**Deprecated:** `variant="bar"` and `variant="line"`. They still type-check and render, as `underline` and `segmented`; write those instead.
|
|
14
|
+
|
|
15
|
+
**Visible change:** a `Tabs` with no `variant`, or with `variant="bar"`, no longer renders one muted pill with the active trigger raised; it renders `underline`. `variant="line"` renders `segmented` (a rounded pill, not the muted strip).
|
|
16
|
+
|
|
17
|
+
### Migration
|
|
18
|
+
|
|
19
|
+
- Search for `variant="bar"` and replace it with `variant="underline"` (or remove it: `underline` is the default). Search for `variant="line"` and replace it with `variant="segmented"`. Search for `variant: 'bar'` / `variant: 'line'` in objects and typed props the same way.
|
|
20
|
+
- The old `bar` look (a muted pill, the active trigger raised) is gone. The closest is `segmented`, a neutral pill whose active tab sits on a raised surface; set `variant="segmented"` explicitly to keep a pill.
|
|
21
|
+
- Tests that queried the old classes must change: `bg-muted`, `h-9`, `rounded-lg` on the list, `bg-background`, `shadow-sm` on the active trigger, `w-full` or `bg-muted/60` on the `line` list. Query the state instead: `getByRole('tab', { selected: true })`, or `toHaveAttribute('data-state', 'active')`, and `data-variant` on `[data-slot="tabs"]` for the look (now always set; it holds the variant as written).
|
|
22
|
+
- A trigger's icon can move from the default slot to `:icon="User"` or `<template #icon>`; one left in the default slot still renders, beside the label.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- [#470](https://github.com/softistx/nxgt-material/pull/470) [`853d093`](https://github.com/softistx/nxgt-material/commit/853d093f0620dc6940ea1ce69e025b61e3b0094d) Thanks [@SteveGT96](https://github.com/SteveGT96)! - The roadmap names the release that ships the two Tabs looks and the emoji groups.
|
|
27
|
+
- Updated dependencies [[`5f984fa`](https://github.com/softistx/nxgt-material/commit/5f984fa95a2944e6c71acd81d0272730d87c2c0a), [`853d093`](https://github.com/softistx/nxgt-material/commit/853d093f0620dc6940ea1ce69e025b61e3b0094d)]:
|
|
28
|
+
- @nxgt/material-core@0.6.0
|
|
29
|
+
|
|
30
|
+
## 0.27.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- [#441](https://github.com/softistx/nxgt-material/pull/441) [`832e0e9`](https://github.com/softistx/nxgt-material/commit/832e0e90d828f13db132bf374cf87a316345565f) Thanks [@SteveGT96](https://github.com/SteveGT96)! - Every day of a `Calendar` (and so of `DateField`, `DateFormField`, `DateRangeField` and the date picker of `EventCalendar`) is named by its full localized date and nothing else: "Thursday, October 1, 2026", "jeudi 1 octobre 2026", on the button and on the `gridcell` around it. The button was already named that way in English; the cell was named by its text, "1", by tools that compute a name from content. The chosen day keeps `aria-selected="true"` on its cell and its button is now described by one hidden, translated "Selected" (`material.calendar.selected`); today carries `aria-current="date"` on its cell and its button. The days of the neighbouring months are named by their own dates, so a bare number matches no day. `DateRangeField`'s calendar does the same.
|
|
35
|
+
|
|
36
|
+
`DateField` and `DateRangeField` follow the `I18nProvider` language (they take no `locale`); only `Calendar` takes a `locale` prop. For a `Calendar` the language is the `locale` prop, else the active `I18nProvider` language (following it when it is switched), else English, and it drives the day names, the heading and the weekday row alike: a `Calendar` under a French `I18nProvider` now shows French month names (pass `locale="en"` to keep English). A test that looked for a day by its number alone must use the date: `getByRole('gridcell', { name: 'Thursday, October 1, 2026', exact: true })` or `getByRole('button', { name: 'Thursday, October 1, 2026', exact: true })`, and check `aria-selected` / `aria-current` on the cell. Compute the name with `Intl.DateTimeFormat('en', { dateStyle: 'full' })` for today's.
|
|
37
|
+
|
|
38
|
+
- [#453](https://github.com/softistx/nxgt-material/pull/453) [`0ad04c8`](https://github.com/softistx/nxgt-material/commit/0ad04c8c013e48761abc70aaed10efaf79001159) Thanks [@SteveGT96](https://github.com/SteveGT96)! - More contrast fixes in `styles.css` and the components. The light `--muted-foreground` is darker (`oklch(0.52 ...)`): muted text reaches 5.5:1 on `--background`, 5.1:1 on the `bg-background-paper` tint and 5.0:1 on `--muted` (it was 4.8, 4.4 and 4.3:1). The dark `--muted` was the light value, a near-white surface on a dark page (`bg-muted`: `Alert variant="foreground"`, code, the default `Timeline` marker...); it is now a dark surface, `oklch(0.279 0.041 260.031)`, and `--muted-foreground` reads at 4.7:1 on it. If your app set its own dark `--muted`, check it against `--muted-foreground` at 4.5:1.
|
|
39
|
+
|
|
40
|
+
In the dark theme, the filled `secondary`, `success`, `info` and `warning` `Button` and `IconButton` are tinted like the `Badge` (`dark:bg-secondary/70`, `dark:bg-success/60`..., hover, focus and pressed further down): their near-white text keeps 5.2, 7.3, 5.7 and 7.5:1 on `--background` and 4.9, 6.7, 5.3 and 7.0:1 on `--card` (it was 4.3, 3.5, 2.5 and 3.4:1).
|
|
41
|
+
|
|
42
|
+
In the light theme, the filled `primary`, `secondary`, `success`, `info`, `warning` and `error` `Button` and `IconButton` no longer fade when hovered, focused or pressed (`hover:bg-primary/90`, `focus:bg-primary/80` and `active:bg-primary/70` let the white page through: down to 3.1:1 pressed, 3.75:1 focused): they darken instead (`hover:bg-[color-mix(in_oklab,var(--color-primary)_90%,black)]`, 85% focused, 80% pressed), so their text keeps at least 4.5:1 in every state, in both themes. The tonal variant's focus tint is lighter (`focus:bg-*/10`, was `/50`: 2.7:1 for `secondary`), the outlined one's `focus:bg-*/15` (was `/20`), and the outlined hover keeps the text colour (`hover:text-*/90` is gone). A `className`/`class` of your own that sets those states back to `/90`, `/80` or `/70` brings the low contrast back: search for `hover:bg-<color>/90`, `focus:bg-<color>/80` and `active:bg-<color>/70` on a filled button and drop them.
|
|
43
|
+
|
|
44
|
+
New `--track` and `--track-fill` tokens (`bg-track`, `bg-track-fill`, `border-track-fill`) for non-text parts that `bg-muted` drew: the `Slider` track, range and thumb border, the password strength meter's empty bars and the `Drawer` handle. In the dark theme the track reaches 3.6:1 on `--background` and 3.2:1 on `--card` (WCAG 1.4.11; `bg-muted` gave 1.4 and 1.2:1) and the fill 3.4:1 on the track; the light theme looks as before. A track, bar, handle or divider of your own drawn with `bg-muted` is near invisible in the dark theme now: draw it with `bg-track`.
|
|
45
|
+
|
|
46
|
+
A library toast's text is no longer faded in with its wrapper (sonner starts it at opacity 0, which an accessibility audit reports as low contrast while it enters): it slides and scales in at full opacity. `toast()` and `toasts.*` render through vue-sonner's `toast.custom` (`data-styled="false"`); a toast vue-sonner styles itself still fades in: a plain `toast('…')` from `vue-sonner`, `sonnerToast`, and for now the toasts of `Notifications` with `toast-on-arrival` and the messages of `AttributesField`. The `StxPlayer` captions menu is a radio group (`menuitemradio`, the chosen entry `aria-checked`), like its speed menu, instead of items marked by a background only. Pressing `c` before choosing a caption language turns on the track's first one, and the menu checks it (it showed every track while "Off" stayed checked). The `focus:bg-muted0` typo on the filled `Button` and `IconButton` is `focus:bg-muted`.
|
|
47
|
+
|
|
48
|
+
`styles.css` now imports its colour tokens from `components/theme-tokens.css` and its base layer from `components/base.css` and `components/base-content.css`, shipped beside it; it has the same rules, in the same order. Nothing to change in an app that imports `styles.css`; an app that copied the file instead of importing it finds the `:root` and `.dark` tokens in `theme-tokens.css`.
|
|
49
|
+
|
|
50
|
+
**Migration, for an app that overrides the theme's tokens** (search its stylesheets for each name under `:root` and under `.dark` / `[data-theme="dark"]`):
|
|
51
|
+
|
|
52
|
+
- A dark `--secondary` of your own: the filled `secondary` `Button`, `IconButton` and `Badge` now draw it at 70 % in the dark theme under near-white text. Keep your value bright enough to read as text on `--background` and `--card` (4.5:1: the `tonal`, `outlined` and `link` secondary variants), and check the filled button's near-white text on it at 70 %. A darker value, picked for white text at 100 %, now fails as text on the dark page and looks dim at 70 %: raise its lightness (the shipped one is `oklch(0.66 0.1135 245.65)`).
|
|
53
|
+
- A light `--muted-foreground` of your own: the shipped one is now `oklch(0.52 0.046 257.417)`. Check yours at 4.5:1 on `--background`, the paper tint and `--muted`, or delete the override.
|
|
54
|
+
- A dark `--muted` of your own: it was a near-white value in the shipped theme and is now a dark surface (`oklch(0.279 0.041 260.031)`). If yours is light, `Alert variant="foreground"`, code and the `Timeline` marker draw near-white blocks on the dark page: replace it with a dark surface that keeps `--muted-foreground` at 4.5:1.
|
|
55
|
+
- New `--track` and `--track-fill`: if you restyled the `Slider`, the password strength meter or the `Drawer` handle by overriding `--muted`, override `--track` (the empty part, 3:1 on `--background` and `--card`) and `--track-fill` (the range and thumb border, 3:1 on the track) instead. Your own tracks, bars, handles and dividers drawn with `bg-muted`: replace `bg-muted` with `bg-track` (and a fill with `bg-track-fill`).
|
|
56
|
+
|
|
57
|
+
The filled `default` `IconButton` drew its icon in the page foreground on a foreground background (invisible); it now draws it in `background`. The `link` `IconButton` took the primary colour for every `color`; each colour now uses its own text colour (in the light theme `warning` and `error`, in the dark theme `primary`, `success`, `warning` and `error`, are mixed toward black or white to keep 4.5:1 in every state; the `Button` link variant uses the same tones). The `DialogScrollContent` close button now hovers on `bg-accent` (it was `bg-secondary`). The `ButtonGroup`'s active item darkens on hover like the filled `Button`.
|
|
58
|
+
|
|
59
|
+
- [#459](https://github.com/softistx/nxgt-material/pull/459) [`9ca2a84`](https://github.com/softistx/nxgt-material/commit/9ca2a847b846c996502978275cf68ef253408e80) Thanks [@SteveGT96](https://github.com/SteveGT96)! - The `tonal` and `outlined` variants of `Button` and `IconButton` read at 4.5:1 or more in every colour, in both themes, on `--background` and `--card`, at rest, hovered, focused and pressed (WCAG 1.4.3). They drew their text and icon in the token itself, which some tokens do not reach as text: in the dark theme `primary` gave 2.5:1 on `--background` and 2.2:1 on `--card` (the dark `--primary` is the same dark indigo as the light one), `error` 3.9 and 3.5:1, `success` 4.0:1 on `--card`; in the light theme `error` gave 3.6:1 on its tonal tint (4.0 hovered, 3.6 outlined and focused) and `warning` 4.3:1. They now take the `link` variant's tones, mixed from the tokens: in the light theme `warning` and `error` mixed with 15 % black (now 6.0 and 5.4:1), in the dark theme `primary` with 60 % white (8.1:1 on `--card`), `success` and `warning` with 15 % white (5.0 and 5.4:1) and `error` with 30 % white (5.9:1); the other colours keep the token (light `primary` 5.8, `secondary` 4.8, `info` 4.9, `success` 4.7:1; dark `secondary` 4.6, `info` 5.3:1). The tones live in one place (`button-tones.ts`) shared by the three variants and by `IconButton`'s icon line.
|
|
60
|
+
|
|
61
|
+
A `Badge` used as a link (`as-child` around an `<a>`) no longer fades on hover (`[a&]:hover:bg-*/90` let the page through: 4.3:1 for `error` in the light theme, 3.0:1 for `info` and 3.5:1 for `secondary` in the dark one): it darkens like a filled button, `[a&]:hover:bg-[color-mix(in_oklab,var(--color-*)_90%,black)]`, and in the dark theme its tint drops by ten points (`dark:[a&]:hover:bg-secondary/60`, `/50` for `success`, `info`, `warning` and `error` (and the `destructive` one)); every colour now keeps 5.9:1 or more hovered.
|
|
62
|
+
|
|
63
|
+
A selected `ListTile` draws its subtitle in `text-foreground/70`: the muted colour gave 4.4:1 on the selected tint, it is now 7.0:1 (8.9:1 in the dark theme).
|
|
64
|
+
|
|
65
|
+
The light `--error` is a shade darker, `oklch(0.56 0.245 27.325)` (was `0.577`): a filled `error` `Button`, `IconButton` or `Badge` gives its near-white text 4.8:1 (was 4.6:1), and the colour as text 5.0:1 on white. The dark `--error` is unchanged. The light `--destructive` (the deprecated alias of `--error`) is now `var(--error)`, so invalid borders and rings drawn with it match.
|
|
66
|
+
|
|
67
|
+
`Guides/Theming/Contrast` now measures `Button` and `IconButton` in every variant and colour and a `Badge` link in every colour, in all four states and both themes; `Data display/ListTile` measures its tiles, the selected one included.
|
|
68
|
+
|
|
69
|
+
**Migration, for an app that overrides tokens or classes:**
|
|
70
|
+
|
|
71
|
+
- A text colour of your own on a `tonal`, `outlined` or `link` button (`class="text-primary"`, `text-error`…) replaces the tone and brings the low contrast back: drop it, or check it at 4.5:1 on `--background`, `--card` and the variant's 15 % tint, in both themes.
|
|
72
|
+
- An override of `--primary`, `--success`, `--warning` or `--error` moves its tone with it (the tones are `color-mix` of the token). A dark `--primary` of your own that is already light enough to read as text now reads lighter still (mixed with 60 % white); a light `--warning` or `--error` of your own is drawn 15 % darker as text. Check both against the page.
|
|
73
|
+
- A light `--error` of your own: the shipped one is now `oklch(0.56 0.245 27.325)`. Keep yours or delete the override; check near-white text on it at 4.5:1.
|
|
74
|
+
- A `Badge` link given a hover fade such as `[a&]:hover:bg-error/90` in its `class`: replace it with a darkening, `[a&]:hover:bg-[color-mix(in_oklab,var(--color-error)_90%,black)]` (with the badge's colour in place of `error`).
|
|
75
|
+
- A subtitle colour of your own on a selected `ListTile`: check it at 4.5:1 on `bg-primary/15`.
|
|
76
|
+
|
|
77
|
+
- [#448](https://github.com/softistx/nxgt-material/pull/448) [`e1a4059`](https://github.com/softistx/nxgt-material/commit/e1a405978ae92c4f5720b864b4f42a513a3b959f) Thanks [@SteveGT96](https://github.com/SteveGT96)! - Text and icons now reach WCAG contrast, and animations honour `prefers-reduced-motion`. In `styles.css`, the light `--secondary`, `--success`, `--info` and `--warning` are darker, so a filled `Badge` or `Button` keeps 4.5:1 with its near-white text (5.7, 5.6, 5.8 and 5.3:1; it was 4.3, 3.5, 2.5 and 3.4:1) and the colour read as text (an `outlined` or `link` button, a link) 5.5:1 or more on the page, at rest. The dark theme keeps bright values, read as text on the dark surfaces: its `--secondary` is brighter, `oklch(0.66 0.1135 245.65)` (6.5:1 on `--background`, 5.8:1 on `--card`; a `tonal` secondary `Button` 5.4 and 4.6:1), and the filled surfaces under near-white text are tinted, the dark `secondary` `Badge` at 70 % (5.2:1 on `--background`, 4.9:1 on `--card`), `success`, `info` and `warning` at 60 % (7.3, 5.7 and 7.5:1); the dark `--muted-foreground` is brighter, `oklch(0.66 ...)` (6.4:1 on `--background`, 5.7:1 on `--card`; it was 4.2 and 3.7:1). The toast's X button has a visible focus ring and no colour transition, and the toast description reads at 4.5:1; the muted shortcut text of a highlighted dropdown, context or menubar entry, and the `LanguageSwitcher` code, switch to `--accent-foreground`. One `@media (prefers-reduced-motion: reduce)` rule, in `components/reduced-motion.css` imported by `styles.css`, turns off the transitions and animations of every catalogue component (dialogs, sheets, menus, toasts, buttons); the `Spinner` and `animate-spin` elements keep turning. If your app overrides those tokens, check them against 4.5:1; to keep one animation for everyone, override it with your own `!important` rule on that element inside the same media query (the rule is unlayered with a specificity of 0, so yours wins). See the theming guide's "Contrast and reduced motion".
|
|
78
|
+
|
|
79
|
+
- [#463](https://github.com/softistx/nxgt-material/pull/463) [`e3b10d6`](https://github.com/softistx/nxgt-material/commit/e3b10d64b55867eb320472ee0185b24d9dea81da) Thanks [@SteveGT96](https://github.com/SteveGT96)! - New `--error-text` token (`--color-error-text` in `@theme`, so `text-error-text`) for error drawn as text. In the light theme it is `color-mix(in oklab, var(--error) 94%, black)`, 5.7:1 on white and 4.8:1 on the `bg-error/10` tint of a focused error menu item (every light menu uses that one tint; `--error` itself gives 5.0 and 4.2:1); in the dark theme it is `color-mix(in oklab, var(--error) 80%, white)`, 5.9:1 on `--background` and 5.3:1 on `--card`, where `--error` as text gave 4.18 and 3.75:1. The dark `--error` itself is unchanged: it stays dark enough for near-white text on a filled error surface.
|
|
80
|
+
|
|
81
|
+
The catalogue's error text now uses it, so it is lighter in the dark theme: `HelperText` in its error state (and so every field's error message), the error of `ConfirmDialog` and of a `ConfirmationDialog` form, the PDF reader's password form, `ImageCropper`, the number-range filter, the chat composer and a failed chat message, a network upload's failed item (its icon too), `StxPlayerMini`, an `OtpField` in error, a textarea's counter over its limit, the error lines of `Terminal`, `ParamTable`'s required mark, the falling values of `StatCard` and the metric cards, and `variant="error"` menu items (`DropdownMenu`, `ContextMenu`, `Menubar`). Fills, borders, rings, the `Button` and `Badge` tones, the `Banner` (its icon only takes the colour; its text is foreground) and standalone icons (the `Alert` and toast icons, `EmptyState`, `Timeline` markers, `RatingField`) keep `--error`.
|
|
82
|
+
|
|
83
|
+
**Migration** (only text changes: lighter in the dark theme, slightly darker in the light one; nothing breaks):
|
|
84
|
+
|
|
85
|
+
- Search the app for `text-error` not followed by `-` (for example `rg '\btext-error($|[^-])'`). Where it colours **text** on a page, card or popover surface (a validation message, an inline error, a red value), and an icon drawn beside such text, replace it with `text-error-text`: `<p class="text-sm text-error">` becomes `<p class="text-sm text-error-text">`, except on a `Button`/`IconButton`, where the tone already colours it: drop the class instead (see the contrast note on button text colours). Leave it on fills, borders, rings and icons that stand alone, and leave `text-error-foreground` (text on a filled error surface) as it is. Check by hand the arbitrary forms the search cannot see: `text-(--color-error)`, `text-[var(--error)]`, and `color: var(--error)` / `var(--color-error)` in CSS.
|
|
86
|
+
- An app that overrides `--error` under `:root` and `.dark, [data-theme="dark"]`: `--error-text` follows it (the 94 % mix with black in light, the 80 % mix with white in dark). Check the dark mix at 4.5:1 on `--background` and `--card`, or set `--error-text` yourself beside your `--error`. Below the root, setting `--error` or `--error-text` on an element changes nothing (`@theme` resolves `--color-error-text` on the root): set `--color-error-text` (and `--color-error` for fills) on that element, or put the override on the root.
|
|
87
|
+
|
|
88
|
+
- [#450](https://github.com/softistx/nxgt-material/pull/450) [`8e568c3`](https://github.com/softistx/nxgt-material/commit/8e568c3544327c1ba273482963ec13e1930d3be6) Thanks [@SteveGT96](https://github.com/SteveGT96)! - A failed submit can focus the first invalid field. vee-validate never moves focus (there is no `shouldFocusError`: `handleSubmit(onValid, onInvalid)` only reports `{ errors }`), so every `*FormField` now marks the element it renders with `data-field-name="<name>"` (through `useFieldBlur`, which stamps the wrapper's `name` prop), and two new helpers from `@nxgt/material-vue/lib` use it: `focusFirstInvalid(errors, scope?)` focuses the first control a user can reach in the first field, in page order, that has an error, and `focusField(name, scope?)` focuses one field, for a server error. Both return whether a control took focus.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const onSubmit = handleSubmit(save, ({ errors }) => focusFirstInvalid(errors));
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A story (`Guides/Forms/Focus on error`) runs it for every wrapper, whatever its shape (an input, a select trigger, chips, a date range, an upload, an editor).
|
|
95
|
+
|
|
96
|
+
`SelectField` (and `SelectFormField`) describe their trigger by the help or error text (`aria-describedby`) and mark it `aria-invalid` on error, so `toHaveAccessibleDescription` and `toBeInvalid` match; the name still comes from the label.
|
|
97
|
+
|
|
98
|
+
`TextField` (and `TextFormField`) describes its input by the help or error text too (`aria-describedby`, after any `aria-describedby` you pass), so a screen reader reads the validation message with the field and `toHaveAccessibleDescription` matches.
|
|
99
|
+
|
|
100
|
+
`SelectChipField` (and `SelectChipFormField`): the chips are toggle buttons, so a chosen one is `aria-pressed="true"` instead of `aria-selected="true"` (which is not allowed on a button), in single mode too since pressing the chosen chip clears it, and the chips sit in a `role="group"` named by the label and described by the help text. A test that matched a chip by `aria-selected`, or by `getByRole('option')`, must query `getByRole('button', { name })` and check `aria-pressed`; to find the group, `getByRole('group', { name: <label> })`. `SelectCardField` is unchanged.
|
|
101
|
+
|
|
102
|
+
- [#449](https://github.com/softistx/nxgt-material/pull/449) [`907bd37`](https://github.com/softistx/nxgt-material/commit/907bd37132e81529c66d4a6312512bf7a9f66c3a) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `ActivityLayout` follows the page instead of fighting it, on desktops and phones:
|
|
103
|
+
|
|
104
|
+
- **Size and scrolling.** It was `100lvw × 100lvh` and scrolled its content inside an absolutely positioned box: the page was a scrollbar's width too wide and, on a phone, the bottom of the content sat under the browser's toolbar. It is now `100%` wide and at least `100dvh` high, and **the document scrolls**; the header and the desktop sidebar are sticky. A screen that needs the old in-shell scrolling (a full-height map, a board) passes the new `scroll="container"`. Migration: a test or script that scrolled the shell's inner `.overflow-auto` box scrolls the window instead; a screen sized `h-full` to fill the shell adds `scroll="container"` to its `ActivityLayout`.
|
|
105
|
+
- **A new screen starts at the top with `scroll="container"`.** The content region outlives navigations, so a new page used to open at the previous page's scroll position, and Back did not restore it (the router's `scrollBehavior` only handles the window). The region now starts each new path at the top (a change of query or hash only — a filter, a tab, an `#anchor` — keeps the position), and Back or Forward returns to where that entry was left. With the default document scrolling, the router's own restoration applies.
|
|
106
|
+
- **One `<main>`.** The content region is now the page's `<main>` landmark (`getByRole('main')`). An app that already renders a `<main>` around the shell passes the new `content-as="div"`.
|
|
107
|
+
- **`class` reaches the outer element** (`[data-slot=activity-layout]`), with the other attributes, as in `@nxgt/material`; it used to land on the inner box. Migration: search for `<ActivityLayout` with a `class` and check the classes were meant for the whole shell.
|
|
108
|
+
- **The phone menu sheet** is a dialog named "Navigation" (new key `material.app-shell.navigation`, "Navigation" in English and French); it was named "Open menu", like its button. Following an entry, or any route change, closes it and moves focus to `<main>`; Escape, the close button and the overlay still return focus to the menu button. Its entries show their labels, so no tooltip opens over the first one as the sheet takes focus (the collapsed desktop rail keeps its tooltips). Migration: `getByRole('dialog', { name: 'Open menu' })` becomes `getByRole('dialog', { name: 'Navigation' })`.
|
|
109
|
+
|
|
110
|
+
`ActivityContent`'s `title` is now a heading, `h1` by default (it was a `<p>`) — a screen's title is the page's top heading, and the shell renders none — with a new `title-as` (`'h1'` … `'h6'`, or `'div'` for no heading). The header card wraps, so the actions go under the title on a narrow screen. Migration: a test can find it with `getByRole('heading', { level: 1, name: 'Invoices' })`; a screen nested under a heading of its own passes `title-as="h2"`.
|
|
111
|
+
|
|
112
|
+
`CardTitle` (already an `h3`) takes a new `as` (`'h2'` … `'h6'`, or `'div'`); `CardTitleProps` and `CardTitleElement` are exported.
|
|
113
|
+
|
|
114
|
+
`ActivityContentTitleElement` is exported.
|
|
115
|
+
|
|
116
|
+
`Typography`'s and `HelperText`'s misspelled `data-test="typogaphy"` is now `data-test="typography"`. Migration: search for `typogaphy` in tests and selectors and replace it with `typography`.
|
|
117
|
+
|
|
118
|
+
`AppHeader` and `AppSidebar` used on their own work without Vue Router, given a `link-component` of your own: no entry is then marked active, and the menu sheet closes only when an entry is followed. With a router, the sheet also closes on every route change.
|
|
119
|
+
|
|
120
|
+
`Background` is sized `w-lvw h-lvh` instead of the custom `size-lv`, which tailwind-merge did not know: a `class` such as `h-96 w-full` or `size-auto` now overrides the viewport size, as the docs said — before, `size-lv` won, which also kept `ActivityLayout` one screen high. Its default look, and `HydrateLoading`'s and `ThreadLayout`'s, are unchanged. The `size-lv` utility is still defined.
|
|
121
|
+
|
|
122
|
+
- [#437](https://github.com/softistx/nxgt-material/pull/437) [`679cc06`](https://github.com/softistx/nxgt-material/commit/679cc06a184f401c34e770ea2acfc12bfd10d24d) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `ActivityLayout`, `AppSidebar` and `AppHeader` menu entries (`MenuItem` with a `url`) now render as real links, `<a href>`, instead of buttons that called `router.push()`. They work on a server-rendered page before hydration, open in a new tab on middle-click or ctrl-click, are announced as links, and the current page carries `aria-current="page"`. By default each is a `RouterLink`, so client-side navigation behaves as before. New optional `linkComponent` prop on `ActivityLayout`, `AppHeader` and `AppSidebar` (a component taking `href`) renders another link; `SidebarMenuItem` also takes `href` and `link-component` to render a link instead of a button. A test or selector that looked for a `button` role on a sidebar entry should look for `link`. The icon inside an entry is decorative (`aria-hidden`), so a link is named by its label alone (`getByRole('link', { name: 'Identities' })`), and an entry shows a focus ring for the keyboard. The link's root must take the attributes Vue falls through to it.
|
|
123
|
+
|
|
124
|
+
- [#454](https://github.com/softistx/nxgt-material/pull/454) [`6b3b904`](https://github.com/softistx/nxgt-material/commit/6b3b904b9d2a281f9680da870a229fafdd3a4704) Thanks [@SteveGT96](https://github.com/SteveGT96)! - A loading `Button` stays focusable and blocks submits, as `@nxgt/material`'s does: `loading` now sets `aria-busy="true"` and `aria-disabled="true"` instead of the native `disabled` (which dropped focus to `<body>` while the form worked), and cancels every click before your `@click` (an `as-child` child's own `@click` and a clickable ancestor's run neither) — the click that Enter in a field sends to the form's submit button included — so a double click or Enter pressed twice submits once. New `lock-on-click`: a click makes the button busy before `loading` has rendered, until `loading` has turned true and back (or for one second when it never does), for a `loading` that turns true only after an `await`. The recommended wiring is `:loading="isSubmitting"` from `useForm`. With `as-child`, the busy attributes, the click guard and the spinner now land on the child element (the spinner used to take them, rendered beside it), and the child stays the same element while `loading` turns on and off — it keeps focus, your template ref and its directives. Its own attributes (`type`, `disabled`, `aria-*`, `data-slot`) win over the button's, as with any reka `as-child` (except `aria-busy` and `aria-disabled` while busy, where the button wins), its `class`, `style` and listeners are merged with the button's, and an idle component child keeps its own slot content, fallback included. Busy buttons show a progress cursor. Migration: a test that expected a loading button to be `disabled` (`toBeDisabled()`, `[disabled]`), or skipped by Tab, checks `toHaveAttribute('aria-busy', 'true')` instead; a `disabled` you pass still sets the native attribute.
|
|
125
|
+
|
|
126
|
+
- [#447](https://github.com/softistx/nxgt-material/pull/447) [`5bafe79`](https://github.com/softistx/nxgt-material/commit/5bafe79c92c2ec65b9be33b603c38d79123173ce) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `DataTable` works on a phone (WCAG 2.2 AA). **Breaking defaults** — search your code for each and act as said:
|
|
127
|
+
|
|
128
|
+
- **No height cap by default.** The table no longer scrolls inside `max-h-[400px]` on `[data-slot="table-wrapper"]`, and the stylesheet no longer caps `[data-slot="data-table"]` and `[data-slot="table-wrapper"]` to viewport-based heights (`max-h-[calc(100lvh-184px)]`, `max-h-[calc(100vh-432px)]`, …): on a phone they made a scroll inside the page's. The table is as tall as its rows. Where a table should scroll inside a box, pass the new `max-height` prop (a number of pixels or a CSS length): `<DataTable :max-height="400" … />`.
|
|
129
|
+
- **The header row shows at every width.** Below 768px it used to be hidden, which lost the column names, sorting and "Select all". Pass `:show-header="false"` to hide it (or `:show-header="!isPhone"` for the old behaviour).
|
|
130
|
+
- **The toolbar renders only when it has content.** An `actions` prop or `#actions` slot that renders nothing (`() => null`, a `v-if` left out) no longer leaves an empty `<fieldset data-slot="header">`; look for what the toolbar holds, not for the fieldset.
|
|
131
|
+
- **A reserved toolbar band with selection actions.** A table with `#actions` and the `select` column now shows the toolbar's height (68px) above the rows before anything is selected, as an empty `aria-hidden` `[data-slot="header-placeholder"]`, so the first selection no longer pushes the rows down. Expect screenshot and visual tests of such tables to change.
|
|
132
|
+
|
|
133
|
+
New: while its columns overflow, the table scrolls sideways in one box (`[data-slot="table-wrapper"]`; the `Table` primitive's own container no longer scrolls inside it), which is then a keyboard-focusable `region` (`tabindex="0"`) named "Scrollable table" (new key `material.table.labels.scroll-region`, or the new `scroll-label` prop), with an edge fade on each side where columns are hidden (`data-scroll-start` / `data-scroll-end` on `[data-slot="table-scroll"]`). A test that used a bare `getByRole('region')` on a page with a wide table must query by name. The empty state is centred within the visible width instead of across the whole scroll width, and the `select` column's checkboxes keep their 16px box with a 24px target around it (WCAG 2.5.8).
|
|
134
|
+
|
|
135
|
+
With `max-height`, the header row sticks to the top of the box, on an opaque background, while the rows scroll under it. Selection actions no longer push the rows down: with `actions` or the `#actions` slot and the `select` column, the toolbar's height (one row of default-size buttons) is reserved, as an empty `aria-hidden` `[data-slot="header-placeholder"]` while they render nothing, so the button that appears on the first selection does not move the tapped row. Once focused, the scroll region stays a tab stop until focus leaves.
|
|
136
|
+
|
|
137
|
+
- [#461](https://github.com/softistx/nxgt-material/pull/461) [`c6cc00a`](https://github.com/softistx/nxgt-material/commit/c6cc00a83a866c632aac043e44e46259272a31bb) Thanks [@SteveGT96](https://github.com/SteveGT96)! - The page behind a modal layer is now `inert`, not only `aria-hidden`: while a `Dialog` (and `DialogScrollContent`, `CommandDialog`), `AlertDialog`, `Sheet` or `Drawer` is open, or a `Select` list, a `ContextMenu`, or a `DropdownMenu` or `Popover` given `modal`, the page's controls leave the tab order and the accessibility tree, and axe's `aria-hidden-focus` ("ARIA hidden element must not be focusable or contain focusable elements") no longer fails on them. The `Toaster`'s live region stays reachable: a toast shown over a dialog is still announced and its action usable. Nested layers stack: a `Select` open in a `Dialog` makes the dialog inert until its list closes. Non-modal layers (`:modal="false"`, and `DropdownMenu` and `Popover` by default) are unchanged.
|
|
138
|
+
|
|
139
|
+
A minor rather than a patch because three things a consumer can see change:
|
|
140
|
+
|
|
141
|
+
- An `@open-auto-focus` that calls `event.preventDefault()` on a modal `DialogContent`, `AlertDialogContent`, `SheetContent` or `DrawerContent` and focuses nothing no longer leaves focus on the element that opened it: the opener is inert, so focus goes to the layer's content. Search for `@open-auto-focus="(event) => event.preventDefault()"`: focus the element you want inside the layer in that handler, or make the layer non-modal (`:modal="false"` on `Dialog`, `Sheet` or `Drawer`) to keep focus behind it.
|
|
142
|
+
- A test or script that reaches the page behind an open modal layer — `getByRole(…, { hidden: true })`, `element.focus()` on it, a Playwright locator outside the layer — now finds elements that are `inert` (they carry `inert` and `data-nxgt-inert`): close the layer first, or query inside it.
|
|
143
|
+
- Only `[aria-live]` regions (and scripts) stay reachable behind the layer, the same set `aria-hidden` keeps. An element with `role="status"`, `"alert"` or `"log"` but no `aria-live` — a `Banner`, for example — is inert while a modal layer is open: it is not announced and its controls cannot be reached. To keep one reachable, give it `aria-live` (`polite` for a status), or show it inside the layer.
|
|
144
|
+
|
|
145
|
+
- [#455](https://github.com/softistx/nxgt-material/pull/455) [`670de59`](https://github.com/softistx/nxgt-material/commit/670de597d2115c613855db9d1fd5cf2c64ddbc53) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `ListTile` takes an optional `link-component` prop, with the app shell's contract (`AppShellLinkComponent`: a component that takes `href` and lets the other attributes fall through to its root), so a tile with an `href` can navigate on the client through a `RouterLink` wrapper instead of reloading the page. The default stays a plain `<a>`, which needs no router; the classes, `aria-current` and `data-slot="item-detail"` are unchanged. `Alert` now carries `data-slot="alert"` on its root, `data-slot="alert-title"` on the title and `data-slot="alert-description"` on the description.
|
|
146
|
+
|
|
147
|
+
- [#446](https://github.com/softistx/nxgt-material/pull/446) [`6d793fc`](https://github.com/softistx/nxgt-material/commit/6d793fc07326025e3bb31e17477b53aca0d7dc52) Thanks [@SteveGT96](https://github.com/SteveGT96)! - Four accessibility fixes found by an application's audit.
|
|
148
|
+
|
|
149
|
+
- **`DropdownMenu` (and `LanguageSwitcher`) is non-modal by default.** Reka's modal menu marks the rest of the page `aria-hidden` while its buttons and links stay focusable, which axe reports as `aria-hidden-focus`. Now nothing is hidden, and the menu still closes on Escape, an outside click or Tab. Pass `modal` to `DropdownMenu` to get the old behaviour back (scroll lock, outside clicks swallowed). If you relied on a click outside the menu being swallowed, add `modal`.
|
|
150
|
+
- **`ResponsiveButton` / `ResponsiveLinkButton` collapse with CSS, not a JS effect.** The label is always in the DOM and hidden below 768 px with a `max-md` media query, so the first paint is right (no flash of the full label on a phone, no hydration mismatch) and the accessible name is the same at every width. `expanded` still forces one state at every width. A test that fakes a mobile `matchMedia` to collapse the button should pass `:expanded="false"`.
|
|
151
|
+
- **`Description` no longer spans the whole row, and its label is a label.** It had `col-start-full`, which broke a parent two-column grid; it now takes one cell (add `class="col-start-full"` for a full-width row). The label is small muted text above the value instead of a title with an underline bar, and the value is in the normal text colour.
|
|
152
|
+
- **`CopyButton` waits for the clipboard and announces the outcome.** "Copied" shows only once the write succeeded; a refusal shows "Copy failed" (new `failed-label` prop and `material.copy.failed` string, in English and French). The result is announced through a visually hidden `role="status"` region next to the button instead of renaming the button, whose accessible name stays `Copy`. Tests that looked for a button named `Copied` should read the status next to the button, `within(button.parentElement!).getByRole('status')`; a test that reads another `role="status"` on a page with a `CopyButton` must now scope the query. `@vueuse/core`'s `useClipboard` is no longer used by it. Its root is a `span` with `display: contents` (no box) around the button and the status region, so `v-show`, `Transition` and `$el` work; a `class` still goes to the button.
|
|
153
|
+
|
|
154
|
+
- [#458](https://github.com/softistx/nxgt-material/pull/458) [`f468ba0`](https://github.com/softistx/nxgt-material/commit/f468ba0b2bae04ed8cc629c5a2b0e353dca2e88e) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `Button` is never wider than its container, and a long label is cut with "…" inside it instead of sticking out of a card on a phone (a 349 px button in a 343 px card). The button had `width: max-content` (`w-max`); it now has `width: fit-content; max-width: 100%` (`w-fit max-w-full`), and its bare text is rendered in a `<span data-slot="button-label">`, which shrinks and ends in an ellipsis. Its accessible name, and a `title` you give it, keep the whole label. New prop `wrap`: the label breaks onto as many lines as it needs, centred, and the button grows taller, never shorter than its `size` (ignored by the `icon*` sizes).
|
|
155
|
+
|
|
156
|
+
**Migration:** a `Button` that relied on growing past its container now truncates. Search for `<Button` with a long label in a narrow box (a card, a dialog footer, a table cell) and pass `wrap`, or give it a width of your own (`class="w-full"`). With `as-child`, the text inside the child element is clipped without an ellipsis: put the label in a `<span>` inside it. Code that read the button's first child node as its text (`button.firstChild`) now gets that span: read `textContent`, or find the button by role and name.
|
|
157
|
+
|
|
158
|
+
`OtpField`'s slots shrink with a narrow field and stay square: six fit a 320 px-wide card, each above WCAG 2.5.8's 24 px target, and the gaps narrow under the `sm` breakpoint (the input-otp container and groups take `min-w-0`). At full width they are the same size as before. A slot in error now carries `data-invalid` instead of `aria-invalid` (not allowed on a plain `div`); the input keeps `aria-invalid="true"`. **Migration:** a test that selected `[data-slot="input-otp-slot"][aria-invalid]` asserts `aria-invalid` on the input (`getByLabelText(<label>)`) instead; CSS keyed on a slot's `aria-invalid` uses `[data-invalid]`.
|
|
159
|
+
|
|
160
|
+
**Migration (tests):** `getByText('<label>')` now returns the inner `<span data-slot="button-label">`, not the button. Search for `getByText(<button label>)` followed by an attribute or class assertion (`toBeDisabled`, `toHaveAttribute`, `toHaveClass`) and switch it to `getByRole('button', { name: '<label>' })`. Consecutive text and numbers (`Delete {{ name }}?`) are one span.
|
|
161
|
+
|
|
162
|
+
**Migration (OtpField):** a composed `InputOTPSlot` that is passed `aria-invalid` must be passed `data-invalid` instead.
|
|
163
|
+
|
|
164
|
+
- [#440](https://github.com/softistx/nxgt-material/pull/440) [`4827b01`](https://github.com/softistx/nxgt-material/commit/4827b015f23623f3e303af042bdc85bc43f8847a) Thanks [@SteveGT96](https://github.com/SteveGT96)! - The required `*` of `Label` (`withAsterisk`, so every `required` field) is now drawn by CSS (a mask in the package stylesheet, still visible in forced-colors mode and in print) instead of typed into the label, so it is in neither the label's text nor its accessible name. `getByLabel('First name', { exact: true })` (Playwright) and `getByLabelText('First name')` (Testing Library) now find a required field; before, the label text was "First name *" even though the asterisk was `aria-hidden`. The name was already "First name" for `getByRole`; screen readers still announce "required" from the field's own `required` / `aria-required`. The `SelectField` trigger is now named by its label (`aria-labelledby`), so `getByRole('combobox', { name: 'Framework' })` finds it.
|
|
165
|
+
|
|
166
|
+
Migration: a test that matched the label text with the asterisk (`getByLabelText('Email*')`, `getByLabel('Email *')`, `{ name: /Email \*/ }`) must drop it; check requiredness with `toBeRequired()` or `toHaveAttribute('aria-required', 'true')`; update DOM/HTML snapshots, which no longer contain the mark. The mark needs the package stylesheet (`label.css` is part of it); without it nothing is drawn. Custom CSS that targeted the asterisk `<span class="text-error">` should target `[data-slot="label-text"][data-required]::after` (colour: `background-color`, size: `width` / `height`).
|
|
167
|
+
|
|
168
|
+
- [#439](https://github.com/softistx/nxgt-material/pull/439) [`b4a3914`](https://github.com/softistx/nxgt-material/commit/b4a3914913673385305f5eec8d5d293f0ab64580) Thanks [@SteveGT96](https://github.com/SteveGT96)! - Toasts are announced by screen readers without double reading: the `Toaster`'s `aria-live="polite"` region (already there) announces every toast, so a `Toast` has no `role` by default. New optional `role` (`'status' | 'alert'`) on `toast()` and `Toast`; `toasts.error` sets `role: 'alert'` as a default a caller's `role` overrides. A `Toast` rendered outside a `Toaster` needs its own live region. In tests, find an error toast with `findByRole('alert')` and the others by their text inside `section[aria-live="polite"]`; the message is the text, not the name (`toHaveTextContent`).
|
|
169
|
+
|
|
170
|
+
- [#443](https://github.com/softistx/nxgt-material/pull/443) [`a92b5f6`](https://github.com/softistx/nxgt-material/commit/a92b5f672fccfab16c7390a2047f239629b02642) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `toast()` itself now treats an `action` as persistent: a toast with an `action` stays until dismissed (`options.duration: Infinity`, WCAG 2.2.1) unless you pass `options.duration`, and shows a close (X) icon button named by the translated `material.toast.buttons.close` ("Close" / "Fermer") beside it, unless you pass your own new optional `dismissLabel` (`''` hides the button). `toasts.success`, `info`, `warning` and `error` no longer replace the `action` you pass with their own "Close" button: `toasts.info({ description: 'A new version is available', action: { label: 'Reload', onClick } })` now shows Reload. A function `action` (`(dismiss) => VNode`) is now rendered, as in `@nxgt/material`; before, `Toast` ignored it. Escape closes a toast from inside it, with or without an action, and the X is now drawn in a visible colour on the toast. A toast without an `action` looks and times out as before. In tests, close an action toast with `toasts.dismiss()` or its "Close" button instead of waiting for it to time out.
|
|
171
|
+
|
|
172
|
+
- [#457](https://github.com/softistx/nxgt-material/pull/457) [`78ef976`](https://github.com/softistx/nxgt-material/commit/78ef976d92b4551e7cb8eaf52e254e61c086199f) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `Toaster` (vue-sonner's, wrapped by the library) changes on phones; its props are the same and all forwarded.
|
|
173
|
+
|
|
174
|
+
- **Toasts show at the top on a phone.** Under 600px wide (the width under which vue-sonner lays toasts out across the screen), a `Toaster` with no `position` now shows its toasts at `top-center` instead of across the bottom, where they covered a form's submit button. This is a visible change: to keep the bottom on phones, pass a `position` (`<Toaster position="bottom-right" />`), which applies on every screen. The server and the client's first render answer for a desktop (`bottom-right`, or the width `provideSSRWidth()` gives), so there is no hydration mismatch; a phone moves the toasts to the top once mounted.
|
|
175
|
+
- **A tap no longer pauses a toast for good.** A toast pauses while a mouse hovers it (WCAG 2.2.1, unchanged on a desktop); on a phone, the hover the browser emulates after a tap left the toast paused until the next tap elsewhere. That emulated hover is now ignored (`@nxgt/material-core/touch-hover`): the toast pauses while the finger is down and its timer runs again once lifted. One consequence on a touch screen: a tap no longer fans out a stack of two or more toasts; pass `expand` (`<Toaster expand />`) to keep the stack open.
|
|
176
|
+
|
|
177
|
+
Nothing to change in an app that mounts `<Toaster />` from `@nxgt/material-vue/components`. An app that mounts vue-sonner's own `Toaster` (`import { Toaster } from 'vue-sonner'`) does not get either fix: import it from `@nxgt/material-vue/components` instead.
|
|
178
|
+
|
|
179
|
+
The `ToasterProps` type is exported beside `Toaster`.
|
|
180
|
+
|
|
181
|
+
### Patch Changes
|
|
182
|
+
|
|
183
|
+
- [#438](https://github.com/softistx/nxgt-material/pull/438) [`2aed8de`](https://github.com/softistx/nxgt-material/commit/2aed8de1561275a74a5fbe8fd055576462746b70) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `DataTable`'s server footer announces its range: "41–60 of 133" is now a polite live region (`role="status"`, `aria-atomic="true"`, named "Rows shown" through the new `material.table.pagination.range-label` key, so it is told apart from the table's own "Loading data…" status: `getByRole('status', { name: 'Rows shown' })`) that is in the DOM from the first render, so a screen reader reads the new range when the page changes. A row with `getRowHref` already exposed one link named by its cell text and no extra role on the row; a story now asserts it.
|
|
184
|
+
|
|
185
|
+
- [#452](https://github.com/softistx/nxgt-material/pull/452) [`74745da`](https://github.com/softistx/nxgt-material/commit/74745da45e1dbeddda162b211fab3a62654f8fcc) Thanks [@SteveGT96](https://github.com/SteveGT96)! - A `ConfirmationDialog` given `:open` with an `update:open` listener (`v-model:open`) now shows exactly what `open` says. It kept its own copy, synced by a watch, so a parent that answered the close by setting `open` back to `true` in the same tick (to show the next step) left the dialog shut while `open` was `true`. Without both, `:open` still only opens it and the dialog closes itself, and `@update:open` alone only listens. A `:key` per step, used to work around it, can be removed.
|
|
186
|
+
|
|
187
|
+
The weekday headers of `Calendar`, `DateField` and `DateRangeField` are labelled by their full weekday in the calendar's language (`aria-label="lundi"`), as `@nxgt/material`'s are, and keyed by column (the narrow names repeat, "M", "M"). Docs: the weekday row on the dates page, "Several steps, one dialog" on the `ConfirmDialog` page, and two troubleshooting entries ("A `ConfirmationDialog` reopened at once stays closed", "A calendar's weekday row is in English on a French page").
|
|
188
|
+
|
|
189
|
+
- [#456](https://github.com/softistx/nxgt-material/pull/456) [`170c936`](https://github.com/softistx/nxgt-material/commit/170c936bbe4d60dd6d88dc9ec08e145836442237) Thanks [@SteveGT96](https://github.com/SteveGT96)! - An Escape pressed while an `AlertDialog`, `Dialog`, `Sheet` or `Drawer` is animating out (a confirmation closed over a `Sheet`, then Escape again at once) now closes the overlay below instead of doing nothing. reka gives Escape to the highest overlay only, and the closed one stayed the highest until its exit animation ended (200ms for a dialog, 300ms for a sheet, 500ms for a drawer). `AlertDialogContent`, `DialogContent`, `DialogScrollContent`, `SheetContent` and `DrawerContent` now press Escape again for the overlay below once they have left. Your `@escape-key-down` still runs first; preventing the default in it while the overlay closes keeps the key. Docs: the troubleshooting entry "The first Escape after closing a nested dialog does nothing".
|
|
190
|
+
|
|
191
|
+
- [#454](https://github.com/softistx/nxgt-material/pull/454) [`ae64988`](https://github.com/softistx/nxgt-material/commit/ae649889e866ee0476e2da2c54f9f1d618803028) Thanks [@SteveGT96](https://github.com/SteveGT96)! - A field's help or error text now describes its control (`aria-describedby`) in `TextareaField`, `NumberField`, `PercentField`, `InputGroupField`, `PhoneField`, `OtpField`, `RatingField`, `ComboboxField`, `TimezoneField` and `Autocomplete` — and so in their `*FormField` wrappers and `FilesFormField`: a screen reader read "invalid" on the control without saying why. `RatingField`'s radio group and `OtpField`'s input now also carry `aria-invalid="true"` on an error. A caller's own `aria-describedby` on `TextareaField`, `InputGroupField` or `OtpField` comes first.
|
|
192
|
+
|
|
193
|
+
- [#454](https://github.com/softistx/nxgt-material/pull/454) [`9e9a571`](https://github.com/softistx/nxgt-material/commit/9e9a571f39b26c86ee37a3dfb954a271b5f6ba21) Thanks [@SteveGT96](https://github.com/SteveGT96)! - Docs: the forms guide and the `OtpField` entry say that a field without an `id` of yours gets a generated one, never its `name`, so two forms on one page sharing a field name keep their labels apart (now covered by a test and the `Form fields/OtpField` `Two On A Page` story).
|
|
194
|
+
|
|
195
|
+
- [#462](https://github.com/softistx/nxgt-material/pull/462) [`ab8c55d`](https://github.com/softistx/nxgt-material/commit/ab8c55de88f20e827f45cf9e3ca3ff32147be381) Thanks [@SteveGT96](https://github.com/SteveGT96)! - On a phone, the menu sheet of `ActivityLayout` and `AppHeader` shows no tooltip over its entries and closes on one Escape: before, focus landing on the first entry opened a tooltip whose own layer took the first Escape.
|
|
196
|
+
|
|
197
|
+
`Sidebar` now tells its `SidebarMenuItem`s whether it is expanded, as in `@nxgt/material`: an entry shows its label as a tooltip only while the sidebar is collapsed (a `SidebarMenuItem` with no `tooltip` used to get none), and an explicit `tooltip` is always shown. A `SidebarMenuItem` in your own expanded `Sidebar` needs no change; one in a collapsed `Sidebar` now shows its label on hover.
|
|
198
|
+
|
|
199
|
+
- [#464](https://github.com/softistx/nxgt-material/pull/464) [`fac20c8`](https://github.com/softistx/nxgt-material/commit/fac20c8a81653219b6658d45f537e3f7b2920bf9) Thanks [@SteveGT96](https://github.com/SteveGT96)! - A toast shown over an open modal `Dialog`, `Sheet`, `Drawer` or `AlertDialog` can be clicked and tapped: its action runs and the dialog stays open. Before, the modal layer's `pointer-events: none` on `<body>` reached the `Toaster`, so its buttons could not be clicked, and a pointer down on a toast counted as outside the dialog. The `Toaster` now sets `pointer-events: auto` (in the stylesheets `styles.css` imports), and the modal contents treat a pointer down or a focus inside the `Toaster` as inside: your `@pointer-down-outside`, `@focus-outside` and `@interact-outside` listeners still run, except where the primitive replaces them: a `Drawer`'s `@focus-outside`, an `AlertDialog`'s pointer-down and interact outside. A non-modal layer (`:modal="false"`) also stays open on a click or a focus on a toast, which is intended.
|
|
200
|
+
|
|
201
|
+
- [#450](https://github.com/softistx/nxgt-material/pull/450) [`8e568c3`](https://github.com/softistx/nxgt-material/commit/8e568c3544327c1ba273482963ec13e1930d3be6) Thanks [@SteveGT96](https://github.com/SteveGT96)! - `zodLocaleError` words the length of a string as a length, and starts every format message with a capital. `z.string().min(3)` now says "Must contain at least 3 characters." / "Doit contenir au moins 3 caractères." and `.max(80)` "Must contain at most 80 characters." / "Doit contenir au plus 80 caractères." (exclusive bounds: "fewer than" / "more than", "moins de" / "plus de"), and one character is singular (`.min(1)`: "Must contain at least 1 character." / "Doit contenir au moins 1 caractère."; the new keys take the count as an ICU `plural`) where it said "This value is too small: it must be at least 3 characters." and, in French, "trop grande … caractères". A number, a list, a set or a file keeps the "too large / too small" wording. The French `invalid_format` message for an email was "adresse e-mail invalide." and is "Adresse e-mail invalide." (the noun opens the sentence, so it is capitalized in every language). New keys, in both `en` and `fr`: `material.zod.too_big_string_inclusive` / `_exclusive` and `material.zod.too_small_string_inclusive` / `_exclusive`; an app that overrode `material.zod.too_big_inclusive` (or `too_small_inclusive`) for strings must override these. A test or a snapshot that asserted the old text must be updated.
|
|
202
|
+
- Updated dependencies [[`7200d8c`](https://github.com/softistx/nxgt-material/commit/7200d8c05e8b94de10910ccb3609be3ed1b59061), [`8e568c3`](https://github.com/softistx/nxgt-material/commit/8e568c3544327c1ba273482963ec13e1930d3be6), [`f6cb264`](https://github.com/softistx/nxgt-material/commit/f6cb2649cbdae34201eba18d0ec69846a281deba), [`025bf28`](https://github.com/softistx/nxgt-material/commit/025bf28872d1954596cf7d7054245646309e4525)]:
|
|
203
|
+
- @nxgt/material-core@0.5.0
|
|
204
|
+
|
|
3
205
|
## 0.26.1
|
|
4
206
|
|
|
5
207
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ stores silently split between two instances.
|
|
|
70
70
|
| `vue-router` | `^4.5 \|\| ^5` | app shell, `LinkButton`, `useSearchParam`, `useNavigationHandler` |
|
|
71
71
|
| `vee-validate` | `^4.15` | every `*FormField` and the built-in forms (attributes, contacts, …) |
|
|
72
72
|
| `@tanstack/vue-table` | `^9` | `DataTable` |
|
|
73
|
-
| `@vueuse/core` | `^13 \|\| ^14` | `useIsMobile`, `useTheme`, `ScrollProgress`,
|
|
73
|
+
| `@vueuse/core` | `^13 \|\| ^14` | `useIsMobile`, `useTheme`, `ScrollProgress`, prop forwarding in primitives |
|
|
74
74
|
| `leaflet`, `@vue-leaflet/vue-leaflet` | `^1.9`, `^0.10` | optional: only for `@nxgt/material-vue/map` |
|
|
75
75
|
| `react`, `@react-pdf/renderer` | `^18.2 \|\| ^19`, `^4.9` | optional: only to render with `@nxgt/material-vue/pdf` |
|
|
76
76
|
| `nuxt` | `>=3.13` | optional: only for `@nxgt/material-vue/nuxt` |
|
|
@@ -101,6 +101,9 @@ starts a second pipeline and the tokens never reach the app. Apps without
|
|
|
101
101
|
Tailwind import the compiled `@nxgt/material-vue` style condition instead —
|
|
102
102
|
never both. Details in [theming](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/theming.md).
|
|
103
103
|
|
|
104
|
+
The required-field mark after a `Label` is drawn by this stylesheet too, so
|
|
105
|
+
a page or test rendered without it shows no `*`.
|
|
106
|
+
|
|
104
107
|
### Pinia and router
|
|
105
108
|
|
|
106
109
|
Widgets keep their UI state in Pinia stores created per instance, and the app
|
|
@@ -168,7 +171,7 @@ does not add the stylesheet. Options and the client-only widgets are in
|
|
|
168
171
|
| `@nxgt/material-vue/composables` | `useTheme`, `useIsClient`, `useIsMobile`, `useSearchParam`, … ([helpers](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/helpers.md)) |
|
|
169
172
|
| `@nxgt/material-vue/dnd` | drag-and-drop primitives ([helpers](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/helpers.md)) |
|
|
170
173
|
| `@nxgt/material-vue/i18n` | `I18nProvider`, `mergeResources`, `createTranslator`, `translateMaterial`, … ([i18n guide](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/i18n.md)) |
|
|
171
|
-
| `@nxgt/material-vue/lib` | `cn`, `clientOnly`, date/number/string utils, Zod error map, Pinia store factories ([helpers](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/helpers.md)) |
|
|
174
|
+
| `@nxgt/material-vue/lib` | `cn`, `clientOnly`, date/number/string utils, Zod error map, `focusFirstInvalid` / `focusField` (focus an invalid field, see [forms](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/forms.md#focusing-the-first-invalid-field)), Pinia store factories ([helpers](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/helpers.md)) |
|
|
172
175
|
| `@nxgt/material-vue/models` | `Status<T>` |
|
|
173
176
|
| `@nxgt/material-vue/types` | shared function types |
|
|
174
177
|
| `@nxgt/material-vue/styles.css` | source Tailwind v4 tokens: **this** is what a Tailwind app imports |
|
|
@@ -308,7 +311,7 @@ Each area a consumer starts from has its own page under `docs/guides/`.
|
|
|
308
311
|
| --- | --- |
|
|
309
312
|
| [Forms](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/forms.md) | wiring fields to vee-validate and Zod, composite values (contacts, opening hours), config-driven `Field`, server errors, what each field emits |
|
|
310
313
|
| [i18n](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/i18n.md) | adding your strings next to the package's, switching language, translated Zod messages, SSR without a text mismatch |
|
|
311
|
-
| [App shell](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/app-shell.md) | laying out an app with `ActivityLayout`, its `items
|
|
314
|
+
| [App shell](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/app-shell.md) | laying out an app with `ActivityLayout`, its `items` (rendered as links; `link-component` to pass your own), the active entry and breadcrumbs (vue-router), the `<main>` landmark, the `h1` title and document scrolling |
|
|
312
315
|
| [Theming](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/theming.md) | changing colours, radius or fonts, dark mode, using the compiled CSS without Tailwind |
|
|
313
316
|
| [Printing](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/printing.md) | printable documents built from the `Print*` components |
|
|
314
317
|
| [PDF documents](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/pdf.md) | PDF files from data with `@nxgt/material-vue/pdf`: builders, `usePdf`, `PdfDownloadButton`, a Nitro route with `pdfResponse`, fonts on an inlined server |
|
|
@@ -358,7 +361,7 @@ React-only for now: `StepperIndicator` ([roadmap](https://cdn.jsdelivr.net/npm/@
|
|
|
358
361
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/primitives-kbd.webp" width="120" alt="Kbd"> | [`Kbd`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/primitives.md#kbd), `KbdShortcut` | One keyboard key, or a key combination with platform glyphs. | `Kbd`, `KbdShortcut` |
|
|
359
362
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/primitives-separator.webp" width="120" alt="Separator"> | [`Separator`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/primitives.md#separator) | Horizontal or vertical divider. | `Separator` |
|
|
360
363
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/primitives-table.webp" width="120" alt="Table"> | [`Table`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/primitives.md#table), `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`, `TableEmpty` + parts | Styled semantic table inside a scrollable wrapper. | `Table` (same parts) |
|
|
361
|
-
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/primitives-tabs.webp" width="120" alt="Tabs"> | [`Tabs`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/primitives.md#tabs), `TabsList`, `TabsTrigger`, `TabsContent
|
|
364
|
+
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/primitives-tabs.webp" width="120" alt="Tabs"> | [`Tabs`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/primitives.md#tabs), `TabsList`, `TabsTrigger`, `TabsContent` | Tabs whose triggers can take an icon, in two looks: `underline` (the default) and the `segmented` pill. | `Tabs` (same parts) |
|
|
362
365
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/primitives-toggle.webp" width="120" alt="Toggle"> | [`Toggle`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/primitives.md#toggle), `ToggleGroup`, `ToggleGroupItem` | Pressable on / off button: default or outline, three sizes. | `Toggle` |
|
|
363
366
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/primitives-togglegroup.webp" width="120" alt="ToggleGroup"> | [`Toggle`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/primitives.md#togglegroup), `ToggleGroup`, `ToggleGroupItem` | Single or multiple toggle group sharing variant and size. | `ToggleGroup`, `ToggleGroupItem`, `ToggleGroupSeparator` |
|
|
364
367
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/primitives-activity.webp" width="120" alt="Activity"> | [`Activity`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/primitives.md#activity) | Keeps its content mounted and hides it with a boolean `visible`. | `Activity` |
|
|
@@ -462,7 +465,7 @@ React-only for now: `StepperIndicator` ([roadmap](https://cdn.jsdelivr.net/npm/@
|
|
|
462
465
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-display-accordioncard.webp" width="120" alt="AccordionCard"> | [`AccordionCard`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-display.md#accordioncard) | Renders `items` (title, content) as a styled accordion, with `#header`, `#title` and `#content` slots. | `AccordionCard` |
|
|
463
466
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-display-timeline.webp" width="120" alt="Timeline"> | [`Timeline`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-display.md#timeline), `TimelineItem`, `RelativeTime` | Vertical event timeline with toned markers and relative times. | `Timeline`, `TimelineItem`, `RelativeTime` |
|
|
464
467
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-display-eventchip.webp" width="120" alt="EventChip"> | [`EventChip`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-display.md#eventchip) | Calendar event pill with time, all-day and continuation edges. | `EventChip` |
|
|
465
|
-
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-display-codeblock.webp" width="120" alt="CodeBlock"> | [`CodeBlock`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-display.md#codeblock), `CodeDiff`, `JsonViewer`, `CodeGroup` | Highlighted code with line numbers and copy, a Monaco diff, a collapsible JSON tree, and tabbed samples (`Tabs
|
|
468
|
+
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-display-codeblock.webp" width="120" alt="CodeBlock"> | [`CodeBlock`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-display.md#codeblock), `CodeDiff`, `JsonViewer`, `CodeGroup` | Highlighted code with line numbers and copy, a Monaco diff, a collapsible JSON tree, and tabbed samples (`Tabs`, underlined). | `CodeBlock`, `CodeDiff`, `JsonViewer`, `CodeGroup` |
|
|
466
469
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-display-markdown.webp" width="120" alt="Markdown"> | [`Markdown`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-display.md#markdown), `MermaidDiagram` | Markdown with KaTeX math and mermaid diagrams (client-only). Fences reuse `CodeBlock`. `ChatMarkdown` renders through it. | `Markdown`, `MermaidDiagram` |
|
|
467
470
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-display-terminal.webp" width="120" alt="Terminal"> | [`Terminal`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-display.md#terminal) | Display-only log with prompt, copy, and error/input tones. Container queries. | `Terminal` |
|
|
468
471
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-display-figure.webp" width="120" alt="Figure"> | [`Figure`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-display.md#figure) | Framed media with an optional caption. | `Figure` |
|
|
@@ -475,7 +478,7 @@ React-only for now: `StepperIndicator` ([roadmap](https://cdn.jsdelivr.net/npm/@
|
|
|
475
478
|
|
|
476
479
|
| Preview | Component | What it does | @nxgt/material |
|
|
477
480
|
| --- | --- | --- | --- |
|
|
478
|
-
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-table-filter-lists-datatable.webp" width="120" alt="DataTable"> | [`DataTable`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-table-filter-and-lists.md#datatable), `DataTableColumnHeader`, `DataTablePagination`, `DataTableServerFooter`, `DataTableViewOptions` | TanStack Table v9 with sorting, filtering, selection, expandable rows, pagination or load-more, column visibility, skeleton rows while loading and your own empty state — or one server page at a time, with controlled sorting and selection, cursor Previous / Next links and row links. | `DataTable`, `DataTableColumnHeader`, `DataTablePagination`, `DataTableServerFooter`, `DataTableViewOptions` |
|
|
481
|
+
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-table-filter-lists-datatable.webp" width="120" alt="DataTable"> | [`DataTable`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-table-filter-and-lists.md#datatable), `DataTableColumnHeader`, `DataTablePagination`, `DataTableServerFooter`, `DataTableViewOptions` | TanStack Table v9 with sorting, filtering, selection, expandable rows, pagination or load-more, column visibility, skeleton rows while loading and your own empty state — or one server page at a time, with controlled sorting and selection, cursor Previous / Next links and row links. On a phone it keeps its header row and scrolls sideways in a keyboard-reachable region; the page does not. | `DataTable`, `DataTableColumnHeader`, `DataTablePagination`, `DataTableServerFooter`, `DataTableViewOptions` |
|
|
479
482
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-table-filter-lists-filter.webp" width="120" alt="Filter"> | [`Filter`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-table-filter-and-lists.md#filter) · `FilterFormField`, `FilterProvider`, `FilterTrigger`, `FilterChips`, `FilterContent`, `FilterGroups`, `FilterGroup`, `FilterField`, `FilterActions`, `FilterPresets` | Schema-driven filters with chips, presets, validation, and URL or storage persistence. | `Filter`, `FilterProvider`, `FilterContent`, `FilterChips`, `FilterPresets`, `FilterFormField` |
|
|
480
483
|
| — | [`FilterInline`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-table-filter-and-lists.md#filterinline-filterpopover-filterdialog-filtersheet-filterdrawer), `FilterDialog`, `FilterSheet`, `FilterDrawer`, `FilterPopover` | The filter panel shown inline, or in a popover, dialog, sheet or drawer. | `FilterInline`, `FilterPopover`, `FilterDialog`, `FilterSheet`, `FilterDrawer` |
|
|
481
484
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/data-table-filter-lists-pagination.webp" width="120" alt="Pagination"> | [`Pagination`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/data-table-filter-and-lists.md#pagination), `PaginationContent`, `PaginationItem`, `PaginationFirst`, `PaginationLast`, `PaginationLink`, `PaginationBar`, `paginationItems` + parts | Pagination primitives on Reka UI's `Pagination`, with first / last buttons and ellipses. `PaginationBar` adds a sibling window with ellipses. | `Pagination` (parts), `PaginationFirst`, `PaginationLast`, `PaginationLink`, `PaginationBar`, `paginationItems` |
|
|
@@ -486,7 +489,7 @@ React-only for now: `StepperIndicator` ([roadmap](https://cdn.jsdelivr.net/npm/@
|
|
|
486
489
|
|
|
487
490
|
| Preview | Component | What it does | @nxgt/material |
|
|
488
491
|
| --- | --- | --- | --- |
|
|
489
|
-
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/navigation-layout-appshell.webp" width="120" alt="ActivityLayout"> | [`ActivityLayout`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/navigation-and-layout.md#activitylayout), `ActivityContent`, `AppSidebar`, `AppHeader`, `AppBreadcrumb` | App shell: sidebar built from `items`, a header with a mobile sheet, route breadcrumbs and a titled page. See the [app shell guide](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/app-shell.md). | `ActivityLayout`, `ActivityContent`, `AppSidebar`, `AppHeader`, `AppBreadcrumb` |
|
|
492
|
+
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/navigation-layout-appshell.webp" width="120" alt="ActivityLayout"> | [`ActivityLayout`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/navigation-and-layout.md#activitylayout), `ActivityContent`, `AppSidebar`, `AppHeader`, `AppBreadcrumb` | App shell: sidebar built from `items`, a header with a mobile sheet, route breadcrumbs and a titled page; entries are real links, and `link-component` swaps the link. See the [app shell guide](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/guides/app-shell.md). | `ActivityLayout`, `ActivityContent`, `AppSidebar`, `AppHeader`, `AppBreadcrumb` |
|
|
490
493
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/navigation-layout-sidebar.webp" width="120" alt="Sidebar"> | [`Sidebar`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/navigation-and-layout.md#sidebar), `SidebarHeader`, `SidebarContent`, `SidebarFooter`, `SidebarMenuItem`, `SidebarMenuLabel` | Collapsible navigation sidebar with icons, tooltips and active items. | `Sidebar` (same parts) |
|
|
491
494
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/navigation-layout-topappbar.webp" width="120" alt="TopAppBar"> | [`TopAppBar`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/navigation-and-layout.md#topappbar), `BottomAppBar` | Top bar surface, and a bottom bar of icon actions with an active state. | `TopAppBar`, `BottomAppBar` |
|
|
492
495
|
| <img src="https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/previews/navigation-layout-breadcrumb.webp" width="120" alt="Breadcrumb"> | [`Breadcrumb`](https://cdn.jsdelivr.net/npm/@nxgt/material-vue@0/docs/components/navigation-and-layout.md#breadcrumb), `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage` + parts | Breadcrumb primitives. | `Breadcrumb` (same parts) |
|