@movk/nuxt-docs 1.1.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.
Files changed (49) hide show
  1. package/README.md +265 -0
  2. package/app/app.config.ts +53 -0
  3. package/app/app.vue +73 -0
  4. package/app/components/AdsCarbon.vue +3 -0
  5. package/app/components/Footer.vue +32 -0
  6. package/app/components/PageHeaderLinks.vue +73 -0
  7. package/app/components/StarsBg.vue +122 -0
  8. package/app/components/content/ComponentEmits.vue +43 -0
  9. package/app/components/content/ComponentExample.vue +247 -0
  10. package/app/components/content/ComponentProps.vue +105 -0
  11. package/app/components/content/ComponentPropsLinks.vue +20 -0
  12. package/app/components/content/ComponentPropsSchema.vue +55 -0
  13. package/app/components/content/ComponentSlots.vue +50 -0
  14. package/app/components/content/HeroBackground.vue +65 -0
  15. package/app/components/content/HighlightInlineType.vue +39 -0
  16. package/app/components/content/Motion.vue +21 -0
  17. package/app/components/header/Header.vue +60 -0
  18. package/app/components/header/HeaderBody.vue +19 -0
  19. package/app/components/header/HeaderBottom.vue +26 -0
  20. package/app/components/header/HeaderLogo.vue +57 -0
  21. package/app/components/theme-picker/ThemePicker.vue +152 -0
  22. package/app/components/theme-picker/ThemePickerButton.vue +37 -0
  23. package/app/composables/fetchComponentExample.ts +32 -0
  24. package/app/composables/fetchComponentMeta.ts +34 -0
  25. package/app/composables/useCategory.ts +5 -0
  26. package/app/composables/useHeader.ts +6 -0
  27. package/app/composables/useNavigation.ts +89 -0
  28. package/app/error.vue +59 -0
  29. package/app/layouts/default.vue +3 -0
  30. package/app/layouts/docs.vue +31 -0
  31. package/app/pages/docs/[...slug].vue +158 -0
  32. package/app/pages/index.vue +30 -0
  33. package/app/pages/releases.vue +92 -0
  34. package/app/plugins/prettier.ts +67 -0
  35. package/app/plugins/theme.ts +82 -0
  36. package/app/types/index.d.ts +37 -0
  37. package/app/workers/prettier.js +36 -0
  38. package/content.config.ts +68 -0
  39. package/modules/component-example.ts +128 -0
  40. package/modules/component-meta.ts +22 -0
  41. package/modules/config.ts +79 -0
  42. package/modules/css.ts +33 -0
  43. package/nuxt.config.ts +80 -0
  44. package/package.json +55 -0
  45. package/server/api/component-example.get.ts +19 -0
  46. package/server/plugins/llms.ts +24 -0
  47. package/server/routes/raw/[...slug].md.get.ts +27 -0
  48. package/utils/git.ts +108 -0
  49. package/utils/meta.ts +29 -0
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ import type { ContentNavigationItem } from '@nuxt/content'
3
+ import { mapContentNavigation } from '@nuxt/ui/utils/content'
4
+
5
+ const route = useRoute()
6
+
7
+ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
8
+
9
+ const items = computed(() => mapContentNavigation(navigation?.value.map(item => ({ ...item, children: undefined })) ?? [])?.map(item => ({
10
+ ...item,
11
+ active: route.path.startsWith(item.to as string)
12
+ })))
13
+ </script>
14
+
15
+ <template>
16
+ <USeparator class="hidden lg:flex" />
17
+
18
+ <UContainer class="hidden lg:flex items-center justify-between">
19
+ <UNavigationMenu
20
+ :items="items"
21
+ variant="pill"
22
+ highlight
23
+ class="-mx-2.5 -mb-px"
24
+ />
25
+ </UContainer>
26
+ </template>
@@ -0,0 +1,57 @@
1
+ <script lang="ts" setup>
2
+ const { header } = useAppConfig()
3
+ </script>
4
+
5
+ <template>
6
+ <NuxtLink
7
+ :to="header.to"
8
+ class="flex items-center gap-2 font-bold text-xl text-highlighted min-w-0 focus-visible:outline-primary shrink-0"
9
+ :aria-label="header.title"
10
+ >
11
+ <svg
12
+ width="256"
13
+ height="256"
14
+ viewBox="0 0 256 256"
15
+ fill="none"
16
+ xmlns="http://www.w3.org/2000/svg"
17
+ class="w-auto h-10 shrink-0"
18
+ >
19
+ <circle
20
+ cx="128"
21
+ cy="128"
22
+ r="120"
23
+ fill="var(--ui-primary)"
24
+ />
25
+ <g fill="white" opacity="0.95">
26
+ <path
27
+ d="M38.5 175.6 c-1.5 -1 -1.9 -2.7 -1.9 -7.7 0 -5.5 0.4 -7.1 3.1 -11.4 1.8 -2.7 4.8 -6.3 6.8 -7.8 1.9 -1.5 3.5 -3.2 3.5 -3.8 0 -1.2 -5.8 -0.4 -11.5 1.6 -11.7 4.1 -16.3 3.7 -19 -1.5 -2.6 -5 -1.2 -8.4 4.2 -11 6.6 -3.1 10.4 -3.5 14.3 -1.5 2.1 1.1 4.1 1.4 5.9 0.9 1.4 -0.4 3.5 -0.9 4.6 -1.1 4.9 -0.9 12.3 -3.8 16 -6.3 13.7 -9.4 18.6 -11.7 34 -16 6.6 -1.9 11.9 -2.4 31.3 -3.1 l23.4 -0.8 8.4 -8.5 c5.7 -5.8 9.5 -8.8 11.9 -9.5 1.9 -0.5 5.8 -2.4 8.7 -4.1 4.5 -2.8 5.4 -3 7.5 -1.9 2 1.1 2.9 0.9 6.2 -0.9 6.4 -3.6 7.6 -2.5 4.4 4.3 l-1.7 3.5 3.3 4.1 c2.8 3.5 3.2 4.6 2.7 7.7 -0.3 2 0 5.1 0.6 6.8 1.8 5 -0.4 8.9 -6.3 11.6 -2.9 1.4 -4.5 2.6 -4 3.3 0.4 0.5 1.6 3.5 2.6 6.5 2.8 8.8 1.9 8.2 13.5 8.5 10.2 0.2 21.7 1.9 25.3 3.7 2 1 2.3 5.1 0.3 6.7 -0.7 0.6 -5.1 1.5 -9.7 2.1 -4.6 0.5 -12.7 1.9 -17.9 3 -8.5 1.8 -12.8 2 -42 1.9 -17.9 -0.1 -35 -0.3 -38 -0.5 -3 -0.1 -10.7 -0.6 -17.1 -1 -9.7 -0.6 -12.4 -0.4 -17 1.1 -7.2 2.3 -7.3 2.4 -5.5 4.4 2.2 2.5 3.1 7.8 1.6 9.6 -2.7 3.3 -9.3 1.2 -17.4 -5.5 -5.6 -4.7 -8.8 -5 -15.6 -1.7 -4.4 2.2 -4.5 2.3 -4.8 7.6 -0.2 3.7 -0.9 5.9 -2 6.8 -2.3 1.7 -10.3 1.6 -12.7 -0.1z"
28
+ />
29
+ </g>
30
+ <g fill="white" opacity="0.98">
31
+ <path
32
+ d="M142 209.3 c-0.7 -1.6 -2.3 -5.7 -3.5 -9.3 -1.3 -3.6 -2.6 -7.2 -3 -8.1 -0.4 -1.1 0 -1.8 1.4 -2.2 1.7 -0.4 2.4 0.4 4.3 5.4 1.3 3.2 2.5 5.9 2.8 5.9 0.3 0 1.9 -2.6 3.5 -5.8 2.1 -4 3.7 -5.8 5.3 -6 1.2 -0.2 2.2 -0.1 2.2 0.3 0 0.5 -10.8 21.5 -11.5 22.3 -0.1 0.1 -0.8 -1 -1.5 -2.5z"
33
+ />
34
+ <path
35
+ d="M84.5 201.3 c1 -12.9 1.6 -13.8 4.8 -6.9 1.5 3.1 2.9 5.6 3.3 5.6 0.3 0 3.1 -2.2 6.2 -5 3 -2.7 5.7 -4.8 5.9 -4.7 0.3 0.4 3.1 19.1 2.9 19.4 -0.1 0.2 -1.3 0.4 -2.7 0.5 -2.2 0.3 -2.4 0 -2.7 -4.9 -0.2 -2.9 -0.6 -5.3 -0.9 -5.3 -0.3 0 -2.3 1.4 -4.4 3.1 -3.7 2.9 -4.1 3 -5.8 1.5 -1.7 -1.6 -1.9 -1.6 -2.5 -0.1 -0.3 0.9 -0.6 2.8 -0.6 4.1 0 1.8 -0.5 2.4 -2.1 2.4 -2 0 -2.1 -0.3 -1.4 -9.7z"
36
+ />
37
+ <path
38
+ d="M115.9 209.5 c-5.9 -3.2 -6.4 -12.8 -0.9 -17.7 3.4 -3.1 5.6 -3.4 10.3 -1.5 4.3 1.8 5.9 4 6 7.9 0.1 6.4 -1.2 10.4 -3.9 11.6 -3.3 1.6 -8.3 1.4 -11.5 -0.3z m9.6 -4.5 c2.1 -2.4 2.4 -7.2 0.5 -9.4 -2 -2.5 -7.7 -2.1 -9.1 0.6 -3.9 7.3 3.3 14.7 8.6 8.8z"
39
+ />
40
+ <path
41
+ d="M158 200 c0 -11 0 -11 2.4 -11 2.3 0 2.4 0.3 1.9 3.6 -0.6 3.5 -0.5 3.6 1.6 2.5 1.2 -0.7 2.7 -2 3.3 -3.1 0.9 -1.4 1.5 -1.6 2.6 -0.8 1 0.9 0.6 1.9 -2.5 5 l-3.8 3.9 2.5 1.9 c1.4 1.1 3.7 2.5 5.3 3.2 3 1.4 3.3 2.1 1.4 3.6 -0.9 0.8 -2.4 0.3 -5.7 -1.9 l-4.5 -3.1 -0.3 3.6 c-0.3 2.8 -0.8 3.6 -2.3 3.6 -1.8 0 -1.9 -0.8 -1.9 -11z"
42
+ />
43
+ </g>
44
+ <circle
45
+ cx="128"
46
+ cy="128"
47
+ r="118"
48
+ stroke="white"
49
+ stroke-width="1.5"
50
+ fill="none"
51
+ opacity="0.15"
52
+ />
53
+ </svg>
54
+
55
+ <p class="font-medium text-highlighted">{{ header.title }}</p>
56
+ </NuxtLink>
57
+ </template>
@@ -0,0 +1,152 @@
1
+ <script setup lang="ts">
2
+ import { omit } from '@movk/core'
3
+ import colors from 'tailwindcss/colors'
4
+
5
+ const appConfig = useAppConfig()
6
+ const colorMode = useColorMode()
7
+ const site = useSiteConfig()
8
+
9
+ const neutralColors = ['slate', 'gray', 'zinc', 'neutral', 'stone']
10
+ const neutral = computed({
11
+ get() {
12
+ return appConfig.ui.colors.neutral
13
+ },
14
+ set(option) {
15
+ appConfig.ui.colors.neutral = option
16
+ window.localStorage.setItem(`${site.name}-ui-neutral`, appConfig.ui.colors.neutral)
17
+ }
18
+ })
19
+
20
+ const colorsToOmit = ['inherit', 'current', 'transparent', 'black', 'white', ...neutralColors]
21
+ const primaryColors = Object.keys(omit(colors, colorsToOmit as any))
22
+ const primary = computed({
23
+ get() {
24
+ return appConfig.ui.colors.primary
25
+ },
26
+ set(option) {
27
+ appConfig.ui.colors.primary = option
28
+ window.localStorage.setItem(`${site.name}-ui-primary`, appConfig.ui.colors.primary)
29
+ setBlackAsPrimary(false)
30
+ }
31
+ })
32
+
33
+ const radiuses = [0, 0.125, 0.25, 0.375, 0.5]
34
+ const radius = computed({
35
+ get() {
36
+ return appConfig.theme.radius
37
+ },
38
+ set(option) {
39
+ appConfig.theme.radius = option
40
+ window.localStorage.setItem(`${site.name}-ui-radius`, String(appConfig.theme.radius))
41
+ }
42
+ })
43
+
44
+ const modes = [
45
+ { label: 'light', icon: 'i-lucide-sun' },
46
+ { label: 'dark', icon: 'i-lucide-moon' },
47
+ { label: 'system', icon: 'i-lucide-monitor' }
48
+ ]
49
+ const mode = computed({
50
+ get() {
51
+ return colorMode.value
52
+ },
53
+ set(option) {
54
+ colorMode.preference = option
55
+ }
56
+ })
57
+
58
+ function setBlackAsPrimary(value: boolean) {
59
+ appConfig.theme.blackAsPrimary = value
60
+ window.localStorage.setItem(`${site.name}-ui-black-as-primary`, String(value))
61
+ }
62
+ </script>
63
+
64
+ <template>
65
+ <UPopover :ui="{ content: 'w-72 px-6 py-4 flex flex-col gap-4' }">
66
+ <template #default="{ open }">
67
+ <UButton
68
+ icon="i-lucide-swatch-book"
69
+ color="neutral"
70
+ :variant="open ? 'soft' : 'ghost'"
71
+ square
72
+ aria-label="Color picker"
73
+ :ui="{ leadingIcon: 'text-primary' }"
74
+ />
75
+ </template>
76
+
77
+ <template #content>
78
+ <fieldset>
79
+ <legend class="text-[11px] leading-none font-semibold mb-2">
80
+ Primary
81
+ </legend>
82
+
83
+ <div class="grid grid-cols-3 gap-1 -mx-2">
84
+ <ThemePickerButton label="Black" :selected="appConfig.theme.blackAsPrimary" @click="setBlackAsPrimary(true)">
85
+ <template #leading>
86
+ <span class="inline-block w-2 h-2 rounded-full bg-black dark:bg-white" />
87
+ </template>
88
+ </ThemePickerButton>
89
+
90
+ <ThemePickerButton
91
+ v-for="color in primaryColors"
92
+ :key="color"
93
+ :label="color"
94
+ :chip="color"
95
+ :selected="!appConfig.theme.blackAsPrimary && primary === color"
96
+ @click="primary = color"
97
+ />
98
+ </div>
99
+ </fieldset>
100
+
101
+ <fieldset>
102
+ <legend class="text-[11px] leading-none font-semibold mb-2">
103
+ Neutral
104
+ </legend>
105
+
106
+ <div class="grid grid-cols-3 gap-1 -mx-2">
107
+ <ThemePickerButton
108
+ v-for="color in neutralColors"
109
+ :key="color"
110
+ :label="color"
111
+ :chip="color === 'neutral' ? 'old-neutral' : color"
112
+ :selected="neutral === color"
113
+ @click="neutral = color"
114
+ />
115
+ </div>
116
+ </fieldset>
117
+
118
+ <fieldset>
119
+ <legend class="text-[11px] leading-none font-semibold mb-2">
120
+ Radius
121
+ </legend>
122
+
123
+ <div class="grid grid-cols-5 gap-1 -mx-2">
124
+ <ThemePickerButton
125
+ v-for="r in radiuses"
126
+ :key="r"
127
+ :label="String(r)"
128
+ class="justify-center px-0"
129
+ :selected="radius === r"
130
+ @click="radius = r"
131
+ />
132
+ </div>
133
+ </fieldset>
134
+
135
+ <fieldset>
136
+ <legend class="text-[11px] leading-none font-semibold mb-2">
137
+ Theme
138
+ </legend>
139
+
140
+ <div class="grid grid-cols-3 gap-1 -mx-2">
141
+ <ThemePickerButton
142
+ v-for="m in modes"
143
+ :key="m.label"
144
+ v-bind="m"
145
+ :selected="colorMode.preference === m.label"
146
+ @click="mode = m.label"
147
+ />
148
+ </div>
149
+ </fieldset>
150
+ </template>
151
+ </UPopover>
152
+ </template>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ label: string
4
+ icon?: string
5
+ chip?: string
6
+ selected?: boolean
7
+ }>()
8
+
9
+ const slots = defineSlots<{
10
+ leading: () => any
11
+ }>()
12
+ </script>
13
+
14
+ <template>
15
+ <UButton
16
+ size="sm"
17
+ color="neutral"
18
+ variant="outline"
19
+ :icon="icon"
20
+ :label="label"
21
+ class="capitalize ring-default rounded-sm text-[11px]"
22
+ :class="[selected ? 'bg-elevated' : 'hover:bg-elevated/50']"
23
+ >
24
+ <template v-if="chip || !!slots.leading" #leading>
25
+ <slot name="leading">
26
+ <span
27
+ class="inline-block size-2 rounded-full bg-(--color-light) dark:bg-(--color-dark)"
28
+
29
+ :style="{
30
+ '--color-light': `var(--color-${chip}-500)`,
31
+ '--color-dark': `var(--color-${chip}-400)`
32
+ }"
33
+ />
34
+ </slot>
35
+ </template>
36
+ </UButton>
37
+ </template>
@@ -0,0 +1,32 @@
1
+ const useComponentExampleState = () => useState<Record<string, any>>('component-example-state', () => ({}))
2
+
3
+ export async function fetchComponentExample(name: string) {
4
+ const state = useComponentExampleState()
5
+
6
+ if (state.value[name]?.then) {
7
+ await state.value[name]
8
+ return state.value[name]
9
+ }
10
+ if (state.value[name]) {
11
+ return state.value[name]
12
+ }
13
+
14
+ // Add to nitro prerender
15
+ if (import.meta.server) {
16
+ const event = useRequestEvent()
17
+ event?.node.res.setHeader(
18
+ 'x-nitro-prerender',
19
+ [event?.node.res.getHeader('x-nitro-prerender'), `/api/component-example/${name}.json`].filter(Boolean).join(',')
20
+ )
21
+ }
22
+
23
+ // Store promise to avoid multiple calls
24
+ state.value[name] = $fetch(`/api/component-example/${name}.json`).then((data) => {
25
+ state.value[name] = data
26
+ }).catch(() => {
27
+ state.value[name] = {}
28
+ })
29
+
30
+ await state.value[name]
31
+ return state.value[name]
32
+ }
@@ -0,0 +1,34 @@
1
+ import type { ComponentMeta } from 'vue-component-meta'
2
+
3
+ const useComponentsMetaState = () => useState<Record<string, any>>('component-meta-state', () => ({}))
4
+
5
+ export async function fetchComponentMeta(name: string): Promise<{ meta: ComponentMeta }> {
6
+ const state = useComponentsMetaState()
7
+
8
+ if (state.value[name]?.then) {
9
+ await state.value[name]
10
+ return state.value[name]
11
+ }
12
+ if (state.value[name]) {
13
+ return state.value[name]
14
+ }
15
+
16
+ // Add to nitro prerender
17
+ if (import.meta.server) {
18
+ const event = useRequestEvent()
19
+ event?.node.res.setHeader(
20
+ 'x-nitro-prerender',
21
+ [event?.node.res.getHeader('x-nitro-prerender'), `/api/component-meta/${name}.json`].filter(Boolean).join(',')
22
+ )
23
+ }
24
+
25
+ // Store promise to avoid multiple calls
26
+ state.value[name] = $fetch(`/api/component-meta/${name}.json`).then((meta) => {
27
+ state.value[name] = meta
28
+ }).catch(() => {
29
+ state.value[name] = {}
30
+ })
31
+
32
+ await state.value[name]
33
+ return state.value[name]
34
+ }
@@ -0,0 +1,5 @@
1
+ export function useCategory() {
2
+ return {
3
+ categories: {} as Record<string, { id: string, title: string, icon: string }[]>
4
+ }
5
+ }
@@ -0,0 +1,6 @@
1
+ export function useHeader() {
2
+ return {
3
+ desktopLinks: computed(() => []),
4
+ mobileLinks: computed(() => [])
5
+ }
6
+ }
@@ -0,0 +1,89 @@
1
+ import type { ContentNavigationItem } from '@nuxt/content'
2
+ import { findPageBreadcrumb, findPageChildren } from '@nuxt/content/utils'
3
+ import { mapContentNavigation } from '@nuxt/ui/utils/content'
4
+
5
+ function groupChildrenByCategory(items: ContentNavigationItem[], slug: string): ContentNavigationItem[] {
6
+ if (!items.length) return []
7
+
8
+ const { categories } = useCategory()
9
+ const groups: ContentNavigationItem[] = []
10
+
11
+ const categorizedMap = new Map<string, ContentNavigationItem[]>()
12
+ const withChildren: ContentNavigationItem[] = []
13
+ const withoutChildren: ContentNavigationItem[] = []
14
+
15
+ for (const item of items) {
16
+ if (item.category) {
17
+ const key = item.category as string
18
+ if (!categorizedMap.has(key)) {
19
+ categorizedMap.set(key, [])
20
+ }
21
+ categorizedMap.get(key)!.push(item)
22
+ } else if (item.children?.length) {
23
+ withChildren.push(item)
24
+ } else {
25
+ withoutChildren.push(item)
26
+ }
27
+ }
28
+
29
+ if (withoutChildren.length) {
30
+ groups.push({
31
+ title: 'Overview',
32
+ path: `/docs/${slug}`,
33
+ icon: 'i-lucide-house',
34
+ children: withoutChildren
35
+ })
36
+ }
37
+
38
+ groups.push(...withChildren)
39
+
40
+ const categoryList = categories[slug as keyof typeof categories] ?? []
41
+ for (const category of categoryList) {
42
+ const children = categorizedMap.get(category.id)
43
+ if (children?.length) {
44
+ groups.push({
45
+ title: category.title,
46
+ path: `/docs/${slug}`,
47
+ icon: category.icon,
48
+ children
49
+ })
50
+ }
51
+ }
52
+
53
+ return groups
54
+ }
55
+
56
+ function processNavigationItem(item: ContentNavigationItem, parent?: ContentNavigationItem): ContentNavigationItem | ContentNavigationItem[] {
57
+ return {
58
+ ...item,
59
+ title: parent?.title ? parent.title : item.title,
60
+ children: item.children?.length ? item.children?.flatMap(child => processNavigationItem(child)) : undefined
61
+ }
62
+ }
63
+
64
+ export function useNavigation(navigation: Ref<ContentNavigationItem[] | undefined>) {
65
+ const rootNavigation = computed(() =>
66
+ navigation.value?.[0]?.children?.map(item => processNavigationItem(item)) as ContentNavigationItem[]
67
+ )
68
+
69
+ const navigationByCategory = computed(() => {
70
+ const route = useRoute()
71
+
72
+ const slug = route.params.slug?.[0] as string
73
+ const children = findPageChildren(navigation?.value, `/docs/${slug}`, { indexAsChild: true })
74
+
75
+ return groupChildrenByCategory(children, slug)
76
+ })
77
+
78
+ function findBreadcrumb(path: string) {
79
+ const breadcrumb = findPageBreadcrumb(navigation?.value, path, { indexAsChild: true })
80
+
81
+ return mapContentNavigation(breadcrumb).map(({ icon, ...link }) => link)
82
+ }
83
+
84
+ return {
85
+ rootNavigation,
86
+ navigationByCategory,
87
+ findBreadcrumb
88
+ }
89
+ }
package/app/error.vue ADDED
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import type { NuxtError } from '#app'
3
+ import colors from 'tailwindcss/colors'
4
+
5
+ defineProps<{
6
+ error: NuxtError
7
+ }>()
8
+
9
+ const appConfig = useAppConfig()
10
+ const colorMode = useColorMode()
11
+ const route = useRoute()
12
+
13
+ const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'))
14
+ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
15
+ server: false
16
+ })
17
+
18
+ const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
19
+ const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
20
+ const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
21
+
22
+ useHead({
23
+ meta: [
24
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
25
+ { key: 'theme-color', name: 'theme-color', content: color }
26
+ ],
27
+ style: [
28
+ { innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
29
+ { innerHTML: blackAsPrimary, id: 'nuxt-ui-black-as-primary', tagPriority: -2 }
30
+ ]
31
+ })
32
+
33
+ useSeoMeta({
34
+ title: 'Page not found',
35
+ description: 'We are sorry but this page could not be found.'
36
+ })
37
+
38
+ const { rootNavigation } = useNavigation(navigation)
39
+
40
+ provide('navigation', rootNavigation)
41
+ </script>
42
+
43
+ <template>
44
+ <UApp>
45
+ <NuxtLoadingIndicator color="var(--ui-primary)" :height="2" />
46
+
47
+ <div :class="[route.path.startsWith('/docs/') && 'root']">
48
+ <Header />
49
+
50
+ <UError :error="error" />
51
+
52
+ <Footer />
53
+
54
+ <ClientOnly>
55
+ <LazyUContentSearch :files="files" :navigation="navigation" />
56
+ </ClientOnly>
57
+ </div>
58
+ </UApp>
59
+ </template>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <slot />
3
+ </template>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import type { ContentNavigationItem } from '@nuxt/content'
3
+
4
+ const route = useRoute()
5
+
6
+ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
7
+
8
+ const { navigationByCategory } = useNavigation(navigation!)
9
+ </script>
10
+
11
+ <template>
12
+ <UMain class="relative">
13
+ <HeroBackground />
14
+
15
+ <UContainer>
16
+ <UPage>
17
+ <template #left>
18
+ <UPageAside>
19
+ <UContentNavigation
20
+ :key="route.path"
21
+ :navigation="navigationByCategory"
22
+ highlight
23
+ />
24
+ </UPageAside>
25
+ </template>
26
+
27
+ <slot />
28
+ </UPage>
29
+ </UContainer>
30
+ </UMain>
31
+ </template>