sdocs 0.0.1

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.
Files changed (129) hide show
  1. package/README.md +43 -0
  2. package/bin/sdocs.js +2 -0
  3. package/dist/Sdocs.svelte +1210 -0
  4. package/dist/Sdocs.svelte.d.ts +5 -0
  5. package/dist/cli/app-plugin.d.ts +7 -0
  6. package/dist/cli/app-plugin.js +69 -0
  7. package/dist/cli/config.d.ts +12 -0
  8. package/dist/cli/config.js +34 -0
  9. package/dist/cli/index.d.ts +1 -0
  10. package/dist/cli/index.js +72 -0
  11. package/dist/cli/server.d.ts +2 -0
  12. package/dist/cli/server.js +62 -0
  13. package/dist/docgen.d.ts +47 -0
  14. package/dist/docgen.js +463 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +4 -0
  17. package/dist/internal/ComponentPreview.svelte +58 -0
  18. package/dist/internal/ComponentPreview.svelte.d.ts +17 -0
  19. package/dist/internal/CssPropsTable.svelte +239 -0
  20. package/dist/internal/CssPropsTable.svelte.d.ts +11 -0
  21. package/dist/internal/Home.svelte +92 -0
  22. package/dist/internal/Home.svelte.d.ts +9 -0
  23. package/dist/internal/MethodsTable.svelte +72 -0
  24. package/dist/internal/MethodsTable.svelte.d.ts +7 -0
  25. package/dist/internal/PropsTable.svelte +342 -0
  26. package/dist/internal/PropsTable.svelte.d.ts +12 -0
  27. package/dist/internal/Showcase.svelte +130 -0
  28. package/dist/internal/Showcase.svelte.d.ts +21 -0
  29. package/dist/types.d.ts +162 -0
  30. package/dist/types.js +1 -0
  31. package/dist/ui/Badge/Badge.docs.svelte +46 -0
  32. package/dist/ui/Badge/Badge.docs.svelte.d.ts +26 -0
  33. package/dist/ui/Badge/Badge.svelte +59 -0
  34. package/dist/ui/Badge/Badge.svelte.d.ts +17 -0
  35. package/dist/ui/Badge/index.d.ts +1 -0
  36. package/dist/ui/Badge/index.js +1 -0
  37. package/dist/ui/Checkbox/Checkbox.docs.svelte +51 -0
  38. package/dist/ui/Checkbox/Checkbox.docs.svelte.d.ts +27 -0
  39. package/dist/ui/Checkbox/Checkbox.svelte +169 -0
  40. package/dist/ui/Checkbox/Checkbox.svelte.d.ts +18 -0
  41. package/dist/ui/Checkbox/index.d.ts +1 -0
  42. package/dist/ui/Checkbox/index.js +1 -0
  43. package/dist/ui/CodeBlock/CodeBlock.docs.svelte +28 -0
  44. package/dist/ui/CodeBlock/CodeBlock.docs.svelte.d.ts +24 -0
  45. package/dist/ui/CodeBlock/CodeBlock.svelte +101 -0
  46. package/dist/ui/CodeBlock/CodeBlock.svelte.d.ts +7 -0
  47. package/dist/ui/CodeBlock/index.d.ts +1 -0
  48. package/dist/ui/CodeBlock/index.js +1 -0
  49. package/dist/ui/Frame/Frame.docs.svelte +140 -0
  50. package/dist/ui/Frame/Frame.docs.svelte.d.ts +26 -0
  51. package/dist/ui/Frame/Frame.svelte +88 -0
  52. package/dist/ui/Frame/Frame.svelte.d.ts +15 -0
  53. package/dist/ui/Frame/index.d.ts +1 -0
  54. package/dist/ui/Frame/index.js +1 -0
  55. package/dist/ui/InputNumber/InputNumber.docs.svelte +50 -0
  56. package/dist/ui/InputNumber/InputNumber.docs.svelte.d.ts +26 -0
  57. package/dist/ui/InputNumber/InputNumber.svelte +275 -0
  58. package/dist/ui/InputNumber/InputNumber.svelte.d.ts +26 -0
  59. package/dist/ui/InputNumber/index.d.ts +1 -0
  60. package/dist/ui/InputNumber/index.js +1 -0
  61. package/dist/ui/InputText/InputText.docs.svelte +43 -0
  62. package/dist/ui/InputText/InputText.docs.svelte.d.ts +26 -0
  63. package/dist/ui/InputText/InputText.svelte +116 -0
  64. package/dist/ui/InputText/InputText.svelte.d.ts +22 -0
  65. package/dist/ui/InputText/index.d.ts +1 -0
  66. package/dist/ui/InputText/index.js +1 -0
  67. package/dist/ui/Panel/CollapsiblePanel.docs.svelte +45 -0
  68. package/dist/ui/Panel/CollapsiblePanel.docs.svelte.d.ts +25 -0
  69. package/dist/ui/Panel/CollapsiblePanel.svelte +93 -0
  70. package/dist/ui/Panel/CollapsiblePanel.svelte.d.ts +14 -0
  71. package/dist/ui/Panel/index.d.ts +1 -0
  72. package/dist/ui/Panel/index.js +1 -0
  73. package/dist/ui/Placeholder/Placeholder.docs.svelte +49 -0
  74. package/dist/ui/Placeholder/Placeholder.docs.svelte.d.ts +26 -0
  75. package/dist/ui/Placeholder/Placeholder.svelte +99 -0
  76. package/dist/ui/Placeholder/Placeholder.svelte.d.ts +21 -0
  77. package/dist/ui/Placeholder/index.d.ts +1 -0
  78. package/dist/ui/Placeholder/index.js +1 -0
  79. package/dist/ui/Radio/Radio.docs.svelte +67 -0
  80. package/dist/ui/Radio/Radio.docs.svelte.d.ts +27 -0
  81. package/dist/ui/Radio/Radio.svelte +165 -0
  82. package/dist/ui/Radio/Radio.svelte.d.ts +22 -0
  83. package/dist/ui/Radio/RadioGroup.docs.svelte +70 -0
  84. package/dist/ui/Radio/RadioGroup.docs.svelte.d.ts +27 -0
  85. package/dist/ui/Radio/RadioGroup.svelte +98 -0
  86. package/dist/ui/Radio/RadioGroup.svelte.d.ts +27 -0
  87. package/dist/ui/Radio/index.d.ts +2 -0
  88. package/dist/ui/Radio/index.js +2 -0
  89. package/dist/ui/SegmentControl/SegmentControl.docs.svelte +54 -0
  90. package/dist/ui/SegmentControl/SegmentControl.docs.svelte.d.ts +25 -0
  91. package/dist/ui/SegmentControl/SegmentControl.svelte +120 -0
  92. package/dist/ui/SegmentControl/SegmentControl.svelte.d.ts +18 -0
  93. package/dist/ui/SegmentControl/index.d.ts +1 -0
  94. package/dist/ui/SegmentControl/index.js +1 -0
  95. package/dist/ui/Stack/Stack.docs.svelte +63 -0
  96. package/dist/ui/Stack/Stack.docs.svelte.d.ts +26 -0
  97. package/dist/ui/Stack/Stack.svelte +45 -0
  98. package/dist/ui/Stack/Stack.svelte.d.ts +19 -0
  99. package/dist/ui/Stack/index.d.ts +1 -0
  100. package/dist/ui/Stack/index.js +1 -0
  101. package/dist/ui/Table/Body.svelte +17 -0
  102. package/dist/ui/Table/Body.svelte.d.ts +11 -0
  103. package/dist/ui/Table/Caption.svelte +17 -0
  104. package/dist/ui/Table/Caption.svelte.d.ts +11 -0
  105. package/dist/ui/Table/Cell.svelte +24 -0
  106. package/dist/ui/Table/Cell.svelte.d.ts +15 -0
  107. package/dist/ui/Table/Foot.svelte +17 -0
  108. package/dist/ui/Table/Foot.svelte.d.ts +11 -0
  109. package/dist/ui/Table/Head.svelte +17 -0
  110. package/dist/ui/Table/Head.svelte.d.ts +11 -0
  111. package/dist/ui/Table/Header.svelte +27 -0
  112. package/dist/ui/Table/Header.svelte.d.ts +17 -0
  113. package/dist/ui/Table/Row.svelte +19 -0
  114. package/dist/ui/Table/Row.svelte.d.ts +13 -0
  115. package/dist/ui/Table/Table.docs.svelte +197 -0
  116. package/dist/ui/Table/Table.docs.svelte.d.ts +28 -0
  117. package/dist/ui/Table/Table.svelte +140 -0
  118. package/dist/ui/Table/Table.svelte.d.ts +27 -0
  119. package/dist/ui/Table/index.js +10 -0
  120. package/dist/ui/css/colors.css +377 -0
  121. package/dist/ui/css/global.css +10 -0
  122. package/dist/ui/index.d.ts +12 -0
  123. package/dist/ui/index.js +12 -0
  124. package/dist/virtual-sdocs.d.ts +20 -0
  125. package/dist/vite-plugin.d.ts +18 -0
  126. package/dist/vite-plugin.js +206 -0
  127. package/dist/vite.d.ts +2 -0
  128. package/dist/vite.js +2 -0
  129. package/package.json +76 -0
