@fragments-sdk/cli 0.7.9 → 0.7.11
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-CNLZQUFO.js} +156 -32
- package/dist/viewer-CNLZQUFO.js.map +1 -0
- package/package.json +8 -2
- 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/components/AccessibilityPanel.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +1 -1
- package/src/viewer/components/App.tsx +563 -166
- package/src/viewer/components/BottomPanel.tsx +1 -1
- package/src/viewer/components/CodePanel.naming.test.tsx +1 -2
- package/src/viewer/components/CodePanel.tsx +1 -2
- 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 +16 -13
- package/src/viewer/components/LeftSidebar.tsx +105 -18
- package/src/viewer/components/MultiViewportPreview.tsx +1 -1
- package/src/viewer/components/PreviewArea.tsx +22 -13
- package/src/viewer/components/PreviewFrameHost.tsx +0 -4
- 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 +106 -9
- package/src/viewer/styles/globals.css +12 -51
- package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +110 -0
- package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +89 -0
- package/src/viewer/vendor/shared/src/DocsPageShell.tsx +119 -0
- package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +134 -0
- package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +66 -0
- package/src/viewer/vendor/shared/src/docs-layout.scss +28 -0
- package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +2 -0
- package/src/viewer/vendor/shared/src/index.ts +26 -0
- package/src/viewer/vendor/shared/src/types.ts +41 -0
- 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
|
@@ -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
|
@@ -30,6 +30,8 @@ const cliPackageRoot = resolve(__dirname, "..");
|
|
|
30
30
|
const viewerRoot = resolve(cliPackageRoot, "src/viewer");
|
|
31
31
|
const packagesRoot = resolve(cliPackageRoot, "..");
|
|
32
32
|
const localUiLibRoot = resolve(packagesRoot, "../libs/ui/src");
|
|
33
|
+
const localSharedLibRoot = resolve(packagesRoot, "../libs/shared/src");
|
|
34
|
+
const vendoredSharedLibRoot = resolve(viewerRoot, "vendor/shared/src");
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Resolve the @fragments/ui alias to the correct path.
|
|
@@ -37,6 +39,11 @@ const localUiLibRoot = resolve(packagesRoot, "../libs/ui/src");
|
|
|
37
39
|
* In the monorepo: use the local libs/ui/src source.
|
|
38
40
|
*/
|
|
39
41
|
function resolveUiLib(nodeModulesDir: string): string {
|
|
42
|
+
// Prefer local monorepo source (when CLI is linked for development)
|
|
43
|
+
const localIndex = join(localUiLibRoot, "index.ts");
|
|
44
|
+
if (existsSync(localIndex)) {
|
|
45
|
+
return localUiLibRoot;
|
|
46
|
+
}
|
|
40
47
|
// Check for installed @fragments-sdk/ui in project's node_modules
|
|
41
48
|
const installedUi = join(nodeModulesDir, "@fragments-sdk/ui/src/index.ts");
|
|
42
49
|
if (existsSync(installedUi)) {
|
|
@@ -47,10 +54,95 @@ function resolveUiLib(nodeModulesDir: string): string {
|
|
|
47
54
|
if (existsSync(installedUiDist)) {
|
|
48
55
|
return installedUiDist;
|
|
49
56
|
}
|
|
50
|
-
// Fallback to local monorepo source
|
|
51
57
|
return localUiLibRoot;
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the @fragments-sdk/shared alias to either monorepo source
|
|
62
|
+
* or vendored viewer fallback for npm installs.
|
|
63
|
+
*/
|
|
64
|
+
function resolveSharedLib(): string {
|
|
65
|
+
const localIndex = join(localSharedLibRoot, "index.ts");
|
|
66
|
+
if (existsSync(localIndex)) {
|
|
67
|
+
return localSharedLibRoot;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const vendoredIndex = join(vendoredSharedLibRoot, "index.ts");
|
|
71
|
+
if (existsSync(vendoredIndex)) {
|
|
72
|
+
return vendoredSharedLibRoot;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return localSharedLibRoot;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Vite plugin to handle CJS-only packages imported from within node_modules.
|
|
80
|
+
* Vite's optimizeDeps only redirects imports from source files to pre-bundled ESM,
|
|
81
|
+
* not imports from other node_modules packages. This plugin provides virtual ESM
|
|
82
|
+
* wrappers for known CJS-only packages.
|
|
83
|
+
*/
|
|
84
|
+
function cjsInteropPlugin(nodeModulesPath: string) {
|
|
85
|
+
// Virtual ESM wrappers for CJS-only packages
|
|
86
|
+
const virtualModules: Record<string, string> = {
|
|
87
|
+
// use-sync-external-store/shim is CJS-only, used by @base-ui/react.
|
|
88
|
+
// Since React 18+, useSyncExternalStore is built-in — redirect to React's native export.
|
|
89
|
+
'use-sync-external-store/shim': `export { useSyncExternalStore } from 'react';`,
|
|
90
|
+
// use-sync-external-store/shim/with-selector is CJS-only, used by @base-ui/utils.
|
|
91
|
+
// Provides useSyncExternalStoreWithSelector — a selector wrapper around useSyncExternalStore.
|
|
92
|
+
'use-sync-external-store/shim/with-selector': `
|
|
93
|
+
import { useSyncExternalStore, useRef, useEffect, useMemo, useDebugValue } from 'react';
|
|
94
|
+
export function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
|
|
95
|
+
var instRef = useRef(null);
|
|
96
|
+
if (instRef.current === null) instRef.current = { hasValue: false, value: null };
|
|
97
|
+
var inst = instRef.current;
|
|
98
|
+
var _useMemo = useMemo(function () {
|
|
99
|
+
var hasMemo = false, memoizedSnapshot, memoizedSelection;
|
|
100
|
+
function memoizedSelector(nextSnapshot) {
|
|
101
|
+
if (!hasMemo) {
|
|
102
|
+
hasMemo = true;
|
|
103
|
+
memoizedSnapshot = nextSnapshot;
|
|
104
|
+
var nextSelection = selector(nextSnapshot);
|
|
105
|
+
if (isEqual !== undefined && inst.hasValue && isEqual(inst.value, nextSelection))
|
|
106
|
+
return memoizedSelection = inst.value;
|
|
107
|
+
return memoizedSelection = nextSelection;
|
|
108
|
+
}
|
|
109
|
+
if (Object.is(memoizedSnapshot, nextSnapshot)) return memoizedSelection;
|
|
110
|
+
var nextSelection = selector(nextSnapshot);
|
|
111
|
+
if (isEqual !== undefined && isEqual(memoizedSelection, nextSelection)) {
|
|
112
|
+
memoizedSnapshot = nextSnapshot;
|
|
113
|
+
return memoizedSelection;
|
|
114
|
+
}
|
|
115
|
+
memoizedSnapshot = nextSnapshot;
|
|
116
|
+
return memoizedSelection = nextSelection;
|
|
117
|
+
}
|
|
118
|
+
var maybeGetServerSnapshot = getServerSnapshot === undefined ? null : getServerSnapshot;
|
|
119
|
+
return [function () { return memoizedSelector(getSnapshot()); },
|
|
120
|
+
maybeGetServerSnapshot === null ? undefined : function () { return memoizedSelector(maybeGetServerSnapshot()); }];
|
|
121
|
+
}, [getSnapshot, getServerSnapshot, selector, isEqual]);
|
|
122
|
+
var value = useSyncExternalStore(subscribe, _useMemo[0], _useMemo[1]);
|
|
123
|
+
useEffect(function () { inst.hasValue = true; inst.value = value; }, [value]);
|
|
124
|
+
useDebugValue(value);
|
|
125
|
+
return value;
|
|
126
|
+
}`,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
name: 'fragments-cjs-interop',
|
|
131
|
+
enforce: 'pre' as const,
|
|
132
|
+
resolveId(source: string) {
|
|
133
|
+
if (source in virtualModules) {
|
|
134
|
+
return `\0cjs-interop:${source}`;
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
},
|
|
138
|
+
load(id: string) {
|
|
139
|
+
if (!id.startsWith('\0cjs-interop:')) return undefined;
|
|
140
|
+
const pkg = id.slice('\0cjs-interop:'.length);
|
|
141
|
+
return virtualModules[pkg];
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
54
146
|
export interface DevServerOptions {
|
|
55
147
|
/** Port to run the server on */
|
|
56
148
|
port?: number;
|
|
@@ -117,6 +209,8 @@ export async function createDevServer(
|
|
|
117
209
|
|
|
118
210
|
// Find node_modules (handles monorepo setups)
|
|
119
211
|
const nodeModulesPath = findNodeModules(projectRoot);
|
|
212
|
+
const uiLibRoot = resolveUiLib(nodeModulesPath);
|
|
213
|
+
const sharedLibRoot = resolveSharedLib();
|
|
120
214
|
console.log(`📁 Using node_modules: ${nodeModulesPath}`);
|
|
121
215
|
|
|
122
216
|
// Collect installed package roots so Vite can serve files from node_modules
|
|
@@ -142,8 +236,8 @@ export async function createDevServer(
|
|
|
142
236
|
port,
|
|
143
237
|
open: open ? "/fragments/" : false,
|
|
144
238
|
fs: {
|
|
145
|
-
// Allow serving files from viewer package, project,
|
|
146
|
-
allow: [viewerRoot,
|
|
239
|
+
// Allow serving files from viewer package, project, shared libs, and node_modules root
|
|
240
|
+
allow: [viewerRoot, uiLibRoot, sharedLibRoot, projectRoot, configDir, dirname(nodeModulesPath), ...installedPkgRoots],
|
|
147
241
|
},
|
|
148
242
|
},
|
|
149
243
|
|
|
@@ -151,6 +245,9 @@ export async function createDevServer(
|
|
|
151
245
|
// React support (if not already in project config)
|
|
152
246
|
...(hasReactPlugin(projectViteConfig) ? [] : [react()]),
|
|
153
247
|
|
|
248
|
+
// CJS interop for packages imported from within node_modules
|
|
249
|
+
cjsInteropPlugin(nodeModulesPath),
|
|
250
|
+
|
|
154
251
|
// Fragments plugins (array including SVGR)
|
|
155
252
|
...fragmentsPlugin({
|
|
156
253
|
fragmentFiles: allFragmentFiles,
|
|
@@ -172,12 +269,12 @@ export async function createDevServer(
|
|
|
172
269
|
// Dedupe ensures all imports of these packages resolve to the same copy
|
|
173
270
|
dedupe: ["react", "react-dom"],
|
|
174
271
|
alias: {
|
|
175
|
-
//
|
|
176
|
-
"@fragments/
|
|
177
|
-
// Resolve @fragments/
|
|
178
|
-
"@fragments/
|
|
179
|
-
// Resolve @fragments/core to the
|
|
180
|
-
"@fragments/core": resolve(cliPackageRoot, "src/core/index.ts"),
|
|
272
|
+
// Resolve @fragments-sdk/ui to local source or installed package
|
|
273
|
+
"@fragments-sdk/ui": uiLibRoot,
|
|
274
|
+
// Resolve @fragments-sdk/shared to monorepo source or vendored fallback
|
|
275
|
+
"@fragments-sdk/shared": sharedLibRoot,
|
|
276
|
+
// Resolve @fragments-sdk/cli/core to the CLI's own core source
|
|
277
|
+
"@fragments-sdk/cli/core": resolve(cliPackageRoot, "src/core/index.ts"),
|
|
181
278
|
// Ensure ALL react imports resolve to project's node_modules
|
|
182
279
|
// This is critical for viewer files loaded from outside project root
|
|
183
280
|
"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
|
* ============================================ */
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Header, NavigationMenu } from '@fragments-sdk/ui';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
import { DocsSearchCommand } from './DocsSearchCommand';
|
|
6
|
+
import type { DocsNavLinkRenderer, HeaderNavEntry, NavSection, SearchItem } from './types';
|
|
7
|
+
import { isDropdown } from './types';
|
|
8
|
+
|
|
9
|
+
interface DocsHeaderBarProps {
|
|
10
|
+
brand: ReactNode;
|
|
11
|
+
headerNav: HeaderNavEntry[];
|
|
12
|
+
mobileSections?: NavSection[];
|
|
13
|
+
currentPath: string;
|
|
14
|
+
searchItems: SearchItem[];
|
|
15
|
+
onSearchSelect: (item: SearchItem) => void;
|
|
16
|
+
renderLink?: DocsNavLinkRenderer;
|
|
17
|
+
isActive?: (href: string, currentPath: string) => boolean;
|
|
18
|
+
actions?: ReactNode;
|
|
19
|
+
showSkipLink?: boolean;
|
|
20
|
+
navAriaLabel?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const defaultLinkRenderer: DocsNavLinkRenderer = ({ href, label, onClick }) => (
|
|
24
|
+
<a href={href} onClick={onClick}>
|
|
25
|
+
{label}
|
|
26
|
+
</a>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
function defaultIsActive(href: string, currentPath: string): boolean {
|
|
30
|
+
return currentPath === href || currentPath.startsWith(`${href}/`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function DocsHeaderBar({
|
|
34
|
+
brand,
|
|
35
|
+
headerNav,
|
|
36
|
+
mobileSections = [],
|
|
37
|
+
currentPath,
|
|
38
|
+
searchItems,
|
|
39
|
+
onSearchSelect,
|
|
40
|
+
renderLink = defaultLinkRenderer,
|
|
41
|
+
isActive = defaultIsActive,
|
|
42
|
+
actions,
|
|
43
|
+
showSkipLink = true,
|
|
44
|
+
navAriaLabel = 'Primary navigation',
|
|
45
|
+
}: DocsHeaderBarProps) {
|
|
46
|
+
return (
|
|
47
|
+
<Header aria-label="Documentation header">
|
|
48
|
+
<Header.Brand>{brand}</Header.Brand>
|
|
49
|
+
{showSkipLink ? <Header.SkipLink /> : null}
|
|
50
|
+
|
|
51
|
+
<NavigationMenu aria-label={navAriaLabel}>
|
|
52
|
+
<NavigationMenu.List>
|
|
53
|
+
{headerNav.map((entry) =>
|
|
54
|
+
isDropdown(entry) ? (
|
|
55
|
+
<NavigationMenu.Item key={entry.label} value={entry.label}>
|
|
56
|
+
<NavigationMenu.Trigger>{entry.label}</NavigationMenu.Trigger>
|
|
57
|
+
<NavigationMenu.Content>
|
|
58
|
+
<div style={{ display: 'flex', flexDirection: 'column', padding: '4px', minWidth: '180px' }}>
|
|
59
|
+
{entry.items.map((child) => (
|
|
60
|
+
<NavigationMenu.Link
|
|
61
|
+
key={child.href}
|
|
62
|
+
href={child.href}
|
|
63
|
+
active={isActive(child.href, currentPath)}
|
|
64
|
+
asChild
|
|
65
|
+
>
|
|
66
|
+
{renderLink({ href: child.href, label: child.label })}
|
|
67
|
+
</NavigationMenu.Link>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</NavigationMenu.Content>
|
|
71
|
+
</NavigationMenu.Item>
|
|
72
|
+
) : (
|
|
73
|
+
<NavigationMenu.Item key={entry.href}>
|
|
74
|
+
<NavigationMenu.Link
|
|
75
|
+
href={entry.href}
|
|
76
|
+
active={isActive(entry.href, currentPath)}
|
|
77
|
+
asChild
|
|
78
|
+
>
|
|
79
|
+
{renderLink({ href: entry.href, label: entry.label })}
|
|
80
|
+
</NavigationMenu.Link>
|
|
81
|
+
</NavigationMenu.Item>
|
|
82
|
+
)
|
|
83
|
+
)}
|
|
84
|
+
</NavigationMenu.List>
|
|
85
|
+
|
|
86
|
+
<NavigationMenu.Viewport />
|
|
87
|
+
|
|
88
|
+
<NavigationMenu.MobileContent>
|
|
89
|
+
{mobileSections.map((section) => (
|
|
90
|
+
<NavigationMenu.MobileSection key={section.title} label={section.title}>
|
|
91
|
+
{section.items.map((item) => (
|
|
92
|
+
<NavigationMenu.Link key={item.href} href={item.href} asChild>
|
|
93
|
+
{renderLink({ href: item.href, label: item.label })}
|
|
94
|
+
</NavigationMenu.Link>
|
|
95
|
+
))}
|
|
96
|
+
</NavigationMenu.MobileSection>
|
|
97
|
+
))}
|
|
98
|
+
</NavigationMenu.MobileContent>
|
|
99
|
+
</NavigationMenu>
|
|
100
|
+
|
|
101
|
+
<Header.Spacer />
|
|
102
|
+
|
|
103
|
+
<Header.Search>
|
|
104
|
+
<DocsSearchCommand searchItems={searchItems} onSelect={onSearchSelect} />
|
|
105
|
+
</Header.Search>
|
|
106
|
+
|
|
107
|
+
<Header.Actions>{actions}</Header.Actions>
|
|
108
|
+
</Header>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
interface DocsPageAsideContextValue {
|
|
7
|
+
asideVisible: boolean;
|
|
8
|
+
setAsideVisible: (visible: boolean) => void;
|
|
9
|
+
asideWidth: string;
|
|
10
|
+
setAsideWidth: (width: string) => void;
|
|
11
|
+
asideContainer: HTMLDivElement | null;
|
|
12
|
+
setAsideContainer: (container: HTMLDivElement | null) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DocsPageAsideContext = React.createContext<DocsPageAsideContextValue | null>(null);
|
|
16
|
+
|
|
17
|
+
export function DocsPageAsideProvider({
|
|
18
|
+
children,
|
|
19
|
+
defaultWidth = '320px',
|
|
20
|
+
}: {
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
defaultWidth?: string;
|
|
23
|
+
}) {
|
|
24
|
+
const [asideVisible, setAsideVisible] = React.useState(false);
|
|
25
|
+
const [asideWidth, setAsideWidth] = React.useState(defaultWidth);
|
|
26
|
+
const [asideContainer, setAsideContainer] = React.useState<HTMLDivElement | null>(null);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<DocsPageAsideContext.Provider
|
|
30
|
+
value={{
|
|
31
|
+
asideVisible,
|
|
32
|
+
setAsideVisible,
|
|
33
|
+
asideWidth,
|
|
34
|
+
setAsideWidth,
|
|
35
|
+
asideContainer,
|
|
36
|
+
setAsideContainer,
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</DocsPageAsideContext.Provider>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function useDocsPageAside() {
|
|
45
|
+
const context = React.useContext(DocsPageAsideContext);
|
|
46
|
+
if (!context) {
|
|
47
|
+
throw new Error('useDocsPageAside must be used within DocsPageAsideProvider');
|
|
48
|
+
}
|
|
49
|
+
return context;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function DocsPageAsidePortal({ children, width = '320px' }: { children: React.ReactNode; width?: string }) {
|
|
53
|
+
const { setAsideVisible, setAsideWidth, asideContainer } = useDocsPageAside();
|
|
54
|
+
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
setAsideVisible(true);
|
|
57
|
+
setAsideWidth(width);
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
setAsideVisible(false);
|
|
61
|
+
};
|
|
62
|
+
}, [setAsideVisible, setAsideWidth, width]);
|
|
63
|
+
|
|
64
|
+
if (!asideContainer) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return createPortal(children, asideContainer);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface DocsPageAsideHostProps {
|
|
72
|
+
className?: string;
|
|
73
|
+
style?: React.CSSProperties;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function DocsPageAsideHost({ className, style }: DocsPageAsideHostProps) {
|
|
77
|
+
const { setAsideContainer } = useDocsPageAside();
|
|
78
|
+
|
|
79
|
+
const asideContainerCallback = React.useCallback(
|
|
80
|
+
(node: HTMLDivElement | null) => {
|
|
81
|
+
setAsideContainer(node);
|
|
82
|
+
},
|
|
83
|
+
[setAsideContainer]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div ref={asideContainerCallback} className={className} style={{ height: '100%', ...style }} />
|
|
88
|
+
);
|
|
89
|
+
}
|