@uxfront/layer-docs 0.4.0 → 0.5.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/README.md +31 -220
- package/app/app.config.ts +8 -92
- package/app/components/content/FrameworkSwitcher.vue +66 -40
- package/app/components/docs/DocsAsideLeftTop.vue +9 -15
- package/app/components/docs/DocsFrameworkSelect.vue +4 -7
- package/app/composables/useFramework.ts +30 -38
- package/nuxt.config.ts +14 -170
- package/package.json +10 -77
- package/CHANGELOG.md +0 -180
- package/LICENSE +0 -21
- package/app/app.vue +0 -138
- package/app/assets/css/main.css +0 -15
- package/app/components/IconMenuToggle.vue +0 -92
- package/app/components/LanguageSelect.vue +0 -73
- package/app/components/MorphingGradientBackground.vue +0 -261
- package/app/components/OgImage/OgImageDocs.satori.vue +0 -40
- package/app/components/OgImage/OgImageLanding.satori.vue +0 -41
- package/app/components/app/AppFooter.vue +0 -13
- package/app/components/app/AppFooterCenter.vue +0 -17
- package/app/components/app/AppFooterLeft.vue +0 -21
- package/app/components/app/AppFooterRight.vue +0 -33
- package/app/components/app/AppHeader.vue +0 -123
- package/app/components/app/AppHeaderAttribution.vue +0 -45
- package/app/components/app/AppHeaderBody.vue +0 -14
- package/app/components/app/AppHeaderCTA.vue +0 -31
- package/app/components/app/AppHeaderCenter.vue +0 -10
- package/app/components/app/AppHeaderLogo.vue +0 -16
- package/app/components/app/AppOgDecoration.vue +0 -27
- package/app/components/app/AppOgLogo.vue +0 -19
- package/app/components/app/AppSearch.vue +0 -59
- package/app/components/app/AppSubHeader.vue +0 -21
- package/app/components/content/BrowserFrame.vue +0 -28
- package/app/components/content/GradientPageHero.vue +0 -35
- package/app/components/content/StorybookEmbed.vue +0 -160
- package/app/components/content/Video.vue +0 -103
- package/app/components/docs/DocsAsideLeftBody.vue +0 -20
- package/app/components/docs/DocsAsideRightBottom.vue +0 -15
- package/app/components/docs/DocsPageHeaderLinks.vue +0 -75
- package/app/composables/useDocsSections.ts +0 -57
- package/app/composables/useDocusI18n.ts +0 -49
- package/app/constants/sections.ts +0 -25
- package/app/error.vue +0 -140
- package/app/layouts/default.vue +0 -24
- package/app/pages/[[lang]]/[...slug].vue +0 -58
- package/app/pages/[[lang]]/docs/[section]/[...slug].vue +0 -180
- package/app/plugins/i18n.ts +0 -21
- package/app/plugins/posthog.client.ts +0 -56
- package/app/types/non-route-categories.ts +0 -12
- package/app/utils/flattenNavigation.ts +0 -22
- package/app/utils/foldNonRouteCategories.ts +0 -47
- package/app/utils/prerender.ts +0 -9
- package/app/utils/storybookEmbed.test.ts +0 -98
- package/app/utils/storybookEmbed.ts +0 -93
- package/i18n/locales/ar.json +0 -24
- package/i18n/locales/be.json +0 -24
- package/i18n/locales/bn.json +0 -24
- package/i18n/locales/ca.json +0 -24
- package/i18n/locales/ckb.json +0 -24
- package/i18n/locales/cs.json +0 -24
- package/i18n/locales/da.json +0 -24
- package/i18n/locales/de.json +0 -24
- package/i18n/locales/el.json +0 -24
- package/i18n/locales/en.json +0 -24
- package/i18n/locales/et.json +0 -24
- package/i18n/locales/fr.json +0 -24
- package/i18n/locales/he.json +0 -24
- package/i18n/locales/hi.json +0 -24
- package/i18n/locales/hy.json +0 -24
- package/i18n/locales/it.json +0 -24
- package/i18n/locales/ja.json +0 -24
- package/i18n/locales/kk.json +0 -24
- package/i18n/locales/km.json +0 -24
- package/i18n/locales/ko.json +0 -24
- package/i18n/locales/ky.json +0 -24
- package/i18n/locales/lb.json +0 -24
- package/i18n/locales/ms.json +0 -24
- package/i18n/locales/nb.json +0 -24
- package/i18n/locales/pl.json +0 -24
- package/i18n/locales/ru.json +0 -24
- package/i18n/locales/sl.json +0 -24
- package/i18n/locales/sv.json +0 -24
- package/i18n/locales/uk.json +0 -24
- package/i18n/locales/ur.json +0 -24
- package/i18n/locales/vi.json +0 -24
- package/modules/config.ts +0 -144
- package/modules/optimizeDeps.ts +0 -45
- package/modules/routing.ts +0 -20
- package/nuxt.schema.ts +0 -374
- package/server/plugins/llms-redirect.ts +0 -60
- package/server/routes/raw/[...slug].md.get.ts +0 -74
- package/storybook/index.test.ts +0 -110
- package/storybook/index.ts +0 -362
- package/test/brand-palette.ts +0 -235
- package/test/no-brand-leakage.test.ts +0 -124
- package/tsconfig.json +0 -17
- package/utils/accent.ts +0 -80
- package/utils/content.ts +0 -193
- package/utils/git.ts +0 -114
- package/utils/meta.ts +0 -28
package/nuxt.config.ts
CHANGED
|
@@ -1,181 +1,25 @@
|
|
|
1
|
-
import { createResolver, useNuxt } from "@nuxt/kit";
|
|
2
|
-
|
|
3
|
-
const { resolve } = createResolver(import.meta.url);
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
|
-
* `@uxfront/layer-docs
|
|
7
|
-
*
|
|
8
|
-
* Consumers extend it via `extends: ['@uxfront/layer-docs']` and supply their
|
|
9
|
-
* own branding (title, logos, socials, palette) through their `app.config.ts`,
|
|
10
|
-
* merged over the layer's neutral defaults by Nuxt's `defu` layer merge. The
|
|
11
|
-
* section topology (`DOCS_SECTIONS`) and markdown content stay in the consumer.
|
|
12
|
-
*
|
|
13
|
-
* i18n is OFF by default: `@nuxtjs/i18n` is not registered here, so a v1 single
|
|
14
|
-
* locale site "just works". A consumer that wants localisation registers the
|
|
15
|
-
* module itself; `modules/config` + `useDocusI18n` pick it up automatically.
|
|
16
|
-
*
|
|
17
|
-
* ## Styling: the consumer owns the single Tailwind entry
|
|
18
|
-
*
|
|
19
|
-
* This layer deliberately does NOT register `app/assets/css/main.css` in `css`.
|
|
20
|
-
* Its base is palette-free, so a consumer's brand `@theme` only compiles if it
|
|
21
|
-
* lives inside the same Tailwind pass — which means the consumer's CSS file has
|
|
22
|
-
* to *import* this base, not sit beside it. Registering it here as well gave
|
|
23
|
-
* every consumer two Tailwind entries and a byte-for-byte duplicate of every
|
|
24
|
-
* base utility in the shipped stylesheet (+26.7 KB gzip on inkline, UXF-118),
|
|
25
|
-
* and left each consumer to un-register what the layer had just registered.
|
|
2
|
+
* `@uxfront/layer-docs`: the Nuxt side of UXFront's documentation sites.
|
|
26
3
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* ```
|
|
33
|
-
*
|
|
34
|
-
* whose first line imports this base, followed by the brand `@theme`. Both
|
|
35
|
-
* halves are guarded by `@uxfront/layer-docs/test`; see README § Styling.
|
|
4
|
+
* It extends Docus, which renders `content/docs/` with its header, sidebar,
|
|
5
|
+
* search and table of contents, and adds a framework switcher on top: a
|
|
6
|
+
* `::framework-switcher` content component with one slot per framework, and a
|
|
7
|
+
* Framework select above the sidebar. The app lists its frameworks in
|
|
8
|
+
* `docsTheme.frameworks` in its `app.config.ts`.
|
|
36
9
|
*
|
|
37
10
|
* https://nuxt.com/docs/getting-started/layers
|
|
38
11
|
*/
|
|
39
12
|
export default defineNuxtConfig({
|
|
40
|
-
|
|
41
|
-
telemetry: false,
|
|
42
|
-
// Expose `app/constants/` (DOCS_SECTIONS, findDocsSectionBySlug) as
|
|
43
|
-
// auto-imports. The layer ships a neutral empty default; a consumer's own
|
|
44
|
-
// `app/constants/` overrides it via auto-import dir precedence.
|
|
45
|
-
imports: {
|
|
46
|
-
dirs: ["constants"],
|
|
47
|
-
},
|
|
48
|
-
modules: [
|
|
49
|
-
resolve("./modules/config"),
|
|
50
|
-
resolve("./modules/routing"),
|
|
51
|
-
resolve("./modules/optimizeDeps"),
|
|
52
|
-
"@nuxt/ui",
|
|
53
|
-
"@nuxt/image",
|
|
54
|
-
"@nuxt/scripts",
|
|
55
|
-
"@nuxtjs/robots",
|
|
56
|
-
"@nuxtjs/sitemap",
|
|
57
|
-
"@nuxt/content",
|
|
58
|
-
"nuxt-llms",
|
|
59
|
-
"nuxt-og-image",
|
|
60
|
-
],
|
|
61
|
-
icon: {
|
|
62
|
-
serverBundle: "local",
|
|
63
|
-
fetchTimeout: 10000,
|
|
64
|
-
},
|
|
65
|
-
// Syntax-highlighting language set + MDC auto-unwrap. Shared across every
|
|
66
|
-
// docs consumer; brand-agnostic.
|
|
67
|
-
content: {
|
|
68
|
-
build: {
|
|
69
|
-
markdown: {
|
|
70
|
-
highlight: {
|
|
71
|
-
langs: [
|
|
72
|
-
"bash",
|
|
73
|
-
"diff",
|
|
74
|
-
"json",
|
|
75
|
-
"js",
|
|
76
|
-
"ts",
|
|
77
|
-
"tsx",
|
|
78
|
-
"html",
|
|
79
|
-
"css",
|
|
80
|
-
"vue",
|
|
81
|
-
"svelte",
|
|
82
|
-
"astro",
|
|
83
|
-
"shell",
|
|
84
|
-
"mdc",
|
|
85
|
-
"md",
|
|
86
|
-
"yaml",
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
remarkPlugins: {
|
|
90
|
-
"remark-mdc": {
|
|
91
|
-
options: {
|
|
92
|
-
autoUnwrap: true,
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
nitro: {
|
|
100
|
-
prerender: {
|
|
101
|
-
crawlLinks: true,
|
|
102
|
-
failOnError: false,
|
|
103
|
-
autoSubfolderIndex: false,
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
hooks: {
|
|
107
|
-
// Seed the prerender crawl with the site root(s). Without i18n that's just
|
|
108
|
-
// "/"; with i18n it's each locale root ("/en", "/fr", ...). Everything else
|
|
109
|
-
// is reached via crawlLinks.
|
|
110
|
-
"nitro:config"(nitroConfig) {
|
|
111
|
-
const nuxt = useNuxt();
|
|
112
|
-
const i18nOptions = nuxt.options.i18n as
|
|
113
|
-
| { locales?: Array<string | { code: string }> }
|
|
114
|
-
| undefined;
|
|
115
|
-
|
|
116
|
-
const routes: string[] = [];
|
|
117
|
-
if (!i18nOptions?.locales?.length) {
|
|
118
|
-
routes.push("/");
|
|
119
|
-
} else {
|
|
120
|
-
routes.push(
|
|
121
|
-
...i18nOptions.locales.map((locale) =>
|
|
122
|
-
typeof locale === "string" ? `/${locale}` : `/${locale.code}`,
|
|
123
|
-
),
|
|
124
|
-
);
|
|
125
|
-
}
|
|
13
|
+
extends: ["docus"],
|
|
126
14
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
* robots, which this layer also registers unasked. A consumer opts out with
|
|
135
|
-
* the module's own `ogImage: { enabled: false }`; no layer-specific key.
|
|
136
|
-
*
|
|
137
|
-
* Prerendering bakes every card at build time, so no runtime generation
|
|
138
|
-
* endpoint is needed (or exposed unsigned).
|
|
139
|
-
* @docs https://nuxtseo.com/og-image/guides/zero-runtime
|
|
140
|
-
*/
|
|
141
|
-
ogImage: {
|
|
142
|
-
zeroRuntime: true,
|
|
143
|
-
// 1.91:1, the ratio every major crawler crops to. The module's own default
|
|
144
|
-
// is 1200×600 (2:1), which gets letterboxed or trimmed on most previews.
|
|
145
|
-
defaults: {
|
|
146
|
-
width: 1200,
|
|
147
|
-
height: 630,
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
// Generic sitemap defaults; brand values (url/name) come from the consumer's
|
|
151
|
-
// `site` config or are inferred in `modules/config`.
|
|
152
|
-
sitemap: {
|
|
153
|
-
enabled: true,
|
|
154
|
-
discoverImages: true,
|
|
155
|
-
discoverVideos: true,
|
|
156
|
-
},
|
|
157
|
-
// Neutral analytics defaults. The PostHog client plugin (ships in this layer)
|
|
158
|
-
// only initialises in production when `app.config` `analytics.enabled` is true
|
|
159
|
-
// and a key is present; consumers override the key/host from their own env.
|
|
160
|
-
runtimeConfig: {
|
|
161
|
-
public: {
|
|
162
|
-
posthog: {
|
|
163
|
-
key: "",
|
|
164
|
-
host: "https://us.i.posthog.com",
|
|
165
|
-
defaults: "2025-05-24",
|
|
15
|
+
icon: {
|
|
16
|
+
clientBundle: {
|
|
17
|
+
scan: {
|
|
18
|
+
// Nuxt Icon's default, plus app config files, where `docsTheme.frameworks`
|
|
19
|
+
// names its icons. The sidebar's select shows them on every page, so
|
|
20
|
+
// without this, pages with no switcher fetch them from the Iconify API.
|
|
21
|
+
globInclude: ["**/*.{vue,jsx,tsx,md,mdc,mdx,yml,yaml}", "**/app.config.{ts,mts,js,mjs}"],
|
|
166
22
|
},
|
|
167
|
-
// Where `StorybookEmbed` points. Empty here — the host is a consumer
|
|
168
|
-
// fact, and where a Storybook is deployed changes without a code change,
|
|
169
|
-
// so this is runtime config (`NUXT_PUBLIC_STORYBOOK_BASE_URL`) rather
|
|
170
|
-
// than app config. A `{framework}` placeholder addresses per-framework
|
|
171
|
-
// deployments; a template without one is used as-is.
|
|
172
|
-
storybookBaseUrl: "",
|
|
173
|
-
// Migration-only: a brand namespace whose `<ns>:theme` / `<ns>:height`
|
|
174
|
-
// messages a consumer's Storybook already speaks. Set it and the embed
|
|
175
|
-
// emits and accepts both those names and the neutral ones, so the docs
|
|
176
|
-
// site and the Storybook can deploy in either order. Unset it once both
|
|
177
|
-
// sides are on `@uxfront/layer-docs/storybook`.
|
|
178
|
-
storybookLegacyMessageNamespace: "",
|
|
179
23
|
},
|
|
180
24
|
},
|
|
181
25
|
});
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uxfront/layer-docs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Nuxt layer for UXFront documentation sites: Docus, plus a framework switcher that shows every reader the examples for their framework.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
7
7
|
"documentation",
|
|
8
8
|
"docus",
|
|
9
9
|
"nuxt",
|
|
10
|
-
"nuxt-layer"
|
|
11
|
-
"theme"
|
|
10
|
+
"nuxt-layer"
|
|
12
11
|
],
|
|
13
12
|
"homepage": "https://github.com/uxfront-com/uxfront/tree/main/packages/layer-docs#readme",
|
|
14
13
|
"bugs": "https://github.com/uxfront-com/uxfront/issues",
|
|
@@ -20,18 +19,8 @@
|
|
|
20
19
|
},
|
|
21
20
|
"files": [
|
|
22
21
|
"app",
|
|
23
|
-
"i18n",
|
|
24
|
-
"modules",
|
|
25
|
-
"server",
|
|
26
|
-
"storybook",
|
|
27
|
-
"test",
|
|
28
|
-
"utils",
|
|
29
22
|
"nuxt.config.ts",
|
|
30
|
-
"
|
|
31
|
-
"tsconfig.json",
|
|
32
|
-
"README.md",
|
|
33
|
-
"CHANGELOG.md",
|
|
34
|
-
"LICENSE"
|
|
23
|
+
"README.md"
|
|
35
24
|
],
|
|
36
25
|
"type": "module",
|
|
37
26
|
"main": "./nuxt.config.ts",
|
|
@@ -41,77 +30,21 @@
|
|
|
41
30
|
"types": "./nuxt.config.ts",
|
|
42
31
|
"import": "./nuxt.config.ts"
|
|
43
32
|
},
|
|
44
|
-
"./content": {
|
|
45
|
-
"types": "./utils/content.ts",
|
|
46
|
-
"import": "./utils/content.ts"
|
|
47
|
-
},
|
|
48
|
-
"./storybook": {
|
|
49
|
-
"types": "./storybook/index.ts",
|
|
50
|
-
"import": "./storybook/index.ts"
|
|
51
|
-
},
|
|
52
|
-
"./test": {
|
|
53
|
-
"types": "./test/brand-palette.ts",
|
|
54
|
-
"import": "./test/brand-palette.ts"
|
|
55
|
-
},
|
|
56
|
-
"./app/assets/css/main.css": "./app/assets/css/main.css",
|
|
57
33
|
"./package.json": "./package.json"
|
|
58
34
|
},
|
|
59
35
|
"publishConfig": {
|
|
60
36
|
"access": "public"
|
|
61
37
|
},
|
|
62
38
|
"dependencies": {
|
|
63
|
-
"@vueuse/core": "^14.
|
|
64
|
-
"
|
|
65
|
-
"git-url-parse": "^16.1.0",
|
|
66
|
-
"minimark": "^0.2.0",
|
|
67
|
-
"motion-v": "^1.7.2",
|
|
68
|
-
"pkg-types": "^2.3.1",
|
|
69
|
-
"scule": "^1.3.0",
|
|
70
|
-
"ufo": "^1.6.4"
|
|
39
|
+
"@vueuse/core": "^14.4.0",
|
|
40
|
+
"docus": "^5.13.0"
|
|
71
41
|
},
|
|
72
42
|
"devDependencies": {
|
|
73
|
-
"
|
|
74
|
-
"
|
|
43
|
+
"nuxt": "^4.5.2",
|
|
44
|
+
"vue": "^3.5.43"
|
|
75
45
|
},
|
|
76
46
|
"peerDependencies": {
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"@nuxt/kit": "^4.4.8",
|
|
80
|
-
"@nuxt/scripts": "^0.11.13",
|
|
81
|
-
"@nuxt/ui": "^4.8.2",
|
|
82
|
-
"@nuxtjs/i18n": "^10.4.0",
|
|
83
|
-
"@nuxtjs/mdc": "^0.22.0",
|
|
84
|
-
"@nuxtjs/robots": "^6.1.1",
|
|
85
|
-
"@nuxtjs/sitemap": "^8.2.1",
|
|
86
|
-
"@resvg/resvg-js": "^2.6.0",
|
|
87
|
-
"nuxt": "^4.4.8",
|
|
88
|
-
"nuxt-llms": "^0.2.0",
|
|
89
|
-
"nuxt-og-image": "^6.6.0",
|
|
90
|
-
"posthog-js": "^1.386.6",
|
|
91
|
-
"satori": "^0.26.0",
|
|
92
|
-
"tailwindcss": "^4.3.1",
|
|
93
|
-
"typescript": "^6.0.3",
|
|
94
|
-
"vitest": "^4.1.10",
|
|
95
|
-
"vue": "^3.5.38"
|
|
96
|
-
},
|
|
97
|
-
"peerDependenciesMeta": {
|
|
98
|
-
"@nuxtjs/i18n": {
|
|
99
|
-
"optional": true
|
|
100
|
-
},
|
|
101
|
-
"@nuxtjs/mdc": {
|
|
102
|
-
"optional": true
|
|
103
|
-
},
|
|
104
|
-
"posthog-js": {
|
|
105
|
-
"optional": true
|
|
106
|
-
},
|
|
107
|
-
"typescript": {
|
|
108
|
-
"optional": true
|
|
109
|
-
},
|
|
110
|
-
"vitest": {
|
|
111
|
-
"optional": true
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
"scripts": {
|
|
115
|
-
"postinstall": "nuxt prepare"
|
|
47
|
+
"nuxt": "^4.5.0",
|
|
48
|
+
"vue": "^3.5.0"
|
|
116
49
|
}
|
|
117
50
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# @uxfront/layer-docs
|
|
2
|
-
|
|
3
|
-
## 0.4.0
|
|
4
|
-
|
|
5
|
-
### Minor Changes
|
|
6
|
-
|
|
7
|
-
- a4caf06: Ship OG image support and the two satori card templates.
|
|
8
|
-
|
|
9
|
-
Every consumer was registering `nuxt-og-image` itself and carrying its own copy of the templates, which meant carrying a page override too — `defineOgImage` cannot be called from the layer if the module is not registered by it. The layer now registers the module beside `@nuxtjs/sitemap` and `@nuxtjs/robots` (same class of SEO plumbing it already turns on unasked) and calls `defineOgImage` from its own docs and landing pages, so a consumer that extends the layer gets branded cards without writing anything. Opt out with the module's own switch, `ogImage: { enabled: false }`.
|
|
10
|
-
|
|
11
|
-
**The accent is a brand token, not a hardcoded colour.** The existing copies hardcoded a Tailwind class, which was wrong in a way that only showed up on the rendered card: every consumer shadows stock Tailwind scale names with its own values in `@theme`, so `text-teal-500` renders Tailwind's teal, not the brand's. Custom properties are not an option either — satori has no CSS cascade, and `var(--ui-primary)` resolves to nothing. The accent is therefore resolved to a literal at build time by following `--ui-primary` to the `--color-<scale>` declaration it aliases in the consumer's registered CSS, the same two hops the browser does and the same pair `describeBrandPaletteCss` already asserts. Override it with a literal via `ogImage.accent` in `app.config.ts`.
|
|
12
|
-
|
|
13
|
-
Two parts of the card are components rather than props, because `defineOgImage` serialises its arguments and a Vue slot cannot cross that boundary. Shadow `app/AppOgDecoration.vue` (a neutral radial wash) or `app/AppOgLogo.vue` (a wordmark from `header.title`) at the same path to replace either.
|
|
14
|
-
|
|
15
|
-
Cards render at 1200×630 rather than the module's 1200×600 default — 1.91:1 is the ratio crawlers crop to.
|
|
16
|
-
|
|
17
|
-
New peer dependencies: `nuxt-og-image`, `satori`, and `@resvg/resvg-js`. Satori renders the template to SVG and needs a separate rasteriser for the PNG; without one, prerendering logs `renderer.createImage error` per card and emits nothing.
|
|
18
|
-
|
|
19
|
-
- 5cc11f2: Add `StorybookEmbed` and the `@uxfront/layer-docs/storybook` bridge.
|
|
20
|
-
|
|
21
|
-
`StorybookEmbed` embeds a deployed Storybook story in a docs page, in one of
|
|
22
|
-
three modes — `preview` (canvas only), `full` (manager without the sidebar), and
|
|
23
|
-
`panel` (manager with the controls panel). It mounts lazily, keeps the
|
|
24
|
-
Storybook's colour mode in sync with the page's, and grows to fit its story
|
|
25
|
-
without shifting layout. The Storybook host comes from
|
|
26
|
-
`NUXT_PUBLIC_STORYBOOK_BASE_URL`, with an optional `{framework}` placeholder for
|
|
27
|
-
per-framework deployments.
|
|
28
|
-
|
|
29
|
-
The `/storybook` subpath export ships the other half of that contract:
|
|
30
|
-
`installDocsEmbedPreviewBridge` and `installDocsEmbedManagerBridge`, which are
|
|
31
|
-
framework-free and import nothing from Nuxt. A Storybook that already speaks a
|
|
32
|
-
branded message namespace can name it via
|
|
33
|
-
`NUXT_PUBLIC_STORYBOOK_LEGACY_MESSAGE_NAMESPACE`, and both sides accept and emit
|
|
34
|
-
both spellings so the two can deploy in either order.
|
|
35
|
-
|
|
36
|
-
## 0.3.0
|
|
37
|
-
|
|
38
|
-
### Minor Changes
|
|
39
|
-
|
|
40
|
-
- 751810d: Stop registering the layer's own `main.css`, and ship the brand-palette guards consumers were each expected to write themselves.
|
|
41
|
-
|
|
42
|
-
**Breaking for any consumer that does not already register its own CSS entry.** The layer registered `app/assets/css/main.css` in `css` while every consumer also imported that same base from its brand CSS — because a brand `@theme` only compiles into real `:root` custom properties when it lives inside a Tailwind pass, which means importing the base rather than sitting beside it. Two entries, two Tailwind passes, and a byte-for-byte duplicate of every base utility in the shipped stylesheet: **+212 KB raw / +26.7 KB gzip (+94%)** on inkline's render-blocking `entry.css`, on every page, for every visitor (UXF-118).
|
|
43
|
-
|
|
44
|
-
The layer no longer registers it. Consumers register exactly one CSS entry of their own, whose first line imports the layer base — which is what all three consumers were already doing, one of them via a `modules:done` hook that un-registered what the layer had just registered. That hook can now be deleted.
|
|
45
|
-
|
|
46
|
-
**Migration.** If your app already has `css: ["./app/assets/css/main.css"]` pointing at a file that starts with `@import "@uxfront/layer-docs/app/assets/css/main.css"`, nothing changes except that your stylesheet halves; drop any hook that stripped the layer's entry. If it does not, add both — see README § Styling. There is no silent middle state: without a CSS entry the app ships no Tailwind utilities at all.
|
|
47
|
-
|
|
48
|
-
**What the duplication actually cost.** Payload, not layout. The duplicate pass measured on inkline was a complete superset of the first and was emitted wholly after it, so the last `sm:`/`lg:` variant still landed after the last conflicting base and won by source order — computed-style A/B across 4 pages × 4 viewports found zero rendering difference. That rescue was incidental rather than designed, which is why the cause is fixed rather than the symptom.
|
|
49
|
-
|
|
50
|
-
**New: `@uxfront/layer-docs/test`.** The two guards that would have caught this before ship, now parameterised by brand scale instead of copy-pasted per repo. `describeBrandPaletteCss` asserts, without a build, that your CSS entry imports the layer base before its `@theme` and defines the scale it maps onto `--ui-primary`. `describeSingleTailwindPass` reads the built stylesheet and asserts every base utility is emitted **exactly once** — catching a duplicate pass and a CSS entry that never reached the bundle with the same assertion. `vitest` is an optional peer dependency, needed only for these.
|
|
51
|
-
|
|
52
|
-
Also fixed: three code comments claimed the second pass "kills every `sm:`/`lg:` responsive variant". It does not, in the equal-superset case measured; the guards now describe the duplicate payload they actually guard.
|
|
53
|
-
|
|
54
|
-
## 0.2.1
|
|
55
|
-
|
|
56
|
-
### Patch Changes
|
|
57
|
-
|
|
58
|
-
- Stop the optional `posthog-js` peer from killing the client bundle of every consumer that omits it.
|
|
59
|
-
|
|
60
|
-
`app/plugins/posthog.client.ts` imported `posthog-js` statically at module scope while declaring it an **optional** `peerDependency`. Rollup has no module to resolve for a consumer that skipped the optional peer, so it emits the specifier as a module-scope throw:
|
|
61
|
-
|
|
62
|
-
```js
|
|
63
|
-
const HB = {};
|
|
64
|
-
throw new Error('Could not resolve "posthog-js" imported by "@uxfront/layer-docs".');
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
That throw runs on evaluation, before the plugin body — so neither the `analytics.enabled` guard nor the `posthog.key` guard ever executed, and the failure was not confined to analytics. The whole client entry died with it: **the site did not hydrate at all**. Every page served as a dead static document — theme toggle stuck on its loading skeleton, framework tabs inert, no client-side routing, `localStorage` never written — with one console error per route as the only signal. Present in `0.1.0`, `0.1.1` and `0.2.0`.
|
|
68
|
-
|
|
69
|
-
`posthog-js` is now imported dynamically, inside the branch that has already confirmed analytics is enabled and a key is provisioned. The unresolvable specifier moves out of the client entry into its own lazy chunk that a consumer without the peer never evaluates — it is emitted, prefetched at most, and never run. The import is also wrapped, which keeps the failure proportional to what failed: a missing or broken module degrades to a console warning and no analytics, instead of a dead app.
|
|
70
|
-
|
|
71
|
-
`modules/optimizeDeps` carried the same assumption from the other direction — it named `posthog-js` in `optimizeDeps.include` unconditionally, which is a hard dev-server error when the package is not installed. The hint is now added only when the dependency actually resolves.
|
|
72
|
-
|
|
73
|
-
Consumers who do not use PostHog need no changes and should upgrade. Consumers who do use it are unaffected: `$posthog()` is provided exactly as before once the client initialises.
|
|
74
|
-
|
|
75
|
-
## 0.2.0
|
|
76
|
-
|
|
77
|
-
### Minor Changes
|
|
78
|
-
|
|
79
|
-
- Absorb documentation-site plumbing that consumers were re-declaring by hand.
|
|
80
|
-
|
|
81
|
-
- `defineDocsCollections(sections, options)` gains `{ sitemap?, changelog? }`. With
|
|
82
|
-
`sitemap: true` the landing and docs collections carry `defineSitemapSchema()`;
|
|
83
|
-
with `changelog: true` a `changelog` collection is registered over
|
|
84
|
-
`changelog/*.md`. Both default to `false`, so existing single-argument calls are
|
|
85
|
-
unchanged. Per-locale collections are now gated on `locales.length > 0` rather
|
|
86
|
-
than the array's mere presence.
|
|
87
|
-
- 30 additional locales ship with the layer (ar, be, bn, ca, ckb, cs, da, de, el,
|
|
88
|
-
et, fr, he, hi, hy, it, ja, kk, km, ko, ky, lb, ms, nb, pl, ru, sl, sv, uk, ur,
|
|
89
|
-
vi), each complete against `en.json`.
|
|
90
|
-
- `nuxt.schema.ts` ships with the layer, so the Content Studio preview schema for
|
|
91
|
-
`app.config.ts` is described once instead of per consumer.
|
|
92
|
-
- New `modules/optimizeDeps` module pre-bundles the layer's own heavier runtime
|
|
93
|
-
dependencies; registered automatically.
|
|
94
|
-
- New `DocsAsideLeftTop` / `DocsFrameworkSelect` render a persistent framework
|
|
95
|
-
selector above the sidebar navigation, and `GradientPageHero` puts `UPageHero`
|
|
96
|
-
on `MorphingGradientBackground`. Both render only when the consumer opts in.
|
|
97
|
-
|
|
98
|
-
**Breaking-ish:** `useFramework()` no longer ships a built-in React/Vue/Vanilla
|
|
99
|
-
default list. `frameworks` is now `appConfig.docsTheme.frameworks ?? []`, making
|
|
100
|
-
app config the single source of truth. Sites that relied on the implicit default
|
|
101
|
-
must declare `docsTheme.frameworks` in their own `app.config.ts`; `FrameworkSwitcher`
|
|
102
|
-
renders no tab bar when the list is empty.
|
|
103
|
-
|
|
104
|
-
### Patch Changes
|
|
105
|
-
|
|
106
|
-
- Correct the changelog so it matches what was actually published. `0.1.1` shipped
|
|
107
|
-
with no entry of its own while an entry for a `0.1.2` that was never published
|
|
108
|
-
sat at the top of the file, so consumers had no way to learn from the changelog
|
|
109
|
-
that `0.1.1` contains accessibility fixes for `button-name` and
|
|
110
|
-
`nested-interactive` violations. `0.1.1` now has a complete entry, and the
|
|
111
|
-
`AppHeaderCTA.vue` typecheck fix previously filed under `0.1.2` is recorded
|
|
112
|
-
under `0.1.0`, the release it actually shipped in.
|
|
113
|
-
|
|
114
|
-
Note: `0.2.0` was versioned by a hand `chore: bump version` commit that bypassed
|
|
115
|
-
`changeset version`, so it published with no changelog entry at all — the tarball
|
|
116
|
-
topped out at a `## 0.1.2` heading for a version that was versioned but never
|
|
117
|
-
published. The entry above is the accurate, reconstructed record of what `0.2.0`
|
|
118
|
-
actually contains, and the changelog-only correction previously filed under
|
|
119
|
-
`0.1.2` is folded in here because `0.2.0` is the release it shipped in. Every
|
|
120
|
-
heading in this file now corresponds to a version that exists on the registry.
|
|
121
|
-
Second occurrence of UXF-67; see UXF-168.
|
|
122
|
-
|
|
123
|
-
## 0.1.1
|
|
124
|
-
|
|
125
|
-
### Minor Changes
|
|
126
|
-
|
|
127
|
-
- Add `header.attribution` so a consuming app can render a partially-linked
|
|
128
|
-
wordmark, e.g. "uxd by UXFront" where only "UXFront" links out. The header now
|
|
129
|
-
owns UHeader's `#left` slot instead of `#title`, because `#title` is rendered
|
|
130
|
-
inside UHeader's own home link and an attribution link there would nest
|
|
131
|
-
anchors. Apps that do not set `attribution` render exactly as before.
|
|
132
|
-
|
|
133
|
-
### Patch Changes
|
|
134
|
-
|
|
135
|
-
- Fix accessible names on icon-only navigation controls and hide the section
|
|
136
|
-
sub-header when there is nothing to switch between.
|
|
137
|
-
|
|
138
|
-
- The mobile menu toggle and the "copy page" dropdown trigger now expose
|
|
139
|
-
accessible names (sourced from the active locale), clearing two critical
|
|
140
|
-
`button-name` violations that shipped on every page. **Consumers on `0.1.0`
|
|
141
|
-
should upgrade** — those violations were present on every page of every
|
|
142
|
-
site extending this layer.
|
|
143
|
-
- The page footer separator is now `decorative`, clearing a serious
|
|
144
|
-
`nested-interactive` violation caused by focusable links inside an element
|
|
145
|
-
with `role="separator"`.
|
|
146
|
-
- The docs sub-header no longer renders when the consumer declares one section
|
|
147
|
-
or none, so a single-section site gets no empty bar and no extra header
|
|
148
|
-
height.
|
|
149
|
-
|
|
150
|
-
- Skip PostHog initialisation when no key is provisioned
|
|
151
|
-
(`NUXT_PUBLIC_POSTHOG_KEY` unset), instead of initialising a client that fires
|
|
152
|
-
requests at nothing.
|
|
153
|
-
|
|
154
|
-
Note: `0.1.1` was versioned by hand rather than through `changeset version`, so
|
|
155
|
-
it published as a patch even though `header.attribution` is additive and would
|
|
156
|
-
have produced `0.2.0`. The published number stands; the entry above is the
|
|
157
|
-
accurate record of its contents. See UXF-67.
|
|
158
|
-
|
|
159
|
-
## 0.1.0
|
|
160
|
-
|
|
161
|
-
### Minor Changes
|
|
162
|
-
|
|
163
|
-
- Initial scaffold of the neutral Nuxt-layer docs theme: auto-config module
|
|
164
|
-
(site metadata / SEO / git inference), neutral `app.config` defaults and UI
|
|
165
|
-
polish, base `main.css` (Tailwind + Nuxt UI wiring, palette-free),
|
|
166
|
-
`MorphingGradientBackground`, and opt-out-gated i18n-redirect and PostHog
|
|
167
|
-
plugins. Nuxt documentation stack declared as pinned `peerDependencies`.
|
|
168
|
-
|
|
169
|
-
### Patch Changes
|
|
170
|
-
|
|
171
|
-
- Fix `nuxt typecheck` failure in `AppHeaderCTA.vue`: the config module seeds a
|
|
172
|
-
partial `header` (`{ title }`), so defu narrows the resolved `AppConfig` header
|
|
173
|
-
to `{ title, logo }` and drops the array-valued `links`. Read `links` through a
|
|
174
|
-
cast on `header` itself instead of the `.links` result, so consumers extending
|
|
175
|
-
the layer typecheck cleanly. Runtime behaviour is unchanged.
|
|
176
|
-
|
|
177
|
-
This fix was previously filed under a `## 0.1.2` heading that no published
|
|
178
|
-
release ever carried. It was written while the package was still named
|
|
179
|
-
`@uxfront/docs-theme` — a name that was never published — and it shipped in
|
|
180
|
-
`@uxfront/layer-docs@0.1.0`, the first release under the current name.
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 uxfront
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/app/app.vue
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { ContentNavigationItem, PageCollections } from "@nuxt/content";
|
|
3
|
-
import * as nuxtUiLocales from "@nuxt/ui/locale";
|
|
4
|
-
|
|
5
|
-
const appConfig = useAppConfig();
|
|
6
|
-
const { seo } = appConfig;
|
|
7
|
-
const site = useSiteConfig();
|
|
8
|
-
const { locale, locales, isEnabled, switchLocalePath } = useDocusI18n();
|
|
9
|
-
|
|
10
|
-
const lang = computed(
|
|
11
|
-
() => nuxtUiLocales[locale.value as keyof typeof nuxtUiLocales]?.code || "en",
|
|
12
|
-
);
|
|
13
|
-
const dir = computed(() => nuxtUiLocales[locale.value as keyof typeof nuxtUiLocales]?.dir || "ltr");
|
|
14
|
-
|
|
15
|
-
const getCollectionName = (key: string) =>
|
|
16
|
-
(isEnabled.value ? `docs_${key}_${locale.value}` : `docs_${key}`) as keyof PageCollections;
|
|
17
|
-
|
|
18
|
-
useHead({
|
|
19
|
-
meta: [{ name: "viewport", content: "width=device-width, initial-scale=1" }],
|
|
20
|
-
link: [{ rel: "icon", href: "/favicon.ico" }],
|
|
21
|
-
htmlAttrs: {
|
|
22
|
-
lang,
|
|
23
|
-
dir,
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
useSeoMeta({
|
|
28
|
-
titleTemplate: seo?.titleTemplate,
|
|
29
|
-
title: seo?.title,
|
|
30
|
-
description: seo?.description,
|
|
31
|
-
ogSiteName: site.name,
|
|
32
|
-
twitterCard: "summary_large_image",
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (isEnabled.value) {
|
|
36
|
-
const route = useRoute();
|
|
37
|
-
const defaultLocale = (useRuntimeConfig().public.i18n as { defaultLocale: string }).defaultLocale;
|
|
38
|
-
onMounted(() => {
|
|
39
|
-
const currentLocale = route.path.split("/")[1];
|
|
40
|
-
if (!locales.some((locale) => locale.code === currentLocale)) {
|
|
41
|
-
return navigateTo(switchLocalePath(defaultLocale) as string);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const { data: navigation } = await useAsyncData(
|
|
47
|
-
() => `navigation_${locale.value}`,
|
|
48
|
-
async () => {
|
|
49
|
-
const results = await Promise.all(
|
|
50
|
-
DOCS_SECTIONS.map(async (section) => {
|
|
51
|
-
const collectionName = getCollectionName(section.key);
|
|
52
|
-
const data = await queryCollectionNavigation(collectionName);
|
|
53
|
-
const rootResult =
|
|
54
|
-
data.find((item) => item.path === `/docs/${section.slug}`)?.children ||
|
|
55
|
-
data.find((item) => item.path === "/docs")?.children ||
|
|
56
|
-
data ||
|
|
57
|
-
[];
|
|
58
|
-
const localeResult =
|
|
59
|
-
rootResult.find((item) => item.path === `/${locale.value}`)?.children || rootResult;
|
|
60
|
-
const sectionPath = `/docs/${section.slug}`;
|
|
61
|
-
let result: ContentNavigationItem[];
|
|
62
|
-
if (Array.isArray(section.folder)) {
|
|
63
|
-
const rootIdx = "rootFolder" in section ? (section.rootFolder as number) : -1;
|
|
64
|
-
const rootWrapper =
|
|
65
|
-
localeResult.length === 1 && localeResult[0]?.path === sectionPath
|
|
66
|
-
? localeResult[0]
|
|
67
|
-
: null;
|
|
68
|
-
const allItems = rootWrapper ? (rootWrapper.children ?? []) : localeResult;
|
|
69
|
-
|
|
70
|
-
if (rootIdx >= 0) {
|
|
71
|
-
const nonRootPaths = new Set(
|
|
72
|
-
section.folder
|
|
73
|
-
.filter((_: string, i: number) => i !== rootIdx)
|
|
74
|
-
.map((f: string) => `${sectionPath}/${f.replace(/^\d+\./, "")}`),
|
|
75
|
-
);
|
|
76
|
-
const items: ContentNavigationItem[] = [];
|
|
77
|
-
for (let i = 0; i < section.folder.length; i++) {
|
|
78
|
-
if (i === rootIdx) {
|
|
79
|
-
items.push({
|
|
80
|
-
...rootWrapper,
|
|
81
|
-
title: rootWrapper?.title ?? section.label,
|
|
82
|
-
path: sectionPath,
|
|
83
|
-
children: allItems.filter((item) => !nonRootPaths.has(item.path)),
|
|
84
|
-
} as ContentNavigationItem);
|
|
85
|
-
} else {
|
|
86
|
-
const found = allItems.find(
|
|
87
|
-
(item) =>
|
|
88
|
-
item.path === `${sectionPath}/${section.folder[i]?.replace(/^\d+\./, "")}`,
|
|
89
|
-
);
|
|
90
|
-
if (found) items.push(found);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
result = items;
|
|
94
|
-
} else {
|
|
95
|
-
result = section.folder
|
|
96
|
-
.map((folder: string) =>
|
|
97
|
-
allItems.find(
|
|
98
|
-
(item) => item.path === `${sectionPath}/${folder.replace(/^\d+\./, "")}`,
|
|
99
|
-
),
|
|
100
|
-
)
|
|
101
|
-
.filter((item): item is ContentNavigationItem => item !== undefined);
|
|
102
|
-
}
|
|
103
|
-
} else {
|
|
104
|
-
result = localeResult;
|
|
105
|
-
}
|
|
106
|
-
return [
|
|
107
|
-
section.key,
|
|
108
|
-
foldNonRouteCategories(flattenNavigation(result), appConfig.nonRouteCategories ?? {}),
|
|
109
|
-
] as const;
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
return Object.fromEntries(results) as Record<string, ContentNavigationItem[]>;
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
watch: [locale],
|
|
116
|
-
},
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const flatNavigation = computed(() =>
|
|
120
|
-
navigation.value ? Object.values(navigation.value).flat() : [],
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
provide("navigation", navigation);
|
|
124
|
-
</script>
|
|
125
|
-
|
|
126
|
-
<template>
|
|
127
|
-
<UApp :locale="nuxtUiLocales[locale as keyof typeof nuxtUiLocales]">
|
|
128
|
-
<NuxtLoadingIndicator color="var(--ui-primary)" />
|
|
129
|
-
|
|
130
|
-
<NuxtLayout>
|
|
131
|
-
<NuxtPage />
|
|
132
|
-
</NuxtLayout>
|
|
133
|
-
|
|
134
|
-
<ClientOnly>
|
|
135
|
-
<AppSearch :navigation="flatNavigation" />
|
|
136
|
-
</ClientOnly>
|
|
137
|
-
</UApp>
|
|
138
|
-
</template>
|