@rokkit/stories 1.0.0-next.134 → 1.0.0-next.136
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/dist/builder.svelte.d.ts +25 -0
- package/dist/index.d.ts +11 -0
- package/dist/{stories.d.ts → lib/stories.d.ts} +3 -2
- package/package.json +13 -2
- package/src/Code.svelte +28 -0
- package/src/CodeViewer.svelte +16 -0
- package/src/CopyToClipboard.svelte +27 -0
- package/src/Demo.svelte +115 -0
- package/src/StoryComponent.svelte +17 -0
- package/src/StoryError.svelte +26 -0
- package/src/StoryLoading.svelte +24 -0
- package/src/StoryViewer.svelte +51 -0
- package/src/builder.svelte.js +68 -0
- package/src/index.js +20 -0
- package/src/lib/stories.js +8 -6
- package/src/lib/stories.spec.js +15 -15
- /package/dist/{shiki.d.ts → lib/shiki.d.ts} +0 -0
- /package/dist/{shiki.spec.d.ts → lib/shiki.spec.d.ts} +0 -0
- /package/dist/{stories.spec.d.ts → lib/stories.spec.d.ts} +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} Example
|
|
3
|
+
* @property {Object[]} files
|
|
4
|
+
* @property {import('svelte').SvelteComponent} App
|
|
5
|
+
*/
|
|
6
|
+
export class StoryBuilder {
|
|
7
|
+
constructor(sources: any, modules: any);
|
|
8
|
+
get loading(): boolean;
|
|
9
|
+
get error(): null;
|
|
10
|
+
get examples(): Record<string, Example>;
|
|
11
|
+
get fragments(): Object[];
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} name
|
|
14
|
+
* @returns {Example}
|
|
15
|
+
*/
|
|
16
|
+
getExample(name: string): Example;
|
|
17
|
+
getFragment(index: any): Object;
|
|
18
|
+
hasExample(name: any): boolean;
|
|
19
|
+
hasFragment(index: any): boolean;
|
|
20
|
+
#private;
|
|
21
|
+
}
|
|
22
|
+
export type Example = {
|
|
23
|
+
files: Object[];
|
|
24
|
+
App: import("svelte").SvelteComponent;
|
|
25
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { default as Code } from "./Code.svelte";
|
|
2
|
+
export { default as CodeViewer } from "./CodeViewer.svelte";
|
|
3
|
+
export { default as CopyToClipboard } from "./CopyToClipboard.svelte";
|
|
4
|
+
export { default as Demo } from "./Demo.svelte";
|
|
5
|
+
export { default as StoryViewer } from "./StoryViewer.svelte";
|
|
6
|
+
export { default as StoryComponent } from "./StoryComponent.svelte";
|
|
7
|
+
export { default as StoryError } from "./StoryError.svelte";
|
|
8
|
+
export { default as StoryLoading } from "./StoryLoading.svelte";
|
|
9
|
+
export { StoryBuilder } from "./builder.svelte.js";
|
|
10
|
+
export { highlightCode, preloadHighlighter } from "./lib/shiki.js";
|
|
11
|
+
export { fetchImports, getSlug, getSections, getAllSections, groupFiles, fetchStories, findSection, findGroupForSection } from "./lib/stories.js";
|
|
@@ -36,9 +36,10 @@ export function fetchStories(sources: Object, modules: Object): Promise<{
|
|
|
36
36
|
}>;
|
|
37
37
|
/**
|
|
38
38
|
* Get all individual sections flattened from groups
|
|
39
|
+
* @param {Metadata[]} sections - The sections to flatten
|
|
39
40
|
* @returns {Array} Array of all tutorial sections
|
|
40
41
|
*/
|
|
41
|
-
export function getAllSections(): any[];
|
|
42
|
+
export function getAllSections(sections: Metadata[]): any[];
|
|
42
43
|
/**
|
|
43
44
|
* Find a section by its ID
|
|
44
45
|
* @param {string} slug - The section ID to find
|
|
@@ -50,7 +51,7 @@ export function findSection(sections: any, slug: string): Object | null;
|
|
|
50
51
|
* @param {string} sectionId - The section ID to find the group for
|
|
51
52
|
* @returns {Object|null} The group object or null if not found
|
|
52
53
|
*/
|
|
53
|
-
export function findGroupForSection(sections: any,
|
|
54
|
+
export function findGroupForSection(sections: any, slug: any): Object | null;
|
|
54
55
|
export type SourceFile = {
|
|
55
56
|
/**
|
|
56
57
|
* - The file path.
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/stories",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.136",
|
|
4
4
|
"description": "Utilities for building tutorials.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/jerrythomas/rokkit.git"
|
|
8
|
+
},
|
|
5
9
|
"publishConfig": {
|
|
6
10
|
"access": "public"
|
|
7
11
|
},
|
|
@@ -19,11 +23,13 @@
|
|
|
19
23
|
"./package.json": "./package.json",
|
|
20
24
|
".": {
|
|
21
25
|
"types": "./dist/index.d.ts",
|
|
26
|
+
"svelte": "./src/index.js",
|
|
22
27
|
"import": "./src/index.js"
|
|
23
28
|
}
|
|
24
29
|
},
|
|
25
30
|
"files": [
|
|
26
31
|
"src/**/*.js",
|
|
32
|
+
"src/**/*.svelte",
|
|
27
33
|
"dist/**/*.d.ts",
|
|
28
34
|
"README.md",
|
|
29
35
|
"LICENSE"
|
|
@@ -39,6 +45,11 @@
|
|
|
39
45
|
"frontmatter": "^0.0.3",
|
|
40
46
|
"ramda": "^0.32.0",
|
|
41
47
|
"shiki": "^3.23.0",
|
|
42
|
-
"@rokkit/core": "latest"
|
|
48
|
+
"@rokkit/core": "workspace:latest",
|
|
49
|
+
"@rokkit/states": "workspace:latest",
|
|
50
|
+
"@rokkit/ui": "workspace:latest"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"svelte": "^5.0.0"
|
|
43
54
|
}
|
|
44
55
|
}
|
package/src/Code.svelte
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { highlightCode } from './lib/shiki.js'
|
|
3
|
+
import { vibe } from '@rokkit/states'
|
|
4
|
+
import CopyToClipboard from './CopyToClipboard.svelte'
|
|
5
|
+
|
|
6
|
+
let { content, language } = $props()
|
|
7
|
+
let theme = $derived(vibe.mode === 'dark' ? 'github-dark' : 'github-light')
|
|
8
|
+
let highlightedCode = $derived(
|
|
9
|
+
content ? highlightCode(content, { lang: language, theme }) : Promise.resolve('')
|
|
10
|
+
)
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div data-code-root>
|
|
14
|
+
<div data-code-overlay>
|
|
15
|
+
<CopyToClipboard {content} class="" />
|
|
16
|
+
</div>
|
|
17
|
+
{#if language}
|
|
18
|
+
<span data-code-lang>{language}</span>
|
|
19
|
+
{/if}
|
|
20
|
+
{#await highlightedCode}
|
|
21
|
+
<div class="text-surface-floating p-2">Highlighting code...</div>
|
|
22
|
+
{:then code}
|
|
23
|
+
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
24
|
+
{@html code}
|
|
25
|
+
{:catch error}
|
|
26
|
+
<div class="p-2 text-red-500">Error highlighting code: {error.message}</div>
|
|
27
|
+
{/await}
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { Tabs } from '@rokkit/ui'
|
|
3
|
+
import Code from './Code.svelte'
|
|
4
|
+
let { files = [] } = $props()
|
|
5
|
+
let current = $state()
|
|
6
|
+
$effect(() => {
|
|
7
|
+
if (files.length > 0 && current === undefined) current = files[0]
|
|
8
|
+
})
|
|
9
|
+
let fields = { label: 'name', icon: 'language' }
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<Tabs options={files} {fields} bind:value={current} class="no-padding">
|
|
13
|
+
{#snippet tabPanel(item)}
|
|
14
|
+
<Code content={item.content} language={item.language} />
|
|
15
|
+
{/snippet}
|
|
16
|
+
</Tabs>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
3
|
+
import { Button } from '@rokkit/ui'
|
|
4
|
+
let { content, class: className = 'absolute right-2 top-2 z-10', title = 'Copy code' } = $props()
|
|
5
|
+
let copySuccess = $state(false)
|
|
6
|
+
|
|
7
|
+
async function copyToClipboard() {
|
|
8
|
+
try {
|
|
9
|
+
await navigator.clipboard.writeText(content || '')
|
|
10
|
+
copySuccess = true
|
|
11
|
+
setTimeout(() => {
|
|
12
|
+
copySuccess = false
|
|
13
|
+
}, 2000)
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.error('Failed to copy code:', err)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Button
|
|
21
|
+
onclick={copyToClipboard}
|
|
22
|
+
class={className}
|
|
23
|
+
{title}
|
|
24
|
+
style="ghost"
|
|
25
|
+
size="sm"
|
|
26
|
+
icon={copySuccess ? DEFAULT_STATE_ICONS.action.copysuccess : DEFAULT_STATE_ICONS.action.copy}
|
|
27
|
+
/>
|
package/src/Demo.svelte
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Code from './Code.svelte'
|
|
3
|
+
|
|
4
|
+
let { App, code, language = 'svelte' } = $props()
|
|
5
|
+
let view = $state('preview')
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div data-demo-root>
|
|
9
|
+
<div data-demo-toggle>
|
|
10
|
+
<button
|
|
11
|
+
data-demo-btn
|
|
12
|
+
aria-pressed={view === 'preview'}
|
|
13
|
+
onclick={() => (view = 'preview')}
|
|
14
|
+
title="Preview"
|
|
15
|
+
>
|
|
16
|
+
<span class="i-solar:eye-bold-duotone"></span>
|
|
17
|
+
</button>
|
|
18
|
+
<button
|
|
19
|
+
data-demo-btn
|
|
20
|
+
aria-pressed={view === 'code'}
|
|
21
|
+
onclick={() => (view = 'code')}
|
|
22
|
+
title="Code"
|
|
23
|
+
>
|
|
24
|
+
<span class="i-solar:code-square-bold-duotone"></span>
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
{#if view === 'preview'}
|
|
29
|
+
<div data-demo-preview>
|
|
30
|
+
{#if App}<App />{/if}
|
|
31
|
+
</div>
|
|
32
|
+
{:else}
|
|
33
|
+
<div data-demo-code>
|
|
34
|
+
<Code content={code} {language} />
|
|
35
|
+
</div>
|
|
36
|
+
{/if}
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<style>
|
|
40
|
+
[data-demo-root] {
|
|
41
|
+
position: relative;
|
|
42
|
+
min-height: 14rem;
|
|
43
|
+
border-radius: 0.5rem;
|
|
44
|
+
border: 1px solid var(--color-surface);
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[data-demo-toggle] {
|
|
49
|
+
position: absolute;
|
|
50
|
+
top: 0.625rem;
|
|
51
|
+
right: 0.625rem;
|
|
52
|
+
z-index: 20;
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 2px;
|
|
55
|
+
background: color-mix(in srgb, var(--color-surface) 80%, transparent);
|
|
56
|
+
backdrop-filter: blur(6px);
|
|
57
|
+
border: 1px solid var(--color-surface);
|
|
58
|
+
border-radius: 0.375rem;
|
|
59
|
+
padding: 2px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[data-demo-btn] {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: center;
|
|
66
|
+
width: 1.75rem;
|
|
67
|
+
height: 1.75rem;
|
|
68
|
+
border-radius: 0.25rem;
|
|
69
|
+
border: none;
|
|
70
|
+
background: transparent;
|
|
71
|
+
color: var(--color-surface);
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
font-size: 0.875rem;
|
|
74
|
+
transition:
|
|
75
|
+
background 120ms ease,
|
|
76
|
+
color 120ms ease;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[data-demo-btn][aria-pressed='true'] {
|
|
80
|
+
background: var(--color-surface);
|
|
81
|
+
color: var(--color-on-surface);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
[data-demo-btn]:hover:not([aria-pressed='true']) {
|
|
85
|
+
background: var(--color-surface-z2);
|
|
86
|
+
color: var(--color-surface-z7);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[data-demo-preview] {
|
|
90
|
+
@apply bg-graph-paper;
|
|
91
|
+
--unit: 20px;
|
|
92
|
+
--size: var(--unit);
|
|
93
|
+
--minor-grid: 0.5px;
|
|
94
|
+
--major-grid: 0px;
|
|
95
|
+
--graph-paper-color: rgba(128, 128, 128, 0.22);
|
|
96
|
+
|
|
97
|
+
background-color: var(--color-surface-z0);
|
|
98
|
+
box-shadow: inset 0 2px 10px rgb(0 0 0 / 0.07);
|
|
99
|
+
|
|
100
|
+
min-height: 14rem;
|
|
101
|
+
padding: 2rem 1.5rem;
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
[data-demo-code] {
|
|
108
|
+
min-height: 14rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[data-demo-code] :global([data-code-root]) {
|
|
112
|
+
border-radius: 0;
|
|
113
|
+
border: none;
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let { component, class: className = '' } = $props()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="story-component p-6 {className}">
|
|
6
|
+
{#if component}
|
|
7
|
+
{@const Component = component}
|
|
8
|
+
<Component />
|
|
9
|
+
{:else}
|
|
10
|
+
<div
|
|
11
|
+
class="text-surface-floating border-surface-300 rounded border border-dashed p-4 text-center"
|
|
12
|
+
>
|
|
13
|
+
<p>Component loading in progress...</p>
|
|
14
|
+
<p class="mt-1 text-sm">If this persists, check console for errors</p>
|
|
15
|
+
</div>
|
|
16
|
+
{/if}
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let { error, class: className = '' } = $props()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div
|
|
6
|
+
class="story-error rounded-lg border border-red-200 bg-red-50 p-6 dark:border-red-800 dark:bg-red-900/20 {className}"
|
|
7
|
+
>
|
|
8
|
+
<div class="flex items-start space-x-3">
|
|
9
|
+
<div class="flex-shrink-0">
|
|
10
|
+
<svg class="h-5 w-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
11
|
+
<path
|
|
12
|
+
stroke-linecap="round"
|
|
13
|
+
stroke-linejoin="round"
|
|
14
|
+
stroke-width="2"
|
|
15
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.314 18.5c-.77.833.192 2.5 1.732 2.5z"
|
|
16
|
+
></path>
|
|
17
|
+
</svg>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="flex-1">
|
|
20
|
+
<h3 class="text-sm font-medium text-red-900 dark:text-red-100">Story Loading Failed</h3>
|
|
21
|
+
<p class="mt-1 text-sm text-red-800 dark:text-red-200">
|
|
22
|
+
{error}
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let { message = 'Loading story...', class: className = '' } = $props()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="story-loading border-surface-z2 bg-surface-z2 rounded-lg border p-6 {className}">
|
|
6
|
+
<div class="flex items-center justify-center space-x-3">
|
|
7
|
+
<div class="animate-spin">
|
|
8
|
+
<svg
|
|
9
|
+
class="text-surface-floating h-5 w-5"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="currentColor"
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
stroke-linecap="round"
|
|
16
|
+
stroke-linejoin="round"
|
|
17
|
+
stroke-width="2"
|
|
18
|
+
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
|
19
|
+
></path>
|
|
20
|
+
</svg>
|
|
21
|
+
</div>
|
|
22
|
+
<span class="text-surface-floating text-sm">{message}</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import CodeViewer from './CodeViewer.svelte'
|
|
3
|
+
import { Button } from '@rokkit/ui'
|
|
4
|
+
|
|
5
|
+
let { App, files } = $props()
|
|
6
|
+
let showCode = $state(false)
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<div data-story-root>
|
|
10
|
+
<!-- Demo preview area with grid background -->
|
|
11
|
+
<div
|
|
12
|
+
data-story-preview
|
|
13
|
+
class="preview-area border-surface-z2 text-surface-z8 rounded-t-md border p-6"
|
|
14
|
+
>
|
|
15
|
+
{#if App}
|
|
16
|
+
<App />
|
|
17
|
+
{:else}
|
|
18
|
+
<div>No preview available</div>
|
|
19
|
+
{/if}
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- Toolbar: toggle code -->
|
|
23
|
+
<div
|
|
24
|
+
class="border-surface-z2 bg-surface-z1 flex items-center justify-between rounded-b-md border border-t-0 px-3 py-1"
|
|
25
|
+
>
|
|
26
|
+
<span class="text-surface-z4 text-xs">Example</span>
|
|
27
|
+
<Button
|
|
28
|
+
label={showCode ? 'Hide code' : 'Show code'}
|
|
29
|
+
icon={showCode ? 'i-solar:minimize-square-bold-duotone' : 'i-solar:code-square-bold-duotone'}
|
|
30
|
+
onclick={() => (showCode = !showCode)}
|
|
31
|
+
style="ghost"
|
|
32
|
+
size="sm"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
{#if showCode}
|
|
37
|
+
<div class="mt-2">
|
|
38
|
+
<CodeViewer {files} />
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<style>
|
|
44
|
+
.preview-area {
|
|
45
|
+
background-color: var(--color-surface-z2, oklch(from var(--color-surface) calc(l + 0.02) c h));
|
|
46
|
+
background-image:
|
|
47
|
+
linear-gradient(rgb(var(--color-surface-200, 128 128 128) / 0.25) 1px, transparent 1px),
|
|
48
|
+
linear-gradient(90deg, rgb(var(--color-surface-200, 128 128 128) / 0.25) 1px, transparent 1px);
|
|
49
|
+
background-size: 20px 20px;
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { fetchStories } from './lib/stories.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} Example
|
|
5
|
+
* @property {Object[]} files
|
|
6
|
+
* @property {import('svelte').SvelteComponent} App
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export class StoryBuilder {
|
|
10
|
+
#modules
|
|
11
|
+
#sources
|
|
12
|
+
/** @type {Record<string, Example>} */
|
|
13
|
+
#processed = $state({})
|
|
14
|
+
#loading = $state(true)
|
|
15
|
+
#error = $state(null)
|
|
16
|
+
|
|
17
|
+
constructor(sources, modules) {
|
|
18
|
+
this.#modules = modules
|
|
19
|
+
this.#sources = sources
|
|
20
|
+
this.#init()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async #init() {
|
|
24
|
+
try {
|
|
25
|
+
this.#processed = await fetchStories(this.#sources, this.#modules)
|
|
26
|
+
this.#loading = false
|
|
27
|
+
} catch (err) {
|
|
28
|
+
this.#error = err
|
|
29
|
+
this.#loading = false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get loading() {
|
|
34
|
+
return this.#loading
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get error() {
|
|
38
|
+
return this.#error
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get examples() {
|
|
42
|
+
return this.#processed || {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get fragments() {
|
|
46
|
+
return this.#processed?.fragments?.files || []
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} name
|
|
51
|
+
* @returns {Example}
|
|
52
|
+
*/
|
|
53
|
+
getExample(name) {
|
|
54
|
+
return this.#processed?.[name]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getFragment(index) {
|
|
58
|
+
return this.#processed?.fragments?.files?.[index]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
hasExample(name) {
|
|
62
|
+
return Boolean(this.#processed?.[name])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
hasFragment(index) {
|
|
66
|
+
return Boolean(this.#processed?.fragments?.files?.[index])
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { default as Code } from './Code.svelte'
|
|
2
|
+
export { default as CodeViewer } from './CodeViewer.svelte'
|
|
3
|
+
export { default as CopyToClipboard } from './CopyToClipboard.svelte'
|
|
4
|
+
export { default as Demo } from './Demo.svelte'
|
|
5
|
+
export { default as StoryViewer } from './StoryViewer.svelte'
|
|
6
|
+
export { default as StoryComponent } from './StoryComponent.svelte'
|
|
7
|
+
export { default as StoryError } from './StoryError.svelte'
|
|
8
|
+
export { default as StoryLoading } from './StoryLoading.svelte'
|
|
9
|
+
export { StoryBuilder } from './builder.svelte.js'
|
|
10
|
+
export { highlightCode, preloadHighlighter } from './lib/shiki.js'
|
|
11
|
+
export {
|
|
12
|
+
fetchImports,
|
|
13
|
+
getSlug,
|
|
14
|
+
getSections,
|
|
15
|
+
getAllSections,
|
|
16
|
+
groupFiles,
|
|
17
|
+
fetchStories,
|
|
18
|
+
findSection,
|
|
19
|
+
findGroupForSection
|
|
20
|
+
} from './lib/stories.js'
|
package/src/lib/stories.js
CHANGED
|
@@ -164,10 +164,11 @@ export async function fetchStories(sources, modules) {
|
|
|
164
164
|
}
|
|
165
165
|
/**
|
|
166
166
|
* Get all individual sections flattened from groups
|
|
167
|
+
* @param {Metadata[]} sections - The sections to flatten
|
|
167
168
|
* @returns {Array} Array of all tutorial sections
|
|
168
169
|
*/
|
|
169
|
-
export function getAllSections() {
|
|
170
|
-
return sections.flatMap((group) => group.children)
|
|
170
|
+
export function getAllSections(sections) {
|
|
171
|
+
return sections.flatMap((group) => group.children ?? [])
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
/**
|
|
@@ -176,8 +177,9 @@ export function getAllSections() {
|
|
|
176
177
|
* @returns {Object|null} The section object or null if not found
|
|
177
178
|
*/
|
|
178
179
|
export function findSection(sections, slug) {
|
|
179
|
-
for (const
|
|
180
|
-
|
|
180
|
+
for (const item of sections) {
|
|
181
|
+
if (item.slug === slug) return item
|
|
182
|
+
const section = item.children?.find((child) => child.slug === slug)
|
|
181
183
|
if (section) return section
|
|
182
184
|
}
|
|
183
185
|
return {}
|
|
@@ -188,6 +190,6 @@ export function findSection(sections, slug) {
|
|
|
188
190
|
* @param {string} sectionId - The section ID to find the group for
|
|
189
191
|
* @returns {Object|null} The group object or null if not found
|
|
190
192
|
*/
|
|
191
|
-
export function findGroupForSection(sections,
|
|
192
|
-
return sections.find((group) => group.children
|
|
193
|
+
export function findGroupForSection(sections, slug) {
|
|
194
|
+
return sections.find((group) => group.children?.some((child) => child.slug === slug)) || null
|
|
193
195
|
}
|
package/src/lib/stories.spec.js
CHANGED
|
@@ -356,47 +356,47 @@ describe('stories.js', () => {
|
|
|
356
356
|
const mockSections = [
|
|
357
357
|
{
|
|
358
358
|
title: 'Welcome',
|
|
359
|
-
|
|
359
|
+
slug: '/welcome',
|
|
360
360
|
children: [
|
|
361
|
-
{ title: 'Introduction',
|
|
362
|
-
{ title: 'Getting Started',
|
|
361
|
+
{ title: 'Introduction', slug: '/welcome/intro' },
|
|
362
|
+
{ title: 'Getting Started', slug: '/welcome/get-started' }
|
|
363
363
|
]
|
|
364
364
|
},
|
|
365
365
|
{
|
|
366
366
|
title: 'Elements',
|
|
367
|
-
|
|
367
|
+
slug: '/elements',
|
|
368
368
|
children: [
|
|
369
|
-
{ title: 'List',
|
|
370
|
-
{ title: 'Button',
|
|
369
|
+
{ title: 'List', slug: '/elements/list' },
|
|
370
|
+
{ title: 'Button', slug: '/elements/button' }
|
|
371
371
|
]
|
|
372
372
|
}
|
|
373
373
|
]
|
|
374
374
|
|
|
375
375
|
it('should find the group containing a section', () => {
|
|
376
|
-
const result = findGroupForSection(mockSections, 'list')
|
|
376
|
+
const result = findGroupForSection(mockSections, '/elements/list')
|
|
377
377
|
expect(result).toEqual({
|
|
378
378
|
title: 'Elements',
|
|
379
|
-
|
|
379
|
+
slug: '/elements',
|
|
380
380
|
children: [
|
|
381
|
-
{ title: 'List',
|
|
382
|
-
{ title: 'Button',
|
|
381
|
+
{ title: 'List', slug: '/elements/list' },
|
|
382
|
+
{ title: 'Button', slug: '/elements/button' }
|
|
383
383
|
]
|
|
384
384
|
})
|
|
385
385
|
})
|
|
386
386
|
|
|
387
387
|
it('should return null when section is not found', () => {
|
|
388
|
-
const result = findGroupForSection(mockSections, 'nonexistent')
|
|
388
|
+
const result = findGroupForSection(mockSections, '/nonexistent')
|
|
389
389
|
expect(result).toBeNull()
|
|
390
390
|
})
|
|
391
391
|
|
|
392
392
|
it('should find group for section in first group', () => {
|
|
393
|
-
const result = findGroupForSection(mockSections, 'intro')
|
|
393
|
+
const result = findGroupForSection(mockSections, '/welcome/intro')
|
|
394
394
|
expect(result).toEqual({
|
|
395
395
|
title: 'Welcome',
|
|
396
|
-
|
|
396
|
+
slug: '/welcome',
|
|
397
397
|
children: [
|
|
398
|
-
{ title: 'Introduction',
|
|
399
|
-
{ title: 'Getting Started',
|
|
398
|
+
{ title: 'Introduction', slug: '/welcome/intro' },
|
|
399
|
+
{ title: 'Getting Started', slug: '/welcome/get-started' }
|
|
400
400
|
]
|
|
401
401
|
})
|
|
402
402
|
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|