@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,247 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ChipProps } from '@nuxt/ui'
|
|
3
|
+
import { camelCase } from 'scule'
|
|
4
|
+
import { hash } from 'ohash'
|
|
5
|
+
import { useElementSize } from '@vueuse/core'
|
|
6
|
+
import { get, set } from '#ui/utils'
|
|
7
|
+
|
|
8
|
+
const { preview = true, source = true, prettier = true, ...props } = defineProps<{
|
|
9
|
+
name: string
|
|
10
|
+
class?: any
|
|
11
|
+
/**
|
|
12
|
+
* Whether to render the component in an iframe
|
|
13
|
+
* @defaultValue false
|
|
14
|
+
*/
|
|
15
|
+
iframe?: boolean | { [key: string]: any }
|
|
16
|
+
/**
|
|
17
|
+
* Whether to display the component in a mobile-sized iframe viewport
|
|
18
|
+
* @defaultValue false
|
|
19
|
+
*/
|
|
20
|
+
iframeMobile?: boolean
|
|
21
|
+
props?: { [key: string]: any }
|
|
22
|
+
/**
|
|
23
|
+
* Whether to format the code with Prettier
|
|
24
|
+
* @defaultValue false
|
|
25
|
+
*/
|
|
26
|
+
prettier?: boolean
|
|
27
|
+
/**
|
|
28
|
+
* Whether to collapse the code block
|
|
29
|
+
* @defaultValue false
|
|
30
|
+
*/
|
|
31
|
+
collapse?: boolean | {
|
|
32
|
+
icon?: string
|
|
33
|
+
name?: string
|
|
34
|
+
openText?: string
|
|
35
|
+
closeText?: string
|
|
36
|
+
open?: boolean
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether to show the preview
|
|
40
|
+
* When `false`, the filename will be shown instead
|
|
41
|
+
* @defaultValue true
|
|
42
|
+
*/
|
|
43
|
+
preview?: boolean
|
|
44
|
+
/**
|
|
45
|
+
* Whether to show the source code
|
|
46
|
+
* @defaultValue true
|
|
47
|
+
*/
|
|
48
|
+
source?: boolean
|
|
49
|
+
/**
|
|
50
|
+
* A list of variable props to link to the component.
|
|
51
|
+
*/
|
|
52
|
+
options?: Array<{
|
|
53
|
+
alias?: string
|
|
54
|
+
name: string
|
|
55
|
+
label: string
|
|
56
|
+
items?: any[]
|
|
57
|
+
default: any
|
|
58
|
+
multiple?: boolean
|
|
59
|
+
}>
|
|
60
|
+
/**
|
|
61
|
+
* A list of line numbers to highlight in the code block
|
|
62
|
+
*/
|
|
63
|
+
highlights?: number[]
|
|
64
|
+
/**
|
|
65
|
+
* Whether to add overflow-hidden to wrapper
|
|
66
|
+
*/
|
|
67
|
+
overflowHidden?: boolean
|
|
68
|
+
}>()
|
|
69
|
+
|
|
70
|
+
const slots = defineSlots<{
|
|
71
|
+
options(props?: {}): any
|
|
72
|
+
code(props?: {}): any
|
|
73
|
+
}>()
|
|
74
|
+
|
|
75
|
+
const el = ref<HTMLElement | null>(null)
|
|
76
|
+
const { $prettier } = useNuxtApp()
|
|
77
|
+
|
|
78
|
+
const { width } = useElementSize(el)
|
|
79
|
+
|
|
80
|
+
const camelName = camelCase(props.name)
|
|
81
|
+
|
|
82
|
+
const data = await fetchComponentExample(camelName)
|
|
83
|
+
|
|
84
|
+
const componentProps = reactive({ ...(props.props || {}) })
|
|
85
|
+
|
|
86
|
+
const code = computed(() => {
|
|
87
|
+
let code = ''
|
|
88
|
+
|
|
89
|
+
if (props.collapse) {
|
|
90
|
+
// 构建 code-collapse 的属性
|
|
91
|
+
const collapseAttrs = typeof props.collapse === 'object'
|
|
92
|
+
? Object.entries(props.collapse)
|
|
93
|
+
.map(([key, value]) => {
|
|
94
|
+
if (typeof value === 'boolean') {
|
|
95
|
+
return value ? key : ''
|
|
96
|
+
}
|
|
97
|
+
return `${key}="${value}"`
|
|
98
|
+
})
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join(' ')
|
|
101
|
+
: ''
|
|
102
|
+
|
|
103
|
+
code += `::code-collapse${collapseAttrs ? `{${collapseAttrs}}` : ''}
|
|
104
|
+
`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
code += `\`\`\`vue ${preview ? '' : ` [${data.pascalName}.vue]`}${props.highlights?.length ? `{${props.highlights.join('-')}}` : ''}
|
|
108
|
+
${data?.code ?? ''}
|
|
109
|
+
\`\`\``
|
|
110
|
+
|
|
111
|
+
if (props.collapse) {
|
|
112
|
+
code += `
|
|
113
|
+
::`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return code
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const { data: ast } = await useAsyncData(`component-example-${camelName}${hash({ props: componentProps, collapse: props.collapse })}`, async () => {
|
|
120
|
+
if (!prettier) {
|
|
121
|
+
return parseMarkdown(code.value)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let formatted = ''
|
|
125
|
+
try {
|
|
126
|
+
formatted = await $prettier.format(code.value, {
|
|
127
|
+
trailingComma: 'none',
|
|
128
|
+
semi: false,
|
|
129
|
+
singleQuote: true,
|
|
130
|
+
printWidth: 100
|
|
131
|
+
})
|
|
132
|
+
} catch {
|
|
133
|
+
formatted = code.value
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return parseMarkdown(formatted)
|
|
137
|
+
}, { watch: [code] })
|
|
138
|
+
|
|
139
|
+
const optionsValues = ref(props.options?.reduce((acc, option) => {
|
|
140
|
+
if (option.name) {
|
|
141
|
+
acc[option.alias || option.name] = option.default
|
|
142
|
+
}
|
|
143
|
+
if (option.name.toLowerCase().endsWith('color') && option.items?.length) {
|
|
144
|
+
option.items = option.items.map((item: any) => ({
|
|
145
|
+
label: item,
|
|
146
|
+
value: item,
|
|
147
|
+
chip: { color: item }
|
|
148
|
+
}))
|
|
149
|
+
}
|
|
150
|
+
return acc
|
|
151
|
+
}, {} as Record<string, any>) || {})
|
|
152
|
+
|
|
153
|
+
const urlSearchParams = computed(() => {
|
|
154
|
+
const params = {
|
|
155
|
+
...optionsValues.value,
|
|
156
|
+
...componentProps
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!props.iframeMobile) {
|
|
160
|
+
params.width = Math.round(width.value).toString()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return new URLSearchParams(params).toString()
|
|
164
|
+
})
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
<template>
|
|
168
|
+
<div ref="el" class="my-5" :style="{ '--ui-header-height': '4rem' }">
|
|
169
|
+
<template v-if="preview">
|
|
170
|
+
<div class="border border-muted relative z-[1]" :class="[{ 'border-b-0 rounded-t-md': source, 'rounded-md': !source, 'overflow-hidden': props.overflowHidden }]">
|
|
171
|
+
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-muted">
|
|
172
|
+
<slot name="options" />
|
|
173
|
+
|
|
174
|
+
<UFormField
|
|
175
|
+
v-for="option in props.options"
|
|
176
|
+
:key="option.name"
|
|
177
|
+
:label="option.label"
|
|
178
|
+
:name="option.name"
|
|
179
|
+
size="sm"
|
|
180
|
+
class="inline-flex ring ring-accented rounded-sm"
|
|
181
|
+
:ui="{
|
|
182
|
+
wrapper: 'bg-elevated/50 rounded-l-sm flex border-r border-accented',
|
|
183
|
+
label: 'text-muted px-2 py-1.5',
|
|
184
|
+
container: 'mt-0'
|
|
185
|
+
}"
|
|
186
|
+
>
|
|
187
|
+
<USelectMenu
|
|
188
|
+
v-if="option.items?.length"
|
|
189
|
+
:model-value="get(optionsValues, option.name)"
|
|
190
|
+
:items="option.items"
|
|
191
|
+
:search-input="false"
|
|
192
|
+
:value-key="option.name.toLowerCase().endsWith('color') ? 'value' : undefined"
|
|
193
|
+
color="neutral"
|
|
194
|
+
variant="soft"
|
|
195
|
+
class="rounded-sm rounded-l-none min-w-12"
|
|
196
|
+
:multiple="option.multiple"
|
|
197
|
+
:class="[option.name.toLowerCase().endsWith('color') && 'pl-6']"
|
|
198
|
+
:ui="{ itemLeadingChip: 'size-2' }"
|
|
199
|
+
@update:model-value="set(optionsValues, option.name, $event)"
|
|
200
|
+
>
|
|
201
|
+
<template v-if="option.name.toLowerCase().endsWith('color')" #leading="{ modelValue, ui }">
|
|
202
|
+
<UChip
|
|
203
|
+
inset
|
|
204
|
+
standalone
|
|
205
|
+
:color="(modelValue as any)"
|
|
206
|
+
:size="(ui.itemLeadingChipSize() as ChipProps['size'])"
|
|
207
|
+
class="size-2"
|
|
208
|
+
/>
|
|
209
|
+
</template>
|
|
210
|
+
</USelectMenu>
|
|
211
|
+
<UInput
|
|
212
|
+
v-else
|
|
213
|
+
:model-value="get(optionsValues, option.name)"
|
|
214
|
+
color="neutral"
|
|
215
|
+
variant="soft"
|
|
216
|
+
:ui="{ base: 'rounded-sm rounded-l-none min-w-12' }"
|
|
217
|
+
@update:model-value="set(optionsValues, option.name, $event)"
|
|
218
|
+
/>
|
|
219
|
+
</UFormField>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<iframe
|
|
223
|
+
v-if="iframe"
|
|
224
|
+
v-bind="typeof iframe === 'object' ? iframe : {}"
|
|
225
|
+
:src="`/examples/${name}?${urlSearchParams}`"
|
|
226
|
+
class="relative w-full"
|
|
227
|
+
:class="[props.class, !iframeMobile && 'lg:left-1/2 lg:-translate-x-1/2 lg:w-[1024px]']"
|
|
228
|
+
/>
|
|
229
|
+
<div v-else class="flex justify-center p-4" :class="props.class">
|
|
230
|
+
<component :is="camelName" v-bind="{ ...componentProps, ...optionsValues }" />
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</template>
|
|
234
|
+
|
|
235
|
+
<template v-if="source">
|
|
236
|
+
<div v-if="!!slots.code" class="[&_pre]:!rounded-t-none [&_div.my-5]:!mt-0">
|
|
237
|
+
<slot name="code" />
|
|
238
|
+
</div>
|
|
239
|
+
<MDCRenderer
|
|
240
|
+
v-else-if="ast"
|
|
241
|
+
:body="ast.body"
|
|
242
|
+
:data="ast.data"
|
|
243
|
+
class="[&_pre]:!rounded-t-none [&_div.my-5]:!mt-0"
|
|
244
|
+
/>
|
|
245
|
+
</template>
|
|
246
|
+
</div>
|
|
247
|
+
</template>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ComponentMeta } from 'vue-component-meta'
|
|
3
|
+
import { camelCase, kebabCase } from 'scule'
|
|
4
|
+
|
|
5
|
+
const { ignore = [], slug } = defineProps<{
|
|
6
|
+
/**
|
|
7
|
+
* The slug of the component to fetch props for.
|
|
8
|
+
* @defaultValue route path's last segment
|
|
9
|
+
*/
|
|
10
|
+
slug?: string
|
|
11
|
+
/**
|
|
12
|
+
* An array of prop names to ignore.
|
|
13
|
+
*/
|
|
14
|
+
ignore?: string[]
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
const route = useRoute()
|
|
18
|
+
|
|
19
|
+
const componentName = camelCase(slug ?? route.path.split('/').pop() ?? '')
|
|
20
|
+
const meta = await fetchComponentMeta(componentName as any)
|
|
21
|
+
|
|
22
|
+
const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
|
|
23
|
+
if (!meta?.meta?.props?.length) {
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return meta.meta.props.filter((prop) => {
|
|
28
|
+
return !ignore?.includes(prop.name)
|
|
29
|
+
}).map((prop) => {
|
|
30
|
+
if (prop.default) {
|
|
31
|
+
prop.default = prop.default.replace(' as never', '').replace(/^"(.*)"$/, '\'$1\'')
|
|
32
|
+
} else {
|
|
33
|
+
const tag = prop.tags?.find(tag => tag.name === 'defaultValue')?.text
|
|
34
|
+
if (tag) {
|
|
35
|
+
prop.default = tag
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// @ts-expect-error - Type is not correct
|
|
40
|
+
prop.type = !prop.type.startsWith('boolean') && prop.schema?.kind === 'enum' && Object.keys(prop.schema.schema)?.length ? Object.values(prop.schema.schema).map(schema => schema?.type ? schema.type : schema).join(' | ') : prop.type
|
|
41
|
+
return prop
|
|
42
|
+
}).sort((a, b) => {
|
|
43
|
+
if (a.name === 'as') {
|
|
44
|
+
return -1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (b.name === 'as') {
|
|
48
|
+
return 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (a.name === 'ui') {
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (b.name === 'ui') {
|
|
56
|
+
return -1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return 0
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<ProseTable>
|
|
66
|
+
<ProseThead>
|
|
67
|
+
<ProseTr>
|
|
68
|
+
<ProseTh>
|
|
69
|
+
Prop
|
|
70
|
+
</ProseTh>
|
|
71
|
+
<ProseTh>
|
|
72
|
+
Default
|
|
73
|
+
</ProseTh>
|
|
74
|
+
<ProseTh>
|
|
75
|
+
Type
|
|
76
|
+
</ProseTh>
|
|
77
|
+
</ProseTr>
|
|
78
|
+
</ProseThead>
|
|
79
|
+
<ProseTbody>
|
|
80
|
+
<ProseTr v-for="prop in metaProps" :key="prop.name">
|
|
81
|
+
<ProseTd>
|
|
82
|
+
<ProseCode>
|
|
83
|
+
{{ prop.name }}
|
|
84
|
+
</ProseCode>
|
|
85
|
+
</ProseTd>
|
|
86
|
+
<ProseTd>
|
|
87
|
+
<HighlightInlineType v-if="prop.default" :type="prop.default" />
|
|
88
|
+
</ProseTd>
|
|
89
|
+
<ProseTd>
|
|
90
|
+
<HighlightInlineType v-if="prop.type" :type="prop.type" />
|
|
91
|
+
|
|
92
|
+
<MDC
|
|
93
|
+
v-if="prop.description"
|
|
94
|
+
:value="prop.description"
|
|
95
|
+
class="text-toned mt-1"
|
|
96
|
+
:cache-key="`${kebabCase(route.path)}-${prop.name}-description`"
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<ComponentPropsLinks v-if="prop.tags?.length" :prop="prop" />
|
|
100
|
+
<ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" />
|
|
101
|
+
</ProseTd>
|
|
102
|
+
</ProseTr>
|
|
103
|
+
</ProseTbody>
|
|
104
|
+
</ProseTable>
|
|
105
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { kebabCase } from 'scule'
|
|
3
|
+
import type { PropertyMeta } from 'vue-component-meta'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
prop: PropertyMeta
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const route = useRoute()
|
|
10
|
+
|
|
11
|
+
const links = computed(() => props.prop.tags?.filter((tag: any) => tag.name === 'link' || tag.name === 'see'))
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<ProseUl v-if="links?.length">
|
|
16
|
+
<ProseLi v-for="(link, index) in links" :key="index">
|
|
17
|
+
<MDC :value="link.text ?? ''" class="my-1" :cache-key="`${kebabCase(route.path)}-${prop.name}-link-${index}`" />
|
|
18
|
+
</ProseLi>
|
|
19
|
+
</ProseUl>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { kebabCase } from 'scule'
|
|
3
|
+
import type { PropertyMeta } from 'vue-component-meta'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
prop: PropertyMeta
|
|
7
|
+
ignore?: string[]
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const route = useRoute()
|
|
11
|
+
|
|
12
|
+
function getSchemaProps(schema: PropertyMeta['schema']): any {
|
|
13
|
+
if (!schema || typeof schema === 'string' || !schema.schema) {
|
|
14
|
+
return []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (schema.kind === 'object') {
|
|
18
|
+
return Object.values(schema.schema).filter(prop => !props.ignore?.includes(prop.name))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (Array.isArray(schema.schema) ? schema.schema : Object.values(schema.schema)).flatMap(getSchemaProps as any)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const schemaProps = computed(() => {
|
|
25
|
+
return getSchemaProps(props.prop.schema).map((prop: any) => {
|
|
26
|
+
const defaultValue = prop.default ?? prop.tags?.find((tag: any) => tag.name === 'defaultValue')?.text
|
|
27
|
+
let description = prop.description
|
|
28
|
+
if (defaultValue) {
|
|
29
|
+
description = description ? `${description} Defaults to \`${defaultValue}\`{lang="ts-type"}.` : `Defaults to \`${defaultValue}\`{lang="ts-type"}.`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
...prop,
|
|
34
|
+
description
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<ProseCollapsible v-if="schemaProps?.length" class="mt-1 mb-0">
|
|
42
|
+
<ProseUl>
|
|
43
|
+
<ProseLi v-for="schemaProp in schemaProps" :key="schemaProp.name">
|
|
44
|
+
<HighlightInlineType :type="`${schemaProp.name}${schemaProp.required === false ? '?' : ''}: ${schemaProp.type}`" />
|
|
45
|
+
|
|
46
|
+
<MDC
|
|
47
|
+
v-if="schemaProp.description"
|
|
48
|
+
:value="schemaProp.description"
|
|
49
|
+
class="text-muted my-1"
|
|
50
|
+
:cache-key="`${kebabCase(route.path)}-${prop.name}-${schemaProp.name}-description`"
|
|
51
|
+
/>
|
|
52
|
+
</ProseLi>
|
|
53
|
+
</ProseUl>
|
|
54
|
+
</ProseCollapsible>
|
|
55
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { camelCase, kebabCase } from 'scule'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
/**
|
|
6
|
+
* The slug of the component to fetch slots for.
|
|
7
|
+
* @defaultValue route path's last segment
|
|
8
|
+
*/
|
|
9
|
+
slug?: string
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const route = useRoute()
|
|
13
|
+
|
|
14
|
+
const componentName = camelCase(props.slug ?? route.path.split('/').pop() ?? '')
|
|
15
|
+
const meta = await fetchComponentMeta(componentName as any)
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<ProseTable>
|
|
20
|
+
<ProseThead>
|
|
21
|
+
<ProseTr>
|
|
22
|
+
<ProseTh>
|
|
23
|
+
Slot
|
|
24
|
+
</ProseTh>
|
|
25
|
+
<ProseTh>
|
|
26
|
+
Type
|
|
27
|
+
</ProseTh>
|
|
28
|
+
</ProseTr>
|
|
29
|
+
</ProseThead>
|
|
30
|
+
<ProseTbody>
|
|
31
|
+
<ProseTr v-for="slot in (meta?.meta?.slots || [])" :key="slot.name">
|
|
32
|
+
<ProseTd>
|
|
33
|
+
<ProseCode>
|
|
34
|
+
{{ slot.name }}
|
|
35
|
+
</ProseCode>
|
|
36
|
+
</ProseTd>
|
|
37
|
+
<ProseTd>
|
|
38
|
+
<HighlightInlineType v-if="slot.type" :type="slot.type" />
|
|
39
|
+
|
|
40
|
+
<MDC
|
|
41
|
+
v-if="slot.description"
|
|
42
|
+
:value="slot.description"
|
|
43
|
+
class="text-toned mt-1"
|
|
44
|
+
:cache-key="`${kebabCase(route.path)}-${slot.name}-description`"
|
|
45
|
+
/>
|
|
46
|
+
</ProseTd>
|
|
47
|
+
</ProseTr>
|
|
48
|
+
</ProseTbody>
|
|
49
|
+
</ProseTable>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { isLoading } = useLoadingIndicator()
|
|
3
|
+
|
|
4
|
+
const route = useRoute()
|
|
5
|
+
const heroBackgroundClass = computed(() => route.meta.heroBackground || '')
|
|
6
|
+
const appear = ref(false)
|
|
7
|
+
const appeared = ref(false)
|
|
8
|
+
|
|
9
|
+
onMounted(() => {
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
appear.value = true
|
|
12
|
+
setTimeout(() => {
|
|
13
|
+
appeared.value = true
|
|
14
|
+
}, 1000)
|
|
15
|
+
}, 0)
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div
|
|
21
|
+
class="absolute w-full -top-px transition-all text-primary shrink-0"
|
|
22
|
+
:class="[
|
|
23
|
+
isLoading ? 'animate-pulse' : (appear ? heroBackgroundClass : 'opacity-0'),
|
|
24
|
+
appeared ? 'duration-[400ms]' : 'duration-1000'
|
|
25
|
+
]"
|
|
26
|
+
>
|
|
27
|
+
<svg
|
|
28
|
+
viewBox="0 0 1440 181"
|
|
29
|
+
fill="none"
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
+
class="pointer-events-none"
|
|
32
|
+
>
|
|
33
|
+
<mask id="path-1-inside-1_414_5526" fill="white">
|
|
34
|
+
<path d="M0 0H1440V181H0V0Z" />
|
|
35
|
+
</mask>
|
|
36
|
+
<path d="M0 0H1440V181H0V0Z" fill="url(#paint0_linear_414_5526)" fill-opacity="0.22" />
|
|
37
|
+
<path d="M0 2H1440V-2H0V2Z" fill="url(#paint1_linear_414_5526)" mask="url(#path-1-inside-1_414_5526)" />
|
|
38
|
+
<defs>
|
|
39
|
+
<linearGradient
|
|
40
|
+
id="paint0_linear_414_5526"
|
|
41
|
+
x1="720"
|
|
42
|
+
y1="0"
|
|
43
|
+
x2="720"
|
|
44
|
+
y2="181"
|
|
45
|
+
gradientUnits="userSpaceOnUse"
|
|
46
|
+
>
|
|
47
|
+
<stop stop-color="currentColor" />
|
|
48
|
+
<stop offset="1" stop-color="currentColor" stop-opacity="0" />
|
|
49
|
+
</linearGradient>
|
|
50
|
+
<linearGradient
|
|
51
|
+
id="paint1_linear_414_5526"
|
|
52
|
+
x1="0"
|
|
53
|
+
y1="90.5"
|
|
54
|
+
x2="1440"
|
|
55
|
+
y2="90.5"
|
|
56
|
+
gradientUnits="userSpaceOnUse"
|
|
57
|
+
>
|
|
58
|
+
<stop stop-color="currentColor" stop-opacity="0" />
|
|
59
|
+
<stop offset="0.395" stop-color="currentColor" />
|
|
60
|
+
<stop offset="1" stop-color="currentColor" stop-opacity="0" />
|
|
61
|
+
</linearGradient>
|
|
62
|
+
</defs>
|
|
63
|
+
</svg>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!-- https://github.com/nuxt/ui/blob/v4/docs/app/components/content/HighlightInlineType.vue -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import { hash } from 'ohash'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
type: string
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const type = computed(() => {
|
|
10
|
+
let type = props.type
|
|
11
|
+
if (type.includes(', "as" | "asChild" | "forceMount">')) {
|
|
12
|
+
type = type.replace(`, "as" | "asChild" | "forceMount">`, ``).replace('Omit<', '')
|
|
13
|
+
}
|
|
14
|
+
if (type.includes(', "as" | "asChild">')) {
|
|
15
|
+
type = type.replace(', "as" | "asChild">', '').replace('Omit<', '')
|
|
16
|
+
}
|
|
17
|
+
if (type.startsWith('undefined |')) {
|
|
18
|
+
type = type.replace('undefined |', '')
|
|
19
|
+
}
|
|
20
|
+
if (type.endsWith('| undefined')) {
|
|
21
|
+
type = type.replace('| undefined', '')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return type
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const { data: ast } = await useAsyncData(`hightlight-inline-code-${hash(type.value).slice(0, 10)}`, () => parseMarkdown(`\`${type.value}\`{lang="ts-type"}`), {
|
|
28
|
+
watch: [type]
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<ClientOnly>
|
|
34
|
+
<MDCRenderer v-if="ast?.body" :body="ast.body" :data="ast.data || {}" />
|
|
35
|
+
<template #fallback>
|
|
36
|
+
<ProseCode>{{ type }}</ProseCode>
|
|
37
|
+
</template>
|
|
38
|
+
</ClientOnly>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { Options } from 'motion-v'
|
|
3
|
+
import { Motion } from 'motion-v'
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
initial = { scale: 1.1, opacity: 0, filter: 'blur(20px)' },
|
|
7
|
+
animate = { scale: 1, opacity: 1, filter: 'blur(0px)' },
|
|
8
|
+
transition = { duration: 0.6, delay: 0.1 }
|
|
9
|
+
} = defineProps<Options>()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<Motion
|
|
14
|
+
:initial="initial"
|
|
15
|
+
:animate="animate"
|
|
16
|
+
:transition="transition"
|
|
17
|
+
v-bind="$attrs"
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</Motion>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ButtonProps } from '@nuxt/ui'
|
|
3
|
+
|
|
4
|
+
const route = useRoute()
|
|
5
|
+
const { desktopLinks } = useHeader()
|
|
6
|
+
const { header, github } = useAppConfig()
|
|
7
|
+
|
|
8
|
+
const links = computed<ButtonProps[]>(() => github && github.url
|
|
9
|
+
? [
|
|
10
|
+
{
|
|
11
|
+
'icon': 'i-simple-icons-github',
|
|
12
|
+
'to': github.url,
|
|
13
|
+
'target': '_blank',
|
|
14
|
+
'aria-label': 'GitHub'
|
|
15
|
+
},
|
|
16
|
+
...header?.links || []
|
|
17
|
+
]
|
|
18
|
+
: header.links)
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<UHeader :ui="{ left: 'min-w-0' }" class="flex flex-col">
|
|
23
|
+
<template #left>
|
|
24
|
+
<HeaderLogo />
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<UNavigationMenu :items="desktopLinks" variant="link" />
|
|
28
|
+
|
|
29
|
+
<template #right>
|
|
30
|
+
<ThemePicker />
|
|
31
|
+
|
|
32
|
+
<UTooltip text="Search" :kbds="['meta', 'K']">
|
|
33
|
+
<UContentSearchButton v-if="header?.search" />
|
|
34
|
+
</UTooltip>
|
|
35
|
+
|
|
36
|
+
<UTooltip text="Color Mode">
|
|
37
|
+
<UColorModeButton v-if="header?.colorMode" aria-label="Color Mode" />
|
|
38
|
+
</UTooltip>
|
|
39
|
+
|
|
40
|
+
<template v-if="links?.length">
|
|
41
|
+
<UTooltip
|
|
42
|
+
v-for="(link, count) in links"
|
|
43
|
+
:key="count"
|
|
44
|
+
:text="link.label || (link as any)['aria-label']"
|
|
45
|
+
class="hidden lg:flex"
|
|
46
|
+
>
|
|
47
|
+
<UButton v-bind="{ color: 'neutral', variant: 'ghost', ...link }" />
|
|
48
|
+
</UTooltip>
|
|
49
|
+
</template>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<template #body>
|
|
53
|
+
<HeaderBody />
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<template v-if="route.path.startsWith('/docs/')" #bottom>
|
|
57
|
+
<HeaderBottom />
|
|
58
|
+
</template>
|
|
59
|
+
</UHeader>
|
|
60
|
+
</template>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ContentNavigationItem } from '@nuxt/content'
|
|
3
|
+
|
|
4
|
+
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
|
5
|
+
|
|
6
|
+
const route = useRoute()
|
|
7
|
+
const { mobileLinks } = useHeader()
|
|
8
|
+
const { navigationByCategory } = useNavigation(navigation!)
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<UNavigationMenu orientation="vertical" :items="mobileLinks" class="-mx-2.5" />
|
|
13
|
+
|
|
14
|
+
<template v-if="route.path.startsWith('/docs/')">
|
|
15
|
+
<USeparator type="dashed" class="mt-4 mb-6" />
|
|
16
|
+
|
|
17
|
+
<UContentNavigation :navigation="navigationByCategory" highlight :ui="{ linkTrailingBadge: 'font-semibold uppercase' }" />
|
|
18
|
+
</template>
|
|
19
|
+
</template>
|