@farming-labs/theme 0.0.2-beta.23 → 0.0.2-beta.24

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.
@@ -1,10 +1,12 @@
1
1
  //#region src/ai-search-dialog.d.ts
2
+ type LoaderVariant = "shimmer-dots" | "circular" | "dots" | "typing" | "wave" | "bars" | "pulse" | "pulse-dot" | "terminal" | "text-blink" | "text-shimmer" | "loading-dots";
2
3
  declare function DocsSearchDialog({
3
4
  open,
4
5
  onOpenChange,
5
6
  api,
6
7
  suggestedQuestions,
7
8
  aiLabel,
9
+ loaderVariant,
8
10
  loadingComponentHtml
9
11
  }: {
10
12
  open: boolean;
@@ -12,6 +14,7 @@ declare function DocsSearchDialog({
12
14
  api?: string;
13
15
  suggestedQuestions?: string[];
14
16
  aiLabel?: string;
17
+ loaderVariant?: LoaderVariant;
15
18
  loadingComponentHtml?: string;
16
19
  }): any;
17
20
  type FloatingPosition = "bottom-right" | "bottom-left" | "bottom-center";
@@ -23,6 +26,7 @@ declare function FloatingAIChat({
23
26
  triggerComponentHtml,
24
27
  suggestedQuestions,
25
28
  aiLabel,
29
+ loaderVariant,
26
30
  loadingComponentHtml
27
31
  }: {
28
32
  api?: string;
@@ -31,6 +35,7 @@ declare function FloatingAIChat({
31
35
  triggerComponentHtml?: string;
32
36
  suggestedQuestions?: string[];
33
37
  aiLabel?: string;
38
+ loaderVariant?: LoaderVariant;
34
39
  loadingComponentHtml?: string;
35
40
  }): any;
36
41
  declare function AIModalDialog({
@@ -39,6 +44,7 @@ declare function AIModalDialog({
39
44
  api,
40
45
  suggestedQuestions,
41
46
  aiLabel,
47
+ loaderVariant,
42
48
  loadingComponentHtml
43
49
  }: {
44
50
  open: boolean;
@@ -46,6 +52,7 @@ declare function AIModalDialog({
46
52
  api?: string;
47
53
  suggestedQuestions?: string[];
48
54
  aiLabel?: string;
55
+ loaderVariant?: LoaderVariant;
49
56
  loadingComponentHtml?: string;
50
57
  }): any;
51
58
  //#endregion
@@ -143,33 +143,145 @@ function XIcon() {
143
143
  children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })]
144
144
  });
145
145
  }
146
- function DefaultLoadingIndicator({ label }) {
147
- return /* @__PURE__ */ jsxs("span", {
148
- className: "fd-ai-loading",
149
- children: [/* @__PURE__ */ jsxs("span", {
150
- className: "fd-ai-loading-text",
151
- children: [label, " is thinking"]
152
- }), /* @__PURE__ */ jsxs("span", {
153
- className: "fd-ai-loading-dots",
154
- children: [
155
- /* @__PURE__ */ jsx("span", { className: "fd-ai-loading-dot" }),
156
- /* @__PURE__ */ jsx("span", { className: "fd-ai-loading-dot" }),
157
- /* @__PURE__ */ jsx("span", { className: "fd-ai-loading-dot" })
158
- ]
159
- })]
160
- });
146
+ function LoaderIndicator({ variant = "shimmer-dots" }) {
147
+ const text = "Thinking";
148
+ switch (variant) {
149
+ case "circular": return /* @__PURE__ */ jsxs("div", {
150
+ className: "fd-ai-loader",
151
+ children: [/* @__PURE__ */ jsx("div", { className: "fd-ai-loader-circular" }), /* @__PURE__ */ jsx("span", {
152
+ className: "sr-only",
153
+ children: "Loading"
154
+ })]
155
+ });
156
+ case "dots": return /* @__PURE__ */ jsx("div", {
157
+ className: "fd-ai-loader",
158
+ children: /* @__PURE__ */ jsxs("span", {
159
+ className: "fd-ai-loader-dots",
160
+ children: [
161
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-bounce-dot" }),
162
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-bounce-dot" }),
163
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-bounce-dot" })
164
+ ]
165
+ })
166
+ });
167
+ case "typing": return /* @__PURE__ */ jsx("div", {
168
+ className: "fd-ai-loader",
169
+ children: /* @__PURE__ */ jsxs("span", {
170
+ className: "fd-ai-loader-typing-dots",
171
+ children: [
172
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" }),
173
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" }),
174
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" })
175
+ ]
176
+ })
177
+ });
178
+ case "wave": return /* @__PURE__ */ jsx("div", {
179
+ className: "fd-ai-loader",
180
+ children: /* @__PURE__ */ jsxs("span", {
181
+ className: "fd-ai-loader-wave",
182
+ children: [
183
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-wave-bar" }),
184
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-wave-bar" }),
185
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-wave-bar" }),
186
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-wave-bar" }),
187
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-wave-bar" })
188
+ ]
189
+ })
190
+ });
191
+ case "bars": return /* @__PURE__ */ jsx("div", {
192
+ className: "fd-ai-loader",
193
+ children: /* @__PURE__ */ jsxs("span", {
194
+ className: "fd-ai-loader-bars",
195
+ children: [
196
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-bar" }),
197
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-bar" }),
198
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-bar" })
199
+ ]
200
+ })
201
+ });
202
+ case "pulse": return /* @__PURE__ */ jsx("div", {
203
+ className: "fd-ai-loader",
204
+ children: /* @__PURE__ */ jsx("div", { className: "fd-ai-loader-pulse" })
205
+ });
206
+ case "pulse-dot": return /* @__PURE__ */ jsx("div", {
207
+ className: "fd-ai-loader",
208
+ children: /* @__PURE__ */ jsx("div", { className: "fd-ai-loader-pulse-dot" })
209
+ });
210
+ case "terminal": return /* @__PURE__ */ jsx("div", {
211
+ className: "fd-ai-loader",
212
+ children: /* @__PURE__ */ jsxs("span", {
213
+ className: "fd-ai-loader-terminal",
214
+ children: [/* @__PURE__ */ jsx("span", {
215
+ className: "fd-ai-loader-terminal-prompt",
216
+ children: ">"
217
+ }), /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-terminal-cursor" })]
218
+ })
219
+ });
220
+ case "text-blink": return /* @__PURE__ */ jsx("div", {
221
+ className: "fd-ai-loader",
222
+ children: /* @__PURE__ */ jsx("span", {
223
+ className: "fd-ai-loader-text-blink",
224
+ children: text
225
+ })
226
+ });
227
+ case "text-shimmer": return /* @__PURE__ */ jsx("div", {
228
+ className: "fd-ai-loader",
229
+ children: /* @__PURE__ */ jsx("span", {
230
+ className: "fd-ai-loader-shimmer-text",
231
+ children: text
232
+ })
233
+ });
234
+ case "loading-dots": return /* @__PURE__ */ jsxs("div", {
235
+ className: "fd-ai-loader",
236
+ children: [/* @__PURE__ */ jsx("span", {
237
+ className: "fd-ai-loader-text",
238
+ children: text
239
+ }), /* @__PURE__ */ jsxs("span", {
240
+ className: "fd-ai-loader-text-dots",
241
+ children: [
242
+ /* @__PURE__ */ jsx("span", {
243
+ className: "fd-ai-loader-text-dot",
244
+ children: "."
245
+ }),
246
+ /* @__PURE__ */ jsx("span", {
247
+ className: "fd-ai-loader-text-dot",
248
+ children: "."
249
+ }),
250
+ /* @__PURE__ */ jsx("span", {
251
+ className: "fd-ai-loader-text-dot",
252
+ children: "."
253
+ })
254
+ ]
255
+ })]
256
+ });
257
+ default: return /* @__PURE__ */ jsxs("div", {
258
+ className: "fd-ai-loader",
259
+ children: [/* @__PURE__ */ jsx("span", {
260
+ className: "fd-ai-loader-shimmer-text",
261
+ children: text
262
+ }), /* @__PURE__ */ jsxs("span", {
263
+ className: "fd-ai-loader-typing-dots",
264
+ children: [
265
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" }),
266
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" }),
267
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" })
268
+ ]
269
+ })]
270
+ });
271
+ }
161
272
  }
162
- function LoadingDots() {
273
+ function InlineLoaderDots() {
163
274
  return /* @__PURE__ */ jsxs("span", {
164
- className: "fd-ai-loading-dots",
275
+ className: "fd-ai-loader-typing-dots",
276
+ style: { marginLeft: 0 },
165
277
  children: [
166
- /* @__PURE__ */ jsx("span", { className: "fd-ai-loading-dot" }),
167
- /* @__PURE__ */ jsx("span", { className: "fd-ai-loading-dot" }),
168
- /* @__PURE__ */ jsx("span", { className: "fd-ai-loading-dot" })
278
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" }),
279
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" }),
280
+ /* @__PURE__ */ jsx("span", { className: "fd-ai-loader-typing-dot" })
169
281
  ]
170
282
  });
171
283
  }
172
- function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loadingComponentHtml }) {
284
+ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
173
285
  const label = aiLabel || "AI";
174
286
  const aiInputRef = useRef(null);
175
287
  const messagesEndRef = useRef(null);
@@ -311,7 +423,13 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
311
423
  children: msg.content
312
424
  }) : /* @__PURE__ */ jsx("div", {
313
425
  className: "fd-ai-bubble-ai",
314
- children: msg.content ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: renderMarkdown(msg.content) } }) : loadingComponentHtml ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: loadingComponentHtml } }) : /* @__PURE__ */ jsx(DefaultLoadingIndicator, { label })
426
+ children: msg.content ? /* @__PURE__ */ jsx("div", {
427
+ className: isStreaming && i === messages.length - 1 ? "fd-ai-streaming" : void 0,
428
+ dangerouslySetInnerHTML: { __html: renderMarkdown(msg.content) }
429
+ }) : loadingComponentHtml ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: loadingComponentHtml } }) : /* @__PURE__ */ jsx(LoaderIndicator, {
430
+ variant: loaderVariant,
431
+ label
432
+ })
315
433
  })]
