@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 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 size="sm" variant="ghost" color="neutral" class="-me-1.5" square>
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
- <template #title>
56
- <AppHeaderLogo class="h-6 w-auto shrink-0" />
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 }}&nbsp;</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>
@@ -68,6 +68,7 @@ async function copyPage() {
68
68
  color="neutral"
69
69
  variant="soft"
70
70
  class="border-l border-muted"
71
+ :aria-label="t('docs.copy.options')"
71
72
  />
72
73
  </UDropdownMenu>
73
74
  </UFieldGroup>
@@ -46,5 +46,12 @@ export function useDocsSections() {
46
46
 
47
47
  const activeSection = computed(() => sections.value.find((section) => section.active));
48
48
 
49
- return { sections, activeSection };
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
- <USeparator>
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,
@@ -9,6 +9,7 @@
9
9
  "docs": {
10
10
  "copy": {
11
11
  "page": "Copy page",
12
+ "options": "More copy options",
12
13
  "link": "Copy Markdown page",
13
14
  "view": "View as Markdown",
14
15
  "gpt": "Open in ChatGPT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxfront/layer-docs",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Neutral, brandable Nuxt-layer documentation theme. Consumers extend it and supply their own branding, content and section topology.",
5
5
  "keywords": [
6
6
  "docs",