sdocs 0.0.15 → 0.0.17

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 (133) hide show
  1. package/bin/sdocs.js +1 -1
  2. package/dist/client/App.svelte +16 -39
  3. package/dist/client/App.svelte.d.ts +1 -1
  4. package/dist/client/favicon.png +0 -0
  5. package/dist/client/tree-builder.js +57 -14
  6. package/dist/client/{CollapsiblePanel.svelte → views/CollapsiblePanel.svelte} +5 -5
  7. package/dist/client/{ComponentView.svelte → views/ComponentView.svelte} +39 -13
  8. package/dist/client/{ComponentView.svelte.d.ts → views/ComponentView.svelte.d.ts} +1 -1
  9. package/dist/client/{ControlsPanel.svelte → views/ControlsPanel.svelte} +10 -15
  10. package/dist/client/{ControlsPanel.svelte.d.ts → views/ControlsPanel.svelte.d.ts} +1 -1
  11. package/dist/client/{DataTable.svelte → views/DataTable.svelte} +8 -8
  12. package/dist/client/{HomePage.svelte → views/HomePage.svelte} +10 -8
  13. package/dist/client/{HomePage.svelte.d.ts → views/HomePage.svelte.d.ts} +1 -1
  14. package/dist/client/{LayoutView.svelte → views/LayoutView.svelte} +1 -1
  15. package/dist/client/{LayoutView.svelte.d.ts → views/LayoutView.svelte.d.ts} +1 -1
  16. package/dist/client/{PageView.svelte → views/PageView.svelte} +7 -7
  17. package/dist/client/{PageView.svelte.d.ts → views/PageView.svelte.d.ts} +1 -1
  18. package/dist/client/{PreviewFrame.svelte → views/PreviewFrame.svelte} +10 -3
  19. package/dist/client/views/Sidebar.svelte +369 -0
  20. package/dist/client/{Sidebar.svelte.d.ts → views/Sidebar.svelte.d.ts} +2 -2
  21. package/dist/commands/build.js +2 -2
  22. package/dist/commands/dev.js +2 -2
  23. package/dist/commands/init.js +4 -4
  24. package/dist/commands/preview.js +1 -1
  25. package/dist/{app-gen.d.ts → server/app-gen.d.ts} +1 -1
  26. package/dist/{app-gen.js → server/app-gen.js} +4 -4
  27. package/dist/{cli.js → server/cli.js} +4 -4
  28. package/dist/{config.d.ts → server/config.d.ts} +1 -1
  29. package/dist/{config.js → server/config.js} +7 -2
  30. package/dist/server/discovery.d.ts +1 -1
  31. package/dist/server/discovery.js +1 -1
  32. package/dist/server/meta-parser.js +1 -1
  33. package/dist/server/prop-parser.js +12 -4
  34. package/dist/server/snippet-compiler.js +8 -0
  35. package/dist/server/snippet-extractor.d.ts +1 -1
  36. package/dist/server/snippet-extractor.js +40 -5
  37. package/dist/server/toc-extractor.d.ts +0 -2
  38. package/dist/server/toc-extractor.js +0 -14
  39. package/dist/types.d.ts +3 -3
  40. package/dist/ui/About.page.sdoc +37 -0
  41. package/dist/ui/Button/Button.sdoc +51 -0
  42. package/dist/ui/Button/Button.svelte +90 -0
  43. package/dist/ui/Button/Button.svelte.d.ts +27 -0
  44. package/dist/ui/Button/index.d.ts +1 -0
  45. package/dist/ui/Button/index.js +1 -0
  46. package/dist/ui/Control/Checkbox.sdoc +17 -0
  47. package/dist/{client/controls/CheckboxControl.svelte → ui/Control/Checkbox.svelte} +1 -1
  48. package/dist/ui/Control/Checkbox.svelte.d.ts +8 -0
  49. package/dist/ui/Control/Color.sdoc +17 -0
  50. package/dist/{client/controls/ColorControl.svelte → ui/Control/Color.svelte} +4 -4
  51. package/dist/ui/Control/Color.svelte.d.ts +8 -0
  52. package/dist/ui/Control/Dimension.sdoc +17 -0
  53. package/dist/{client/controls/DimensionControl.svelte → ui/Control/Dimension.svelte} +5 -5
  54. package/dist/ui/Control/Dimension.svelte.d.ts +8 -0
  55. package/dist/ui/Control/Number.sdoc +17 -0
  56. package/dist/{client/controls/NumberControl.svelte → ui/Control/Number.svelte} +4 -4
  57. package/dist/ui/Control/Number.svelte.d.ts +8 -0
  58. package/dist/ui/Control/Select.sdoc +18 -0
  59. package/dist/{client/controls/SelectControl.svelte → ui/Control/Select.svelte} +4 -4
  60. package/dist/ui/Control/Select.svelte.d.ts +9 -0
  61. package/dist/ui/Control/Text.sdoc +17 -0
  62. package/dist/{client/controls/TextControl.svelte → ui/Control/Text.svelte} +4 -4
  63. package/dist/ui/Control/Text.svelte.d.ts +8 -0
  64. package/dist/ui/Control/index.d.ts +6 -0
  65. package/dist/ui/Control/index.js +6 -0
  66. package/dist/ui/Dashboard.layout.sdoc +60 -0
  67. package/dist/ui/Frame/Frame.sdoc +54 -0
  68. package/dist/ui/Frame/Frame.svelte +88 -0
  69. package/dist/ui/Frame/Frame.svelte.d.ts +15 -0
  70. package/dist/ui/Frame/index.d.ts +1 -0
  71. package/dist/ui/Frame/index.js +1 -0
  72. package/dist/ui/Icon/Icon.sdoc +45 -0
  73. package/dist/ui/Icon/Icon.svelte +73 -0
  74. package/dist/ui/Icon/Icon.svelte.d.ts +16 -0
  75. package/dist/ui/Icon/icons/bookmark.svg +1 -0
  76. package/dist/ui/Icon/icons/chevron-down.svg +1 -0
  77. package/dist/ui/Icon/icons/chevron-left.svg +1 -0
  78. package/dist/ui/Icon/icons/chevron-right.svg +1 -0
  79. package/dist/ui/Icon/icons/chevron-up.svg +1 -0
  80. package/dist/ui/Icon/icons/code.svg +1 -0
  81. package/dist/ui/Icon/icons/component.svg +1 -0
  82. package/dist/ui/Icon/icons/copy.svg +1 -0
  83. package/dist/ui/Icon/icons/diamond.svg +1 -0
  84. package/dist/ui/Icon/icons/file-code.svg +1 -0
  85. package/dist/ui/Icon/icons/file-text.svg +1 -0
  86. package/dist/ui/Icon/icons/folder-open.svg +1 -0
  87. package/dist/ui/Icon/icons/folder.svg +1 -0
  88. package/dist/ui/Icon/icons/panels-top-left.svg +1 -0
  89. package/dist/ui/Icon/icons/sdocs.svg +8 -0
  90. package/dist/ui/Icon/index.d.ts +1 -0
  91. package/dist/ui/Icon/index.js +1 -0
  92. package/dist/ui/NavTree/Group.sdoc +46 -0
  93. package/dist/ui/NavTree/Group.svelte +82 -0
  94. package/dist/ui/NavTree/Group.svelte.d.ts +11 -0
  95. package/dist/ui/NavTree/Item.sdoc +70 -0
  96. package/dist/ui/NavTree/Item.svelte +164 -0
  97. package/dist/ui/NavTree/Item.svelte.d.ts +33 -0
  98. package/dist/ui/NavTree/NavTree.sdoc +119 -0
  99. package/dist/ui/NavTree/NavTree.svelte +31 -0
  100. package/dist/ui/NavTree/NavTree.svelte.d.ts +8 -0
  101. package/dist/ui/NavTree/index.js +6 -0
  102. package/dist/ui/Stack/Stack.sdoc +44 -0
  103. package/dist/ui/Stack/Stack.svelte +41 -0
  104. package/dist/ui/Stack/Stack.svelte.d.ts +19 -0
  105. package/dist/ui/Stack/index.d.ts +1 -0
  106. package/dist/ui/Stack/index.js +1 -0
  107. package/dist/ui/index.d.ts +6 -0
  108. package/dist/ui/index.js +6 -0
  109. package/dist/{client/theme.css → ui/styles/fonts.css} +0 -37
  110. package/dist/ui/styles/theme.css +910 -0
  111. package/dist/vite.js +47 -51
  112. package/package.json +5 -2
  113. package/dist/client/Sidebar.svelte +0 -329
  114. package/dist/client/controls/CheckboxControl.svelte.d.ts +0 -8
  115. package/dist/client/controls/ColorControl.svelte.d.ts +0 -8
  116. package/dist/client/controls/DimensionControl.svelte.d.ts +0 -8
  117. package/dist/client/controls/NumberControl.svelte.d.ts +0 -8
  118. package/dist/client/controls/SelectControl.svelte.d.ts +0 -9
  119. package/dist/client/controls/TextControl.svelte.d.ts +0 -8
  120. package/dist/server/sdocx-parser.d.ts +0 -11
  121. package/dist/server/sdocx-parser.js +0 -197
  122. /package/dist/client/{CollapsiblePanel.svelte.d.ts → views/CollapsiblePanel.svelte.d.ts} +0 -0
  123. /package/dist/client/{DataTable.svelte.d.ts → views/DataTable.svelte.d.ts} +0 -0
  124. /package/dist/client/{PreviewFrame.svelte.d.ts → views/PreviewFrame.svelte.d.ts} +0 -0
  125. /package/dist/{cli.d.ts → server/cli.d.ts} +0 -0
  126. /package/dist/{client → ui/styles}/fonts/figtree-italic-latin-ext.woff2 +0 -0
  127. /package/dist/{client → ui/styles}/fonts/figtree-italic-latin.woff2 +0 -0
  128. /package/dist/{client → ui/styles}/fonts/figtree-latin-ext.woff2 +0 -0
  129. /package/dist/{client → ui/styles}/fonts/figtree-latin.woff2 +0 -0
  130. /package/dist/{client → ui/styles}/fonts/jetbrains-mono-italic-latin-ext.woff2 +0 -0
  131. /package/dist/{client → ui/styles}/fonts/jetbrains-mono-italic-latin.woff2 +0 -0
  132. /package/dist/{client → ui/styles}/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  133. /package/dist/{client → ui/styles}/fonts/jetbrains-mono-latin.woff2 +0 -0