316
434
  }, i)), /* @__PURE__ */ jsx("div", { ref: messagesEndRef })]
317
435
  }), /* @__PURE__ */ jsxs("div", {
@@ -353,7 +471,7 @@ function AIChat({ api, messages, setMessages, aiInput, setAiInput, isStreaming,
353
471
  })]
354
472
  });
355
473
  }
356
- function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loadingComponentHtml }) {
474
+ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
357
475
  const [tab, setTab] = useState("search");
358
476
  const [searchQuery, setSearchQuery] = useState("");
359
477
  const [searchResults, setSearchResults] = useState([]);
@@ -496,7 +614,7 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
496
614
  onKeyDown: handleSearchKeyDown,
497
615
  className: "fd-ai-input"
498
616
  }),
499
- isSearching && /* @__PURE__ */ jsx(LoadingDots, {})
617
+ isSearching && /* @__PURE__ */ jsx(InlineLoaderDots, {})
500
618
  ]
501
619
  }), /* @__PURE__ */ jsx("div", {
502
620
  className: "fd-ai-results",
@@ -528,6 +646,7 @@ function DocsSearchDialog({ open, onOpenChange, api = "/api/docs", suggestedQues
528
646
  setIsStreaming,
529
647
  suggestedQuestions,
530
648
  aiLabel,
649
+ loaderVariant,
531
650
  loadingComponentHtml
532
651
  })
533
652
  ]
@@ -602,7 +721,7 @@ function getContainerStyles(style, position) {
602
721
  function getAnimation(style) {
603
722
  return style === "modal" ? "fd-ai-float-center-in 200ms ease-out" : "fd-ai-float-in 200ms ease-out";
604
723
  }
605
- function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loadingComponentHtml }) {
724
+ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
606
725
  const [mounted, setMounted] = useState(false);
607
726
  const [isOpen, setIsOpen] = useState(false);
608
727
  const [messages, setMessages] = useState([]);
@@ -640,6 +759,7 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
640
759
  setIsStreaming,
641
760
  suggestedQuestions,
642
761
  aiLabel,
762
+ loaderVariant,
643
763
  loadingComponentHtml,
644
764
  triggerComponentHtml,
645
765
  position
@@ -688,6 +808,7 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
688
808
  setIsStreaming,
689
809
  suggestedQuestions,
690
810
  aiLabel,
811
+ loaderVariant,
691
812
  loadingComponentHtml
692
813
  })]
