@uxfront/layer-docs 0.1.0 → 0.1.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 +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 +36 -0
- package/app/components/app/AppSubHeader.vue +2 -2
- package/app/components/docs/DocsPageHeaderLinks.vue +1 -0
- package/app/composables/useDocsSections.ts +8 -1
- package/app/pages/[[lang]]/docs/[section]/[...slug].vue +4 -1
- package/app/plugins/posthog.client.ts +6 -0
- package/i18n/locales/en.json +1 -0
- package/package.json +1 -1
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,36 @@
|
|
|
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
|
+
const appConfig = useAppConfig();
|
|
19
|
+
|
|
20
|
+
const attribution = computed(() => appConfig.header?.attribution);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<p v-if="attribution?.label" class="shrink-0 text-sm text-muted">
|
|
25
|
+
<!-- Non-breaking space: Vue's `condense` whitespace handling drops a plain
|
|
26
|
+
trailing space before a newline, and the credit should not wrap. -->
|
|
27
|
+
<template v-if="attribution.prefix">{{ attribution.prefix }} </template>
|
|
28
|
+
<ULink
|
|
29
|
+
v-if="attribution.to"
|
|
30
|
+
:to="attribution.to"
|
|
31
|
+
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"
|
|
32
|
+
>{{ attribution.label }}</ULink
|
|
33
|
+
>
|
|
34
|
+
<template v-else>{{ attribution.label }}</template>
|
|
35
|
+
</p>
|
|
36
|
+
</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>
|
|
@@ -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
|
}
|
|
@@ -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,
|
package/i18n/locales/en.json
CHANGED
package/package.json
CHANGED