@@ -0,0 +1,1210 @@
1
+ <script lang="ts">
2
+ import { docs as virtualDocs } from 'virtual:sdocs';
3
+ import type { SdocsProps, DocFile, Example, DocMeta } from './types.js';
4
+ import { propsToArgTypes, propsToDefaultArgs, cssVarsToCssProps, docgenToMethods, type ComponentDocgen } from './docgen.js';
5
+ import Showcase from './internal/Showcase.svelte';
6
+ import { CodeBlock, CollapsiblePanel } from './ui/index.js';
7
+ import ComponentPreview from './internal/ComponentPreview.svelte';
8
+ import Home from './internal/Home.svelte';
9
+ import ChevronRight from 'lucide-svelte/icons/chevron-right';
10
+ import Folder from 'lucide-svelte/icons/folder';
11
+ import FolderOpen from 'lucide-svelte/icons/folder-open';
12
+ import Component from 'lucide-svelte/icons/component';
13
+ import FileText from 'lucide-svelte/icons/file-text';
14
+ import Bookmark from 'lucide-svelte/icons/bookmark';
15
+ import './ui/css/global.css';
16
+
17
+ const browser = typeof window !== 'undefined';
18
+
19
+ let { options }: SdocsProps = $props();
20
+
21
+ // Use docs from virtual module (auto-loaded by sdocsPlugin)
22
+ const docModules = $derived(virtualDocs ?? {});
23
+
24
+ type ViewMode = 'docs' | 'story';
25
+
26
+ // Tree node for hierarchical navigation
27
+ interface NavNode {
28
+ name: string;
29
+ children: NavNode[];
30
+ file?: DocFile; // Only leaf nodes have a file
31
+ }
32
+
33
+ // Parse doc files from the glob import
34
+ function parseDocs(
35
+ docModulesInput: Record<string, unknown>
36
+ ): DocFile[] {
37
+ return Object.entries(docModulesInput).map(([path, mod]) => {
38
+ const module = mod as Record<string, unknown>;
39
+ const isMarkdown = path.endsWith('.svx');
40
+ const rawMeta = (module.meta as DocMeta) ?? { component: null };
41
+
42
+ // Get snippet sources and example order (injected by vite plugin)
43
+ const snippetSources = (module.__snippetSources as Record<string, string>) ?? {};
44
+ const exampleOrder = (module.__exampleOrder as string[]) ?? [];
45
+
46
+ // Extract examples from the module
47
+ const exampleEntries: Example[] = Object.entries(module)
48
+ .filter(([key, value]) => key !== 'meta' && key !== 'default' && key !== '__snippetSources' && key !== '__exampleOrder' && typeof value === 'function')
49
+ .map(([name, render]) => ({
50
+ name,
51
+ render: render as Example['render'],
52
+ source: snippetSources[name]
53
+ }));
54
+
55
+ // Sort examples by export order if available
56
+ if (exampleOrder.length > 0) {
57
+ exampleEntries.sort((a, b) => {
58
+ const aIndex = exampleOrder.indexOf(a.name);
59
+ const bIndex = exampleOrder.indexOf(b.name);
60
+ // Put examples not in order list at the end
61
+ if (aIndex === -1 && bIndex === -1) return 0;
62
+ if (aIndex === -1) return 1;
63
+ if (bIndex === -1) return -1;
64
+ return aIndex - bIndex;
65
+ });
66
+ }
67
+
68
+ // Try to get auto-generated docgen from component
69
+ const component = rawMeta.component as { __docgen?: ComponentDocgen } | undefined;
70
+ const docgen = component?.__docgen;
71
+
72
+ // Auto-generate argTypes from docgen
73
+ const argTypes = docgen ? propsToArgTypes(docgen) : {};
74
+
75
+ // Extract default args from docgen, merge with manual args (manual overrides auto)
76
+ const autoArgs = docgen ? propsToDefaultArgs(docgen) : {};
77
+ const args = rawMeta.args ? { ...autoArgs, ...rawMeta.args } : autoArgs;
78
+
79
+ // Get description from docgen (auto-generated from JSDoc)
80
+ const description = rawMeta.description ?? docgen?.description;
81
+
82
+ // Auto-generate cssProps from @cssvar JSDoc tags
83
+ const cssProps = docgen ? cssVarsToCssProps(docgen) : undefined;
84
+
85
+ // Auto-generate methods from exported functions
86
+ const methods = docgen ? docgenToMethods(docgen) : undefined;
87
+
88
+ // Determine type: 'page' for standalone docs, 'component' for component documentation
89
+ const hasExamples = exampleEntries.length > 0;
90
+ const type = isMarkdown && !hasExamples ? 'page' : 'component';
91
+
92
+ return {
93
+ path,
94
+ meta: {
95
+ title: rawMeta.title ?? pathToTitle(path),
96
+ description,
97
+ component: rawMeta.component,
98
+ args,
99
+ argTypes,
100
+ cssProps,
101
+ methods
102
+ },
103
+ examples: exampleEntries,
104
+ module,
105
+ type,
106
+ isMarkdown
107
+ };
108
+ });
109
+ }
110
+
111
+ // Expand args references in source code with actual prop values
112
+ function expandArgsInSource(source: string, args: Record<string, unknown>): string {
113
+ let result = source;
114
+
115
+ // Replace {...args} with individual prop="value" pairs
116
+ result = result.replace(/\{\s*\.\.\.args\s*\}/g, () => {
117
+ const props = Object.entries(args)
118
+ .map(([key, value]) => {
119
+ if (typeof value === 'string') {
120
+ return `${key}="${value}"`;
121
+ } else if (typeof value === 'boolean') {
122
+ return value ? key : `${key}={false}`;
123
+ } else {
124
+ return `${key}={${JSON.stringify(value)}}`;
125
+ }
126
+ })
127
+ .join(' ');
128
+ return props;
129
+ });
130
+
131
+ // Replace {args.propName as Type} or {args.propName} with actual values
132
+ result = result.replace(/\{args\.(\w+)(?:\s+as\s+[^}]+)?\}/g, (_, propName) => {
133
+ const value = args[propName];
134
+ if (value === undefined) return `{args.${propName}}`;
135
+ if (typeof value === 'string') {
136
+ return `"${value}"`;
137
+ } else if (typeof value === 'boolean') {
138
+ return `{${value}}`;
139
+ } else {
140
+ return `{${JSON.stringify(value)}}`;
141
+ }
142
+ });
143
+
144
+ return result;
145
+ }
146
+
147
+ // Convert file path to readable title
148
+ function pathToTitle(path: string): string {
149
+ const filename = path.split('/').pop() ?? path;
150
+ return filename
151
+ .replace('.docs.svelte', '')
152
+ .replace('.docs.svx', '')
153
+ .replace(/([A-Z])/g, ' $1')
154
+ .trim();
155
+ }
156
+
157
+ // Build navigation tree from flat doc files
158
+ function buildNavTree(files: DocFile[]): NavNode[] {
159
+ const root: NavNode[] = [];
160
+
161
+ for (const file of files) {
162
+ const parts = (file.meta.title ?? '').split('/').map(p => p.trim());
163
+ let currentLevel = root;
164
+
165
+ for (let i = 0; i < parts.length; i++) {
166
+ const part = parts[i];
167
+ const isLast = i === parts.length - 1;
168
+
169
+ let existing = currentLevel.find(n => n.name === part);
170
+ if (!existing) {
171
+ existing = {
172
+ name: part,
173
+ children: [],
174
+ file: isLast ? file : undefined
175
+ };
176
+ currentLevel.push(existing);
177
+ } else if (isLast) {
178
+ // Update existing folder node to also be a file
179
+ existing.file = file;
180
+ }
181
+
182
+ currentLevel = existing.children;
183
+ }
184
+ }
185
+
186
+ // Sort tree with custom order support (supports any depth via path keys)
187
+ // Order format: ['First', 'Second', '*', 'Last'] - items before * come first, after * come last
188
+ function sortTree(nodes: NavNode[], order?: string[], orderMap?: Record<string, string[]>, currentPath = ''): void {
189
+ nodes.sort((a, b) => {
190
+ if (order && order.length > 0) {
191
+ const starIndex = order.indexOf('*');
192
+ const aIndex = order.findIndex((o) => o === '*' ? false : o.toLowerCase() === a.name.toLowerCase());
193
+ const bIndex = order.findIndex((o) => o === '*' ? false : o.toLowerCase() === b.name.toLowerCase());
194
+
195
+ // Determine position category: -1 = before *, 0 = is *, 1 = after *
196
+ const aPos = aIndex === -1 ? 0 : (starIndex === -1 || aIndex < starIndex ? -1 : 1);
197
+ const bPos = bIndex === -1 ? 0 : (starIndex === -1 || bIndex < starIndex ? -1 : 1);
198
+
199
+ // Different categories: before * < * group < after *
200
+ if (aPos !== bPos) return aPos - bPos;
201
+
202
+ // Same category
203
+ if (aPos === 0) {
204
+ // Both in * group - alphabetical
205
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
206
+ }
207
+ // Both explicitly ordered (both before or both after *) - by index
208
+ return aIndex - bIndex;
209
+ }
210
+ // No order specified - alphabetical
211
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
212
+ });
213
+ // Recursively sort children with their specific order if available
214
+ for (const node of nodes) {
215
+ if (node.children.length > 0) {
216
+ const nodePath = currentPath ? `${currentPath}/${node.name}` : node.name;
217
+ // Try path-based key first, then just the node name
218
+ const childOrder = orderMap?.[nodePath] ?? orderMap?.[node.name];
219
+ sortTree(node.children, childOrder, orderMap, nodePath);
220
+ }
221
+ }
222
+ }
223
+
224
+ // Normalize sidebar order to extract top-level order and folder-specific orders
225
+ const sidebarOrder = options?.sidebar?.order;
226
+ let topLevelOrder: string[] | undefined;
227
+ let orderMap: Record<string, string[]> | undefined;
228
+
229
+ if (Array.isArray(sidebarOrder)) {
230
+ topLevelOrder = sidebarOrder;
231
+ } else if (sidebarOrder) {
232
+ topLevelOrder = sidebarOrder['root'];
233
+ orderMap = sidebarOrder;
234
+ }
235
+
236
+ sortTree(root, topLevelOrder, orderMap);
237
+
238
+ return root;
239
+ }
240
+
241
+ // Get the display name (last part of title)
242
+ function getDisplayName(title: string | undefined): string {
243
+ if (!title) return '';
244
+ const parts = title.split('/');
245
+ return parts[parts.length - 1].trim();
246
+ }
247
+
248
+ let docFiles = $derived(parseDocs(docModules));
249
+ let navTree = $derived(buildNavTree(docFiles));
250
+
251
+ // Count components and pages for Home
252
+ let componentCount = $derived(docFiles.filter(f => f.type === 'component').length);
253
+ let pageCount = $derived(docFiles.filter(f => f.type === 'page').length);
254
+
255
+ let selectedFile = $state<DocFile | null>(null);
256
+ let selectedExample = $state<Example | null>(null);
257
+ let viewMode = $state<ViewMode>('docs');
258
+ let args = $state<Record<string, unknown>>({});
259
+ let showCode = $state(false);
260
+ let initialized = $state(false);
261
+ let expandedFolders = $state<Set<string>>(new Set());
262
+ let collapsedFolders = $state<Set<string>>(new Set()); // Track explicitly collapsed folders
263
+
264
+ // Active file is the selected file (can be null to show Home)
265
+ let activeFile = $derived(selectedFile);
266
+
267
+ function toggleFolder(path: string) {
268
+ if (expandedFolders.has(path)) {
269
+ expandedFolders.delete(path);
270
+ } else {
271
+ expandedFolders.add(path);
272
+ }
273
+ expandedFolders = new Set(expandedFolders); // Trigger reactivity
274
+ }
275
+
276
+ function selectComponent(file: DocFile, nodePath: string) {
277
+ const isCurrentlySelected = activeFile?.path === file.path && viewMode === 'docs';
278
+ const isExpanded = isFolderExpanded(nodePath);
279
+
280
+ if (isCurrentlySelected && isExpanded) {
281
+ // Clicking again: deselect and close
282
+ selectedFile = null;
283
+ toggleFolder(nodePath);
284
+ } else {
285
+ // Select and show docs
286
+ selectDocs(file);
287
+ }
288
+ }
289
+
290
+ function isFolderExpanded(path: string): boolean {
291
+ return effectiveExpandedFolders.has(path);
292
+ }
293
+
294
+ // Auto-expand folders that contain the selected file AND the file itself
295
+ function expandPathToFile(file: DocFile) {
296
+ const parts = (file.meta.title ?? '').split('/').map(p => p.trim());
297
+ let path = '';
298
+ // Expand all parts including the component itself
299
+ for (let i = 0; i < parts.length; i++) {
300
+ path = path ? `${path}/${parts[i]}` : parts[i];
301
+ expandedFolders.add(path);
302
+ }
303
+ expandedFolders = new Set(expandedFolders);
304
+ }
305
+
306
+ // Get argTypes from active file's meta
307
+ let currentArgTypes = $derived(activeFile?.meta.argTypes ?? {});
308
+
309
+ // Default args from active file (for SSR)
310
+ let defaultArgs = $derived(activeFile?.meta.args ?? {});
311
+
312
+ // Paths to open by default from options
313
+ let configuredOpenPaths = $derived(new Set(options?.sidebar?.open ?? []));
314
+
315
+ // Compute expanded folders for the active file (works during SSR)
316
+ let defaultExpandedPaths = $derived.by(() => {
317
+ if (!activeFile) return new Set<string>();
318
+ const parts = (activeFile.meta.title ?? '').split('/').map(p => p.trim());
319
+ const paths = new Set<string>();
320
+ let path = '';
321
+ for (let i = 0; i < parts.length; i++) {
322
+ path = path ? `${path}/${parts[i]}` : parts[i];
323
+ paths.add(path);
324
+ }
325
+ return paths;
326
+ });
327
+
328
+ // Merge explicit expanded folders with defaults and configured open paths, excluding explicitly collapsed ones
329
+ let effectiveExpandedFolders = $derived.by(() => {
330
+ const merged = new Set([...configuredOpenPaths, ...defaultExpandedPaths, ...expandedFolders]);
331
+ for (const path of collapsedFolders) {
332
+ merged.delete(path);
333
+ }
334
+ return merged;
335
+ });
336
+
337
+ // Create a plain object snapshot of args for rendering
338
+ // Use defaultArgs during SSR when args is empty
339
+ let argsSnapshot = $derived(
340
+ Object.keys(args).length > 0 ? $state.snapshot(args) : defaultArgs
341
+ );
342
+
343
+ // Read URL params and initialize selection
344
+ function initFromUrl() {
345
+ if (!browser) return;
346
+
347
+ const params = new URLSearchParams(window.location.search);
348
+ const componentName = params.get('component');
349
+ const storyName = params.get('story');
350
+
351
+ if (componentName) {
352
+ const file = docFiles.find(f => f.meta.title === componentName);
353
+ if (file) {
354
+ selectedFile = file;
355
+ args = { ...(file.meta.args ?? {}) };
356
+ expandPathToFile(file);
357
+
358
+ if (storyName) {
359
+ const example = file.examples.find(s => s.name === storyName);
360
+ if (example) {
361
+ selectedExample = example;
362
+ viewMode = 'story';
363
+ } else {
364
+ viewMode = 'docs';
365
+ }
366
+ } else {
367
+ viewMode = 'docs';
368
+ }
369
+ }
370
+ }
371
+ // No component param = show Home (selectedFile stays null)
372
+ initialized = true;
373
+ }
374
+
375
+ // Update URL when selection changes
376
+ function updateUrl() {
377
+ if (!browser || !initialized) return;
378
+
379
+ if (!selectedFile) {
380
+ // Going home - clear params
381
+ window.history.replaceState({}, '', window.location.pathname);
382
+ return;
383
+ }
384
+
385
+ const params = new URLSearchParams();
386
+ params.set('component', selectedFile.meta.title ?? '');
387
+
388
+ if (viewMode === 'story' && selectedExample) {
389
+ params.set('story', selectedExample.name);
390
+ }
391
+
392
+ const newUrl = `${window.location.pathname}?${params.toString()}`;
393
+ window.history.replaceState({}, '', newUrl);
394
+ }
395
+
396
+ // Initialize from URL when docs load (use $effect.pre to run before render)
397
+ $effect.pre(() => {
398
+ if (docFiles.length > 0 && !initialized) {
399
+ initFromUrl();
400
+ }
401
+ });
402
+
403
+ // Handle browser back/forward
404
+ $effect(() => {
405
+ if (!browser) return;
406
+
407
+ const handlePopState = () => {
408
+ initialized = false;
409
+ initFromUrl();
410
+ };
411
+
412
+ window.addEventListener('popstate', handlePopState);
413
+ return () => window.removeEventListener('popstate', handlePopState);
414
+ });
415
+
416
+ function goHome() {
417
+ selectedFile = null;
418
+ selectedExample = null;
419
+ viewMode = 'docs';
420
+ args = {};
421
+ showCode = false;
422
+ updateUrl();
423
+ }
424
+
425
+ function selectDocs(file: DocFile) {
426
+ selectedFile = file;
427
+ selectedExample = null;
428
+ viewMode = 'docs';
429
+ args = { ...(file.meta.args ?? {}) };
430
+ showCode = false;
431
+ expandPathToFile(file);
432
+ updateUrl();
433
+ }
434
+
435
+ function selectExample(file: DocFile, example: Example) {
436
+ selectedFile = file;
437
+ selectedExample = example;
438
+ viewMode = 'story';
439
+ args = { ...(file.meta.args ?? {}) };
440
+ showCode = false;
441
+ expandPathToFile(file);
442
+ updateUrl();
443
+ }
444
+
445
+ function handleArgsChange(newArgs: Record<string, unknown>) {
446
+ args = newArgs;
447
+ }
448
+
449
+ function toggleCode() {
450
+ showCode = !showCode;
451
+ }
452
+ </script>
453
+
454
+ {#snippet renderNavNode(node: NavNode, depth: number, parentPath: string)}
455
+ {@const nodePath = parentPath ? `${parentPath}/${node.name}` : node.name}
456
+ {@const isFolder = node.children.length > 0 && !node.file}
457
+ {@const isFolderWithFile = node.children.length > 0 && node.file}
458
+ {@const expanded = isFolderExpanded(nodePath)}
459
+
460
+ {#if isFolder || isFolderWithFile}
461
+ <!-- Folder node -->
462
+ <div class="sdocs-folder" style="--depth: {depth}">
463
+ <button
464
+ type="button"
465
+ class="sdocs-folder-btn"
466
+ onclick={() => {
467
+ const wasExpanded = isFolderExpanded(nodePath);
468
+ if (wasExpanded) {
469
+ collapsedFolders.add(nodePath);
470
+ collapsedFolders = new Set(collapsedFolders);
471
+ expandedFolders.delete(nodePath);
472
+ expandedFolders = new Set(expandedFolders);
473
+ } else {
474
+ collapsedFolders.delete(nodePath);
475
+ collapsedFolders = new Set(collapsedFolders);
476
+ expandedFolders.add(nodePath);
477
+ expandedFolders = new Set(expandedFolders);
478
+ }
479
+ }}
480
+ >
481
+ {#if expanded}
482
+ <FolderOpen size={16} strokeWidth={1.75} />
483
+ {:else}
484
+ <Folder size={16} strokeWidth={1.75} />
485
+ {/if}
486
+ <span class="sdocs-item-name">{node.name}</span>
487
+ <span class="sdocs-folder-icon" class:expanded>
488
+ <ChevronRight size={14} strokeWidth={2} />
489
+ </span>
490
+ </button>
491
+ {#if expanded}
492
+ {#if isFolderWithFile && node.file}
493
+ <!-- Folder that is also a component -->
494
+ <ul class="sdocs-story-list">
495
+ <li>
496
+ <button
497
+ type="button"
498
+ class="sdocs-story-btn sdocs-docs-btn"
499
+ class:active={activeFile?.path === node.file.path && viewMode === 'docs'}
500
+ onclick={() => node.file && selectDocs(node.file)}
501
+ >
502
+ <FileText size={14} strokeWidth={1.75} />
503
+ Docs
504
+ </button>
505
+ </li>
506
+ {#each node.file.examples.filter(e => e.name !== 'Default') as example (example.name)}
507
+ <li>
508
+ <button
509
+ type="button"
510
+ class="sdocs-story-btn"
511
+ class:active={activeFile?.path === node.file?.path && selectedExample?.name === example.name && viewMode === 'story'}
512
+ onclick={() => node.file && selectExample(node.file, example)}
513
+ >
514
+ <Bookmark size={14} strokeWidth={1.75} />
515
+ {example.name}
516
+ </button>
517
+ </li>
518
+ {/each}
519
+ </ul>
520
+ {/if}
521
+ <div class="sdocs-folder-children">
522
+ {#each node.children as child (child.name)}
523
+ {@render renderNavNode(child, depth + 1, nodePath)}
524
+ {/each}
525
+ </div>
526
+ {/if}
527
+ </div>
528
+ {:else if node.file}
529
+ <!-- Leaf node -->
530
+ {#if node.file.type === 'page'}
531
+ <!-- Doc page (markdown) - no expansion needed -->
532
+ <div class="sdocs-doc-item" style="--depth: {depth}">
533
+ <button
534
+ type="button"
535
+ class="sdocs-doc-btn"
536
+ class:active={activeFile?.path === node.file.path}
537
+ onclick={() => node.file && selectDocs(node.file)}
538
+ >
539
+ <FileText size={14} strokeWidth={1.75} />
540
+ {node.name}
541
+ </button>
542
+ </div>
543
+ {:else}
544
+ <!-- Component with examples -->
545
+ <div class="sdocs-component" style="--depth: {depth}">
546
+ <button
547
+ type="button"
548
+ class="sdocs-component-btn"
549
+ onclick={() => {
550
+ const wasExpanded = isFolderExpanded(nodePath);
551
+
552
+ if (wasExpanded) {
553
+ // Collapse: add to explicitly collapsed folders
554
+ collapsedFolders.add(nodePath);
555
+ collapsedFolders = new Set(collapsedFolders);
556
+ expandedFolders.delete(nodePath);
557
+ expandedFolders = new Set(expandedFolders);
558
+ } else {
559
+ // Expand: remove from collapsed, add to expanded, select docs
560
+ collapsedFolders.delete(nodePath);
561
+ collapsedFolders = new Set(collapsedFolders);
562
+ expandedFolders.add(nodePath);
563
+ expandedFolders = new Set(expandedFolders);
564
+ if (node.file) {
565
+ selectDocs(node.file);
566
+ }
567
+ }
568
+ }}
569
+ >
570
+ <span class="sdocs-component-icon">
571
+ <Component size={14} strokeWidth={1.75} />
572
+ </span>
573
+ <span class="sdocs-item-name">{node.name}</span>
574
+ <span class="sdocs-folder-icon" class:expanded>
575
+ <ChevronRight size={14} strokeWidth={2} />
576
+ </span>
577
+ </button>
578
+ {#if expanded}
579
+ <ul class="sdocs-story-list">
580
+ <li>
581
+ <button
582
+ type="button"
583
+ class="sdocs-story-btn sdocs-docs-btn"
584
+ class:active={activeFile?.path === node.file.path && viewMode === 'docs'}
585
+ onclick={() => node.file && selectDocs(node.file)}
586
+ >
587
+ <FileText size={14} strokeWidth={1.75} />
588
+ Docs
589
+ </button>
590
+ </li>
591
+ {#each node.file.examples.filter(e => e.name !== 'Default') as example (example.name)}
592
+ <li>
593
+ <button
594
+ type="button"
595
+ class="sdocs-story-btn"
596
+ class:active={activeFile?.path === node.file?.path && selectedExample?.name === example.name && viewMode === 'story'}
597
+ onclick={() => node.file && selectExample(node.file, example)}
598
+ >
599
+ <Bookmark size={14} strokeWidth={1.75} />
600
+ {example.name}
601
+ </button>
602
+ </li>
603
+ {/each}
604
+ </ul>
605
+ {/if}
606
+ </div>
607
+ {/if}
608
+ {/if}
609
+ {/snippet}
610
+
611
+ <div class="sdocs">
612
+ <aside class="sdocs-sidebar">
613
+ <button type="button" class="sdocs-title" onclick={goHome}>sdocs</button>
614
+ <nav class="sdocs-nav">
615
+ {#each navTree as node (node.name)}
616
+ {@render renderNavNode(node, 0, '')}
617
+ {/each}
618
+ </nav>
619
+ </aside>
620
+
621
+ <main class="sdocs-main">
622
+ {#if !activeFile}
623
+ <!-- Home view -->
624
+ <Home {componentCount} {pageCount} />
625
+ {:else if activeFile.type === 'page' && viewMode === 'docs'}
626
+ <!-- Markdown doc view -->
627
+ {@const DocComponent = activeFile.module.default as import('svelte').Component}
628
+ <div class="sdocs-docs-view sdocs-markdown">
629
+ <DocComponent />
630
+ </div>
631
+ {:else if viewMode === 'docs'}
632
+ <!-- Component docs view -->
633
+ {@const defaultExample = activeFile.examples.find(s => s.name === 'Default')}
634
+ {@const otherExamples = activeFile.examples.filter(s => s.name !== 'Default')}
635
+ <div class="sdocs-docs-view">
636
+ <div class="sdocs-docs-header">
637
+ <h2 class="sdocs-docs-title">{getDisplayName(activeFile.meta.title)}</h2>
638
+ </div>
639
+ {#if activeFile.meta.description}
640
+ <p class="sdocs-docs-description">{activeFile.meta.description}</p>
641
+ {/if}
642
+
643
+ <!-- Component Showcase (preview + controls + props) -->
644
+ {#if defaultExample}
645
+ <Showcase
646
+ render={defaultExample.render}
647
+ args={argsSnapshot}
648
+ argTypes={currentArgTypes}
649
+ cssProps={activeFile.meta.cssProps}
650
+ methods={activeFile.meta.methods}
651
+ onchange={handleArgsChange}
652
+ source={defaultExample.source ? expandArgsInSource(defaultExample.source, argsSnapshot) : undefined}
653
+ />
654
+ {:else}
655
+ <ComponentPreview padding="l">
656
+ <p class="sdocs-no-default">No default example provided</p>
657
+ </ComponentPreview>
658
+ {/if}
659
+
660
+ <!-- Markdown content for .svx files (client-only to avoid hydration mismatch) -->
661
+ {#if activeFile.isMarkdown && browser}
662
+ {@const DocComponent = activeFile.module.default as import('svelte').Component}
663
+ <section class="sdocs-section sdocs-markdown">
664
+ <DocComponent />
665
+ </section>
666
+ {/if}
667
+
668
+ <!-- Examples section (excludes Default) -->
669
+ {#if otherExamples.length > 0}
670
+ <section class="sdocs-section">
671
+ <h3 class="sdocs-section-title">Examples</h3>
672
+ <div class="sdocs-examples-grid">
673
+ {#each otherExamples as example (example.name)}
674
+ <div class="sdocs-example-card">
675
+ <div class="sdocs-example-card-header">
676
+ <span class="sdocs-example-card-name">{example.name}</span>
677
+ <button
678
+ type="button"
679
+ class="sdocs-link-btn"
680
+ onclick={() => activeFile && selectExample(activeFile, example)}
681
+ >
682
+ Open
683
+ </button>
684
+ </div>
685
+ <ComponentPreview bordered={false}>
686
+ {@render example.render(argsSnapshot)}
687
+ </ComponentPreview>
688
+ {#if example.source}
689
+ <CollapsiblePanel title="Code" open={false} no_content_padding>
690
+ <CodeBlock code={example.source} />
691
+ </CollapsiblePanel>
692
+ {/if}
693
+ </div>
694
+ {/each}
695
+ </div>
696
+ </section>
697
+ {/if}
698
+ </div>
699
+ {:else if selectedExample && viewMode === 'story'}
700
+ <!-- Single example view -->
701
+ <div class="sdocs-example-view">
702
+ <div class="sdocs-example-header">
703
+ <h2 class="sdocs-example-title">{selectedExample.name}</h2>
704
+ <button type="button" class="sdocs-code-btn" onclick={toggleCode}>
705
+ {showCode ? 'Hide Code' : 'Show Code'}
706
+ </button>
707
+ </div>
708
+
709
+ <ComponentPreview padding="l">
710
+ {@render selectedExample.render(argsSnapshot)}
711
+ </ComponentPreview>
712
+
713
+ {#if showCode && selectedExample?.source}
714
+ <CodeBlock code={expandArgsInSource(selectedExample.source, argsSnapshot)} />
715
+ {:else if showCode}
716
+ <CodeBlock code="Source code not available" />
717
+ {/if}
718
+ </div>
719
+ {/if}
720
+ </main>
721
+ </div>
722
+
723
+ <style>
724
+ /* Reset body margin when Sdocs is used */
725
+ :global(body:has(.sdocs)) {
726
+ margin: 0;
727
+ padding: 0;
728
+ }
729
+
730
+ /* Reset all inherited styles for isolation */
731
+ .sdocs,
732
+ .sdocs *,
733
+ .sdocs *::before,
734
+ .sdocs *::after {
735
+ all: revert;
736
+ box-sizing: border-box;
737
+ }
738
+
739
+ .sdocs {
740
+ display: grid;
741
+ grid-template-columns: 260px 1fr;
742
+ height: 100vh;
743
+ font-family: var(--font-sans);
744
+ background: var(--color-bg);
745
+ color: var(--color-text);
746
+ line-height: 1.5;
747
+ -webkit-font-smoothing: antialiased;
748
+ }
749
+
750
+ .sdocs-sidebar {
751
+ background: var(--color-bg-elevated);
752
+ color: var(--color-text);
753
+ padding: 0;
754
+ overflow-y: auto;
755
+ border-right: 1px solid var(--color-border);
756
+ display: flex;
757
+ flex-direction: column;
758
+ }
759
+
760
+ .sdocs-title {
761
+ font-size: 18px;
762
+ font-weight: 700;
763
+ margin: 0;
764
+ padding: 16px 20px;
765
+ color: var(--color-text);
766
+ display: flex;
767
+ align-items: center;
768
+ gap: 8px;
769
+ border: none;
770
+ border-bottom: 1px solid var(--color-border-subtle);
771
+ background: linear-gradient(to bottom, var(--color-bg-elevated), var(--base-50));
772
+ cursor: pointer;
773
+ width: 100%;
774
+ text-align: left;
775
+ transition: background 0.15s ease;
776
+ }
777
+
778
+ .sdocs-title:hover {
779
+ background: linear-gradient(to bottom, var(--base-50), var(--base-100));
780
+ }
781
+
782
+ .sdocs-nav {
783
+ display: flex;
784
+ flex-direction: column;
785
+ gap: 2px;
786
+ padding: 16px 12px;
787
+ flex: 1;
788
+ }
789
+
790
+ .sdocs-folder {
791
+ margin-left: calc(var(--depth, 0) * 12px);
792
+ }
793
+
794
+ .sdocs-folder-btn {
795
+ display: flex;
796
+ align-items: center;
797
+ gap: 6px;
798
+ width: 100%;
799
+ height: 28px;
800
+ text-align: left;
801
+ background: none;
802
+ border: none;
803
+ color: var(--color-text-secondary);
804
+ padding: 0 8px;
805
+ border-radius: 6px;
806
+ cursor: pointer;
807
+ font-size: 14px;
808
+ font-weight: 600;
809
+ transition: all 0.15s ease;
810
+ }
811
+
812
+ .sdocs-folder-btn:hover {
813
+ background: var(--color-bg-hover);
814
+ color: var(--color-text);
815
+ }
816
+
817
+ .sdocs-folder-btn :global(svg:not(:first-child)) {
818
+ color: var(--base-500);
819
+ }
820
+
821
+ .sdocs-item-name {
822
+ flex: 1;
823
+ text-align: left;
824
+ }
825
+
826
+ .sdocs-folder-icon {
827
+ display: flex;
828
+ align-items: center;
829
+ color: var(--color-text-muted);
830
+ transition: transform 0.15s ease;
831
+ margin-left: auto;
832
+ }
833
+
834
+ .sdocs-folder-icon.expanded {
835
+ transform: rotate(90deg);
836
+ }
837
+
838
+ .sdocs-folder-children {
839
+ margin-top: 2px;
840
+ }
841
+
842
+ .sdocs-component {
843
+ margin-left: calc(var(--depth, 0) * 12px);
844
+ margin-bottom: 4px;
845
+ }
846
+
847
+ .sdocs-component-btn {
848
+ display: flex;
849
+ align-items: center;
850
+ gap: 6px;
851
+ width: 100%;
852
+ height: 28px;
853
+ text-align: left;
854
+ background: none;
855
+ border: none;
856
+ font-size: 14px;
857
+ font-weight: 600;
858
+ color: var(--color-text);
859
+ padding: 0 8px;
860
+ border-radius: 6px;
861
+ cursor: pointer;
862
+ transition: all 0.15s ease;
863
+ }
864
+
865
+ .sdocs-component-btn:hover {
866
+ background: var(--color-bg-hover);
867
+ }
868
+
869
+ .sdocs-component-icon {
870
+ display: flex;
871
+ color: var(--pink-500);
872
+ }
873
+
874
+ /* Target the component icon SVG */
875
+ :global(.sdocs-component-btn > svg:first-child) {
876
+ color: var(--pink-500);
877
+ }
878
+
879
+ .sdocs-doc-item {
880
+ margin-left: calc(var(--depth, 0) * 12px);
881
+ margin-bottom: 4px;
882
+ }
883
+
884
+ .sdocs-doc-btn {
885
+ display: flex;
886
+ align-items: center;
887
+ gap: 8px;
888
+ width: 100%;
889
+ height: 28px;
890
+ text-align: left;
891
+ background: none;
892
+ border: none;
893
+ font-size: 14px;
894
+ font-weight: 500;
895
+ color: var(--color-text-secondary);
896
+ padding: 0 8px;
897
+ border-radius: 6px;
898
+ cursor: pointer;
899
+ transition: all 0.15s ease;
900
+ }
901
+
902
+ .sdocs-doc-btn:hover {
903
+ background: var(--color-bg-hover);
904
+ color: var(--color-text);
905
+ }
906
+
907
+ .sdocs-doc-btn.active {
908
+ background: var(--focus-100);
909
+ color: var(--color-text);
910
+ font-weight: 600;
911
+ }
912
+
913
+ .sdocs-doc-btn :global(svg) {
914
+ color: var(--focus-500);
915
+ }
916
+
917
+ .sdocs-story-list {
918
+ list-style: none;
919
+ margin: 0;
920
+ padding: 0;
921
+ padding-left: calc(14px + 8px); /* icon width + gap */
922
+ }
923
+
924
+ .sdocs-story-list li {
925
+ margin-bottom: 2px;
926
+ }
927
+
928
+ .sdocs-story-btn {
929
+ display: flex;
930
+ align-items: center;
931
+ gap: 8px;
932
+ width: 100%;
933
+ height: 28px;
934
+ text-align: left;
935
+ background: none;
936
+ border: none;
937
+ color: var(--color-text-secondary);
938
+ padding: 0 12px;
939
+ border-radius: 6px;
940
+ cursor: pointer;
941
+ font-size: 14px;
942
+ transition: all 0.15s ease;
943
+ }
944
+
945
+ .sdocs-docs-btn {
946
+ color: var(--color-text);
947
+ }
948
+
949
+ .sdocs-docs-btn :global(svg) {
950
+ color: var(--warning-500);
951
+ }
952
+
953
+ .sdocs-story-btn:not(.sdocs-docs-btn) :global(svg) {
954
+ color: var(--sky-500);
955
+ }
956
+
957
+ .sdocs-story-btn:hover {
958
+ background: var(--color-bg-hover);
959
+ color: var(--color-text);
960
+ }
961
+
962
+ .sdocs-story-btn.active {
963
+ background: var(--focus-100);
964
+ color: var(--color-text);
965
+ font-weight: 500;
966
+ }
967
+
968
+ .sdocs-docs-btn.active {
969
+ font-weight: 500;
970
+ }
971
+
972
+ .sdocs-main {
973
+ background: var(--color-bg);
974
+ overflow: auto;
975
+ padding: 24px 32px;
976
+ }
977
+
978
+ /* Docs view */
979
+ .sdocs-docs-view {
980
+ max-width: 860px;
981
+ display: flex;
982
+ flex-direction: column;
983
+ gap: 24px;
984
+ }
985
+
986
+ .sdocs-docs-header {
987
+ display: flex;
988
+ justify-content: space-between;
989
+ align-items: center;
990
+ }
991
+
992
+ .sdocs-docs-title {
993
+ font-size: 30px;
994
+ font-weight: 700;
995
+ margin: 0;
996
+ color: var(--color-text);
997
+ letter-spacing: -0.02em;
998
+ }
999
+
1000
+ .sdocs-docs-description {
1001
+ font-size: 16px;
1002
+ color: var(--color-text-secondary);
1003
+ margin: -16px 0 0;
1004
+ line-height: 1.5;
1005
+ }
1006
+
1007
+ .sdocs-section {
1008
+ display: flex;
1009
+ flex-direction: column;
1010
+ gap: 12px;
1011
+ }
1012
+
1013
+ .sdocs-section-title {
1014
+ font-size: 12px;
1015
+ font-weight: 600;
1016
+ color: var(--color-text-muted);
1017
+ margin: 0;
1018
+ text-transform: uppercase;
1019
+ letter-spacing: 0.08em;
1020
+ }
1021
+
1022
+ /* Examples list */
1023
+ .sdocs-examples-grid {
1024
+ display: flex;
1025
+ flex-direction: column;
1026
+ gap: 8px;
1027
+ }
1028
+
1029
+ .sdocs-example-card {
1030
+ display: flex;
1031
+ flex-direction: column;
1032
+ gap: 1px;
1033
+ background: var(--color-border);
1034
+ border: 1px solid var(--color-border);
1035
+ border-radius: 8px;
1036
+ overflow: hidden;
1037
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
1038
+ }
1039
+
1040
+ .sdocs-example-card-header {
1041
+ display: flex;
1042
+ justify-content: space-between;
1043
+ align-items: center;
1044
+ padding: 12px 12px 12px 16px;
1045
+ background: var(--color-bg);
1046
+ }
1047
+
1048
+ .sdocs-example-card-name {
1049
+ font-weight: 600;
1050
+ font-size: 14px;
1051
+ color: var(--color-text);
1052
+ }
1053
+
1054
+ .sdocs-link-btn {
1055
+ background: none;
1056
+ border: 1px solid var(--color-border);
1057
+ color: var(--color-text-secondary);
1058
+ font-size: 12px;
1059
+ font-weight: 500;
1060
+ cursor: pointer;
1061
+ padding: 6px 12px;
1062
+ border-radius: 6px;
1063
+ transition: all 0.15s ease;
1064
+ }
1065
+
1066
+ .sdocs-link-btn:hover {
1067
+ background: var(--color-primary);
1068
+ border-color: var(--color-primary);
1069
+ color: var(--base-0);
1070
+ }
1071
+
1072
+ /* Example view */
1073
+ .sdocs-example-view {
1074
+ max-width: 860px;
1075
+ }
1076
+
1077
+ .sdocs-example-header {
1078
+ display: flex;
1079
+ justify-content: space-between;
1080
+ align-items: center;
1081
+ margin-bottom: 24px;
1082
+ }
1083
+
1084
+ .sdocs-example-title {
1085
+ font-size: 24px;
1086
+ font-weight: 700;
1087
+ margin: 0;
1088
+ color: var(--color-text);
1089
+ letter-spacing: -0.01em;
1090
+ }
1091
+
1092
+ .sdocs-code-btn {
1093
+ background: var(--color-bg-elevated);
1094
+ border: 1px solid var(--color-border);
1095
+ color: var(--color-text-secondary);
1096
+ font-size: 12px;
1097
+ font-weight: 500;
1098
+ padding: 8px 16px;
1099
+ border-radius: 6px;
1100
+ cursor: pointer;
1101
+ transition: all 0.15s ease;
1102
+ }
1103
+
1104
+ .sdocs-code-btn:hover {
1105
+ background: var(--color-bg-hover);
1106
+ border-color: var(--base-300);
1107
+ }
1108
+
1109
+ .sdocs-no-default {
1110
+ color: var(--color-text-muted);
1111
+ font-style: italic;
1112
+ margin: 0;
1113
+ }
1114
+
1115
+ /* Markdown content styles */
1116
+ .sdocs-markdown {
1117
+ max-width: 720px;
1118
+ }
1119
+
1120
+ .sdocs-markdown :global(h1) {
1121
+ font-size: 30px;
1122
+ font-weight: 700;
1123
+ color: var(--color-text);
1124
+ margin: 0 0 24px;
1125
+ letter-spacing: -0.02em;
1126
+ }
1127
+
1128
+ .sdocs-markdown :global(h2) {
1129
+ font-size: 24px;
1130
+ font-weight: 600;
1131
+ color: var(--color-text);
1132
+ margin: 40px 0 16px;
1133
+ letter-spacing: -0.01em;
1134
+ }
1135
+
1136
+ .sdocs-markdown :global(h3) {
1137
+ font-size: 20px;
1138
+ font-weight: 600;
1139
+ color: var(--color-text);
1140
+ margin: 32px 0 12px;
1141
+ }
1142
+
1143
+ .sdocs-markdown :global(p) {
1144
+ font-size: 16px;
1145
+ color: var(--color-text-secondary);
1146
+ line-height: 1.75;
1147
+ margin: 0 0 20px;
1148
+ }
1149
+
1150
+ .sdocs-markdown :global(ul),
1151
+ .sdocs-markdown :global(ol) {
1152
+ margin: 0 0 20px;
1153
+ padding-left: 24px;
1154
+ color: var(--color-text-secondary);
1155
+ }
1156
+
1157
+ .sdocs-markdown :global(li) {
1158
+ line-height: 1.75;
1159
+ margin-bottom: 8px;
1160
+ }
1161
+
1162
+ .sdocs-markdown :global(code) {
1163
+ font-family: var(--font-mono);
1164
+ font-size: 0.875em;
1165
+ background: var(--color-bg-hover);
1166
+ padding: 0.2em 0.4em;
1167
+ border-radius: 4px;
1168
+ color: var(--color-text);
1169
+ }
1170
+
1171
+ .sdocs-markdown :global(pre) {
1172
+ background: var(--base-50);
1173
+ border-radius: 10px;
1174
+ border: 1px solid var(--base-200);
1175
+ padding: 16px 20px;
1176
+ overflow-x: auto;
1177
+ margin: 0 0 24px;
1178
+ }
1179
+
1180
+ .sdocs-markdown :global(pre code) {
1181
+ background: none;
1182
+ padding: 0;
1183
+ color: var(--base-800);
1184
+ font-size: 13px;
1185
+ line-height: 1.6;
1186
+ white-space: pre;
1187
+ }
1188
+
1189
+ .sdocs-markdown :global(blockquote) {
1190
+ border-left: 3px solid var(--color-primary);
1191
+ margin: 0 0 24px;
1192
+ padding: 8px 0 8px 20px;
1193
+ color: var(--color-text-secondary);
1194
+ }
1195
+
1196
+ .sdocs-markdown :global(a) {
1197
+ color: var(--color-primary);
1198
+ text-decoration: none;
1199
+ }
1200
+
1201
+ .sdocs-markdown :global(a:hover) {
1202
+ text-decoration: underline;
1203
+ }
1204
+
1205
+ .sdocs-markdown :global(hr) {
1206
+ border: none;
1207
+ border-top: 1px solid var(--color-border);
1208
+ margin: 32px 0;
1209
+ }
1210
+ </style>