@farming-labs/nuxt-theme 0.0.2-beta.23 → 0.0.2-beta.26

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/nuxt-theme",
3
- "version": "0.0.2-beta.23",
3
+ "version": "0.0.2-beta.26",
4
4
  "description": "Nuxt/Vue UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
5
5
  "keywords": [
6
6
  "docs",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "sugar-high": "^0.9.5",
56
- "@farming-labs/docs": "0.0.2-beta.23"
56
+ "@farming-labs/docs": "0.0.2-beta.26"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "nuxt": ">=3.0.0",
@@ -304,122 +304,124 @@ const showFloatingAI = computed(
304
304
  </div>
305
305
 
306
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">
354
- <NuxtLink
355
- v-if="node.index"
356
- :to="node.index.url"
357
- class="fd-sidebar-link fd-sidebar-child-link"
358
- :class="{ 'fd-sidebar-link-active': isActive(node.index.url) }"
359
- @click="closeSidebar"
360
- >
361
- {{ node.index.name }}
362
- </NuxtLink>
363
- <template
364
- v-for="child in node.children"
365
- :key="child.name + ((child as any).url ?? '')"
366
- >
307
+ <slot name="sidebar" :tree="tree" :is-active="isActive">
308
+ <template v-if="tree?.children">
309
+ <template v-for="(node, i) in tree.children" :key="node.name + (node.url ?? '')">
310
+ <NuxtLink
311
+ v-if="node.type === 'page'"
312
+ :to="node.url!"
313
+ class="fd-sidebar-link fd-sidebar-top-link"
314
+ :class="{
315
+ 'fd-sidebar-link-active': isActive(node.url ?? ''),
316
+ 'fd-sidebar-first-item': i === 0,
317
+ }"
318
+ @click="closeSidebar"
319
+ >
320
+ <span
321
+ v-if="getIcon(node.icon)"
322
+ class="fd-sidebar-icon"
323
+ v-html="getIcon(node.icon)"
324
+ />
325
+ {{ node.name }}
326
+ </NuxtLink>
327
+ <details
328
+ v-else-if="node.type === 'folder'"
329
+ class="fd-sidebar-folder"
330
+ :class="{ 'fd-sidebar-first-item': i === 0 }"
331
+ open
332
+ >
333
+ <summary class="fd-sidebar-folder-trigger">
334
+ <span class="fd-sidebar-folder-label">
335
+ <span
336
+ v-if="getIcon(node.icon)"
337
+ class="fd-sidebar-icon"
338
+ v-html="getIcon(node.icon)"
339
+ />
340
+ {{ node.name }}
341
+ </span>
342
+ <svg
343
+ class="fd-sidebar-chevron"
344
+ width="14"
345
+ height="14"
346
+ viewBox="0 0 24 24"
347
+ fill="none"
348
+ stroke="currentColor"
349
+ stroke-width="2"
350
+ >
351
+ <polyline points="6 9 12 15 18 9" />
352
+ </svg>
353
+ </summary>
354
+ <div class="fd-sidebar-folder-content">
367
355
  <NuxtLink
368
- v-if="child.type === 'page'"
369
- :to="(child as any).url"
356
+ v-if="node.index"
357
+ :to="node.index.url"
370
358
  class="fd-sidebar-link fd-sidebar-child-link"
371
- :class="{ 'fd-sidebar-link-active': isActive((child as any).url) }"
359
+ :class="{ 'fd-sidebar-link-active': isActive(node.index.url) }"
372
360
  @click="closeSidebar"
373
361
  >
374
- {{ child.name }}
362
+ {{ node.index.name }}
375
363
  </NuxtLink>
376
- <details
377
- v-else-if="child.type === 'folder'"
378
- class="fd-sidebar-folder fd-sidebar-nested-folder"
379
- open
364
+ <template
365
+ v-for="child in node.children"
366
+ :key="child.name + ((child as any).url ?? '')"
380
367
  >
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>
368
+ <NuxtLink
369
+ v-if="child.type === 'page'"
370
+ :to="(child as any).url"
371
+ class="fd-sidebar-link fd-sidebar-child-link"
372
+ :class="{ 'fd-sidebar-link-active': isActive((child as any).url) }"
373
+ @click="closeSidebar"
374
+ >
375
+ {{ child.name }}
376
+ </NuxtLink>
377
+ <details
378
+ v-else-if="child.type === 'folder'"
379
+ class="fd-sidebar-folder fd-sidebar-nested-folder"
380
+ open
381
+ >
382
+ <summary class="fd-sidebar-folder-trigger">
383
+ <span class="fd-sidebar-folder-label">{{ child.name }}</span>
384
+ <svg
385
+ class="fd-sidebar-chevron"
386
+ width="14"
387
+ height="14"
388
+ viewBox="0 0 24 24"
389
+ fill="none"
390
+ stroke="currentColor"
391
+ stroke-width="2"
392
+ >
393
+ <polyline points="6 9 12 15 18 9" />
394
+ </svg>
395
+ </summary>
396
+ <div class="fd-sidebar-folder-content">
397
+ <NuxtLink
398
+ v-if="(child as any).index"
399
+ :to="(child as any).index.url"
400
+ class="fd-sidebar-link fd-sidebar-child-link"
401
+ :class="{ 'fd-sidebar-link-active': isActive((child as any).index.url) }"
402
+ @click="closeSidebar"
403
+ >
404
+ {{ (child as any).index.name }}
405
+ </NuxtLink>
406
+ <NuxtLink
407
+ v-for="grandchild in (child as any).children"
408
+ v-if="grandchild.type === 'page'"
409
+ :key="grandchild.url"
410
+ :to="grandchild.url"
411
+ class="fd-sidebar-link fd-sidebar-child-link"
412
+ :class="{ 'fd-sidebar-link-active': isActive(grandchild.url) }"
413
+ @click="closeSidebar"
414
+ >
415
+ {{ grandchild.name }}
416
+ </NuxtLink>
417
+ </div>
418
+ </details>
419
+ </template>
420
+ </div>
421
+ </details>
422
+ </template>
421
423
  </template>
422
- </template>
424
+ </slot>
423
425
  </nav>
424
426
 
425
427
  <div v-if="showThemeToggle" class="fd-sidebar-footer">
@@ -213,12 +213,15 @@ function handleFmKeyDown(e: KeyboardEvent) {
213
213
  </div>
214
214
  <div class="fd-ai-fm-msg-content">
215
215
  <template v-if="msg.content">
216
- <div v-html="renderMarkdown(msg.content)" />
216
+ <div :class="isStreaming && i === messages.length - 1 && msg.role === 'assistant' ? 'fd-ai-streaming' : ''" v-html="renderMarkdown(msg.content)" />
217
217
  </template>
218
- <div v-else class="fd-ai-fm-thinking">
219
- <span class="fd-ai-fm-thinking-dot" />
220
- <span class="fd-ai-fm-thinking-dot" />
221
- <span class="fd-ai-fm-thinking-dot" />
218
+ <div v-else class="fd-ai-loader">
219
+ <span class="fd-ai-loader-shimmer-text">Thinking</span>
220
+ <span class="fd-ai-loader-typing-dots">
221
+ <span class="fd-ai-loader-typing-dot" />
222
+ <span class="fd-ai-loader-typing-dot" />
223
+ <span class="fd-ai-loader-typing-dot" />
224
+ </span>
222
225
  </div>
223
226
  </div>
224
227
  </div>
@@ -286,10 +289,10 @@ function handleFmKeyDown(e: KeyboardEvent) {
286
289
  aria-label="Stop"
287
290
  @click="isStreaming = false"
288
291
  >
289
- <span class="fd-ai-loading-dots">
290
- <span class="fd-ai-loading-dot" />
291
- <span class="fd-ai-loading-dot" />
292
- <span class="fd-ai-loading-dot" />
292
+ <span class="fd-ai-loader-typing-dots" style="margin-left:0">
293
+ <span class="fd-ai-loader-typing-dot" />
294
+ <span class="fd-ai-loader-typing-dot" />
295
+ <span class="fd-ai-loader-typing-dot" />
293
296
  </span>
294
297
  </button>
295
298
  <button
@@ -427,16 +430,16 @@ function handleFmKeyDown(e: KeyboardEvent) {
427
430
  <div v-if="msg.role === 'user'" class="fd-ai-bubble-user">{{ msg.content }}</div>
428
431
  <div v-else class="fd-ai-bubble-ai">
429
432
  <template v-if="msg.content">
430
- <div v-html="renderMarkdown(msg.content)" />
433
+ <div :class="isStreaming && i === messages.length - 1 ? 'fd-ai-streaming' : ''" v-html="renderMarkdown(msg.content)" />
431
434
  </template>
432
- <span v-else class="fd-ai-loading">
433
- <span class="fd-ai-loading-text">{{ label }} is thinking</span>
434
- <span class="fd-ai-loading-dots">
435
- <span class="fd-ai-loading-dot" />
436
- <span class="fd-ai-loading-dot" />
437
- <span class="fd-ai-loading-dot" />
435
+ <div v-else class="fd-ai-loader">
436
+ <span class="fd-ai-loader-shimmer-text">Thinking</span>
437
+ <span class="fd-ai-loader-typing-dots">
438
+ <span class="fd-ai-loader-typing-dot" />
439
+ <span class="fd-ai-loader-typing-dot" />
440
+ <span class="fd-ai-loader-typing-dot" />
438
441
  </span>
439
- </span>
442
+ </div>
440
443
  </div>
441
444
  </div>
442
445
  </template>
package/styles/docs.css CHANGED
@@ -1211,8 +1211,9 @@ html.dark pre.shiki {
1211
1211
 
1212
1212
  .fd-edit-on-github {
1213
1213
  display: flex;
1214
+ flex-wrap: wrap;
1214
1215
  align-items: center;
1215
- gap: 16px;
1216
+ gap: 10px 16px;
1216
1217
  margin-top: 32px;
1217
1218
  padding-top: 16px;
1218
1219
  border-top: 1px solid var(--color-fd-border);
@@ -1237,6 +1238,12 @@ html.dark pre.shiki {
1237
1238
  font-size: 12px;
1238
1239
  }
1239
1240
 
1241
+ @media (max-width: 640px) {
1242
+ .fd-last-modified {
1243
+ width: 100%;
1244
+ }
1245
+ }
1246
+
1240
1247
  .fd-llms-txt-links {
1241
1248
  display: inline-flex;
1242
1249
  align-items: center;
@@ -1329,19 +1336,6 @@ html.dark pre.shiki {
1329
1336
  * AI Chat & Search Dialog — base styles (fd-ai-*)
1330
1337
  * ═══════════════════════════════════════════════════════════════════════ */
1331
1338
 
1332
- @keyframes fd-ai-dot {
1333
- 0%,
1334
- 80%,
1335
- 100% {
1336
- transform: scale(0);
1337
- opacity: 0.5;
1338
- }
1339
- 40% {
1340
- transform: scale(1);
1341
- opacity: 1;
1342
- }
1343
- }
1344
-
1345
1339
  @keyframes fd-ai-fade-in {
1346
1340
  from {
1347
1341
  opacity: 0;
@@ -1693,6 +1687,12 @@ html.dark pre.shiki {
1693
1687
  line-height: 1.6;
1694
1688
  max-width: 95%;
1695
1689
  word-break: break-word;
1690
+ animation: fd-ai-msg-in 300ms ease-out;
1691
+ }
1692
+
1693
+ @keyframes fd-ai-msg-in {
1694
+ from { opacity: 0; transform: translateY(6px); }
1695
+ to { opacity: 1; transform: translateY(0); }
1696
1696
  }
1697
1697
 
1698
1698
  .fd-ai-chat-footer {
@@ -1745,36 +1745,70 @@ html.dark pre.shiki {
1745
1745
  color: var(--color-fd-primary-foreground);
1746
1746
  }
1747
1747
 
1748
- .fd-ai-loading {
1748
+ .fd-ai-loader {
1749
1749
  display: inline-flex;
1750
- gap: 6px;
1751
1750
  align-items: center;
1751
+ gap: 6px;
1752
+ animation: fd-ai-loader-in 300ms ease-out;
1752
1753
  }
1753
1754
 
1754
- .fd-ai-loading-text {
1755
- font-size: 12px;
1756
- color: var(--color-fd-muted-foreground);
1755
+ @keyframes fd-ai-loader-in {
1756
+ from { opacity: 0; transform: translateY(4px); }
1757
+ to { opacity: 1; transform: translateY(0); }
1757
1758
  }
1758
1759
 
1759
- .fd-ai-loading-dots {
1760
+ .fd-ai-loader-shimmer-text {
1761
+ font-size: 13px;
1762
+ font-weight: 500;
1763
+ background: linear-gradient(to right, var(--color-fd-muted-foreground, #888) 40%, var(--color-fd-foreground, #fff) 60%, var(--color-fd-muted-foreground, #888) 80%);
1764
+ background-size: 200% auto;
1765
+ background-clip: text;
1766
+ -webkit-background-clip: text;
1767
+ color: transparent;
1768
+ animation: fd-ai-shimmer-text 3s linear infinite;
1769
+ }
1770
+
1771
+ @keyframes fd-ai-shimmer-text {
1772
+ 0% { background-position: 150% center; }
1773
+ 100% { background-position: -150% center; }
1774
+ }
1775
+
1776
+ .fd-ai-loader-typing-dots {
1760
1777
  display: inline-flex;
1761
- gap: 3px;
1762
1778
  align-items: center;
1779
+ gap: 2px;
1763
1780
  }
1764
1781
 
1765
- .fd-ai-loading-dot {
1766
- width: 5px;
1767
- height: 5px;
1782
+ .fd-ai-loader-typing-dot {
1783
+ width: 4px;
1784
+ height: 4px;
1768
1785
  border-radius: 50%;
1769
- background: var(--color-fd-muted-foreground);
1770
- animation: fd-ai-dot 1.4s infinite ease-in-out both;
1786
+ background: var(--color-fd-primary, #6366f1);
1787
+ animation: fd-ai-typing 1s infinite;
1788
+ }
1789
+
1790
+ .fd-ai-loader-typing-dot:nth-child(2) { animation-delay: 250ms; }
1791
+ .fd-ai-loader-typing-dot:nth-child(3) { animation-delay: 500ms; }
1792
+
1793
+ @keyframes fd-ai-typing {
1794
+ 0%, 100% { transform: translateY(0); opacity: 0.5; }
1795
+ 50% { transform: translateY(-2px); opacity: 1; }
1771
1796
  }
1772
1797
 
1773
- .fd-ai-loading-dot:nth-child(2) {
1774
- animation-delay: 0.16s;
1798
+ .fd-ai-streaming::after {
1799
+ content: "";
1800
+ display: inline-block;
1801
+ width: 2px;
1802
+ height: 1em;
1803
+ background: var(--color-fd-primary, #6366f1);
1804
+ margin-left: 2px;
1805
+ vertical-align: text-bottom;
1806
+ animation: fd-ai-cursor-blink 0.8s step-end infinite;
1775
1807
  }
1776
- .fd-ai-loading-dot:nth-child(3) {
1777
- animation-delay: 0.32s;
1808
+
1809
+ @keyframes fd-ai-cursor-blink {
1810
+ 0%, 100% { opacity: 1; }
1811
+ 50% { opacity: 0; }
1778
1812
  }
1779
1813
 
1780
1814
  /* ─── Markdown in AI responses ───────────────────────────────── */
@@ -2161,41 +2195,7 @@ html.dark pre.shiki {
2161
2195
  font-size: 12px;
2162
2196
  }
2163
2197
 
2164
- /* ─── Thinking dots ──────────────────────────────────────────── */
2165
-
2166
- .fd-ai-fm-thinking {
2167
- display: flex;
2168
- gap: 4px;
2169
- align-items: center;
2170
- }
2171
-
2172
- .fd-ai-fm-thinking-dot {
2173
- width: 6px;
2174
- height: 6px;
2175
- border-radius: 9999px;
2176
- background: var(--color-fd-primary, #6366f1);
2177
- animation: fd-ai-fm-bounce 1s infinite ease-in-out;
2178
- }
2179
-
2180
- .fd-ai-fm-thinking-dot:nth-child(2) {
2181
- animation-delay: 150ms;
2182
- }
2183
- .fd-ai-fm-thinking-dot:nth-child(3) {
2184
- animation-delay: 300ms;
2185
- }
2186
-
2187
- @keyframes fd-ai-fm-bounce {
2188
- 0%,
2189
- 80%,
2190
- 100% {
2191
- transform: scale(0.6);
2192
- opacity: 0.4;
2193
- }
2194
- 40% {
2195
- transform: scale(1);
2196
- opacity: 1;
2197
- }
2198
- }
2198
+ /* Full-modal now uses the shared .fd-ai-loader indicator */
2199
2199
 
2200
2200
  /* ─── Bottom input bar ───────────────────────────────────────── */
2201
2201
 
@@ -477,13 +477,13 @@
477
477
  letter-spacing: 0.06em;
478
478
  }
479
479
 
480
- .fd-ai-loading-text {
480
+ .fd-ai-loader-shimmer-text {
481
481
  text-transform: uppercase;
482
482
  letter-spacing: 0.04em;
483
483
  font-size: 11px;
484
484
  }
485
485
 
486
- .fd-ai-loading-dot {
486
+ .fd-ai-loader-typing-dot {
487
487
  border-radius: 0;
488
488
  width: 4px;
489
489
  height: 4px;
@@ -561,11 +561,7 @@
561
561
  font-size: 11px;
562
562
  }
563
563
 
564
- .fd-ai-fm-thinking-dot {
565
- border-radius: 0;
566
- width: 5px;
567
- height: 5px;
568
- }
564
+ /* Full-modal now uses .fd-ai-loader-typing-dot (see above) */
569
565
 
570
566
  /* ─── Code blocks in AI chat (pixel-border) ──────────────────────── */
571
567