@fragments-sdk/cli 0.7.13 → 0.7.15
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/bin.js +7 -7
- package/dist/{chunk-CRTN6BIW.js → chunk-QLTLLQBI.js} +2 -2
- package/dist/{chunk-TQOGBAOZ.js → chunk-WLXFE6XW.js} +91 -2
- package/dist/chunk-WLXFE6XW.js.map +1 -0
- package/dist/core/index.d.ts +44 -3
- package/dist/core/index.js +11 -3
- package/dist/{defineFragment-C6PFzZyo.d.ts → defineFragment-BI9KoPrs.d.ts} +1 -1
- package/dist/{generate-ZPERYZLF.js → generate-ICIPKCKV.js} +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{init-GID2DXB3.js → init-V42FFMUJ.js} +3 -3
- package/dist/mcp-bin.js +2 -2
- package/dist/{scan-BSMLGBX4.js → scan-X3DI2X5G.js} +2 -2
- package/dist/{service-QACVPR37.js → service-JEWWTSKI.js} +2 -2
- package/dist/{static-viewer-2RQD5QLR.js → static-viewer-JIWCYKVK.js} +2 -2
- package/dist/{tokens-A3BZIQPB.js → tokens-K2AGUUOJ.js} +2 -2
- package/dist/{viewer-CNLZQUFO.js → viewer-7I4WGVU3.js} +60 -12
- package/dist/viewer-7I4WGVU3.js.map +1 -0
- package/package.json +1 -1
- package/src/core/__tests__/preview-runtime.test.tsx +111 -0
- package/src/core/index.ts +13 -0
- package/src/core/preview-runtime.tsx +144 -0
- package/src/viewer/components/App.tsx +6 -8
- package/src/viewer/components/FragmentRenderer.tsx +61 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +10 -8
- package/src/viewer/components/PreviewFrameHost.tsx +27 -60
- package/src/viewer/components/SkeletonLoader.tsx +114 -125
- package/src/viewer/components/ThemeProvider.tsx +24 -78
- package/src/viewer/components/VariantMatrix.tsx +3 -3
- package/src/viewer/entry.tsx +26 -2
- package/src/viewer/index.html +1 -1
- package/src/viewer/public/favicon.ico +0 -0
- package/src/viewer/server.ts +1 -0
- package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +19 -0
- package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +1 -1
- package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +69 -104
- package/src/viewer/vite-plugin.ts +58 -1
- package/dist/chunk-TQOGBAOZ.js.map +0 -1
- package/dist/viewer-CNLZQUFO.js.map +0 -1
- package/src/viewer/components/StoryRenderer.tsx +0 -121
- /package/dist/{chunk-CRTN6BIW.js.map → chunk-QLTLLQBI.js.map} +0 -0
- /package/dist/{generate-ZPERYZLF.js.map → generate-ICIPKCKV.js.map} +0 -0
- /package/dist/{init-GID2DXB3.js.map → init-V42FFMUJ.js.map} +0 -0
- /package/dist/{scan-BSMLGBX4.js.map → scan-X3DI2X5G.js.map} +0 -0
- /package/dist/{service-QACVPR37.js.map → service-JEWWTSKI.js.map} +0 -0
- /package/dist/{static-viewer-2RQD5QLR.js.map → static-viewer-JIWCYKVK.js.map} +0 -0
- /package/dist/{tokens-A3BZIQPB.js.map → tokens-K2AGUUOJ.js.map} +0 -0
|
@@ -11,149 +11,138 @@ import { Skeleton, Loading } from '@fragments-sdk/ui';
|
|
|
11
11
|
*/
|
|
12
12
|
export function AppSkeleton() {
|
|
13
13
|
return (
|
|
14
|
-
<div
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
<div
|
|
15
|
+
style={{
|
|
16
|
+
display: 'grid',
|
|
17
|
+
minHeight: '100vh',
|
|
18
|
+
minHeight: '100dvh',
|
|
19
|
+
gridTemplateRows: '56px 1fr',
|
|
20
|
+
gridTemplateColumns: '260px 1fr 240px',
|
|
21
|
+
gridTemplateAreas: '"header header header" "sidebar main aside"',
|
|
22
|
+
backgroundColor: 'var(--bg-primary)',
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<div
|
|
26
|
+
style={{
|
|
27
|
+
gridArea: 'header',
|
|
25
28
|
display: 'flex',
|
|
26
29
|
alignItems: 'center',
|
|
27
30
|
justifyContent: 'space-between',
|
|
28
|
-
|
|
31
|
+
gap: '16px',
|
|
32
|
+
padding: '0 16px',
|
|
29
33
|
borderBottom: '1px solid var(--border)',
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<Skeleton variant="rectangular" style={{ width: '100%', height: '32px', borderRadius: '6px' }} />
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
{/* Component list */}
|
|
41
|
-
<div style={{ flex: 1, padding: '0 8px', overflow: 'hidden' }}>
|
|
42
|
-
{/* Category 1 */}
|
|
43
|
-
<div style={{ marginBottom: '16px' }}>
|
|
44
|
-
<Skeleton variant="text" style={{ width: '64px', margin: '0 8px 8px' }} />
|
|
45
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
46
|
-
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
47
|
-
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
48
|
-
<Skeleton variant="rectangular" style={{ width: '75%', height: '28px', borderRadius: '6px' }} />
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
{/* Category 2 */}
|
|
53
|
-
<div style={{ marginBottom: '16px' }}>
|
|
54
|
-
<Skeleton variant="text" style={{ width: '80px', margin: '0 8px 8px' }} />
|
|
55
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
56
|
-
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
57
|
-
<Skeleton variant="rectangular" style={{ width: '83%', height: '28px', borderRadius: '6px' }} />
|
|
58
|
-
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
59
|
-
<Skeleton variant="rectangular" style={{ width: '66%', height: '28px', borderRadius: '6px' }} />
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
{/* Category 3 */}
|
|
64
|
-
<div>
|
|
65
|
-
<Skeleton variant="text" style={{ width: '96px', margin: '0 8px 8px' }} />
|
|
66
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
67
|
-
<Skeleton variant="rectangular" style={{ width: '80%', height: '28px', borderRadius: '6px' }} />
|
|
68
|
-
<Skeleton variant="rectangular" style={{ width: '100%', height: '28px', borderRadius: '6px' }} />
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
34
|
+
backgroundColor: 'var(--bg-primary)',
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
|
38
|
+
<Skeleton.Circle size={20} />
|
|
39
|
+
<Skeleton variant="text" width={96} />
|
|
40
|
+
<Skeleton variant="text" width={64} />
|
|
71
41
|
</div>
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
<Skeleton
|
|
42
|
+
<Skeleton variant="rect" width={240} height={32} radius="md" />
|
|
43
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
44
|
+
<Skeleton variant="rect" width={64} height={28} radius="md" />
|
|
45
|
+
<Skeleton.Circle size={20} />
|
|
46
|
+
<Skeleton.Circle size={20} />
|
|
47
|
+
<Skeleton.Circle size={20} />
|
|
76
48
|
</div>
|
|
77
49
|
</div>
|
|
78
50
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
51
|
+
<aside
|
|
52
|
+
style={{
|
|
53
|
+
gridArea: 'sidebar',
|
|
54
|
+
borderRight: '1px solid var(--border)',
|
|
55
|
+
backgroundColor: 'var(--bg-primary)',
|
|
83
56
|
display: 'flex',
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
91
|
-
<Skeleton variant="text" style={{ width: '96px', height: '20px' }} />
|
|
92
|
-
<Skeleton variant="text" style={{ width: '64px', height: '16px' }} />
|
|
93
|
-
</div>
|
|
94
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
95
|
-
<Skeleton variant="rectangular" style={{ width: '64px', height: '28px', borderRadius: '4px' }} />
|
|
96
|
-
<Skeleton variant="rectangular" style={{ width: '96px', height: '28px', borderRadius: '4px' }} />
|
|
97
|
-
<Skeleton variant="rectangular" style={{ width: '80px', height: '28px', borderRadius: '4px' }} />
|
|
98
|
-
<Skeleton variant="circular" style={{ width: '24px', height: '24px' }} />
|
|
99
|
-
<Skeleton variant="circular" style={{ width: '24px', height: '24px' }} />
|
|
100
|
-
</div>
|
|
57
|
+
flexDirection: 'column',
|
|
58
|
+
minHeight: 0,
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
<div style={{ padding: '12px 16px' }}>
|
|
62
|
+
<Skeleton variant="rect" height={32} radius="md" />
|
|
101
63
|
</div>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
<Skeleton variant="rectangular" style={{ width: '80px', height: '28px', borderRadius: '6px' }} />
|
|
112
|
-
<Skeleton variant="rectangular" style={{ width: '56px', height: '28px', borderRadius: '6px' }} />
|
|
113
|
-
<Skeleton variant="rectangular" style={{ width: '72px', height: '28px', borderRadius: '6px' }} />
|
|
114
|
-
<Skeleton variant="rectangular" style={{ width: '48px', height: '28px', borderRadius: '6px' }} />
|
|
115
|
-
</div>
|
|
64
|
+
<div style={{ padding: '0 12px', display: 'flex', flexDirection: 'column', gap: '12px', overflow: 'hidden' }}>
|
|
65
|
+
<Skeleton variant="text" width={68} />
|
|
66
|
+
<Skeleton variant="rect" height={30} radius="md" />
|
|
67
|
+
<Skeleton variant="rect" height={30} radius="md" />
|
|
68
|
+
<Skeleton variant="rect" height={30} radius="md" />
|
|
69
|
+
<Skeleton variant="text" width={84} />
|
|
70
|
+
<Skeleton variant="rect" height={30} radius="md" />
|
|
71
|
+
<Skeleton variant="rect" height={30} radius="md" />
|
|
72
|
+
<Skeleton variant="rect" width="78%" height={30} radius="md" />
|
|
116
73
|
</div>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
|
|
120
|
-
<Skeleton variant="rectangular" style={{ width: '256px', height: '128px', borderRadius: '8px' }} />
|
|
74
|
+
<div style={{ marginTop: 'auto', padding: '12px 16px', borderTop: '1px solid var(--border-subtle)' }}>
|
|
75
|
+
<Skeleton variant="text" width={92} />
|
|
121
76
|
</div>
|
|
77
|
+
</aside>
|
|
122
78
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<div style={{
|
|
143
|
-
<Skeleton variant="text"
|
|
144
|
-
<Skeleton variant="
|
|
79
|
+
<main
|
|
80
|
+
style={{
|
|
81
|
+
gridArea: 'main',
|
|
82
|
+
display: 'flex',
|
|
83
|
+
flexDirection: 'column',
|
|
84
|
+
minWidth: 0,
|
|
85
|
+
minHeight: 0,
|
|
86
|
+
backgroundColor: 'var(--bg-primary)',
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<div style={{ flex: 1, padding: '20px', overflow: 'hidden' }}>
|
|
90
|
+
<div
|
|
91
|
+
style={{
|
|
92
|
+
border: '1px solid var(--border)',
|
|
93
|
+
borderRadius: '10px',
|
|
94
|
+
overflow: 'hidden',
|
|
95
|
+
marginBottom: '16px',
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
<div style={{ padding: '12px 14px', borderBottom: '1px solid var(--border-subtle)', backgroundColor: 'var(--bg-secondary)', display: 'flex', gap: '10px' }}>
|
|
99
|
+
<Skeleton variant="text" width={84} />
|
|
100
|
+
<Skeleton variant="text" width={120} />
|
|
101
|
+
</div>
|
|
102
|
+
<div style={{ padding: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
103
|
+
<Skeleton variant="rect" width={280} height={120} radius="lg" />
|
|
145
104
|
</div>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div
|
|
108
|
+
style={{
|
|
109
|
+
border: '1px solid var(--border)',
|
|
110
|
+
borderRadius: '10px',
|
|
111
|
+
overflow: 'hidden',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<div style={{ height: '40px', display: 'flex', alignItems: 'center', gap: '6px', padding: '0 14px', borderBottom: '1px solid var(--border-subtle)', backgroundColor: 'var(--bg-secondary)' }}>
|
|
115
|
+
<Skeleton variant="rect" width={52} height={22} radius="md" />
|
|
116
|
+
<Skeleton variant="rect" width={88} height={22} radius="md" />
|
|
117
|
+
<Skeleton variant="rect" width={58} height={22} radius="md" />
|
|
149
118
|
</div>
|
|
150
|
-
<div style={{
|
|
151
|
-
<Skeleton
|
|
152
|
-
<Skeleton variant="rectangular" style={{ width: '80px', height: '32px', borderRadius: '4px' }} />
|
|
119
|
+
<div style={{ padding: '16px' }}>
|
|
120
|
+
<Skeleton.Text lines={4} lastLineWidth={72} />
|
|
153
121
|
</div>
|
|
154
122
|
</div>
|
|
155
123
|
</div>
|
|
156
|
-
</
|
|
124
|
+
</main>
|
|
125
|
+
|
|
126
|
+
<aside
|
|
127
|
+
style={{
|
|
128
|
+
gridArea: 'aside',
|
|
129
|
+
borderLeft: '1px solid var(--border)',
|
|
130
|
+
backgroundColor: 'var(--bg-primary)',
|
|
131
|
+
padding: '16px 14px',
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
|
135
|
+
<Skeleton variant="text" width={92} />
|
|
136
|
+
<Skeleton variant="text" width={64} />
|
|
137
|
+
<Skeleton variant="text" width={48} />
|
|
138
|
+
<Skeleton variant="text" width={76} />
|
|
139
|
+
<Skeleton variant="rect" height={1} />
|
|
140
|
+
<Skeleton variant="text" width={86} />
|
|
141
|
+
<Skeleton variant="rect" width="100%" height={26} radius="md" />
|
|
142
|
+
<Skeleton variant="rect" width="84%" height={26} radius="md" />
|
|
143
|
+
<Skeleton variant="rect" width="70%" height={26} radius="md" />
|
|
144
|
+
</div>
|
|
145
|
+
</aside>
|
|
157
146
|
</div>
|
|
158
147
|
);
|
|
159
148
|
}
|
|
@@ -1,96 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ThemeProvider as FragmentsThemeProvider,
|
|
4
|
+
useTheme as useFragmentsTheme,
|
|
5
|
+
} from '@fragments-sdk/ui';
|
|
2
6
|
import { BRAND } from '../../core/index.js';
|
|
3
7
|
|
|
4
8
|
type Theme = 'light' | 'dark' | 'system';
|
|
5
9
|
|
|
6
|
-
interface
|
|
10
|
+
interface ViewerThemeContextValue {
|
|
7
11
|
theme: Theme;
|
|
8
12
|
resolvedTheme: 'light' | 'dark';
|
|
9
13
|
setTheme: (theme: Theme) => void;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
13
|
-
|
|
14
16
|
const STORAGE_KEY = `${BRAND.storagePrefix}theme`;
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
function getStoredTheme(): Theme {
|
|
22
|
-
if (typeof window === 'undefined') return 'system';
|
|
23
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
24
|
-
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
|
25
|
-
return stored;
|
|
26
|
-
}
|
|
27
|
-
return 'system';
|
|
28
|
-
}
|
|
29
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Viewer theme adapter that reuses the shared UI ThemeProvider while preserving
|
|
20
|
+
* existing viewer storage keys and API shape.
|
|
21
|
+
*/
|
|
30
22
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
31
|
-
const [theme, setThemeState] = useState<Theme>(getStoredTheme);
|
|
32
|
-
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(() => {
|
|
33
|
-
const stored = getStoredTheme();
|
|
34
|
-
return stored === 'system' ? getSystemTheme() : stored;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const setTheme = useCallback((newTheme: Theme) => {
|
|
38
|
-
setThemeState(newTheme);
|
|
39
|
-
localStorage.setItem(STORAGE_KEY, newTheme);
|
|
40
|
-
|
|
41
|
-
// Immediately update resolved theme to avoid timing issues
|
|
42
|
-
const resolved = newTheme === 'system' ? getSystemTheme() : newTheme;
|
|
43
|
-
setResolvedTheme(resolved);
|
|
44
|
-
|
|
45
|
-
// Immediately apply to document
|
|
46
|
-
if (resolved === 'dark') {
|
|
47
|
-
document.documentElement.classList.add('dark');
|
|
48
|
-
} else {
|
|
49
|
-
document.documentElement.classList.remove('dark');
|
|
50
|
-
}
|
|
51
|
-
}, []);
|
|
52
|
-
|
|
53
|
-
// Update resolved theme and apply to document
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
const resolved = theme === 'system' ? getSystemTheme() : theme;
|
|
56
|
-
setResolvedTheme(resolved);
|
|
57
|
-
|
|
58
|
-
if (resolved === 'dark') {
|
|
59
|
-
document.documentElement.classList.add('dark');
|
|
60
|
-
} else {
|
|
61
|
-
document.documentElement.classList.remove('dark');
|
|
62
|
-
}
|
|
63
|
-
}, [theme]);
|
|
64
|
-
|
|
65
|
-
// Listen for system theme changes
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (theme !== 'system') return;
|
|
68
|
-
|
|
69
|
-
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
70
|
-
const handler = (e: MediaQueryListEvent) => {
|
|
71
|
-
setResolvedTheme(e.matches ? 'dark' : 'light');
|
|
72
|
-
if (e.matches) {
|
|
73
|
-
document.documentElement.classList.add('dark');
|
|
74
|
-
} else {
|
|
75
|
-
document.documentElement.classList.remove('dark');
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
mediaQuery.addEventListener('change', handler);
|
|
80
|
-
return () => mediaQuery.removeEventListener('change', handler);
|
|
81
|
-
}, [theme]);
|
|
82
|
-
|
|
83
23
|
return (
|
|
84
|
-
<
|
|
24
|
+
<FragmentsThemeProvider
|
|
25
|
+
defaultMode="system"
|
|
26
|
+
storageKey={STORAGE_KEY}
|
|
27
|
+
attribute="class"
|
|
28
|
+
>
|
|
85
29
|
{children}
|
|
86
|
-
</
|
|
30
|
+
</FragmentsThemeProvider>
|
|
87
31
|
);
|
|
88
32
|
}
|
|
89
33
|
|
|
90
|
-
export function useTheme() {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
34
|
+
export function useTheme(): ViewerThemeContextValue {
|
|
35
|
+
const { mode, setMode, resolvedMode } = useFragmentsTheme();
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
theme: mode,
|
|
39
|
+
resolvedTheme: resolvedMode,
|
|
40
|
+
setTheme: setMode,
|
|
41
|
+
};
|
|
96
42
|
}
|
|
@@ -14,7 +14,7 @@ import { useState, useMemo, useRef, useCallback } from "react";
|
|
|
14
14
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
15
15
|
import type { FragmentVariant } from "../../core/index.js";
|
|
16
16
|
import { ErrorBoundary } from "./ErrorBoundary.js";
|
|
17
|
-
import {
|
|
17
|
+
import { FragmentRenderer, LoaderIndicator } from "./FragmentRenderer.js";
|
|
18
18
|
import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
|
|
19
19
|
import { ChevronDownIcon } from "./Icons.js";
|
|
20
20
|
|
|
@@ -389,7 +389,7 @@ function VariantCard({
|
|
|
389
389
|
</div>
|
|
390
390
|
}
|
|
391
391
|
>
|
|
392
|
-
<
|
|
392
|
+
<FragmentRenderer variant={variant}>
|
|
393
393
|
{(content, isLoading, error) => {
|
|
394
394
|
if (isLoading) {
|
|
395
395
|
return (
|
|
@@ -407,7 +407,7 @@ function VariantCard({
|
|
|
407
407
|
}
|
|
408
408
|
return content;
|
|
409
409
|
}}
|
|
410
|
-
</
|
|
410
|
+
</FragmentRenderer>
|
|
411
411
|
</ErrorBoundary>
|
|
412
412
|
</div>
|
|
413
413
|
)}
|
package/src/viewer/entry.tsx
CHANGED
|
@@ -149,6 +149,8 @@ type FragmentItem = {
|
|
|
149
149
|
let fragments: FragmentItem[] = window.__FRAGMENTS__ ?? [];
|
|
150
150
|
let loadError: string | null = window.__FRAGMENTS_ERROR__ ?? null;
|
|
151
151
|
let appRoot: Root | null = null;
|
|
152
|
+
const SKELETON_DELAY_MS = 120;
|
|
153
|
+
const MIN_SKELETON_DISPLAY_MS = 220;
|
|
152
154
|
|
|
153
155
|
// Filter helper
|
|
154
156
|
function filterValidFragments(items: FragmentItem[]): FragmentItem[] {
|
|
@@ -257,11 +259,33 @@ if (rootElement) {
|
|
|
257
259
|
|
|
258
260
|
// Show skeleton immediately if no fragments yet
|
|
259
261
|
if (fragments.length === 0 && !loadError) {
|
|
260
|
-
|
|
262
|
+
const loadStartedAt = Date.now();
|
|
263
|
+
let skeletonShown = false;
|
|
264
|
+
|
|
265
|
+
const skeletonTimer = setTimeout(() => {
|
|
266
|
+
skeletonShown = true;
|
|
267
|
+
renderApp(true);
|
|
268
|
+
}, SKELETON_DELAY_MS);
|
|
261
269
|
|
|
262
270
|
// Load fragments asynchronously and re-render
|
|
263
271
|
loadFragmentsFromVirtualModule().then(() => {
|
|
264
|
-
|
|
272
|
+
clearTimeout(skeletonTimer);
|
|
273
|
+
|
|
274
|
+
if (!skeletonShown) {
|
|
275
|
+
renderApp(false);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const elapsed = Date.now() - loadStartedAt;
|
|
280
|
+
const remaining = Math.max(MIN_SKELETON_DISPLAY_MS - elapsed, 0);
|
|
281
|
+
if (remaining === 0) {
|
|
282
|
+
renderApp(false);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
renderApp(false);
|
|
288
|
+
}, remaining);
|
|
265
289
|
});
|
|
266
290
|
} else {
|
|
267
291
|
// We have fragments from window, render immediately
|
package/src/viewer/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Fragments</title>
|
|
7
|
-
<link rel="icon" type="image/
|
|
7
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link
|
|
Binary file
|
package/src/viewer/server.ts
CHANGED
|
@@ -230,6 +230,7 @@ export async function createDevServer(
|
|
|
230
230
|
const fragmentsConfig: InlineConfig = {
|
|
231
231
|
configFile: false, // Don't load config again
|
|
232
232
|
root: projectRoot, // Run from PROJECT root
|
|
233
|
+
publicDir: resolve(viewerRoot, "public"), // Serve static assets (favicon) from viewer
|
|
233
234
|
base: "/",
|
|
234
235
|
|
|
235
236
|
server: {
|
|
@@ -85,7 +85,26 @@ export function DocsHeaderBar({
|
|
|
85
85
|
|
|
86
86
|
<NavigationMenu.Viewport />
|
|
87
87
|
|
|
88
|
+
<NavigationMenu.MobileBrand>{brand}</NavigationMenu.MobileBrand>
|
|
89
|
+
|
|
88
90
|
<NavigationMenu.MobileContent>
|
|
91
|
+
{/* Render all headerNav items in the mobile drawer */}
|
|
92
|
+
<NavigationMenu.MobileSection>
|
|
93
|
+
{headerNav.map((entry) =>
|
|
94
|
+
isDropdown(entry) ? (
|
|
95
|
+
entry.items.map((child) => (
|
|
96
|
+
<NavigationMenu.Link key={child.href} href={child.href} asChild>
|
|
97
|
+
{renderLink({ href: child.href, label: child.label })}
|
|
98
|
+
</NavigationMenu.Link>
|
|
99
|
+
))
|
|
100
|
+
) : (
|
|
101
|
+
<NavigationMenu.Link key={entry.href} href={entry.href} asChild>
|
|
102
|
+
{renderLink({ href: entry.href, label: entry.label })}
|
|
103
|
+
</NavigationMenu.Link>
|
|
104
|
+
)
|
|
105
|
+
)}
|
|
106
|
+
</NavigationMenu.MobileSection>
|
|
107
|
+
|
|
89
108
|
{mobileSections.map((section) => (
|
|
90
109
|
<NavigationMenu.MobileSection key={section.title} label={section.title}>
|
|
91
110
|
{section.items.map((item) => (
|
|
@@ -52,7 +52,7 @@ export function useDocsPageAside() {
|
|
|
52
52
|
export function DocsPageAsidePortal({ children, width = '320px' }: { children: React.ReactNode; width?: string }) {
|
|
53
53
|
const { setAsideVisible, setAsideWidth, asideContainer } = useDocsPageAside();
|
|
54
54
|
|
|
55
|
-
React.
|
|
55
|
+
React.useLayoutEffect(() => {
|
|
56
56
|
setAsideVisible(true);
|
|
57
57
|
setAsideWidth(width);
|
|
58
58
|
|