@nxgt/material-vue 0.42.0 → 0.43.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 +111 -0
- package/README.md +18 -5
- package/dist/{Button-CWmzvv9p.js → Button-C3K6dhqd.js} +4 -4
- package/dist/{HoverCardTrigger-C5AbAyHE.js → HoverCardTrigger-Bga4YjEr.js} +69 -62
- package/dist/backgrounds/backgrounds.css +8 -0
- package/dist/backgrounds/css/base.css +169 -0
- package/dist/backgrounds/css/gradient.css +53 -0
- package/dist/backgrounds/css/grain.css +19 -0
- package/dist/backgrounds/css/motion.css +30 -0
- package/dist/backgrounds.js +111 -0
- package/dist/{camera-C-9slBUV.js → camera-BbF9_-ys.js} +4 -4
- package/dist/components/base.css +5 -2
- package/dist/components/components.css +5 -0
- package/dist/components/theme-tokens.css +21 -0
- package/dist/{components-DX4EycMi.js → components-yCdbry6Q.js} +14077 -13577
- package/dist/components.js +8 -8
- package/dist/composables.js +3 -3
- package/dist/dnd.js +1 -1
- package/dist/{form-scope-CN68PTXc.js → form-scope-CkWJrBCu.js} +1 -1
- package/dist/{i18n-provider-DmZSTBnp.js → i18n-provider-tL27-a8y.js} +1 -1
- package/dist/i18n.js +4 -4
- package/dist/{icon-button-DErh1aJS.js → icon-button-MyFFJpXA.js} +4 -4
- package/dist/lib/backgrounds/background/background.vue.d.ts +22 -0
- package/dist/lib/backgrounds/background/index.d.ts +2 -0
- package/dist/lib/backgrounds/background/partials/background-layers.vue.d.ts +9 -0
- package/dist/lib/backgrounds/background/partials/scene-svg.d.ts +10 -0
- package/dist/lib/backgrounds/background/types.d.ts +24 -0
- package/dist/lib/backgrounds/index.d.ts +13 -0
- package/dist/lib/components/ui/actions/button/button-tones.d.ts +3 -3
- package/dist/lib/components/ui/actions/responsive-button/responsive-button.vue.d.ts +3 -3
- package/dist/lib/components/ui/collections/data-table/data-table-context.d.ts +4 -0
- package/dist/lib/components/ui/collections/data-table/data-table.vue.d.ts +8 -0
- package/dist/lib/components/ui/collections/data-table/partials/data-table-body.vue.d.ts +8 -0
- 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-toolbar.d.ts +16 -3
- package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +36 -0
- package/dist/lib/components/ui/collections/data-table/types.d.ts +15 -0
- package/dist/lib/components/ui/collections/data-table/use-data-table.d.ts +2 -0
- package/dist/lib/components/ui/feedback/circular-progress/circular-progress.vue.d.ts +1 -1
- package/dist/lib/components/ui/feedback/empty-state/empty-state.vue.d.ts +21 -8
- package/dist/lib/components/ui/feedback/empty-state/index.d.ts +1 -1
- package/dist/lib/components/ui/feedback/error-state/error-state.vue.d.ts +54 -0
- package/dist/lib/components/ui/feedback/error-state/index.d.ts +3 -0
- package/dist/lib/components/ui/feedback/error-state/partials/error-state-details.vue.d.ts +7 -0
- package/dist/lib/components/ui/feedback/index.d.ts +1 -0
- package/dist/lib/components/ui/feedback/state-frame/state-frame.vue.d.ts +36 -0
- package/dist/lib/components/ui/feedback/state-frame/state-icon.vue.d.ts +7 -0
- package/dist/lib/components/ui/feedback/state-frame/state-illustration.vue.d.ts +8 -0
- package/dist/lib/components/ui/feedback/state-frame/state-tones.d.ts +11 -0
- package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
- package/dist/lib/components/ui/layout/app-shell/use-truncated-title.d.ts +10 -0
- package/dist/lib/components/ui/layout/background/background.vue.d.ts +18 -3
- package/dist/lib/components/ui/layout/doc/partials/doc-empty.vue.d.ts +7 -4
- package/dist/lib/components/ui/media/stx-player/stx-player-viewport.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/command/CommandSeparator.vue.d.ts +3 -1
- package/dist/lib/components/ui/overlays/dialog/in-dialog.d.ts +1 -1
- package/dist/lib/components/ui/overlays/dialog/use-escape-forward.d.ts +13 -0
- package/dist/lib/components/ui/overlays/dialog/use-modal-content.d.ts +2 -0
- package/dist/lib/components/ui/primitives/nonce-provider/nonce-provider.vue.d.ts +4 -1
- package/dist/lib/components/ui/selection/autocomplete/autocomplete-keyboard.d.ts +22 -4
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-box.vue.d.ts +6 -2
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-list.vue.d.ts +7 -0
- package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-active.d.ts +17 -0
- package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-keydown.d.ts +21 -0
- package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-open.d.ts +3 -3
- package/dist/lib/components/ui/selection/autocomplete/use-autocomplete.d.ts +2 -1
- package/dist/lib/components/ui/selection/autocomplete/use-drawer-room.d.ts +11 -0
- package/dist/lib/components/ui/selection/autocomplete/use-query-change.d.ts +8 -0
- package/dist/lib/components/ui/widgets/charts/chart-container.vue.d.ts +2 -2
- package/dist/lib/components/ui/widgets/kanban/partials/kanban-empty.vue.d.ts +1 -0
- package/dist/lib/composables/use-background-activity.d.ts +8 -0
- package/dist/lib/i18n/resources/en/en.d.ts +24 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +25 -1
- package/dist/lib/i18n/resources/fr/fr.d.ts +24 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +25 -1
- package/dist/lib/i18n/resources/resources.d.ts +48 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/lib/index.d.ts +1 -0
- package/dist/lib/nuxt/component-names.d.ts +1 -1
- package/dist/lib/nuxt/module.d.ts +2 -11
- package/dist/lib/nuxt/pdf-fonts.d.ts +1 -1
- package/dist/lib/nuxt/plugin-options.d.ts +58 -0
- package/dist/lib/vite/index.d.ts +4 -8
- package/dist/lib.js +52 -51
- package/dist/main.js +17 -17
- package/dist/map.js +4 -4
- package/dist/material-vue.css +1 -1
- package/dist/{pdf-outline-sidebar-BU4o_hBI.js → pdf-outline-sidebar-Kqsfd_cO.js} +2 -2
- package/dist/{pdf-page-anNe78Lc.js → pdf-page-Dx3jwxu6.js} +1 -1
- package/dist/{pdf-page-list-CSY76HH6.js → pdf-page-list-BsQmEHaY.js} +2 -2
- package/dist/{pdf-reader-BhZNrW7R.js → pdf-reader-CJo8SQVl.js} +15 -15
- package/dist/{pdf-sidebar-DcIpG7eh.js → pdf-sidebar-CcCUATl-.js} +3 -3
- package/dist/{pdf-thumbnail-sidebar-CUC0V4if.js → pdf-thumbnail-sidebar-BRxNRGfG.js} +4 -4
- package/dist/{pdf-toolbar-C373S-ul.js → pdf-toolbar-B0ckYBJ2.js} +4 -4
- package/dist/pdf.js +2 -2
- package/dist/{print-format.utils-C81c0weJ.js → print-format.utils-51joHpFb.js} +2 -2
- package/dist/{sortable-provider-CL2UGGn8.js → sortable-provider-BEx1B0Ky.js} +2 -2
- package/dist/{stored-language-Wtcj2HsU.js → stored-language-DVP9KzRu.js} +50 -2
- package/dist/{stx-player-CMmM3F5Z.js → stx-player-KPp9c7vS.js} +12 -12
- package/dist/styles.css +4 -0
- package/dist/{translator-D5Dp-dob.js → translator-Bm7i7dv-.js} +1 -1
- package/dist/use-background-activity-DdTBbNpX.js +17 -0
- package/dist/{use-heading-spy-BIBHtUXN.js → use-heading-spy-jn8f2ioE.js} +13 -13
- package/dist/{use-material-translation-COoBTGpB.js → use-material-translation-CfFZPSe0.js} +3 -3
- package/dist/vite.js +7 -2
- package/docs/README.md +3 -0
- package/docs/components/buttons-and-actions.md +2 -0
- package/docs/components/data-table-filter-and-lists.md +59 -8
- package/docs/components/feedback-and-status.md +79 -8
- package/docs/components/media-and-files.md +2 -0
- package/docs/components/navigation-and-layout.md +13 -5
- package/docs/components/overlays-and-menus.md +20 -3
- package/docs/components/primitives.md +5 -3
- package/docs/components/rich-widgets.md +1 -1
- package/docs/components/selection-and-choice.md +14 -4
- package/docs/getting-started.md +22 -3
- package/docs/guides/app-shell.md +1 -1
- package/docs/guides/backgrounds.md +143 -0
- package/docs/guides/csp.md +48 -6
- package/docs/guides/helpers.md +1 -0
- package/docs/guides/i18n.md +51 -1
- package/docs/guides/migrate-to-vite-plugin.md +259 -0
- package/docs/guides/pdf.md +11 -4
- package/docs/guides/ssr.md +1 -1
- package/docs/guides/theming.md +52 -4
- package/docs/guides/vite-plugin.md +194 -0
- package/docs/previews/backgrounds-matrix.webp +0 -0
- package/docs/previews/feedback-status-emptystate.webp +0 -0
- package/docs/previews/feedback-status-errorstate.webp +0 -0
- package/docs/roadmap.md +60 -32
- package/docs/troubleshooting.md +251 -7
- package/lib/backgrounds/backgrounds.css +8 -0
- package/lib/backgrounds/css/base.css +169 -0
- package/lib/backgrounds/css/gradient.css +53 -0
- package/lib/backgrounds/css/grain.css +19 -0
- package/lib/backgrounds/css/motion.css +30 -0
- package/lib/components/base.css +5 -2
- package/lib/components/components.css +5 -0
- package/lib/components/theme-tokens.css +21 -0
- package/lib/nuxt/component-names.ts +1 -0
- package/lib/nuxt/module.ts +29 -15
- package/lib/nuxt/pdf-fonts.ts +1 -1
- package/lib/nuxt/plugin-options.ts +92 -0
- package/lib/nuxt/runtime/inter.css +6 -0
- package/lib/styles.css +4 -0
- package/lib/vite/index.ts +19 -2
- package/package.json +24 -2
package/docs/guides/csp.md
CHANGED
|
@@ -101,9 +101,30 @@ On the server, the nonce goes down through `provide`/`inject` only. On the
|
|
|
101
101
|
client, `NonceProvider` also keeps it for the code that runs outside Vue
|
|
102
102
|
(`claimCspNonce`, once mounted, so never on a server: one request's nonce
|
|
103
103
|
never reaches another's). With nested providers, the innermost one mounted is
|
|
104
|
-
the page's nonce, and it falls back to the outer one's when it unmounts.
|
|
105
|
-
|
|
106
|
-
the
|
|
104
|
+
the page's nonce, and it falls back to the outer one's when it unmounts.
|
|
105
|
+
|
|
106
|
+
On the client, `<NonceProvider>` with no `nonce` prop works: it falls back to
|
|
107
|
+
the nonce the page carries (`getCspNonce()`: the `csp-nonce` meta tag, a
|
|
108
|
+
`<script nonce>`, …), read in `setup`, so it is there from the first render (a
|
|
109
|
+
`<style>` a child creates in its own `onMounted` carries it). A server has no
|
|
110
|
+
page to read and keeps no nonce in a global: it passes the prop, the nonce the
|
|
111
|
+
page carries, so the hydration pass renders what the server did. A server that
|
|
112
|
+
renders without the prop leaves its `<style>` elements blocked: a browser
|
|
113
|
+
checks the policy when it parses them, and a nonce set after hydration does
|
|
114
|
+
not bring them back. A parent `ConfigProvider`'s nonce still wins over the
|
|
115
|
+
page's, and the prop wins over both.
|
|
116
|
+
|
|
117
|
+
```vue
|
|
118
|
+
<NonceProvider><App /></NonceProvider>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
"The page's nonce" here is what `getCspNonce()` returns: first a nonce a mounted
|
|
122
|
+
provider has already claimed, else the `csp-nonce` meta tag or a nonced
|
|
123
|
+
`<script>`/`<style>`. A provider whose own `nonce` goes from a value to
|
|
124
|
+
`undefined` therefore keeps its earlier nonce.
|
|
125
|
+
|
|
126
|
+
The server always passes the nonce itself. To hand the client the one the
|
|
127
|
+
server wrote, read it from the page and give the root the same prop:
|
|
107
128
|
|
|
108
129
|
```ts
|
|
109
130
|
// entry-client.ts
|
|
@@ -147,8 +168,8 @@ const Keyframes = () => h('style', { nonce: nonce.value }, props.css);
|
|
|
147
168
|
|
|
148
169
|
`useCspNonce()` is a computed ref: `NonceProvider`'s nonce, else the nearest
|
|
149
170
|
reka `ConfigProvider`'s, else `undefined`. It never reads the page in
|
|
150
|
-
`setup
|
|
151
|
-
|
|
171
|
+
`setup` itself. A `NonceProvider` without a prop does, on the client, which
|
|
172
|
+
is why a server-rendered app passes the prop to the provider on both sides. Code that runs once mounted (an imperative
|
|
152
173
|
widget creating its own `<style>`) can call `getCspNonce()` instead.
|
|
153
174
|
|
|
154
175
|
## `<style>` elements the components inject
|
|
@@ -200,6 +221,8 @@ Not fixable by configuration yet, and still reported by a strict policy:
|
|
|
200
221
|
|
|
201
222
|
## zod and `'unsafe-eval'`
|
|
202
223
|
|
|
224
|
+
> **With the Vite plugin** (`nxgtMaterial()` from `@nxgt/material-vue/vite`, the [Vite plugin guide](vite-plugin.md)) the KaTeX and zod steps below are done for you, and so is the Inter step once `@fontsource-variable/inter` is installed (without it the plugin warns and keeps the system font), and the build writes the CSP header (by hash, or with a nonce placeholder). What follows is the route without it.
|
|
225
|
+
|
|
203
226
|
Building a `z.object()` makes zod probe `new Function` once, to choose
|
|
204
227
|
between its compiled parser and its interpreter. Without `'unsafe-eval'` the
|
|
205
228
|
browser reports a violation. The error is caught, so nothing breaks, but the
|
|
@@ -244,6 +267,8 @@ the stand-in is not seen through the factory's schema.
|
|
|
244
267
|
|
|
245
268
|
## KaTeX fonts
|
|
246
269
|
|
|
270
|
+
> **With the Vite plugin** (`nxgtMaterial()` from `@nxgt/material-vue/vite`, the [Vite plugin guide](vite-plugin.md)) the KaTeX and zod steps below are done for you, and so is the Inter step once `@fontsource-variable/inter` is installed (without it the plugin warns and keeps the system font), and the build writes the CSP header (by hash, or with a nonce placeholder). What follows is the route without it.
|
|
271
|
+
|
|
247
272
|
`Markdown` renders formulas with KaTeX, which needs its fonts
|
|
248
273
|
(`KaTeX_*.woff2`). They are files on both routes, so `font-src 'self'` allows
|
|
249
274
|
them, and a browser downloads a font only when a formula draws a glyph from
|
|
@@ -280,6 +305,8 @@ The library's CI fails when a stylesheet in `dist/` carries a `data:` font.
|
|
|
280
305
|
|
|
281
306
|
## Inter / the sans font
|
|
282
307
|
|
|
308
|
+
> **With the Vite plugin** (`nxgtMaterial()` from `@nxgt/material-vue/vite`, the [Vite plugin guide](vite-plugin.md)) the KaTeX and zod steps below are done for you, and so is the Inter step once `@fontsource-variable/inter` is installed (without it the plugin warns and keeps the system font), and the build writes the CSP header (by hash, or with a nonce placeholder). What follows is the route without it.
|
|
309
|
+
|
|
283
310
|
The library ships no web font. Its sans text uses the Tailwind token
|
|
284
311
|
`--font-sans`, which the theme does not set, so it is Tailwind's system stack
|
|
285
312
|
(`ui-sans-serif, system-ui, sans-serif`, then the emoji fonts). Many apps name
|
|
@@ -388,14 +415,29 @@ stamps Nuxt's scripts with it), so both render the same tree.
|
|
|
388
415
|
|
|
389
416
|
On your own Vite SSR server, generate the nonce in the request handler, put it
|
|
390
417
|
in the header and in every `<script>` and `<style>` you emit, and pass it to
|
|
391
|
-
`NonceProvider` through your root component's props
|
|
418
|
+
`NonceProvider` through your root component's props. `createCspNonce()` and
|
|
419
|
+
`cspPolicy({ nonce }).header` from `@nxgt/material-vue/lib` make both (they
|
|
420
|
+
use `globalThis.crypto`, so the same code runs on Node, Bun, Deno and
|
|
421
|
+
workers); add your own origins with the `directives` option. Never serve the
|
|
422
|
+
build placeholder `CSP_NONCE_PLACEHOLDER` as a nonce: both helpers refuse it.
|
|
423
|
+
A page that cannot have a per-request nonce (static hosting) allows its inline
|
|
424
|
+
scripts by hash instead: `await cspHash(script)`, then
|
|
425
|
+
`cspPolicy({ hashes: { script: [hash] } })`, whose
|
|
426
|
+
`style-src-elem` is then `'self' 'unsafe-inline'`: the components create
|
|
427
|
+
`<style>` elements at runtime, which have no hash, and a style hash would void
|
|
428
|
+
`'unsafe-inline'` and block them. A nonce is the strict option for styles. For zod without `'unsafe-eval'`,
|
|
429
|
+
put `zodJitlessTag(nonce)` first in the head.
|
|
392
430
|
|
|
393
431
|
```ts
|
|
394
432
|
// entry-server.ts
|
|
395
433
|
import { createSSRApp } from 'vue';
|
|
396
434
|
import { renderToString } from 'vue/server-renderer';
|
|
435
|
+
import { createCspNonce, cspPolicy } from '@nxgt/material-vue/lib';
|
|
397
436
|
import App from './App.vue';
|
|
398
437
|
|
|
438
|
+
// In the request handler:
|
|
439
|
+
// const nonce = createCspNonce();
|
|
440
|
+
// response.setHeader('Content-Security-Policy', cspPolicy({ nonce }).header);
|
|
399
441
|
export function render(nonce: string) {
|
|
400
442
|
return renderToString(createSSRApp(App, { nonce }));
|
|
401
443
|
}
|
package/docs/guides/helpers.md
CHANGED
|
@@ -18,6 +18,7 @@ differences are listed under each table. React's `/hooks` is
|
|
|
18
18
|
| --- | --- |
|
|
19
19
|
| `cn(...classes)` | `clsx` + `tailwind-merge`: joins conditional classes, last Tailwind utility wins. |
|
|
20
20
|
| `getCspNonce(doc?)`, `setCspNonce(nonce)` | The page's Content Security Policy nonce, from `@nxgt/material-core/csp-nonce`: `getCspNonce` reads what `setCspNonce` kept, else the `csp-nonce` meta tag, a `<script nonce>` or `<style nonce>`, else `__webpack_nonce__`; `undefined` on a server, where `setCspNonce` does nothing. See [CSP](csp.md#nonceprovider). |
|
|
21
|
+
| `createCspNonce(bytes?)`, `cspPolicy({ nonce, dev, hashes, directives })`, `cspHash(source)`, `applyCspNonce(html, nonce)`, `CSP_NONCE_PLACEHOLDER`, `ZOD_JITLESS_SCRIPT`, `zodJitlessTag(nonce?)` | The server and build side of the CSP, from `@nxgt/material-core/csp` (no DOM; Node, Bun, Deno and workers): a nonce per request, the recommended policy as a `header`, the `'sha256-…'` source of an inline script, the nonce filled into built HTML, zod's jitless script. The public placeholder is refused as a nonce. See [CSP](csp.md#ssr). |
|
|
21
22
|
| `printOnly(element, name?)`, `PRINT_ATTRIBUTE`, `PRINT_TARGET_ATTRIBUTE` | Opens the print dialog for one element of the page instead of the whole page (the marks are removed once printing is over), from `@nxgt/material-core/print-only`; the `@media print` rules are in the package's `styles.css`. See [printing](printing.md#printing-one-element-of-the-page). |
|
|
22
23
|
| `clientOnly(component)` | Wraps a component so it renders nothing on the server and on the first client render, and mounts only after `onMounted`. Pair it with `defineAsyncComponent(() => import(…))`. See [SSR](ssr.md). |
|
|
23
24
|
| `renderSlot(slot, context)`, `ContentSlot<T>` | Resolves a `ContentSlot`: a `VNode`, a string or number, or a `(context) => VNode \| string \| number`. |
|
package/docs/guides/i18n.md
CHANGED
|
@@ -310,7 +310,8 @@ names and text, which reka-ui left in English (`material.pagination.first`,
|
|
|
310
310
|
step announces after its title, "Completed" or "Error"
|
|
311
311
|
(`material.stepper.completed` / `error`, "Terminée" / "Erreur" in French);
|
|
312
312
|
`CommandDialog`'s
|
|
313
|
-
default title and description (`material.command
|
|
313
|
+
default title and description (`material.command.title` / `description`) and
|
|
314
|
+
`CommandList`'s accessible name (`material.command.list`); the carousel's
|
|
314
315
|
`aria-roledescription` (`material.carousel.roledescription` / `slide`); the
|
|
315
316
|
filter's empty state and `+N more` chip (`material.filter.empty` / `more`) and
|
|
316
317
|
the Yes/No, From, Until and Up to of its chips (`material.filter.value.*`;
|
|
@@ -455,6 +456,55 @@ reported with `console.warn`, one line per language:
|
|
|
455
456
|
|
|
456
457
|
If you see that warning and did not mean it, move your key out of `material`.
|
|
457
458
|
|
|
459
|
+
### Empty and error states
|
|
460
|
+
|
|
461
|
+
`EmptyState` reads `material['empty-state'].<kind>.title` and `description` for
|
|
462
|
+
its four kinds, and `ErrorState` reads `material['error-state'].<kind>.title`
|
|
463
|
+
and `description` for its three. The other keys the two components and the
|
|
464
|
+
places that use them read:
|
|
465
|
+
|
|
466
|
+
| Key | English | French | Read by |
|
|
467
|
+
| --- | --- | --- | --- |
|
|
468
|
+
| `material.empty-state.empty.title` / `description` | No Data Found / There is nothing to display here at the moment. | Aucune donnée / Il n'y a rien à afficher pour le moment. | `EmptyState kind="empty"` |
|
|
469
|
+
| `material.empty-state.search.title` / `description` | No results / Try a different query or clear the filters. | Aucun résultat / Essayez une autre recherche ou retirez les filtres. | `kind="search"` |
|
|
470
|
+
| `material.empty-state.not-found.title` / `description` | Page Not Found / The page you are looking for doesn't exist or has been moved. | Page introuvable / La page que vous recherchez n'existe pas ou a été déplacée. | `kind="not-found"` |
|
|
471
|
+
| `material.empty-state.unauthorized.title` / `description` | Access Denied / You don't have permission to view this content. | Accès refusé / Vous n'avez pas la permission de consulter ce contenu. | `kind="unauthorized"` |
|
|
472
|
+
| `material.error-state.generic.title` / `description` | Something Went Wrong / An unexpected error occurred. Please try again later. | Une erreur est survenue / Une erreur inattendue s'est produite. Veuillez réessayer plus tard. | `ErrorState kind="generic"` |
|
|
473
|
+
| `material.error-state.offline.title` / `description` | You are offline / Check your connection and try again. | Vous êtes hors ligne / Vérifiez votre connexion et réessayez. | `kind="offline"` |
|
|
474
|
+
| `material.error-state.maintenance.title` / `description` | We'll be right back / The service is down for a short update. | On revient tout de suite / Le service est en courte mise à jour. | `kind="maintenance"` |
|
|
475
|
+
| `material.error-state.retry` | Retry | Réessayer | `ErrorState`'s button |
|
|
476
|
+
| `material.error-state.details` | Technical details | Détails techniques | the collapsible summary |
|
|
477
|
+
| `material.error-state.details-name` / `details-message` / `details-cause` | Name / Message / Cause | Nom / Message / Cause | the rows inside it |
|
|
478
|
+
| `material.table.labels.no-data` | No data available | Données non disponibles | `DataTable`'s default for a table with no data |
|
|
479
|
+
| `material.table.labels.no-results` / `no-results-description` | No results / Try a different search or clear the filters. | Aucun résultat / Essayez une autre recherche ou retirez les filtres. | `DataTable`'s default when a filter hides every row, also announced by its status |
|
|
480
|
+
| `material.command.empty` | No results | Aucun résultat | `CommandEmpty` with no children |
|
|
481
|
+
| `material.pdf-reader.retry` | Retry | Réessayer | not read yet: the Vue `PdfReader` has no Retry button (React only for now, [#702](https://github.com/softistx/nxgt-material/issues/702)) |
|
|
482
|
+
|
|
483
|
+
`material.empty-state.generic.*`, `offline.*` and `maintenance.*` still exist
|
|
484
|
+
for the deprecated `variant`; `ErrorState` does not read them. An app that
|
|
485
|
+
overrode those three for an `EmptyState` error variant copies its overrides to
|
|
486
|
+
`material.error-state.<kind>.title` / `description`:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
import { mergeResources, resources as materialResources } from '@nxgt/material-vue/i18n';
|
|
490
|
+
|
|
491
|
+
const appResources = {
|
|
492
|
+
en: {
|
|
493
|
+
material: {
|
|
494
|
+
'error-state': {
|
|
495
|
+
offline: { title: 'No connection', description: 'Reconnect to continue.' },
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
export const merged = mergeResources(materialResources, appResources);
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Text you pass (`title`, `description`, `retryLabel`) always wins over these keys.
|
|
505
|
+
A `title` of your own drops the default description, so pass a `description`
|
|
506
|
+
to keep a line under it.
|
|
507
|
+
|
|
458
508
|
## Zod messages
|
|
459
509
|
|
|
460
510
|
```ts
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Migrating to the Vite plugin
|
|
2
|
+
|
|
3
|
+
This page is for an app that already sends a strict Content Security Policy
|
|
4
|
+
(CSP) with `@nxgt/material-vue` by hand, and wants `nxgtMaterial()` to do that
|
|
5
|
+
work. It is written as search → replace pairs, so a person or an upgrade agent
|
|
6
|
+
can run each `grep`, change what it finds and check the result. Read the
|
|
7
|
+
[Vite plugin guide](vite-plugin.md) first for what each option does.
|
|
8
|
+
|
|
9
|
+
Nothing here is breaking: the old exports (`pdfkitStandardFonts`,
|
|
10
|
+
`cspAssetsInlineLimit`, `createCspNonce`, `cspPolicy`, `applyCspNonce`) stay,
|
|
11
|
+
and an app that changes nothing keeps working. Migrate pair by pair.
|
|
12
|
+
|
|
13
|
+
## Step 0: add the plugin
|
|
14
|
+
|
|
15
|
+
Search `vite.config.*` for `plugins:`. Add the plugin last, spread (it returns an
|
|
16
|
+
array). Inter needs no install to build: without `@fontsource-variable/inter` the plugin warns once and keeps the system font (pair 4). Nuxt: skip to pair 8; the module calls the plugin for you.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// before
|
|
20
|
+
plugins: [vue(), tailwindcss()],
|
|
21
|
+
|
|
22
|
+
// after
|
|
23
|
+
import { nxgtMaterial } from '@nxgt/material-vue/vite';
|
|
24
|
+
|
|
25
|
+
plugins: [vue(), tailwindcss(), ...nxgtMaterial()],
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
bun add @fontsource-variable/inter # optional: to get Inter. Without it the plugin warns and keeps the system font;
|
|
30
|
+
# pass fonts: { sans: false } to keep it silently
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Verify**: `vite build` ends with a log line holding a ready
|
|
34
|
+
`Content-Security-Policy` header, and `dist/.vite/nxgt-csp.json` exists.
|
|
35
|
+
|
|
36
|
+
## The pairs
|
|
37
|
+
|
|
38
|
+
| # | Search for | Replace with |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| 1 | a hand-written static CSP header for a static host | [the generated header](#1-a-hand-written-static-csp-header) |
|
|
41
|
+
| 2 | `randomBytes(16).toString('base64')` plus a hand-written header | [`csp: { mode: 'nonce' }` and the helpers](#2-a-server-nonce-setup) |
|
|
42
|
+
| 3 | `build.assetsInlineLimit: cspAssetsInlineLimit` | [remove it](#3-assetsinlinelimit) |
|
|
43
|
+
| 4 | `@import "@fontsource-variable/inter"` plus `--font-sans` in `@theme` | [remove it, or `fonts: { sans: false }`](#4-the-inter-import) |
|
|
44
|
+
| 5 | a `zod-config.ts` with `z.config({ jitless: true })` | [remove it](#5-zod-configts) |
|
|
45
|
+
| 6 | `pdfkitStandardFonts()` plus `ssr.noExternal: ['@nxgt/material-vue', …]` | [`pdf: { bundle: true }`](#6-pdf-on-a-bundled-server) (Vite SSR server; for Nuxt see pair 8) |
|
|
46
|
+
| 7 | `<NonceProvider :nonce="getCspNonce()">` on the client | [`<NonceProvider>`](#7-nonceprovider-on-the-client) |
|
|
47
|
+
| 8 | Nuxt: a hand-made CSP of your own | [stays with `nuxt-security`](#8-nuxt) |
|
|
48
|
+
|
|
49
|
+
### 1. A hand-written static CSP header
|
|
50
|
+
|
|
51
|
+
Search the host configuration (`_headers`, a CDN rule, `nginx.conf`, a
|
|
52
|
+
`<meta http-equiv="Content-Security-Policy">`) for `script-src`. For a
|
|
53
|
+
statically hosted SPA, the old route was a header you wrote yourself, often
|
|
54
|
+
with `'unsafe-inline'` or `'unsafe-eval'` to make zod and Vite's inline code
|
|
55
|
+
run.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
# before (_headers)
|
|
59
|
+
/*
|
|
60
|
+
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; …
|
|
61
|
+
|
|
62
|
+
# after: the header the build prints and writes to dist/.vite/nxgt-csp.json
|
|
63
|
+
/*
|
|
64
|
+
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-…'; style-src-elem 'self' 'unsafe-inline'; …
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Copy `header` from `dist/.vite/nxgt-csp.json` (or from the build log) into the
|
|
68
|
+
host's configuration. Rebuild, and recopy it: the hashes follow the HTML. To
|
|
69
|
+
extend it (an API origin, say), use `nxgtMaterial({ csp: { policy: { directives: { 'connect-src': ["'self'", 'https://api.example.com'] } } } })`
|
|
70
|
+
and recopy.
|
|
71
|
+
|
|
72
|
+
**Verify**: open the deployed app with the console open: no `Refused to
|
|
73
|
+
execute inline script` and no `unsafe-eval` report. `nxgt-csp.json` lists
|
|
74
|
+
`scriptHashes` that cover the page's inline `<script>` elements.
|
|
75
|
+
|
|
76
|
+
### 2. A server nonce setup
|
|
77
|
+
|
|
78
|
+
Search `randomBytes(`, `crypto.randomUUID()` used as a nonce, `nonce-${`.
|
|
79
|
+
If the app has a server that serves or renders its HTML, pick nonce mode and
|
|
80
|
+
the library helpers (they use `globalThis.crypto`, so Node, Bun, Deno and
|
|
81
|
+
workers alike).
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// before (server)
|
|
85
|
+
const nonce = randomBytes(16).toString('base64');
|
|
86
|
+
res.setHeader('Content-Security-Policy', `default-src 'self'; script-src 'nonce-${nonce}' 'strict-dynamic'; …`);
|
|
87
|
+
const html = template.replaceAll('__NONCE__', nonce);
|
|
88
|
+
|
|
89
|
+
// vite.config.ts, after
|
|
90
|
+
nxgtMaterial({ csp: { mode: 'nonce' } })
|
|
91
|
+
|
|
92
|
+
// server, after
|
|
93
|
+
import { applyCspNonce, createCspNonce, cspPolicy } from '@nxgt/material-vue/lib';
|
|
94
|
+
|
|
95
|
+
const nonce = createCspNonce();
|
|
96
|
+
const html = applyCspNonce(builtHtml, nonce);
|
|
97
|
+
res.setHeader('Content-Security-Policy', cspPolicy({ nonce }).header);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The built HTML carries the public placeholder `NXGT_CSP_NONCE` (or the one you
|
|
101
|
+
set with `csp.placeholder` / Vite's `html.cspNonce`, which the plugin reuses):
|
|
102
|
+
**never serve it as it is**. `applyCspNonce` and `cspPolicy({ nonce })` throw
|
|
103
|
+
when given the placeholder. The `header` in `nxgt-csp.json` is a template in
|
|
104
|
+
this mode, not something to serve. Keep your own placeholder replacement only
|
|
105
|
+
if you must: then set `csp.placeholder` to its token.
|
|
106
|
+
|
|
107
|
+
**Verify**: `curl` a page twice: the `nonce="…"` values differ between
|
|
108
|
+
requests, equal the nonce in the response header, and the string
|
|
109
|
+
`NXGT_CSP_NONCE` appears nowhere in the response. No CSP violation in the
|
|
110
|
+
console.
|
|
111
|
+
|
|
112
|
+
### 3. `assetsInlineLimit`
|
|
113
|
+
|
|
114
|
+
Search `cspAssetsInlineLimit` and `assetsInlineLimit` in `vite.config.*`. The
|
|
115
|
+
plugin's `fonts.katex` (on by default) keeps every font out of `data:` URIs,
|
|
116
|
+
and composes with a `build.assetsInlineLimit` of your own.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// before
|
|
120
|
+
build: { assetsInlineLimit: cspAssetsInlineLimit },
|
|
121
|
+
|
|
122
|
+
// after (delete the line; keep a limit of your own if you have one)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Verify**: after `vite build`, `grep -l "data:font" dist/assets/*` finds
|
|
126
|
+
nothing, and the KaTeX `.woff2` files are listed under `dist/assets/`.
|
|
127
|
+
|
|
128
|
+
### 4. The Inter import
|
|
129
|
+
|
|
130
|
+
Search the app stylesheet for `@fontsource-variable/inter` and `--font-sans`.
|
|
131
|
+
The plugin imports Inter in the stylesheet that imports
|
|
132
|
+
`@nxgt/material-vue/styles.css` and sets `--font-sans` unlayered, and it skips
|
|
133
|
+
both when it finds your own, so the old lines are optional to remove. Inter needs
|
|
134
|
+
`@fontsource-variable/inter` installed (`bun add @fontsource-variable/inter`):
|
|
135
|
+
without it the plugin warns once and keeps the system font, and the build still
|
|
136
|
+
succeeds. `fonts: { sans: false }` keeps the system font without the warning.
|
|
137
|
+
|
|
138
|
+
```css
|
|
139
|
+
/* before */
|
|
140
|
+
@import "@fontsource-variable/inter";
|
|
141
|
+
@theme { --font-sans: "Inter Variable", sans-serif; }
|
|
142
|
+
|
|
143
|
+
/* after: delete both lines (the plugin adds them), or keep them
|
|
144
|
+
and write nxgtMaterial({ fonts: { sans: false } }) */
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A JavaScript `import '@fontsource-variable/inter'` is not detected: keep it only
|
|
148
|
+
with `fonts: { sans: false }`. If you use another font, also pass
|
|
149
|
+
`fonts: { sans: false }`.
|
|
150
|
+
|
|
151
|
+
**Verify**: the built CSS has `@font-face` rules for `Inter Variable` and
|
|
152
|
+
`:root{--font-sans:…}`, the font files are same-origin, and no `font-src`
|
|
153
|
+
violation shows in the console.
|
|
154
|
+
|
|
155
|
+
### 5. `zod-config.ts`
|
|
156
|
+
|
|
157
|
+
Search `z.config({ jitless: true })` and the `import './zod-config'` that
|
|
158
|
+
precedes the app. Remove both: the plugin injects zod's jitless script
|
|
159
|
+
(`ZOD_JITLESS_SCRIPT`) first in `index.html`, and hashes it (hash mode) or
|
|
160
|
+
stamps it with the nonce (nonce mode).
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// before
|
|
164
|
+
import './zod-config'; // first
|
|
165
|
+
import './app';
|
|
166
|
+
|
|
167
|
+
// after
|
|
168
|
+
import './app';
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Keep the file in an app that renders HTML on its own server with no
|
|
172
|
+
`index.html` (an SSR framework or Nuxt): there, put `zodJitlessTag(nonce)` first in
|
|
173
|
+
the head instead (see [CSP: SSR](csp.md#ssr)).
|
|
174
|
+
|
|
175
|
+
**Verify**: `grep -c "__zod_globalConfig" dist/index.html` prints at least `1`, the
|
|
176
|
+
script has no `nonce` in hash mode, and the console shows no `unsafe-eval`
|
|
177
|
+
report when a form mounts.
|
|
178
|
+
|
|
179
|
+
### 6. PDF on a bundled server
|
|
180
|
+
|
|
181
|
+
Search `pdfkitStandardFonts` and `noExternal` in `vite.config.*`. This is the
|
|
182
|
+
pair for a Vite SSR server that renders `@nxgt/material-vue/pdf` and ships its
|
|
183
|
+
bundle with no `node_modules`.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// before
|
|
187
|
+
plugins: [pdfkitStandardFonts()],
|
|
188
|
+
ssr: { noExternal: ['@nxgt/material-vue', '@nxgt/material-core'] },
|
|
189
|
+
|
|
190
|
+
// after
|
|
191
|
+
plugins: [...nxgtMaterial({ pdf: { bundle: true } })],
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Keep an `ssr.noExternal` entry for what the plugin does not walk (your
|
|
195
|
+
app's own dependencies). Your own array is merged, never replaced.
|
|
196
|
+
|
|
197
|
+
**Verify**: `vite build` for the server, run the server from a folder with no
|
|
198
|
+
`node_modules`, and request a PDF: no `ENOENT … .afm` error.
|
|
199
|
+
|
|
200
|
+
### 7. `NonceProvider` on the client
|
|
201
|
+
|
|
202
|
+
Search `getCspNonce()` next to `NonceProvider` in the client entry. On the
|
|
203
|
+
client, a provider without a `nonce` prop reads the nonce the page carries in
|
|
204
|
+
`setup`, from its first render: the one the server passed, so the hydration
|
|
205
|
+
pass renders what the server did. The server keeps passing its request's
|
|
206
|
+
nonce: it has no page to read.
|
|
207
|
+
|
|
208
|
+
```vue
|
|
209
|
+
<!-- before (client) -->
|
|
210
|
+
<NonceProvider :nonce="getCspNonce()"><RouterView /></NonceProvider>
|
|
211
|
+
|
|
212
|
+
<!-- after -->
|
|
213
|
+
<NonceProvider><RouterView /></NonceProvider>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Leave the server render as it is. In hash mode there is no nonce at all:
|
|
217
|
+
`<NonceProvider>` renders its children, and the `<style>` elements the
|
|
218
|
+
components create are allowed by the header's `style-src-elem 'self'
|
|
219
|
+
'unsafe-inline'`. Nonce mode is the strict option for styles.
|
|
220
|
+
|
|
221
|
+
**Verify**: no hydration warning in the console, and a `Select` or a
|
|
222
|
+
`ScrollArea` `<style>` carries the page's nonce in nonce mode.
|
|
223
|
+
|
|
224
|
+
### 8. Nuxt
|
|
225
|
+
|
|
226
|
+
The Nuxt module calls `nxgtMaterial()` for you with its own options, and keeps
|
|
227
|
+
the CSP **off**: a Nuxt page is rendered per request, so there is no build-time
|
|
228
|
+
`index.html` to hash or stamp. Pairs 1 and 2 do not apply: keep `nuxt-security`
|
|
229
|
+
for the policy. What does apply:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// nuxt.config.ts, before
|
|
233
|
+
export default defineNuxtConfig({
|
|
234
|
+
nxgtMaterial: { pdf: { bundleStandardFonts: true } },
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// after
|
|
238
|
+
export default defineNuxtConfig({
|
|
239
|
+
nxgtMaterial: { pdf: { bundle: true } },
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`pdf.bundleStandardFonts` still works and warns until you rename it. For zod
|
|
244
|
+
(pair 5), keep the `z.config` file or set `nxgtMaterial: { csp: { zodJitless: true } }`,
|
|
245
|
+
which puts the script in every head; `nuxt-security` with `nonce: true`
|
|
246
|
+
stamps it.
|
|
247
|
+
|
|
248
|
+
**Verify**: `nuxt build` prints no `bundleStandardFonts is deprecated` warning,
|
|
249
|
+
and the server output renders a PDF without `node_modules`.
|
|
250
|
+
|
|
251
|
+
## Checking the whole migration
|
|
252
|
+
|
|
253
|
+
1. `vite build` succeeds and prints the header (hash mode), or `nxgt-csp.json`
|
|
254
|
+
exists (both modes).
|
|
255
|
+
2. Serve the build with that header (or the nonce flow of pair 2).
|
|
256
|
+
3. Open the app and exercise a form, a select and a modal. The console
|
|
257
|
+
shows no CSP violation.
|
|
258
|
+
4. `git grep -n "zod-config\|cspAssetsInlineLimit\|fontsource-variable/inter"`
|
|
259
|
+
lists only what you kept on purpose.
|
package/docs/guides/pdf.md
CHANGED
|
@@ -281,20 +281,27 @@ artifact, `nitro: { noExternals: true }`), bundle the fonts in:
|
|
|
281
281
|
// nuxt.config.ts
|
|
282
282
|
export default defineNuxtConfig({
|
|
283
283
|
modules: ['@nxgt/material-vue/nuxt'],
|
|
284
|
-
nxgtMaterial: { pdf: {
|
|
284
|
+
nxgtMaterial: { pdf: { bundle: true } }, // was pdf.bundleStandardFonts
|
|
285
285
|
});
|
|
286
286
|
```
|
|
287
287
|
|
|
288
288
|
```ts
|
|
289
289
|
// vite.config.ts (a Vite SSR server without Nuxt)
|
|
290
|
-
import {
|
|
290
|
+
import { nxgtMaterial } from '@nxgt/material-vue/vite';
|
|
291
291
|
|
|
292
292
|
export default defineConfig({
|
|
293
|
-
plugins: [
|
|
294
|
-
ssr: { noExternal: ['@nxgt/material-vue', '@nxgt/material-core'] },
|
|
293
|
+
plugins: [...nxgtMaterial({ pdf: { bundle: true } })],
|
|
295
294
|
});
|
|
296
295
|
```
|
|
297
296
|
|
|
297
|
+
`pdf: { bundle: true }` adds the standard-fonts transform
|
|
298
|
+
(`pdfkitStandardFonts()`, still exported on its own) and puts the library, core,
|
|
299
|
+
the renderer, pdfkit and the renderer's runtime dependencies into
|
|
300
|
+
`ssr.noExternal`, merged with your own (`react` and your app's own dependencies
|
|
301
|
+
are yours to bundle). It replaces
|
|
302
|
+
the hand-written `plugins: [pdfkitStandardFonts()]` plus `ssr.noExternal` pair
|
|
303
|
+
([Vite plugin guide](vite-plugin.md#rendering-pdfs-on-a-bundled-server)).
|
|
304
|
+
|
|
298
305
|
`@nxgt/material-core`, which the library's built code imports by name, is
|
|
299
306
|
bundled with it: leave it out and the server fails at boot with
|
|
300
307
|
[`Cannot find package '@nxgt/material-core'`](../troubleshooting.md#cannot-find-package-nxgtmaterial-core).
|
package/docs/guides/ssr.md
CHANGED
|
@@ -134,7 +134,7 @@ What to know in a Nuxt app:
|
|
|
134
134
|
- **A PDF from `@nxgt/material-vue/pdf`** is imported where you use it, like
|
|
135
135
|
the map. `usePdf` renders nothing on the server, and a Nitro route answers
|
|
136
136
|
with `pdfResponse`; a server with no `node_modules` needs
|
|
137
|
-
`nxgtMaterial: { pdf: {
|
|
137
|
+
`nxgtMaterial: { pdf: { bundle: true } }` ([PDF documents](pdf.md)).
|
|
138
138
|
- **Your own browser-only component**: name it `*.client.vue` (Nuxt then
|
|
139
139
|
leaves it out of the server bundle), or use `clientOnly` +
|
|
140
140
|
`defineAsyncComponent` as in the next section. Wrapping a statically
|
package/docs/guides/theming.md
CHANGED
|
@@ -40,6 +40,10 @@ build:
|
|
|
40
40
|
and `[data-theme="dark"]`;
|
|
41
41
|
- a `@theme` block that maps each token to a Tailwind colour, so `bg-primary`,
|
|
42
42
|
`text-error-foreground`, `border-border` are real utilities;
|
|
43
|
+
- the **background presets' base, gradient and motion CSS** (`lib/backgrounds/css/`),
|
|
44
|
+
which `Background` from `/components` draws with: plain CSS keyed on
|
|
45
|
+
`data-background`, in `@layer components`, so a utility on the element wins.
|
|
46
|
+
The other presets are opt-in ([backgrounds](backgrounds.md));
|
|
43
47
|
- a `dark` custom variant, base styles (scrollbars, borders, selection, body
|
|
44
48
|
colours), and a few utilities: `center`, `size-lv`, `col-start-full`,
|
|
45
49
|
`h-fill`, `w-fill`, `size-fill`.
|
|
@@ -51,10 +55,12 @@ build:
|
|
|
51
55
|
| `--background`, `--foreground` | `bg-background`, `text-foreground` | page and text |
|
|
52
56
|
| `--primary`, `--primary-foreground` | `bg-primary`, `text-primary-foreground` | filled buttons, active states, focus |
|
|
53
57
|
| `--secondary`, `--secondary-foreground` | `bg-secondary`, … | the secondary colour |
|
|
58
|
+
| `--tertiary`, `--tertiary-foreground` | `bg-tertiary`, `text-tertiary`, `bg-tertiary-container`, … | the tertiary colour (strix violet) |
|
|
54
59
|
| `--muted`, `--muted-foreground` | `bg-muted`, `text-muted-foreground` | quiet surfaces, hints, placeholders |
|
|
55
60
|
| `--track`, `--track-fill` | `bg-track`, `bg-track-fill`, `border-track-fill` | the empty part of a slider or meter, the drawer handle; the slider range and thumb |
|
|
56
61
|
| `--accent`, `--accent-foreground` | `bg-accent`, … | hover surfaces |
|
|
57
62
|
| `--error`, `--success`, `--info`, `--warning` (+ `-foreground`) | `bg-error`, `text-success`, … | the status colours of `Button`, `Alert`, `Badge`, toasts |
|
|
63
|
+
| `--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. Equal to `--primary` in light; in dark, `--primary` mixed 60 % with white (`color-mix(in oklab, var(--primary) 40%, white)`), well over 4.5:1 on `--background` and `--card` |
|
|
58
64
|
| `--error-text` | `text-error-text` | error drawn as text: field messages, `HelperText` in its error state, form and dialog errors, `variant="error"` menu items |
|
|
59
65
|
| `--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` |
|
|
60
66
|
| `--card`, `--popover`, `--tooltip` (+ `-foreground`) | `bg-card`, `bg-popover`, `bg-tooltip` | surfaces |
|
|
@@ -130,6 +136,26 @@ import { Button } from '@nxgt/material-vue/components';
|
|
|
130
136
|
</template>
|
|
131
137
|
```
|
|
132
138
|
|
|
139
|
+
## Links
|
|
140
|
+
|
|
141
|
+
`styles.css` styles every bare `<a>` as a link in running text: the info
|
|
142
|
+
colour, an underline on hover and focus, an info tint behind it. Links the
|
|
143
|
+
components render with a look of their own (`Toc`, `SeeAlso`, `Pager`,
|
|
144
|
+
`LinkCard`, `ListTile`, the doc shell's links, `BreadcrumbLink`, `SkipLink`,
|
|
145
|
+
the sidebar entries) opt out of it. Do the same for an anchor of yours that
|
|
146
|
+
has its own look, such as a card, a menu entry or a button-like link: add the
|
|
147
|
+
`nxgt-plain-link` class.
|
|
148
|
+
|
|
149
|
+
```vue
|
|
150
|
+
<a href="/pricing" class="nxgt-plain-link flex items-center gap-2 rounded p-2 hover:bg-muted">
|
|
151
|
+
Pricing
|
|
152
|
+
</a>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The class only removes the prose style (colour, underline, tint, pointer);
|
|
156
|
+
the anchor then inherits its text colour like any other element, so style
|
|
157
|
+
the rest yourself. Without the class the anchor keeps the prose style.
|
|
158
|
+
|
|
133
159
|
## Contrast and reduced motion
|
|
134
160
|
|
|
135
161
|
**Contrast.** The shipped light palette keeps the text of every filled status
|
|
@@ -139,7 +165,10 @@ colour at 4.5:1 or more (WCAG 1.4.3): `--secondary` 5.7:1, `--success` 5.6:1,
|
|
|
139
165
|
`--background`, 4.8:1 on its own 15 % `tonal` tint.
|
|
140
166
|
|
|
141
167
|
The dark theme keeps brighter values, read as text on the dark surfaces: the
|
|
142
|
-
dark `--
|
|
168
|
+
dark `--tertiary` (`oklch(0.7 0.15 310.75)`) is 7.0:1 on `--background` and
|
|
169
|
+
6.3:1 on `--card`, with a dark `--tertiary-foreground` that keeps 7.0:1 on
|
|
170
|
+
a filled tertiary surface (light: 7.6:1 under near-white). The dark
|
|
171
|
+
`--secondary` (`oklch(0.66 0.1135 245.65)`) is 6.5:1 on `--background`
|
|
143
172
|
and 5.8:1 on `--card` (a `tonal` secondary `Button` 5.4:1 and 4.6:1). Under
|
|
144
173
|
near-white text those brights are too light, so every filled surface built on
|
|
145
174
|
them is tinted in the dark theme: a filled `secondary` `Button`, `IconButton`
|
|
@@ -162,12 +191,12 @@ their text and icon in one tone per colour, read on `--background`, on
|
|
|
162
191
|
the light theme `primary`, `secondary`, `info` and `success` use the token
|
|
163
192
|
itself (5.8, 4.8, 4.9 and 4.7:1 at their lowest) and `warning` and `error` the
|
|
164
193
|
token mixed with 15 % black (6.4 and 5.4:1). In the dark theme `primary` is
|
|
165
|
-
mixed with 60 % white (8.1:1 on `--card`: the token itself is a dark indigo,
|
|
194
|
+
mixed with 60 % white, the `--primary-text` token (8.1:1 on `--card`: the token itself is a dark indigo,
|
|
166
195
|
2.2:1), `success` and `warning` with 15 % white (5.0 and 6.3:1), `error` with
|
|
167
196
|
30 % white (5.9:1), and `secondary` and `info` use the token (4.6 and 5.3:1).
|
|
168
197
|
The tones are mixed from the tokens (`color-mix(in oklab, var(--color-error)
|
|
169
198
|
85%, black)`), so an override of a token moves its tone with it; a text colour
|
|
170
|
-
of your own on one of those buttons (`class="text-primary"`) replaces the tone
|
|
199
|
+
of your own on one of those buttons (`class="text-primary-text"`) replaces the tone
|
|
171
200
|
and is yours to check.
|
|
172
201
|
|
|
173
202
|
A `Badge` used as a link (`as-child` around an `<a>`) darkens on hover like a filled
|
|
@@ -230,12 +259,31 @@ and `.dark, [data-theme="dark"]` moves it, but setting `--error-text` or
|
|
|
230
259
|
`--color-error-text` (and `--color-error` for fills) on that element, or put
|
|
231
260
|
the override on the root.
|
|
232
261
|
|
|
262
|
+
**Primary text has a token of its own**, `--primary-text` (`text-primary-text`),
|
|
263
|
+
for the same reason: the brand colour is a dark fill (it keeps near-white text
|
|
264
|
+
readable on a button) and reads at only about 2:1 as text on the dark surfaces.
|
|
265
|
+
The light value is `var(--primary)`; the dark one is
|
|
266
|
+
`color-mix(in oklab, var(--primary) 40%, white)` (primary mixed 60 % with
|
|
267
|
+
white, the recipe of the tonal `Button` labels). `Typography tone="primary"`,
|
|
268
|
+
the `Alert` primary icon, links, selected labels and the other primary text of
|
|
269
|
+
the catalogue use it.
|
|
270
|
+
|
|
271
|
+
- **`text-primary-text`** for primary written as text, or an icon beside that
|
|
272
|
+
text, on a page, card or popover surface.
|
|
273
|
+
- **`text-primary`** (with `bg-primary`, `border-primary`, `ring-primary`) for
|
|
274
|
+
fills, borders and rings. Text on a primary fill is `text-primary-foreground`.
|
|
275
|
+
|
|
276
|
+
Like `--error-text`, it is computed where it is declared, on the root: an
|
|
277
|
+
override of `--primary` under `:root` and `.dark, [data-theme="dark"]` moves it
|
|
278
|
+
(override `--primary-text` itself after it to pick another dark colour); below
|
|
279
|
+
the root, set `--color-primary-text` on the element.
|
|
280
|
+
|
|
233
281
|
Storybook's `Guides/Theming/Contrast` measures all of this in both themes:
|
|
234
282
|
the surfaces' text, the error text (`HelperText` and a field's message on
|
|
235
283
|
`--background` and `--card`), the `Alert`s and the `Timeline`, `Button` and
|
|
236
284
|
`IconButton` in every variant and colour, and a `Badge` link in every colour,
|
|
237
285
|
at rest, hovered, focused and pressed, the tracks and the tabs;
|
|
238
|
-
`Data display/ListTile` measures the selected tile, `Data table, filter & lists/DataTable/On a card` the header, the cells, the links and the selected rows on a page and on a `Card` (also right-to-left), and `Feedback & status/Alert/Tones contrast`
|
|
286
|
+
`Data display/ListTile` measures the selected tile, `Data table, filter & lists/DataTable/On a card` the header, the cells, the links and the selected rows on a page and on a `Card` (also right-to-left), and `Feedback & status/Alert/Tones contrast` every alert tone there, and `Primitives/Typography/Tones contrast` the text tones (primary included). Run them against your own
|
|
239
287
|
tokens after an override.
|
|
240
288
|
|
|
241
289
|
**Tabs.** An `underline` tab's active label is `--primary` in the light
|