693
814
  }),
@@ -705,7 +826,7 @@ function FloatingAIChat({ api = "/api/docs", position = "bottom-right", floating
705
826
  }))
706
827
  ] }), document.body);
707
828
  }
708
- function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loadingComponentHtml, triggerComponentHtml, position }) {
829
+ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInput, setAiInput, isStreaming, setIsStreaming, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml, triggerComponentHtml, position }) {
709
830
  const label = aiLabel || "AI";
710
831
  const inputRef = useRef(null);
711
832
  const listRef = useRef(null);
@@ -831,13 +952,12 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
831
952
  children: msg.role === "user" ? "you" : label
832
953
  }), /* @__PURE__ */ jsx("div", {
833
954
  className: "fd-ai-fm-msg-content",
834
- children: msg.content ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: renderMarkdown(msg.content) } }) : loadingComponentHtml ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: loadingComponentHtml } }) : /* @__PURE__ */ jsxs("div", {
835
- className: "fd-ai-fm-thinking",
836
- children: [
837
- /* @__PURE__ */ jsx("span", { className: "fd-ai-fm-thinking-dot" }),
838
- /* @__PURE__ */ jsx("span", { className: "fd-ai-fm-thinking-dot" }),
839
- /* @__PURE__ */ jsx("span", { className: "fd-ai-fm-thinking-dot" })
840
- ]
955
+ children: msg.content ? /* @__PURE__ */ jsx("div", {
956
+ className: isStreaming && i === messages.length - 1 ? "fd-ai-streaming" : void 0,
957
+ dangerouslySetInnerHTML: { __html: renderMarkdown(msg.content) }
958
+ }) : loadingComponentHtml ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: loadingComponentHtml } }) : /* @__PURE__ */ jsx(LoaderIndicator, {
959
+ variant: loaderVariant,
960
+ label
841
961
  })
