sdocs 0.0.18 → 0.0.20
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 +281 -0
- package/dist/client/App.svelte +1 -1
- package/dist/client/App.svelte.d.ts +1 -1
- package/dist/client/views/CollapsiblePanel.svelte +15 -15
- package/dist/client/views/ComponentView.svelte +186 -32
- package/dist/client/views/DataTable.svelte +1 -0
- package/dist/client/views/PreviewFrame.svelte +0 -4
- package/dist/server/app-gen.js +1 -0
- package/dist/server/snippet-extractor.js +12 -1
- package/dist/ui/About.page.sdoc +1 -1
- package/dist/ui/Button/Button.sdoc +1 -1
- package/dist/ui/Dashboard.layout.sdoc +1 -1
- package/dist/ui/Stack/Stack.sdoc +9 -1
- package/dist/ui/styles/{fonts.css → fonts/fonts.css} +8 -8
- package/dist/ui/styles/sdocs.css +239 -0
- package/dist/ui/styles/theme.css +18 -252
- package/dist/vite.js +19 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# sdocs
|
|
2
|
+
|
|
3
|
+
A lightweight documentation tool for Svelte 5 components. Discover `.sdoc` files in your project and get an interactive component explorer with live previews, prop controls, and code highlighting.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Initialize config
|
|
9
|
+
npx sdocs init
|
|
10
|
+
|
|
11
|
+
# Start dev server
|
|
12
|
+
npx sdocs dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install sdocs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Requirements:** Svelte 5, Vite 6+, `@sveltejs/vite-plugin-svelte` 5+
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
sdocs can be used in two ways: as a **standalone CLI tool** or **embedded in your existing project**.
|
|
26
|
+
|
|
27
|
+
### Standalone (CLI)
|
|
28
|
+
|
|
29
|
+
Run sdocs as its own dev server:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx sdocs dev # Start dev server with HMR
|
|
33
|
+
npx sdocs build # Build static documentation site
|
|
34
|
+
npx sdocs preview # Preview the built site locally
|
|
35
|
+
npx sdocs init # Scaffold a sdocs.config.js file
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Embedded in a SvelteKit / Vite Project
|
|
39
|
+
|
|
40
|
+
Use sdocs as a Vite plugin inside your existing project. This way sdocs runs alongside your app without needing a separate server.
|
|
41
|
+
|
|
42
|
+
**1. Add the Vite plugin**
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
// vite.config.js
|
|
46
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
47
|
+
import { sdocsPlugin } from 'sdocs/vite';
|
|
48
|
+
|
|
49
|
+
export default {
|
|
50
|
+
plugins: [
|
|
51
|
+
sveltekit(),
|
|
52
|
+
sdocsPlugin({
|
|
53
|
+
include: ['./src/lib/**/*.sdoc'],
|
|
54
|
+
css: './src/styles/global.css',
|
|
55
|
+
logo: 'My Design System',
|
|
56
|
+
})
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The plugin discovers `.sdoc` files and exposes them via a `virtual:sdocs` module.
|
|
62
|
+
|
|
63
|
+
**2. Create a page that mounts the sdocs app**
|
|
64
|
+
|
|
65
|
+
```svelte
|
|
66
|
+
<!-- src/routes/docs/+page.svelte -->
|
|
67
|
+
<script>
|
|
68
|
+
import App from 'sdocs/client';
|
|
69
|
+
import { docs, cssNames } from 'virtual:sdocs';
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<App
|
|
73
|
+
{docs}
|
|
74
|
+
{cssNames}
|
|
75
|
+
logo="My Design System"
|
|
76
|
+
sidebarConfig={{
|
|
77
|
+
order: { root: ['Components', '*'] },
|
|
78
|
+
open: ['Components'],
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**3. Add the virtual module type declaration** (optional, for TypeScript)
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// src/app.d.ts or any .d.ts file
|
|
87
|
+
declare module 'virtual:sdocs' {
|
|
88
|
+
import type { DocEntry } from 'sdocs';
|
|
89
|
+
export const docs: DocEntry[];
|
|
90
|
+
export const cssNames: string[];
|
|
91
|
+
export default docs;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
That's it — your docs page lives at `/docs` inside your existing app.
|
|
96
|
+
|
|
97
|
+
## Writing Docs
|
|
98
|
+
|
|
99
|
+
sdocs supports three types of doc files:
|
|
100
|
+
|
|
101
|
+
### Component Docs (`.sdoc`)
|
|
102
|
+
|
|
103
|
+
Document a Svelte component with interactive controls and examples.
|
|
104
|
+
|
|
105
|
+
```svelte
|
|
106
|
+
<!-- Button.sdoc -->
|
|
107
|
+
<script lang="ts">
|
|
108
|
+
import Button from './Button.svelte';
|
|
109
|
+
|
|
110
|
+
export const meta = {
|
|
111
|
+
component: Button,
|
|
112
|
+
title: 'Components / Button',
|
|
113
|
+
description: 'A flexible button component.',
|
|
114
|
+
args: {
|
|
115
|
+
label: 'Click me',
|
|
116
|
+
size: 'md',
|
|
117
|
+
disabled: false,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
{#snippet Default()}
|
|
123
|
+
<Button {...args} />
|
|
124
|
+
{/snippet}
|
|
125
|
+
|
|
126
|
+
{#snippet WithIcon()}
|
|
127
|
+
<Button>
|
|
128
|
+
<Icon name="settings" /> Settings
|
|
129
|
+
</Button>
|
|
130
|
+
{/snippet}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- **`component`** — the Svelte component to document (auto-extracts props, events, snippets, methods, state, CSS custom properties)
|
|
134
|
+
- **`title`** — slash-separated path for sidebar navigation (e.g. `'Components / Button'`)
|
|
135
|
+
- **`args`** — default prop values, used as initial values for interactive controls
|
|
136
|
+
- **`Default` snippet** — gets live interactive controls. Auto-generated as `<Component {...args} />` if omitted.
|
|
137
|
+
- **Named snippets** — static examples listed in the sidebar
|
|
138
|
+
|
|
139
|
+
### Page Docs (`.page.sdoc`)
|
|
140
|
+
|
|
141
|
+
Freeform content pages with auto-generated table of contents.
|
|
142
|
+
|
|
143
|
+
```svelte
|
|
144
|
+
<!-- GettingStarted.page.sdoc -->
|
|
145
|
+
<script lang="ts">
|
|
146
|
+
export const meta = {
|
|
147
|
+
title: 'Docs / Getting Started',
|
|
148
|
+
description: 'How to set up sdocs.',
|
|
149
|
+
};
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<h1>Getting Started</h1>
|
|
153
|
+
<p>Install sdocs and create your first doc file.</p>
|
|
154
|
+
|
|
155
|
+
<h2>Installation</h2>
|
|
156
|
+
<p>Run <code>npm install sdocs</code>...</p>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Table of contents is auto-generated from `<h2>`, `<h3>`, and `<h4>` headings.
|
|
160
|
+
|
|
161
|
+
### Layout Docs (`.layout.sdoc`)
|
|
162
|
+
|
|
163
|
+
Component compositions rendered in an isolated iframe.
|
|
164
|
+
|
|
165
|
+
```svelte
|
|
166
|
+
<!-- LoginForm.layout.sdoc -->
|
|
167
|
+
<script lang="ts">
|
|
168
|
+
import Card from './Card.svelte';
|
|
169
|
+
import Input from './Input.svelte';
|
|
170
|
+
import Button from './Button.svelte';
|
|
171
|
+
|
|
172
|
+
export const meta = {
|
|
173
|
+
title: 'Patterns / Login Form',
|
|
174
|
+
description: 'A login form combining multiple components.',
|
|
175
|
+
settings: { padding: '24px' },
|
|
176
|
+
};
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<Card padding="24px">
|
|
180
|
+
<Input label="Email" type="email" />
|
|
181
|
+
<Input label="Password" type="password" />
|
|
182
|
+
<Button>Sign in</Button>
|
|
183
|
+
</Card>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Prop Extraction
|
|
187
|
+
|
|
188
|
+
sdocs automatically extracts from your Svelte components:
|
|
189
|
+
|
|
190
|
+
| What | Source |
|
|
191
|
+
|------|--------|
|
|
192
|
+
| **Props** | `$props()` destructuring + `interface Props {}` |
|
|
193
|
+
| **Events** | Callback props (`onclick`, `onchange`, etc.) |
|
|
194
|
+
| **Snippets** | Props typed as `Snippet` or `Snippet<[...]>` |
|
|
195
|
+
| **Methods** | Exported functions |
|
|
196
|
+
| **State** | Exported `$state` / `$derived` values |
|
|
197
|
+
| **CSS Custom Properties** | `var(--name)` usages in `<style>` |
|
|
198
|
+
|
|
199
|
+
JSDoc comments on props are picked up as descriptions.
|
|
200
|
+
|
|
201
|
+
## Interactive Controls
|
|
202
|
+
|
|
203
|
+
The Default snippet gets live controls based on prop types:
|
|
204
|
+
|
|
205
|
+
| Prop Type | Control |
|
|
206
|
+
|-----------|---------|
|
|
207
|
+
| `string` | Text input |
|
|
208
|
+
| `number` | Number input |
|
|
209
|
+
| `boolean` | Checkbox |
|
|
210
|
+
| Color (`#hex`) | Color picker |
|
|
211
|
+
| Dimension (`16px`) | Number + unit |
|
|
212
|
+
|
|
213
|
+
## Configuration
|
|
214
|
+
|
|
215
|
+
Create `sdocs.config.js` in your project root (or run `npx sdocs init`):
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
/** @type {import('sdocs').SdocsConfig} */
|
|
219
|
+
export default {
|
|
220
|
+
// Glob pattern(s) to find .sdoc files
|
|
221
|
+
include: ['./src/**/*.sdoc'],
|
|
222
|
+
|
|
223
|
+
// Dev server port
|
|
224
|
+
port: 3000,
|
|
225
|
+
|
|
226
|
+
// Open browser on start
|
|
227
|
+
open: true,
|
|
228
|
+
|
|
229
|
+
// Sidebar logo text
|
|
230
|
+
logo: 'My Design System',
|
|
231
|
+
|
|
232
|
+
// CSS loaded in preview iframes
|
|
233
|
+
css: './src/styles/global.css',
|
|
234
|
+
|
|
235
|
+
// Sidebar ordering
|
|
236
|
+
sidebar: {
|
|
237
|
+
order: {
|
|
238
|
+
root: ['Components', '*', 'Documentation'],
|
|
239
|
+
},
|
|
240
|
+
open: ['Components'],
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### CSS Stylesheet Switching
|
|
246
|
+
|
|
247
|
+
Provide named stylesheets to let users switch between them (e.g. light/dark themes):
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
css: {
|
|
251
|
+
light: './src/styles/light.css',
|
|
252
|
+
dark: './src/styles/dark.css',
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Sidebar Ordering
|
|
257
|
+
|
|
258
|
+
Control the order of items in the sidebar with `sidebar.order`. Use `'*'` as a wildcard for remaining items in alphabetical order:
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
sidebar: {
|
|
262
|
+
order: {
|
|
263
|
+
root: ['Getting Started', 'Components', '*'],
|
|
264
|
+
Components: ['Button', 'Input', '*'],
|
|
265
|
+
},
|
|
266
|
+
open: ['Components'],
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Package Exports
|
|
271
|
+
|
|
272
|
+
| Export | Description |
|
|
273
|
+
|--------|-------------|
|
|
274
|
+
| `sdocs` | Main entry — `sdocsPlugin` + types |
|
|
275
|
+
| `sdocs/vite` | Vite plugin function |
|
|
276
|
+
| `sdocs/client` | App.svelte UI component |
|
|
277
|
+
| `sdocs/ui` | Reusable UI components (Button, Frame, Icon, Control, NavTree, Stack) |
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
MIT
|
package/dist/client/App.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import { Icon } from '../../ui/Icon/index.js';
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
title: string;
|
|
@@ -12,9 +13,11 @@
|
|
|
12
13
|
</script>
|
|
13
14
|
|
|
14
15
|
<div class="sdocs-panel">
|
|
15
|
-
<button class="sdocs-panel-header" onclick={() => expanded = !expanded}>
|
|
16
|
-
<span class="sdocs-panel-arrow" class:expanded>▶</span>
|
|
16
|
+
<button class="sdocs-panel-header" onclick={() => (expanded = !expanded)}>
|
|
17
17
|
<span class="sdocs-panel-title">{title}</span>
|
|
18
|
+
<span class="sdocs-panel-chevron">
|
|
19
|
+
<Icon name={expanded ? 'chevron-down' : 'chevron-right'} --w="14px" --h="14px" />
|
|
20
|
+
</span>
|
|
18
21
|
</button>
|
|
19
22
|
{#if expanded}
|
|
20
23
|
<div class="sdocs-panel-body">
|
|
@@ -25,39 +28,36 @@
|
|
|
25
28
|
|
|
26
29
|
<style>
|
|
27
30
|
.sdocs-panel {
|
|
28
|
-
border: 1px solid var(--color-base-200);
|
|
29
|
-
border-radius: 8px;
|
|
31
|
+
/* border: 1px solid var(--color-base-200); */
|
|
32
|
+
/* border-radius: 8px; */
|
|
30
33
|
overflow: hidden;
|
|
34
|
+
background: var(--color-base-0);
|
|
31
35
|
}
|
|
32
36
|
.sdocs-panel-header {
|
|
33
37
|
display: flex;
|
|
34
38
|
align-items: center;
|
|
35
39
|
gap: 8px;
|
|
36
40
|
width: 100%;
|
|
37
|
-
padding:
|
|
41
|
+
padding: 6px 8px 6px 12px;
|
|
38
42
|
border: none;
|
|
39
43
|
background: var(--color-base-50);
|
|
40
44
|
font: inherit;
|
|
41
45
|
font-size: 13px;
|
|
42
46
|
font-weight: 600;
|
|
43
|
-
color: var(--color-base-
|
|
47
|
+
color: var(--color-base-700);
|
|
44
48
|
cursor: pointer;
|
|
45
49
|
text-align: left;
|
|
46
50
|
}
|
|
47
51
|
.sdocs-panel-header:hover {
|
|
48
52
|
background: var(--color-base-100);
|
|
49
53
|
}
|
|
50
|
-
.sdocs-panel-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
width: 10px;
|
|
55
|
-
}
|
|
56
|
-
.sdocs-panel-arrow.expanded {
|
|
57
|
-
transform: rotate(90deg);
|
|
54
|
+
.sdocs-panel-chevron {
|
|
55
|
+
margin-left: auto;
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
58
|
}
|
|
59
59
|
.sdocs-panel-body {
|
|
60
60
|
padding: 14px;
|
|
61
|
-
border-top: 1px solid var(--color-base-
|
|
61
|
+
border-top: 1px solid var(--color-base-100);
|
|
62
62
|
}
|
|
63
63
|
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { DocEntry } from '../../types.js';
|
|
3
|
+
import { Icon } from '../../ui/Icon/index.js';
|
|
3
4
|
import CollapsiblePanel from './CollapsiblePanel.svelte';
|
|
4
5
|
import PreviewFrame from './PreviewFrame.svelte';
|
|
5
6
|
import ControlsPanel from './ControlsPanel.svelte';
|
|
@@ -50,17 +51,17 @@
|
|
|
50
51
|
cssValues = { ...cssValues, [name]: value };
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
function
|
|
54
|
+
function formatAttr(name: string, value: unknown): string {
|
|
55
|
+
if (typeof value === 'string') return `${name}="${value}"`;
|
|
56
|
+
if (typeof value === 'boolean') return value ? name : `${name}={false}`;
|
|
57
|
+
return `${name}={${JSON.stringify(value)}}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function generateFallbackCode(name: string, props: Record<string, unknown>, css: Record<string, string>): string {
|
|
54
61
|
const attrs: string[] = [];
|
|
55
62
|
for (const [key, value] of Object.entries(props)) {
|
|
56
63
|
if (value === undefined || value === null || value === '') continue;
|
|
57
|
-
|
|
58
|
-
attrs.push(`${key}="${value}"`);
|
|
59
|
-
} else if (typeof value === 'boolean') {
|
|
60
|
-
attrs.push(value ? key : `${key}={false}`);
|
|
61
|
-
} else {
|
|
62
|
-
attrs.push(`${key}={${JSON.stringify(value)}}`);
|
|
63
|
-
}
|
|
64
|
+
attrs.push(formatAttr(key, value));
|
|
64
65
|
}
|
|
65
66
|
for (const [key, value] of Object.entries(css)) {
|
|
66
67
|
if (value === undefined || value === '') continue;
|
|
@@ -71,7 +72,126 @@
|
|
|
71
72
|
return `<${name}\n ${attrs.join('\n ')}\n/>`;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Patch the Default snippet body with prop/CSS changes from Controls.
|
|
77
|
+
* Finds the root component opening tag and updates/adds changed attributes.
|
|
78
|
+
*/
|
|
79
|
+
function patchSnippetCode(
|
|
80
|
+
snippetBody: string,
|
|
81
|
+
name: string,
|
|
82
|
+
currentProps: Record<string, unknown>,
|
|
83
|
+
currentCss: Record<string, string>,
|
|
84
|
+
initialProps: Record<string, unknown>,
|
|
85
|
+
initialCss: Record<string, string>,
|
|
86
|
+
): string {
|
|
87
|
+
// Collect only changed props/css
|
|
88
|
+
const changes: [string, unknown][] = [];
|
|
89
|
+
for (const [key, value] of Object.entries(currentProps)) {
|
|
90
|
+
if (JSON.stringify(value) !== JSON.stringify(initialProps[key])) {
|
|
91
|
+
changes.push([key, value]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const [key, value] of Object.entries(currentCss)) {
|
|
95
|
+
if (value !== (initialCss[key] ?? '')) {
|
|
96
|
+
changes.push([key, value]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (changes.length === 0) return snippetBody;
|
|
101
|
+
|
|
102
|
+
// Find the opening tag: <ComponentName ...> or <ComponentName ... />
|
|
103
|
+
const tagStart = snippetBody.indexOf(`<${name}`);
|
|
104
|
+
if (tagStart === -1) return snippetBody;
|
|
105
|
+
|
|
106
|
+
// Find the end of the opening tag, respecting {} expressions
|
|
107
|
+
let braceDepth = 0;
|
|
108
|
+
let tagEnd = -1;
|
|
109
|
+
for (let i = tagStart + name.length + 1; i < snippetBody.length; i++) {
|
|
110
|
+
if (snippetBody[i] === '{') braceDepth++;
|
|
111
|
+
else if (snippetBody[i] === '}') braceDepth--;
|
|
112
|
+
else if (braceDepth === 0 && snippetBody[i] === '>') {
|
|
113
|
+
tagEnd = i;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (tagEnd === -1) return snippetBody;
|
|
118
|
+
|
|
119
|
+
const isSelfClosing = snippetBody[tagEnd - 1] === '/';
|
|
120
|
+
const attrsStart = tagStart + `<${name}`.length;
|
|
121
|
+
const attrsEnd = isSelfClosing ? tagEnd - 1 : tagEnd;
|
|
122
|
+
let attrs = snippetBody.slice(attrsStart, attrsEnd);
|
|
123
|
+
|
|
124
|
+
for (const [attrName, value] of changes) {
|
|
125
|
+
const formatted = formatAttr(attrName, value);
|
|
126
|
+
const escaped = attrName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
127
|
+
|
|
128
|
+
// Try replacing existing: name="...", name={...}, or bare name
|
|
129
|
+
const patterns = [
|
|
130
|
+
new RegExp(`(\\s)${escaped}="[^"]*"`),
|
|
131
|
+
new RegExp(`(\\s)${escaped}=\\{[^}]*\\}`),
|
|
132
|
+
new RegExp(`(\\s)${escaped}(?=\\s|$)`),
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
let replaced = false;
|
|
136
|
+
for (const pattern of patterns) {
|
|
137
|
+
if (pattern.test(attrs)) {
|
|
138
|
+
attrs = attrs.replace(pattern, `$1${formatted}`);
|
|
139
|
+
replaced = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!replaced) {
|
|
145
|
+
attrs += ` ${formatted}`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const closing = isSelfClosing ? '/>' : '>';
|
|
150
|
+
return snippetBody.slice(0, attrsStart) + attrs + closing + snippetBody.slice(tagEnd + 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Initial values for diffing (computed once per doc change)
|
|
154
|
+
const initialProps = $derived(meta.args ?? {});
|
|
155
|
+
const initialCss = $derived.by(() => {
|
|
156
|
+
const css: Record<string, string> = {};
|
|
157
|
+
if (cd?.cssProps) {
|
|
158
|
+
for (const cp of cd.cssProps) {
|
|
159
|
+
if (cp.default) css[cp.name] = cp.default;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return css;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const usageCode = $derived.by(() => {
|
|
166
|
+
if (defaultSnippet?.body) {
|
|
167
|
+
return patchSnippetCode(defaultSnippet.body, componentName, propValues, cssValues, initialProps, initialCss);
|
|
168
|
+
}
|
|
169
|
+
return generateFallbackCode(componentName, propValues, cssValues);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Highlighted usage code via server-side Shiki
|
|
173
|
+
let highlightedUsageHtml = $state('');
|
|
174
|
+
let highlightTimer: ReturnType<typeof setTimeout> | undefined;
|
|
175
|
+
|
|
176
|
+
$effect(() => {
|
|
177
|
+
const code = usageCode;
|
|
178
|
+
clearTimeout(highlightTimer);
|
|
179
|
+
highlightTimer = setTimeout(async () => {
|
|
180
|
+
try {
|
|
181
|
+
const res = await fetch('/__sdocs/highlight', {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
headers: { 'Content-Type': 'application/json' },
|
|
184
|
+
body: JSON.stringify({ code, lang: 'svelte' }),
|
|
185
|
+
});
|
|
186
|
+
if (res.ok) {
|
|
187
|
+
const { html } = await res.json();
|
|
188
|
+
highlightedUsageHtml = html;
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
// Fallback: leave previous value
|
|
192
|
+
}
|
|
193
|
+
}, 150);
|
|
194
|
+
});
|
|
75
195
|
|
|
76
196
|
function handleReset() {
|
|
77
197
|
propValues = { ...(meta.args ?? {}) };
|
|
@@ -146,7 +266,7 @@
|
|
|
146
266
|
</div>
|
|
147
267
|
<div class="sdocs-panels">
|
|
148
268
|
<CollapsiblePanel title="Preview">
|
|
149
|
-
<PreviewFrame src={focusedSnippet.previewUrl} {activeStylesheet} />
|
|
269
|
+
<PreviewFrame src={focusedSnippet.previewUrl ?? ''} {activeStylesheet} />
|
|
150
270
|
</CollapsiblePanel>
|
|
151
271
|
<CollapsiblePanel title="Code" defaultExpanded={false}>
|
|
152
272
|
<div class="sdocs-code-block">{@html focusedSnippet.highlightedHtml ?? ''}</div>
|
|
@@ -164,17 +284,23 @@
|
|
|
164
284
|
<div class="sdocs-panels">
|
|
165
285
|
<!-- Showcase -->
|
|
166
286
|
{#if defaultSnippet}
|
|
167
|
-
<
|
|
287
|
+
<div class="sdocs-preview-wrapper">
|
|
168
288
|
<PreviewFrame
|
|
169
|
-
src={defaultSnippet.previewUrl}
|
|
289
|
+
src={defaultSnippet.previewUrl ?? ''}
|
|
170
290
|
props={propValues}
|
|
171
291
|
cssVars={cssValues}
|
|
172
292
|
{activeStylesheet}
|
|
173
293
|
/>
|
|
174
|
-
</
|
|
294
|
+
</div>
|
|
175
295
|
|
|
176
|
-
<CollapsiblePanel title="Preview Code">
|
|
177
|
-
<div class="sdocs-code-block"
|
|
296
|
+
<CollapsiblePanel title="Preview Code" defaultExpanded={false}>
|
|
297
|
+
<div class="sdocs-code-block">
|
|
298
|
+
{#if highlightedUsageHtml}
|
|
299
|
+
{@html highlightedUsageHtml}
|
|
300
|
+
{:else}
|
|
301
|
+
<pre><code>{usageCode}</code></pre>
|
|
302
|
+
{/if}
|
|
303
|
+
</div>
|
|
178
304
|
</CollapsiblePanel>
|
|
179
305
|
{/if}
|
|
180
306
|
|
|
@@ -262,29 +388,37 @@
|
|
|
262
388
|
/>
|
|
263
389
|
</CollapsiblePanel>
|
|
264
390
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
{#if exampleSnippets.length > 0}
|
|
394
|
+
<hr class="sdocs-divider" />
|
|
395
|
+
<h2 class="sdocs-section-title">Examples</h2>
|
|
396
|
+
{#each exampleSnippets as example (example.name)}
|
|
397
|
+
<div class="sdocs-example">
|
|
398
|
+
<h3 class="sdocs-example-title">
|
|
399
|
+
<Icon name="bookmark" --w="14px" --h="14px" --fill="var(--color-example-500)" />
|
|
400
|
+
{example.name}
|
|
401
|
+
</h3>
|
|
402
|
+
<div class="sdocs-panels">
|
|
403
|
+
<div class="sdocs-preview-wrapper">
|
|
404
|
+
<PreviewFrame src={example.previewUrl ?? ''} {activeStylesheet} />
|
|
405
|
+
</div>
|
|
274
406
|
<CollapsiblePanel title="Code" defaultExpanded={false}>
|
|
275
407
|
<div class="sdocs-code-block">{@html example.highlightedHtml ?? ''}</div>
|
|
276
408
|
</CollapsiblePanel>
|
|
277
409
|
</div>
|
|
278
|
-
|
|
279
|
-
{/
|
|
410
|
+
</div>
|
|
411
|
+
{/each}
|
|
412
|
+
{/if}
|
|
280
413
|
|
|
281
|
-
|
|
282
|
-
|
|
414
|
+
{#if doc.highlightedSource}
|
|
415
|
+
<hr class="sdocs-divider" />
|
|
416
|
+
<div class="sdocs-panels">
|
|
283
417
|
<CollapsiblePanel title="Component Source" defaultExpanded={false}>
|
|
284
418
|
<div class="sdocs-code-block">{@html doc.highlightedSource}</div>
|
|
285
419
|
</CollapsiblePanel>
|
|
286
|
-
|
|
287
|
-
|
|
420
|
+
</div>
|
|
421
|
+
{/if}
|
|
288
422
|
{/if}
|
|
289
423
|
</div>
|
|
290
424
|
|
|
@@ -311,13 +445,29 @@
|
|
|
311
445
|
.sdocs-panels {
|
|
312
446
|
display: flex;
|
|
313
447
|
flex-direction: column;
|
|
314
|
-
gap:
|
|
448
|
+
gap: 1px;
|
|
449
|
+
border: 1px solid var(--color-base-200);
|
|
450
|
+
background: var(--color-base-200);
|
|
451
|
+
border-radius: 8px;
|
|
452
|
+
overflow: hidden;
|
|
453
|
+
}
|
|
454
|
+
.sdocs-preview-wrapper {
|
|
455
|
+
background: var(--color-base-0);
|
|
456
|
+
padding: 16px;
|
|
457
|
+
}
|
|
458
|
+
.sdocs-divider {
|
|
459
|
+
border: none;
|
|
460
|
+
border-top: 1px solid var(--color-base-200);
|
|
461
|
+
margin: 24px 0;
|
|
315
462
|
}
|
|
316
463
|
.sdocs-section-title {
|
|
317
464
|
font-size: 18px;
|
|
318
465
|
font-weight: 600;
|
|
319
466
|
color: var(--color-base-900);
|
|
320
|
-
margin: 24px 0
|
|
467
|
+
margin: 24px 0 16px;
|
|
468
|
+
}
|
|
469
|
+
.sdocs-example + .sdocs-example {
|
|
470
|
+
margin-top: 16px;
|
|
321
471
|
}
|
|
322
472
|
.sdocs-example {
|
|
323
473
|
display: flex;
|
|
@@ -325,6 +475,9 @@
|
|
|
325
475
|
gap: 8px;
|
|
326
476
|
}
|
|
327
477
|
.sdocs-example-title {
|
|
478
|
+
display: flex;
|
|
479
|
+
align-items: center;
|
|
480
|
+
gap: 6px;
|
|
328
481
|
font-size: 15px;
|
|
329
482
|
font-weight: 600;
|
|
330
483
|
color: var(--color-base-800);
|
|
@@ -334,6 +487,7 @@
|
|
|
334
487
|
overflow-x: auto;
|
|
335
488
|
font-size: 13px;
|
|
336
489
|
line-height: 1.5;
|
|
490
|
+
tab-size: 4;
|
|
337
491
|
}
|
|
338
492
|
.sdocs-code-block :global(pre) {
|
|
339
493
|
margin: 0;
|
|
@@ -83,15 +83,11 @@
|
|
|
83
83
|
|
|
84
84
|
<style>
|
|
85
85
|
.sdocs-preview-frame {
|
|
86
|
-
border: 1px solid var(--color-base-200);
|
|
87
|
-
border-radius: 6px;
|
|
88
86
|
overflow: hidden;
|
|
89
87
|
background: var(--color-base-0);
|
|
90
88
|
}
|
|
91
89
|
.sdocs-preview-frame.full-height {
|
|
92
90
|
flex: 1;
|
|
93
|
-
border: none;
|
|
94
|
-
border-radius: 0;
|
|
95
91
|
}
|
|
96
92
|
.sdocs-iframe {
|
|
97
93
|
width: 100%;
|
package/dist/server/app-gen.js
CHANGED
|
@@ -33,6 +33,7 @@ function generateIndexHtml(title) {
|
|
|
33
33
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
34
34
|
<link rel="icon" type="image/png" href="./client/favicon.png">
|
|
35
35
|
<title>${title}</title>
|
|
36
|
+
<style>body { margin: 0; }</style>
|
|
36
37
|
</head>
|
|
37
38
|
<body>
|
|
38
39
|
<div id="app"></div>
|