Stelle docs-site auf Nuxt-UI-Docs-Template-Stil um

This commit is contained in:
2026-04-22 19:07:15 +02:00
parent 76f86e87c1
commit 63b1c563c1
23 changed files with 13114 additions and 215 deletions

View File

@@ -0,0 +1,57 @@
export default defineAppConfig({
ui: {
colors: {
primary: 'green',
neutral: 'slate'
},
footer: {
slots: {
root: 'border-t border-default',
left: 'text-sm text-muted'
}
}
},
seo: {
siteName: 'FEDEO Docs'
},
header: {
title: 'FEDEO Docs',
to: '/',
search: true,
colorMode: true,
links: [
{
icon: 'i-simple-icons-github',
to: 'https://git.federspiel.tech/flfeders/FEDEO',
target: '_blank',
'aria-label': 'Repository'
}
]
},
footer: {
credits: `Built with Nuxt UI • © ${new Date().getFullYear()} FEDEO`,
colorMode: false,
links: [
{
icon: 'i-simple-icons-github',
to: 'https://git.federspiel.tech/flfeders/FEDEO',
target: '_blank',
'aria-label': 'Repository'
}
]
},
toc: {
title: 'Inhaltsverzeichnis',
bottom: {
title: 'Links',
links: [
{
icon: 'i-lucide-book-open',
label: 'FEDEO Projekt',
to: 'https://git.federspiel.tech/flfeders/FEDEO',
target: '_blank'
}
]
}
}
})

44
docs-site/app/app.vue Normal file
View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
const { seo } = useAppConfig()
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'))
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
server: false
})
useHead({
meta: [{ name: 'viewport', content: 'width=device-width, initial-scale=1' }],
htmlAttrs: { lang: 'de' }
})
useSeoMeta({
titleTemplate: `%s - ${seo?.siteName}`,
ogSiteName: seo?.siteName,
twitterCard: 'summary_large_image'
})
provide('navigation', navigation)
</script>
<template>
<UApp>
<NuxtLoadingIndicator />
<AppHeader />
<UMain>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</UMain>
<AppFooter />
<ClientOnly>
<LazyUContentSearch
:files="files"
:navigation="navigation"
/>
</ClientOnly>
</UApp>
</template>

View File

@@ -0,0 +1,25 @@
@import "tailwindcss";
@import "@nuxt/ui";
@source "../../../content/**/*";
@theme static {
--container-8xl: 90rem;
--font-sans: 'Public Sans', sans-serif;
--color-green-50: #EFFDF5;
--color-green-100: #D9FBE8;
--color-green-200: #B3F5D1;
--color-green-300: #75EDAE;
--color-green-400: #00DC82;
--color-green-500: #00C16A;
--color-green-600: #00A155;
--color-green-700: #007F45;
--color-green-800: #016538;
--color-green-900: #0A5331;
--color-green-950: #052E16;
}
:root {
--ui-container: var(--container-8xl);
}

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
const { footer } = useAppConfig()
</script>
<template>
<UFooter>
<template #left>
{{ footer.credits }}
</template>
<template #right>
<UColorModeButton v-if="footer?.colorMode" />
<template v-if="footer?.links">
<UButton
v-for="(link, index) of footer?.links"
:key="index"
v-bind="{ color: 'neutral', variant: 'ghost', ...link }"
/>
</template>
</template>
</UFooter>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const { header } = useAppConfig()
</script>
<template>
<UHeader
:ui="{ center: 'flex-1' }"
:to="header?.to || '/'"
>
<UContentSearchButton
v-if="header?.search"
:collapsed="false"
class="w-full"
/>
<template #left>
<NuxtLink :to="header?.to || '/'">
<AppLogo class="w-auto h-6 shrink-0" />
</NuxtLink>
</template>
<template #right>
<UContentSearchButton
v-if="header?.search"
class="lg:hidden"
/>
<UColorModeButton v-if="header?.colorMode" />
<template v-if="header?.links">
<UButton
v-for="(link, index) of header.links"
:key="index"
v-bind="{ color: 'neutral', variant: 'ghost', ...link }"
/>
</template>
</template>
<template #body>
<UContentNavigation
highlight
:navigation="navigation"
/>
</template>
</UHeader>
</template>

View File

@@ -0,0 +1,3 @@
<template>
<span class="font-semibold text-primary">FEDEO Docs</span>
</template>

31
docs-site/app/error.vue Normal file
View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
defineProps<{
error: NuxtError
}>()
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'))
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
server: false
})
provide('navigation', navigation)
</script>
<template>
<UApp>
<AppHeader />
<UError :error="error" />
<AppFooter />
<ClientOnly>
<LazyUContentSearch
:files="files"
:navigation="navigation"
/>
</ClientOnly>
</UApp>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
</script>
<template>
<UContainer>
<UPage>
<template #left>
<UPageAside>
<UContentNavigation
highlight
:navigation="navigation"
/>
</UPageAside>
</template>
<slot />
</UPage>
</UContainer>
</template>

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
import { findPageHeadline } from '@nuxt/content/utils'
definePageMeta({
layout: 'docs'
})
const route = useRoute()
const { toc } = useAppConfig()
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const { data: page } = await useAsyncData(route.path, () => queryCollection('docs').path(route.path).first())
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Seite nicht gefunden', fatal: true })
}
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
return queryCollectionItemSurroundings('docs', route.path, {
fields: ['description']
})
})
const title = page.value.seo?.title || page.value.title
const description = page.value.seo?.description || page.value.description
useSeoMeta({
title,
ogTitle: title,
description,
ogDescription: description
})
const headline = computed(() => findPageHeadline(navigation?.value, page.value?.path))
const links = computed(() => {
return [...(toc?.bottom?.links || [])].filter(Boolean)
})
</script>
<template>
<UPage v-if="page">
<UPageHeader
:title="page.title"
:description="page.description"
:headline="headline"
/>
<UPageBody>
<ContentRenderer
v-if="page"
:value="page"
/>
<USeparator v-if="surround?.length" />
<UContentSurround :surround="surround" />
</UPageBody>
<template
v-if="page?.body?.toc?.links?.length"
#right
>
<UContentToc
:title="toc?.title"
:links="page.body?.toc?.links"
>
<template
v-if="toc?.bottom"
#bottom
>
<div
class="hidden lg:block space-y-6"
:class="{ 'mt-6!': page.body?.toc?.links?.length }"
>
<USeparator
v-if="page.body?.toc?.links?.length"
type="dashed"
/>
<UPageLinks
:title="toc.bottom.title"
:links="links"
/>
</div>
</template>
</UContentToc>
</template>
</UPage>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
const { data: page } = await useAsyncData('index', () => queryCollection('landing').path('/').first())
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Seite nicht gefunden', fatal: true })
}
const title = page.value.seo?.title || page.value.title
const description = page.value.seo?.description || page.value.description
useSeoMeta({
titleTemplate: '',
title,
ogTitle: title,
description,
ogDescription: description
})
</script>
<template>
<ContentRenderer
v-if="page"
:value="page"
:prose="false"
/>
</template>