@farming-labs/nuxt-theme 0.0.2-beta.20 → 0.0.2-beta.22

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/package.json CHANGED
@@ -1,7 +1,20 @@
1
1
  {
2
2
  "name": "@farming-labs/nuxt-theme",
3
- "version": "0.0.2-beta.20",
3
+ "version": "0.0.2-beta.22",
4
4
  "description": "Nuxt/Vue UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
5
+ "keywords": [
6
+ "docs",
7
+ "documentation",
8
+ "nuxt",
9
+ "theme",
10
+ "vue"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Farming Labs",
14
+ "files": [
15
+ "src",
16
+ "styles"
17
+ ],
5
18
  "type": "module",
6
19
  "exports": {
7
20
  ".": {
@@ -38,26 +51,13 @@
38
51
  "./styles/colorful.css": "./styles/colorful.css",
39
52
  "./colorful/css": "./styles/colorful-bundle.css"
40
53
  },
41
- "files": [
42
- "src",
43
- "styles"
44
- ],
45
- "keywords": [
46
- "docs",
47
- "nuxt",
48
- "vue",
49
- "theme",
50
- "documentation"
51
- ],
52
- "author": "Farming Labs",
53
- "license": "MIT",
54
54
  "dependencies": {
55
55
  "sugar-high": "^0.9.5",
56
- "@farming-labs/docs": "0.0.2-beta.20"
56
+ "@farming-labs/docs": "0.0.2-beta.22"
57
57
  },
58
58
  "peerDependencies": {
59
- "vue": ">=3.0.0",
60
- "nuxt": ">=3.0.0"
59
+ "nuxt": ">=3.0.0",
60
+ "vue": ">=3.0.0"
61
61
  },
62
62
  "peerDependenciesMeta": {
63
63
  "nuxt": {
@@ -1,10 +1,10 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from "vue";
3
3
 
4
- const props = withDefaults(
5
- defineProps<{ pathname?: string; entry?: string }>(),
6
- { pathname: "", entry: "docs" }
7
- );
4
+ const props = withDefaults(defineProps<{ pathname?: string; entry?: string }>(), {
5
+ pathname: "",
6
+ entry: "docs",
7
+ });
8
8
 
9
9
  const segments = computed(() => {
10
10
  return props.pathname.split("/").filter(Boolean);
@@ -18,16 +18,12 @@ const props = defineProps<{
18
18
  const titleSuffix = computed(() =>
19
19
  props.config?.metadata?.titleTemplate
20
20
  ? String(props.config.metadata.titleTemplate).replace("%s", "")
21
- : " – Docs"
21
+ : " – Docs",
22
22
  );
23
23
 
24
- const tocEnabled = computed(
25
- () => (props.config?.theme as any)?.ui?.layout?.toc?.enabled ?? true
26
- );
24
+ const tocEnabled = computed(() => (props.config?.theme as any)?.ui?.layout?.toc?.enabled ?? true);
27
25
 
28
- const tocStyle = computed(
29
- () => (props.config?.theme as any)?.ui?.layout?.toc?.style ?? "default"
30
- );
26
+ const tocStyle = computed(() => (props.config?.theme as any)?.ui?.layout?.toc?.style ?? "default");
31
27
 
32
28
  const breadcrumbEnabled = computed(() => {
33
29
  const bc = props.config?.breadcrumb;
@@ -37,26 +33,19 @@ const breadcrumbEnabled = computed(() => {
37
33
  return true;
38
34
  });
39
35
 
40
- const showEditOnGithub = computed(
41
- () => !!props.config?.github && !!props.data.editOnGithub
42
- );
36
+ const showEditOnGithub = computed(() => !!props.config?.github && !!props.data.editOnGithub);
43
37
  const showLastModified = computed(() => !!props.data.lastModified);
44
38
 
45
39
  const entry = computed(() => (props.config?.entry as string) ?? "docs");
46
40
 
47
41
  const metaDescription = computed(
48
- () =>
49
- props.data.description ??
50
- (props.config?.metadata as any)?.description ??
51
- undefined
42
+ () => props.data.description ?? (props.config?.metadata as any)?.description ?? undefined,
52
43
  );
53
44
 
54
45
  useHead({
55
46
  title: () => `${props.data.title}${titleSuffix.value}`,
56
47
  meta: () =>
57
- metaDescription.value
58
- ? [{ name: "description", content: metaDescription.value }]
59
- : [],
48
+ metaDescription.value ? [{ name: "description", content: metaDescription.value }] : [],
60
49
  });
61
50
  </script>
62
51
 
@@ -21,17 +21,13 @@ const props = withDefaults(
21
21
  titleUrl?: string;
22
22
  triggerComponent?: object | null;
23
23
  }>(),
24
- { config: null, title: undefined, titleUrl: undefined, triggerComponent: null }
24
+ { config: null, title: undefined, titleUrl: undefined, triggerComponent: null },
25
25
  );
26
26
 
27
27
  const route = useRoute();
28
28
 
29
- const resolvedTitle = computed(
30
- () => props.title ?? props.config?.nav?.title ?? "Docs"
31
- );
32
- const resolvedTitleUrl = computed(
33
- () => props.titleUrl ?? props.config?.nav?.url ?? "/docs"
34
- );
29
+ const resolvedTitle = computed(() => props.title ?? props.config?.nav?.title ?? "Docs");
30
+ const resolvedTitleUrl = computed(() => props.titleUrl ?? props.config?.nav?.url ?? "/docs");
35
31
 
36
32
  const showThemeToggle = computed(() => {
37
33
  const toggle = props.config?.themeToggle;
@@ -62,7 +58,9 @@ const themeInitScript = computed(() => {
62
58
  return `document.documentElement.classList.remove('light','dark');document.documentElement.classList.add('${forcedTheme.value}')`;
63
59
  }
64
60
  const def = defaultTheme.value;
65
- const fallback = def ? `'${def}'` : `(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light')`;
61
+ const fallback = def
62
+ ? `'${def}'`
63
+ : `(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light')`;
66
64
  return [
67
65
  "(function(){",
68
66
  "var m=document.cookie.match(/(?:^|;\\s*)theme=(\\w+)/);",
@@ -142,25 +140,26 @@ function buildLayoutCSS(layout: Record<string, any> | undefined): string {
142
140
  if (rootVars.length === 0 && desktopVars.length === 0) return "";
143
141
  const parts: string[] = [];
144
142
  if (rootVars.length > 0) parts.push(`:root {\n ${rootVars.join("\n ")}\n}`);
145
- if (desktopVars.length > 0) parts.push(`@media (min-width: 1024px) {\n :root {\n ${desktopVars.join("\n ")}\n }\n}`);
143
+ if (desktopVars.length > 0)
144
+ parts.push(
145
+ `@media (min-width: 1024px) {\n :root {\n ${desktopVars.join("\n ")}\n }\n}`,
146
+ );
146
147
  return parts.join("\n");
147
148
  }
148
149
 
149
150
  const overrideCSS = computed(() => {
150
- const colorOverrides = (props.config?.theme as any)?._userColorOverrides
151
- ?? (props.config?.theme as any)?.ui?.colors;
151
+ const colorOverrides =
152
+ (props.config?.theme as any)?._userColorOverrides ?? (props.config?.theme as any)?.ui?.colors;
152
153
  const typography = (props.config?.theme as any)?.ui?.typography;
153
154
  const layout = (props.config?.theme as any)?.ui?.layout;
154
- return [buildColorsCSS(colorOverrides), buildTypographyCSS(typography), buildLayoutCSS(layout)].filter(Boolean).join("\n");
155
+ return [buildColorsCSS(colorOverrides), buildTypographyCSS(typography), buildLayoutCSS(layout)]
156
+ .filter(Boolean)
157
+ .join("\n");
155
158
  });
156
159
 
157
160
  useHead(() => ({
158
- script: [
159
- { innerHTML: themeInitScript.value, tagPosition: "head" },
160
- ],
161
- style: overrideCSS.value
162
- ? [{ innerHTML: overrideCSS.value }]
163
- : [],
161
+ script: [{ innerHTML: themeInitScript.value, tagPosition: "head" }],
162
+ style: overrideCSS.value ? [{ innerHTML: overrideCSS.value }] : [],
164
163
  }));
165
164
 
166
165
  // ─── Sidebar / search / keyboard ─────────────────────────────
@@ -189,12 +188,16 @@ function isActive(url: string) {
189
188
 
190
189
  const ICON_MAP: Record<string, string> = {
191
190
  book: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
192
- terminal: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>',
191
+ terminal:
192
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>',
193
193
  code: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
194
194
  file: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>',
195
- folder: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>',
196
- rocket: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>',
197
- settings: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
195
+ folder:
196
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>',
197
+ rocket:
198
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>',
199
+ settings:
200
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
198
201
  };
199
202
 
200
203
  function getIcon(iconKey?: string) {
@@ -214,156 +217,231 @@ function handleKeydown(e: KeyboardEvent) {
214
217
  }
215
218
 
216
219
  const showFloatingAI = computed(
217
- () =>
218
- props.config?.ai?.enabled &&
219
- props.config?.ai?.mode === "floating"
220
+ () => props.config?.ai?.enabled && props.config?.ai?.mode === "floating",
220
221
  );
221
222
  </script>
222
223
 
223
224
  <template>
224
225
  <div class="fd-layout-root">
225
- <div class="fd-layout" @keydown="handleKeydown">
226
- <header class="fd-header">
227
- <button class="fd-menu-btn" type="button" aria-label="Toggle sidebar" @click="toggleSidebar">
228
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
229
- <line x1="3" y1="6" x2="21" y2="6" />
230
- <line x1="3" y1="12" x2="21" y2="12" />
231
- <line x1="3" y1="18" x2="21" y2="18" />
232
- </svg>
233
- </button>
234
- <NuxtLink :to="resolvedTitleUrl" class="fd-header-title">{{ resolvedTitle }}</NuxtLink>
235
- <button class="fd-search-trigger-mobile" type="button" aria-label="Search" @click="openSearch">
236
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
237
- <circle cx="11" cy="11" r="8" />
238
- <line x1="21" y1="21" x2="16.65" y2="16.65" />
239
- </svg>
240
- </button>
241
- </header>
242
-
243
- <div v-if="sidebarOpen" class="fd-sidebar-overlay" aria-hidden="true" @click="closeSidebar" />
244
-
245
- <aside class="fd-sidebar" :class="{ 'fd-sidebar-open': sidebarOpen }">
246
- <div class="fd-sidebar-header">
247
- <NuxtLink :to="resolvedTitleUrl" class="fd-sidebar-title" @click="closeSidebar">
248
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
249
- <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
250
- <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
226
+ <div class="fd-layout" @keydown="handleKeydown">
227
+ <header class="fd-header">
228
+ <button
229
+ class="fd-menu-btn"
230
+ type="button"
231
+ aria-label="Toggle sidebar"
232
+ @click="toggleSidebar"
233
+ >
234
+ <svg
235
+ width="20"
236
+ height="20"
237
+ viewBox="0 0 24 24"
238
+ fill="none"
239
+ stroke="currentColor"
240
+ stroke-width="2"
241
+ >
242
+ <line x1="3" y1="6" x2="21" y2="6" />
243
+ <line x1="3" y1="12" x2="21" y2="12" />
244
+ <line x1="3" y1="18" x2="21" y2="18" />
251
245
  </svg>
252
- {{ resolvedTitle }}
253
- </NuxtLink>
254
- </div>
255
-
256
- <div class="fd-sidebar-search">
257
- <button type="button" class="fd-sidebar-search-btn" @click="openSearch">
258
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
246
+ </button>
247
+ <NuxtLink :to="resolvedTitleUrl" class="fd-header-title">{{ resolvedTitle }}</NuxtLink>
248
+ <button
249
+ class="fd-search-trigger-mobile"
250
+ type="button"
251
+ aria-label="Search"
252
+ @click="openSearch"
253
+ >
254
+ <svg
255
+ width="18"
256
+ height="18"
257
+ viewBox="0 0 24 24"
258
+ fill="none"
259
+ stroke="currentColor"
260
+ stroke-width="2"
261
+ >
259
262
  <circle cx="11" cy="11" r="8" />
260
263
  <line x1="21" y1="21" x2="16.65" y2="16.65" />
261
264
  </svg>
262
- <span>Search</span>
263
- <kbd>⌘</kbd><kbd>K</kbd>
264
265
  </button>
265
- </div>
266
+ </header>
267
+
268
+ <div v-if="sidebarOpen" class="fd-sidebar-overlay" aria-hidden="true" @click="closeSidebar" />
266
269
 
267
- <nav class="fd-sidebar-nav">
268
- <template v-if="tree?.children">
269
- <template v-for="(node, i) in tree.children" :key="node.name + (node.url ?? '')">
270
- <NuxtLink
271
- v-if="node.type === 'page'"
272
- :to="node.url!"
273
- class="fd-sidebar-link fd-sidebar-top-link"
274
- :class="{ 'fd-sidebar-link-active': isActive(node.url ?? ''), 'fd-sidebar-first-item': i === 0 }"
275
- @click="closeSidebar"
270
+ <aside class="fd-sidebar" :class="{ 'fd-sidebar-open': sidebarOpen }">
271
+ <div class="fd-sidebar-header">
272
+ <NuxtLink :to="resolvedTitleUrl" class="fd-sidebar-title" @click="closeSidebar">
273
+ <svg
274
+ width="18"
275
+ height="18"
276
+ viewBox="0 0 24 24"
277
+ fill="none"
278
+ stroke="currentColor"
279
+ stroke-width="2"
276
280
  >
277
- <span v-if="getIcon(node.icon)" class="fd-sidebar-icon" v-html="getIcon(node.icon)" />
278
- {{ node.name }}
279
- </NuxtLink>
280
- <details v-else-if="node.type === 'folder'" class="fd-sidebar-folder" :class="{ 'fd-sidebar-first-item': i === 0 }" open>
281
- <summary class="fd-sidebar-folder-trigger">
282
- <span class="fd-sidebar-folder-label">
283
- <span v-if="getIcon(node.icon)" class="fd-sidebar-icon" v-html="getIcon(node.icon)" />
284
- {{ node.name }}
285
- </span>
286
- <svg class="fd-sidebar-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
287
- <polyline points="6 9 12 15 18 9" />
288
- </svg>
289
- </summary>
290
- <div class="fd-sidebar-folder-content">
291
- <NuxtLink
292
- v-if="node.index"
293
- :to="node.index.url"
294
- class="fd-sidebar-link fd-sidebar-child-link"
295
- :class="{ 'fd-sidebar-link-active': isActive(node.index.url) }"
296
- @click="closeSidebar"
297
- >
298
- {{ node.index.name }}
299
- </NuxtLink>
300
- <template v-for="child in node.children" :key="child.name + ((child as any).url ?? '')">
281
+ <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
282
+ <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
283
+ </svg>
284
+ {{ resolvedTitle }}
285
+ </NuxtLink>
286
+ </div>
287
+
288
+ <div class="fd-sidebar-search">
289
+ <button type="button" class="fd-sidebar-search-btn" @click="openSearch">
290
+ <svg
291
+ width="15"
292
+ height="15"
293
+ viewBox="0 0 24 24"
294
+ fill="none"
295
+ stroke="currentColor"
296
+ stroke-width="2"
297
+ >
298
+ <circle cx="11" cy="11" r="8" />
299
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
300
+ </svg>
301
+ <span>Search</span>
302
+ <kbd>⌘</kbd><kbd>K</kbd>
303
+ </button>
304
+ </div>
305
+
306
+ <nav class="fd-sidebar-nav">
307
+ <template v-if="tree?.children">
308
+ <template v-for="(node, i) in tree.children" :key="node.name + (node.url ?? '')">
309
+ <NuxtLink
310
+ v-if="node.type === 'page'"
311
+ :to="node.url!"
312
+ class="fd-sidebar-link fd-sidebar-top-link"
313
+ :class="{
314
+ 'fd-sidebar-link-active': isActive(node.url ?? ''),
315
+ 'fd-sidebar-first-item': i === 0,
316
+ }"
317
+ @click="closeSidebar"
318
+ >
319
+ <span
320
+ v-if="getIcon(node.icon)"
321
+ class="fd-sidebar-icon"
322
+ v-html="getIcon(node.icon)"
323
+ />
324
+ {{ node.name }}
325
+ </NuxtLink>
326
+ <details
327
+ v-else-if="node.type === 'folder'"
328
+ class="fd-sidebar-folder"
329
+ :class="{ 'fd-sidebar-first-item': i === 0 }"
330
+ open
331
+ >
332
+ <summary class="fd-sidebar-folder-trigger">
333
+ <span class="fd-sidebar-folder-label">
334
+ <span
335
+ v-if="getIcon(node.icon)"
336
+ class="fd-sidebar-icon"
337
+ v-html="getIcon(node.icon)"
338
+ />
339
+ {{ node.name }}
340
+ </span>
341
+ <svg
342
+ class="fd-sidebar-chevron"
343
+ width="14"
344
+ height="14"
345
+ viewBox="0 0 24 24"
346
+ fill="none"
347
+ stroke="currentColor"
348
+ stroke-width="2"
349
+ >
350
+ <polyline points="6 9 12 15 18 9" />
351
+ </svg>
352
+ </summary>
353
+ <div class="fd-sidebar-folder-content">
301
354
  <NuxtLink
302
- v-if="child.type === 'page'"
303
- :to="(child as any).url"
355
+ v-if="node.index"
356
+ :to="node.index.url"
304
357
  class="fd-sidebar-link fd-sidebar-child-link"
305
- :class="{ 'fd-sidebar-link-active': isActive((child as any).url) }"
358
+ :class="{ 'fd-sidebar-link-active': isActive(node.index.url) }"
306
359
  @click="closeSidebar"
307
360
  >
308
- {{ child.name }}
361
+ {{ node.index.name }}
309
362
  </NuxtLink>
310
- <details v-else-if="child.type === 'folder'" class="fd-sidebar-folder fd-sidebar-nested-folder" open>
311
- <summary class="fd-sidebar-folder-trigger">
312
- <span class="fd-sidebar-folder-label">{{ child.name }}</span>
313
- <svg class="fd-sidebar-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
314
- <polyline points="6 9 12 15 18 9" />
315
- </svg>
316
- </summary>
317
- <div class="fd-sidebar-folder-content">
318
- <NuxtLink
319
- v-if="(child as any).index"
320
- :to="(child as any).index.url"
321
- class="fd-sidebar-link fd-sidebar-child-link"
322
- :class="{ 'fd-sidebar-link-active': isActive((child as any).index.url) }"
323
- @click="closeSidebar"
324
- >
325
- {{ (child as any).index.name }}
326
- </NuxtLink>
327
- <NuxtLink
328
- v-for="grandchild in (child as any).children"
329
- v-if="grandchild.type === 'page'"
330
- :key="grandchild.url"
331
- :to="grandchild.url"
332
- class="fd-sidebar-link fd-sidebar-child-link"
333
- :class="{ 'fd-sidebar-link-active': isActive(grandchild.url) }"
334
- @click="closeSidebar"
335
- >
336
- {{ grandchild.name }}
337
- </NuxtLink>
338
- </div>
339
- </details>
340
- </template>
341
- </div>
342
- </details>
363
+ <template
364
+ v-for="child in node.children"
365
+ :key="child.name + ((child as any).url ?? '')"
366
+ >
367
+ <NuxtLink
368
+ v-if="child.type === 'page'"
369
+ :to="(child as any).url"
370
+ class="fd-sidebar-link fd-sidebar-child-link"
371
+ :class="{ 'fd-sidebar-link-active': isActive((child as any).url) }"
372
+ @click="closeSidebar"
373
+ >
374
+ {{ child.name }}
375
+ </NuxtLink>
376
+ <details
377
+ v-else-if="child.type === 'folder'"
378
+ class="fd-sidebar-folder fd-sidebar-nested-folder"
379
+ open
380
+ >
381
+ <summary class="fd-sidebar-folder-trigger">
382
+ <span class="fd-sidebar-folder-label">{{ child.name }}</span>
383
+ <svg
384
+ class="fd-sidebar-chevron"
385
+ width="14"
386
+ height="14"
387
+ viewBox="0 0 24 24"
388
+ fill="none"
389
+ stroke="currentColor"
390
+ stroke-width="2"
391
+ >
392
+ <polyline points="6 9 12 15 18 9" />
393
+ </svg>
394
+ </summary>
395
+ <div class="fd-sidebar-folder-content">
396
+ <NuxtLink
397
+ v-if="(child as any).index"
398
+ :to="(child as any).index.url"
399
+ class="fd-sidebar-link fd-sidebar-child-link"
400
+ :class="{ 'fd-sidebar-link-active': isActive((child as any).index.url) }"
401
+ @click="closeSidebar"
402
+ >
403
+ {{ (child as any).index.name }}
404
+ </NuxtLink>
405
+ <NuxtLink
406
+ v-for="grandchild in (child as any).children"
407
+ v-if="grandchild.type === 'page'"
408
+ :key="grandchild.url"
409
+ :to="grandchild.url"
410
+ class="fd-sidebar-link fd-sidebar-child-link"
411
+ :class="{ 'fd-sidebar-link-active': isActive(grandchild.url) }"
412
+ @click="closeSidebar"
413
+ >
414
+ {{ grandchild.name }}
415
+ </NuxtLink>
416
+ </div>
417
+ </details>
418
+ </template>
419
+ </div>
420
+ </details>
421
+ </template>
343
422
  </template>
344
- </template>
345
- </nav>
423
+ </nav>
346
424
 
347
- <div v-if="showThemeToggle" class="fd-sidebar-footer">
348
- <ThemeToggle />
349
- </div>
350
- </aside>
425
+ <div v-if="showThemeToggle" class="fd-sidebar-footer">
426
+ <ThemeToggle />
427
+ </div>
428
+ </aside>
351
429
 
352
- <main class="fd-main">
353
- <slot />
354
- </main>
355
- </div>
430
+ <main class="fd-main">
431
+ <slot />
432
+ </main>
433
+ </div>
356
434
 
357
- <FloatingAIChat
358
- v-if="showFloatingAI"
359
- api="/api/docs"
360
- :suggested-questions="config?.ai?.suggestedQuestions ?? []"
361
- :ai-label="config?.ai?.aiLabel ?? 'AI'"
362
- :position="config?.ai?.position ?? 'bottom-right'"
363
- :floating-style="config?.ai?.floatingStyle ?? 'panel'"
364
- :trigger-component="triggerComponent"
365
- />
435
+ <FloatingAIChat
436
+ v-if="showFloatingAI"
437
+ api="/api/docs"
438
+ :suggested-questions="config?.ai?.suggestedQuestions ?? []"
439
+ :ai-label="config?.ai?.aiLabel ?? 'AI'"
440
+ :position="config?.ai?.position ?? 'bottom-right'"
441
+ :floating-style="config?.ai?.floatingStyle ?? 'panel'"
442
+ :trigger-component="triggerComponent"
443
+ />
366
444
 
367
- <SearchDialog v-if="searchOpen" @close="closeSearch" />
445
+ <SearchDialog v-if="searchOpen" @close="closeSearch" />
368
446
  </div>
369
447
  </template>