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.
- package/README.md +43 -0
- package/bin/sdocs.js +2 -0
- package/dist/Sdocs.svelte +1210 -0
- package/dist/Sdocs.svelte.d.ts +5 -0
- package/dist/cli/app-plugin.d.ts +7 -0
- package/dist/cli/app-plugin.js +69 -0
- package/dist/cli/config.d.ts +12 -0
- package/dist/cli/config.js +34 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +72 -0
- package/dist/cli/server.d.ts +2 -0
- package/dist/cli/server.js +62 -0
- package/dist/docgen.d.ts +47 -0
- package/dist/docgen.js +463 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/internal/ComponentPreview.svelte +58 -0
- package/dist/internal/ComponentPreview.svelte.d.ts +17 -0
- package/dist/internal/CssPropsTable.svelte +239 -0
- package/dist/internal/CssPropsTable.svelte.d.ts +11 -0
- package/dist/internal/Home.svelte +92 -0
- package/dist/internal/Home.svelte.d.ts +9 -0
- package/dist/internal/MethodsTable.svelte +72 -0
- package/dist/internal/MethodsTable.svelte.d.ts +7 -0
- package/dist/internal/PropsTable.svelte +342 -0
- package/dist/internal/PropsTable.svelte.d.ts +12 -0
- package/dist/internal/Showcase.svelte +130 -0
- package/dist/internal/Showcase.svelte.d.ts +21 -0
- package/dist/types.d.ts +162 -0
- package/dist/types.js +1 -0
- package/dist/ui/Badge/Badge.docs.svelte +46 -0
- package/dist/ui/Badge/Badge.docs.svelte.d.ts +26 -0
- package/dist/ui/Badge/Badge.svelte +59 -0
- package/dist/ui/Badge/Badge.svelte.d.ts +17 -0
- package/dist/ui/Badge/index.d.ts +1 -0
- package/dist/ui/Badge/index.js +1 -0
- package/dist/ui/Checkbox/Checkbox.docs.svelte +51 -0
- package/dist/ui/Checkbox/Checkbox.docs.svelte.d.ts +27 -0
- package/dist/ui/Checkbox/Checkbox.svelte +169 -0
- package/dist/ui/Checkbox/Checkbox.svelte.d.ts +18 -0
- package/dist/ui/Checkbox/index.d.ts +1 -0
- package/dist/ui/Checkbox/index.js +1 -0
- package/dist/ui/CodeBlock/CodeBlock.docs.svelte +28 -0
- package/dist/ui/CodeBlock/CodeBlock.docs.svelte.d.ts +24 -0
- package/dist/ui/CodeBlock/CodeBlock.svelte +101 -0
- package/dist/ui/CodeBlock/CodeBlock.svelte.d.ts +7 -0
- package/dist/ui/CodeBlock/index.d.ts +1 -0
- package/dist/ui/CodeBlock/index.js +1 -0
- package/dist/ui/Frame/Frame.docs.svelte +140 -0
- package/dist/ui/Frame/Frame.docs.svelte.d.ts +26 -0
- package/dist/ui/Frame/Frame.svelte +88 -0
- package/dist/ui/Frame/Frame.svelte.d.ts +15 -0
- package/dist/ui/Frame/index.d.ts +1 -0
- package/dist/ui/Frame/index.js +1 -0
- package/dist/ui/InputNumber/InputNumber.docs.svelte +50 -0
- package/dist/ui/InputNumber/InputNumber.docs.svelte.d.ts +26 -0
- package/dist/ui/InputNumber/InputNumber.svelte +275 -0
- package/dist/ui/InputNumber/InputNumber.svelte.d.ts +26 -0
- package/dist/ui/InputNumber/index.d.ts +1 -0
- package/dist/ui/InputNumber/index.js +1 -0
- package/dist/ui/InputText/InputText.docs.svelte +43 -0
- package/dist/ui/InputText/InputText.docs.svelte.d.ts +26 -0
- package/dist/ui/InputText/InputText.svelte +116 -0
- package/dist/ui/InputText/InputText.svelte.d.ts +22 -0
- package/dist/ui/InputText/index.d.ts +1 -0
- package/dist/ui/InputText/index.js +1 -0
- package/dist/ui/Panel/CollapsiblePanel.docs.svelte +45 -0
- package/dist/ui/Panel/CollapsiblePanel.docs.svelte.d.ts +25 -0
- package/dist/ui/Panel/CollapsiblePanel.svelte +93 -0
- package/dist/ui/Panel/CollapsiblePanel.svelte.d.ts +14 -0
- package/dist/ui/Panel/index.d.ts +1 -0
- package/dist/ui/Panel/index.js +1 -0
- package/dist/ui/Placeholder/Placeholder.docs.svelte +49 -0
- package/dist/ui/Placeholder/Placeholder.docs.svelte.d.ts +26 -0
- package/dist/ui/Placeholder/Placeholder.svelte +99 -0
- package/dist/ui/Placeholder/Placeholder.svelte.d.ts +21 -0
- package/dist/ui/Placeholder/index.d.ts +1 -0
- package/dist/ui/Placeholder/index.js +1 -0
- package/dist/ui/Radio/Radio.docs.svelte +67 -0
- package/dist/ui/Radio/Radio.docs.svelte.d.ts +27 -0
- package/dist/ui/Radio/Radio.svelte +165 -0
- package/dist/ui/Radio/Radio.svelte.d.ts +22 -0
- package/dist/ui/Radio/RadioGroup.docs.svelte +70 -0
- package/dist/ui/Radio/RadioGroup.docs.svelte.d.ts +27 -0
- package/dist/ui/Radio/RadioGroup.svelte +98 -0
- package/dist/ui/Radio/RadioGroup.svelte.d.ts +27 -0
- package/dist/ui/Radio/index.d.ts +2 -0
- package/dist/ui/Radio/index.js +2 -0
- package/dist/ui/SegmentControl/SegmentControl.docs.svelte +54 -0
- package/dist/ui/SegmentControl/SegmentControl.docs.svelte.d.ts +25 -0
- package/dist/ui/SegmentControl/SegmentControl.svelte +120 -0
- package/dist/ui/SegmentControl/SegmentControl.svelte.d.ts +18 -0
- package/dist/ui/SegmentControl/index.d.ts +1 -0
- package/dist/ui/SegmentControl/index.js +1 -0
- package/dist/ui/Stack/Stack.docs.svelte +63 -0
- package/dist/ui/Stack/Stack.docs.svelte.d.ts +26 -0
- package/dist/ui/Stack/Stack.svelte +45 -0
- package/dist/ui/Stack/Stack.svelte.d.ts +19 -0
- package/dist/ui/Stack/index.d.ts +1 -0
- package/dist/ui/Stack/index.js +1 -0
- package/dist/ui/Table/Body.svelte +17 -0
- package/dist/ui/Table/Body.svelte.d.ts +11 -0
- package/dist/ui/Table/Caption.svelte +17 -0
- package/dist/ui/Table/Caption.svelte.d.ts +11 -0
- package/dist/ui/Table/Cell.svelte +24 -0
- package/dist/ui/Table/Cell.svelte.d.ts +15 -0
- package/dist/ui/Table/Foot.svelte +17 -0
- package/dist/ui/Table/Foot.svelte.d.ts +11 -0
- package/dist/ui/Table/Head.svelte +17 -0
- package/dist/ui/Table/Head.svelte.d.ts +11 -0
- package/dist/ui/Table/Header.svelte +27 -0
- package/dist/ui/Table/Header.svelte.d.ts +17 -0
- package/dist/ui/Table/Row.svelte +19 -0
- package/dist/ui/Table/Row.svelte.d.ts +13 -0
- package/dist/ui/Table/Table.docs.svelte +197 -0
- package/dist/ui/Table/Table.docs.svelte.d.ts +28 -0
- package/dist/ui/Table/Table.svelte +140 -0
- package/dist/ui/Table/Table.svelte.d.ts +27 -0
- package/dist/ui/Table/index.js +10 -0
- package/dist/ui/css/colors.css +377 -0
- package/dist/ui/css/global.css +10 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +12 -0
- package/dist/virtual-sdocs.d.ts +20 -0
- package/dist/vite-plugin.d.ts +18 -0
- package/dist/vite-plugin.js +206 -0
- package/dist/vite.d.ts +2 -0
- package/dist/vite.js +2 -0
- 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>
|