842
962
  })]
843
963
  }, i))
@@ -871,7 +991,7 @@ function FullModalAIChat({ api, isOpen, setIsOpen, messages, setMessages, aiInpu
871
991
  }), isStreaming ? /* @__PURE__ */ jsx("button", {
872
992
  className: "fd-ai-fm-send-btn",
873
993
  onClick: () => setIsStreaming(false),
874
- children: /* @__PURE__ */ jsx(LoadingDots, {})
994
+ children: /* @__PURE__ */ jsx(InlineLoaderDots, {})
875
995
  }) : /* @__PURE__ */ jsx("button", {
876
996
  className: "fd-ai-fm-send-btn",
877
997
  "data-active": canSend,
@@ -932,7 +1052,7 @@ function TrashIcon() {
932
1052
  ]
933
1053
  });
934
1054
  }
935
- function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loadingComponentHtml }) {
1055
+ function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
936
1056
  const [messages, setMessages] = useState([]);
937
1057
  const [aiInput, setAiInput] = useState("");
938
1058
  const [isStreaming, setIsStreaming] = useState(false);
@@ -999,6 +1119,7 @@ function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestio
999
1119
  setIsStreaming,
1000
1120
  suggestedQuestions,
1001
1121
  aiLabel,
1122
+ loaderVariant,
1002
1123
  loadingComponentHtml
1003
1124
  }),
1004
1125
  /* @__PURE__ */ jsx("div", {
@@ -8,6 +8,7 @@ interface DocsAIFeaturesProps {
8
8
  triggerComponentHtml?: string;
9
9
  suggestedQuestions?: string[];
10
10
  aiLabel?: string;
11
+ loaderVariant?: string;
11
12
  loadingComponentHtml?: string;
12
13
  }
13
14
  declare function DocsAIFeatures({
@@ -17,6 +18,7 @@ declare function DocsAIFeatures({
17
18
  triggerComponentHtml,
18
19
  suggestedQuestions,
19
20
  aiLabel,
21
+ loaderVariant,
20
22
  loadingComponentHtml
21
23
  }: DocsAIFeaturesProps): react_jsx_runtime0.JSX.Element;
22
24
  //#endregion
@@ -19,15 +19,17 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
19
19
  * This component is rendered inside the docs layout so the user's root layout
20
20
  * never needs to be modified — AI features work purely from `docs.config.tsx`.
21
21
  */
22
- function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loadingComponentHtml }) {
22
+ function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
23
23
  if (mode === "search") return /* @__PURE__ */ jsx(SearchModeAI, {
24
24
  suggestedQuestions,
25
25
  aiLabel,
26
+ loaderVariant,
26
27
  loadingComponentHtml
27
28
  });
28
29
  if (mode === "sidebar-icon") return /* @__PURE__ */ jsx(SidebarIconModeAI, {
29
30
  suggestedQuestions,
30
31
  aiLabel,
32
+ loaderVariant,
31
33
  loadingComponentHtml
32
34
  });
33
35
  return /* @__PURE__ */ jsx(FloatingAIChat, {
@@ -37,15 +39,11 @@ function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "pane
37
39
  triggerComponentHtml,
38
40
  suggestedQuestions,
39
41
  aiLabel,
42
+ loaderVariant,
40
43
  loadingComponentHtml
41
44
  });
42
45
  }
43
- /**
44
- * Search mode: intercepts Cmd+K / Ctrl+K globally and opens the
45
- * custom search dialog (with Search + Ask AI tabs) instead of
46
- * fumadocs' built-in search dialog.
47
- */
48
- function SearchModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }) {
46
+ function SearchModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
49
47
  const [open, setOpen] = useState(false);
50
48
  useEffect(() => {
51
49
  function handler(e) {
@@ -80,15 +78,11 @@ function SearchModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }) {
80
78
  api: "/api/docs",
81
79
  suggestedQuestions,
82
80
  aiLabel,
81
+ loaderVariant,
83
82
  loadingComponentHtml
84
83
  });
85
84
  }
