@fragments-sdk/cli 0.7.9 → 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/components/AccessibilityPanel.tsx +1 -1
- package/src/viewer/components/ActionsPanel.tsx +1 -1
- package/src/viewer/components/App.tsx +137 -96
- 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
package/src/core/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser-safe exports for @fragments/core
|
|
3
|
-
* For Node.js-only APIs (config, discovery, loader),
|
|
2
|
+
* Browser-safe exports for @fragments-sdk/cli/core
|
|
3
|
+
* For Node.js-only APIs (config, discovery, loader), import from '@fragments-sdk/cli' directly.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Brand and default constants
|
package/src/core/loader.ts
CHANGED
|
@@ -27,8 +27,8 @@ function createFragmentsCoreShimPlugin(): Plugin {
|
|
|
27
27
|
return {
|
|
28
28
|
name: BRAND.vitePluginNamespace,
|
|
29
29
|
setup(build) {
|
|
30
|
-
// Intercept @fragments/core imports
|
|
31
|
-
build.onResolve({ filter: /^@fragments\/core$/ }, (args) => {
|
|
30
|
+
// Intercept @fragments-sdk/cli/core imports
|
|
31
|
+
build.onResolve({ filter: /^@fragments-sdk\/cli\/core$/ }, (args) => {
|
|
32
32
|
return {
|
|
33
33
|
path: args.path,
|
|
34
34
|
namespace: BRAND.vitePluginNamespace,
|
|
@@ -71,7 +71,7 @@ export async function loadFragmentFile(
|
|
|
71
71
|
|
|
72
72
|
try {
|
|
73
73
|
// Use esbuild to bundle the fragment file
|
|
74
|
-
// We inject a shim for @fragments/core so it doesn't need to be installed
|
|
74
|
+
// We inject a shim for @fragments-sdk/cli/core so it doesn't need to be installed
|
|
75
75
|
// Using CommonJS format to avoid ESM/CJS interop issues with node_modules
|
|
76
76
|
await build({
|
|
77
77
|
entryPoints: [absolutePath],
|
package/src/core/schema.ts
CHANGED
|
@@ -149,7 +149,7 @@ export const fragmentGeneratedSchema = z.object({
|
|
|
149
149
|
* Schema for AI-specific metadata for playground context generation
|
|
150
150
|
*/
|
|
151
151
|
export const aiMetadataSchema = z.object({
|
|
152
|
-
compositionPattern: z.enum(['compound', 'simple', 'controlled']).optional(),
|
|
152
|
+
compositionPattern: z.enum(['compound', 'simple', 'controlled', 'wrapper']).optional(),
|
|
153
153
|
subComponents: z.array(z.string()).optional(),
|
|
154
154
|
requiredChildren: z.array(z.string()).optional(),
|
|
155
155
|
commonPatterns: z.array(z.string()).optional(),
|
package/src/index.ts
CHANGED
|
@@ -43,6 +43,12 @@ export type { AnalyzeOptions, AnalyzeResult } from "./analyze.js";
|
|
|
43
43
|
// Static Viewer
|
|
44
44
|
export { generateStaticViewer, generateViewerFromJson } from "./static-viewer.js";
|
|
45
45
|
|
|
46
|
+
// Config type (used by generated fragments.config.ts)
|
|
47
|
+
export type { FragmentsConfig } from "./core/types.js";
|
|
48
|
+
|
|
49
|
+
// Fragment definition API (used by generated .fragment.tsx files)
|
|
50
|
+
export { defineFragment, defineBlock } from "./core/defineFragment.js";
|
|
51
|
+
|
|
46
52
|
// CLI Command metadata (for docs)
|
|
47
53
|
export { CLI_COMMANDS, CLI_COMMAND_CATEGORIES } from "./cli-commands.js";
|
|
48
54
|
export type { CliCommandDef, CliOptionDef, CliCommandCategory, CliCategoryInfo } from "./cli-commands.js";
|
package/src/migrate/converter.ts
CHANGED
|
@@ -550,7 +550,7 @@ ${generated.skippedVariants.map(sv => ` { name: "${escapeString(sv.name)}",
|
|
|
550
550
|
}
|
|
551
551
|
|
|
552
552
|
// Import the actual component - this makes the fragment immediately usable
|
|
553
|
-
return `import { defineFragment } from "@fragments/core";
|
|
553
|
+
return `import { defineFragment } from "@fragments-sdk/cli/core";
|
|
554
554
|
import { ${componentName} } from "${componentImport}";
|
|
555
555
|
|
|
556
556
|
export default defineFragment({
|
|
@@ -45,7 +45,7 @@ describe('validateSnippetPolicy', () => {
|
|
|
45
45
|
projectDir,
|
|
46
46
|
'Button',
|
|
47
47
|
`import React from 'react';
|
|
48
|
-
import { defineFragment } from '@fragments/core';
|
|
48
|
+
import { defineFragment } from '@fragments-sdk/cli/core';
|
|
49
49
|
import { Button } from '.';
|
|
50
50
|
|
|
51
51
|
export default defineFragment({
|
|
@@ -86,7 +86,7 @@ export default defineFragment({
|
|
|
86
86
|
projectDir,
|
|
87
87
|
'Icon',
|
|
88
88
|
`import React from 'react';
|
|
89
|
-
import { defineFragment } from '@fragments/core';
|
|
89
|
+
import { defineFragment } from '@fragments-sdk/cli/core';
|
|
90
90
|
import { Icon } from '.';
|
|
91
91
|
|
|
92
92
|
export default defineFragment({
|
|
@@ -124,7 +124,7 @@ import { House } from '@phosphor-icons/react';
|
|
|
124
124
|
projectDir,
|
|
125
125
|
'Header',
|
|
126
126
|
`import React from 'react';
|
|
127
|
-
import { defineFragment } from '@fragments/core';
|
|
127
|
+
import { defineFragment } from '@fragments-sdk/cli/core';
|
|
128
128
|
import { Header } from '.';
|
|
129
129
|
|
|
130
130
|
export default defineFragment({
|
|
@@ -166,7 +166,7 @@ export default defineFragment({
|
|
|
166
166
|
projectDir,
|
|
167
167
|
'Alpha',
|
|
168
168
|
`import React from 'react';
|
|
169
|
-
import { defineFragment } from '@fragments/core';
|
|
169
|
+
import { defineFragment } from '@fragments-sdk/cli/core';
|
|
170
170
|
import { Alpha } from '.';
|
|
171
171
|
|
|
172
172
|
export default defineFragment({
|
|
@@ -183,7 +183,7 @@ export default defineFragment({
|
|
|
183
183
|
projectDir,
|
|
184
184
|
'Beta',
|
|
185
185
|
`import React from 'react';
|
|
186
|
-
import { defineFragment } from '@fragments/core';
|
|
186
|
+
import { defineFragment } from '@fragments-sdk/cli/core';
|
|
187
187
|
import { Beta } from '.';
|
|
188
188
|
|
|
189
189
|
export default defineFragment({
|
|
@@ -118,7 +118,6 @@ function normalizePolicy(
|
|
|
118
118
|
function isFragmentsModule(modulePath: string): boolean {
|
|
119
119
|
return (
|
|
120
120
|
modulePath === '@fragments-sdk/ui'
|
|
121
|
-
|| modulePath === '@fragments/ui'
|
|
122
121
|
|| modulePath === '.'
|
|
123
122
|
|| modulePath === '..'
|
|
124
123
|
|| modulePath.startsWith('@/components/')
|
|
@@ -12,7 +12,7 @@ import { discoverInstalledFragments } from "../../core/discovery.js";
|
|
|
12
12
|
* After packages were merged into @fragments-sdk/cli, the viewer's
|
|
13
13
|
* path resolution changed. These tests verify that:
|
|
14
14
|
* - Viewer assets (HTML, TSX entry points) are found at the correct paths
|
|
15
|
-
* - The @fragments/core alias resolves to the consolidated core source
|
|
15
|
+
* - The @fragments-sdk/cli/core alias resolves to the consolidated core source
|
|
16
16
|
* - The virtual module generates valid import statements
|
|
17
17
|
* - The Vite config references correct file system locations
|
|
18
18
|
*/
|
|
@@ -61,9 +61,9 @@ describe("viewer path resolution", () => {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
describe("@fragments/core alias resolution", () => {
|
|
64
|
+
describe("@fragments-sdk/cli/core alias resolution", () => {
|
|
65
65
|
it("core/index.ts exists at the expected path", () => {
|
|
66
|
-
// server.ts: "@fragments/core": resolve(cliPackageRoot, "src/core/index.ts")
|
|
66
|
+
// server.ts: "@fragments-sdk/cli/core": resolve(cliPackageRoot, "src/core/index.ts")
|
|
67
67
|
const corePath = resolve(cliPackageRoot, "src/core/index.ts");
|
|
68
68
|
expect(existsSync(corePath)).toBe(true);
|
|
69
69
|
});
|
|
@@ -82,32 +82,25 @@ describe("@fragments/core alias resolution", () => {
|
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
describe("virtual module @fragments/core import", () => {
|
|
86
|
-
it("vite-plugin generates import from @fragments/core (resolved via alias)", async () => {
|
|
87
|
-
// The virtual module template in vite-plugin.ts must import from @fragments/core
|
|
85
|
+
describe("virtual module @fragments-sdk/cli/core import", () => {
|
|
86
|
+
it("vite-plugin generates import from @fragments-sdk/cli/core (resolved via alias)", async () => {
|
|
87
|
+
// The virtual module template in vite-plugin.ts must import from @fragments-sdk/cli/core
|
|
88
88
|
// which is resolved by the Vite alias to the consolidated core source
|
|
89
89
|
const pluginPath = resolve(viewerDir, "vite-plugin.ts");
|
|
90
90
|
const content = await readFile(pluginPath, "utf-8");
|
|
91
91
|
|
|
92
|
-
// The generated virtual module string should reference @fragments/core
|
|
92
|
+
// The generated virtual module string should reference @fragments-sdk/cli/core
|
|
93
93
|
expect(content).toContain(
|
|
94
|
-
'import { storyModuleToFragment, setPreviewConfig } from "@fragments/core"'
|
|
94
|
+
'import { storyModuleToFragment, setPreviewConfig } from "@fragments-sdk/cli/core"'
|
|
95
95
|
);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
it("server.ts sets up @fragments/core alias to src/core/index.ts", async () => {
|
|
98
|
+
it("server.ts sets up @fragments-sdk/cli/core alias to src/core/index.ts", async () => {
|
|
99
99
|
const serverPath = resolve(viewerDir, "server.ts");
|
|
100
100
|
const content = await readFile(serverPath, "utf-8");
|
|
101
101
|
|
|
102
|
-
// The alias must resolve @fragments/core to the
|
|
103
|
-
expect(content).toContain('"@fragments/core": resolve(cliPackageRoot, "src/core/index.ts")');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("server.ts sets up @fragments/viewer alias", async () => {
|
|
107
|
-
const serverPath = resolve(viewerDir, "server.ts");
|
|
108
|
-
const content = await readFile(serverPath, "utf-8");
|
|
109
|
-
|
|
110
|
-
expect(content).toContain('"@fragments/viewer": viewerRoot');
|
|
102
|
+
// The alias must resolve @fragments-sdk/cli/core to the CLI's core source
|
|
103
|
+
expect(content).toContain('"@fragments-sdk/cli/core": resolve(cliPackageRoot, "src/core/index.ts")');
|
|
111
104
|
});
|
|
112
105
|
|
|
113
106
|
it("vite-plugin merges authored variant code from metadata fragments", async () => {
|
|
@@ -228,20 +221,20 @@ describe("no stale @fragments/* package references", () => {
|
|
|
228
221
|
const serverPath = resolve(viewerDir, "server.ts");
|
|
229
222
|
const content = await readFile(serverPath, "utf-8");
|
|
230
223
|
|
|
231
|
-
// Should NOT try to resolve @fragments/core from node_modules
|
|
224
|
+
// Should NOT try to resolve @fragments-sdk/cli/core from node_modules
|
|
232
225
|
expect(content).not.toContain('resolveFragmentsPackage("core"');
|
|
233
226
|
});
|
|
234
227
|
|
|
235
|
-
it("no source files import from @fragments/core as a runtime dependency", async () => {
|
|
228
|
+
it("no source files import from @fragments-sdk/cli/core as a runtime dependency", async () => {
|
|
236
229
|
// All source-level imports should use relative paths (../core/).
|
|
237
|
-
// Only the generated virtual module string uses @fragments/core (resolved via alias).
|
|
230
|
+
// Only the generated virtual module string uses @fragments-sdk/cli/core (resolved via alias).
|
|
238
231
|
const serverPath = resolve(viewerDir, "server.ts");
|
|
239
232
|
const content = await readFile(serverPath, "utf-8");
|
|
240
233
|
|
|
241
|
-
// server.ts should import from relative paths, not @fragments/core
|
|
234
|
+
// server.ts should import from relative paths, not @fragments-sdk/cli/core
|
|
242
235
|
const lines = content.split("\n");
|
|
243
236
|
const importLines = lines.filter(
|
|
244
|
-
(l) => l.startsWith("import") && l.includes("@fragments/core")
|
|
237
|
+
(l) => l.startsWith("import") && l.includes("@fragments-sdk/cli/core")
|
|
245
238
|
);
|
|
246
239
|
expect(importLines).toHaveLength(0);
|
|
247
240
|
});
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
13
13
|
import type { Result, NodeResult, ImpactValue, RunOptions } from "axe-core";
|
|
14
|
-
import { Badge, Tabs, Dialog, Card, Alert, Text, Stack, Box, Button, Chip, EmptyState } from "@fragments/ui";
|
|
14
|
+
import { Badge, Tabs, Dialog, Card, Alert, Text, Stack, Box, Button, Chip, EmptyState } from "@fragments-sdk/ui";
|
|
15
15
|
import { BRAND } from "../../core/index.js";
|
|
16
16
|
import {
|
|
17
17
|
updateComponentA11yResult,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { useState, useMemo } from "react";
|
|
9
9
|
import type { ActionLog } from "../hooks/useActions.js";
|
|
10
10
|
import { formatActionArg } from "../hooks/useActions.js";
|
|
11
|
-
import { Button, Stack, Text, Badge, Input, Menu, Separator } from "@fragments/ui";
|
|
11
|
+
import { Button, Stack, Text, Badge, Input, Menu, Separator } from "@fragments-sdk/ui";
|
|
12
12
|
import {
|
|
13
13
|
TrashIcon,
|
|
14
14
|
ChevronDownIcon,
|
|
@@ -15,8 +15,8 @@ import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp.js";
|
|
|
15
15
|
import { useToast } from "./Toast.js";
|
|
16
16
|
|
|
17
17
|
// Toolbar
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { PreviewMenu } from "./PreviewMenu.js";
|
|
19
|
+
import { getBackgroundStyle } from "../constants/ui.js";
|
|
20
20
|
|
|
21
21
|
// Preview & Rendering
|
|
22
22
|
import { PreviewArea } from "./PreviewArea.js";
|
|
@@ -28,10 +28,10 @@ import { useAllFigmaUrls } from "./FigmaEmbed.js";
|
|
|
28
28
|
import { ActionCapture } from "./ActionCapture.js";
|
|
29
29
|
|
|
30
30
|
// Fragments UI
|
|
31
|
-
import { Header, Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert, ScrollArea, Input, ThemeToggle } from "@fragments/ui";
|
|
31
|
+
import { Header, Stack, Text, Separator, Tooltip, Button, EmptyState, Box, Alert, ScrollArea, Input, ThemeToggle } from "@fragments-sdk/ui";
|
|
32
32
|
|
|
33
33
|
// Icons
|
|
34
|
-
import { EmptyIcon,
|
|
34
|
+
import { EmptyIcon, FigmaIcon, CompareIcon } from "./Icons.js";
|
|
35
35
|
|
|
36
36
|
// Logo
|
|
37
37
|
import { fragmentsLogo } from "../assets/fragments-logo.js";
|
|
@@ -54,9 +54,6 @@ import { useUrlState, findFragmentByName, findVariantIndex } from "../hooks/useU
|
|
|
54
54
|
import { usePanelDock } from "./ResizablePanel.js";
|
|
55
55
|
import { useTheme } from "./ThemeProvider.js";
|
|
56
56
|
|
|
57
|
-
// Utilities
|
|
58
|
-
import { ScreenshotButton } from "./ScreenshotButton.js";
|
|
59
|
-
|
|
60
57
|
interface AppProps {
|
|
61
58
|
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
62
59
|
}
|
|
@@ -237,6 +234,8 @@ export function App({ fragments }: AppProps) {
|
|
|
237
234
|
goToVariant: (index) => index < variantCount && handleSelectVariant(index),
|
|
238
235
|
toggleTheme: viewSettings.toggleTheme,
|
|
239
236
|
togglePanel: uiActions.togglePanel,
|
|
237
|
+
toggleMatrix: () => uiActions.setMatrixView(!uiState.showMatrixView),
|
|
238
|
+
toggleResponsive: () => uiActions.setMultiViewport(!uiState.showMultiViewport),
|
|
240
239
|
copyLink: handleCopyLink,
|
|
241
240
|
showHelp: uiActions.toggleShortcutsHelp,
|
|
242
241
|
openSearch: focusSearchInput,
|
|
@@ -307,11 +306,19 @@ export function App({ fragments }: AppProps) {
|
|
|
307
306
|
uiState={uiState}
|
|
308
307
|
uiActions={uiActions}
|
|
309
308
|
figmaUrl={figmaUrl}
|
|
310
|
-
linkCopied={uiState.linkCopied}
|
|
311
|
-
onCopyLink={handleCopyLink}
|
|
312
309
|
searchQuery={searchQuery}
|
|
313
310
|
onSearchChange={setSearchQuery}
|
|
314
311
|
searchInputRef={searchInputRef}
|
|
312
|
+
onPrevComponent={() => {
|
|
313
|
+
const prevIndex = currentFragmentIndex > 0 ? currentFragmentIndex - 1 : sortedFragmentPaths.length - 1;
|
|
314
|
+
handleSelectFragment(sortedFragmentPaths[prevIndex]);
|
|
315
|
+
}}
|
|
316
|
+
onNextComponent={() => {
|
|
317
|
+
const nextIndex = currentFragmentIndex < sortedFragmentPaths.length - 1 ? currentFragmentIndex + 1 : 0;
|
|
318
|
+
handleSelectFragment(sortedFragmentPaths[nextIndex]);
|
|
319
|
+
}}
|
|
320
|
+
onPrevVariant={() => handleSelectVariant(activeVariantIndex > 0 ? activeVariantIndex - 1 : variantCount - 1)}
|
|
321
|
+
onNextVariant={() => handleSelectVariant(activeVariantIndex < variantCount - 1 ? activeVariantIndex + 1 : 0)}
|
|
315
322
|
/>
|
|
316
323
|
) : (
|
|
317
324
|
<ViewerHeader
|
|
@@ -362,9 +369,6 @@ export function App({ fragments }: AppProps) {
|
|
|
362
369
|
activeIndex={activeVariantIndex}
|
|
363
370
|
onSelect={handleSelectVariant}
|
|
364
371
|
showMatrixView={uiState.showMatrixView}
|
|
365
|
-
showMultiViewport={uiState.showMultiViewport}
|
|
366
|
-
onToggleMatrix={() => uiActions.setMatrixView(!uiState.showMatrixView)}
|
|
367
|
-
onToggleMultiViewport={() => uiActions.setMultiViewport(!uiState.showMultiViewport)}
|
|
368
372
|
/>
|
|
369
373
|
)}
|
|
370
374
|
|
|
@@ -455,11 +459,13 @@ interface TopToolbarProps {
|
|
|
455
459
|
uiState: ReturnType<typeof useAppState>['state'];
|
|
456
460
|
uiActions: ReturnType<typeof useAppState>['actions'];
|
|
457
461
|
figmaUrl?: string;
|
|
458
|
-
linkCopied: boolean;
|
|
459
|
-
onCopyLink: () => void;
|
|
460
462
|
searchQuery: string;
|
|
461
463
|
onSearchChange: (value: string) => void;
|
|
462
464
|
searchInputRef: RefObject<HTMLInputElement>;
|
|
465
|
+
onPrevComponent: () => void;
|
|
466
|
+
onNextComponent: () => void;
|
|
467
|
+
onPrevVariant: () => void;
|
|
468
|
+
onNextVariant: () => void;
|
|
463
469
|
}
|
|
464
470
|
|
|
465
471
|
interface ViewerHeaderProps {
|
|
@@ -543,16 +549,36 @@ function TopToolbar({
|
|
|
543
549
|
uiState,
|
|
544
550
|
uiActions,
|
|
545
551
|
figmaUrl,
|
|
546
|
-
linkCopied,
|
|
547
|
-
onCopyLink,
|
|
548
552
|
searchQuery,
|
|
549
553
|
onSearchChange,
|
|
550
554
|
searchInputRef,
|
|
555
|
+
onPrevComponent,
|
|
556
|
+
onNextComponent,
|
|
557
|
+
onPrevVariant,
|
|
558
|
+
onNextVariant,
|
|
551
559
|
}: TopToolbarProps) {
|
|
552
560
|
const { setTheme, resolvedTheme } = useTheme();
|
|
553
561
|
return (
|
|
554
562
|
<Header aria-label="Component preview toolbar">
|
|
555
563
|
<Header.Trigger />
|
|
564
|
+
<PreviewMenu
|
|
565
|
+
zoom={viewSettings.zoom}
|
|
566
|
+
background={viewSettings.background}
|
|
567
|
+
viewport={viewSettings.viewport}
|
|
568
|
+
showMatrixView={uiState.showMatrixView}
|
|
569
|
+
showMultiViewport={uiState.showMultiViewport}
|
|
570
|
+
panelOpen={uiState.panelOpen}
|
|
571
|
+
onZoomChange={viewSettings.setZoom}
|
|
572
|
+
onBackgroundChange={viewSettings.setBackground}
|
|
573
|
+
onViewportChange={viewSettings.setViewport}
|
|
574
|
+
onToggleMatrix={() => uiActions.setMatrixView(!uiState.showMatrixView)}
|
|
575
|
+
onToggleMultiViewport={() => uiActions.setMultiViewport(!uiState.showMultiViewport)}
|
|
576
|
+
onTogglePanel={uiActions.togglePanel}
|
|
577
|
+
onPrevComponent={onPrevComponent}
|
|
578
|
+
onNextComponent={onNextComponent}
|
|
579
|
+
onPrevVariant={onPrevVariant}
|
|
580
|
+
onNextVariant={onNextVariant}
|
|
581
|
+
/>
|
|
556
582
|
<Header.Brand>
|
|
557
583
|
<Stack direction="row" align="center" gap="sm">
|
|
558
584
|
<img src={fragmentsLogo} alt="" width={20} height={20} style={{ display: 'block' }} />
|
|
@@ -563,21 +589,6 @@ function TopToolbar({
|
|
|
563
589
|
<HeaderSearch value={searchQuery} onChange={onSearchChange} inputRef={searchInputRef} />
|
|
564
590
|
<Header.Spacer />
|
|
565
591
|
<Header.Actions>
|
|
566
|
-
<PreviewToolbar
|
|
567
|
-
zoom={viewSettings.zoom}
|
|
568
|
-
background={viewSettings.background}
|
|
569
|
-
onZoomChange={viewSettings.setZoom}
|
|
570
|
-
onBackgroundChange={viewSettings.setBackground}
|
|
571
|
-
/>
|
|
572
|
-
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
573
|
-
<ViewportSelector
|
|
574
|
-
viewport={viewSettings.viewport}
|
|
575
|
-
customSize={viewSettings.customSize}
|
|
576
|
-
onViewportChange={viewSettings.setViewport}
|
|
577
|
-
onCustomSizeChange={viewSettings.setCustomSize}
|
|
578
|
-
/>
|
|
579
|
-
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
580
|
-
|
|
581
592
|
{figmaUrl && (
|
|
582
593
|
<>
|
|
583
594
|
<Tooltip content={uiState.showComparison ? "Hide Figma comparison" : "Compare with Figma design"}>
|
|
@@ -602,41 +613,6 @@ function TopToolbar({
|
|
|
602
613
|
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
603
614
|
</>
|
|
604
615
|
)}
|
|
605
|
-
|
|
606
|
-
{variant && (
|
|
607
|
-
<>
|
|
608
|
-
<Tooltip content="Open in new window">
|
|
609
|
-
<Button
|
|
610
|
-
onClick={() => {
|
|
611
|
-
const url = new URL(window.location.href);
|
|
612
|
-
url.hash = '';
|
|
613
|
-
url.searchParams.set('isolated', 'true');
|
|
614
|
-
url.searchParams.set('component', fragment.fragment.meta.name);
|
|
615
|
-
url.searchParams.set('variant', variant.name);
|
|
616
|
-
if (viewSettings.zoom !== 100) url.searchParams.set('zoom', String(viewSettings.zoom));
|
|
617
|
-
if (viewSettings.background !== 'transparent') url.searchParams.set('bg', viewSettings.background);
|
|
618
|
-
window.open(url.toString(), '_blank', 'noopener,noreferrer');
|
|
619
|
-
}}
|
|
620
|
-
variant="ghost"
|
|
621
|
-
size="sm"
|
|
622
|
-
>
|
|
623
|
-
<ExternalLinkIcon style={{ width: '16px', height: '16px' }} />
|
|
624
|
-
</Button>
|
|
625
|
-
</Tooltip>
|
|
626
|
-
<ScreenshotButton componentName={fragment.fragment.meta.name} variantName={variant.name} />
|
|
627
|
-
<Tooltip content="Copy link to share">
|
|
628
|
-
<Button
|
|
629
|
-
onClick={onCopyLink}
|
|
630
|
-
variant="ghost"
|
|
631
|
-
size="sm"
|
|
632
|
-
style={linkCopied ? { color: '#16a34a', backgroundColor: 'rgba(22, 163, 74, 0.1)' } : {}}
|
|
633
|
-
>
|
|
634
|
-
{linkCopied ? <CheckIcon style={{ width: '16px', height: '16px' }} /> : <LinkIcon style={{ width: '16px', height: '16px' }} />}
|
|
635
|
-
</Button>
|
|
636
|
-
</Tooltip>
|
|
637
|
-
</>
|
|
638
|
-
)}
|
|
639
|
-
<Separator orientation="vertical" style={{ height: '16px' }} />
|
|
640
616
|
<ThemeToggle
|
|
641
617
|
size="sm"
|
|
642
618
|
value={resolvedTheme}
|
|
@@ -672,14 +648,11 @@ interface VariantTabsBarProps {
|
|
|
672
648
|
activeIndex: number;
|
|
673
649
|
onSelect: (index: number) => void;
|
|
674
650
|
showMatrixView: boolean;
|
|
675
|
-
showMultiViewport: boolean;
|
|
676
|
-
onToggleMatrix: () => void;
|
|
677
|
-
onToggleMultiViewport: () => void;
|
|
678
651
|
}
|
|
679
652
|
|
|
680
|
-
function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView
|
|
653
|
+
function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView }: VariantTabsBarProps) {
|
|
681
654
|
return (
|
|
682
|
-
<
|
|
655
|
+
<div style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', backgroundColor: 'var(--bg-primary)', flexShrink: 0 }}>
|
|
683
656
|
{!showMatrixView ? (
|
|
684
657
|
<ScrollArea orientation="horizontal" showFades style={{ flex: 1, minWidth: 0 }}>
|
|
685
658
|
<VariantTabs variants={variants} activeIndex={activeIndex} onSelect={onSelect} />
|
|
@@ -687,31 +660,7 @@ function VariantTabsBar({ variants, activeIndex, onSelect, showMatrixView, showM
|
|
|
687
660
|
) : (
|
|
688
661
|
<Text size="sm" color="secondary">Showing all {variants.length} variants</Text>
|
|
689
662
|
)}
|
|
690
|
-
|
|
691
|
-
{variants.length > 1 && (
|
|
692
|
-
<Button
|
|
693
|
-
onClick={onToggleMatrix}
|
|
694
|
-
variant="ghost"
|
|
695
|
-
size="sm"
|
|
696
|
-
title={showMatrixView ? "Show single variant" : "Show all variants in grid"}
|
|
697
|
-
style={showMatrixView ? { backgroundColor: 'rgba(59, 130, 246, 0.1)', color: 'var(--color-accent)' } : {}}
|
|
698
|
-
>
|
|
699
|
-
<GridIcon style={{ width: '16px', height: '16px' }} />
|
|
700
|
-
{showMatrixView ? "Exit Matrix" : "Matrix"}
|
|
701
|
-
</Button>
|
|
702
|
-
)}
|
|
703
|
-
<Button
|
|
704
|
-
onClick={onToggleMultiViewport}
|
|
705
|
-
variant="ghost"
|
|
706
|
-
size="sm"
|
|
707
|
-
title={showMultiViewport ? "Exit multi-viewport" : "Show at multiple screen sizes"}
|
|
708
|
-
style={showMultiViewport ? { backgroundColor: 'rgba(34, 197, 94, 0.1)', color: '#16a34a' } : {}}
|
|
709
|
-
>
|
|
710
|
-
<DevicesIcon style={{ width: '16px', height: '16px' }} />
|
|
711
|
-
{showMultiViewport ? "Exit Responsive" : "Responsive"}
|
|
712
|
-
</Button>
|
|
713
|
-
</Stack>
|
|
714
|
-
</Stack>
|
|
663
|
+
</div>
|
|
715
664
|
);
|
|
716
665
|
}
|
|
717
666
|
|
|
@@ -721,6 +670,12 @@ interface NoVariantsMessageProps {
|
|
|
721
670
|
}
|
|
722
671
|
|
|
723
672
|
function NoVariantsMessage({ fragment }: NoVariantsMessageProps) {
|
|
673
|
+
// Check for load error (missing dependencies, schema errors, etc.)
|
|
674
|
+
const loadError = (fragment as any)?._loadError;
|
|
675
|
+
if (loadError) {
|
|
676
|
+
return <LoadErrorMessage error={loadError} componentName={fragment?.meta?.name} />;
|
|
677
|
+
}
|
|
678
|
+
|
|
724
679
|
const skippedVariants = (fragment?._generated as any)?.skippedVariants;
|
|
725
680
|
|
|
726
681
|
if (!skippedVariants || skippedVariants.length === 0) {
|
|
@@ -761,6 +716,92 @@ function NoVariantsMessage({ fragment }: NoVariantsMessageProps) {
|
|
|
761
716
|
);
|
|
762
717
|
}
|
|
763
718
|
|
|
719
|
+
// Load error message — shown when a fragment failed to import (missing deps, schema errors, etc.)
|
|
720
|
+
function LoadErrorMessage({ error, componentName }: { error: { message: string; dependencies: string[] }; componentName?: string }) {
|
|
721
|
+
const deps = error.dependencies || [];
|
|
722
|
+
const errorMessage = error.message || 'Unknown error';
|
|
723
|
+
|
|
724
|
+
// Determine if the error is a missing module/dependency issue
|
|
725
|
+
const isModuleError = /Failed to resolve import|Cannot find module|Module not found|does not provide an export/.test(errorMessage);
|
|
726
|
+
|
|
727
|
+
// Only suggest packages if the error is actually about missing modules
|
|
728
|
+
let suggestedPackages: string[] = [];
|
|
729
|
+
if (isModuleError) {
|
|
730
|
+
if (deps.length > 0) {
|
|
731
|
+
suggestedPackages = [...deps];
|
|
732
|
+
} else {
|
|
733
|
+
const match = errorMessage.match(/Failed to resolve import ["']([^"']+)["']/);
|
|
734
|
+
if (match) {
|
|
735
|
+
suggestedPackages = [match[1]];
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const hasMissingDeps = suggestedPackages.length > 0;
|
|
741
|
+
const installCmd = `pnpm add ${suggestedPackages.join(' ')}`;
|
|
742
|
+
|
|
743
|
+
return (
|
|
744
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', padding: '24px' }}>
|
|
745
|
+
<Alert variant="warning">
|
|
746
|
+
<Alert.Body>
|
|
747
|
+
<Alert.Title>
|
|
748
|
+
{hasMissingDeps ? 'Missing Dependencies' : 'Failed to Load'}
|
|
749
|
+
</Alert.Title>
|
|
750
|
+
<Alert.Content>
|
|
751
|
+
<Stack direction="column" gap="sm">
|
|
752
|
+
{hasMissingDeps ? (
|
|
753
|
+
<>
|
|
754
|
+
<Text size="xs" color="secondary">
|
|
755
|
+
{componentName ? `${componentName} requires` : 'This component requires'} packages that are not installed in your project.
|
|
756
|
+
</Text>
|
|
757
|
+
<Text size="xs" weight="semibold" color="secondary">Install with:</Text>
|
|
758
|
+
<code style={{
|
|
759
|
+
display: 'block',
|
|
760
|
+
padding: '8px 12px',
|
|
761
|
+
borderRadius: '6px',
|
|
762
|
+
backgroundColor: 'var(--bg-tertiary, #f3f4f6)',
|
|
763
|
+
fontFamily: 'monospace',
|
|
764
|
+
fontSize: '12px',
|
|
765
|
+
color: 'var(--text-primary, #111827)',
|
|
766
|
+
userSelect: 'all',
|
|
767
|
+
}}>
|
|
768
|
+
{installCmd}
|
|
769
|
+
</code>
|
|
770
|
+
<Text size="xs" color="tertiary">
|
|
771
|
+
After installing, restart the dev server.
|
|
772
|
+
</Text>
|
|
773
|
+
</>
|
|
774
|
+
) : (
|
|
775
|
+
<>
|
|
776
|
+
<Text size="xs" color="secondary">
|
|
777
|
+
{componentName ? `${componentName} couldn't` : 'This component couldn\'t'} be loaded. This may be due to a schema validation error or missing imports.
|
|
778
|
+
</Text>
|
|
779
|
+
<Text size="xs" weight="semibold" color="secondary">Error:</Text>
|
|
780
|
+
<pre style={{
|
|
781
|
+
padding: '8px 12px',
|
|
782
|
+
borderRadius: '6px',
|
|
783
|
+
backgroundColor: 'var(--bg-tertiary, #f3f4f6)',
|
|
784
|
+
fontFamily: 'monospace',
|
|
785
|
+
fontSize: '11px',
|
|
786
|
+
color: 'var(--text-secondary, #374151)',
|
|
787
|
+
whiteSpace: 'pre-wrap',
|
|
788
|
+
wordBreak: 'break-word',
|
|
789
|
+
margin: 0,
|
|
790
|
+
maxHeight: '200px',
|
|
791
|
+
overflow: 'auto',
|
|
792
|
+
}}>
|
|
793
|
+
{errorMessage}
|
|
794
|
+
</pre>
|
|
795
|
+
</>
|
|
796
|
+
)}
|
|
797
|
+
</Stack>
|
|
798
|
+
</Alert.Content>
|
|
799
|
+
</Alert.Body>
|
|
800
|
+
</Alert>
|
|
801
|
+
</div>
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
764
805
|
// Empty variant message
|
|
765
806
|
interface EmptyVariantMessageProps {
|
|
766
807
|
reason: string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { memo, useCallback } from 'react';
|
|
7
7
|
import type { FragmentDefinition, FragmentVariant } from '../../core/index.js';
|
|
8
|
-
import { Tabs, Badge } from '@fragments/ui';
|
|
8
|
+
import { Tabs, Badge } from '@fragments-sdk/ui';
|
|
9
9
|
import { ResizablePanel } from './ResizablePanel.js';
|
|
10
10
|
import { CodePanel } from './CodePanel.js';
|
|
11
11
|
import { TokenStylePanel } from './TokenStylePanel.js';
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { useState, useEffect, useRef, useMemo, useCallback } from "react";
|
|
13
|
-
import { Dialog, Stack, Text, Badge, Separator, Input } from '@fragments/ui';
|
|
13
|
+
import { Dialog, Stack, Text, Badge, Separator, Input } from '@fragments-sdk/ui';
|
|
14
14
|
import type { FragmentDefinition } from "../../core/index.js";
|
|
15
15
|
import { SearchIcon, ChevronRightIcon } from "./Icons.js";
|
|
16
16
|
|
|
@@ -12,7 +12,7 @@ import { useMemo, useState } from "react";
|
|
|
12
12
|
import type { FragmentDefinition, ComponentRelation, RelationshipType } from "../../core/index.js";
|
|
13
13
|
import { ChevronRightIcon, EmptyIcon, WandIcon } from "./Icons.js";
|
|
14
14
|
import { detectAllRelationships, mergeRelationships } from "../utils/detectRelationships.js";
|
|
15
|
-
import { Stack, Text, Badge, Button, EmptyState, CodeBlock, Grid, Separator } from "@fragments/ui";
|
|
15
|
+
import { Stack, Text, Badge, Button, EmptyState, CodeBlock, Grid, Separator } from "@fragments-sdk/ui";
|
|
16
16
|
|
|
17
17
|
interface ComponentGraphProps {
|
|
18
18
|
/** Current fragment definition */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FragmentMeta } from '../../core/index.js';
|
|
2
|
-
import { Badge, Alert, Text, Stack, Separator } from '@fragments/ui';
|
|
2
|
+
import { Badge, Alert, Text, Stack, Separator } from '@fragments-sdk/ui';
|
|
3
3
|
import { WarningIcon, BeakerIcon } from './Icons.js';
|
|
4
4
|
|
|
5
5
|
interface ComponentHeaderProps {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { memo } from 'react';
|
|
7
7
|
import type { FragmentContract } from '../../core/index.js';
|
|
8
|
-
import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge } from '@fragments/ui';
|
|
8
|
+
import { Stack, Text, Card, Chip, Alert, EmptyState, CodeBlock, Badge } from '@fragments-sdk/ui';
|
|
9
9
|
|
|
10
10
|
interface ContractPanelProps {
|
|
11
11
|
contract?: FragmentContract;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Component, type ReactNode, type ErrorInfo } from 'react';
|
|
2
|
-
import { Button, Alert, CodeBlock, Collapsible, Stack, Text } from '@fragments/ui';
|
|
2
|
+
import { Button, Alert, CodeBlock, Collapsible, Stack, Text } from '@fragments-sdk/ui';
|
|
3
3
|
import { ErrorIcon, RefreshIcon } from './Icons.js';
|
|
4
4
|
|
|
5
5
|
interface ErrorBoundaryProps {
|