@sewadah/selaras-docs 1.0.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 ADDED
@@ -0,0 +1,75 @@
1
+ # Selaras Docs
2
+
3
+ A zero-config Nuxt documentation layer for Selaras.
4
+
5
+ Install Nuxt and the layer as development dependencies:
6
+
7
+ ```sh
8
+ bun add -d nuxt @sewadah/selaras-docs
9
+ ```
10
+
11
+ Add the layer to `nuxt.config.ts`:
12
+
13
+ ```ts
14
+ export default defineNuxtConfig({
15
+ extends: ['@sewadah/selaras-docs'],
16
+ })
17
+ ```
18
+
19
+ Then create `content/index.md`:
20
+
21
+ ```md
22
+ ---
23
+ title: My documentation
24
+ description: Documentation for my project.
25
+ ---
26
+
27
+ # My documentation
28
+
29
+ Start writing.
30
+ ```
31
+
32
+ The layer supplies Nuxt Content, Tailwind CSS v4, Selaras, an accessible
33
+ responsive documentation shell, local navigation search, and dark mode. It
34
+ uses neutral system font stacks by default. You do not
35
+ need an app root, Content config, or stylesheet for the default setup.
36
+
37
+ `nuxt` remains a peer because the documentation site is a Nuxt application;
38
+ the layer is normally a development dependency, just like Nuxt itself.
39
+
40
+ ## Customization
41
+
42
+ Use `app.config.ts` for site identity and the default shell:
43
+
44
+ ```ts
45
+ export default defineAppConfig({
46
+ selarasDocs: {
47
+ site: {
48
+ name: 'My project',
49
+ description: 'Documentation for my project.',
50
+ },
51
+ repository: {
52
+ url: 'https://github.com/example/project',
53
+ editLinks: true,
54
+ },
55
+ header: {
56
+ links: [
57
+ { label: 'Guide', to: '/guide/getting-started' },
58
+ {
59
+ label: 'Resources',
60
+ children: [
61
+ { label: 'API reference', to: '/api' },
62
+ { label: 'GitHub', to: 'https://github.com/example/project', target: '_blank', rel: 'noreferrer' },
63
+ ],
64
+ },
65
+ ],
66
+ },
67
+ },
68
+ })
69
+ ```
70
+
71
+ Create an application component with the same name as a layer component, such
72
+ as `components/DocsHeader.vue`, to replace that part of the shell. For a host
73
+ that owns Tailwind compilation or uses a class prefix, set
74
+ `selarasDocs: { css: false }` in `nuxt.config.ts` and import Selaras and the
75
+ docs stylesheet from the host CSS entry.
@@ -0,0 +1,22 @@
1
+ export default defineAppConfig({
2
+ selarasDocs: {
3
+ site: {
4
+ name: 'Documentation',
5
+ },
6
+ repository: {
7
+ branch: 'main',
8
+ contentDirectory: 'content',
9
+ editLinks: true,
10
+ },
11
+ header: {
12
+ showTitle: true,
13
+ search: true,
14
+ colorMode: true,
15
+ },
16
+ toc: {
17
+ enabled: true,
18
+ title: 'On this page',
19
+ },
20
+ footer: {},
21
+ },
22
+ })
package/app/app.vue ADDED
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <SApp>
3
+ <NuxtLayout>
4
+ <NuxtPage />
5
+ </NuxtLayout>
6
+ <ClientOnly>
7
+ <DocsSearch />
8
+ </ClientOnly>
9
+ </SApp>
10
+ </template>
@@ -0,0 +1,187 @@
1
+ .selaras-docs-content button p,
2
+ .selaras-docs-content a p {
3
+ margin: 0;
4
+ color: inherit;
5
+ }
6
+
7
+ .selaras-docs-root {
8
+ background: var(--selaras-resolved-surface-canvas, var(--selaras-resolved-surface-default));
9
+ }
10
+
11
+ .selaras-docs-skip-link {
12
+ position: fixed;
13
+ z-index: 100;
14
+ top: 0.75rem;
15
+ left: 0.75rem;
16
+ transform: translateY(-160%);
17
+ border-radius: var(--selaras-resolved-radius-md);
18
+ background: var(--selaras-resolved-color-primary-fill);
19
+ color: var(--selaras-resolved-color-primary-on-fill);
20
+ padding: 0.5rem 0.75rem;
21
+ text-decoration: none;
22
+ }
23
+
24
+ .selaras-docs-skip-link:focus {
25
+ transform: translateY(0);
26
+ outline: 2px solid var(--selaras-resolved-color-primary-focus);
27
+ outline-offset: 2px;
28
+ }
29
+
30
+ .selaras-docs-shell {
31
+ display: flex;
32
+ width: min(100%, 90rem);
33
+ min-height: calc(100vh - 4rem);
34
+ margin: 0 auto;
35
+ }
36
+
37
+ .selaras-docs-sidebar {
38
+ border-inline-end: 1px solid var(--selaras-resolved-border-default);
39
+ }
40
+
41
+ .selaras-docs-main {
42
+ min-width: 0;
43
+ flex: 1;
44
+ padding: 2rem;
45
+ }
46
+
47
+ .selaras-docs-page {
48
+ max-width: 78rem;
49
+ margin: 0 auto;
50
+ }
51
+
52
+ .selaras-docs-page-grid {
53
+ display: grid;
54
+ grid-template-columns: minmax(0, 1fr);
55
+ gap: 3rem;
56
+ }
57
+
58
+ .selaras-docs-article {
59
+ min-width: 0;
60
+ }
61
+
62
+ .selaras-docs-page-header {
63
+ margin-bottom: 1.5rem;
64
+ }
65
+
66
+ .selaras-docs-content {
67
+ min-width: 0;
68
+ }
69
+
70
+ .selaras-docs-toc {
71
+ width: 14rem;
72
+ }
73
+
74
+ .selaras-docs-edit-link {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ gap: 0.375rem;
78
+ margin-top: 2.5rem;
79
+ color: var(--selaras-resolved-text-muted);
80
+ font-size: 0.875rem;
81
+ text-decoration: none;
82
+ }
83
+
84
+ .selaras-docs-edit-link:hover {
85
+ color: var(--selaras-resolved-text-default);
86
+ text-decoration: underline;
87
+ }
88
+
89
+ .selaras-docs-surround {
90
+ margin-top: 2rem;
91
+ }
92
+
93
+ .selaras-docs-footer {
94
+ border-top: 1px solid var(--selaras-resolved-border-default);
95
+ }
96
+
97
+ .selaras-docs-footer-inner {
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: space-between;
101
+ gap: 1rem;
102
+ min-height: 5rem;
103
+ }
104
+
105
+ .selaras-docs-footer-text {
106
+ margin: 0;
107
+ color: var(--selaras-resolved-text-muted);
108
+ font-size: 0.875rem;
109
+ }
110
+
111
+ .selaras-docs-footer-links {
112
+ display: flex;
113
+ flex-wrap: wrap;
114
+ gap: 1rem;
115
+ }
116
+
117
+ .selaras-docs-footer-links a {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: 0.375rem;
121
+ color: var(--selaras-resolved-text-muted);
122
+ font-size: 0.875rem;
123
+ text-decoration: none;
124
+ }
125
+
126
+ .selaras-docs-footer-links a:hover {
127
+ color: var(--selaras-resolved-text-default);
128
+ text-decoration: underline;
129
+ }
130
+
131
+ @media (min-width: 64rem) {
132
+ .selaras-docs-nav-toggle {
133
+ display: none;
134
+ }
135
+
136
+ .selaras-docs-page-grid {
137
+ grid-template-columns: minmax(0, 1fr) 14rem;
138
+ }
139
+ }
140
+
141
+ @media (max-width: 63.999rem) {
142
+ .selaras-docs-main {
143
+ padding: 1.5rem;
144
+ }
145
+
146
+ .selaras-docs-header-link.selaras-docs-header-link--desktop {
147
+ display: none;
148
+ }
149
+
150
+ .selaras-docs-footer-inner {
151
+ align-items: flex-start;
152
+ flex-direction: column;
153
+ justify-content: center;
154
+ padding-top: 1rem;
155
+ padding-bottom: 1rem;
156
+ }
157
+ }
158
+
159
+ .selaras-docs-brand {
160
+ display: inline-flex;
161
+ align-items: center;
162
+ color: inherit;
163
+ text-decoration: none;
164
+ }
165
+
166
+ .selaras-docs-brand {
167
+ gap: 0.625rem;
168
+ font-weight: 600;
169
+ }
170
+
171
+ .selaras-docs-logo {
172
+ display: block;
173
+ width: auto;
174
+ height: 1.75rem;
175
+ }
176
+
177
+ .selaras-docs-logo--dark {
178
+ display: none;
179
+ }
180
+
181
+ .dark .selaras-docs-logo--light {
182
+ display: none;
183
+ }
184
+
185
+ .dark .selaras-docs-logo--dark {
186
+ display: block;
187
+ }
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ path: string
4
+ }>()
5
+
6
+ const appConfig = useAppConfig()
7
+ const docsConfig = computed(() => appConfig.selarasDocs ?? {})
8
+
9
+ const editUrl = computed(() => {
10
+ const repository = docsConfig.value.repository
11
+ if (!repository?.url || repository.editLinks === false)
12
+ return undefined
13
+
14
+ const base = repository.url.replace(/\/$/, '')
15
+ const relativePath = props.path === '/' ? 'index.md' : `${props.path.replace(/^\//, '')}.md`
16
+ const contentPath = [repository.contentDirectory ?? 'content', relativePath].join('/')
17
+ const branch = repository.branch ?? 'main'
18
+
19
+ if (base.startsWith('https://github.com/'))
20
+ return `${base}/edit/${branch}/${contentPath}`
21
+ if (base.startsWith('https://gitlab.com/'))
22
+ return `${base}/-/edit/${branch}/${contentPath}`
23
+ return undefined
24
+ })
25
+ </script>
26
+
27
+ <template>
28
+ <a v-if="editUrl" :href="editUrl" class="selaras-docs-edit-link" target="_blank" rel="noreferrer">
29
+ Edit this page
30
+ </a>
31
+ </template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ const appConfig = useAppConfig()
3
+ const docsConfig = computed(() => appConfig.selarasDocs ?? {})
4
+ const footer = computed(() => docsConfig.value.footer)
5
+ </script>
6
+
7
+ <template>
8
+ <footer v-if="footer?.text || footer?.links?.length" class="selaras-docs-footer">
9
+ <SContainer size="full" class="selaras-docs-footer-inner">
10
+ <p v-if="footer?.text" class="selaras-docs-footer-text">
11
+ {{ footer.text }}
12
+ </p>
13
+ <nav v-if="footer?.links?.length" class="selaras-docs-footer-links" aria-label="Footer navigation">
14
+ <NuxtLink
15
+ v-for="link in footer.links"
16
+ :key="`${link.to}:${link.label ?? link.icon ?? ''}`"
17
+ :to="link.to"
18
+ :target="link.target"
19
+ :aria-label="link.ariaLabel ?? link.label"
20
+ >
21
+ <SIcon v-if="link.icon" :name="link.icon" />
22
+ <span v-if="link.label">{{ link.label }}</span>
23
+ </NuxtLink>
24
+ </nav>
25
+ </SContainer>
26
+ </footer>
27
+ </template>
@@ -0,0 +1,69 @@
1
+ <script setup lang="ts">
2
+ const route = useRoute()
3
+ const appConfig = useAppConfig()
4
+ const { data: navigation } = useDocsNavigation()
5
+ const navigationOpen = ref(false)
6
+
7
+ const docsConfig = computed(() => appConfig.selarasDocs ?? {})
8
+ const siteName = computed(() => docsConfig.value.header?.title ?? docsConfig.value.site?.name ?? 'Documentation')
9
+ const showTitle = computed(() => docsConfig.value.header?.showTitle !== false)
10
+ const logo = computed(() => docsConfig.value.site?.logo)
11
+ const logoAlt = computed(() => typeof logo.value === 'object' ? (logo.value.alt ?? siteName.value) : siteName.value)
12
+ const lightLogo = computed(() => typeof logo.value === 'string' ? logo.value : (logo.value?.light ?? logo.value?.dark))
13
+ const darkLogo = computed(() => typeof logo.value === 'object' ? (logo.value.dark ?? logo.value.light) : logo.value)
14
+ const hasDistinctLogos = computed(() => Boolean(lightLogo.value && darkLogo.value && lightLogo.value !== darkLogo.value))
15
+ const headerLinks = computed(() => docsConfig.value.header?.links ?? [])
16
+ const repositoryUrl = computed(() => docsConfig.value.repository?.url)
17
+
18
+ watch(() => route.path, () => {
19
+ navigationOpen.value = false
20
+ })
21
+ </script>
22
+
23
+ <template>
24
+ <SHeader>
25
+ <NuxtLink to="/" class="selaras-docs-brand" :aria-label="`${siteName} home`">
26
+ <template v-if="lightLogo">
27
+ <img :src="lightLogo" :alt="logoAlt" class="selaras-docs-logo" :class="{ 'selaras-docs-logo--light': hasDistinctLogos }">
28
+ <img v-if="hasDistinctLogos" :src="darkLogo" :alt="logoAlt" class="selaras-docs-logo selaras-docs-logo--dark">
29
+ </template>
30
+ <span v-if="showTitle">{{ siteName }}</span>
31
+ </NuxtLink>
32
+ <template #right>
33
+ <SNavigationMenu
34
+ v-if="headerLinks.length"
35
+ :items="headerLinks"
36
+ variant="link"
37
+ :ui="{ root: 'hidden w-auto lg:flex' }"
38
+ />
39
+ <DocsSearchButton v-if="docsConfig.header?.search !== false" />
40
+ <SColorModeToggle v-if="docsConfig.header?.colorMode !== false" />
41
+ <SButton
42
+ v-if="repositoryUrl"
43
+ as="a"
44
+ :href="repositoryUrl"
45
+ target="_blank"
46
+ rel="noreferrer"
47
+ aria-label="Source repository"
48
+ title="Source repository"
49
+ icon="hugeicons:github"
50
+ variant="ghost"
51
+ color="neutral"
52
+ />
53
+ <SButton
54
+ variant="ghost"
55
+ color="neutral"
56
+ class="selaras-docs-nav-toggle"
57
+ aria-label="Open documentation navigation"
58
+ @click="navigationOpen = true"
59
+ >
60
+ Menu
61
+ </SButton>
62
+ </template>
63
+ </SHeader>
64
+ <SDrawer v-model:open="navigationOpen" side="left" title="Documentation navigation" :handle="false">
65
+ <template #body>
66
+ <SContentNavigation :navigation="navigation ?? []" />
67
+ </template>
68
+ </SDrawer>
69
+ </template>
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ import type { CommandPaletteGroup } from '@sewadah/selaras/components/CommandPalette.vue'
3
+ import type { ContentNavigationLink } from '@sewadah/selaras/components/ContentNavigation.vue'
4
+
5
+ const appConfig = useAppConfig()
6
+ const { data: navigation } = useDocsNavigation()
7
+ const docsConfig = computed(() => appConfig.selarasDocs ?? {})
8
+
9
+ function leafPages(links: ContentNavigationLink[]): ContentNavigationLink[] {
10
+ return links.flatMap(link => link.children?.length ? leafPages(link.children) : [link])
11
+ }
12
+
13
+ const groups = computed<CommandPaletteGroup[]>(() => {
14
+ return (navigation.value ?? [])
15
+ .map(section => ({
16
+ label: section.title,
17
+ items: leafPages(section.children?.length ? section.children : [section]).map(page => ({
18
+ label: page.title,
19
+ onSelect: () => navigateTo(page.path),
20
+ })),
21
+ }))
22
+ .filter(group => group.items.length > 0)
23
+ })
24
+ </script>
25
+
26
+ <template>
27
+ <SCommandPalette v-if="docsConfig.header?.search !== false" :groups="groups" />
28
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ const { open } = useCommandPalette()
3
+ const icons = useIcons()
4
+ const messages = useMessages()
5
+ </script>
6
+
7
+ <template>
8
+ <SButton variant="soft" color="neutral" size="sm" :icon="icons.search" aria-label="Search documentation" @click="open">
9
+ <span class="hidden sm:inline">{{ messages.search }}</span>
10
+ <SKbd value="meta" size="sm" />
11
+ <SKbd value="K" size="sm" />
12
+ </SButton>
13
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ const { data: navigation } = useDocsNavigation()
3
+ </script>
4
+
5
+ <template>
6
+ <SPageAside
7
+ class="selaras-docs-sidebar"
8
+ aria-label="Documentation navigation"
9
+ :ui="{ root: 'top-16 h-[calc(100vh-4rem)]' }"
10
+ >
11
+ <SContentNavigation :navigation="navigation ?? []" />
12
+ </SPageAside>
13
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import type { TocLink } from '@sewadah/selaras/components/ContentToc.vue'
3
+
4
+ defineProps<{
5
+ links: TocLink[]
6
+ }>()
7
+
8
+ const appConfig = useAppConfig()
9
+ const docsConfig = computed(() => appConfig.selarasDocs ?? {})
10
+ </script>
11
+
12
+ <template>
13
+ <SPageAside
14
+ v-if="links.length && docsConfig.toc?.enabled !== false"
15
+ class="selaras-docs-toc"
16
+ aria-label="Table of contents"
17
+ :ui="{ root: 'top-16 h-[calc(100vh-4rem)]' }"
18
+ >
19
+ <SContentToc :links="links" :title="docsConfig.toc?.title" />
20
+ </SPageAside>
21
+ </template>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ name: string
4
+ }>()
5
+
6
+ const exampleModules = import.meta.glob('~/components/content/examples/**/*.vue')
7
+
8
+ function pascalCase(value: string) {
9
+ return value.split('-').map(part => part.charAt(0).toUpperCase() + part.slice(1)).join('')
10
+ }
11
+
12
+ const resolvedComponent = computed(() => {
13
+ const suffix = `/${pascalCase(props.name)}.vue`
14
+ const match = Object.entries(exampleModules).find(([path]) => path.endsWith(suffix))
15
+ return match ? defineAsyncComponent(match[1] as any) : null
16
+ })
17
+ </script>
18
+
19
+ <template>
20
+ <section v-if="resolvedComponent" :data-example="name">
21
+ <component :is="resolvedComponent" />
22
+ </section>
23
+ <p v-else role="alert">
24
+ Example "{{ name }}" not found
25
+ </p>
26
+ </template>
@@ -0,0 +1,4 @@
1
+ export function useDocsNavigation() {
2
+ return useAsyncData('selaras-docs-navigation', () =>
3
+ queryCollectionNavigation('docs').order('order', 'ASC'))
4
+ }
@@ -0,0 +1,20 @@
1
+ import type { ContentNavigationLink } from '@sewadah/selaras/components/ContentNavigation.vue'
2
+
3
+ function flattenNavigation(links: ContentNavigationLink[]): ContentNavigationLink[] {
4
+ return links.flatMap(link => link.children?.length ? flattenNavigation(link.children) : [link])
5
+ }
6
+
7
+ /** The adjacent leaf pages in the collection's reading order. */
8
+ export function useDocsSurround() {
9
+ const route = useRoute()
10
+ const { data: navigation } = useDocsNavigation()
11
+
12
+ return computed(() => {
13
+ const links = flattenNavigation(navigation.value ?? [])
14
+ const index = links.findIndex(link => link.path === route.path)
15
+
16
+ return index === -1
17
+ ? { prev: undefined, next: undefined }
18
+ : { prev: links[index - 1], next: links[index + 1] }
19
+ })
20
+ }
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <div class="selaras-docs-root min-h-screen text-[var(--selaras-resolved-text-default)]">
3
+ <a class="selaras-docs-skip-link" href="#selaras-docs-main">Skip to content</a>
4
+ <DocsHeader />
5
+ <div class="selaras-docs-shell">
6
+ <DocsSidebar />
7
+ <main id="selaras-docs-main" class="selaras-docs-main" tabindex="-1">
8
+ <slot />
9
+ </main>
10
+ </div>
11
+ <DocsFooter />
12
+ </div>
13
+ </template>
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ import type { TocLink } from '@sewadah/selaras/components/ContentToc.vue'
3
+ import ProseH1 from '@sewadah/selaras/components/ProseH1.vue'
4
+ import ProseH2 from '@sewadah/selaras/components/ProseH2.vue'
5
+ import ProseH3 from '@sewadah/selaras/components/ProseH3.vue'
6
+ import ProseH4 from '@sewadah/selaras/components/ProseH4.vue'
7
+ import ProseH5 from '@sewadah/selaras/components/ProseH5.vue'
8
+ import ProseH6 from '@sewadah/selaras/components/ProseH6.vue'
9
+ import ProsePre from '@sewadah/selaras/components/ProsePre.vue'
10
+
11
+ const route = useRoute()
12
+ const appConfig = useAppConfig()
13
+ const { data: page } = await useAsyncData(`selaras-docs:${route.path}`, () => queryCollection('docs').path(route.path).first())
14
+
15
+ if (!page.value) {
16
+ throw createError({ statusCode: 404, statusMessage: 'Documentation page not found', fatal: true })
17
+ }
18
+
19
+ definePageMeta({ layout: 'selaras-docs' })
20
+ useSeoMeta({ title: page.value.title, description: page.value.description ?? appConfig.selarasDocs?.site?.description })
21
+
22
+ const proseComponents = {
23
+ h1: ProseH1,
24
+ h2: ProseH2,
25
+ h3: ProseH3,
26
+ h4: ProseH4,
27
+ h5: ProseH5,
28
+ h6: ProseH6,
29
+ pre: ProsePre,
30
+ }
31
+
32
+ const tocLinks = computed(() => (page.value?.body?.toc?.links ?? []) as unknown as TocLink[])
33
+ const surround = useDocsSurround()
34
+ </script>
35
+
36
+ <template>
37
+ <SContainer size="full" class="selaras-docs-page">
38
+ <div class="selaras-docs-page-grid">
39
+ <article class="selaras-docs-article">
40
+ <p v-if="page!.description" class="selaras-docs-description">
41
+ {{ page!.description }}
42
+ </p>
43
+ <ContentRenderer :value="page!" :components="proseComponents" class="selaras-docs-content selaras-prose" />
44
+ <DocsEditLink :path="route.path" />
45
+ <SContentSurround :prev="surround.prev" :next="surround.next" class="selaras-docs-surround" />
46
+ </article>
47
+ <DocsTableOfContents :links="tocLinks" />
48
+ </div>
49
+ </SContainer>
50
+ </template>
@@ -0,0 +1,20 @@
1
+ import { resolve } from 'node:path'
2
+ import { defineCollection, defineContentConfig, z } from '@nuxt/content'
3
+ import { useNuxt } from '@nuxt/kit'
4
+
5
+ const { options } = useNuxt()
6
+
7
+ export default defineContentConfig({
8
+ collections: {
9
+ docs: defineCollection({
10
+ type: 'page',
11
+ source: {
12
+ cwd: resolve(options.rootDir, 'content'),
13
+ include: '**/*.md',
14
+ },
15
+ schema: z.object({
16
+ order: z.number().optional(),
17
+ }),
18
+ }),
19
+ },
20
+ })
@@ -0,0 +1,41 @@
1
+ import { resolve } from 'node:path'
2
+ import { addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
3
+
4
+ function cssPath(path) {
5
+ return path.replaceAll('\\', '/')
6
+ }
7
+
8
+ export default defineNuxtModule({
9
+ meta: {
10
+ name: '@sewadah/selaras-docs',
11
+ configKey: 'selarasDocs',
12
+ },
13
+ defaults: {
14
+ css: true,
15
+ },
16
+ setup(options, nuxt) {
17
+ if (!options.css)
18
+ return
19
+
20
+ const { resolve: resolveLayer } = createResolver(import.meta.url)
21
+ const layerApp = cssPath(resolveLayer('../app'))
22
+ const consumerContent = cssPath(resolve(nuxt.options.rootDir, 'content'))
23
+ const consumerComponents = cssPath(resolve(nuxt.options.rootDir, 'components'))
24
+ const stylesheet = addTemplate({
25
+ filename: 'selaras-docs.css',
26
+ write: true,
27
+ getContents: () => `@import "tailwindcss";
28
+ @import "@sewadah/selaras/theme.css";
29
+ @import "#selaras/tailwind.css";
30
+ @import "@sewadah/selaras/prose.css";
31
+ @import "@sewadah/selaras-docs/style.css";
32
+
33
+ @source "${layerApp}/**/*";
34
+ @source "${consumerContent}/**/*";
35
+ @source "${consumerComponents}/**/*";
36
+ `,
37
+ })
38
+
39
+ nuxt.options.css.unshift(stylesheet.dst)
40
+ },
41
+ })
@@ -0,0 +1,15 @@
1
+ import { createResolver } from '@nuxt/kit'
2
+ import { defineNuxtConfig } from 'nuxt/config'
3
+
4
+ const { resolve } = createResolver(import.meta.url)
5
+
6
+ export default defineNuxtConfig({
7
+ modules: ['@sewadah/selaras', '@nuxt/content', resolve('./modules/docs.mjs')],
8
+ // Selaras supports Node versions with node:sqlite. Keeping Content on its
9
+ // native connector lets an installed docs layer avoid a native addon.
10
+ content: {
11
+ experimental: {
12
+ sqliteConnector: 'native',
13
+ },
14
+ },
15
+ })
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@sewadah/selaras-docs",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "Nuxt Content documentation layer for Selaras",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./types.d.ts",
9
+ "import": "./nuxt.config.mjs"
10
+ },
11
+ "./style.css": "./app/assets/style.css"
12
+ },
13
+ "main": "./nuxt.config.mjs",
14
+ "files": [
15
+ "app",
16
+ "content.config.ts",
17
+ "modules",
18
+ "nuxt.config.mjs",
19
+ "types.d.ts"
20
+ ],
21
+ "engines": {
22
+ "node": "^22.19.0 || ^24.11.0 || >=26.0.0"
23
+ },
24
+ "peerDependencies": {
25
+ "nuxt": "^4.5.2"
26
+ },
27
+ "dependencies": {
28
+ "@nuxt/content": "^3.16.0",
29
+ "@nuxt/kit": "^4.5.2",
30
+ "@sewadah/selaras": "^0.3.0",
31
+ "tailwindcss": "^4.3.0"
32
+ }
33
+ }
package/types.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ export interface SelarasDocsModuleOptions {
2
+ /**
3
+ * Inject the default unprefixed Tailwind and Selaras stylesheet.
4
+ * Disable this when the consuming app owns a prefixed or complete-theme CSS entry.
5
+ * @default true
6
+ */
7
+ css?: boolean
8
+ }
9
+
10
+ export interface SelarasDocsLink {
11
+ label?: string
12
+ to: string
13
+ icon?: string
14
+ target?: string
15
+ ariaLabel?: string
16
+ }
17
+
18
+ /** A top-level header item rendered by Selaras' NavigationMenu. */
19
+ export interface SelarasDocsNavigationItem {
20
+ /** Required so every header control has an accessible name. */
21
+ label: string
22
+ to?: string
23
+ icon?: string
24
+ target?: string
25
+ rel?: string
26
+ ariaLabel?: string
27
+ disabled?: boolean
28
+ children?: SelarasDocsNavigationItem[]
29
+ }
30
+
31
+ export interface SelarasDocsLogo {
32
+ light?: string
33
+ dark?: string
34
+ alt?: string
35
+ }
36
+
37
+ export interface SelarasDocsAppConfig {
38
+ site?: {
39
+ name?: string
40
+ description?: string
41
+ logo?: string | SelarasDocsLogo
42
+ }
43
+ repository?: {
44
+ url?: string
45
+ branch?: string
46
+ contentDirectory?: string
47
+ editLinks?: boolean
48
+ }
49
+ header?: {
50
+ title?: string
51
+ showTitle?: boolean
52
+ search?: boolean
53
+ colorMode?: boolean
54
+ links?: SelarasDocsNavigationItem[]
55
+ }
56
+ toc?: {
57
+ enabled?: boolean
58
+ title?: string
59
+ }
60
+ footer?: {
61
+ text?: string
62
+ links?: SelarasDocsLink[]
63
+ }
64
+ }
65
+
66
+ declare module '@nuxt/schema' {
67
+ interface AppConfig {
68
+ selarasDocs?: SelarasDocsAppConfig
69
+ }
70
+
71
+ interface NuxtConfig {
72
+ selarasDocs?: SelarasDocsModuleOptions
73
+ }
74
+
75
+ interface NuxtOptions {
76
+ selarasDocs: SelarasDocsModuleOptions
77
+ }
78
+ }
79
+
80
+ declare module 'nuxt/schema' {
81
+ interface AppConfig {
82
+ selarasDocs?: SelarasDocsAppConfig
83
+ }
84
+
85
+ interface NuxtConfig {
86
+ selarasDocs?: SelarasDocsModuleOptions
87
+ }
88
+
89
+ interface NuxtOptions {
90
+ selarasDocs: SelarasDocsModuleOptions
91
+ }
92
+ }