@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.
Files changed (106) hide show
  1. package/dist/bin.js +13 -13
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-CWKQQR6C.js → chunk-57OW43NL.js} +3 -3
  4. package/dist/chunk-57OW43NL.js.map +1 -0
  5. package/dist/{chunk-AA6CAHCZ.js → chunk-7CRC46HV.js} +2 -2
  6. package/dist/chunk-7CRC46HV.js.map +1 -0
  7. package/dist/{chunk-3JPJTU25.js → chunk-CRTN6BIW.js} +5 -5
  8. package/dist/chunk-CRTN6BIW.js.map +1 -0
  9. package/dist/{chunk-LHIIBI6F.js → chunk-M42XIHPV.js} +2 -2
  10. package/dist/{chunk-2EFVPE5Q.js → chunk-TQOGBAOZ.js} +2 -2
  11. package/dist/chunk-TQOGBAOZ.js.map +1 -0
  12. package/dist/core/index.d.ts +1944 -0
  13. package/dist/{core-YAPWXDZW.js → core/index.js} +5 -5
  14. package/dist/defineFragment-C6PFzZyo.d.ts +656 -0
  15. package/dist/{generate-LEBVZCCH.js → generate-ZPERYZLF.js} +4 -4
  16. package/dist/index.d.ts +4 -159
  17. package/dist/index.js +9 -4
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-4VXL3Q6N.js → init-GID2DXB3.js} +69 -7
  20. package/dist/init-GID2DXB3.js.map +1 -0
  21. package/dist/mcp-bin.js +3 -3
  22. package/dist/{scan-3NYSRF6G.js → scan-BSMLGBX4.js} +5 -5
  23. package/dist/{service-HL6TMP3B.js → service-QACVPR37.js} +3 -3
  24. package/dist/{static-viewer-KLD24I4R.js → static-viewer-2RQD5QLR.js} +3 -3
  25. package/dist/{test-Y7YZOJLE.js → test-36UELXTE.js} +3 -3
  26. package/dist/{tokens-M4FCJKBK.js → tokens-A3BZIQPB.js} +4 -4
  27. package/dist/{viewer-ZWQQ74FV.js → viewer-CNLZQUFO.js} +156 -32
  28. package/dist/viewer-CNLZQUFO.js.map +1 -0
  29. package/package.json +8 -2
  30. package/src/commands/add.ts +1 -1
  31. package/src/commands/init.ts +84 -4
  32. package/src/core/defineFragment.ts +1 -1
  33. package/src/core/figma.ts +1 -1
  34. package/src/core/index.ts +2 -2
  35. package/src/core/loader.ts +3 -3
  36. package/src/core/schema.ts +1 -1
  37. package/src/index.ts +6 -0
  38. package/src/migrate/converter.ts +1 -1
  39. package/src/service/snippet-validation.test.ts +5 -5
  40. package/src/service/snippet-validation.ts +0 -1
  41. package/src/viewer/__tests__/viewer-integration.test.ts +16 -23
  42. package/src/viewer/components/AccessibilityPanel.tsx +1 -1
  43. package/src/viewer/components/ActionsPanel.tsx +1 -1
  44. package/src/viewer/components/App.tsx +563 -166
  45. package/src/viewer/components/BottomPanel.tsx +1 -1
  46. package/src/viewer/components/CodePanel.naming.test.tsx +1 -2
  47. package/src/viewer/components/CodePanel.tsx +1 -2
  48. package/src/viewer/components/CommandPalette.tsx +1 -1
  49. package/src/viewer/components/ComponentGraph.tsx +1 -1
  50. package/src/viewer/components/ComponentHeader.tsx +1 -1
  51. package/src/viewer/components/ContractPanel.tsx +1 -1
  52. package/src/viewer/components/ErrorBoundary.tsx +1 -1
  53. package/src/viewer/components/HealthDashboard.tsx +1 -1
  54. package/src/viewer/components/HmrStatusIndicator.tsx +1 -1
  55. package/src/viewer/components/InteractionsPanel.tsx +1 -1
  56. package/src/viewer/components/IsolatedRender.tsx +1 -1
  57. package/src/viewer/components/KeyboardShortcutsHelp.tsx +1 -1
  58. package/src/viewer/components/LandingPage.tsx +1 -1
  59. package/src/viewer/components/Layout.tsx +16 -13
  60. package/src/viewer/components/LeftSidebar.tsx +105 -18
  61. package/src/viewer/components/MultiViewportPreview.tsx +1 -1
  62. package/src/viewer/components/PreviewArea.tsx +22 -13
  63. package/src/viewer/components/PreviewFrameHost.tsx +0 -4
  64. package/src/viewer/components/PreviewToolbar.tsx +1 -1
  65. package/src/viewer/components/PropsEditor.tsx +1 -1
  66. package/src/viewer/components/PropsTable.tsx +1 -1
  67. package/src/viewer/components/RightSidebar.tsx +1 -1
  68. package/src/viewer/components/ScreenshotButton.tsx +1 -1
  69. package/src/viewer/components/SkeletonLoader.tsx +1 -1
  70. package/src/viewer/components/Toast.tsx +2 -2
  71. package/src/viewer/components/TokenStylePanel.tsx +1 -1
  72. package/src/viewer/components/VariantMatrix.tsx +1 -1
  73. package/src/viewer/components/VariantTabs.tsx +1 -1
  74. package/src/viewer/components/ViewportSelector.tsx +1 -1
  75. package/src/viewer/constants/ui.ts +14 -0
  76. package/src/viewer/entry.tsx +3 -4
  77. package/src/viewer/hooks/useKeyboardShortcuts.ts +65 -17
  78. package/src/viewer/hooks/useViewSettings.ts +1 -2
  79. package/src/viewer/index.ts +1 -1
  80. package/src/viewer/preview-frame.html +6 -9
  81. package/src/viewer/server.ts +106 -9
  82. package/src/viewer/styles/globals.css +12 -51
  83. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +110 -0
  84. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +89 -0
  85. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +119 -0
  86. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +134 -0
  87. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +66 -0
  88. package/src/viewer/vendor/shared/src/docs-layout.scss +28 -0
  89. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +2 -0
  90. package/src/viewer/vendor/shared/src/index.ts +26 -0
  91. package/src/viewer/vendor/shared/src/types.ts +41 -0
  92. package/src/viewer/vite-plugin.ts +70 -9
  93. package/dist/chunk-2EFVPE5Q.js.map +0 -1
  94. package/dist/chunk-3JPJTU25.js.map +0 -1
  95. package/dist/chunk-AA6CAHCZ.js.map +0 -1
  96. package/dist/chunk-CWKQQR6C.js.map +0 -1
  97. package/dist/init-4VXL3Q6N.js.map +0 -1
  98. package/dist/viewer-ZWQQ74FV.js.map +0 -1
  99. /package/dist/{chunk-LHIIBI6F.js.map → chunk-M42XIHPV.js.map} +0 -0
  100. /package/dist/{core-YAPWXDZW.js.map → core/index.js.map} +0 -0
  101. /package/dist/{generate-LEBVZCCH.js.map → generate-ZPERYZLF.js.map} +0 -0
  102. /package/dist/{scan-3NYSRF6G.js.map → scan-BSMLGBX4.js.map} +0 -0
  103. /package/dist/{service-HL6TMP3B.js.map → service-QACVPR37.js.map} +0 -0
  104. /package/dist/{static-viewer-KLD24I4R.js.map → static-viewer-2RQD5QLR.js.map} +0 -0
  105. /package/dist/{test-Y7YZOJLE.js.map → test-36UELXTE.js.map} +0 -0
  106. /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
