@fragments-sdk/viewer 0.2.1
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 +84 -0
- package/index.html +28 -0
- package/package.json +71 -0
- package/src/__tests__/a11y-fixes.test.ts +358 -0
- package/src/__tests__/jsx-parser.test.ts +502 -0
- package/src/__tests__/render-utils.test.ts +232 -0
- package/src/__tests__/style-utils.test.ts +404 -0
- package/src/app/index.ts +1 -0
- package/src/assets/fragments-logo.ts +4 -0
- package/src/assets/fragments_logo.png +0 -0
- package/src/components/AccessibilityPanel.tsx +1457 -0
- package/src/components/ActionCapture.tsx +172 -0
- package/src/components/ActionsPanel.tsx +332 -0
- package/src/components/AllVariantsPreview.tsx +78 -0
- package/src/components/App.tsx +604 -0
- package/src/components/BottomPanel.tsx +288 -0
- package/src/components/CodePanel.naming.test.tsx +59 -0
- package/src/components/CodePanel.tsx +118 -0
- package/src/components/CommandPalette.tsx +392 -0
- package/src/components/ComponentDocView.tsx +164 -0
- package/src/components/ComponentGraph.tsx +380 -0
- package/src/components/ComponentHeader.tsx +88 -0
- package/src/components/ContractPanel.tsx +241 -0
- package/src/components/DeviceMockup.tsx +156 -0
- package/src/components/EmptyVariantMessage.tsx +54 -0
- package/src/components/ErrorBoundary.tsx +97 -0
- package/src/components/FigmaEmbed.tsx +238 -0
- package/src/components/FragmentEditor.tsx +525 -0
- package/src/components/FragmentRenderer.tsx +61 -0
- package/src/components/HeaderSearch.tsx +24 -0
- package/src/components/HealthDashboard.tsx +441 -0
- package/src/components/HmrStatusIndicator.tsx +61 -0
- package/src/components/Icons.tsx +479 -0
- package/src/components/InteractionsPanel.tsx +757 -0
- package/src/components/IsolatedPreviewFrame.tsx +390 -0
- package/src/components/IsolatedRender.tsx +113 -0
- package/src/components/KeyboardShortcutsHelp.tsx +53 -0
- package/src/components/LandingPage.tsx +420 -0
- package/src/components/Layout.tsx +27 -0
- package/src/components/LeftSidebar.tsx +472 -0
- package/src/components/LoadErrorMessage.tsx +102 -0
- package/src/components/MultiViewportPreview.tsx +527 -0
- package/src/components/NoVariantsMessage.tsx +59 -0
- package/src/components/PanelShell.tsx +161 -0
- package/src/components/PerformancePanel.tsx +304 -0
- package/src/components/PreviewArea.tsx +254 -0
- package/src/components/PreviewAside.tsx +168 -0
- package/src/components/PreviewFrameHost.tsx +304 -0
- package/src/components/PreviewToolbar.tsx +80 -0
- package/src/components/PropsEditor.tsx +506 -0
- package/src/components/PropsTable.tsx +111 -0
- package/src/components/RelationsSection.tsx +88 -0
- package/src/components/ResizablePanel.tsx +271 -0
- package/src/components/RightSidebar.tsx +102 -0
- package/src/components/RuntimeToolsRegistrar.tsx +17 -0
- package/src/components/ScreenshotButton.tsx +90 -0
- package/src/components/ShadowPreview.tsx +204 -0
- package/src/components/Sidebar.tsx +169 -0
- package/src/components/SkeletonLoader.tsx +161 -0
- package/src/components/ThemeProvider.tsx +42 -0
- package/src/components/Toast.tsx +3 -0
- package/src/components/TokenStylePanel.tsx +699 -0
- package/src/components/TopToolbar.tsx +159 -0
- package/src/components/Untitled +1 -0
- package/src/components/UsageSection.tsx +95 -0
- package/src/components/VariantMatrix.tsx +391 -0
- package/src/components/VariantRenderer.tsx +131 -0
- package/src/components/VariantTabs.tsx +40 -0
- package/src/components/ViewerHeader.tsx +69 -0
- package/src/components/ViewerStateSync.tsx +52 -0
- package/src/components/ViewportSelector.tsx +172 -0
- package/src/components/WebMCPDevTools.tsx +503 -0
- package/src/components/WebMCPIntegration.tsx +47 -0
- package/src/components/WebMCPStatusIndicator.tsx +60 -0
- package/src/components/_future/CreatePage.tsx +835 -0
- package/src/components/viewer-utils.ts +16 -0
- package/src/composition-renderer.ts +381 -0
- package/src/constants/index.ts +1 -0
- package/src/constants/ui.ts +166 -0
- package/src/entry.tsx +335 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useA11yCache.ts +383 -0
- package/src/hooks/useA11yService.ts +364 -0
- package/src/hooks/useActions.ts +138 -0
- package/src/hooks/useAppState.ts +147 -0
- package/src/hooks/useCompiledFragments.ts +42 -0
- package/src/hooks/useFigmaIntegration.ts +132 -0
- package/src/hooks/useHmrStatus.ts +109 -0
- package/src/hooks/useKeyboardShortcuts.ts +270 -0
- package/src/hooks/usePreviewBridge.ts +347 -0
- package/src/hooks/useScrollSpy.ts +78 -0
- package/src/hooks/useShadowStyles.ts +221 -0
- package/src/hooks/useUrlState.ts +318 -0
- package/src/hooks/useViewSettings.ts +111 -0
- package/src/intelligence/healthReport.ts +505 -0
- package/src/intelligence/styleDrift.ts +340 -0
- package/src/intelligence/usageScanner.ts +309 -0
- package/src/jsx-parser.ts +486 -0
- package/src/preview-frame-entry.tsx +25 -0
- package/src/preview-frame.html +148 -0
- package/src/render-template.html +68 -0
- package/src/render-utils.ts +311 -0
- package/src/shared/ComponentDocContent.module.scss +10 -0
- package/src/shared/ComponentDocContent.module.scss.d.ts +2 -0
- package/src/shared/ComponentDocContent.tsx +274 -0
- package/src/shared/DocsHeaderBar.tsx +129 -0
- package/src/shared/DocsPageAsideHost.tsx +89 -0
- package/src/shared/DocsPageShell.tsx +124 -0
- package/src/shared/DocsSearchCommand.tsx +99 -0
- package/src/shared/DocsSidebarNav.tsx +66 -0
- package/src/shared/PropsTable.module.scss +68 -0
- package/src/shared/PropsTable.module.scss.d.ts +2 -0
- package/src/shared/PropsTable.tsx +76 -0
- package/src/shared/VariantPreviewCard.module.scss +114 -0
- package/src/shared/VariantPreviewCard.module.scss.d.ts +2 -0
- package/src/shared/VariantPreviewCard.tsx +137 -0
- package/src/shared/docs-data/index.ts +32 -0
- package/src/shared/docs-data/mcp-configs.ts +72 -0
- package/src/shared/docs-data/palettes.ts +75 -0
- package/src/shared/docs-data/setup-examples.ts +55 -0
- package/src/shared/docs-layout.scss +28 -0
- package/src/shared/docs-layout.scss.d.ts +2 -0
- package/src/shared/index.ts +34 -0
- package/src/shared/types.ts +53 -0
- package/src/style-utils.ts +414 -0
- package/src/styles/globals.css +278 -0
- package/src/types/a11y.ts +197 -0
- package/src/utils/a11y-fixes.ts +509 -0
- package/src/utils/actionExport.ts +372 -0
- package/src/utils/colorSchemes.ts +201 -0
- package/src/utils/contrast.ts +246 -0
- package/src/utils/detectRelationships.ts +256 -0
- package/src/webmcp/__tests__/analytics.test.ts +108 -0
- package/src/webmcp/analytics.ts +165 -0
- package/src/webmcp/index.ts +3 -0
- package/src/webmcp/posthog-bridge.ts +39 -0
- package/src/webmcp/runtime-tools.ts +152 -0
- package/src/webmcp/scan-utils.ts +135 -0
- package/src/webmcp/use-tool-analytics.ts +69 -0
- package/src/webmcp/viewer-state.ts +45 -0
- package/tsconfig.json +20 -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
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
* Fragments Viewer Shell Styles
|
|
3
|
+
* ============================================
|
|
4
|
+
* Viewer-specific token aliases and utility classes.
|
|
5
|
+
* Base resets (box-sizing, body, scrollbars) come from
|
|
6
|
+
* @fragments/ui globals imported via entry.tsx.
|
|
7
|
+
* ============================================ */
|
|
8
|
+
|
|
9
|
+
/* ============================================
|
|
10
|
+
* Token Aliases
|
|
11
|
+
* ============================================
|
|
12
|
+
* Shorthand aliases mapping to --fui-* tokens so the viewer
|
|
13
|
+
* shell CSS can use shorter variable names.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
:root {
|
|
17
|
+
--bg-primary: var(--fui-bg-primary, #ffffff);
|
|
18
|
+
--bg-secondary: var(--fui-bg-secondary, #f7f7f8);
|
|
19
|
+
--bg-tertiary: var(--fui-bg-tertiary, #f2f2f2);
|
|
20
|
+
--bg-elevated: var(--fui-bg-elevated, #ffffff);
|
|
21
|
+
--bg-hover: var(--fui-bg-hover, rgba(0, 0, 0, 0.06));
|
|
22
|
+
--bg-active: var(--fui-bg-active, rgba(0, 0, 0, 0.1));
|
|
23
|
+
|
|
24
|
+
--text-primary: var(--fui-text-primary, #171717);
|
|
25
|
+
--text-secondary: var(--fui-text-secondary, #525252);
|
|
26
|
+
--text-tertiary: var(--fui-text-tertiary, #8a8a8a);
|
|
27
|
+
--text-muted: var(--fui-text-tertiary, #8a8a8a);
|
|
28
|
+
|
|
29
|
+
--border: var(--fui-border, rgba(0, 0, 0, 0.1));
|
|
30
|
+
--border-subtle: var(--fui-border, rgba(0, 0, 0, 0.1));
|
|
31
|
+
--border-strong: var(--fui-border-strong, rgba(0, 0, 0, 0.16));
|
|
32
|
+
|
|
33
|
+
--color-accent: var(--fui-color-accent, #10a37f);
|
|
34
|
+
--color-accent-hover: var(--fui-color-accent-hover, #0d8a6a);
|
|
35
|
+
--color-accent-subtle: var(--fui-color-success-bg, rgba(16, 163, 127, 0.1));
|
|
36
|
+
|
|
37
|
+
--color-success: var(--fui-color-success, #10a37f);
|
|
38
|
+
--color-success-bg: var(--fui-color-success-bg, rgba(16, 163, 127, 0.1));
|
|
39
|
+
--color-warning: var(--fui-color-warning, #f59e0b);
|
|
40
|
+
--color-warning-bg: var(--fui-color-warning-bg, rgba(245, 158, 11, 0.1));
|
|
41
|
+
--color-danger: var(--fui-color-danger, #ef4444);
|
|
42
|
+
--color-danger-bg: var(--fui-color-danger-bg, rgba(239, 68, 68, 0.1));
|
|
43
|
+
--color-info: var(--fui-color-info, #3b82f6);
|
|
44
|
+
--color-info-bg: var(--fui-color-info-bg, rgba(59, 130, 246, 0.1));
|
|
45
|
+
|
|
46
|
+
--shadow-sm: var(--fui-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.05));
|
|
47
|
+
--shadow-md: var(--fui-shadow-md, 0 4px 6px -1px rgba(0, 0, 0, 0.08));
|
|
48
|
+
--shadow-lg: var(--fui-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.12));
|
|
49
|
+
|
|
50
|
+
--radius-sm: var(--fui-radius-sm, 6px);
|
|
51
|
+
--radius-md: var(--fui-radius-md, 8px);
|
|
52
|
+
--radius-lg: var(--fui-radius-lg, 12px);
|
|
53
|
+
|
|
54
|
+
--transition-fast: var(--fui-transition-fast, 150ms ease);
|
|
55
|
+
--transition-normal: var(--fui-transition-normal, 200ms ease);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ============================================
|
|
59
|
+
* Viewer-specific overrides
|
|
60
|
+
* ============================================ */
|
|
61
|
+
|
|
62
|
+
* {
|
|
63
|
+
border-color: var(--border);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ============================================
|
|
67
|
+
* Theme-aware Utility Classes
|
|
68
|
+
* ============================================ */
|
|
69
|
+
|
|
70
|
+
.bg-primary { background-color: var(--bg-primary); }
|
|
71
|
+
.bg-secondary { background-color: var(--bg-secondary); }
|
|
72
|
+
.bg-tertiary { background-color: var(--bg-tertiary); }
|
|
73
|
+
.bg-elevated { background-color: var(--bg-elevated); }
|
|
74
|
+
|
|
75
|
+
.text-primary { color: var(--text-primary); }
|
|
76
|
+
.text-secondary { color: var(--text-secondary); }
|
|
77
|
+
.text-tertiary { color: var(--text-tertiary); }
|
|
78
|
+
.text-accent { color: var(--color-accent); }
|
|
79
|
+
|
|
80
|
+
.border-subtle { border-color: var(--border-subtle); }
|
|
81
|
+
.border-strong { border-color: var(--border-strong); }
|
|
82
|
+
|
|
83
|
+
/* ============================================
|
|
84
|
+
* Surface Styles
|
|
85
|
+
* ============================================ */
|
|
86
|
+
|
|
87
|
+
.surface {
|
|
88
|
+
background-color: var(--bg-elevated);
|
|
89
|
+
border: 1px solid var(--border);
|
|
90
|
+
border-radius: var(--radius-lg);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.surface-interactive {
|
|
94
|
+
background-color: var(--bg-elevated);
|
|
95
|
+
border: 1px solid var(--border);
|
|
96
|
+
border-radius: var(--radius-lg);
|
|
97
|
+
transition: all var(--transition-fast);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.surface-interactive:hover {
|
|
101
|
+
border-color: var(--border-strong);
|
|
102
|
+
box-shadow: var(--shadow-sm);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ============================================
|
|
106
|
+
* Badge Styles
|
|
107
|
+
* ============================================ */
|
|
108
|
+
|
|
109
|
+
.badge {
|
|
110
|
+
display: inline-flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
padding: 3px 8px;
|
|
113
|
+
border-radius: var(--radius-sm);
|
|
114
|
+
font-size: 11px;
|
|
115
|
+
font-weight: 500;
|
|
116
|
+
letter-spacing: 0.01em;
|
|
117
|
+
transition: all var(--transition-fast);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.badge-default {
|
|
121
|
+
background-color: var(--bg-tertiary);
|
|
122
|
+
color: var(--text-secondary);
|
|
123
|
+
border: 1px solid var(--border-subtle);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.badge-success {
|
|
127
|
+
background-color: var(--color-success-bg);
|
|
128
|
+
color: var(--color-success);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.badge-warning {
|
|
132
|
+
background-color: var(--color-warning-bg);
|
|
133
|
+
color: var(--color-warning);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.badge-danger {
|
|
137
|
+
background-color: var(--color-danger-bg);
|
|
138
|
+
color: var(--color-danger);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ============================================
|
|
142
|
+
* Focus Ring
|
|
143
|
+
* ============================================ */
|
|
144
|
+
|
|
145
|
+
.focus-ring:focus-visible {
|
|
146
|
+
outline: none;
|
|
147
|
+
box-shadow: 0 0 0 2px var(--bg-primary), 0 0 0 4px var(--color-accent);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* ============================================
|
|
151
|
+
* Transition Utilities
|
|
152
|
+
* ============================================ */
|
|
153
|
+
|
|
154
|
+
.transition-colors {
|
|
155
|
+
transition: color var(--transition-fast), background-color var(--transition-fast), border-color var(--transition-fast);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.transition-all {
|
|
159
|
+
transition: all var(--transition-fast);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* ============================================
|
|
163
|
+
* Code Styling
|
|
164
|
+
* ============================================ */
|
|
165
|
+
|
|
166
|
+
code {
|
|
167
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
168
|
+
font-size: 0.875em;
|
|
169
|
+
background-color: var(--bg-tertiary);
|
|
170
|
+
padding: 2px 6px;
|
|
171
|
+
border-radius: var(--radius-sm);
|
|
172
|
+
border: 1px solid var(--border-subtle);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
pre code {
|
|
176
|
+
background: none;
|
|
177
|
+
padding: 0;
|
|
178
|
+
border: none;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ============================================
|
|
182
|
+
* Table Base Styles
|
|
183
|
+
* ============================================ */
|
|
184
|
+
|
|
185
|
+
table {
|
|
186
|
+
width: 100%;
|
|
187
|
+
border-collapse: separate;
|
|
188
|
+
border-spacing: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
th {
|
|
192
|
+
text-align: left;
|
|
193
|
+
font-weight: 500;
|
|
194
|
+
font-size: 12px;
|
|
195
|
+
text-transform: uppercase;
|
|
196
|
+
letter-spacing: 0.05em;
|
|
197
|
+
color: var(--text-tertiary);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
td {
|
|
201
|
+
border-top: 1px solid var(--border-subtle);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ============================================
|
|
205
|
+
* Utility Classes
|
|
206
|
+
* ============================================ */
|
|
207
|
+
|
|
208
|
+
.divider {
|
|
209
|
+
height: 1px;
|
|
210
|
+
background-color: var(--border);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.text-balance {
|
|
214
|
+
text-wrap: balance;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Scroll utilities */
|
|
218
|
+
.scroll-smooth {
|
|
219
|
+
scroll-behavior: smooth;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.scrollbar-hide {
|
|
223
|
+
-ms-overflow-style: none;
|
|
224
|
+
scrollbar-width: none;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.scrollbar-hide::-webkit-scrollbar {
|
|
228
|
+
display: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* ============================================
|
|
232
|
+
* Gradient Utilities
|
|
233
|
+
* ============================================ */
|
|
234
|
+
|
|
235
|
+
.gradient-fade-b {
|
|
236
|
+
background: linear-gradient(to bottom, transparent, var(--bg-primary));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.gradient-fade-t {
|
|
240
|
+
background: linear-gradient(to top, transparent, var(--bg-primary));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Subtle glow effect for interactive elements */
|
|
244
|
+
.glow-accent:hover {
|
|
245
|
+
box-shadow: 0 0 20px rgba(16, 163, 127, 0.15);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* ============================================
|
|
249
|
+
* Animations
|
|
250
|
+
* ============================================ */
|
|
251
|
+
|
|
252
|
+
@keyframes spin {
|
|
253
|
+
to { transform: rotate(360deg); }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@keyframes fadeIn {
|
|
257
|
+
from { opacity: 0; }
|
|
258
|
+
to { opacity: 1; }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@keyframes slideIn {
|
|
262
|
+
from {
|
|
263
|
+
opacity: 0;
|
|
264
|
+
transform: translateY(4px);
|
|
265
|
+
}
|
|
266
|
+
to {
|
|
267
|
+
opacity: 1;
|
|
268
|
+
transform: translateY(0);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.animate-fadeIn {
|
|
273
|
+
animation: fadeIn var(--transition-normal) ease-out;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.animate-slideIn {
|
|
277
|
+
animation: slideIn var(--transition-normal) ease-out;
|
|
278
|
+
}
|