@m3ui-vue/nuxt 0.1.1 → 0.2.1
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/README.md +55 -9
- package/dist/module.d.mts +10 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +18 -4
- package/dist/runtime/plugin.js +17 -3
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -10,6 +10,11 @@ Nuxt module for [m3ui-vue](https://m3ui.dev), the Material Design 3 component li
|
|
|
10
10
|
It installs the library's plugin, auto-imports its components and composables, adds its stylesheet, and puts the
|
|
11
11
|
user's theme (dark mode, palette, contrast) on the server-rendered page before the first paint.
|
|
12
12
|
|
|
13
|
+
> **Beta.** Server-side rendering and this module are new. Every component renders on the server and is tested
|
|
14
|
+
> there, but real apps will find cases we haven't: if something breaks, renders differently on the server, or Vue
|
|
15
|
+
> warns about a hydration mismatch, please [open an issue](https://github.com/SrWither/m3ui-vue-nuxt/issues) (or on
|
|
16
|
+
> [m3ui-vue](https://github.com/SrWither/m3ui-vue/issues) for a component).
|
|
17
|
+
|
|
13
18
|
## Install
|
|
14
19
|
|
|
15
20
|
```bash
|
|
@@ -87,27 +92,60 @@ Everything `createM3UI()` takes, plus what the module itself does:
|
|
|
87
92
|
| Option | Default | |
|
|
88
93
|
|---|---|---|
|
|
89
94
|
| `palette`, `seed`, `seedOptions`, `contrast`, `persistPalette`, `customPalettes`, `themeColor` | | As in `createM3UI()` |
|
|
90
|
-
| `locale` | English | A preset's code (`'es'`, `'fr'`, `'pt'`, `'de'`, `'ja'`, `'zh'`, `'ko'`)
|
|
95
|
+
| `locale` | English | A preset's code (`'es'`, `'fr'`, `'pt'`, `'de'`, `'ja'`, `'zh'`, `'ko'`), your own strings, or `'auto'` to follow `@nuxtjs/i18n` |
|
|
91
96
|
| `components` | `true` | Auto-import the components |
|
|
92
97
|
| `composables` | `{ prefix: 'M3' }` | Auto-import the composables with this prefix; `false` to import them yourself |
|
|
93
98
|
| `css` | `true` | Add `@m3ui-vue/m3ui-vue/styles` |
|
|
99
|
+
| `cookies` | `true` | Keep the theme, palette and contrast in cookies too, so the server renders with them |
|
|
94
100
|
| `themeScript` | `true` | Inline `themeInitScript()` in `<head>` |
|
|
95
101
|
|
|
102
|
+
## With @nuxtjs/i18n
|
|
103
|
+
|
|
104
|
+
`locale: 'auto'` makes the components' built-in text (pagination, pickers, dialogs…) follow `@nuxtjs/i18n`'s current
|
|
105
|
+
language, on the server and when the user switches it. A language tag picks its language's preset (`es-AR` → Spanish);
|
|
106
|
+
languages without one fall back to English.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
export default defineNuxtConfig({
|
|
110
|
+
modules: ['@m3ui-vue/nuxt', '@nuxtjs/i18n'],
|
|
111
|
+
i18n: {
|
|
112
|
+
locales: [{ code: 'en', language: 'en-US' }, { code: 'es', language: 'es-AR' }],
|
|
113
|
+
defaultLocale: 'en',
|
|
114
|
+
},
|
|
115
|
+
m3ui: { locale: 'auto' },
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
With `'auto'` all seven presets are bundled (about 11 kB gzipped), since the language can change at any time.
|
|
120
|
+
|
|
96
121
|
## Server-side rendering
|
|
97
122
|
|
|
98
|
-
The user's theme
|
|
99
|
-
|
|
100
|
-
on the server
|
|
101
|
-
|
|
123
|
+
The library keeps the user's theme, palette and contrast in `localStorage`, and with `cookies` (on by default) in
|
|
124
|
+
cookies too, which the server reads. So `useM3Theme()` and `useM3ColorPalette()` report the user's choice while the
|
|
125
|
+
page renders on the server, and anything built from them (a theme toggle's icon, the selected palette swatch) hydrates
|
|
126
|
+
without mismatches, no `<ClientOnly>` needed:
|
|
102
127
|
|
|
103
128
|
```vue
|
|
104
|
-
<
|
|
129
|
+
<template>
|
|
105
130
|
<MIconButton :icon="theme === 'dark' ? 'dark_mode' : 'light_mode'" label="Theme" @click="cycle" />
|
|
106
|
-
</
|
|
131
|
+
</template>
|
|
132
|
+
|
|
133
|
+
<script setup lang="ts">
|
|
134
|
+
const { theme, cycle } = useM3Theme()
|
|
135
|
+
</script>
|
|
107
136
|
```
|
|
108
137
|
|
|
138
|
+
The inlined script puts dark mode, the palette and the contrast on `<html>` before the page paints. What the server
|
|
139
|
+
can't know is the system's preferences: with the `'system'` theme or contrast, `theme` is `'system'` there (whether
|
|
140
|
+
the page ends up dark is decided in the browser), and `activeContrast` is `'standard'`. If cookies were cleared but
|
|
141
|
+
`localStorage` wasn't, the first render shows the defaults and switches to the stored choice once hydrated. The other way
|
|
142
|
+
around, a choice only in the cookies is used too: your server can set `m3-theme`, `m3-palette` or `m3-contrast` to apply
|
|
143
|
+
a theme saved in the user's account, for instance.
|
|
144
|
+
|
|
109
145
|
The adaptive layouts (`MNavigationSuite`, `MListDetail`, `MSupportingPane`) measure their own size, which the server
|
|
110
|
-
|
|
146
|
+
can't. With `cookies`, the window's size is kept in a cookie too (`m3-viewport`), so from the second visit on they
|
|
147
|
+
render on the server for the user's screen (one in a narrower column corrects itself on the client). On a first visit
|
|
148
|
+
they render their compact layout and adapt once the page is interactive.
|
|
111
149
|
|
|
112
150
|
A `seed` default can't be generated before the app loads: on the first visit the page shows the baseline palette
|
|
113
151
|
until it does (later visits use the cached scheme).
|
|
@@ -118,10 +156,18 @@ until it does (later visits use the cached scheme).
|
|
|
118
156
|
pnpm install
|
|
119
157
|
pnpm dev:prepare # stub the module and generate types
|
|
120
158
|
pnpm dev # the playground, with the library linked from ../m3ui-vue
|
|
121
|
-
pnpm test # builds
|
|
159
|
+
pnpm test # builds fixture apps, checks their server-rendered HTML and hydrates one in Chromium
|
|
122
160
|
pnpm test:types
|
|
123
161
|
```
|
|
124
162
|
|
|
163
|
+
The hydration test (`test/browser.test.ts`) drives the Chromium already on the machine through `playwright-core` (no
|
|
164
|
+
browser download): `/usr/bin/chromium` and the usual paths, or `CHROMIUM_PATH`. It fails on any hydration warning
|
|
165
|
+
or page error, including overlays open on load; without a Chromium it's skipped.
|
|
166
|
+
|
|
167
|
+
`dev:prepare` stubs the module (it loads `src/module.ts` on the fly), and with the stub the editor types the `m3ui`
|
|
168
|
+
options in the playground's `nuxt.config.ts` loosely (no autocompletion of palette ids, for instance). `pnpm build`
|
|
169
|
+
followed by `nuxi prepare playground` types them like the published package.
|
|
170
|
+
|
|
125
171
|
## License
|
|
126
172
|
|
|
127
173
|
MIT
|
package/dist/module.d.mts
CHANGED
|
@@ -15,9 +15,10 @@ type LocalePreset = keyof typeof LOCALE_PRESETS | 'en';
|
|
|
15
15
|
interface ModuleOptions extends Omit<M3UIOptions, 'locale'> {
|
|
16
16
|
/**
|
|
17
17
|
* Built-in text: a preset's language code (`'es'`, `'fr'`, `'pt'`, `'de'`, `'ja'`, `'zh'`, `'ko'`; `'en'` is the
|
|
18
|
-
* default)
|
|
18
|
+
* default), your own strings, which override English, or `'auto'` to follow `@nuxtjs/i18n`'s current language
|
|
19
|
+
* (by its language tag, `es-AR` → `es`; English where there's no preset), switching with it.
|
|
19
20
|
*/
|
|
20
|
-
locale?: LocalePreset | Partial<M3Locale>;
|
|
21
|
+
locale?: LocalePreset | 'auto' | Partial<M3Locale>;
|
|
21
22
|
/** Auto-import the components (`<MButton>`…). Each one is bundled only where it's used. Default `true`. */
|
|
22
23
|
components?: boolean;
|
|
23
24
|
/**
|
|
@@ -30,6 +31,13 @@ interface ModuleOptions extends Omit<M3UIOptions, 'locale'> {
|
|
|
30
31
|
};
|
|
31
32
|
/** Add the components' own stylesheet (`@m3ui-vue/m3ui-vue/styles`). Default `true`. */
|
|
32
33
|
css?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Remember the theme, the palette and the contrast level in cookies too, so the server renders with the user's
|
|
36
|
+
* choice: `useM3Theme()` and `useM3ColorPalette()` report it during server rendering and hydration, and UI built
|
|
37
|
+
* from them needs no `<ClientOnly>`. The window's size goes in one too, so the adaptive layouts (`MNavigationSuite`,
|
|
38
|
+
* `MListDetail`, `MSupportingPane`) render for the user's screen instead of compact. Default `true`.
|
|
39
|
+
*/
|
|
40
|
+
cookies?: boolean;
|
|
33
41
|
/**
|
|
34
42
|
* Inline `themeInitScript()` in `<head>`, so dark mode, the palette and the contrast level the user picked are
|
|
35
43
|
* on the server-rendered page before the first paint. Default `true`.
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
|
-
import { defineNuxtModule, useLogger, createResolver, resolvePath, tryResolveModule, addComponent, addImports, addTemplate, addTypeTemplate, addPlugin } from '@nuxt/kit';
|
|
3
|
+
import { defineNuxtModule, useLogger, createResolver, resolvePath, tryResolveModule, addComponent, addImports, hasNuxtModule, addTemplate, addTypeTemplate, addPlugin } from '@nuxt/kit';
|
|
4
4
|
|
|
5
5
|
const LIB = "@m3ui-vue/m3ui-vue";
|
|
6
6
|
const LOCALE_PRESETS = { es: "esLocale", fr: "frLocale", pt: "ptLocale", de: "deLocale", ja: "jaLocale", zh: "zhLocale", ko: "koLocale" };
|
|
@@ -23,12 +23,13 @@ const module$1 = defineNuxtModule({
|
|
|
23
23
|
components: true,
|
|
24
24
|
composables: { prefix: "M3" },
|
|
25
25
|
css: true,
|
|
26
|
+
cookies: true,
|
|
26
27
|
themeScript: true
|
|
27
28
|
},
|
|
28
29
|
async setup(options, nuxt) {
|
|
29
30
|
const logger = useLogger("@m3ui-vue/nuxt");
|
|
30
31
|
const resolver = createResolver(import.meta.url);
|
|
31
|
-
const { components, composables, css, themeScript, locale, ...plugin } = options;
|
|
32
|
+
const { components, composables, css, cookies, themeScript, locale, ...plugin } = options;
|
|
32
33
|
const entry = await resolvePath(LIB, { extensions: [".js", ".mjs"] });
|
|
33
34
|
let api;
|
|
34
35
|
try {
|
|
@@ -49,14 +50,24 @@ const module$1 = defineNuxtModule({
|
|
|
49
50
|
addImports(api.values.filter((value) => /^use[A-Z]/.test(value.name)).map((value) => ({ name: value.name, as: `use${prefix}${value.name.slice(3)}`, from: value.import })));
|
|
50
51
|
}
|
|
51
52
|
if (css) nuxt.options.css.push(`${LIB}/styles`);
|
|
52
|
-
const
|
|
53
|
-
|
|
53
|
+
const auto = locale === "auto";
|
|
54
|
+
const preset = typeof locale === "string" && locale !== "en" && !auto ? LOCALE_PRESETS[locale] : void 0;
|
|
55
|
+
if (typeof locale === "string" && locale !== "en" && !auto && !preset) logger.warn(`Unknown locale preset '${locale}', using English`);
|
|
56
|
+
if (auto) {
|
|
57
|
+
nuxt.hook("modules:done", () => {
|
|
58
|
+
if (!hasNuxtModule("@nuxtjs/i18n")) logger.warn(`locale: 'auto' follows @nuxtjs/i18n, which isn't installed: using English`);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
54
61
|
addTemplate({
|
|
55
62
|
filename: "m3ui.options.mjs",
|
|
56
63
|
getContents: () => [
|
|
57
64
|
preset ? `import { ${preset} } from '${LIB}/locales'` : "",
|
|
65
|
+
auto ? `import * as presets from '${LIB}/locales'` : "",
|
|
58
66
|
`export const options = ${JSON.stringify(plugin)}`,
|
|
59
67
|
`export const locale = ${preset ?? (typeof locale === "object" ? JSON.stringify(locale) : "undefined")}`,
|
|
68
|
+
`export const autoLocale = ${auto}`,
|
|
69
|
+
`export const localePresets = ${auto ? `{ ${Object.entries(LOCALE_PRESETS).map(([code, name]) => `${code}: presets.${name}`).join(", ")} }` : "{}"}`,
|
|
70
|
+
`export const cookies = ${JSON.stringify(cookies)}`,
|
|
60
71
|
`export const themeScript = ${JSON.stringify(themeScript)}`
|
|
61
72
|
].join("\n")
|
|
62
73
|
});
|
|
@@ -66,6 +77,9 @@ const module$1 = defineNuxtModule({
|
|
|
66
77
|
`declare module '#build/m3ui.options.mjs' {`,
|
|
67
78
|
` export const options: import('${LIB}').M3UIOptions`,
|
|
68
79
|
` export const locale: Partial<import('${LIB}').M3Locale> | undefined`,
|
|
80
|
+
` export const autoLocale: boolean`,
|
|
81
|
+
` export const localePresets: Record<string, Partial<import('${LIB}').M3Locale>>`,
|
|
82
|
+
` export const cookies: boolean`,
|
|
69
83
|
` export const themeScript: boolean`,
|
|
70
84
|
`}`
|
|
71
85
|
].join("\n")
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
1
2
|
import { createM3UI, themeInitScript } from "@m3ui-vue/m3ui-vue";
|
|
2
|
-
import { defineNuxtPlugin, useHead } from "#app";
|
|
3
|
-
import { locale, options, themeScript } from "#build/m3ui.options.mjs";
|
|
3
|
+
import { defineNuxtPlugin, useCookie, useHead } from "#app";
|
|
4
|
+
import { autoLocale, cookies, locale, localePresets, options, themeScript } from "#build/m3ui.options.mjs";
|
|
4
5
|
export default defineNuxtPlugin({
|
|
5
6
|
name: "m3ui",
|
|
6
7
|
enforce: "pre",
|
|
7
8
|
setup(nuxtApp) {
|
|
8
|
-
|
|
9
|
+
const read = (name) => useCookie(name, { decode: decodeURIComponent, readonly: true }).value;
|
|
10
|
+
const [width, height] = (read("m3-viewport") ?? "").split("x").map(Number);
|
|
11
|
+
const initialState = cookies ? {
|
|
12
|
+
theme: read("m3-theme"),
|
|
13
|
+
palette: read("m3-palette"),
|
|
14
|
+
contrast: read("m3-contrast"),
|
|
15
|
+
viewport: width && height ? { width, height } : void 0
|
|
16
|
+
} : void 0;
|
|
17
|
+
const appLocale = autoLocale ? computed(() => {
|
|
18
|
+
const i18n = nuxtApp.$i18n;
|
|
19
|
+
const tag = i18n?.localeProperties?.value.language || i18n?.locale.value || "";
|
|
20
|
+
return localePresets[tag.toLowerCase().split("-")[0]] ?? {};
|
|
21
|
+
}) : locale;
|
|
22
|
+
nuxtApp.vueApp.use(createM3UI({ ...options, locale: appLocale, cookies, initialState, isHydrating: () => !!nuxtApp.isHydrating }));
|
|
9
23
|
if (import.meta.server && themeScript) {
|
|
10
24
|
useHead({
|
|
11
25
|
script: [{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m3ui-vue/nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Nuxt module for m3ui-vue, the Material Design 3 component library for Vue 3 + Tailwind CSS v4",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
@@ -49,16 +49,18 @@
|
|
|
49
49
|
"@nuxt/kit": "^4.6.2"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@m3ui-vue/m3ui-vue": "^1.0
|
|
52
|
+
"@m3ui-vue/m3ui-vue": "^1.1.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@m3ui-vue/m3ui-vue": "^1.0
|
|
55
|
+
"@m3ui-vue/m3ui-vue": "^1.1.0",
|
|
56
56
|
"@nuxt/module-builder": "^1.0.3",
|
|
57
57
|
"@nuxt/schema": "^4.6.2",
|
|
58
58
|
"@nuxt/test-utils": "^4.3.4",
|
|
59
|
+
"@nuxtjs/i18n": "^10.6.0",
|
|
59
60
|
"@tailwindcss/vite": "^4.3.3",
|
|
60
61
|
"@types/node": "^24.0.0",
|
|
61
62
|
"nuxt": "^4.6.2",
|
|
63
|
+
"playwright-core": "^1.64.1",
|
|
62
64
|
"tailwindcss": "^4.3.3",
|
|
63
65
|
"typescript": "^6.0.3",
|
|
64
66
|
"vitest": "^5.0.5",
|