86
- /**
87
- * Sidebar-icon mode: injects a sparkle icon button next to the search bar
88
- * in the sidebar header. The search button opens the Cmd+K search dialog,
89
- * and the AI sparkle button opens a pure AI modal (no search tabs).
90
- */
91
- function SidebarIconModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }) {
85
+ function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
92
86
  const [searchOpen, setSearchOpen] = useState(false);
93
87
  const [aiOpen, setAiOpen] = useState(false);
94
88
  useEffect(() => {
@@ -123,6 +117,7 @@ function SidebarIconModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }
123
117
  api: "/api/docs",
124
118
  suggestedQuestions,
125
119
  aiLabel,
120
+ loaderVariant,
126
121
  loadingComponentHtml
127
122
  }), /* @__PURE__ */ jsx(AIModalDialog, {
128
123
  open: aiOpen,
@@ -130,6 +125,7 @@ function SidebarIconModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }
130
125
  api: "/api/docs",
131
126
  suggestedQuestions,
132
127
  aiLabel,
128
+ loaderVariant,
133
129
  loadingComponentHtml
134
130
  })] });
135
131
  }
@@ -375,6 +375,7 @@ function createDocsLayout(config) {
375
375
  const aiTriggerComponentHtml = aiConfig?.triggerComponent ? serializeIcon(aiConfig.triggerComponent) : void 0;
376
376
  const aiSuggestedQuestions = aiConfig?.suggestedQuestions;
377
377
  const aiLabel = aiConfig?.aiLabel;
378
+ const aiLoaderVariant = aiConfig?.loader;
378
379
  const aiLoadingComponentHtml = typeof aiConfig?.loadingComponent === "function" ? serializeIcon(aiConfig.loadingComponent({ name: aiLabel || "AI" })) : void 0;
379
380
  const lastModifiedMap = buildLastModifiedMap(config.entry);
380
381
  const descriptionMap = buildDescriptionMap(config.entry);
@@ -401,6 +402,7 @@ function createDocsLayout(config) {
401
402
  triggerComponentHtml: aiTriggerComponentHtml,
402
403
  suggestedQuestions: aiSuggestedQuestions,
403
404
  aiLabel,
405
+ loaderVariant: aiLoaderVariant,
404
406
  loadingComponentHtml: aiLoadingComponentHtml
405
407
  }),
