@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.
- package/README.md +265 -0
- package/app/app.config.ts +53 -0
- package/app/app.vue +73 -0
- package/app/components/AdsCarbon.vue +3 -0
- package/app/components/Footer.vue +32 -0
- package/app/components/PageHeaderLinks.vue +73 -0
- package/app/components/StarsBg.vue +122 -0
- package/app/components/content/ComponentEmits.vue +43 -0
- package/app/components/content/ComponentExample.vue +247 -0
- package/app/components/content/ComponentProps.vue +105 -0
- package/app/components/content/ComponentPropsLinks.vue +20 -0
- package/app/components/content/ComponentPropsSchema.vue +55 -0
- package/app/components/content/ComponentSlots.vue +50 -0
- package/app/components/content/HeroBackground.vue +65 -0
- package/app/components/content/HighlightInlineType.vue +39 -0
- package/app/components/content/Motion.vue +21 -0
- package/app/components/header/Header.vue +60 -0
- package/app/components/header/HeaderBody.vue +19 -0
- package/app/components/header/HeaderBottom.vue +26 -0
- package/app/components/header/HeaderLogo.vue +57 -0
- package/app/components/theme-picker/ThemePicker.vue +152 -0
- package/app/components/theme-picker/ThemePickerButton.vue +37 -0
- package/app/composables/fetchComponentExample.ts +32 -0
- package/app/composables/fetchComponentMeta.ts +34 -0
- package/app/composables/useCategory.ts +5 -0
- package/app/composables/useHeader.ts +6 -0
- package/app/composables/useNavigation.ts +89 -0
- package/app/error.vue +59 -0
- package/app/layouts/default.vue +3 -0
- package/app/layouts/docs.vue +31 -0
- package/app/pages/docs/[...slug].vue +158 -0
- package/app/pages/index.vue +30 -0
- package/app/pages/releases.vue +92 -0
- package/app/plugins/prettier.ts +67 -0
- package/app/plugins/theme.ts +82 -0
- package/app/types/index.d.ts +37 -0
- package/app/workers/prettier.js +36 -0
- package/content.config.ts +68 -0
- package/modules/component-example.ts +128 -0
- package/modules/component-meta.ts +22 -0
- package/modules/config.ts +79 -0
- package/modules/css.ts +33 -0
- package/nuxt.config.ts +80 -0
- package/package.json +55 -0
- package/server/api/component-example.get.ts +19 -0
- package/server/plugins/llms.ts +24 -0
- package/server/routes/raw/[...slug].md.get.ts +27 -0
- package/utils/git.ts +108 -0
- 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,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,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>
|