inkbridge 0.1.0-beta.2 → 0.1.0-beta.21
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/README.md +108 -25
- package/bin/inkbridge.mjs +354 -83
- package/code.js +40 -11802
- package/manifest.json +1 -0
- package/package.json +74 -23
- package/scanner/adapter-utils-regression.ts +159 -0
- package/scanner/aspect-percent-position-regression.ts +237 -0
- package/scanner/aspect-ratio-regression.ts +90 -0
- package/scanner/blob-placement-regression.ts +2 -2
- package/scanner/block-cache-regression.ts +195 -0
- package/scanner/bundle-size-regression.ts +50 -0
- package/scanner/child-sizing-matrix-regression.ts +303 -0
- package/scanner/cli.ts +342 -13
- package/scanner/component-scanner.ts +2108 -174
- package/scanner/component-sections-regression.ts +198 -0
- package/scanner/compound-classes-lookup-regression.ts +163 -0
- package/scanner/css-token-reader-regression.ts +7 -6
- package/scanner/css-token-reader.ts +152 -31
- package/scanner/cva-jsx-child-fallback-regression.ts +98 -0
- package/scanner/cva-master-icon-regression.ts +315 -0
- package/scanner/data-attr-prop-alias-regression.ts +129 -0
- package/scanner/explicit-size-root-regression.ts +102 -0
- package/scanner/font-family-extract-regression.ts +113 -0
- package/scanner/font-style-resolver-regression.ts +1 -1
- package/scanner/framework-adapter-shadcn-regression.ts +480 -0
- package/scanner/full-width-matrix-regression.ts +338 -0
- package/scanner/grid-cols-extraction-regression.ts +110 -0
- package/scanner/image-src-collector-regression.ts +204 -0
- package/scanner/inline-flex-regression.ts +235 -0
- package/scanner/input-range-regression.ts +217 -0
- package/scanner/instance-rendering-regression.ts +224 -0
- package/scanner/jsx-prop-unresolved-regression.ts +178 -0
- package/scanner/jsx-text-regression.ts +178 -0
- package/scanner/layout-alignment-regression.ts +108 -0
- package/scanner/layout-flex-regression.ts +90 -0
- package/scanner/layout-mode-regression.ts +71 -0
- package/scanner/layout-sizing-regression.ts +227 -0
- package/scanner/layout-spacing-regression.ts +135 -0
- package/scanner/local-const-className-regression.ts +331 -0
- package/scanner/percent-position-regression.ts +105 -0
- package/scanner/provider-cascade-regression.ts +224 -0
- package/scanner/provider-flatten-regression.ts +235 -0
- package/scanner/radial-gradient-regression.ts +1 -1
- package/scanner/render-prop-parser-regression.ts +161 -0
- package/scanner/ring-utility-regression.ts +153 -0
- package/scanner/sandbox-spread-regression.ts +125 -0
- package/scanner/selection-pressed-regression.ts +241 -0
- package/scanner/size-full-normalization-regression.ts +127 -0
- package/scanner/state-classification-regression.ts +175 -0
- package/scanner/story-diagnostics-regression.ts +216 -0
- package/scanner/story-dimensioning-regression.ts +298 -0
- package/scanner/story-render-strategy-regression.ts +205 -0
- package/scanner/stretch-to-parent-width-regression.ts +147 -0
- package/scanner/svg-fill-parent-regression.ts +98 -0
- package/scanner/svg-group-inheritance-regression.ts +166 -0
- package/scanner/svg-marker-inline-regression.ts +211 -0
- package/scanner/svg-marker-regression.ts +116 -0
- package/scanner/tailwind-parser.ts +46 -4
- package/scanner/text-resize-matrix-regression.ts +173 -0
- package/scanner/transform-math-regression.ts +1 -1
- package/scanner/types.ts +26 -2
- package/src/cache/frame-cache.ts +150 -0
- package/src/cache/index.ts +2 -0
- package/src/{component-defs.ts → components/component-defs.ts} +25 -10
- package/src/{component-gen.ts → components/component-gen.ts} +43 -116
- package/src/components/component-instance.ts +386 -0
- package/src/components/component-library.ts +44 -0
- package/src/components/component-lookup.ts +161 -0
- package/src/components/index.ts +7 -0
- package/src/components/scanner-types.ts +39 -0
- package/src/components/symbol-instance-policy.ts +312 -0
- package/src/design-system/block-cache.ts +130 -0
- package/src/design-system/component-sections.ts +107 -0
- package/src/design-system/cva-inference.ts +187 -0
- package/src/design-system/cva-master.ts +427 -0
- package/src/design-system/cva-utils.ts +29 -0
- package/src/design-system/design-system.ts +334 -0
- package/src/design-system/frame-stabilizers.ts +191 -0
- package/src/design-system/frame-utils.ts +46 -0
- package/src/design-system/generated-node.ts +84 -0
- package/src/design-system/icon-rendering.ts +229 -0
- package/src/design-system/index.ts +13 -0
- package/src/design-system/instance-rendering.ts +307 -0
- package/src/design-system/master-shared.ts +133 -0
- package/src/design-system/node-helpers.ts +237 -0
- package/src/design-system/node-variants.ts +196 -0
- package/src/design-system/non-cva-master.ts +104 -0
- package/src/design-system/portal-handling.ts +138 -0
- package/src/design-system/preview-builder.ts +738 -0
- package/src/{render-context.ts → design-system/render-context.ts} +32 -6
- package/src/design-system/render-prop-parser.ts +50 -0
- package/src/design-system/responsive-resolver.ts +180 -0
- package/src/design-system/selectable-state.ts +157 -0
- package/src/design-system/state-master.ts +267 -0
- package/src/design-system/state-utils.ts +15 -0
- package/src/design-system/story-builder-context.ts +40 -0
- package/src/design-system/story-builder.ts +1322 -0
- package/src/design-system/story-diagnostics.ts +80 -0
- package/src/design-system/story-dimensioning.ts +272 -0
- package/src/design-system/story-frames.ts +400 -0
- package/src/design-system/story-instance.ts +333 -0
- package/src/{story-layout.ts → design-system/story-layout.ts} +2 -2
- package/src/design-system/story-render-strategy.ts +150 -0
- package/src/design-system/story-tree-search.ts +110 -0
- package/src/design-system/symbol-fallback.ts +89 -0
- package/src/design-system/symbol-source.ts +172 -0
- package/src/design-system/table-helpers.ts +56 -0
- package/src/design-system/tag-predicates.ts +99 -0
- package/src/design-system/theme-context.ts +52 -0
- package/src/design-system/typography.ts +100 -0
- package/src/design-system/ui-builder.ts +2676 -0
- package/src/{clip-path-decorative.ts → effects/clip-path-decorative.ts} +11 -11
- package/src/effects/icon-builder.ts +1074 -0
- package/src/effects/index.ts +5 -0
- package/src/effects/portal-panel.ts +369 -0
- package/src/{radial-gradient.ts → effects/radial-gradient.ts} +1 -1
- package/src/framework-adapters/index.ts +47 -0
- package/src/framework-adapters/shadcn.ts +541 -0
- package/src/{github.ts → github/github.ts} +46 -21
- package/src/github/index.ts +1 -0
- package/src/layout/deferred-layout.ts +1556 -0
- package/src/layout/index.ts +24 -0
- package/src/layout/layout-parser.ts +375 -0
- package/src/{layout-utils.ts → layout/layout-utils.ts} +23 -17
- package/src/layout/parser/alignment.ts +54 -0
- package/src/layout/parser/flex.ts +59 -0
- package/src/layout/parser/index.ts +65 -0
- package/src/layout/parser/ir.ts +80 -0
- package/src/layout/parser/layout-mode.ts +57 -0
- package/src/layout/parser/sizing.ts +241 -0
- package/src/layout/parser/spacing-scale.ts +78 -0
- package/src/layout/parser/spacing.ts +134 -0
- package/src/layout/ring-utils.ts +120 -0
- package/src/layout/size-utils.ts +143 -0
- package/src/layout/text-resize-decision.ts +51 -0
- package/src/{width-solver.ts → layout/width-solver.ts} +168 -37
- package/src/main.ts +444 -162
- package/src/{config.ts → plugin/config.ts} +12 -12
- package/src/{dev-server.ts → plugin/dev-server.ts} +3 -3
- package/src/plugin/image-src-collector.ts +52 -0
- package/src/plugin/index.ts +3 -0
- package/src/plugin/packs/index.ts +2 -0
- package/src/{pack-provider.ts → plugin/packs/pack-provider.ts} +12 -12
- package/src/{packs.ts → plugin/packs/packs.ts} +22 -17
- package/src/render-engine-version.ts +2 -0
- package/src/tailwind/adapter-utils.ts +137 -0
- package/src/{class-utils.ts → tailwind/class-utils.ts} +33 -6
- package/src/tailwind/index.ts +8 -0
- package/src/tailwind/jsx-utils.ts +319 -0
- package/src/{node-ir.ts → tailwind/node-ir.ts} +208 -19
- package/src/{responsive-analyzer.ts → tailwind/responsive-analyzer.ts} +32 -2
- package/src/{state-analyzer.ts → tailwind/state-analyzer.ts} +71 -5
- package/src/{tailwind.ts → tailwind/tailwind.ts} +423 -674
- package/src/{utility-resolver.ts → tailwind/utility-resolver.ts} +27 -6
- package/src/{font-style-resolver.ts → text/font-style-resolver.ts} +0 -2
- package/src/text/index.ts +4 -0
- package/src/{inline-text.ts → text/inline-text.ts} +13 -13
- package/src/{text-builder.ts → text/text-builder.ts} +24 -7
- package/src/{text-line.ts → text/text-line.ts} +2 -2
- package/src/{change-detection.ts → tokens/change-detection.ts} +12 -12
- package/src/{color-resolver.ts → tokens/color-resolver.ts} +1 -6
- package/src/{colors.ts → tokens/colors.ts} +13 -6
- package/src/tokens/index.ts +6 -0
- package/src/{token-source.ts → tokens/token-source.ts} +4 -1
- package/src/{tokens.ts → tokens/tokens.ts} +116 -20
- package/src/{variables.ts → tokens/variables.ts} +447 -102
- package/templates/patch-tokens-route.ts +25 -6
- package/templates/scan-components-route.ts +26 -5
- package/ui.html +485 -37
- package/src/component-lookup.ts +0 -82
- package/src/design-system.ts +0 -59
- package/src/icon-builder.ts +0 -607
- package/src/layout-parser.ts +0 -667
- package/src/story-builder.ts +0 -1706
- package/src/ui-builder.ts +0 -1996
- /package/src/{image-cache.ts → cache/image-cache.ts} +0 -0
- /package/src/{blob-placement.ts → effects/blob-placement.ts} +0 -0
- /package/src/{transform-math.ts → tailwind/transform-math.ts} +0 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { splitClassName, applyTailwindStylesToFrame, tailwindClassesToStyle, type TailwindStyle } from '../tailwind';
|
|
2
|
+
import { parseColor, bindColorVariable } from '../tokens';
|
|
3
|
+
import { createTextNode } from '../text';
|
|
4
|
+
import { getRingInfoFromClasses, markRingNode, applyRingIfPossible, enforceFixedBoxSizingAfterLayout } from '../layout';
|
|
5
|
+
import { MASTER_ICON_NAME_KEY } from '../components/component-instance';
|
|
6
|
+
import { findChildByName, getFrameHash, setFrameHash, hashString, hashDef } from '../cache';
|
|
7
|
+
import { RENDER_ENGINE_VERSION } from '../render-engine-version';
|
|
8
|
+
import { tagGeneratedNode } from './generated-node';
|
|
9
|
+
import { getThemeContext } from './theme-context';
|
|
10
|
+
import {
|
|
11
|
+
ENABLE_SYMBOL_MASTERS,
|
|
12
|
+
toFigmaVariantPropertyName,
|
|
13
|
+
toFigmaVariantPropertyValue,
|
|
14
|
+
ensureThemeComponentLibrary,
|
|
15
|
+
} from './master-shared';
|
|
16
|
+
import { buildCvaClassesWithSelection } from './cva-utils';
|
|
17
|
+
import { getPreviewTypography } from './typography';
|
|
18
|
+
import { buildCvaSymbolSourceNode } from './symbol-source';
|
|
19
|
+
import type { StoryBuilderContext } from './story-builder-context';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* CVA-master logic: enumerating Class-Variance-Authority variant
|
|
23
|
+
* combinations and producing a Figma component set per design-token theme.
|
|
24
|
+
*
|
|
25
|
+
* Shared concepts live in sibling utility modules so this file can import
|
|
26
|
+
* its dependencies directly without callback indirection:
|
|
27
|
+
* - buildCvaClassesWithSelection → cva-utils.ts
|
|
28
|
+
* - getPreviewTypography → typography.ts
|
|
29
|
+
* - buildCvaSymbolSourceNode → symbol-source.ts
|
|
30
|
+
*
|
|
31
|
+
* Re-exported here for back-compat with story-builder's existing imports.
|
|
32
|
+
*/
|
|
33
|
+
export { buildCvaClassesWithSelection };
|
|
34
|
+
|
|
35
|
+
export const MAX_CVA_MASTER_COMBINATIONS = 96;
|
|
36
|
+
|
|
37
|
+
export function applyStyleToFrame(frame: FrameNode, style: TailwindStyle, theme: string): void {
|
|
38
|
+
if (!style) return;
|
|
39
|
+
|
|
40
|
+
if (style.bg) {
|
|
41
|
+
const bgBound = style.bgToken && bindColorVariable(frame, style.bgToken, 'fill', theme);
|
|
42
|
+
if (bgBound) {
|
|
43
|
+
if (style.bgOpacity != null && Array.isArray(frame.fills) && frame.fills.length > 0) {
|
|
44
|
+
const nextFills = JSON.parse(JSON.stringify(frame.fills));
|
|
45
|
+
nextFills[0].opacity = style.bgOpacity;
|
|
46
|
+
frame.fills = nextFills;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
const bg = parseColor(style.bg);
|
|
50
|
+
const bgOpacity = style.bgOpacity != null ? style.bgOpacity : (bg.a == null ? 1 : bg.a);
|
|
51
|
+
frame.fills = [{ type: 'SOLID', color: { r: bg.r, g: bg.g, b: bg.b }, opacity: bgOpacity }];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (style.border) {
|
|
56
|
+
const borderBound = style.borderToken && bindColorVariable(frame, style.borderToken, 'stroke', theme);
|
|
57
|
+
if (!borderBound) {
|
|
58
|
+
const borderColor = parseColor(style.border);
|
|
59
|
+
frame.strokes = [{ type: 'SOLID', color: { r: borderColor.r, g: borderColor.g, b: borderColor.b } }];
|
|
60
|
+
}
|
|
61
|
+
frame.strokeWeight = 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (style.opacity != null) {
|
|
65
|
+
frame.opacity = style.opacity;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeVariantLookupValue(rawValue: unknown): string {
|
|
70
|
+
return String(rawValue == null ? '' : rawValue).trim().toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeVariantOptionValue(rawValue: unknown): string {
|
|
74
|
+
let value = String(rawValue == null ? '' : rawValue).trim();
|
|
75
|
+
if (
|
|
76
|
+
(value.startsWith('"') && value.endsWith('"'))
|
|
77
|
+
|| (value.startsWith("'") && value.endsWith("'"))
|
|
78
|
+
) {
|
|
79
|
+
value = value.slice(1, -1).trim();
|
|
80
|
+
}
|
|
81
|
+
return value.toLowerCase();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
function resolveCvaVariantValue(def: any, variantKey: string, candidate: unknown): string | null {
|
|
86
|
+
const values = def && def.variants && def.variants[variantKey];
|
|
87
|
+
if (!Array.isArray(values) || values.length === 0) return null;
|
|
88
|
+
const normalizedCandidate = normalizeVariantLookupValue(candidate);
|
|
89
|
+
if (!normalizedCandidate) return null;
|
|
90
|
+
for (let i = 0; i < values.length; i++) {
|
|
91
|
+
const option = values[i];
|
|
92
|
+
if (
|
|
93
|
+
normalizeVariantLookupValue(option) === normalizedCandidate
|
|
94
|
+
|| normalizeVariantOptionValue(option) === normalizedCandidate
|
|
95
|
+
) {
|
|
96
|
+
return String(option);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
+
function getDefaultCvaVariantValue(def: any, variantKey: string): string | null {
|
|
104
|
+
const fromDefault = resolveCvaVariantValue(
|
|
105
|
+
def,
|
|
106
|
+
variantKey,
|
|
107
|
+
def && def.defaultVariants ? def.defaultVariants[variantKey] : undefined
|
|
108
|
+
);
|
|
109
|
+
if (fromDefault) return fromDefault;
|
|
110
|
+
const values = def && def.variants && def.variants[variantKey];
|
|
111
|
+
if (!Array.isArray(values) || values.length === 0) return null;
|
|
112
|
+
return String(values[0]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isTruthyVariantValue(rawValue: unknown): boolean {
|
|
116
|
+
const lowered = normalizeVariantLookupValue(rawValue);
|
|
117
|
+
return lowered === 'true' || lowered === '1' || lowered === 'yes' || lowered === 'on';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
121
|
+
function inferCvaVariantValueFromClassName(def: any, variantKey: string, className: unknown): string | null {
|
|
122
|
+
const classTokens = splitClassName(className == null ? '' : String(className));
|
|
123
|
+
if (classTokens.length === 0) return null;
|
|
124
|
+
const present: Record<string, boolean> = {};
|
|
125
|
+
for (let i = 0; i < classTokens.length; i++) {
|
|
126
|
+
present[classTokens[i]] = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const values = def && def.variants && def.variants[variantKey];
|
|
130
|
+
if (!Array.isArray(values) || values.length === 0) return null;
|
|
131
|
+
|
|
132
|
+
let bestValue: string | null = null;
|
|
133
|
+
let bestScore = 0;
|
|
134
|
+
let tied = false;
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < values.length; i++) {
|
|
137
|
+
const value = String(values[i]);
|
|
138
|
+
const valueClasses = def && def.variantClasses && def.variantClasses[variantKey] && def.variantClasses[variantKey][value];
|
|
139
|
+
if (!Array.isArray(valueClasses) || valueClasses.length === 0) continue;
|
|
140
|
+
|
|
141
|
+
let score = 0;
|
|
142
|
+
for (let j = 0; j < valueClasses.length; j++) {
|
|
143
|
+
const token = String(valueClasses[j] || '').trim();
|
|
144
|
+
if (!token) continue;
|
|
145
|
+
if (present[token]) score++;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (score > bestScore) {
|
|
149
|
+
bestScore = score;
|
|
150
|
+
bestValue = value;
|
|
151
|
+
tied = false;
|
|
152
|
+
} else if (score > 0 && score === bestScore) {
|
|
153
|
+
tied = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (bestScore <= 0 || tied || !bestValue) return null;
|
|
158
|
+
return bestValue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
export function getCvaSelectionFromInstance(def: any, instance: any): Record<string, string> {
|
|
163
|
+
const props = instance && instance.props ? instance.props : {};
|
|
164
|
+
const selection: Record<string, string> = {};
|
|
165
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
166
|
+
for (let i = 0; i < variantKeys.length; i++) {
|
|
167
|
+
const key = variantKeys[i];
|
|
168
|
+
const resolved =
|
|
169
|
+
resolveCvaVariantValue(def, key, props[key])
|
|
170
|
+
|| inferCvaVariantValueFromClassName(def, key, props.className)
|
|
171
|
+
|| getDefaultCvaVariantValue(def, key);
|
|
172
|
+
if (resolved) selection[key] = resolved;
|
|
173
|
+
}
|
|
174
|
+
return selection;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
178
|
+
export function countCvaVariantCombinations(def: any): number {
|
|
179
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
180
|
+
if (variantKeys.length === 0) return 0;
|
|
181
|
+
let total = 1;
|
|
182
|
+
for (let i = 0; i < variantKeys.length; i++) {
|
|
183
|
+
const key = variantKeys[i];
|
|
184
|
+
const values = def && def.variants && def.variants[key];
|
|
185
|
+
if (!Array.isArray(values) || values.length === 0) return 0;
|
|
186
|
+
total *= values.length;
|
|
187
|
+
}
|
|
188
|
+
return total;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
192
|
+
function enumerateCvaVariantSelections(def: any): Record<string, string>[] {
|
|
193
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
194
|
+
const out: Record<string, string>[] = [];
|
|
195
|
+
if (variantKeys.length === 0) return out;
|
|
196
|
+
|
|
197
|
+
function walk(index: number, current: Record<string, string>): void {
|
|
198
|
+
if (index >= variantKeys.length) {
|
|
199
|
+
out.push(Object.assign({}, current));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const key = variantKeys[index];
|
|
203
|
+
const values = def && def.variants && def.variants[key];
|
|
204
|
+
if (!Array.isArray(values) || values.length === 0) return;
|
|
205
|
+
for (let i = 0; i < values.length; i++) {
|
|
206
|
+
current[key] = String(values[i]);
|
|
207
|
+
walk(index + 1, current);
|
|
208
|
+
}
|
|
209
|
+
delete current[key];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
walk(0, {});
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
|
+
export function buildCvaMasterHash(def: any, theme: string): string {
|
|
218
|
+
const themeContext = getThemeContext(theme);
|
|
219
|
+
return hashString(
|
|
220
|
+
hashDef(def)
|
|
221
|
+
+ ':'
|
|
222
|
+
+ theme
|
|
223
|
+
+ ':cva-master-v3:'
|
|
224
|
+
+ RENDER_ENGINE_VERSION
|
|
225
|
+
+ ':'
|
|
226
|
+
+ JSON.stringify({
|
|
227
|
+
colorGroup: themeContext.colorGroup,
|
|
228
|
+
radiusGroup: themeContext.radiusGroup,
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
234
|
+
export function shouldCreateCvaMaster(def: any): boolean {
|
|
235
|
+
if (!def || def.type !== 'cva') return false;
|
|
236
|
+
if (def.symbolCandidate === false) return false;
|
|
237
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
238
|
+
if (variantKeys.length === 0) return false;
|
|
239
|
+
const combinations = countCvaVariantCombinations(def);
|
|
240
|
+
return combinations > 0 && combinations <= MAX_CVA_MASTER_COMBINATIONS;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
244
|
+
export function getCvaSymbolFallbackReason(def: any, instance: any): string {
|
|
245
|
+
const props = instance && instance.props ? instance.props : {};
|
|
246
|
+
if (splitClassName(props.className).length > 0) return 'class_override';
|
|
247
|
+
if (def && def.symbolCandidate === false) return 'symbol_candidate_disabled';
|
|
248
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
249
|
+
if (variantKeys.length === 0) return 'missing_variants';
|
|
250
|
+
const combinations = countCvaVariantCombinations(def);
|
|
251
|
+
if (combinations <= 0) return 'invalid_variant_combinations';
|
|
252
|
+
if (combinations > MAX_CVA_MASTER_COMBINATIONS) return 'variant_combination_limit';
|
|
253
|
+
return 'component_set_or_instance_unavailable';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function ensureCvaComponentSet(
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
|
+
def: any,
|
|
259
|
+
theme: string,
|
|
260
|
+
ctx: StoryBuilderContext
|
|
261
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
262
|
+
): any | null {
|
|
263
|
+
if (!ENABLE_SYMBOL_MASTERS) return null;
|
|
264
|
+
if (!def || def.type !== 'cva') return null;
|
|
265
|
+
if (def.symbolCandidate === false) return null;
|
|
266
|
+
|
|
267
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
268
|
+
if (variantKeys.length === 0) return null;
|
|
269
|
+
|
|
270
|
+
const combinationCount = countCvaVariantCombinations(def);
|
|
271
|
+
if (combinationCount <= 0 || combinationCount > MAX_CVA_MASTER_COMBINATIONS) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const themeLibrary = ensureThemeComponentLibrary(theme);
|
|
276
|
+
if (!themeLibrary) return null;
|
|
277
|
+
|
|
278
|
+
const setName = def.name + ' [' + theme + ']';
|
|
279
|
+
const masterHash = buildCvaMasterHash(def, theme);
|
|
280
|
+
const existing = findChildByName(themeLibrary, setName);
|
|
281
|
+
if (existing && existing.type === 'COMPONENT_SET' && getFrameHash(existing) === masterHash) {
|
|
282
|
+
return existing;
|
|
283
|
+
}
|
|
284
|
+
if (existing) {
|
|
285
|
+
existing.remove();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const themeContext = getThemeContext(theme);
|
|
289
|
+
const colorGroup = themeContext.colorGroup;
|
|
290
|
+
const radiusGroup = themeContext.radiusGroup;
|
|
291
|
+
const selections = enumerateCvaVariantSelections(def);
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
293
|
+
const components: any[] = [];
|
|
294
|
+
|
|
295
|
+
for (let i = 0; i < selections.length; i++) {
|
|
296
|
+
const selection = selections[i];
|
|
297
|
+
const classes = buildCvaClassesWithSelection(def, selection, '');
|
|
298
|
+
|
|
299
|
+
const comp = figma.createComponent();
|
|
300
|
+
comp.name = variantKeys
|
|
301
|
+
.map(function(key: string) {
|
|
302
|
+
return toFigmaVariantPropertyName(key) + '=' + toFigmaVariantPropertyValue(selection[key]);
|
|
303
|
+
})
|
|
304
|
+
.join(', ');
|
|
305
|
+
comp.primaryAxisSizingMode = 'AUTO';
|
|
306
|
+
comp.counterAxisSizingMode = 'AUTO';
|
|
307
|
+
comp.counterAxisAlignItems = 'CENTER';
|
|
308
|
+
comp.primaryAxisAlignItems = 'CENTER';
|
|
309
|
+
comp.itemSpacing = 8;
|
|
310
|
+
comp.fills = [];
|
|
311
|
+
comp.strokes = [];
|
|
312
|
+
// ComponentNode and FrameNode share the auto-layout / fills / strokes
|
|
313
|
+
// members these helpers touch. Cast deliberately at the call boundary.
|
|
314
|
+
ctx.applyClipBehavior(comp as unknown as FrameNode, classes);
|
|
315
|
+
|
|
316
|
+
const storyNode = buildCvaSymbolSourceNode(def, selection, theme, ctx);
|
|
317
|
+
if (storyNode) {
|
|
318
|
+
comp.layoutMode = 'VERTICAL';
|
|
319
|
+
comp.primaryAxisSizingMode = 'AUTO';
|
|
320
|
+
comp.counterAxisSizingMode = 'AUTO';
|
|
321
|
+
comp.counterAxisAlignItems = 'MIN';
|
|
322
|
+
comp.primaryAxisAlignItems = 'MIN';
|
|
323
|
+
comp.itemSpacing = 0;
|
|
324
|
+
comp.appendChild(storyNode);
|
|
325
|
+
// Propagate the icon name (stamped by buildCvaSymbolSourceNode) from
|
|
326
|
+
// the rendered sourceNode up to the master variant Component. The
|
|
327
|
+
// CVA-instance creator reads this in `tryCreateCvaComponentInstance`
|
|
328
|
+
// to decide whether an instance with element children (an icon)
|
|
329
|
+
// matches the master and can safely reuse it.
|
|
330
|
+
try {
|
|
331
|
+
const iconName = typeof (storyNode as SceneNode).getPluginData === 'function'
|
|
332
|
+
? (storyNode as SceneNode).getPluginData(MASTER_ICON_NAME_KEY)
|
|
333
|
+
: '';
|
|
334
|
+
if (iconName) {
|
|
335
|
+
comp.setPluginData(MASTER_ICON_NAME_KEY, iconName);
|
|
336
|
+
}
|
|
337
|
+
} catch (_e) {
|
|
338
|
+
// ignore plugin-data access errors
|
|
339
|
+
}
|
|
340
|
+
// Set the inner storyNode to "Fill" horizontally so that when an
|
|
341
|
+
// instance of this master is resized by a stretching parent (e.g.
|
|
342
|
+
// shadcn DialogFooter's mobile `flex flex-col-reverse`, which gives
|
|
343
|
+
// children CSS `align-items: stretch`), the visible button pill
|
|
344
|
+
// follows the instance's new width. Equivalent to a designer
|
|
345
|
+
// manually switching the inner frame's W sizing to "Fill" in Figma's
|
|
346
|
+
// auto-layout panel. `layoutSizingHorizontal = 'FILL'` is the modern
|
|
347
|
+
// Figma API that the UI "Fill" toggle uses; `layoutAlign = 'STRETCH'`
|
|
348
|
+
// is the legacy equivalent and doesn't always propagate through HUG
|
|
349
|
+
// comp parents. Masters still hug at rest because the comp's
|
|
350
|
+
// counter-axis remains AUTO — FILL only activates width propagation
|
|
351
|
+
// when something pins the outer comp to an explicit size.
|
|
352
|
+
try {
|
|
353
|
+
if ('layoutSizingHorizontal' in storyNode) {
|
|
354
|
+
storyNode.layoutSizingHorizontal = 'FILL';
|
|
355
|
+
} else if ('layoutAlign' in storyNode) {
|
|
356
|
+
storyNode.layoutAlign = 'STRETCH';
|
|
357
|
+
}
|
|
358
|
+
} catch (_err) {
|
|
359
|
+
// ignore if plugin runtime rejects the assignment
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
// applyTailwindStylesToFrame / markRingNode / applyStyleToFrame all
|
|
363
|
+
// touch the shared frame mixins (fills, strokes, opacity, layout). Cast
|
|
364
|
+
// ComponentNode → FrameNode at the boundary; no `any`.
|
|
365
|
+
const compFrame = comp as unknown as FrameNode;
|
|
366
|
+
applyTailwindStylesToFrame(compFrame, classes, colorGroup, radiusGroup, theme);
|
|
367
|
+
const disabledVariantKey = variantKeys.find(function(key: string) {
|
|
368
|
+
return key.toLowerCase() === 'disabled';
|
|
369
|
+
});
|
|
370
|
+
const state = disabledVariantKey && isTruthyVariantValue(selection[disabledVariantKey])
|
|
371
|
+
? 'disabled'
|
|
372
|
+
: 'default';
|
|
373
|
+
const style = tailwindClassesToStyle(classes, state, colorGroup);
|
|
374
|
+
applyStyleToFrame(compFrame, style, theme);
|
|
375
|
+
|
|
376
|
+
const typography = getPreviewTypography(classes);
|
|
377
|
+
const textColor = style.text
|
|
378
|
+
? parseColor(style.text)
|
|
379
|
+
: (colorGroup.foreground ? parseColor(colorGroup.foreground) : { r: 0, g: 0, b: 0 });
|
|
380
|
+
const text = createTextNode(def.name, {
|
|
381
|
+
bold: typography.bold,
|
|
382
|
+
fontSize: typography.fontSize,
|
|
383
|
+
fill: textColor,
|
|
384
|
+
});
|
|
385
|
+
if (style.underline && text.textDecoration !== undefined) {
|
|
386
|
+
text.textDecoration = 'UNDERLINE';
|
|
387
|
+
}
|
|
388
|
+
comp.appendChild(text);
|
|
389
|
+
const ringInfo = getRingInfoFromClasses(classes, colorGroup);
|
|
390
|
+
if (ringInfo) markRingNode(compFrame, ringInfo);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
enforceFixedBoxSizingAfterLayout(comp, classes);
|
|
394
|
+
|
|
395
|
+
// combineAsVariants expects components to exist in the document tree.
|
|
396
|
+
themeLibrary.appendChild(comp);
|
|
397
|
+
// Comp is now in the theme library and its sizing has been finalised by
|
|
398
|
+
// `enforceFixedBoxSizingAfterLayout` — apply the (marked) ring overlay.
|
|
399
|
+
applyRingIfPossible(comp as unknown as FrameNode, themeLibrary);
|
|
400
|
+
components.push(comp);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (components.length === 0) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
408
|
+
let componentSet: any = null;
|
|
409
|
+
try {
|
|
410
|
+
componentSet = figma.combineAsVariants(components, themeLibrary);
|
|
411
|
+
} catch (_error) {
|
|
412
|
+
for (let i = 0; i < components.length; i++) {
|
|
413
|
+
try {
|
|
414
|
+
components[i].remove();
|
|
415
|
+
} catch (_cleanupError) {
|
|
416
|
+
// ignore cleanup errors
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
componentSet.name = setName;
|
|
423
|
+
componentSet.visible = false;
|
|
424
|
+
setFrameHash(componentSet, masterHash);
|
|
425
|
+
tagGeneratedNode(componentSet, 'cva-component-set:' + def.name + ':' + theme);
|
|
426
|
+
return componentSet;
|
|
427
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { splitClassName } from '../tailwind';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure CVA-class assembly. Takes a component def, a variant selection,
|
|
5
|
+
* and an optional className override; returns the resolved class list.
|
|
6
|
+
*
|
|
7
|
+
* Lifted out of cva-master.ts so symbol-source.ts can import it without
|
|
8
|
+
* cycling back through cva-master (which imports buildCvaSymbolSourceNode
|
|
9
|
+
* from symbol-source.ts).
|
|
10
|
+
*/
|
|
11
|
+
export function buildCvaClassesWithSelection(
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
def: any,
|
|
14
|
+
selection: Record<string, string>,
|
|
15
|
+
extraClassName: unknown
|
|
16
|
+
): string[] {
|
|
17
|
+
let classes = def && def.baseClasses ? def.baseClasses.slice() : [];
|
|
18
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
19
|
+
for (let i = 0; i < variantKeys.length; i++) {
|
|
20
|
+
const key = variantKeys[i];
|
|
21
|
+
const value = selection[key];
|
|
22
|
+
if (!value) continue;
|
|
23
|
+
if (def && def.variantClasses && def.variantClasses[key] && def.variantClasses[key][value]) {
|
|
24
|
+
classes = classes.concat(def.variantClasses[key][value]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
classes = classes.concat(splitClassName(extraClassName == null ? undefined : String(extraClassName)));
|
|
28
|
+
return classes;
|
|
29
|
+
}
|