@fragments-sdk/cli 0.7.8 → 0.7.10
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 +13 -13
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-CWKQQR6C.js → chunk-57OW43NL.js} +3 -3
- package/dist/chunk-57OW43NL.js.map +1 -0
- package/dist/{chunk-AA6CAHCZ.js → chunk-7CRC46HV.js} +2 -2
- package/dist/chunk-7CRC46HV.js.map +1 -0
- package/dist/{chunk-3JPJTU25.js → chunk-CRTN6BIW.js} +5 -5
- package/dist/chunk-CRTN6BIW.js.map +1 -0
- package/dist/{chunk-LHIIBI6F.js → chunk-M42XIHPV.js} +2 -2
- package/dist/{chunk-2EFVPE5Q.js → chunk-TQOGBAOZ.js} +2 -2
- package/dist/chunk-TQOGBAOZ.js.map +1 -0
- package/dist/core/index.d.ts +1944 -0
- package/dist/{core-YAPWXDZW.js → core/index.js} +5 -5
- package/dist/defineFragment-C6PFzZyo.d.ts +656 -0
- package/dist/{generate-LEBVZCCH.js → generate-ZPERYZLF.js} +4 -4
- package/dist/index.d.ts +4 -159
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/{init-4VXL3Q6N.js → init-GID2DXB3.js} +69 -7
- package/dist/init-GID2DXB3.js.map +1 -0
- package/dist/mcp-bin.js +3 -3
- package/dist/{scan-3NYSRF6G.js → scan-BSMLGBX4.js} +5 -5
- package/dist/{service-HL6TMP3B.js → service-QACVPR37.js} +3 -3
- package/dist/{static-viewer-KLD24I4R.js → static-viewer-2RQD5QLR.js} +3 -3
- package/dist/{test-Y7YZOJLE.js → test-36UELXTE.js} +3 -3
- package/dist/{tokens-M4FCJKBK.js → tokens-A3BZIQPB.js} +4 -4
- package/dist/{viewer-ZWQQ74FV.js → viewer-ZA7WK3EY.js} +137 -30
- package/dist/viewer-ZA7WK3EY.js.map +1 -0
- package/package.json +6 -1
- package/src/commands/add.ts +1 -1
- package/src/commands/init.ts +84 -4
- package/src/core/defineFragment.ts +1 -1
- package/src/core/figma.ts +1 -1
- package/src/core/index.ts +2 -2
- package/src/core/loader.ts +3 -3
- package/src/core/schema.ts +1 -1
- package/src/index.ts +6 -0
- package/src/migrate/converter.ts +1 -1
- package/src/service/snippet-validation.test.ts +5 -5
- package/src/service/snippet-validation.ts +0 -1
- package/src/viewer/__tests__/viewer-integration.test.ts +16 -23
- package/src/viewer/assets/fragments-logo.ts +4 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +1 -1
- package/src/viewer/components/App.tsx +138 -97
- package/src/viewer/components/BottomPanel.tsx +1 -1
- package/src/viewer/components/CodePanel.naming.test.tsx +1 -1
- package/src/viewer/components/CodePanel.tsx +1 -1
- package/src/viewer/components/CommandPalette.tsx +1 -1
- package/src/viewer/components/ComponentGraph.tsx +1 -1
- package/src/viewer/components/ComponentHeader.tsx +1 -1
- package/src/viewer/components/ContractPanel.tsx +1 -1
- package/src/viewer/components/ErrorBoundary.tsx +1 -1
- package/src/viewer/components/HealthDashboard.tsx +1 -1
- package/src/viewer/components/HmrStatusIndicator.tsx +1 -1
- package/src/viewer/components/InteractionsPanel.tsx +1 -1
- package/src/viewer/components/IsolatedRender.tsx +1 -1
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +1 -1
- package/src/viewer/components/LandingPage.tsx +1 -1
- package/src/viewer/components/Layout.tsx +1 -1
- package/src/viewer/components/LeftSidebar.tsx +105 -18
- package/src/viewer/components/MultiViewportPreview.tsx +1 -1
- package/src/viewer/components/PreviewArea.tsx +1 -2
- package/src/viewer/components/PreviewFrameHost.tsx +0 -4
- package/src/viewer/components/PreviewMenu.tsx +247 -0
- package/src/viewer/components/PreviewToolbar.tsx +1 -1
- package/src/viewer/components/PropsEditor.tsx +1 -1
- package/src/viewer/components/PropsTable.tsx +1 -1
- package/src/viewer/components/RightSidebar.tsx +1 -1
- package/src/viewer/components/ScreenshotButton.tsx +1 -1
- package/src/viewer/components/SkeletonLoader.tsx +1 -1
- package/src/viewer/components/Toast.tsx +2 -2
- package/src/viewer/components/TokenStylePanel.tsx +1 -1
- package/src/viewer/components/VariantMatrix.tsx +1 -1
- package/src/viewer/components/VariantTabs.tsx +1 -1
- package/src/viewer/components/ViewportSelector.tsx +1 -1
- package/src/viewer/constants/ui.ts +14 -0
- package/src/viewer/entry.tsx +3 -4
- package/src/viewer/hooks/useKeyboardShortcuts.ts +65 -17
- package/src/viewer/hooks/useViewSettings.ts +1 -2
- package/src/viewer/index.ts +1 -1
- package/src/viewer/preview-frame.html +6 -9
- package/src/viewer/server.ts +80 -7
- package/src/viewer/styles/globals.css +12 -51
- package/src/viewer/vite-plugin.ts +70 -9
- package/dist/chunk-2EFVPE5Q.js.map +0 -1
- package/dist/chunk-3JPJTU25.js.map +0 -1
- package/dist/chunk-AA6CAHCZ.js.map +0 -1
- package/dist/chunk-CWKQQR6C.js.map +0 -1
- package/dist/init-4VXL3Q6N.js.map +0 -1
- package/dist/viewer-ZWQQ74FV.js.map +0 -1
- /package/dist/{chunk-LHIIBI6F.js.map → chunk-M42XIHPV.js.map} +0 -0
- /package/dist/{core-YAPWXDZW.js.map → core/index.js.map} +0 -0
- /package/dist/{generate-LEBVZCCH.js.map → generate-ZPERYZLF.js.map} +0 -0
- /package/dist/{scan-3NYSRF6G.js.map → scan-BSMLGBX4.js.map} +0 -0
- /package/dist/{service-HL6TMP3B.js.map → service-QACVPR37.js.map} +0 -0
- /package/dist/{static-viewer-KLD24I4R.js.map → static-viewer-2RQD5QLR.js.map} +0 -0
- /package/dist/{test-Y7YZOJLE.js.map → test-36UELXTE.js.map} +0 -0
- /package/dist/{tokens-M4FCJKBK.js.map → tokens-A3BZIQPB.js.map} +0 -0
|
@@ -126,6 +126,20 @@ export const VIEWPORT_PRESETS = {
|
|
|
126
126
|
|
|
127
127
|
export type ViewportPreset = keyof typeof VIEWPORT_PRESETS | 'custom';
|
|
128
128
|
|
|
129
|
+
export interface ViewportSize {
|
|
130
|
+
width: number | null;
|
|
131
|
+
height: number | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get viewport width from preset.
|
|
136
|
+
*/
|
|
137
|
+
export function getViewportWidth(viewport: ViewportPreset, customSize: ViewportSize): number | null {
|
|
138
|
+
if (viewport === 'custom') return customSize.width;
|
|
139
|
+
if (viewport === 'responsive') return null;
|
|
140
|
+
return VIEWPORT_PRESETS[viewport]?.width ?? null;
|
|
141
|
+
}
|
|
142
|
+
|
|
129
143
|
/**
|
|
130
144
|
* Get CSS background style for preview pane.
|
|
131
145
|
*/
|
package/src/viewer/entry.tsx
CHANGED
|
@@ -4,11 +4,10 @@ import { App } from "./components/App.js";
|
|
|
4
4
|
import { ThemeProvider } from "./components/ThemeProvider.js";
|
|
5
5
|
import { ToastProvider } from "./components/Toast.js";
|
|
6
6
|
import { AppSkeleton } from "./components/SkeletonLoader.js";
|
|
7
|
-
//
|
|
8
|
-
|
|
7
|
+
// Fragments UI globals - base resets (box-sizing, body, scrollbars) + --fui-* CSS variables
|
|
8
|
+
import "@fragments-sdk/ui";
|
|
9
|
+
// Viewer-specific token aliases and utility classes
|
|
9
10
|
import "./styles/globals.css";
|
|
10
|
-
// Fragments UI globals - adds --fui-* CSS variables for dogfooding UI components
|
|
11
|
-
import "@fragments/ui";
|
|
12
11
|
|
|
13
12
|
// App-level error boundary that catches any unhandled errors
|
|
14
13
|
class AppErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean; error: Error | null; errorInfo: ErrorInfo | null }> {
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
* Keyboard Shortcuts Hook
|
|
3
3
|
*
|
|
4
4
|
* Provides global keyboard navigation for the viewer:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
5
|
+
* - ⌘↓/⌘↑: Navigate components
|
|
6
|
+
* - ⌘←/⌘→: Navigate variants
|
|
7
|
+
* - j/k or ↓/↑: Navigate components (legacy)
|
|
8
|
+
* - [/] or ←/→: Navigate variants (legacy)
|
|
7
9
|
* - 1-9: Jump to variant by number
|
|
8
10
|
* - t: Toggle preview theme
|
|
9
11
|
* - p: Toggle panel
|
|
12
|
+
* - m: Toggle matrix view
|
|
13
|
+
* - r: Toggle responsive view
|
|
10
14
|
* - cmd+shift+c: Copy link
|
|
11
15
|
* - ?: Show shortcuts help
|
|
12
16
|
* - Escape: Close modals/clear search
|
|
@@ -29,6 +33,10 @@ export interface ShortcutActions {
|
|
|
29
33
|
toggleTheme: () => void;
|
|
30
34
|
/** Toggle panel open/closed */
|
|
31
35
|
togglePanel: () => void;
|
|
36
|
+
/** Toggle matrix view */
|
|
37
|
+
toggleMatrix: () => void;
|
|
38
|
+
/** Toggle responsive/multi-viewport view */
|
|
39
|
+
toggleResponsive: () => void;
|
|
32
40
|
/** Copy shareable link */
|
|
33
41
|
copyLink: () => void;
|
|
34
42
|
/** Show shortcuts help */
|
|
@@ -84,6 +92,7 @@ export function useKeyboardShortcuts(
|
|
|
84
92
|
|
|
85
93
|
// Don't capture events from input elements (except for specific shortcuts)
|
|
86
94
|
const isInput = isInputElement(event.target);
|
|
95
|
+
const isMeta = event.metaKey || event.ctrlKey;
|
|
87
96
|
|
|
88
97
|
// Global shortcuts that work even in inputs
|
|
89
98
|
if (event.key === "Escape") {
|
|
@@ -92,19 +101,41 @@ export function useKeyboardShortcuts(
|
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
// cmd+shift+c: Copy link (works everywhere)
|
|
95
|
-
if (
|
|
104
|
+
if (isMeta && event.shiftKey && event.key === "c") {
|
|
96
105
|
event.preventDefault();
|
|
97
106
|
actions.copyLink?.();
|
|
98
107
|
return;
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
// cmd+k: Open search/command palette (works everywhere)
|
|
102
|
-
if (
|
|
111
|
+
if (isMeta && event.key === "k") {
|
|
103
112
|
event.preventDefault();
|
|
104
113
|
actions.openSearch?.();
|
|
105
114
|
return;
|
|
106
115
|
}
|
|
107
116
|
|
|
117
|
+
// cmd+arrow: Navigation (works everywhere)
|
|
118
|
+
if (isMeta && event.key === "ArrowDown") {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
actions.nextComponent?.();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (isMeta && event.key === "ArrowUp") {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
actions.prevComponent?.();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (isMeta && event.key === "ArrowRight") {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
actions.nextVariant?.();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (isMeta && event.key === "ArrowLeft") {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
actions.prevVariant?.();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
108
139
|
// Skip other shortcuts if in input
|
|
109
140
|
if (isInput) return;
|
|
110
141
|
|
|
@@ -120,7 +151,7 @@ export function useKeyboardShortcuts(
|
|
|
120
151
|
// Component navigation
|
|
121
152
|
case "j":
|
|
122
153
|
case "ArrowDown":
|
|
123
|
-
if (!
|
|
154
|
+
if (!isMeta && !event.altKey) {
|
|
124
155
|
event.preventDefault();
|
|
125
156
|
actions.nextComponent?.();
|
|
126
157
|
}
|
|
@@ -128,7 +159,7 @@ export function useKeyboardShortcuts(
|
|
|
128
159
|
|
|
129
160
|
case "k":
|
|
130
161
|
case "ArrowUp":
|
|
131
|
-
if (!
|
|
162
|
+
if (!isMeta && !event.altKey) {
|
|
132
163
|
event.preventDefault();
|
|
133
164
|
actions.prevComponent?.();
|
|
134
165
|
}
|
|
@@ -137,7 +168,7 @@ export function useKeyboardShortcuts(
|
|
|
137
168
|
// Variant navigation
|
|
138
169
|
case "[":
|
|
139
170
|
case "ArrowLeft":
|
|
140
|
-
if (!
|
|
171
|
+
if (!isMeta && !event.altKey) {
|
|
141
172
|
event.preventDefault();
|
|
142
173
|
actions.prevVariant?.();
|
|
143
174
|
}
|
|
@@ -145,7 +176,7 @@ export function useKeyboardShortcuts(
|
|
|
145
176
|
|
|
146
177
|
case "]":
|
|
147
178
|
case "ArrowRight":
|
|
148
|
-
if (!
|
|
179
|
+
if (!isMeta && !event.altKey) {
|
|
149
180
|
event.preventDefault();
|
|
150
181
|
actions.nextVariant?.();
|
|
151
182
|
}
|
|
@@ -161,7 +192,7 @@ export function useKeyboardShortcuts(
|
|
|
161
192
|
case "7":
|
|
162
193
|
case "8":
|
|
163
194
|
case "9":
|
|
164
|
-
if (!
|
|
195
|
+
if (!isMeta && !event.altKey) {
|
|
165
196
|
const index = parseInt(event.key, 10) - 1;
|
|
166
197
|
if (index < variantCount) {
|
|
167
198
|
event.preventDefault();
|
|
@@ -172,7 +203,7 @@ export function useKeyboardShortcuts(
|
|
|
172
203
|
|
|
173
204
|
// Theme toggle
|
|
174
205
|
case "t":
|
|
175
|
-
if (!
|
|
206
|
+
if (!isMeta && !event.altKey) {
|
|
176
207
|
event.preventDefault();
|
|
177
208
|
actions.toggleTheme?.();
|
|
178
209
|
}
|
|
@@ -180,12 +211,28 @@ export function useKeyboardShortcuts(
|
|
|
180
211
|
|
|
181
212
|
// Panel toggle
|
|
182
213
|
case "p":
|
|
183
|
-
if (!
|
|
214
|
+
if (!isMeta && !event.altKey) {
|
|
184
215
|
event.preventDefault();
|
|
185
216
|
actions.togglePanel?.();
|
|
186
217
|
}
|
|
187
218
|
break;
|
|
188
219
|
|
|
220
|
+
// Matrix view toggle
|
|
221
|
+
case "m":
|
|
222
|
+
if (!isMeta && !event.altKey) {
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
actions.toggleMatrix?.();
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
// Responsive view toggle
|
|
229
|
+
case "v":
|
|
230
|
+
if (!isMeta && !event.altKey) {
|
|
231
|
+
event.preventDefault();
|
|
232
|
+
actions.toggleResponsive?.();
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
|
|
189
236
|
// Help
|
|
190
237
|
case "?":
|
|
191
238
|
event.preventDefault();
|
|
@@ -206,14 +253,15 @@ export function useKeyboardShortcuts(
|
|
|
206
253
|
* Keyboard shortcuts data for help display
|
|
207
254
|
*/
|
|
208
255
|
export const SHORTCUTS = [
|
|
209
|
-
{ keys: ["
|
|
210
|
-
{ keys: ["
|
|
211
|
-
{ keys: ["
|
|
212
|
-
{ keys: ["
|
|
256
|
+
{ keys: ["⌘↓"], description: "Next component" },
|
|
257
|
+
{ keys: ["⌘↑"], description: "Previous component" },
|
|
258
|
+
{ keys: ["⌘→"], description: "Next variant" },
|
|
259
|
+
{ keys: ["⌘←"], description: "Previous variant" },
|
|
213
260
|
{ keys: ["1-9"], description: "Go to variant" },
|
|
214
261
|
{ keys: ["t"], description: "Toggle preview theme" },
|
|
215
|
-
{ keys: ["p"], description: "Toggle panel" },
|
|
216
|
-
{ keys: ["
|
|
262
|
+
{ keys: ["p"], description: "Toggle addons panel" },
|
|
263
|
+
{ keys: ["m"], description: "Matrix view" },
|
|
264
|
+
{ keys: ["v"], description: "Responsive view" },
|
|
217
265
|
{ keys: ["/", "⌘K"], description: "Search" },
|
|
218
266
|
{ keys: ["?"], description: "Show shortcuts" },
|
|
219
267
|
{ keys: ["Esc"], description: "Close / Clear" },
|
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useReducer, useCallback, useMemo } from 'react';
|
|
7
|
-
import type { ZoomLevel, BackgroundOption } from '../
|
|
7
|
+
import type { ZoomLevel, BackgroundOption, ViewportPreset, ViewportSize } from '../constants/ui.js';
|
|
8
8
|
|
|
9
9
|
// Preview theme type (for canvas theming)
|
|
10
10
|
export type PreviewTheme = 'light' | 'dark';
|
|
11
|
-
import type { ViewportPreset, ViewportSize } from '../components/ViewportSelector.js';
|
|
12
11
|
|
|
13
12
|
interface ViewSettings {
|
|
14
13
|
zoom: ZoomLevel;
|
package/src/viewer/index.ts
CHANGED
|
@@ -7,5 +7,5 @@ export { fragmentsPlugin } from "./vite-plugin.js";
|
|
|
7
7
|
export type { FragmentsPluginOptions } from "./vite-plugin.js";
|
|
8
8
|
|
|
9
9
|
// Note: Viewer components (App, Layout, etc.) are NOT exported here.
|
|
10
|
-
// They use @fragments/ui which is
|
|
10
|
+
// They use @fragments-sdk/ui which is resolved by the Vite dev server.
|
|
11
11
|
// Vite serves these components directly from source during development.
|
|
@@ -30,19 +30,16 @@
|
|
|
30
30
|
width: 100%;
|
|
31
31
|
min-height: 100vh;
|
|
32
32
|
display: flex;
|
|
33
|
-
align-items:
|
|
34
|
-
justify-content:
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
35
|
padding: 24px;
|
|
36
36
|
box-sizing: border-box;
|
|
37
37
|
overflow: auto;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
body[data-preview-mode='centered'] #preview-root {
|
|
41
|
-
align-items: center;
|
|
42
|
-
justify-content: center;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
40
|
body[data-preview-mode='full-bleed'] #preview-root {
|
|
41
|
+
align-items: flex-start;
|
|
42
|
+
justify-content: flex-start;
|
|
46
43
|
padding: 0;
|
|
47
44
|
min-height: 100vh;
|
|
48
45
|
}
|
|
@@ -119,8 +116,8 @@
|
|
|
119
116
|
}
|
|
120
117
|
</style>
|
|
121
118
|
</head>
|
|
122
|
-
<body
|
|
123
|
-
<main>
|
|
119
|
+
<body>
|
|
120
|
+
<main style="height: 100%">
|
|
124
121
|
<h1 class="sr-only">Component Preview</h1>
|
|
125
122
|
<div id="preview-root">
|
|
126
123
|
<div class="preview-loading">
|
package/src/viewer/server.ts
CHANGED
|
@@ -37,6 +37,11 @@ const localUiLibRoot = resolve(packagesRoot, "../libs/ui/src");
|
|
|
37
37
|
* In the monorepo: use the local libs/ui/src source.
|
|
38
38
|
*/
|
|
39
39
|
function resolveUiLib(nodeModulesDir: string): string {
|
|
40
|
+
// Prefer local monorepo source (when CLI is linked for development)
|
|
41
|
+
const localIndex = join(localUiLibRoot, "index.ts");
|
|
42
|
+
if (existsSync(localIndex)) {
|
|
43
|
+
return localUiLibRoot;
|
|
44
|
+
}
|
|
40
45
|
// Check for installed @fragments-sdk/ui in project's node_modules
|
|
41
46
|
const installedUi = join(nodeModulesDir, "@fragments-sdk/ui/src/index.ts");
|
|
42
47
|
if (existsSync(installedUi)) {
|
|
@@ -47,10 +52,77 @@ function resolveUiLib(nodeModulesDir: string): string {
|
|
|
47
52
|
if (existsSync(installedUiDist)) {
|
|
48
53
|
return installedUiDist;
|
|
49
54
|
}
|
|
50
|
-
// Fallback to local monorepo source
|
|
51
55
|
return localUiLibRoot;
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Vite plugin to handle CJS-only packages imported from within node_modules.
|
|
60
|
+
* Vite's optimizeDeps only redirects imports from source files to pre-bundled ESM,
|
|
61
|
+
* not imports from other node_modules packages. This plugin provides virtual ESM
|
|
62
|
+
* wrappers for known CJS-only packages.
|
|
63
|
+
*/
|
|
64
|
+
function cjsInteropPlugin(nodeModulesPath: string) {
|
|
65
|
+
// Virtual ESM wrappers for CJS-only packages
|
|
66
|
+
const virtualModules: Record<string, string> = {
|
|
67
|
+
// use-sync-external-store/shim is CJS-only, used by @base-ui/react.
|
|
68
|
+
// Since React 18+, useSyncExternalStore is built-in — redirect to React's native export.
|
|
69
|
+
'use-sync-external-store/shim': `export { useSyncExternalStore } from 'react';`,
|
|
70
|
+
// use-sync-external-store/shim/with-selector is CJS-only, used by @base-ui/utils.
|
|
71
|
+
// Provides useSyncExternalStoreWithSelector — a selector wrapper around useSyncExternalStore.
|
|
72
|
+
'use-sync-external-store/shim/with-selector': `
|
|
73
|
+
import { useSyncExternalStore, useRef, useEffect, useMemo, useDebugValue } from 'react';
|
|
74
|
+
export function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
|
|
75
|
+
var instRef = useRef(null);
|
|
76
|
+
if (instRef.current === null) instRef.current = { hasValue: false, value: null };
|
|
77
|
+
var inst = instRef.current;
|
|
78
|
+
var _useMemo = useMemo(function () {
|
|
79
|
+
var hasMemo = false, memoizedSnapshot, memoizedSelection;
|
|
80
|
+
function memoizedSelector(nextSnapshot) {
|
|
81
|
+
if (!hasMemo) {
|
|
82
|
+
hasMemo = true;
|
|
83
|
+
memoizedSnapshot = nextSnapshot;
|
|
84
|
+
var nextSelection = selector(nextSnapshot);
|
|
85
|
+
if (isEqual !== undefined && inst.hasValue && isEqual(inst.value, nextSelection))
|
|
86
|
+
return memoizedSelection = inst.value;
|
|
87
|
+
return memoizedSelection = nextSelection;
|
|
88
|
+
}
|
|
89
|
+
if (Object.is(memoizedSnapshot, nextSnapshot)) return memoizedSelection;
|
|
90
|
+
var nextSelection = selector(nextSnapshot);
|
|
91
|
+
if (isEqual !== undefined && isEqual(memoizedSelection, nextSelection)) {
|
|
92
|
+
memoizedSnapshot = nextSnapshot;
|
|
93
|
+
return memoizedSelection;
|
|
94
|
+
}
|
|
95
|
+
memoizedSnapshot = nextSnapshot;
|
|
96
|
+
return memoizedSelection = nextSelection;
|
|
97
|
+
}
|
|
98
|
+
var maybeGetServerSnapshot = getServerSnapshot === undefined ? null : getServerSnapshot;
|
|
99
|
+
return [function () { return memoizedSelector(getSnapshot()); },
|
|
100
|
+
maybeGetServerSnapshot === null ? undefined : function () { return memoizedSelector(maybeGetServerSnapshot()); }];
|
|
101
|
+
}, [getSnapshot, getServerSnapshot, selector, isEqual]);
|
|
102
|
+
var value = useSyncExternalStore(subscribe, _useMemo[0], _useMemo[1]);
|
|
103
|
+
useEffect(function () { inst.hasValue = true; inst.value = value; }, [value]);
|
|
104
|
+
useDebugValue(value);
|
|
105
|
+
return value;
|
|
106
|
+
}`,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
name: 'fragments-cjs-interop',
|
|
111
|
+
enforce: 'pre' as const,
|
|
112
|
+
resolveId(source: string) {
|
|
113
|
+
if (source in virtualModules) {
|
|
114
|
+
return `\0cjs-interop:${source}`;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
},
|
|
118
|
+
load(id: string) {
|
|
119
|
+
if (!id.startsWith('\0cjs-interop:')) return undefined;
|
|
120
|
+
const pkg = id.slice('\0cjs-interop:'.length);
|
|
121
|
+
return virtualModules[pkg];
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
54
126
|
export interface DevServerOptions {
|
|
55
127
|
/** Port to run the server on */
|
|
56
128
|
port?: number;
|
|
@@ -151,6 +223,9 @@ export async function createDevServer(
|
|
|
151
223
|
// React support (if not already in project config)
|
|
152
224
|
...(hasReactPlugin(projectViteConfig) ? [] : [react()]),
|
|
153
225
|
|
|
226
|
+
// CJS interop for packages imported from within node_modules
|
|
227
|
+
cjsInteropPlugin(nodeModulesPath),
|
|
228
|
+
|
|
154
229
|
// Fragments plugins (array including SVGR)
|
|
155
230
|
...fragmentsPlugin({
|
|
156
231
|
fragmentFiles: allFragmentFiles,
|
|
@@ -172,12 +247,10 @@ export async function createDevServer(
|
|
|
172
247
|
// Dedupe ensures all imports of these packages resolve to the same copy
|
|
173
248
|
dedupe: ["react", "react-dom"],
|
|
174
249
|
alias: {
|
|
175
|
-
//
|
|
176
|
-
"@fragments/
|
|
177
|
-
// Resolve @fragments/
|
|
178
|
-
"@fragments/
|
|
179
|
-
// Resolve @fragments/core to the consolidated core source
|
|
180
|
-
"@fragments/core": resolve(cliPackageRoot, "src/core/index.ts"),
|
|
250
|
+
// Resolve @fragments-sdk/ui to local source or installed package
|
|
251
|
+
"@fragments-sdk/ui": resolveUiLib(nodeModulesPath),
|
|
252
|
+
// Resolve @fragments-sdk/cli/core to the CLI's own core source
|
|
253
|
+
"@fragments-sdk/cli/core": resolve(cliPackageRoot, "src/core/index.ts"),
|
|
181
254
|
// Ensure ALL react imports resolve to project's node_modules
|
|
182
255
|
// This is critical for viewer files loaded from outside project root
|
|
183
256
|
"react": safeRealpath(join(nodeModulesPath, "react")),
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/* ============================================
|
|
2
|
-
* Fragments Viewer
|
|
2
|
+
* Fragments Viewer Shell Styles
|
|
3
3
|
* ============================================
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Viewer-specific token aliases and utility classes.
|
|
5
|
+
* Base resets (box-sizing, body, scrollbars) come from
|
|
6
|
+
* @fragments/ui globals imported via entry.tsx.
|
|
6
7
|
* ============================================ */
|
|
7
8
|
|
|
9
|
+
/* ============================================
|
|
10
|
+
* Token Aliases
|
|
11
|
+
* ============================================
|
|
12
|
+
* Shorthand aliases mapping to --fui-* tokens so the viewer
|
|
13
|
+
* shell CSS can use shorter variable names.
|
|
14
|
+
*/
|
|
15
|
+
|
|
8
16
|
:root {
|
|
9
17
|
--bg-primary: var(--fui-bg-primary, #ffffff);
|
|
10
18
|
--bg-secondary: var(--fui-bg-secondary, #f7f7f8);
|
|
@@ -48,60 +56,13 @@
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
/* ============================================
|
|
51
|
-
*
|
|
59
|
+
* Viewer-specific overrides
|
|
52
60
|
* ============================================ */
|
|
53
61
|
|
|
54
62
|
* {
|
|
55
63
|
border-color: var(--border);
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
*::selection {
|
|
59
|
-
background-color: var(--color-accent-subtle);
|
|
60
|
-
color: var(--text-primary);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
html {
|
|
64
|
-
scroll-behavior: smooth;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
body {
|
|
68
|
-
margin: 0;
|
|
69
|
-
font-family: var(--fui-font-sans, Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
|
|
70
|
-
background-color: var(--bg-primary);
|
|
71
|
-
color: var(--text-primary);
|
|
72
|
-
-webkit-font-smoothing: antialiased;
|
|
73
|
-
-moz-osx-font-smoothing: grayscale;
|
|
74
|
-
font-feature-settings: "rlig" 1, "calt" 1;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/* ============================================
|
|
78
|
-
* Scrollbar Styles
|
|
79
|
-
* ============================================ */
|
|
80
|
-
|
|
81
|
-
::-webkit-scrollbar {
|
|
82
|
-
width: 6px;
|
|
83
|
-
height: 6px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
::-webkit-scrollbar-track {
|
|
87
|
-
background-color: transparent;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
::-webkit-scrollbar-thumb {
|
|
91
|
-
background-color: var(--text-muted);
|
|
92
|
-
border-radius: 9999px;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
::-webkit-scrollbar-thumb:hover {
|
|
96
|
-
background-color: var(--text-tertiary);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/* Firefox scrollbar */
|
|
100
|
-
* {
|
|
101
|
-
scrollbar-width: thin;
|
|
102
|
-
scrollbar-color: var(--text-muted) transparent;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
66
|
/* ============================================
|
|
106
67
|
* Theme-aware Utility Classes
|
|
107
68
|
* ============================================ */
|
|
@@ -1566,6 +1566,41 @@ function getBaseComponentPath(filePath: string): string {
|
|
|
1566
1566
|
return filePath.replace(/\.(fragment|stories)\.(tsx?|jsx?)$/, "");
|
|
1567
1567
|
}
|
|
1568
1568
|
|
|
1569
|
+
/**
|
|
1570
|
+
* Extract component name from a fragment/story file path.
|
|
1571
|
+
* e.g., "src/components/Chart/Chart.fragment.tsx" -> "Chart"
|
|
1572
|
+
* e.g., "@fragments-sdk/ui/src/components/Button/Button.fragment.tsx" -> "Button"
|
|
1573
|
+
*/
|
|
1574
|
+
function extractComponentName(filePath: string): string {
|
|
1575
|
+
const match = filePath.match(/([^/\\]+)\.(fragment|stories)\.(tsx?|jsx?)$/);
|
|
1576
|
+
return match ? match[1] : filePath.split("/").pop() || filePath;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* Extract dependency names from a fragment source file (best effort).
|
|
1581
|
+
* Reads the file and looks for `dependencies: [{ name: '...' }, ...]` patterns.
|
|
1582
|
+
* Returns an array of package names, or empty array if extraction fails.
|
|
1583
|
+
*/
|
|
1584
|
+
async function extractDependenciesFromSource(absolutePath: string): Promise<string[]> {
|
|
1585
|
+
try {
|
|
1586
|
+
const source = await readFile(absolutePath, "utf-8");
|
|
1587
|
+
// Match the dependencies array in the defineFragment call
|
|
1588
|
+
const depsMatch = source.match(/dependencies:\s*\[([\s\S]*?)\]/);
|
|
1589
|
+
if (!depsMatch) return [];
|
|
1590
|
+
|
|
1591
|
+
// Extract name values from dependency objects
|
|
1592
|
+
const names: string[] = [];
|
|
1593
|
+
const nameRegex = /name:\s*['"]([^'"]+)['"]/g;
|
|
1594
|
+
let match;
|
|
1595
|
+
while ((match = nameRegex.exec(depsMatch[1])) !== null) {
|
|
1596
|
+
names.push(match[1]);
|
|
1597
|
+
}
|
|
1598
|
+
return names;
|
|
1599
|
+
} catch {
|
|
1600
|
+
return [];
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1569
1604
|
/**
|
|
1570
1605
|
* Generate the virtual fragments module.
|
|
1571
1606
|
* Uses dynamic imports for lazy loading - fragments are loaded on demand.
|
|
@@ -1577,11 +1612,11 @@ function getBaseComponentPath(filePath: string): string {
|
|
|
1577
1612
|
* - Merge METADATA from .fragment.tsx (Figma URLs, AI descriptions, usage guidelines)
|
|
1578
1613
|
* - This gives us the best of both worlds: working renders + rich metadata
|
|
1579
1614
|
*/
|
|
1580
|
-
function generateFragmentsModule(
|
|
1615
|
+
async function generateFragmentsModule(
|
|
1581
1616
|
fragmentFiles: Array<{ absolutePath: string; relativePath: string }>,
|
|
1582
1617
|
config: FragmentsConfig,
|
|
1583
1618
|
previewConfigPath: string | null
|
|
1584
|
-
): string {
|
|
1619
|
+
): Promise<string> {
|
|
1585
1620
|
// Group files by base component path to identify pairs
|
|
1586
1621
|
const filesByBasePath = new Map<string, {
|
|
1587
1622
|
storyFile?: { absolutePath: string; relativePath: string };
|
|
@@ -1605,8 +1640,8 @@ function generateFragmentsModule(
|
|
|
1605
1640
|
|
|
1606
1641
|
// Generate loaders with metadata merge support
|
|
1607
1642
|
// Priority: stories for rendering, fragment for metadata (Figma URLs, etc.)
|
|
1608
|
-
const
|
|
1609
|
-
.map((files) => {
|
|
1643
|
+
const loaderEntries = await Promise.all(
|
|
1644
|
+
Array.from(filesByBasePath.values()).map(async (files) => {
|
|
1610
1645
|
// Determine which file to use for rendering
|
|
1611
1646
|
const primaryFile = files.storyFile || files.fragmentFile;
|
|
1612
1647
|
if (!primaryFile) return null;
|
|
@@ -1618,15 +1653,25 @@ function generateFragmentsModule(
|
|
|
1618
1653
|
? files.fragmentFile.absolutePath
|
|
1619
1654
|
: null;
|
|
1620
1655
|
|
|
1656
|
+
// Extract component name from file path
|
|
1657
|
+
const componentName = extractComponentName(primaryFile.relativePath);
|
|
1658
|
+
|
|
1659
|
+
// Extract dependencies from source (best effort, for error stubs)
|
|
1660
|
+
const fragmentSource = files.fragmentFile || primaryFile;
|
|
1661
|
+
const dependencies = await extractDependenciesFromSource(fragmentSource.absolutePath);
|
|
1662
|
+
|
|
1621
1663
|
return ` {
|
|
1622
1664
|
path: "${primaryFile.relativePath}",
|
|
1623
1665
|
isStory: ${isStory},
|
|
1666
|
+
componentName: ${JSON.stringify(componentName)},
|
|
1667
|
+
dependencies: ${JSON.stringify(dependencies)},
|
|
1624
1668
|
loader: () => import("${primaryFile.absolutePath}"),
|
|
1625
1669
|
metadataLoader: ${metadataPath ? `() => import("${metadataPath}")` : 'null'}
|
|
1626
1670
|
}`;
|
|
1627
1671
|
})
|
|
1628
|
-
|
|
1629
|
-
|
|
1672
|
+
);
|
|
1673
|
+
|
|
1674
|
+
const loaders = loaderEntries.filter(Boolean).join(",\n");
|
|
1630
1675
|
|
|
1631
1676
|
// Generate preview config import if available
|
|
1632
1677
|
const previewImport = previewConfigPath
|
|
@@ -1647,7 +1692,7 @@ setPreviewConfig({
|
|
|
1647
1692
|
: "";
|
|
1648
1693
|
|
|
1649
1694
|
return `
|
|
1650
|
-
import { storyModuleToFragment, setPreviewConfig } from "@fragments/core";
|
|
1695
|
+
import { storyModuleToFragment, setPreviewConfig } from "@fragments-sdk/cli/core";
|
|
1651
1696
|
${previewImport}
|
|
1652
1697
|
${previewSetup}
|
|
1653
1698
|
// Lazy fragment loaders (supports both .fragment.tsx and .stories.tsx)
|
|
@@ -1739,11 +1784,27 @@ export async function loadAllFragments() {
|
|
|
1739
1784
|
return { path: loader.path, fragment };
|
|
1740
1785
|
} catch (error) {
|
|
1741
1786
|
console.warn("[Fragments] Failed to load " + loader.path + ":", error.message);
|
|
1742
|
-
|
|
1787
|
+
// Create an error stub so the component still appears in the sidebar
|
|
1788
|
+
// with a helpful message about what went wrong
|
|
1789
|
+
return {
|
|
1790
|
+
path: loader.path,
|
|
1791
|
+
fragment: {
|
|
1792
|
+
meta: {
|
|
1793
|
+
name: loader.componentName || loader.path,
|
|
1794
|
+
description: 'Failed to load',
|
|
1795
|
+
category: '',
|
|
1796
|
+
},
|
|
1797
|
+
variants: [],
|
|
1798
|
+
_loadError: {
|
|
1799
|
+
message: error.message,
|
|
1800
|
+
dependencies: loader.dependencies || [],
|
|
1801
|
+
},
|
|
1802
|
+
},
|
|
1803
|
+
};
|
|
1743
1804
|
}
|
|
1744
1805
|
})
|
|
1745
1806
|
);
|
|
1746
|
-
// Filter out
|
|
1807
|
+
// Filter out nulls (fragments that had no component)
|
|
1747
1808
|
return results.filter(r => r !== null);
|
|
1748
1809
|
}
|
|
1749
1810
|
|