- * - j/k or ↓/↑: Navigate components
6
- * - [/] or ←/→: Navigate variants
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 ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === "c") {
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 ((event.metaKey || event.ctrlKey) && event.key === "k") {
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 (!event.metaKey && !event.ctrlKey && !event.altKey) {
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 (!event.metaKey && !event.ctrlKey && !event.altKey) {
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 (!event.metaKey && !event.ctrlKey && !event.altKey) {
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 (!event.metaKey && !event.ctrlKey && !event.altKey) {
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 (!event.metaKey && !event.ctrlKey && !event.altKey) {
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 (!event.metaKey && !event.ctrlKey && !event.altKey) {
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 (!event.metaKey && !event.ctrlKey && !event.altKey) {
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: ["j", "↓"], description: "Next component" },
210
- { keys: ["k", "↑"], description: "Previous component" },
211
- { keys: ["[", "←"], description: "Previous variant" },
212
- { keys: ["]", "→"], description: "Next variant" },
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: ["⌘⇧C"], description: "Copy link" },
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 '../components/PreviewToolbar.js';
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;
@@ -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 a Vite-only alias and can't be resolved by Node.js.
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: flex-start;
34
- justify-content: flex-start;
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 data-preview-mode="centered">
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">
@@ -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, UI library, and node_modules root
146
- allow: [viewerRoot, resolveUiLib(nodeModulesPath), projectRoot, configDir, dirname(nodeModulesPath), ...installedPkgRoots],
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
- // Allow importing from viewer package
176
- "@fragments/viewer": viewerRoot,
177
- // Resolve @fragments/ui to installed @fragments-sdk/ui or local source
178
- "@fragments/ui": resolveUiLib(nodeModulesPath),
179
- // Resolve @fragments/core to the consolidated core source
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 Theme System
2
+ * Fragments Viewer Shell Styles
3
3
  * ============================================
4
- * The viewer shell (sidebar, toolbar, panels) mirrors @fragments/ui
5
- * tokens so previews and shell feel like the live docs experience.
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
- * Base Element Styles
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
+ }