@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,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style comparison utilities for comparing Figma design properties
|
|
3
|
+
* with rendered component computed styles.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Style diff result for a single CSS property
|
|
8
|
+
*/
|
|
9
|
+
export interface StyleDiffItem {
|
|
10
|
+
/** CSS property name */
|
|
11
|
+
property: string;
|
|
12
|
+
/** Expected value from Figma */
|
|
13
|
+
figma: string;
|
|
14
|
+
/** Actual value from rendered component */
|
|
15
|
+
rendered: string;
|
|
16
|
+
/** Whether values match (within tolerance) */
|
|
17
|
+
match: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Result of comparing styles
|
|
22
|
+
*/
|
|
23
|
+
export interface StyleComparisonResult {
|
|
24
|
+
/** Whether all styles match */
|
|
25
|
+
match: boolean;
|
|
26
|
+
/** Individual property comparisons */
|
|
27
|
+
properties: StyleDiffItem[];
|
|
28
|
+
/** CSS properties from Figma design */
|
|
29
|
+
figmaStyles: Record<string, string>;
|
|
30
|
+
/** Computed CSS properties from rendered component */
|
|
31
|
+
renderedStyles: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Compare Figma CSS properties with rendered computed styles.
|
|
36
|
+
*/
|
|
37
|
+
export function compareStyles(
|
|
38
|
+
figmaStyles: Record<string, string | undefined>,
|
|
39
|
+
renderedStyles: Record<string, string>
|
|
40
|
+
): StyleComparisonResult {
|
|
41
|
+
const properties: StyleDiffItem[] = [];
|
|
42
|
+
const cleanFigmaStyles: Record<string, string> = {};
|
|
43
|
+
|
|
44
|
+
// Properties to compare
|
|
45
|
+
const propsToCompare = [
|
|
46
|
+
"backgroundColor",
|
|
47
|
+
"borderColor",
|
|
48
|
+
"borderWidth",
|
|
49
|
+
"borderRadius",
|
|
50
|
+
"fontFamily",
|
|
51
|
+
"fontSize",
|
|
52
|
+
"fontWeight",
|
|
53
|
+
"lineHeight",
|
|
54
|
+
"letterSpacing",
|
|
55
|
+
"textAlign",
|
|
56
|
+
"boxShadow",
|
|
57
|
+
"padding",
|
|
58
|
+
"gap",
|
|
59
|
+
"opacity",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
for (const prop of propsToCompare) {
|
|
63
|
+
const figmaValue = figmaStyles[prop];
|
|
64
|
+
const renderedValue = renderedStyles[prop];
|
|
65
|
+
|
|
66
|
+
if (figmaValue !== undefined) {
|
|
67
|
+
cleanFigmaStyles[prop] = figmaValue;
|
|
68
|
+
|
|
69
|
+
const match = compareStyleValue(prop, figmaValue, renderedValue || "");
|
|
70
|
+
properties.push({
|
|
71
|
+
property: prop,
|
|
72
|
+
figma: figmaValue,
|
|
73
|
+
rendered: renderedValue || "(not set)",
|
|
74
|
+
match,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const allMatch = properties.every((p) => p.match);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
match: allMatch,
|
|
83
|
+
properties,
|
|
84
|
+
figmaStyles: cleanFigmaStyles,
|
|
85
|
+
renderedStyles,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Compare a single style value with tolerance for color and numeric differences.
|
|
91
|
+
*/
|
|
92
|
+
export function compareStyleValue(
|
|
93
|
+
prop: string,
|
|
94
|
+
figma: string,
|
|
95
|
+
rendered: string
|
|
96
|
+
): boolean {
|
|
97
|
+
// Normalize values for comparison
|
|
98
|
+
const normalizedFigma = normalizeStyleValue(prop, figma);
|
|
99
|
+
const normalizedRendered = normalizeStyleValue(prop, rendered);
|
|
100
|
+
|
|
101
|
+
// Direct match
|
|
102
|
+
if (normalizedFigma === normalizedRendered) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Color comparison with tolerance
|
|
107
|
+
if (prop === "backgroundColor" || prop === "borderColor") {
|
|
108
|
+
return compareColors(normalizedFigma, normalizedRendered, 5);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Numeric comparison with tolerance (for pixels)
|
|
112
|
+
if (
|
|
113
|
+
["borderWidth", "borderRadius", "fontSize", "padding", "gap"].includes(prop)
|
|
114
|
+
) {
|
|
115
|
+
return compareNumericValues(normalizedFigma, normalizedRendered, 1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Normalize a style value for comparison.
|
|
123
|
+
*/
|
|
124
|
+
export function normalizeStyleValue(prop: string, value: string): string {
|
|
125
|
+
// Remove extra whitespace
|
|
126
|
+
let normalized = value.trim().replace(/\s+/g, " ");
|
|
127
|
+
|
|
128
|
+
// Normalize "none" shadow to empty
|
|
129
|
+
if (prop === "boxShadow" && normalized === "none") {
|
|
130
|
+
normalized = "";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Normalize rgba(0, 0, 0, 0) to "transparent"
|
|
134
|
+
if (normalized.match(/rgba\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/)) {
|
|
135
|
+
normalized = "transparent";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return normalized;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Compare two color values with tolerance.
|
|
143
|
+
*/
|
|
144
|
+
export function compareColors(
|
|
145
|
+
color1: string,
|
|
146
|
+
color2: string,
|
|
147
|
+
tolerance: number
|
|
148
|
+
): boolean {
|
|
149
|
+
const rgb1 = parseColor(color1);
|
|
150
|
+
const rgb2 = parseColor(color2);
|
|
151
|
+
|
|
152
|
+
if (!rgb1 || !rgb2) {
|
|
153
|
+
return color1 === color2;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
Math.abs(rgb1.r - rgb2.r) <= tolerance &&
|
|
158
|
+
Math.abs(rgb1.g - rgb2.g) <= tolerance &&
|
|
159
|
+
Math.abs(rgb1.b - rgb2.b) <= tolerance &&
|
|
160
|
+
Math.abs((rgb1.a ?? 1) - (rgb2.a ?? 1)) <= 0.05
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Parse a color string to RGB values.
|
|
166
|
+
*/
|
|
167
|
+
export function parseColor(
|
|
168
|
+
color: string
|
|
169
|
+
): { r: number; g: number; b: number; a?: number } | null {
|
|
170
|
+
// Handle hex colors
|
|
171
|
+
const hexMatch = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
172
|
+
if (hexMatch) {
|
|
173
|
+
return {
|
|
174
|
+
r: parseInt(hexMatch[1], 16),
|
|
175
|
+
g: parseInt(hexMatch[2], 16),
|
|
176
|
+
b: parseInt(hexMatch[3], 16),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle rgb/rgba
|
|
181
|
+
const rgbaMatch = color.match(
|
|
182
|
+
/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/
|
|
183
|
+
);
|
|
184
|
+
if (rgbaMatch) {
|
|
185
|
+
return {
|
|
186
|
+
r: parseInt(rgbaMatch[1], 10),
|
|
187
|
+
g: parseInt(rgbaMatch[2], 10),
|
|
188
|
+
b: parseInt(rgbaMatch[3], 10),
|
|
189
|
+
a: rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Compare numeric values (e.g., "10px" vs "11px") with tolerance.
|
|
198
|
+
*/
|
|
199
|
+
export function compareNumericValues(
|
|
200
|
+
value1: string,
|
|
201
|
+
value2: string,
|
|
202
|
+
tolerance: number
|
|
203
|
+
): boolean {
|
|
204
|
+
const num1 = parseFloat(value1);
|
|
205
|
+
const num2 = parseFloat(value2);
|
|
206
|
+
|
|
207
|
+
if (isNaN(num1) || isNaN(num2)) {
|
|
208
|
+
return value1 === value2;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return Math.abs(num1 - num2) <= tolerance;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ----- Enhanced Token-Aware Style Comparison -----
|
|
215
|
+
|
|
216
|
+
import type {
|
|
217
|
+
EnhancedStyleDiffItem,
|
|
218
|
+
TokenFix,
|
|
219
|
+
TokenUsageSummary,
|
|
220
|
+
DesignToken,
|
|
221
|
+
} from "../core/index.js";
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Enhanced style diff result with token information
|
|
225
|
+
*/
|
|
226
|
+
export interface EnhancedStyleComparisonResult extends StyleComparisonResult {
|
|
227
|
+
/** Individual property comparisons with token info */
|
|
228
|
+
properties: EnhancedStyleDiffItem[];
|
|
229
|
+
/** Token usage summary */
|
|
230
|
+
tokenSummary?: TokenUsageSummary;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Token registry interface for style comparison
|
|
235
|
+
* (subset of TokenRegistryManager methods needed here)
|
|
236
|
+
*/
|
|
237
|
+
export interface TokenLookup {
|
|
238
|
+
findByValue(value: string, theme?: string): string[];
|
|
239
|
+
getToken(name: string): DesignToken | undefined;
|
|
240
|
+
calculateUsageSummary(
|
|
241
|
+
styleDiffs: Array<{
|
|
242
|
+
property: string;
|
|
243
|
+
figma: string;
|
|
244
|
+
rendered: string;
|
|
245
|
+
match: boolean;
|
|
246
|
+
}>,
|
|
247
|
+
theme?: string
|
|
248
|
+
): TokenUsageSummary;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Compare styles with token awareness.
|
|
253
|
+
*
|
|
254
|
+
* This enhanced version:
|
|
255
|
+
* 1. Performs normal style comparison
|
|
256
|
+
* 2. Identifies which values match design tokens
|
|
257
|
+
* 3. Flags hardcoded values that should use tokens
|
|
258
|
+
* 4. Generates fix suggestions
|
|
259
|
+
*/
|
|
260
|
+
export function compareStylesWithTokens(
|
|
261
|
+
figmaStyles: Record<string, string | undefined>,
|
|
262
|
+
renderedStyles: Record<string, string>,
|
|
263
|
+
tokenLookup?: TokenLookup,
|
|
264
|
+
theme = "default"
|
|
265
|
+
): EnhancedStyleComparisonResult {
|
|
266
|
+
const properties: EnhancedStyleDiffItem[] = [];
|
|
267
|
+
const cleanFigmaStyles: Record<string, string> = {};
|
|
268
|
+
|
|
269
|
+
// Properties to compare
|
|
270
|
+
const propsToCompare = [
|
|
271
|
+
"backgroundColor",
|
|
272
|
+
"borderColor",
|
|
273
|
+
"borderWidth",
|
|
274
|
+
"borderRadius",
|
|
275
|
+
"fontFamily",
|
|
276
|
+
"fontSize",
|
|
277
|
+
"fontWeight",
|
|
278
|
+
"lineHeight",
|
|
279
|
+
"letterSpacing",
|
|
280
|
+
"textAlign",
|
|
281
|
+
"boxShadow",
|
|
282
|
+
"padding",
|
|
283
|
+
"gap",
|
|
284
|
+
"opacity",
|
|
285
|
+
"color",
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
for (const prop of propsToCompare) {
|
|
289
|
+
const figmaValue = figmaStyles[prop];
|
|
290
|
+
const renderedValue = renderedStyles[prop];
|
|
291
|
+
|
|
292
|
+
if (figmaValue !== undefined) {
|
|
293
|
+
cleanFigmaStyles[prop] = figmaValue;
|
|
294
|
+
|
|
295
|
+
const match = compareStyleValue(prop, figmaValue, renderedValue || "");
|
|
296
|
+
|
|
297
|
+
// Build enhanced diff item
|
|
298
|
+
const item: EnhancedStyleDiffItem = {
|
|
299
|
+
property: prop,
|
|
300
|
+
figma: figmaValue,
|
|
301
|
+
rendered: renderedValue || "(not set)",
|
|
302
|
+
match,
|
|
303
|
+
isHardcoded: false,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Add token information if registry is available
|
|
307
|
+
if (tokenLookup) {
|
|
308
|
+
const figmaTokens = tokenLookup.findByValue(figmaValue, theme);
|
|
309
|
+
const renderedTokens = renderedValue
|
|
310
|
+
? tokenLookup.findByValue(renderedValue, theme)
|
|
311
|
+
: [];
|
|
312
|
+
|
|
313
|
+
if (figmaTokens.length > 0) {
|
|
314
|
+
item.figmaToken = figmaTokens[0];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (renderedTokens.length > 0) {
|
|
318
|
+
item.renderedToken = renderedTokens[0];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Determine if this is a hardcoded value
|
|
322
|
+
// Hardcoded = Figma matches a token, but rendered doesn't use a token
|
|
323
|
+
item.isHardcoded = !!item.figmaToken && !item.renderedToken;
|
|
324
|
+
|
|
325
|
+
// Generate fix suggestion if hardcoded
|
|
326
|
+
if (item.isHardcoded && item.figmaToken) {
|
|
327
|
+
const token = tokenLookup.getToken(item.figmaToken);
|
|
328
|
+
if (token) {
|
|
329
|
+
const cssProperty = toCssProperty(prop);
|
|
330
|
+
item.suggestedFix = {
|
|
331
|
+
tokenName: item.figmaToken,
|
|
332
|
+
tokenValue: token.resolvedValue,
|
|
333
|
+
codeFix: `${cssProperty}: var(${item.figmaToken});`,
|
|
334
|
+
confidence: 0.9,
|
|
335
|
+
reason: `Figma uses token ${item.figmaToken} (${token.resolvedValue}). Replace hardcoded value with token for consistency.`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
properties.push(item);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const allMatch = properties.every((p) => p.match);
|
|
346
|
+
|
|
347
|
+
// Calculate token summary if registry available
|
|
348
|
+
let tokenSummary: TokenUsageSummary | undefined;
|
|
349
|
+
if (tokenLookup) {
|
|
350
|
+
tokenSummary = tokenLookup.calculateUsageSummary(
|
|
351
|
+
properties.map((p) => ({
|
|
352
|
+
property: p.property,
|
|
353
|
+
figma: p.figma,
|
|
354
|
+
rendered: p.rendered,
|
|
355
|
+
match: p.match,
|
|
356
|
+
})),
|
|
357
|
+
theme
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
match: allMatch,
|
|
363
|
+
properties,
|
|
364
|
+
figmaStyles: cleanFigmaStyles,
|
|
365
|
+
renderedStyles,
|
|
366
|
+
tokenSummary,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Convert camelCase to kebab-case CSS property
|
|
372
|
+
*/
|
|
373
|
+
function toCssProperty(prop: string): string {
|
|
374
|
+
return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Format token summary for display
|
|
379
|
+
*/
|
|
380
|
+
export function formatTokenSummary(summary: TokenUsageSummary): string {
|
|
381
|
+
const lines: string[] = [];
|
|
382
|
+
|
|
383
|
+
lines.push(`Token Compliance: ${summary.compliancePercent}%`);
|
|
384
|
+
lines.push(
|
|
385
|
+
`${summary.usingTokens}/${summary.totalProperties} properties using tokens`
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
if (summary.hardcoded > 0) {
|
|
389
|
+
lines.push(`${summary.hardcoded} hardcoded value(s) detected`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (summary.implicitMatches > 0) {
|
|
393
|
+
lines.push(`${summary.implicitMatches} implicit match(es)`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return lines.join("\n");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get status badge for token compliance
|
|
401
|
+
*/
|
|
402
|
+
export function getComplianceBadge(
|
|
403
|
+
compliancePercent: number
|
|
404
|
+
): { label: string; color: string } {
|
|
405
|
+
if (compliancePercent >= 100) {
|
|
406
|
+
return { label: "Excellent", color: "green" };
|
|
407
|
+
} else if (compliancePercent >= 80) {
|
|
408
|
+
return { label: "Good", color: "blue" };
|
|
409
|
+
} else if (compliancePercent >= 50) {
|
|
410
|
+
return { label: "Fair", color: "yellow" };
|
|
411
|
+
} else {
|
|
412
|
+
return { label: "Poor", color: "red" };
|
|
413
|
+
}
|
|
414
|
+
}
|