@@ -12,6 +12,7 @@
12
12
  let { src, props = {}, cssVars = {}, activeStylesheet, fullHeight = false }: Props = $props();
13
13
  let iframe: HTMLIFrameElement;
14
14
  let ready = $state(false);
15
+ let contentHeight = $state(0);
15
16
 
16
17
  onMount(() => {
17
18
  function onMessage(e: MessageEvent) {
@@ -21,6 +22,9 @@
21
22
  sendCss();
22
23
  sendStylesheet();
23
24
  }
25
+ if (e.data?.type === 'sdocs:resize' && e.source === iframe?.contentWindow) {
26
+ contentHeight = e.data.height;
27
+ }
24
28
  }
25
29
  window.addEventListener('message', onMessage);
26
30
  return () => window.removeEventListener('message', onMessage);
@@ -72,15 +76,17 @@
72
76
  {src}
73
77
  title="Component preview"
74
78
  class="sdocs-iframe"
79
+ scrolling="no"
80
+ style:height={contentHeight ? `${contentHeight}px` : undefined}
75
81
  ></iframe>
76
82
  </div>
77
83
 
78
84
  <style>
79
85
  .sdocs-preview-frame {
80
- border: 1px solid var(--sdocs-border);
86
+ border: 1px solid var(--color-base-200);
81
87
  border-radius: 6px;
82
88
  overflow: hidden;
83
- background: var(--sdocs-bg);
89
+ background: var(--color-base-0);
84
90
  }
85
91
  .sdocs-preview-frame.full-height {
86
92
  flex: 1;
@@ -89,7 +95,8 @@
89
95
  }
90
96
  .sdocs-iframe {
91
97
  width: 100%;
92
- min-height: 120px;
98
+ max-height: 800px;
99
+ overflow-y: auto;
93
100
  border: none;
94
101
  display: block;
95
102
  }
@@ -0,0 +1,369 @@
1
+ <script lang="ts">
2
+ import { SvelteSet } from 'svelte/reactivity';
3
+ import type { TreeNode } from '../tree-builder.js';
4
+ import { pathToHash } from '../router.svelte.js';
5
+ import { Icon } from '../../ui/Icon/index.js';
6
+ import { NavTree } from '../../ui/index.js';
7
+
8
+ type ThemeMode = 'light' | 'dark';
9
+
10
+ interface Props {
11
+ tree: TreeNode[];
12
+ currentPath: string[];
13
+ logo: string;
14
+ cssNames?: string[];
15
+ activeStylesheet?: string;
16
+ theme?: ThemeMode;
17
+ onToggleFullscreen?: () => void;
18
+ onStylesheetChange?: (name: string) => void;
19
+ onThemeChange?: (theme: ThemeMode) => void;
20
+ }
21
+
22
+ let { tree, currentPath, logo, cssNames = [], activeStylesheet, theme = 'light', onToggleFullscreen, onStylesheetChange, onThemeChange }: Props = $props();
23
+
24
+ const themeIcons: Record<ThemeMode, string> = { light: '\u2600', dark: '\u263D' };
25
+ const themeLabels: Record<ThemeMode, string> = { light: 'Light', dark: 'Dark' };
26
+
27
+ function toggleTheme() {
28
+ onThemeChange?.(theme === 'light' ? 'dark' : 'light');
29
+ }
30
+ let searchQuery = $state('');
31
+
32
+ // Track expanded state by path key (SvelteSet for reactivity)
33
+ let expandedSet = new SvelteSet<string>();
34
+ let initialized = false;
35
+
36
+ // Initialize expanded state from tree defaults
37
+ $effect(() => {
38
+ if (!initialized && tree.length > 0) {
39
+ collectDefaults(tree, expandedSet);
40
+ initialized = true;
41
+ }
42
+ });
43
+
44
+ function collectDefaults(nodes: TreeNode[], set: SvelteSet<string>) {
45
+ for (const node of nodes) {
46
+ if (node.defaultExpanded || node.type === 'group') {
47
+ set.add(node.path.join('/'));
48
+ }
49
+ if (node.children.length > 0) {
50
+ collectDefaults(node.children, set);
51
+ }
52
+ }
53
+ }
54
+
55
+ function toggleExpanded(pathKey: string) {
56
+ if (expandedSet.has(pathKey)) {
57
+ expandedSet.delete(pathKey);
58
+ } else {
59
+ expandedSet.add(pathKey);
60
+ }
61
+ }
62
+
63
+ function isExpanded(pathKey: string): boolean {
64
+ if (searchQuery.trim()) return true; // Auto-expand when searching
65
+ return expandedSet.has(pathKey);
66
+ }
67
+
68
+ const filteredTree = $derived(
69
+ searchQuery.trim() ? filterTree(tree, searchQuery.trim().toLowerCase()) : tree,
70
+ );
71
+
72
+ function filterTree(nodes: TreeNode[], query: string): TreeNode[] {
73
+ const result: TreeNode[] = [];
74
+ for (const node of nodes) {
75
+ if (node.name.toLowerCase().includes(query)) {
76
+ result.push(node);
77
+ } else if (node.children.length > 0) {
78
+ const filtered = filterTree(node.children, query);
79
+ if (filtered.length > 0) {
80
+ result.push({ ...node, children: filtered });
81
+ }
82
+ }
83
+ }
84
+ return result;
85
+ }
86
+
87
+ function isActive(nodePath: string[]): boolean {
88
+ if (nodePath.length > currentPath.length) return false;
89
+ return nodePath.every((seg, i) => seg === currentPath[i]);
90
+ }
91
+
92
+ function isExactActive(nodePath: string[]): boolean {
93
+ return nodePath.length === currentPath.length && nodePath.every((seg, i) => seg === currentPath[i]);
94
+ }
95
+
96
+ function iconName(node: TreeNode, expanded: boolean): string {
97
+ switch (node.type) {
98
+ case 'folder': return expanded ? 'folder-open' : 'folder';
99
+ case 'component':
100
+ if (node.children.length > 0) {
101
+ const hasChildComponent = node.children.some(c => c.type === 'component' && c.children.length > 0);
102
+ return hasChildComponent ? 'component' : 'diamond';
103
+ }
104
+ if (node.name === 'Docs') return 'file-code';
105
+ return 'bookmark';
106
+ case 'page': return 'file-text';
107
+ case 'layout': return 'panels-top-left';
108
+ default: return 'file';
109
+ }
110
+ }
111
+
112
+ function iconColor(node: TreeNode): string {
113
+ switch (node.type) {
114
+ case 'folder': return 'var(--color-base-400)';
115
+ case 'component':
116
+ if (node.children.length > 0) return 'var(--color-component-500)';
117
+ if (node.name === 'Docs') return 'var(--color-docs-500)';
118
+ return 'var(--color-example-500)';
119
+ case 'page': return 'var(--color-page-550)';
120
+ case 'layout': return 'var(--color-layout-500)';
121
+ default: return 'inherit';
122
+ }
123
+ }
124
+
125
+ function expanderActiveColor(node: TreeNode): string {
126
+ switch (node.type) {
127
+ case 'component': return 'var(--color-component-400)';
128
+ case 'page': return 'var(--color-page-400)';
129
+ case 'layout': return 'var(--color-layout-400)';
130
+ default: return 'var(--color-base-400)';
131
+ }
132
+ }
133
+
134
+ function expanderHoverColor(node: TreeNode): string {
135
+ switch (node.type) {
136
+ case 'component': return 'var(--color-component-600)';
137
+ case 'page': return 'var(--color-page-600)';
138
+ case 'layout': return 'var(--color-layout-600)';
139
+ default: return 'var(--color-base-500)';
140
+ }
141
+ }
142
+
143
+ function hoverBg(node: TreeNode): string {
144
+ switch (node.type) {
145
+ case 'component':
146
+ if (node.children.length > 0) return 'var(--color-component-50)';
147
+ if (node.name === 'Docs') return 'var(--color-docs-50)';
148
+ return 'var(--color-example-50)';
149
+ case 'page': return 'var(--color-page-50)';
150
+ case 'layout': return 'var(--color-layout-50)';
151
+ default: return 'var(--color-base-100)';
152
+ }
153
+ }
154
+
155
+ function activeBg(node: TreeNode): string {
156
+ switch (node.type) {
157
+ case 'component':
158
+ if (node.children.length > 0) return 'var(--color-component-100)';
159
+ if (node.name === 'Docs') return 'var(--color-docs-100)';
160
+ return 'var(--color-example-100)';
161
+ case 'page': return 'var(--color-page-100)';
162
+ case 'layout': return 'var(--color-layout-100)';
163
+ default: return 'var(--color-base-100)';
164
+ }
165
+ }
166
+
167
+ function activeHoverBg(node: TreeNode): string {
168
+ switch (node.type) {
169
+ case 'component':
170
+ if (node.children.length > 0) return 'var(--color-component-150)';
171
+ if (node.name === 'Docs') return 'var(--color-docs-150)';
172
+ return 'var(--color-example-150)';
173
+ case 'page': return 'var(--color-page-150)';
174
+ case 'layout': return 'var(--color-layout-150)';
175
+ default: return 'var(--color-base-150)';
176
+ }
177
+ }
178
+
179
+ function leafWeight(node: TreeNode): string {
180
+ if (node.type === 'page' || node.type === 'layout') return '500';
181
+ return '400';
182
+ }
183
+ </script>
184
+
185
+ <aside class="sdocs-sidebar">
186
+ <div class="sdocs-sidebar-header">
187
+ <a href="#/" class="sdocs-logo">
188
+ <Icon name="sdocs" --w="22px" --h="22px" --fill="#FC1D29" />
189
+ {logo}
190
+ </a>
191
+ <div class="sdocs-header-actions">
192
+ {#if cssNames.length > 1}
193
+ <select
194
+ class="sdocs-css-picker"
195
+ value={activeStylesheet}
196
+ onchange={(e) => onStylesheetChange?.(e.currentTarget.value)}
197
+ >
198
+ {#each cssNames as name (name)}
199
+ <option value={name}>{name}</option>
200
+ {/each}
201
+ </select>
202
+ {/if}
203
+ <button class="sdocs-theme-btn" onclick={toggleTheme} title="{themeLabels[theme]} theme">
204
+ {themeIcons[theme]}
205
+ </button>
206
+ <button class="sdocs-fullscreen-btn" onclick={() => onToggleFullscreen?.()} title="Fullscreen">
207
+ &#x26F6;
208
+ </button>
209
+ </div>
210
+ </div>
211
+
212
+ <div class="sdocs-sidebar-search">
213
+ <input
214
+ type="text"
215
+ placeholder="Search..."
216
+ bind:value={searchQuery}
217
+ class="sdocs-search-input"
218
+ />
219
+ </div>
220
+
221
+ <nav class="sdocs-sidebar-tree">
222
+ {#each filteredTree as node (node.path.join('/'))}
223
+ {@render treeNode(node)}
224
+ {/each}
225
+ </nav>
226
+ </aside>
227
+
228
+ {#snippet treeNode(node: TreeNode)}
229
+ {@const pathKey = node.path.join('/')}
230
+ {@const expanded = isExpanded(pathKey)}
231
+
232
+ {#if node.type === 'group'}
233
+ <NavTree.Group label={node.name} {expanded} onclick={() => toggleExpanded(pathKey)}>
234
+ {#each node.children as child (child.path.join('/'))}
235
+ {@render treeNode(child)}
236
+ {/each}
237
+ </NavTree.Group>
238
+ {:else if node.children.length > 0}
239
+ <NavTree.Item
240
+ label={node.name}
241
+ {expanded}
242
+ active={isActive(node.path)}
243
+ onclick={() => {
244
+ const wasCollapsed = !expandedSet.has(pathKey);
245
+ toggleExpanded(pathKey);
246
+ if (wasCollapsed && node.type !== 'folder') {
247
+ window.location.hash = pathToHash(node.path);
248
+ }
249
+ }}
250
+ --font-weight="500"
251
+ --bg-hover={hoverBg(node)}
252
+ --bg-active={activeBg(node)}
253
+ --bg-active-hover={activeHoverBg(node)}
254
+ --r="4px"
255
+ --expander-color-active={expanderActiveColor(node)}
256
+ --expander-color-hover={expanderHoverColor(node)}
257
+ >
258
+ {#snippet left()}<Icon name={iconName(node, expanded)} --w="14px" --h="14px" --fill={iconColor(node)} />{/snippet}
259
+ {#each node.children as child (child.path.join('/'))}
260
+ {@render treeNode(child)}
261
+ {/each}
262
+ </NavTree.Item>
263
+ {:else}
264
+ <NavTree.Item
265
+ label={node.name}
266
+ href={pathToHash(node.path)}
267
+ active={isExactActive(node.path)}
268
+ --font-weight={leafWeight(node)}
269
+ --bg-hover={hoverBg(node)}
270
+ --bg-active={activeBg(node)}
271
+ --bg-active-hover={activeHoverBg(node)}
272
+ --r="4px"
273
+ >
274
+ {#snippet left()}<Icon name={iconName(node, false)} --w="14px" --h="14px" --fill={iconColor(node)} />{/snippet}
275
+ </NavTree.Item>
276
+ {/if}
277
+ {/snippet}
278
+
279
+ <style>
280
+ .sdocs-sidebar {
281
+ width: 260px;
282
+ height: 100vh;
283
+ overflow-y: auto;
284
+ border-right: 1px solid var(--color-base-200);
285
+ background: var(--color-base-0);
286
+ display: flex;
287
+ flex-direction: column;
288
+ font-family: var(--sans);
289
+ font-size: 13px;
290
+ }
291
+ .sdocs-sidebar-header {
292
+ padding: 12px 16px;
293
+ border-bottom: 1px solid var(--color-base-200);
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: space-between;
297
+ }
298
+ .sdocs-logo {
299
+ display: flex;
300
+ align-items: center;
301
+ gap: 6px;
302
+ font-weight: 700;
303
+ font-size: 20px;
304
+ color: var(--color-base-900);
305
+ text-decoration: none;
306
+ }
307
+ .sdocs-header-actions {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: 6px;
311
+ }
312
+ .sdocs-css-picker {
313
+ font-size: 12px;
314
+ padding: 2px 4px;
315
+ border: 1px solid var(--color-base-200);
316
+ border-radius: 4px;
317
+ background: var(--color-base-0);
318
+ color: var(--color-base-600);
319
+ }
320
+ .sdocs-theme-btn {
321
+ padding: 2px 6px;
322
+ border: 1px solid var(--color-base-200);
323
+ border-radius: 4px;
324
+ background: var(--color-base-0);
325
+ color: var(--color-base-600);
326
+ cursor: pointer;
327
+ font-size: 14px;
328
+ line-height: 1;
329
+ }
330
+ .sdocs-theme-btn:hover {
331
+ background: var(--color-base-100);
332
+ }
333
+ .sdocs-fullscreen-btn {
334
+ padding: 2px 6px;
335
+ border: 1px solid var(--color-base-200);
336
+ border-radius: 4px;
337
+ background: var(--color-base-0);
338
+ color: var(--color-base-600);
339
+ cursor: pointer;
340
+ font-size: 14px;
341
+ line-height: 1;
342
+ }
343
+ .sdocs-fullscreen-btn:hover {
344
+ background: var(--color-base-100);
345
+ }
346
+ .sdocs-sidebar-search {
347
+ padding: 8px 16px;
348
+ }
349
+ .sdocs-search-input {
350
+ width: 100%;
351
+ padding: 6px 10px;
352
+ border: 1px solid var(--color-base-200);
353
+ border-radius: 6px;
354
+ font-size: 13px;
355
+ outline: none;
356
+ box-sizing: border-box;
357
+ background: var(--color-base-0);
358
+ color: var(--color-base-600);
359
+ }
360
+ .sdocs-search-input:focus {
361
+ border-color: var(--color-action-500);
362
+ box-shadow: 0 0 0 2px var(--color-action-100);
363
+ }
364
+ .sdocs-sidebar-tree {
365
+ flex: 1;
366
+ overflow-y: auto;
367
+ padding: 8px;
368
+ }
369
+ </style>
@@ -1,5 +1,5 @@
1
- import type { TreeNode } from './tree-builder.js';
2
- type ThemeMode = 'light' | 'dark' | 'system';
1
+ import type { TreeNode } from '../tree-builder.js';
2
+ type ThemeMode = 'light' | 'dark';
3
3
  interface Props {
4
4
  tree: TreeNode[];
5
5
  currentPath: string[];
@@ -1,9 +1,9 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { build } from 'vite';
3
3
  import { svelte } from '@sveltejs/vite-plugin-svelte';
4
- import { loadConfig } from '../config.js';
4
+ import { loadConfig } from '../server/config.js';
5
5
  import { sdocsPlugin } from '../vite.js';
6
- import { generateBuildFiles, cleanBuildFiles } from '../app-gen.js';
6
+ import { generateBuildFiles, cleanBuildFiles } from '../server/app-gen.js';
7
7
  export async function buildCommand() {
8
8
  const cwd = process.cwd();
9
9
  const config = await loadConfig(cwd);
@@ -1,9 +1,9 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { createServer } from 'vite';
3
3
  import { svelte } from '@sveltejs/vite-plugin-svelte';
4
- import { loadConfig } from '../config.js';
4
+ import { loadConfig } from '../server/config.js';
5
5
  import { sdocsPlugin } from '../vite.js';
6
- import { generateDevFiles, cleanBuildFiles } from '../app-gen.js';
6
+ import { generateDevFiles, cleanBuildFiles } from '../server/app-gen.js';
7
7
  export async function devCommand() {
8
8
  const cwd = process.cwd();
9
9
  const config = await loadConfig(cwd);
@@ -1,14 +1,14 @@
1
1
  import { writeFile } from 'node:fs/promises';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { findConfigFile } from '../config.js';
4
+ import { findConfigFile } from '../server/config.js';
5
5
  const DEFAULT_CONFIG = `/** @type {import('sdocs').SdocsConfig} */
6
6
  export default {
7
7
  // Glob pattern(s) to find sdoc files
8
- // include: ['./src/**/*.sdoc', './src/**/*.sdocx'],
8
+ // include: ['./src/**/*.sdoc'],
9
9
 
10
- // Dev server port (default: 5174)
11
- // port: 5174,
10
+ // Dev server port (default: 3000)
11
+ // port: 3000,
12
12
 
13
13
  // Open browser on start (default: false)
14
14
  // open: false,
@@ -1,7 +1,7 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { preview } from 'vite';
4
- import { loadConfig } from '../config.js';
4
+ import { loadConfig } from '../server/config.js';
5
5
  export async function previewCommand() {
6
6
  const cwd = process.cwd();
7
7
  const config = await loadConfig(cwd);
@@ -1,4 +1,4 @@
1
- import type { ResolvedSdocsConfig } from './types.js';
1
+ import type { ResolvedSdocsConfig } from '../types.js';
2
2
  /** Generate .sdocs/ directory with entry files for dev mode */
3
3
  export declare function generateDevFiles(config: ResolvedSdocsConfig, cwd: string): Promise<string>;
4
4
  /** Generate .sdocs/ directory with entry + preview HTML files for build mode */
@@ -1,9 +1,9 @@
1
1
  import { mkdir, writeFile, rm, readFile, copyFile, readdir } from 'node:fs/promises';
2
2
  import { dirname, resolve, join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { discoverDocFiles, getSdocKind } from './server/discovery.js';
5
- import { extractSnippets, hasDefaultSnippet } from './server/snippet-extractor.js';
6
- import { base64urlEncode } from './server/snippet-compiler.js';
4
+ import { discoverDocFiles, getSdocKind } from './discovery.js';
5
+ import { extractSnippets, hasDefaultSnippet } from './snippet-extractor.js';
6
+ import { base64urlEncode } from './snippet-compiler.js';
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
  /** Source client directory in the installed package */
9
9
  function getClientSourceDir() {
@@ -31,7 +31,7 @@ function generateIndexHtml(title) {
31
31
  <head>
32
32
  <meta charset="UTF-8">
33
33
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
34
- <link rel="icon" type="image/png" href="./favicon.png">
34
+ <link rel="icon" type="image/png" href="./client/favicon.png">
35
35
  <title>${title}</title>
36
36
  </head>
37
37
  <body>
@@ -40,22 +40,22 @@ async function main() {
40
40
  }
41
41
  switch (command) {
42
42
  case 'dev': {
43
- const { devCommand } = await import('./commands/dev.js');
43
+ const { devCommand } = await import('../commands/dev.js');
44
44
  await devCommand();
45
45
  break;
46
46
  }
47
47
  case 'build': {
48
- const { buildCommand } = await import('./commands/build.js');
48
+ const { buildCommand } = await import('../commands/build.js');
49
49
  await buildCommand();
50
50
  break;
51
51
  }
52
52
  case 'preview': {
53
- const { previewCommand } = await import('./commands/preview.js');
53
+ const { previewCommand } = await import('../commands/preview.js');
54
54
  await previewCommand();
55
55
  break;
56
56
  }
57
57
  case 'init': {
58
- const { initCommand } = await import('./commands/init.js');
58
+ const { initCommand } = await import('../commands/init.js');
59
59
  await initCommand();
60
60
  break;
61
61
  }
@@ -1,4 +1,4 @@
1
- import type { SdocsConfig, ResolvedSdocsConfig } from './types.js';
1
+ import type { SdocsConfig, ResolvedSdocsConfig } from '../types.js';
2
2
  /** Find the config file path in the given directory */
3
3
  export declare function findConfigFile(root: string): string | null;
4
4
  /** Load and resolve the sdocs config with defaults */
@@ -3,8 +3,8 @@ import { existsSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  const CONFIG_NAMES = ['sdocs.config.ts', 'sdocs.config.mjs', 'sdocs.config.js'];
5
5
  const DEFAULTS = {
6
- include: ['./src/**/*.sdoc', './src/**/*.sdocx'],
7
- port: 5000,
6
+ include: ['./src/**/*.sdoc'],
7
+ port: 3000,
8
8
  open: false,
9
9
  css: null,
10
10
  logo: 'sdocs',
@@ -22,6 +22,10 @@ export function findConfigFile(root) {
22
22
  }
23
23
  return null;
24
24
  }
25
+ /** Resolve include glob patterns to absolute paths */
26
+ function resolveIncludePatterns(patterns, root) {
27
+ return patterns.map((p) => (p.startsWith('/') ? p : resolve(root, p)));
28
+ }
25
29
  /** Resolve CSS paths to absolute filesystem paths */
26
30
  function resolveCssPaths(css, root) {
27
31
  if (!css)
@@ -42,6 +46,7 @@ export async function loadConfig(root) {
42
46
  return { ...DEFAULTS };
43
47
  const userConfig = await importConfig(configPath);
44
48
  const resolved = resolveConfig(userConfig);
49
+ resolved.include = resolveIncludePatterns(resolved.include, root);
45
50
  resolved.css = resolveCssPaths(resolved.css, root);
46
51
  return resolved;
47
52
  }
@@ -1,4 +1,4 @@
1
- /** Discover all .sdoc/.sdocx files matching the include patterns */
1
+ /** Discover all .sdoc files matching the include patterns */
2
2
  export declare function discoverDocFiles(include: string[], root: string): Promise<string[]>;
3
3
  /** Determine the sdoc kind from the file path */
4
4
  export declare function getSdocKind(filePath: string): 'component' | 'page' | 'layout';
@@ -1,6 +1,6 @@
1
1
  import { glob } from 'tinyglobby';
2
2
  import { resolve } from 'node:path';
3
- /** Discover all .sdoc/.sdocx files matching the include patterns */
3
+ /** Discover all .sdoc files matching the include patterns */
4
4
  export async function discoverDocFiles(include, root) {
5
5
  const files = await glob(include, {
6
6
  cwd: root,
@@ -105,5 +105,5 @@ function resolveComponentPath(meta, imports, docFilePath) {
105
105
  /** Guess a title from the file path */
106
106
  function guessTitle(filePath) {
107
107
  const fileName = filePath.split('/').pop() ?? '';
108
- return fileName.replace(/\.(sdoc|sdocx)$/, '').replace(/\./g, ' ');
108
+ return fileName.replace(/\.sdoc$/, '').replace(/\./g, ' ');
109
109
  }
@@ -64,10 +64,17 @@ function parsePropsDestructuring(sourceFile) {
64
64
  ts.isObjectBindingPattern(node.name)) {
65
65
  for (const element of node.name.elements) {
66
66
  if (ts.isBindingElement(element)) {
67
- const name = element.name.getText(sourceFile);
68
- const defaultValue = element.initializer
67
+ // Use propertyName when present (e.g. `class: className`)
68
+ const name = element.propertyName
69
+ ? element.propertyName.getText(sourceFile)
70
+ : element.name.getText(sourceFile);
71
+ const rawDefault = element.initializer
69
72
  ? element.initializer.getText(sourceFile)
70
73
  : null;
74
+ // Strip wrapping quotes from string literals
75
+ const defaultValue = rawDefault?.match(/^['"`](.*?)['"`]$/)
76
+ ? rawDefault.slice(1, -1) || null
77
+ : rawDefault;
71
78
  props.push({ name, default: defaultValue });
72
79
  }
73
80
  }
@@ -210,7 +217,8 @@ function parseExportedState(sourceFile) {
210
217
  function parseCssProps(fullSource, styleContent) {
211
218
  const propMap = new Map();
212
219
  // Extract var(--name) and var(--name, default) from <style>
213
- const varRegex = /var\(\s*(--[\w-]+)(?:\s*,\s*([^)]+))?\s*\)/g;
220
+ // Supports one level of nested parens: var(--x, var(--y)), var(--x, rgba(0,0,0,0.5))
221
+ const varRegex = /var\(\s*(--[\w-]+)(?:\s*,\s*((?:[^()]+|\([^)]*\))+))?\s*\)/g;
214
222
  let match;
215
223
  while ((match = varRegex.exec(styleContent)) !== null) {
216
224
  const name = match[1];
@@ -241,7 +249,7 @@ function parseCssProps(fullSource, styleContent) {
241
249
  propMap.set(name, { name, type, default: defaultVal, description });
242
250
  }
243
251
  }
244
- return Array.from(propMap.values());
252
+ return Array.from(propMap.values()).sort((a, b) => a.name.localeCompare(b.name));
245
253
  }
246
254
  // ─── Helpers ───
247
255
  function getJsdocComment(node, sourceFile) {
@@ -56,6 +56,14 @@ export function generateIframeComponent(absoluteImports, snippetBody) {
56
56
  }
57
57
 
58
58
  window.parent.postMessage({ type: 'sdocs:preview-ready' }, '*');
59
+
60
+ // Report content height to parent for auto-sizing
61
+ const ro = new ResizeObserver(() => {
62
+ const height = document.getElementById('sdocs-preview')?.scrollHeight ?? 0;
63
+ window.parent.postMessage({ type: 'sdocs:resize', height }, '*');
64
+ });
65
+ ro.observe(document.getElementById('sdocs-preview'));
66
+ return () => ro.disconnect();
59
67
  });
60
68
  </script>
61
69
 
@@ -1,5 +1,5 @@
1
1
  import type { ExtractedSnippet } from '../types.js';
2
- /** Extract {#snippet} blocks from .sdoc source */
2
+ /** Extract top-level {#snippet} blocks from .sdoc source, handling nested snippets */
3
3
  export declare function extractSnippets(source: string): ExtractedSnippet[];
4
4
  /** Check if a Default snippet exists or needs auto-generation */
5
5
  export declare function hasDefaultSnippet(snippets: ExtractedSnippet[]): boolean;