406
408
  /* @__PURE__ */ jsx(DocsPageClient, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.0.2-beta.23",
3
+ "version": "0.0.2-beta.24",
4
4
  "description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
5
5
  "keywords": [
6
6
  "docs",
@@ -98,7 +98,7 @@
98
98
  "next": ">=14.0.0",
99
99
  "tsdown": "^0.20.3",
100
100
  "typescript": "^5.9.3",
101
- "@farming-labs/docs": "0.0.2-beta.23"
101
+ "@farming-labs/docs": "0.0.2-beta.24"
102
102
  },
103
103
  "peerDependencies": {
104
104
  "@farming-labs/docs": ">=0.0.1",
package/styles/ai.css CHANGED
@@ -7,19 +7,6 @@
7
7
 
8
8
  /* ─── Animations ─────────────────────────────────────────────────── */
9
9
 
10
- @keyframes fd-ai-dot {
11
- 0%,
12
- 80%,
13
- 100% {
14
- transform: scale(0);
15
- opacity: 0.5;
16
- }
17
- 40% {
18
- transform: scale(1);
19
- opacity: 1;
20
- }
21
- }
22
-
23
10
  @keyframes fd-ai-fade-in {
24
11
  from {
25
12
  opacity: 0;
@@ -343,6 +330,12 @@
343
330
  line-height: 1.6;
344
331
  max-width: 95%;
345
332
  word-break: break-word;
333
+ animation: fd-ai-msg-in 300ms ease-out;
334
+ }
335
+
336
+ @keyframes fd-ai-msg-in {
337
+ from { opacity: 0; transform: translateY(6px); }
338
+ to { opacity: 1; transform: translateY(0); }
346
339
  }
347
340
 
348
341
  /* ─── Chat input ─────────────────────────────────────────────────── */
@@ -397,38 +390,274 @@
397
390
  color: var(--color-fd-primary-foreground, #fff);
398
391
  }
399
392
 
400
- /* ─── Loading indicator ──────────────────────────────────────────── */
393
+ /* ═══════════════════════════════════════════════════════════════════
394
+ * AI Loader variants — fd-ai-loader-*
395
+ * Default: "shimmer-dots" (shimmer text + typing dots in a row)
396
+ * ═══════════════════════════════════════════════════════════════════ */
401
397
 
402
- .fd-ai-loading {
398
+ .fd-ai-loader {
403
399
  display: inline-flex;
400
+ align-items: center;
404
401
  gap: 6px;
402
+ animation: fd-ai-loader-in 300ms ease-out;
403
+ }
404
+
405
+ @keyframes fd-ai-loader-in {
406
+ from { opacity: 0; transform: translateY(4px); }
407
+ to { opacity: 1; transform: translateY(0); }
408
+ }
409
+
410
+ /* ── shimmer-dots (default): shimmer text + typing dots ──────────── */
411
+
412
+ .fd-ai-loader-shimmer-text {
413
+ font-size: 13px;
414
+ font-weight: 500;
415
+ background: linear-gradient(
416
+ to right,
417
+ var(--color-fd-muted-foreground, #888) 40%,
418
+ var(--color-fd-foreground, #fff) 60%,
419
+ var(--color-fd-muted-foreground, #888) 80%
420
+ );
421
+ background-size: 200% auto;
422
+ background-clip: text;
423
+ -webkit-background-clip: text;
424
+ color: transparent;
425
+ animation: fd-ai-shimmer-text 3s linear infinite;
426
+ }
427
+
428
+ @keyframes fd-ai-shimmer-text {
429
+ 0% { background-position: 150% center; }
430
+ 100% { background-position: -150% center; }
431
+ }
432
+
433
+ .fd-ai-loader-typing-dots {
434
+ display: inline-flex;
405
435
  align-items: center;
436
+ gap: 2px;
406
437
  }
407
438
 
408
- .fd-ai-loading-text {
409
- font-size: 12px;
410
- color: var(--color-fd-muted-foreground, #888);
439
+ .fd-ai-loader-typing-dot {
440
+ width: 4px;
441
+ height: 4px;
442
+ border-radius: 50%;
443
+ background: var(--color-fd-primary, #6366f1);
444
+ animation: fd-ai-typing 1s infinite;
411
445
  }
412
446
 
413
- .fd-ai-loading-dots {
447
+ .fd-ai-loader-typing-dot:nth-child(2) { animation-delay: 250ms; }
448
+ .fd-ai-loader-typing-dot:nth-child(3) { animation-delay: 500ms; }
449
+
450
+ @keyframes fd-ai-typing {
451
+ 0%, 100% { transform: translateY(0); opacity: 0.5; }
452
+ 50% { transform: translateY(-2px); opacity: 1; }
453
+ }
454
+
455
+ /* ── circular: spinning ring ─────────────────────────────────────── */
456
+
457
+ .fd-ai-loader-circular {
458
+ width: 16px;
459
+ height: 16px;
460
+ border: 2px solid var(--color-fd-primary, #6366f1);
461
+ border-top-color: transparent;
462
+ border-radius: 50%;
463
+ animation: fd-ai-spin 0.8s linear infinite;
464
+ }
465
+
466
+ @keyframes fd-ai-spin {
467
+ to { transform: rotate(360deg); }
468
+ }
469
+
470
+ /* ── dots: bouncing dots ─────────────────────────────────────────── */
471
+
472
+ .fd-ai-loader-dots {
414
473
  display: inline-flex;
474
+ align-items: center;
415
475
  gap: 3px;
476
+ }
477
+
478
+ .fd-ai-loader-bounce-dot {
479
+ width: 6px;
480
+ height: 6px;
481
+ border-radius: 50%;
482
+ background: var(--color-fd-primary, #6366f1);
483
+ animation: fd-ai-bounce-dots 1.4s ease-in-out infinite;
484
+ }
485
+
486
+ .fd-ai-loader-bounce-dot:nth-child(2) { animation-delay: 160ms; }
487
+ .fd-ai-loader-bounce-dot:nth-child(3) { animation-delay: 320ms; }
488
+
489
+ @keyframes fd-ai-bounce-dots {
490
+ 0%, 100% { transform: scale(0.8); opacity: 0.5; }
491
+ 50% { transform: scale(1.2); opacity: 1; }
492
+ }
493
+
494
+ /* ── wave: wave bars ─────────────────────────────────────────────── */
495
+
496
+ .fd-ai-loader-wave {
497
+ display: inline-flex;
416
498
  align-items: center;
499
+ gap: 2px;
500
+ height: 16px;
417
501
  }
418
502
 
419
- .fd-ai-loading-dot {
420
- width: 5px;
421
- height: 5px;
503
+ .fd-ai-loader-wave-bar {
504
+ width: 2px;
505
+ border-radius: 2px;
506
+ background: var(--color-fd-primary, #6366f1);
507
+ animation: fd-ai-wave 1s ease-in-out infinite;
508
+ }
509
+
510
+ .fd-ai-loader-wave-bar:nth-child(1) { height: 6px; animation-delay: 0ms; }
511
+ .fd-ai-loader-wave-bar:nth-child(2) { height: 10px; animation-delay: 100ms; }
512
+ .fd-ai-loader-wave-bar:nth-child(3) { height: 14px; animation-delay: 200ms; }
513
+ .fd-ai-loader-wave-bar:nth-child(4) { height: 10px; animation-delay: 300ms; }
514
+ .fd-ai-loader-wave-bar:nth-child(5) { height: 6px; animation-delay: 400ms; }
515
+
516
+ @keyframes fd-ai-wave {
517
+ 0%, 100% { transform: scaleY(1); }
518
+ 50% { transform: scaleY(0.6); }
519
+ }
520
+
521
+ /* ── pulse: pulsing ring ─────────────────────────────────────────── */
522
+
523
+ .fd-ai-loader-pulse {
524
+ width: 16px;
525
+ height: 16px;
526
+ border: 2px solid var(--color-fd-primary, #6366f1);
422
527
  border-radius: 50%;
423
- background: var(--color-fd-muted-foreground, #888);
424
- animation: fd-ai-dot 1.4s infinite ease-in-out both;
528
+ animation: fd-ai-pulse 1.5s ease-in-out infinite;
529
+ }
530
+
531
+ @keyframes fd-ai-pulse {
532
+ 0%, 100% { transform: scale(0.95); opacity: 0.8; }
533
+ 50% { transform: scale(1.05); opacity: 0.4; }
534
+ }
535
+
536
+ /* ── pulse-dot: pulsing dot ──────────────────────────────────────── */
537
+
538
+ .fd-ai-loader-pulse-dot {
539
+ width: 8px;
540
+ height: 8px;
541
+ border-radius: 50%;
542
+ background: var(--color-fd-primary, #6366f1);
543
+ animation: fd-ai-pulse-dot 1.2s ease-in-out infinite;
544
+ }
545
+
546
+ @keyframes fd-ai-pulse-dot {
547
+ 0%, 100% { transform: scale(1); opacity: 0.8; }
548
+ 50% { transform: scale(1.5); opacity: 1; }
549
+ }
550
+
551
+ /* ── terminal: blinking cursor ───────────────────────────────────── */
552
+
553
+ .fd-ai-loader-terminal {
554
+ display: inline-flex;
555
+ align-items: center;
556
+ gap: 3px;
557
+ }
558
+
559
+ .fd-ai-loader-terminal-prompt {
560
+ font-family: var(--fd-font-mono, ui-monospace, monospace);
561
+ font-size: 13px;
562
+ color: var(--color-fd-primary, #6366f1);
563
+ }
564
+
565
+ .fd-ai-loader-terminal-cursor {
566
+ width: 7px;
567
+ height: 14px;
568
+ background: var(--color-fd-primary, #6366f1);
569
+ animation: fd-ai-blink 1s step-end infinite;
570
+ }
571
+
572
+ @keyframes fd-ai-blink {
573
+ 0%, 100% { opacity: 1; }
574
+ 50% { opacity: 0; }
575
+ }
576
+
577
+ /* ── text-shimmer: shimmer text only ─────────────────────────────── */
578
+ /* (reuses .fd-ai-loader-shimmer-text above) */
579
+
580
+ /* ── text-blink: blinking text ───────────────────────────────────── */
581
+
582
+ .fd-ai-loader-text-blink {
583
+ font-size: 13px;
584
+ font-weight: 500;
585
+ animation: fd-ai-text-blink 2s ease-in-out infinite;
586
+ }
587
+
588
+ @keyframes fd-ai-text-blink {
589
+ 0%, 100% { color: var(--color-fd-primary, #6366f1); }
590
+ 50% { color: var(--color-fd-muted-foreground, #888); }
591
+ }
592
+
593
+ /* ── loading-dots: "Thinking..." with animated dots ──────────────── */
594
+
595
+ .fd-ai-loader-text {
596
+ font-size: 13px;
597
+ font-weight: 500;
598
+ color: var(--color-fd-primary, #6366f1);
425
599
  }
426
600
 
427
- .fd-ai-loading-dot:nth-child(2) {
428
- animation-delay: 0.16s;
601
+ .fd-ai-loader-text-dots {
602
+ display: inline-flex;
429
603
  }
430
- .fd-ai-loading-dot:nth-child(3) {
431
- animation-delay: 0.32s;
604
+
605
+ .fd-ai-loader-text-dot {
606
+ font-size: 13px;
607
+ font-weight: 500;
608
+ color: var(--color-fd-primary, #6366f1);
609
+ animation: fd-ai-loading-dots 1.4s infinite;
610
+ }
611
+
612
+ .fd-ai-loader-text-dot:nth-child(1) { animation-delay: 200ms; }
613
+ .fd-ai-loader-text-dot:nth-child(2) { animation-delay: 400ms; }
614
+ .fd-ai-loader-text-dot:nth-child(3) { animation-delay: 600ms; }
615
+
616
+ @keyframes fd-ai-loading-dots {
617
+ 0%, 100% { opacity: 0; }
618
+ 50% { opacity: 1; }
619
+ }
620
+
621
+ /* ── bars: thick wave bars ───────────────────────────────────────── */
622
+
623
+ .fd-ai-loader-bars {
624
+ display: inline-flex;
625
+ align-items: stretch;
626
+ gap: 3px;
627
+ height: 16px;
628
+ }
629
+
630
+ .fd-ai-loader-bar {
631
+ width: 4px;
632
+ background: var(--color-fd-primary, #6366f1);
633
+ animation: fd-ai-wave-bars 1.2s ease-in-out infinite;
634
+ }
635
+
636
+ .fd-ai-loader-bar:nth-child(1) { animation-delay: 0s; }
637
+ .fd-ai-loader-bar:nth-child(2) { animation-delay: 0.2s; }
638
+ .fd-ai-loader-bar:nth-child(3) { animation-delay: 0.4s; }
639
+
640
+ @keyframes fd-ai-wave-bars {
641
+ 0%, 100% { transform: scaleY(1); opacity: 0.5; }
642
+ 50% { transform: scaleY(0.6); opacity: 1; }
643
+ }
644
+
645
+ /* ─── Streaming cursor ───────────────────────────────────────────── */
646
+
647
+ .fd-ai-streaming::after {
648
+ content: "";
649
+ display: inline-block;
650
+ width: 2px;
651
+ height: 1em;
652
+ background: var(--color-fd-primary, #6366f1);
653
+ margin-left: 2px;
654
+ vertical-align: text-bottom;
655
+ animation: fd-ai-cursor-blink 0.8s step-end infinite;
656
+ }
657
+
658
+ @keyframes fd-ai-cursor-blink {
659
+ 0%, 100% { opacity: 1; }
660
+ 50% { opacity: 0; }
432
661
  }
433
662
 
434
663
  /* ─── Floating trigger button ────────────────────────────────────── */
@@ -747,39 +976,7 @@
747
976
 
748
977
  /* ─── Thinking dots ──────────────────────────────────────────────── */
749
978
 
750
- .fd-ai-fm-thinking {
751
- display: flex;
752
- gap: 4px;
753
- align-items: center;
754
- }
755
-
756
- .fd-ai-fm-thinking-dot {
757
- width: 6px;
758
- height: 6px;
759
- border-radius: 9999px;
760
- background: var(--color-fd-primary, #6366f1);
761
- animation: fd-ai-fm-bounce 1s infinite ease-in-out;
762
- }
763
-
764
- .fd-ai-fm-thinking-dot:nth-child(2) {
765
- animation-delay: 150ms;
766
- }
767
- .fd-ai-fm-thinking-dot:nth-child(3) {
768
- animation-delay: 300ms;
769
- }
770
-
771
- @keyframes fd-ai-fm-bounce {
772
- 0%,
773
- 80%,
774
- 100% {
775
- transform: scale(0.6);
776
- opacity: 0.4;
777
- }
778
- 40% {
779
- transform: scale(1);
780
- opacity: 1;
781
- }
782
- }
979
+ /* Full-modal now uses the shared .fd-ai-loader indicator */
783
980
 
784
981
  /* ─── Bottom input bar ───────────────────────────────────────────── */
785
982
 
@@ -408,7 +408,7 @@ article a[class*="text-fd-muted-foreground"] {
408
408
  border-radius: 2px;
409
409
  }
410
410
 
411
- .fd-ai-fm-thinking-dot {
411
+ .fd-ai-loader-typing-dot {
412
412
  border-radius: 2px;
413
413
  }
414
414
 
@@ -556,13 +556,13 @@ figure.shiki > div:first-child {
556
556
  letter-spacing: 0.06em;
557
557
  }
558
558
 
559
- .fd-ai-loading-text {
559
+ .fd-ai-loader-shimmer-text {
560
560
  text-transform: uppercase;
561
561
  letter-spacing: 0.04em;
562
562
  font-size: 11px;
563
563
  }
564
564
 
565
- .fd-ai-loading-dot {
565
+ .fd-ai-loader-typing-dot {
566
566
  border-radius: 0;
567
567
  width: 4px;
568
568
  height: 4px;
@@ -640,11 +640,7 @@ figure.shiki > div:first-child {
640
640
  font-size: 11px;
641
641
  }
642
642
 
643
- .fd-ai-fm-thinking-dot {
644
- border-radius: 0;
645
- width: 5px;
646
- height: 5px;
647
- }
643
+ /* Full-modal now uses .fd-ai-loader-typing-dot (see above) */
648
644
 
649
645
  /* ─── Code blocks (pixel-border) ─────────────────────────────────── */
650
646