@uxfront/layer-docs 0.1.0 → 0.2.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 +57 -5
- package/README.md +18 -0
- package/app/app.config.ts +14 -0
- package/app/components/IconMenuToggle.vue +14 -1
- package/app/components/app/AppHeader.vue +20 -2
- package/app/components/app/AppHeaderAttribution.vue +45 -0
- package/app/components/app/AppSubHeader.vue +2 -2
- package/app/components/content/FrameworkSwitcher.vue +1 -0
- package/app/components/content/GradientPageHero.vue +35 -0
- package/app/components/docs/DocsAsideLeftTop.vue +24 -0
- package/app/components/docs/DocsFrameworkSelect.vue +18 -0
- package/app/components/docs/DocsPageHeaderLinks.vue +1 -0
- package/app/composables/useDocsSections.ts +8 -1
- package/app/composables/useFramework.ts +20 -29
- package/app/layouts/default.vue +1 -0
- package/app/pages/[[lang]]/docs/[section]/[...slug].vue +4 -1
- package/app/plugins/posthog.client.ts +6 -0
- package/i18n/locales/ar.json +24 -0
- package/i18n/locales/be.json +24 -0
- package/i18n/locales/bn.json +24 -0
- package/i18n/locales/ca.json +24 -0
- package/i18n/locales/ckb.json +24 -0
- package/i18n/locales/cs.json +24 -0
- package/i18n/locales/da.json +24 -0
- package/i18n/locales/de.json +24 -0
- package/i18n/locales/el.json +24 -0
- package/i18n/locales/en.json +2 -0
- package/i18n/locales/et.json +24 -0
- package/i18n/locales/fr.json +24 -0
- package/i18n/locales/he.json +24 -0
- package/i18n/locales/hi.json +24 -0
- package/i18n/locales/hy.json +24 -0
- package/i18n/locales/it.json +24 -0
- package/i18n/locales/ja.json +24 -0
- package/i18n/locales/kk.json +24 -0
- package/i18n/locales/km.json +24 -0
- package/i18n/locales/ko.json +24 -0
- package/i18n/locales/ky.json +24 -0
- package/i18n/locales/lb.json +24 -0
- package/i18n/locales/ms.json +24 -0
- package/i18n/locales/nb.json +24 -0
- package/i18n/locales/pl.json +24 -0
- package/i18n/locales/ru.json +24 -0
- package/i18n/locales/sl.json +24 -0
- package/i18n/locales/sv.json +24 -0
- package/i18n/locales/uk.json +24 -0
- package/i18n/locales/ur.json +24 -0
- package/i18n/locales/vi.json +24 -0
- package/modules/optimizeDeps.ts +28 -0
- package/nuxt.config.ts +1 -0
- package/nuxt.schema.ts +359 -0
- package/package.json +2 -1
- package/utils/content.ts +85 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,50 @@
|
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
- Correct the changelog so it matches what was actually published. `0.1.1` shipped
|
|
8
|
+
with no entry of its own while an entry for a `0.1.2` that was never published
|
|
9
|
+
sat at the top of the file, so consumers had no way to learn from the changelog
|
|
10
|
+
that `0.1.1` contains accessibility fixes for `button-name` and
|
|
11
|
+
`nested-interactive` violations. `0.1.1` now has a complete entry, and the
|
|
12
|
+
`AppHeaderCTA.vue` typecheck fix previously filed under `0.1.2` is recorded
|
|
13
|
+
under `0.1.0`, the release it actually shipped in. No runtime code changed in
|
|
14
|
+
this release.
|
|
15
|
+
|
|
16
|
+
## 0.1.1
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- Add `header.attribution` so a consuming app can render a partially-linked
|
|
21
|
+
wordmark, e.g. "uxd by UXFront" where only "UXFront" links out. The header now
|
|
22
|
+
owns UHeader's `#left` slot instead of `#title`, because `#title` is rendered
|
|
23
|
+
inside UHeader's own home link and an attribution link there would nest
|
|
24
|
+
anchors. Apps that do not set `attribution` render exactly as before.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Fix accessible names on icon-only navigation controls and hide the section
|
|
29
|
+
sub-header when there is nothing to switch between.
|
|
30
|
+
|
|
31
|
+
- The mobile menu toggle and the "copy page" dropdown trigger now expose
|
|
32
|
+
accessible names (sourced from the active locale), clearing two critical
|
|
33
|
+
`button-name` violations that shipped on every page. **Consumers on `0.1.0`
|
|
34
|
+
should upgrade** — those violations were present on every page of every
|
|
35
|
+
site extending this layer.
|
|
36
|
+
- The page footer separator is now `decorative`, clearing a serious
|
|
37
|
+
`nested-interactive` violation caused by focusable links inside an element
|
|
38
|
+
with `role="separator"`.
|
|
39
|
+
- The docs sub-header no longer renders when the consumer declares one section
|
|
40
|
+
or none, so a single-section site gets no empty bar and no extra header
|
|
41
|
+
height.
|
|
42
|
+
|
|
43
|
+
- Skip PostHog initialisation when no key is provisioned
|
|
44
|
+
(`NUXT_PUBLIC_POSTHOG_KEY` unset), instead of initialising a client that fires
|
|
45
|
+
requests at nothing.
|
|
46
|
+
|
|
47
|
+
Note: `0.1.1` was versioned by hand rather than through `changeset version`, so
|
|
48
|
+
it published as a patch even though `header.attribution` is additive and would
|
|
49
|
+
have produced `0.2.0`. The published number stands; the entry above is the
|
|
50
|
+
accurate record of its contents. See UXF-67.
|
|
12
51
|
|
|
13
52
|
## 0.1.0
|
|
14
53
|
|
|
@@ -19,3 +58,16 @@
|
|
|
19
58
|
polish, base `main.css` (Tailwind + Nuxt UI wiring, palette-free),
|
|
20
59
|
`MorphingGradientBackground`, and opt-out-gated i18n-redirect and PostHog
|
|
21
60
|
plugins. Nuxt documentation stack declared as pinned `peerDependencies`.
|
|
61
|
+
|
|
62
|
+
### Patch Changes
|
|
63
|
+
|
|
64
|
+
- Fix `nuxt typecheck` failure in `AppHeaderCTA.vue`: the config module seeds a
|
|
65
|
+
partial `header` (`{ title }`), so defu narrows the resolved `AppConfig` header
|
|
66
|
+
to `{ title, logo }` and drops the array-valued `links`. Read `links` through a
|
|
67
|
+
cast on `header` itself instead of the `.links` result, so consumers extending
|
|
68
|
+
the layer typecheck cleanly. Runtime behaviour is unchanged.
|
|
69
|
+
|
|
70
|
+
This fix was previously filed under a `## 0.1.2` heading that no published
|
|
71
|
+
release ever carried. It was written while the package was still named
|
|
72
|
+
`@uxfront/docs-theme` — a name that was never published — and it shipped in
|
|
73
|
+
`@uxfront/layer-docs@0.1.0`, the first release under the current name.
|
package/README.md
CHANGED
|
@@ -42,6 +42,24 @@ export default defineAppConfig({
|
|
|
42
42
|
});
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
### Brand attribution
|
|
46
|
+
|
|
47
|
+
`header.attribution` renders an optional credit beside the wordmark, where only
|
|
48
|
+
the `label` is a link:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
export default defineAppConfig({
|
|
52
|
+
header: {
|
|
53
|
+
title: "uxd",
|
|
54
|
+
attribution: { prefix: "by", label: "UXFront", to: "https://uxfront.com" },
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
That renders `uxd by UXFront` — the wordmark still links home, `by` is plain
|
|
60
|
+
text, and `UXFront` links out. Omit `attribution` (or leave `label` empty) to
|
|
61
|
+
render the wordmark alone.
|
|
62
|
+
|
|
45
63
|
## Compatibility
|
|
46
64
|
|
|
47
65
|
Pinned to the Nuxt 4 documentation stack (Nuxt 4.4, Nuxt UI 4.8, Content 3.14,
|
package/app/app.config.ts
CHANGED
|
@@ -13,6 +13,20 @@ export default defineAppConfig({
|
|
|
13
13
|
title: "On this page",
|
|
14
14
|
},
|
|
15
15
|
|
|
16
|
+
header: {
|
|
17
|
+
/**
|
|
18
|
+
* Optional brand attribution rendered beside the header wordmark.
|
|
19
|
+
* `title: "uxd"` plus `{ prefix: "by", label: "UXFront", to: "https://uxfront.com" }`
|
|
20
|
+
* renders "uxd by UXFront" with only "UXFront" linked. An empty `label`
|
|
21
|
+
* renders nothing, which is the neutral default.
|
|
22
|
+
*/
|
|
23
|
+
attribution: {
|
|
24
|
+
prefix: "",
|
|
25
|
+
label: "",
|
|
26
|
+
to: "",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
|
|
16
30
|
/**
|
|
17
31
|
* Opt-out flags for the layer's client plugins. Defaults keep them on so an
|
|
18
32
|
* existing consumer is unchanged; a consumer opts out by setting `false`.
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { motion } from "motion-v";
|
|
3
3
|
import type { VariantType } from "motion-v";
|
|
4
|
+
import { useLocale } from "@nuxt/ui/composables/useLocale";
|
|
4
5
|
|
|
5
6
|
const props = defineProps<{
|
|
6
7
|
open: boolean;
|
|
7
8
|
}>();
|
|
8
9
|
|
|
10
|
+
// Same strings Nuxt UI's default `UHeader` toggle uses, so the accessible name
|
|
11
|
+
// stays in sync with the app's locale instead of being hardcoded here.
|
|
12
|
+
const { t } = useLocale();
|
|
13
|
+
|
|
9
14
|
const variants: {
|
|
10
15
|
[k: string]: VariantType | ((custom: unknown) => VariantType);
|
|
11
16
|
} = {
|
|
@@ -33,7 +38,14 @@ const state = computed(() => (props.open ? "close" : "normal"));
|
|
|
33
38
|
</script>
|
|
34
39
|
|
|
35
40
|
<template>
|
|
36
|
-
<UButton
|
|
41
|
+
<UButton
|
|
42
|
+
size="sm"
|
|
43
|
+
variant="ghost"
|
|
44
|
+
color="neutral"
|
|
45
|
+
class="-me-1.5"
|
|
46
|
+
square
|
|
47
|
+
:aria-label="open ? t('header.close') : t('header.open')"
|
|
48
|
+
>
|
|
37
49
|
<svg
|
|
38
50
|
xmlns="http://www.w3.org/2000/svg"
|
|
39
51
|
class="size-5"
|
|
@@ -43,6 +55,7 @@ const state = computed(() => (props.open ? "close" : "normal"));
|
|
|
43
55
|
stroke-width="2"
|
|
44
56
|
stroke-linecap="round"
|
|
45
57
|
stroke-linejoin="round"
|
|
58
|
+
aria-hidden="true"
|
|
46
59
|
>
|
|
47
60
|
<motion.line
|
|
48
61
|
x1="4"
|
|
@@ -52,8 +52,26 @@ const links = computed(() =>
|
|
|
52
52
|
>
|
|
53
53
|
<AppHeaderCenter />
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
<!--
|
|
56
|
+
`#left` rather than `#title`: UHeader wraps the `#title` slot in its own
|
|
57
|
+
`<ULink :to>` home link, so an attribution link rendered there would be an
|
|
58
|
+
anchor nested in an anchor — invalid HTML that browsers unnest at parse
|
|
59
|
+
time. Owning `#left` lets the wordmark and the attribution sit as
|
|
60
|
+
siblings. The link classes restate UHeader's `title` slot theme, which the
|
|
61
|
+
slot itself does not expose.
|
|
62
|
+
-->
|
|
63
|
+
<template #left>
|
|
64
|
+
<div class="flex shrink-0 items-baseline gap-1.5">
|
|
65
|
+
<ULink
|
|
66
|
+
:to="localePath('/')"
|
|
67
|
+
:aria-label="appConfig.header?.title || site.name"
|
|
68
|
+
class="flex shrink-0 items-end gap-1.5 rounded-xs text-xl font-bold text-highlighted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
69
|
+
>
|
|
70
|
+
<AppHeaderLogo class="h-6 w-auto shrink-0" />
|
|
71
|
+
</ULink>
|
|
72
|
+
|
|
73
|
+
<AppHeaderAttribution />
|
|
74
|
+
</div>
|
|
57
75
|
</template>
|
|
58
76
|
|
|
59
77
|
<template #right>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Optional brand attribution rendered next to the header wordmark, e.g.
|
|
4
|
+
* "uxd by UXFront" where only "UXFront" is a link.
|
|
5
|
+
*
|
|
6
|
+
* Declared by the consuming app as:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* header: {
|
|
10
|
+
* title: "uxd",
|
|
11
|
+
* attribution: { prefix: "by", label: "UXFront", to: "https://uxfront.com" },
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* `prefix` stays plain text so the link's accessible name is exactly the brand
|
|
16
|
+
* it points at — "by UXFront" would read as the link text otherwise.
|
|
17
|
+
*/
|
|
18
|
+
type Attribution = { prefix?: string; label?: string; to?: string };
|
|
19
|
+
|
|
20
|
+
const appConfig = useAppConfig();
|
|
21
|
+
|
|
22
|
+
// Read through a local type, as `AppHeaderCTA` does for `header.links`: Nuxt's
|
|
23
|
+
// generated `AppConfig` prefers the shape inferred from the merged
|
|
24
|
+
// `app.config.ts` values over the `nuxt.schema.ts` type, so a consumer that
|
|
25
|
+
// declares `header` without `attribution` erases the key from the type even
|
|
26
|
+
// though the layer's own defaults supply it at runtime.
|
|
27
|
+
const attribution = computed(
|
|
28
|
+
() => (appConfig.header as { attribution?: Attribution } | undefined)?.attribution,
|
|
29
|
+
);
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<p v-if="attribution?.label" class="shrink-0 text-sm text-muted">
|
|
34
|
+
<!-- Non-breaking space: Vue's `condense` whitespace handling drops a plain
|
|
35
|
+
trailing space before a newline, and the credit should not wrap. -->
|
|
36
|
+
<template v-if="attribution.prefix">{{ attribution.prefix }} </template>
|
|
37
|
+
<ULink
|
|
38
|
+
v-if="attribution.to"
|
|
39
|
+
:to="attribution.to"
|
|
40
|
+
class="rounded-xs font-medium text-toned underline underline-offset-2 hover:text-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
41
|
+
>{{ attribution.label }}</ULink
|
|
42
|
+
>
|
|
43
|
+
<template v-else>{{ attribution.label }}</template>
|
|
44
|
+
</p>
|
|
45
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { NavigationMenuItem } from "@nuxt/ui";
|
|
3
3
|
|
|
4
|
-
const { sections } = useDocsSections();
|
|
4
|
+
const { sections, hasSectionSwitcher } = useDocsSections();
|
|
5
5
|
|
|
6
6
|
const items = computed<NavigationMenuItem[]>(() =>
|
|
7
7
|
sections.value.map((section) => ({
|
|
@@ -13,7 +13,7 @@ const items = computed<NavigationMenuItem[]>(() =>
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<div class="border-t border-default max-lg:hidden">
|
|
16
|
+
<div v-if="hasSectionSwitcher" class="border-t border-default max-lg:hidden">
|
|
17
17
|
<UContainer class="py-1">
|
|
18
18
|
<UNavigationMenu :items="items" :ui="{ root: 'overflow-x-auto no-scrollbar' }" />
|
|
19
19
|
</UContainer>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* `UPageHero` on the layer's `MorphingGradientBackground`, usable from MDC:
|
|
4
|
+
*
|
|
5
|
+
* ```mdc
|
|
6
|
+
* ::gradient-page-hero
|
|
7
|
+
* ---
|
|
8
|
+
* ---
|
|
9
|
+
* #title
|
|
10
|
+
* Your headline
|
|
11
|
+
* ::
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Ships with `MorphingGradientBackground` rather than beside it: a package that
|
|
15
|
+
* hands you the ingredient should hand you the recipe too. The gradient's own
|
|
16
|
+
* base surface follows `--ui-bg`; its blob colours are the background
|
|
17
|
+
* component's own fixed palette, overridable per consumer by restating the
|
|
18
|
+
* `--color1..5` custom properties on an ancestor.
|
|
19
|
+
*/
|
|
20
|
+
defineOptions({
|
|
21
|
+
inheritAttrs: false,
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<MorphingGradientBackground v-slot="{ onMousemove }">
|
|
27
|
+
<UPageHero v-bind="$attrs" @mousemove="onMousemove">
|
|
28
|
+
<slot />
|
|
29
|
+
<template #title><slot name="title" /></template>
|
|
30
|
+
<template #description><slot name="description" /></template>
|
|
31
|
+
<template #headline><slot name="headline" /></template>
|
|
32
|
+
<template #links><slot name="links" /></template>
|
|
33
|
+
</UPageHero>
|
|
34
|
+
</MorphingGradientBackground>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Persistent framework selector above the docs sidebar navigation.
|
|
4
|
+
*
|
|
5
|
+
* Renders nothing unless the consumer declares `docsTheme.frameworks` in its
|
|
6
|
+
* `app.config.ts`. The in-content `FrameworkSwitcher` shares the same state,
|
|
7
|
+
* but per-page tabs alone scale poorly past a handful of frameworks — this is
|
|
8
|
+
* the always-visible control for the same preference.
|
|
9
|
+
*/
|
|
10
|
+
const { frameworks } = useFramework();
|
|
11
|
+
const { t } = useDocusI18n();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div v-if="frameworks.length">
|
|
16
|
+
<span
|
|
17
|
+
class="group relative w-full pr-2.5 py-1.5 flex items-center gap-1.5 text-sm text-muted cursor-default hover:text-highlighted transition-colors font-normal"
|
|
18
|
+
>
|
|
19
|
+
<span class="truncate">{{ t("docs.framework") }}</span>
|
|
20
|
+
</span>
|
|
21
|
+
|
|
22
|
+
<DocsFrameworkSelect class="mb-2" />
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { framework, frameworks } = useFramework();
|
|
3
|
+
|
|
4
|
+
const current = computed(
|
|
5
|
+
() => frameworks.value.find((option) => option.value === framework.value) ?? frameworks.value[0],
|
|
6
|
+
);
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<USelect
|
|
11
|
+
v-model="framework"
|
|
12
|
+
:items="[...frameworks]"
|
|
13
|
+
variant="ghost"
|
|
14
|
+
color="neutral"
|
|
15
|
+
:icon="current?.icon"
|
|
16
|
+
class="w-full"
|
|
17
|
+
/>
|
|
18
|
+
</template>
|
|
@@ -46,5 +46,12 @@ export function useDocsSections() {
|
|
|
46
46
|
|
|
47
47
|
const activeSection = computed(() => sections.value.find((section) => section.active));
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Whether the section switcher is worth rendering. A single section (or none)
|
|
51
|
+
* gives the reader nothing to switch between, so the sub-header is suppressed
|
|
52
|
+
* entirely rather than rendering an empty bar.
|
|
53
|
+
*/
|
|
54
|
+
const hasSectionSwitcher = computed(() => sections.value.length > 1);
|
|
55
|
+
|
|
56
|
+
return { sections, activeSection, hasSectionSwitcher };
|
|
50
57
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useLocalStorage } from "@vueuse/core";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A single selectable framework
|
|
5
|
-
* `value` doubles as the slot name and the persisted
|
|
6
|
-
* the visible
|
|
7
|
-
* a consumer can declare any framework set
|
|
8
|
-
*
|
|
4
|
+
* A single selectable framework in the docs `FrameworkSwitcher` and the sidebar
|
|
5
|
+
* `DocsFrameworkSelect`. `value` doubles as the slot name and the persisted
|
|
6
|
+
* storage token; `label` is the visible text; `icon` is an Iconify name.
|
|
7
|
+
* `value` is an open string so a consumer can declare any framework set
|
|
8
|
+
* (e.g. `svelte`, `solid`, `angular`).
|
|
9
9
|
*/
|
|
10
10
|
export interface FrameworkOption {
|
|
11
11
|
value: string;
|
|
@@ -13,9 +13,12 @@ export interface FrameworkOption {
|
|
|
13
13
|
icon: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// Neutral, layer-owned storage key — not tied to any consuming product.
|
|
17
|
+
const STORAGE_KEY = "docs-theme:framework";
|
|
18
|
+
|
|
16
19
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
20
|
+
* Reads the framework list a consumer declares — in display order — in its
|
|
21
|
+
* `app.config.ts`:
|
|
19
22
|
*
|
|
20
23
|
* ```ts
|
|
21
24
|
* export default defineAppConfig({
|
|
@@ -24,42 +27,30 @@ export interface FrameworkOption {
|
|
|
24
27
|
* { value: "react", label: "React", icon: "i-mdi-react" },
|
|
25
28
|
* { value: "vue", label: "Vue", icon: "i-mdi-vuejs" },
|
|
26
29
|
* { value: "svelte", label: "Svelte", icon: "i-mdi-svelte" },
|
|
27
|
-
* // ...N frameworks
|
|
28
30
|
* ],
|
|
29
31
|
* },
|
|
30
32
|
* });
|
|
31
33
|
* ```
|
|
32
34
|
*
|
|
35
|
+
* `docsTheme.frameworks` is the only source. Which frameworks a product
|
|
36
|
+
* documents is the product's fact, not the theme's, so the layer ships no
|
|
37
|
+
* default list; a consumer that declares none gets an empty list and the
|
|
38
|
+
* framework UI renders nothing.
|
|
39
|
+
*
|
|
33
40
|
* The list is read from app config rather than merged as a layer default on
|
|
34
41
|
* purpose: Nuxt's `defu` layer merge *concatenates* arrays, so a shipped default
|
|
35
|
-
* would append to (not replace) the consumer's list.
|
|
36
|
-
* and only reading a consumer-supplied override guarantees a clean replacement,
|
|
37
|
-
* while an app with no `frameworks` configured keeps the original behaviour.
|
|
42
|
+
* would append to (not replace) the consumer's list.
|
|
38
43
|
*/
|
|
39
|
-
export const DEFAULT_FRAMEWORKS: readonly FrameworkOption[] = [
|
|
40
|
-
{ value: "react", label: "React", icon: "i-mdi-react" },
|
|
41
|
-
{
|
|
42
|
-
value: "other",
|
|
43
|
-
label: "Vanilla",
|
|
44
|
-
icon: "i-mdi-language-typescript",
|
|
45
|
-
},
|
|
46
|
-
{ value: "vue", label: "Vue", icon: "i-mdi-vuejs" },
|
|
47
|
-
] as const;
|
|
48
|
-
|
|
49
|
-
// Neutral, layer-owned storage key — not tied to any consuming product.
|
|
50
|
-
const STORAGE_KEY = "docs-theme:framework";
|
|
51
|
-
|
|
52
44
|
export const useFramework = () => {
|
|
53
45
|
const appConfig = useAppConfig() as {
|
|
54
46
|
docsTheme?: { frameworks?: FrameworkOption[] };
|
|
55
47
|
};
|
|
56
48
|
|
|
57
|
-
const frameworks = computed<readonly FrameworkOption[]>(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
49
|
+
const frameworks = computed<readonly FrameworkOption[]>(
|
|
50
|
+
() => appConfig.docsTheme?.frameworks ?? [],
|
|
51
|
+
);
|
|
61
52
|
|
|
62
|
-
const framework = useLocalStorage<string>(STORAGE_KEY, frameworks.value[0]
|
|
53
|
+
const framework = useLocalStorage<string>(STORAGE_KEY, frameworks.value[0]?.value ?? "", {
|
|
63
54
|
initOnMounted: true,
|
|
64
55
|
});
|
|
65
56
|
|
package/app/layouts/default.vue
CHANGED
|
@@ -128,7 +128,10 @@ const editLink = computed(() => {
|
|
|
128
128
|
<UPageBody>
|
|
129
129
|
<ContentRenderer v-if="page" :value="page" />
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
<!-- `decorative` drops the `separator` role: the rule is decoration around
|
|
132
|
+
the edit/report actions, and a separator with focusable descendants is
|
|
133
|
+
a `nested-interactive` violation. -->
|
|
134
|
+
<USeparator decorative>
|
|
132
135
|
<div v-if="github" class="flex items-center gap-2 text-sm text-muted">
|
|
133
136
|
<UButton
|
|
134
137
|
variant="link"
|
|
@@ -14,6 +14,12 @@ export default defineNuxtPlugin(() => {
|
|
|
14
14
|
|
|
15
15
|
const runtimeConfig = useRuntimeConfig();
|
|
16
16
|
|
|
17
|
+
// No key provisioned (`NUXT_PUBLIC_POSTHOG_KEY` unset) — no-op instead of
|
|
18
|
+
// initialising a client that would fire requests at nothing.
|
|
19
|
+
if (!runtimeConfig.public.posthog.key) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
const posthogClient = posthog.init(runtimeConfig.public.posthog.key, {
|
|
18
24
|
api_host: runtimeConfig.public.posthog.host,
|
|
19
25
|
defaults: runtimeConfig.public.posthog.defaults as ConfigDefaults,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"or": "أو",
|
|
4
|
+
"error": {
|
|
5
|
+
"title": "الصفحة غير موجودة",
|
|
6
|
+
"description": "نأسف، لكن الصفحة التي تبحث عنها غير موجودة."
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"docs": {
|
|
10
|
+
"copy": {
|
|
11
|
+
"page": "نسخ الصفحة",
|
|
12
|
+
"options": "خيارات نسخ إضافية",
|
|
13
|
+
"link": "نسخ صفحة Markdown",
|
|
14
|
+
"view": "عرض كـ Markdown",
|
|
15
|
+
"gpt": "فتح في ChatGPT",
|
|
16
|
+
"claude": "فتح في Claude"
|
|
17
|
+
},
|
|
18
|
+
"framework": "إطار العمل",
|
|
19
|
+
"links": "المجتمع",
|
|
20
|
+
"toc": "في هذه الصفحة",
|
|
21
|
+
"report": "الإبلاغ عن مشكلة",
|
|
22
|
+
"edit": "تحرير هذه الصفحة"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"or": "або",
|
|
4
|
+
"error": {
|
|
5
|
+
"title": "Старонка не знойдзена",
|
|
6
|
+
"description": "Прабачце, але старонка, якую вы шукаеце, не знойдзена."
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"docs": {
|
|
10
|
+
"copy": {
|
|
11
|
+
"page": "Скапіраваць старонку",
|
|
12
|
+
"options": "Дадатковыя параметры капіравання",
|
|
13
|
+
"link": "Скапіраваць Markdown-старонку",
|
|
14
|
+
"view": "Праглядзець як Markdown",
|
|
15
|
+
"gpt": "Адкрыць у ChatGPT",
|
|
16
|
+
"claude": "Адкрыць у Claude"
|
|
17
|
+
},
|
|
18
|
+
"framework": "Фреймворк",
|
|
19
|
+
"links": "Супольнасць",
|
|
20
|
+
"toc": "На гэтай старонцы",
|
|
21
|
+
"report": "Паведаміць пра праблему",
|
|
22
|
+
"edit": "Рэдагаваць гэтую старонку"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"or": "অথবা",
|
|
4
|
+
"error": {
|
|
5
|
+
"title": "পেজ খুঁজে পাওয়া যায়নি",
|
|
6
|
+
"description": "দুঃখিত, আপনি যে পেজটি খুঁজছেন সেটি পাওয়া যায়নি।"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"docs": {
|
|
10
|
+
"copy": {
|
|
11
|
+
"page": "পেজ কপি করুন",
|
|
12
|
+
"options": "আরও কপি অপশন",
|
|
13
|
+
"link": "Markdown পেজ কপি করুন",
|
|
14
|
+
"view": "Markdown হিসেবে দেখুন",
|
|
15
|
+
"gpt": "ChatGPT এ খুলুন",
|
|
16
|
+
"claude": "Claude এ খুলুন"
|
|
17
|
+
},
|
|
18
|
+
"framework": "ফ্রেমওয়ার্ক",
|
|
19
|
+
"links": "কমিউনিটি",
|
|
20
|
+
"toc": "এই পেজে",
|
|
21
|
+
"report": "সমস্যা রিপোর্ট করুন",
|
|
22
|
+
"edit": "এই পেজ সম্পাদনা করুন"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"or": "o",
|
|
4
|
+
"error": {
|
|
5
|
+
"title": "Pàgina no trobada",
|
|
6
|
+
"description": "Ho sentim, però la pàgina que cerques no existeix."
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"docs": {
|
|
10
|
+
"copy": {
|
|
11
|
+
"page": "Copiar pàgina",
|
|
12
|
+
"options": "Més opcions de còpia",
|
|
13
|
+
"link": "Copiar pàgina Markdown",
|
|
14
|
+
"view": "Veure com a Markdown",
|
|
15
|
+
"gpt": "Obrir a ChatGPT",
|
|
16
|
+
"claude": "Obrir a Claude"
|
|
17
|
+
},
|
|
18
|
+
"framework": "Framework",
|
|
19
|
+
"links": "Comunitat",
|
|
20
|
+
"toc": "En aquesta pàgina",
|
|
21
|
+
"report": "Informar d'un problema",
|
|
22
|
+
"edit": "Editar aquesta pàgina"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"or": "یان",
|
|
4
|
+
"error": {
|
|
5
|
+
"title": "پەڕە نەدۆزرایەوە",
|
|
6
|
+
"description": "بەداخەوە، ئەم پەڕەیە نەدۆزرایەوە."
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"docs": {
|
|
10
|
+
"copy": {
|
|
11
|
+
"page": "کۆپیکردنی پەڕە",
|
|
12
|
+
"options": "بژاردەی زیاتری لەبەرگرتنەوە",
|
|
13
|
+
"link": "کۆپیکردنی پەڕەی Markdown",
|
|
14
|
+
"view": "بینین وەک Markdown",
|
|
15
|
+
"gpt": "کردنەوە لە ChatGPT",
|
|
16
|
+
"claude": "کردنەوە لە Claude"
|
|
17
|
+
},
|
|
18
|
+
"framework": "چوارچێوە",
|
|
19
|
+
"links": "کۆمەڵگا",
|
|
20
|
+
"toc": "لەم پەڕەدا",
|
|
21
|
+
"report": "ڕاپۆرتکردنی کێشە",
|
|
22
|
+
"edit": "دەستکاریکردنی ئەم پەڕەیە"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"or": "nebo",
|
|
4
|
+
"error": {
|
|
5
|
+
"title": "Stránka nenalezena",
|
|
6
|
+
"description": "Je nám líto, ale stránka, kterou hledáte, nebyla nalezena."
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"docs": {
|
|
10
|
+
"copy": {
|
|
11
|
+
"page": "Kopírovat stránku",
|
|
12
|
+
"options": "Další možnosti kopírování",
|
|
13
|
+
"link": "Kopírovat Markdown stránku",
|
|
14
|
+
"view": "Zobrazit jako Markdown",
|
|
15
|
+
"gpt": "Otevřít v ChatGPT",
|
|
16
|
+
"claude": "Otevřít v Claude"
|
|
17
|
+
},
|
|
18
|
+
"framework": "Framework",
|
|
19
|
+
"links": "Komunita",
|
|
20
|
+
"toc": "Na této stránce",
|
|
21
|
+
"report": "Nahlásit problém",
|
|
22
|
+
"edit": "Upravit tuto stránku"
|
|
23
|
+
}
|
|
24
|
+
}
|