@fragments-sdk/cli 0.2.2
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/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action Export Utilities
|
|
3
|
+
*
|
|
4
|
+
* Generate test assertions from recorded user interactions.
|
|
5
|
+
* Supports multiple export formats: JSON, Jest, Playwright
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ActionLog } from '../hooks/useActions.js';
|
|
9
|
+
|
|
10
|
+
export type ExportFormat = 'json' | 'jest' | 'playwright';
|
|
11
|
+
|
|
12
|
+
export interface ExportOptions {
|
|
13
|
+
/** Component name for generated test */
|
|
14
|
+
componentName?: string;
|
|
15
|
+
/** Variant name for generated test */
|
|
16
|
+
variantName?: string;
|
|
17
|
+
/** Include timestamps in export */
|
|
18
|
+
includeTimestamps?: boolean;
|
|
19
|
+
/** Include event details in comments */
|
|
20
|
+
includeComments?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Export actions as JSON
|
|
25
|
+
*/
|
|
26
|
+
export function exportActionsAsJson(
|
|
27
|
+
logs: ActionLog[],
|
|
28
|
+
options: ExportOptions = {}
|
|
29
|
+
): string {
|
|
30
|
+
const { includeTimestamps = false } = options;
|
|
31
|
+
|
|
32
|
+
const exportData = logs.map((log) => ({
|
|
33
|
+
action: log.name,
|
|
34
|
+
args: serializeArgs(log.args),
|
|
35
|
+
count: log.count > 1 ? log.count : undefined,
|
|
36
|
+
timestamp: includeTimestamps ? log.timestamp : undefined,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
return JSON.stringify(exportData, null, 2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Export actions as Jest test assertions
|
|
44
|
+
*/
|
|
45
|
+
export function exportActionsAsJest(
|
|
46
|
+
logs: ActionLog[],
|
|
47
|
+
options: ExportOptions = {}
|
|
48
|
+
): string {
|
|
49
|
+
const {
|
|
50
|
+
componentName = 'Component',
|
|
51
|
+
variantName = 'Default',
|
|
52
|
+
includeComments = true,
|
|
53
|
+
} = options;
|
|
54
|
+
|
|
55
|
+
const lines: string[] = [];
|
|
56
|
+
|
|
57
|
+
// Import statements
|
|
58
|
+
lines.push("import { render, screen, fireEvent } from '@testing-library/react';");
|
|
59
|
+
lines.push("import userEvent from '@testing-library/user-event';");
|
|
60
|
+
lines.push(`import { ${componentName} } from './${componentName}';`);
|
|
61
|
+
lines.push('');
|
|
62
|
+
|
|
63
|
+
// Test block
|
|
64
|
+
lines.push(`describe('${componentName}', () => {`);
|
|
65
|
+
lines.push(` it('${variantName} - recorded interactions', async () => {`);
|
|
66
|
+
lines.push(' const user = userEvent.setup();');
|
|
67
|
+
lines.push('');
|
|
68
|
+
|
|
69
|
+
// Mock handlers
|
|
70
|
+
const actionNames = [...new Set(logs.map((l) => l.name))];
|
|
71
|
+
for (const name of actionNames) {
|
|
72
|
+
const handlerName = `mock${capitalize(name)}`;
|
|
73
|
+
lines.push(` const ${handlerName} = jest.fn();`);
|
|
74
|
+
}
|
|
75
|
+
lines.push('');
|
|
76
|
+
|
|
77
|
+
// Render
|
|
78
|
+
lines.push(` render(`);
|
|
79
|
+
lines.push(` <${componentName}`);
|
|
80
|
+
for (const name of actionNames) {
|
|
81
|
+
lines.push(` ${name}={mock${capitalize(name)}}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push(` />`);
|
|
84
|
+
lines.push(` );`);
|
|
85
|
+
lines.push('');
|
|
86
|
+
|
|
87
|
+
// Assertions for each action
|
|
88
|
+
if (includeComments) {
|
|
89
|
+
lines.push(' // Recorded interactions:');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const log of logs) {
|
|
93
|
+
const handlerName = `mock${capitalize(log.name)}`;
|
|
94
|
+
|
|
95
|
+
if (includeComments && log.args.length > 0) {
|
|
96
|
+
lines.push(` // ${log.name} called with: ${summarizeArgs(log.args)}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (log.count > 1) {
|
|
100
|
+
lines.push(` expect(${handlerName}).toHaveBeenCalledTimes(${log.count});`);
|
|
101
|
+
} else {
|
|
102
|
+
const serializedArgs = serializeArgsForJest(log.args);
|
|
103
|
+
if (serializedArgs.length > 0) {
|
|
104
|
+
lines.push(` expect(${handlerName}).toHaveBeenCalledWith(${serializedArgs});`);
|
|
105
|
+
} else {
|
|
106
|
+
lines.push(` expect(${handlerName}).toHaveBeenCalled();`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
lines.push(' });');
|
|
112
|
+
lines.push('});');
|
|
113
|
+
|
|
114
|
+
return lines.join('\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Export actions as Playwright test assertions
|
|
119
|
+
*/
|
|
120
|
+
export function exportActionsAsPlaywright(
|
|
121
|
+
logs: ActionLog[],
|
|
122
|
+
options: ExportOptions = {}
|
|
123
|
+
): string {
|
|
124
|
+
const {
|
|
125
|
+
componentName = 'Component',
|
|
126
|
+
variantName = 'Default',
|
|
127
|
+
includeComments = true,
|
|
128
|
+
} = options;
|
|
129
|
+
|
|
130
|
+
const lines: string[] = [];
|
|
131
|
+
|
|
132
|
+
// Import statements
|
|
133
|
+
lines.push("import { test, expect } from '@playwright/test';");
|
|
134
|
+
lines.push('');
|
|
135
|
+
|
|
136
|
+
// Test block
|
|
137
|
+
lines.push(`test('${componentName} - ${variantName}', async ({ page }) => {`);
|
|
138
|
+
lines.push(` // Navigate to component`);
|
|
139
|
+
lines.push(` await page.goto('/?component=${encodeURIComponent(componentName)}&variant=${encodeURIComponent(variantName)}');`);
|
|
140
|
+
lines.push('');
|
|
141
|
+
lines.push(` // Wait for component to render`);
|
|
142
|
+
lines.push(` await page.waitForSelector('[data-preview-container="true"]');`);
|
|
143
|
+
lines.push('');
|
|
144
|
+
|
|
145
|
+
if (includeComments) {
|
|
146
|
+
lines.push(' // Recorded interactions:');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Generate interaction replay
|
|
150
|
+
for (const log of logs) {
|
|
151
|
+
if (includeComments) {
|
|
152
|
+
lines.push(` // ${log.name}${log.count > 1 ? ` (×${log.count})` : ''}: ${summarizeArgs(log.args)}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Generate appropriate Playwright command based on action type
|
|
156
|
+
const playwrightCode = generatePlaywrightAction(log);
|
|
157
|
+
if (playwrightCode) {
|
|
158
|
+
lines.push(` ${playwrightCode}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
lines.push('');
|
|
163
|
+
lines.push(' // Add assertions here based on expected state changes');
|
|
164
|
+
lines.push(' // Example: await expect(page.getByRole("button")).toBeDisabled();');
|
|
165
|
+
|
|
166
|
+
lines.push('});');
|
|
167
|
+
|
|
168
|
+
return lines.join('\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Export actions in specified format
|
|
173
|
+
*/
|
|
174
|
+
export function exportActions(
|
|
175
|
+
logs: ActionLog[],
|
|
176
|
+
format: ExportFormat,
|
|
177
|
+
options: ExportOptions = {}
|
|
178
|
+
): string {
|
|
179
|
+
switch (format) {
|
|
180
|
+
case 'json':
|
|
181
|
+
return exportActionsAsJson(logs, options);
|
|
182
|
+
case 'jest':
|
|
183
|
+
return exportActionsAsJest(logs, options);
|
|
184
|
+
case 'playwright':
|
|
185
|
+
return exportActionsAsPlaywright(logs, options);
|
|
186
|
+
default:
|
|
187
|
+
throw new Error(`Unknown export format: ${format}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Copy text to clipboard
|
|
193
|
+
*/
|
|
194
|
+
export async function copyToClipboard(text: string): Promise<boolean> {
|
|
195
|
+
try {
|
|
196
|
+
await navigator.clipboard.writeText(text);
|
|
197
|
+
return true;
|
|
198
|
+
} catch {
|
|
199
|
+
// Fallback for older browsers
|
|
200
|
+
const textarea = document.createElement('textarea');
|
|
201
|
+
textarea.value = text;
|
|
202
|
+
textarea.style.position = 'fixed';
|
|
203
|
+
textarea.style.opacity = '0';
|
|
204
|
+
document.body.appendChild(textarea);
|
|
205
|
+
textarea.select();
|
|
206
|
+
try {
|
|
207
|
+
document.execCommand('copy');
|
|
208
|
+
return true;
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
} finally {
|
|
212
|
+
document.body.removeChild(textarea);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Download text as a file
|
|
219
|
+
*/
|
|
220
|
+
export function downloadAsFile(content: string, filename: string): void {
|
|
221
|
+
const blob = new Blob([content], { type: 'text/plain' });
|
|
222
|
+
const url = URL.createObjectURL(blob);
|
|
223
|
+
const a = document.createElement('a');
|
|
224
|
+
a.href = url;
|
|
225
|
+
a.download = filename;
|
|
226
|
+
document.body.appendChild(a);
|
|
227
|
+
a.click();
|
|
228
|
+
document.body.removeChild(a);
|
|
229
|
+
URL.revokeObjectURL(url);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get file extension for export format
|
|
234
|
+
*/
|
|
235
|
+
export function getFileExtension(format: ExportFormat): string {
|
|
236
|
+
switch (format) {
|
|
237
|
+
case 'json':
|
|
238
|
+
return '.json';
|
|
239
|
+
case 'jest':
|
|
240
|
+
return '.test.tsx';
|
|
241
|
+
case 'playwright':
|
|
242
|
+
return '.spec.ts';
|
|
243
|
+
default:
|
|
244
|
+
return '.txt';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Helper functions
|
|
249
|
+
|
|
250
|
+
function capitalize(str: string): string {
|
|
251
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function serializeArgs(args: unknown[]): unknown[] {
|
|
255
|
+
return args.map((arg) => {
|
|
256
|
+
if (arg instanceof Event) {
|
|
257
|
+
return {
|
|
258
|
+
__type: 'Event',
|
|
259
|
+
type: arg.type,
|
|
260
|
+
target: arg.target instanceof Element ? describeElement(arg.target) : null,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (arg instanceof Element) {
|
|
264
|
+
return {
|
|
265
|
+
__type: 'Element',
|
|
266
|
+
...describeElement(arg),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (typeof arg === 'function') {
|
|
270
|
+
return { __type: 'function' };
|
|
271
|
+
}
|
|
272
|
+
return arg;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function describeElement(el: Element): object {
|
|
277
|
+
return {
|
|
278
|
+
tagName: el.tagName.toLowerCase(),
|
|
279
|
+
id: el.id || undefined,
|
|
280
|
+
className: el.className || undefined,
|
|
281
|
+
textContent: el.textContent?.slice(0, 50) || undefined,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function serializeArgsForJest(args: unknown[]): string {
|
|
286
|
+
if (args.length === 0) return '';
|
|
287
|
+
|
|
288
|
+
const parts: string[] = [];
|
|
289
|
+
for (const arg of args) {
|
|
290
|
+
if (arg instanceof Event) {
|
|
291
|
+
parts.push('expect.any(Event)');
|
|
292
|
+
} else if (arg instanceof Element) {
|
|
293
|
+
parts.push('expect.any(HTMLElement)');
|
|
294
|
+
} else if (typeof arg === 'function') {
|
|
295
|
+
parts.push('expect.any(Function)');
|
|
296
|
+
} else if (typeof arg === 'object' && arg !== null) {
|
|
297
|
+
parts.push(`expect.objectContaining(${JSON.stringify(arg)})`);
|
|
298
|
+
} else {
|
|
299
|
+
parts.push(JSON.stringify(arg));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return parts.join(', ');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function summarizeArgs(args: unknown[]): string {
|
|
306
|
+
if (args.length === 0) return '(no args)';
|
|
307
|
+
|
|
308
|
+
const summaries = args.map((arg) => {
|
|
309
|
+
if (arg instanceof Event) {
|
|
310
|
+
return `${arg.type} event`;
|
|
311
|
+
}
|
|
312
|
+
if (arg instanceof Element) {
|
|
313
|
+
return `<${arg.tagName.toLowerCase()}>`;
|
|
314
|
+
}
|
|
315
|
+
if (typeof arg === 'function') {
|
|
316
|
+
return 'ƒ()';
|
|
317
|
+
}
|
|
318
|
+
if (typeof arg === 'object' && arg !== null) {
|
|
319
|
+
return '{...}';
|
|
320
|
+
}
|
|
321
|
+
return JSON.stringify(arg);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return summaries.join(', ');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function generatePlaywrightAction(log: ActionLog): string | null {
|
|
328
|
+
const name = log.name.toLowerCase();
|
|
329
|
+
|
|
330
|
+
// Common click handlers
|
|
331
|
+
if (name.includes('click') || name === 'onpress') {
|
|
332
|
+
return "await page.getByRole('button').click(); // Adjust selector as needed";
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Change handlers
|
|
336
|
+
if (name.includes('change')) {
|
|
337
|
+
const value = log.args[0];
|
|
338
|
+
if (typeof value === 'string') {
|
|
339
|
+
return `await page.getByRole('textbox').fill(${JSON.stringify(value)});`;
|
|
340
|
+
}
|
|
341
|
+
return "await page.getByRole('textbox').fill('...'); // Add value";
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Submit handlers
|
|
345
|
+
if (name.includes('submit')) {
|
|
346
|
+
return "await page.getByRole('button', { name: 'Submit' }).click();";
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Focus/blur handlers
|
|
350
|
+
if (name.includes('focus')) {
|
|
351
|
+
return "await page.getByRole('textbox').focus();";
|
|
352
|
+
}
|
|
353
|
+
if (name.includes('blur')) {
|
|
354
|
+
return "await page.getByRole('textbox').blur();";
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Mouse events
|
|
358
|
+
if (name.includes('mouseenter') || name.includes('mouseover')) {
|
|
359
|
+
return "await page.getByRole('button').hover();";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Keyboard events
|
|
363
|
+
if (name.includes('keydown') || name.includes('keyup') || name.includes('keypress')) {
|
|
364
|
+
const event = log.args[0] as { key?: string } | undefined;
|
|
365
|
+
if (event?.key) {
|
|
366
|
+
return `await page.keyboard.press(${JSON.stringify(event.key)});`;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Default: add as comment
|
|
371
|
+
return `// ${log.name}: ${summarizeArgs(log.args)}`;
|
|
372
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
export type ColorSchemeType =
|
|
2
|
+
| 'monochromatic'
|
|
3
|
+
| 'complementary'
|
|
4
|
+
| 'analogous'
|
|
5
|
+
| 'triadic'
|
|
6
|
+
| 'split-complementary'
|
|
7
|
+
| 'tetradic';
|
|
8
|
+
|
|
9
|
+
export const COLOR_SCHEME_OPTIONS = [
|
|
10
|
+
{ value: 'monochromatic', label: 'Monochromatic', description: 'Variations of a single hue' },
|
|
11
|
+
{ value: 'complementary', label: 'Complementary', description: 'Two opposite colors' },
|
|
12
|
+
{ value: 'analogous', label: 'Analogous', description: 'Three adjacent colors' },
|
|
13
|
+
{ value: 'triadic', label: 'Triadic', description: 'Three evenly spaced colors' },
|
|
14
|
+
{ value: 'split-complementary', label: 'Split Complementary', description: 'Base + two adjacent to complement' },
|
|
15
|
+
{ value: 'tetradic', label: 'Tetradic (Square)', description: 'Four evenly spaced colors' },
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert hex color to HSL
|
|
20
|
+
*/
|
|
21
|
+
function hexToHsl(hex: string): [number, number, number] {
|
|
22
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
23
|
+
if (!result) return [0, 0, 0];
|
|
24
|
+
|
|
25
|
+
let r = parseInt(result[1], 16) / 255;
|
|
26
|
+
let g = parseInt(result[2], 16) / 255;
|
|
27
|
+
let b = parseInt(result[3], 16) / 255;
|
|
28
|
+
|
|
29
|
+
const max = Math.max(r, g, b);
|
|
30
|
+
const min = Math.min(r, g, b);
|
|
31
|
+
let h = 0;
|
|
32
|
+
let s = 0;
|
|
33
|
+
const l = (max + min) / 2;
|
|
34
|
+
|
|
35
|
+
if (max !== min) {
|
|
36
|
+
const d = max - min;
|
|
37
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
38
|
+
|
|
39
|
+
switch (max) {
|
|
40
|
+
case r:
|
|
41
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
42
|
+
break;
|
|
43
|
+
case g:
|
|
44
|
+
h = ((b - r) / d + 2) / 6;
|
|
45
|
+
break;
|
|
46
|
+
case b:
|
|
47
|
+
h = ((r - g) / d + 4) / 6;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convert HSL to hex color
|
|
57
|
+
*/
|
|
58
|
+
function hslToHex(h: number, s: number, l: number): string {
|
|
59
|
+
h = h / 360;
|
|
60
|
+
s = s / 100;
|
|
61
|
+
l = l / 100;
|
|
62
|
+
|
|
63
|
+
let r: number, g: number, b: number;
|
|
64
|
+
|
|
65
|
+
if (s === 0) {
|
|
66
|
+
r = g = b = l;
|
|
67
|
+
} else {
|
|
68
|
+
const hue2rgb = (p: number, q: number, t: number) => {
|
|
69
|
+
if (t < 0) t += 1;
|
|
70
|
+
if (t > 1) t -= 1;
|
|
71
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
72
|
+
if (t < 1 / 2) return q;
|
|
73
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
74
|
+
return p;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
78
|
+
const p = 2 * l - q;
|
|
79
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
80
|
+
g = hue2rgb(p, q, h);
|
|
81
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const toHex = (x: number) => {
|
|
85
|
+
const hex = Math.round(x * 255).toString(16);
|
|
86
|
+
return hex.length === 1 ? '0' + hex : hex;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalize hue to 0-360 range
|
|
94
|
+
*/
|
|
95
|
+
function normalizeHue(h: number): number {
|
|
96
|
+
h = h % 360;
|
|
97
|
+
return h < 0 ? h + 360 : h;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate a color scheme based on the type
|
|
102
|
+
*/
|
|
103
|
+
export function generateColorScheme(baseColor: string, type: ColorSchemeType): string[] {
|
|
104
|
+
const [h, s, l] = hexToHsl(baseColor);
|
|
105
|
+
|
|
106
|
+
switch (type) {
|
|
107
|
+
case 'monochromatic':
|
|
108
|
+
// Same hue, varying saturation and lightness
|
|
109
|
+
return [
|
|
110
|
+
hslToHex(h, Math.max(s - 20, 10), Math.min(l + 30, 90)),
|
|
111
|
+
hslToHex(h, s, Math.min(l + 15, 85)),
|
|
112
|
+
hslToHex(h, s, l), // Base
|
|
113
|
+
hslToHex(h, Math.min(s + 10, 100), Math.max(l - 15, 15)),
|
|
114
|
+
hslToHex(h, Math.min(s + 20, 100), Math.max(l - 30, 10)),
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
case 'complementary':
|
|
118
|
+
// Base and opposite on color wheel
|
|
119
|
+
return [
|
|
120
|
+
hslToHex(h, s, Math.min(l + 20, 85)),
|
|
121
|
+
hslToHex(h, s, l), // Base
|
|
122
|
+
hslToHex(h, s, Math.max(l - 20, 15)),
|
|
123
|
+
hslToHex(normalizeHue(h + 180), s, Math.min(l + 10, 80)),
|
|
124
|
+
hslToHex(normalizeHue(h + 180), s, Math.max(l - 10, 20)),
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
case 'analogous':
|
|
128
|
+
// Adjacent colors on the wheel (30° apart)
|
|
129
|
+
return [
|
|
130
|
+
hslToHex(normalizeHue(h - 30), s, l),
|
|
131
|
+
hslToHex(normalizeHue(h - 15), s, l),
|
|
132
|
+
hslToHex(h, s, l), // Base
|
|
133
|
+
hslToHex(normalizeHue(h + 15), s, l),
|
|
134
|
+
hslToHex(normalizeHue(h + 30), s, l),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
case 'triadic':
|
|
138
|
+
// Three colors 120° apart
|
|
139
|
+
return [
|
|
140
|
+
hslToHex(h, s, l), // Base
|
|
141
|
+
hslToHex(h, s, Math.min(l + 20, 85)),
|
|
142
|
+
hslToHex(normalizeHue(h + 120), s, l),
|
|
143
|
+
hslToHex(normalizeHue(h + 120), s, Math.max(l - 15, 20)),
|
|
144
|
+
hslToHex(normalizeHue(h + 240), s, l),
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
case 'split-complementary':
|
|
148
|
+
// Base + two colors adjacent to the complement
|
|
149
|
+
return [
|
|
150
|
+
hslToHex(h, s, l), // Base
|
|
151
|
+
hslToHex(h, s, Math.min(l + 20, 85)),
|
|
152
|
+
hslToHex(normalizeHue(h + 150), s, l),
|
|
153
|
+
hslToHex(normalizeHue(h + 180), s, Math.min(l + 15, 80)),
|
|
154
|
+
hslToHex(normalizeHue(h + 210), s, l),
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
case 'tetradic':
|
|
158
|
+
// Four colors 90° apart (square)
|
|
159
|
+
return [
|
|
160
|
+
hslToHex(h, s, l), // Base
|
|
161
|
+
hslToHex(normalizeHue(h + 90), s, l),
|
|
162
|
+
hslToHex(normalizeHue(h + 180), s, l),
|
|
163
|
+
hslToHex(normalizeHue(h + 270), s, l),
|
|
164
|
+
hslToHex(h, s, Math.min(l + 25, 85)), // Lighter base
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
default:
|
|
168
|
+
return [baseColor];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Generate CSS variables for a color scheme
|
|
174
|
+
*/
|
|
175
|
+
export function generateCssVariables(colors: string[], prefix = 'color'): Record<string, string> {
|
|
176
|
+
const vars: Record<string, string> = {};
|
|
177
|
+
const names = ['primary', 'secondary', 'tertiary', 'accent', 'muted'];
|
|
178
|
+
|
|
179
|
+
colors.forEach((color, index) => {
|
|
180
|
+
if (names[index]) {
|
|
181
|
+
vars[`--${prefix}-${names[index]}`] = color;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return vars;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if a color is light or dark
|
|
190
|
+
*/
|
|
191
|
+
export function isLightColor(hex: string): boolean {
|
|
192
|
+
const [, , l] = hexToHsl(hex);
|
|
193
|
+
return l > 50;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get a contrasting text color (black or white)
|
|
198
|
+
*/
|
|
199
|
+
export function getContrastColor(hex: string): string {
|
|
200
|
+
return isLightColor(hex) ? '#000000' : '#ffffff';
|
|
201
|
+
}
|