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,400 @@
|
|
|
1
|
+
import { splitClassName, parseUtilityClass, applyTailwindStylesToFrame } from '../tailwind';
|
|
2
|
+
import { parseLength } from '../tailwind';
|
|
3
|
+
import { parseColor } from '../tokens';
|
|
4
|
+
import { createIcon } from '../effects';
|
|
5
|
+
import { createTextNode } from '../text';
|
|
6
|
+
import { enforceFixedBoxSizingAfterLayout } from '../layout';
|
|
7
|
+
import { createCompoundComponent } from '../components';
|
|
8
|
+
import { isTruthyStateProp } from './state-utils';
|
|
9
|
+
import { normalizeComponentName, findFirstMatchingInstance } from './story-tree-search';
|
|
10
|
+
import { getThemeContext } from './theme-context';
|
|
11
|
+
import { setGeneratedFallbackReason } from './generated-node';
|
|
12
|
+
import type { StoryBuilderContext, StoryRenderContext } from './story-builder-context';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Frame builders that render a single story instance into a Figma frame.
|
|
16
|
+
*
|
|
17
|
+
* - `createStateStoryFrame` produces the per-state visual for state-typed
|
|
18
|
+
* components (Input, Checkbox, Switch); also used by ensureStateComponentSet
|
|
19
|
+
* when assembling state component sets.
|
|
20
|
+
* - `createSimpleStoryFrame` is the fallback path for simple components
|
|
21
|
+
* when no symbol-instance match is available.
|
|
22
|
+
* - `buildNonCvaSymbolSourceNode` chooses the right frame builder for a
|
|
23
|
+
* given non-CVA def (simple / state / compound) — used by
|
|
24
|
+
* ensureNonCvaComponentMaster (still in story-builder, phase 7).
|
|
25
|
+
*
|
|
26
|
+
* The state-class addActive helper and the translate-x / state-active
|
|
27
|
+
* helpers all support the state-frame variant logic.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
function addActiveDataVariantClasses(classes: string[], props: Record<string, any>): string[] {
|
|
32
|
+
const checked = isTruthyStateProp(props.checked) || isTruthyStateProp(props.defaultChecked) || isTruthyStateProp(props['aria-checked']);
|
|
33
|
+
const disabled = isTruthyStateProp(props.disabled) || isTruthyStateProp(props['aria-disabled']);
|
|
34
|
+
const out = classes.slice();
|
|
35
|
+
|
|
36
|
+
function pushIfMissing(token: string): void {
|
|
37
|
+
if (!token) return;
|
|
38
|
+
if (out.indexOf(token) !== -1) return;
|
|
39
|
+
out.push(token);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < classes.length; i++) {
|
|
43
|
+
const cls = classes[i];
|
|
44
|
+
if (checked && cls.startsWith('data-[checked]:')) {
|
|
45
|
+
pushIfMissing(cls.substring('data-[checked]:'.length));
|
|
46
|
+
}
|
|
47
|
+
if (checked && cls.startsWith('group-data-[checked]:')) {
|
|
48
|
+
pushIfMissing(cls.substring('group-data-[checked]:'.length));
|
|
49
|
+
}
|
|
50
|
+
if (disabled && cls.startsWith('data-[disabled]:')) {
|
|
51
|
+
pushIfMissing(cls.substring('data-[disabled]:'.length));
|
|
52
|
+
}
|
|
53
|
+
if (disabled && cls.startsWith('group-data-[disabled]:')) {
|
|
54
|
+
pushIfMissing(cls.substring('group-data-[disabled]:'.length));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
function normalizeStateValue(value: any): string {
|
|
63
|
+
if (value == null) return '';
|
|
64
|
+
return String(value).trim().toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isStateActiveFromProps(
|
|
68
|
+
stateName: string,
|
|
69
|
+
trigger: string | undefined,
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
props: Record<string, any>,
|
|
72
|
+
explicitState: string,
|
|
73
|
+
checked: boolean,
|
|
74
|
+
disabled: boolean
|
|
75
|
+
): boolean {
|
|
76
|
+
const normalizedName = normalizeStateValue(stateName);
|
|
77
|
+
const normalizedTrigger = normalizeStateValue(trigger);
|
|
78
|
+
|
|
79
|
+
if (normalizedName && explicitState === normalizedName) return true;
|
|
80
|
+
|
|
81
|
+
if (normalizedName === 'checked') return checked;
|
|
82
|
+
if (normalizedName === 'disabled') return disabled;
|
|
83
|
+
if (normalizedName === 'error') return isTruthyStateProp(props['aria-invalid']);
|
|
84
|
+
if (normalizedName === 'open') {
|
|
85
|
+
return explicitState === 'open' || normalizeStateValue(props['data-state']) === 'open';
|
|
86
|
+
}
|
|
87
|
+
if (normalizedName === 'focus') return explicitState === 'focus' || explicitState === 'focus-visible';
|
|
88
|
+
if (normalizedName === 'active') return explicitState === 'active';
|
|
89
|
+
if (normalizedName === 'hover') return explicitState === 'hover';
|
|
90
|
+
|
|
91
|
+
if (!normalizedTrigger) return false;
|
|
92
|
+
if (normalizedTrigger.indexOf('checked') !== -1) return checked;
|
|
93
|
+
if (normalizedTrigger.indexOf('disabled') !== -1) return disabled;
|
|
94
|
+
if (normalizedTrigger.indexOf('invalid') !== -1) return isTruthyStateProp(props['aria-invalid']);
|
|
95
|
+
if (normalizedTrigger.indexOf('state=open') !== -1) {
|
|
96
|
+
return explicitState === 'open' || normalizeStateValue(props['data-state']) === 'open';
|
|
97
|
+
}
|
|
98
|
+
if (normalizedTrigger.indexOf('focus') !== -1) return explicitState === 'focus' || explicitState === 'focus-visible';
|
|
99
|
+
if (normalizedTrigger.indexOf('active') !== -1) return explicitState === 'active';
|
|
100
|
+
if (normalizedTrigger.indexOf('hover') !== -1) return explicitState === 'hover';
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseTranslateDistanceToken(token: string): number | null {
|
|
105
|
+
const normalized = String(token || '').trim();
|
|
106
|
+
if (!normalized) return null;
|
|
107
|
+
if (normalized === 'px') return 1;
|
|
108
|
+
if (normalized === '0') return 0;
|
|
109
|
+
if (normalized.startsWith('[') && normalized.endsWith(']')) {
|
|
110
|
+
const arbitrary = normalized.slice(1, -1).trim();
|
|
111
|
+
if (!arbitrary) return null;
|
|
112
|
+
const parsed = parseLength(arbitrary);
|
|
113
|
+
if (parsed != null) return parsed;
|
|
114
|
+
const num = parseFloat(arbitrary);
|
|
115
|
+
if (!Number.isNaN(num)) return num;
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const spaced = parseFloat(normalized);
|
|
119
|
+
if (!Number.isNaN(spaced)) return spaced * 4;
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function parseTranslateXValue(utility: string): number | null {
|
|
124
|
+
if (!utility) return null;
|
|
125
|
+
let sign = 1;
|
|
126
|
+
let candidate = utility;
|
|
127
|
+
if (candidate.startsWith('-')) {
|
|
128
|
+
sign = -1;
|
|
129
|
+
candidate = candidate.slice(1);
|
|
130
|
+
}
|
|
131
|
+
if (!candidate.startsWith('translate-x-')) return null;
|
|
132
|
+
const token = candidate.slice('translate-x-'.length);
|
|
133
|
+
const distance = parseTranslateDistanceToken(token);
|
|
134
|
+
if (distance == null) return null;
|
|
135
|
+
return sign * distance;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getTranslateXProfile(classes: string[]): { hasTranslateX: boolean; baseOffset: number; activeOffset: number } {
|
|
139
|
+
let hasTranslateX = false;
|
|
140
|
+
let activeOffset = 0;
|
|
141
|
+
let baseOffset: number | null = null;
|
|
142
|
+
for (let i = 0; i < classes.length; i++) {
|
|
143
|
+
const cls = classes[i];
|
|
144
|
+
const atom = parseUtilityClass(cls);
|
|
145
|
+
const value = parseTranslateXValue(atom.utility || '');
|
|
146
|
+
if (value == null) continue;
|
|
147
|
+
hasTranslateX = true;
|
|
148
|
+
activeOffset = value;
|
|
149
|
+
if (baseOffset == null && atom.variants.length === 0) {
|
|
150
|
+
baseOffset = value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
hasTranslateX,
|
|
155
|
+
baseOffset: baseOffset ?? 0,
|
|
156
|
+
activeOffset,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
161
|
+
function findStorySlotClasses(story: any, slotName: string): string[] {
|
|
162
|
+
const tree = story && story.jsxTree;
|
|
163
|
+
if (!tree) return [];
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
165
|
+
const elements: any[] = [];
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
167
|
+
function walk(node: any): void {
|
|
168
|
+
if (!node || node.type !== 'element') return;
|
|
169
|
+
elements.push(node);
|
|
170
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
171
|
+
for (let i = 0; i < children.length; i++) walk(children[i]);
|
|
172
|
+
}
|
|
173
|
+
walk(tree);
|
|
174
|
+
const target = String(slotName || '').toLowerCase();
|
|
175
|
+
for (let i = 0; i < elements.length; i++) {
|
|
176
|
+
const el = elements[i];
|
|
177
|
+
const dataSlot = el && el.props ? String(el.props['data-slot'] || '').toLowerCase() : '';
|
|
178
|
+
if (!dataSlot || dataSlot !== target) continue;
|
|
179
|
+
return splitClassName(el.props && el.props.className);
|
|
180
|
+
}
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
|
+
export function createStateStoryFrame(def: any, instance: any, theme: string, ctx: StoryBuilderContext, fallbackReason?: string, story?: any): any {
|
|
186
|
+
const themeContext = getThemeContext(theme);
|
|
187
|
+
const colorGroup = themeContext.colorGroup;
|
|
188
|
+
const radiusGroup = themeContext.radiusGroup;
|
|
189
|
+
const props = instance.props || {};
|
|
190
|
+
const normalizedName = normalizeComponentName(def && def.name ? def.name : '');
|
|
191
|
+
const isSwitchControl = normalizedName === 'switch';
|
|
192
|
+
|
|
193
|
+
let classes = def.baseClasses ? def.baseClasses.slice() : [];
|
|
194
|
+
classes = classes.concat(splitClassName(props.className));
|
|
195
|
+
classes = addActiveDataVariantClasses(classes, props);
|
|
196
|
+
|
|
197
|
+
const comp = figma.createFrame();
|
|
198
|
+
comp.name = def.name;
|
|
199
|
+
comp.primaryAxisSizingMode = 'AUTO';
|
|
200
|
+
comp.counterAxisSizingMode = 'AUTO';
|
|
201
|
+
comp.primaryAxisAlignItems = 'CENTER';
|
|
202
|
+
comp.counterAxisAlignItems = 'CENTER';
|
|
203
|
+
comp.itemSpacing = 8;
|
|
204
|
+
comp.fills = [];
|
|
205
|
+
comp.strokes = [];
|
|
206
|
+
ctx.applyClipBehavior(comp, classes);
|
|
207
|
+
|
|
208
|
+
applyTailwindStylesToFrame(comp, classes, colorGroup, radiusGroup, theme);
|
|
209
|
+
|
|
210
|
+
const explicitState = normalizeStateValue(props.state || props['data-state']);
|
|
211
|
+
const isChecked = isTruthyStateProp(props.checked) || isTruthyStateProp(props.defaultChecked) || isTruthyStateProp(props['aria-checked']);
|
|
212
|
+
const isDisabled = isTruthyStateProp(props.disabled) || isTruthyStateProp(props['aria-disabled']);
|
|
213
|
+
if (def && def.states) {
|
|
214
|
+
const stateKeys = Object.keys(def.states);
|
|
215
|
+
for (let i = 0; i < stateKeys.length; i++) {
|
|
216
|
+
const key = stateKeys[i];
|
|
217
|
+
if (key === 'default') continue;
|
|
218
|
+
const stateDef = def.states[key];
|
|
219
|
+
const trigger = stateDef && stateDef.trigger ? String(stateDef.trigger) : '';
|
|
220
|
+
if (!isStateActiveFromProps(key, trigger, props, explicitState, isChecked, isDisabled)) continue;
|
|
221
|
+
applyTailwindStylesToFrame(comp, stateDef && stateDef.classes ? stateDef.classes : [], colorGroup, radiusGroup, theme);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (isSwitchControl) {
|
|
226
|
+
let thumbClasses = findStorySlotClasses(story, 'switch-thumb');
|
|
227
|
+
if (thumbClasses.length === 0) {
|
|
228
|
+
thumbClasses = splitClassName('pointer-events-none block size-5 rounded-full bg-background shadow-sm transition-transform duration-200 translate-x-0.5 group-data-[checked]:translate-x-[1.25rem]');
|
|
229
|
+
}
|
|
230
|
+
thumbClasses = addActiveDataVariantClasses(thumbClasses, props);
|
|
231
|
+
const translateProfile = getTranslateXProfile(thumbClasses);
|
|
232
|
+
const thumb = figma.createFrame();
|
|
233
|
+
thumb.name = def.name + ' Thumb';
|
|
234
|
+
thumb.layoutMode = 'VERTICAL';
|
|
235
|
+
thumb.primaryAxisSizingMode = 'AUTO';
|
|
236
|
+
thumb.counterAxisSizingMode = 'AUTO';
|
|
237
|
+
thumb.fills = [];
|
|
238
|
+
thumb.strokes = [];
|
|
239
|
+
ctx.applyClipBehavior(thumb, thumbClasses);
|
|
240
|
+
applyTailwindStylesToFrame(thumb, thumbClasses, colorGroup, radiusGroup, theme);
|
|
241
|
+
if (translateProfile.hasTranslateX) {
|
|
242
|
+
const inset = Math.max(0, Math.round(Math.abs(translateProfile.baseOffset)));
|
|
243
|
+
comp.itemSpacing = 0;
|
|
244
|
+
comp.primaryAxisAlignItems = translateProfile.activeOffset > translateProfile.baseOffset ? 'MAX' : 'MIN';
|
|
245
|
+
if (inset > 0) {
|
|
246
|
+
comp.paddingLeft = inset;
|
|
247
|
+
comp.paddingRight = inset;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
comp.appendChild(thumb);
|
|
251
|
+
} else if (isChecked) {
|
|
252
|
+
const indicatorColor = colorGroup['primary-foreground']
|
|
253
|
+
? parseColor(colorGroup['primary-foreground'])
|
|
254
|
+
: { r: 1, g: 1, b: 1 };
|
|
255
|
+
const checkIcon = createIcon('check', indicatorColor);
|
|
256
|
+
if (checkIcon) {
|
|
257
|
+
// Match the scanner's `<Check className="size-3" />` — 12px square.
|
|
258
|
+
try { checkIcon.resize(12, 12); } catch { /* ignore */ }
|
|
259
|
+
comp.appendChild(checkIcon);
|
|
260
|
+
} else {
|
|
261
|
+
comp.appendChild(createTextNode('✓', { fontSize: 10, bold: true, fill: indicatorColor }));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const hasCheckedState = !!(def && def.states && def.states.checked);
|
|
266
|
+
const labelFromChildren = instance && instance.children != null ? String(instance.children).trim() : '';
|
|
267
|
+
const label = (labelFromChildren !== '' ? labelFromChildren : '') || props.placeholder || props.defaultValue;
|
|
268
|
+
const labelText = label != null ? String(label).trim() : '';
|
|
269
|
+
const isSyntheticNameLabel = normalizeComponentName(labelText) === normalizeComponentName(def && def.name ? def.name : '');
|
|
270
|
+
const suppressInlineLabel = hasCheckedState && labelFromChildren !== '';
|
|
271
|
+
const isTextarea = normalizedName === 'textarea';
|
|
272
|
+
if (!suppressInlineLabel && labelText !== '' && !isSyntheticNameLabel) {
|
|
273
|
+
const textColor = colorGroup['muted-foreground'] ? parseColor(colorGroup['muted-foreground']) : { r: 0.4, g: 0.4, b: 0.4 };
|
|
274
|
+
const text = createTextNode(labelText, { fontSize: 14, fill: textColor });
|
|
275
|
+
// Textarea is multi-line: wrap text to the frame's content width and
|
|
276
|
+
// grow the frame vertically. Without this a multi-line `defaultValue`
|
|
277
|
+
// overflows the frame's right edge (state-instance label is rendered
|
|
278
|
+
// as a single line by default).
|
|
279
|
+
if (isTextarea) {
|
|
280
|
+
try {
|
|
281
|
+
comp.layoutMode = 'VERTICAL';
|
|
282
|
+
comp.counterAxisSizingMode = 'FIXED';
|
|
283
|
+
comp.primaryAxisSizingMode = 'AUTO';
|
|
284
|
+
comp.primaryAxisAlignItems = 'MIN';
|
|
285
|
+
comp.counterAxisAlignItems = 'MIN';
|
|
286
|
+
} catch { /* ignore */ }
|
|
287
|
+
}
|
|
288
|
+
comp.appendChild(text);
|
|
289
|
+
if (isTextarea) {
|
|
290
|
+
try {
|
|
291
|
+
const padX = (comp.paddingLeft || 0) + (comp.paddingRight || 0);
|
|
292
|
+
const contentWidth = Math.max(1, comp.width - padX);
|
|
293
|
+
text.textAutoResize = 'HEIGHT';
|
|
294
|
+
text.resize(contentWidth, text.height);
|
|
295
|
+
} catch { /* ignore */ }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (fallbackReason) {
|
|
299
|
+
setGeneratedFallbackReason(comp, fallbackReason);
|
|
300
|
+
}
|
|
301
|
+
enforceFixedBoxSizingAfterLayout(comp, classes);
|
|
302
|
+
|
|
303
|
+
return comp;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
307
|
+
export function createSimpleStoryFrame(def: any, instance: any, theme: string, ctx: StoryBuilderContext, fallbackReason?: string): any {
|
|
308
|
+
const themeContext = getThemeContext(theme);
|
|
309
|
+
const colorGroup = themeContext.colorGroup;
|
|
310
|
+
const radiusGroup = themeContext.radiusGroup;
|
|
311
|
+
const props = instance.props || {};
|
|
312
|
+
|
|
313
|
+
let classes = def.classes ? def.classes.slice() : [];
|
|
314
|
+
classes = classes.concat(splitClassName(props.className));
|
|
315
|
+
|
|
316
|
+
const frame = figma.createFrame();
|
|
317
|
+
frame.name = def.name;
|
|
318
|
+
frame.layoutMode = 'VERTICAL';
|
|
319
|
+
frame.itemSpacing = 0;
|
|
320
|
+
frame.primaryAxisSizingMode = 'AUTO';
|
|
321
|
+
frame.counterAxisSizingMode = 'AUTO';
|
|
322
|
+
frame.counterAxisAlignItems = 'MIN';
|
|
323
|
+
frame.fills = [];
|
|
324
|
+
ctx.applyClipBehavior(frame, classes);
|
|
325
|
+
|
|
326
|
+
applyTailwindStylesToFrame(frame, classes, colorGroup, radiusGroup, theme);
|
|
327
|
+
|
|
328
|
+
let renderedStructuredChildren = 0;
|
|
329
|
+
const jsxChildren = Array.isArray(props.__jsxChildren) ? props.__jsxChildren : [];
|
|
330
|
+
if (jsxChildren.length > 0) {
|
|
331
|
+
const maxWidth = Number.isFinite(frame.width)
|
|
332
|
+
? Math.max(1, frame.width - (frame.paddingLeft || 0) - (frame.paddingRight || 0))
|
|
333
|
+
: undefined;
|
|
334
|
+
const renderContext: StoryRenderContext = {
|
|
335
|
+
maxWidth: maxWidth,
|
|
336
|
+
parentLayout: 'VERTICAL',
|
|
337
|
+
textAlign: undefined,
|
|
338
|
+
};
|
|
339
|
+
for (let i = 0; i < jsxChildren.length; i++) {
|
|
340
|
+
const childNode = ctx.renderJsxTree(
|
|
341
|
+
jsxChildren[i],
|
|
342
|
+
colorGroup,
|
|
343
|
+
radiusGroup,
|
|
344
|
+
theme,
|
|
345
|
+
0,
|
|
346
|
+
renderContext
|
|
347
|
+
);
|
|
348
|
+
if (!childNode) continue;
|
|
349
|
+
ctx.applyAbsoluteIfAllowed(childNode, frame, false);
|
|
350
|
+
frame.appendChild(childNode);
|
|
351
|
+
renderedStructuredChildren++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (renderedStructuredChildren === 0) {
|
|
356
|
+
const label = instance.children || props.children;
|
|
357
|
+
if (label) {
|
|
358
|
+
frame.appendChild(createTextNode(label, { fontSize: 12 }));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (fallbackReason) {
|
|
362
|
+
setGeneratedFallbackReason(frame, fallbackReason);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return frame;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
369
|
+
export function buildNonCvaSymbolSourceNode(def: any, theme: string, ctx: StoryBuilderContext): any | null {
|
|
370
|
+
if (!def) return null;
|
|
371
|
+
if (def.type === 'simple') {
|
|
372
|
+
const frame = createSimpleStoryFrame(def, { props: {} }, theme, ctx);
|
|
373
|
+
return frame;
|
|
374
|
+
}
|
|
375
|
+
if (def.type === 'state') {
|
|
376
|
+
const sourceInstance = findFirstMatchingInstance(def);
|
|
377
|
+
const frame = createStateStoryFrame(def, {
|
|
378
|
+
props: Object.assign({}, (sourceInstance && sourceInstance.props) || {}),
|
|
379
|
+
children: sourceInstance && sourceInstance.children ? sourceInstance.children : undefined,
|
|
380
|
+
}, theme, ctx);
|
|
381
|
+
return frame;
|
|
382
|
+
}
|
|
383
|
+
if (def.type === 'compound') {
|
|
384
|
+
const scratch = figma.createFrame();
|
|
385
|
+
scratch.name = '__scratch';
|
|
386
|
+
scratch.layoutMode = 'VERTICAL';
|
|
387
|
+
scratch.primaryAxisSizingMode = 'AUTO';
|
|
388
|
+
scratch.counterAxisSizingMode = 'AUTO';
|
|
389
|
+
scratch.fills = [];
|
|
390
|
+
const frame = createCompoundComponent(scratch, def, theme);
|
|
391
|
+
if (!frame) {
|
|
392
|
+
scratch.remove();
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
const clone = frame.clone();
|
|
396
|
+
scratch.remove();
|
|
397
|
+
return clone;
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|