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.
Files changed (178) hide show
  1. package/README.md +108 -25
  2. package/bin/inkbridge.mjs +354 -83
  3. package/code.js +40 -11802
  4. package/manifest.json +1 -0
  5. package/package.json +74 -23
  6. package/scanner/adapter-utils-regression.ts +159 -0
  7. package/scanner/aspect-percent-position-regression.ts +237 -0
  8. package/scanner/aspect-ratio-regression.ts +90 -0
  9. package/scanner/blob-placement-regression.ts +2 -2
  10. package/scanner/block-cache-regression.ts +195 -0
  11. package/scanner/bundle-size-regression.ts +50 -0
  12. package/scanner/child-sizing-matrix-regression.ts +303 -0
  13. package/scanner/cli.ts +342 -13
  14. package/scanner/component-scanner.ts +2108 -174
  15. package/scanner/component-sections-regression.ts +198 -0
  16. package/scanner/compound-classes-lookup-regression.ts +163 -0
  17. package/scanner/css-token-reader-regression.ts +7 -6
  18. package/scanner/css-token-reader.ts +152 -31
  19. package/scanner/cva-jsx-child-fallback-regression.ts +98 -0
  20. package/scanner/cva-master-icon-regression.ts +315 -0
  21. package/scanner/data-attr-prop-alias-regression.ts +129 -0
  22. package/scanner/explicit-size-root-regression.ts +102 -0
  23. package/scanner/font-family-extract-regression.ts +113 -0
  24. package/scanner/font-style-resolver-regression.ts +1 -1
  25. package/scanner/framework-adapter-shadcn-regression.ts +480 -0
  26. package/scanner/full-width-matrix-regression.ts +338 -0
  27. package/scanner/grid-cols-extraction-regression.ts +110 -0
  28. package/scanner/image-src-collector-regression.ts +204 -0
  29. package/scanner/inline-flex-regression.ts +235 -0
  30. package/scanner/input-range-regression.ts +217 -0
  31. package/scanner/instance-rendering-regression.ts +224 -0
  32. package/scanner/jsx-prop-unresolved-regression.ts +178 -0
  33. package/scanner/jsx-text-regression.ts +178 -0
  34. package/scanner/layout-alignment-regression.ts +108 -0
  35. package/scanner/layout-flex-regression.ts +90 -0
  36. package/scanner/layout-mode-regression.ts +71 -0
  37. package/scanner/layout-sizing-regression.ts +227 -0
  38. package/scanner/layout-spacing-regression.ts +135 -0
  39. package/scanner/local-const-className-regression.ts +331 -0
  40. package/scanner/percent-position-regression.ts +105 -0
  41. package/scanner/provider-cascade-regression.ts +224 -0
  42. package/scanner/provider-flatten-regression.ts +235 -0
  43. package/scanner/radial-gradient-regression.ts +1 -1
  44. package/scanner/render-prop-parser-regression.ts +161 -0
  45. package/scanner/ring-utility-regression.ts +153 -0
  46. package/scanner/sandbox-spread-regression.ts +125 -0
  47. package/scanner/selection-pressed-regression.ts +241 -0
  48. package/scanner/size-full-normalization-regression.ts +127 -0
  49. package/scanner/state-classification-regression.ts +175 -0
  50. package/scanner/story-diagnostics-regression.ts +216 -0
  51. package/scanner/story-dimensioning-regression.ts +298 -0
  52. package/scanner/story-render-strategy-regression.ts +205 -0
  53. package/scanner/stretch-to-parent-width-regression.ts +147 -0
  54. package/scanner/svg-fill-parent-regression.ts +98 -0
  55. package/scanner/svg-group-inheritance-regression.ts +166 -0
  56. package/scanner/svg-marker-inline-regression.ts +211 -0
  57. package/scanner/svg-marker-regression.ts +116 -0
  58. package/scanner/tailwind-parser.ts +46 -4
  59. package/scanner/text-resize-matrix-regression.ts +173 -0
  60. package/scanner/transform-math-regression.ts +1 -1
  61. package/scanner/types.ts +26 -2
  62. package/src/cache/frame-cache.ts +150 -0
  63. package/src/cache/index.ts +2 -0
  64. package/src/{component-defs.ts → components/component-defs.ts} +25 -10
  65. package/src/{component-gen.ts → components/component-gen.ts} +43 -116
  66. package/src/components/component-instance.ts +386 -0
  67. package/src/components/component-library.ts +44 -0
  68. package/src/components/component-lookup.ts +161 -0
  69. package/src/components/index.ts +7 -0
  70. package/src/components/scanner-types.ts +39 -0
  71. package/src/components/symbol-instance-policy.ts +312 -0
  72. package/src/design-system/block-cache.ts +130 -0
  73. package/src/design-system/component-sections.ts +107 -0
  74. package/src/design-system/cva-inference.ts +187 -0
  75. package/src/design-system/cva-master.ts +427 -0
  76. package/src/design-system/cva-utils.ts +29 -0
  77. package/src/design-system/design-system.ts +334 -0
  78. package/src/design-system/frame-stabilizers.ts +191 -0
  79. package/src/design-system/frame-utils.ts +46 -0
  80. package/src/design-system/generated-node.ts +84 -0
  81. package/src/design-system/icon-rendering.ts +229 -0
  82. package/src/design-system/index.ts +13 -0
  83. package/src/design-system/instance-rendering.ts +307 -0
  84. package/src/design-system/master-shared.ts +133 -0
  85. package/src/design-system/node-helpers.ts +237 -0
  86. package/src/design-system/node-variants.ts +196 -0
  87. package/src/design-system/non-cva-master.ts +104 -0
  88. package/src/design-system/portal-handling.ts +138 -0
  89. package/src/design-system/preview-builder.ts +738 -0
  90. package/src/{render-context.ts → design-system/render-context.ts} +32 -6
  91. package/src/design-system/render-prop-parser.ts +50 -0
  92. package/src/design-system/responsive-resolver.ts +180 -0
  93. package/src/design-system/selectable-state.ts +157 -0
  94. package/src/design-system/state-master.ts +267 -0
  95. package/src/design-system/state-utils.ts +15 -0
  96. package/src/design-system/story-builder-context.ts +40 -0
  97. package/src/design-system/story-builder.ts +1322 -0
  98. package/src/design-system/story-diagnostics.ts +80 -0
  99. package/src/design-system/story-dimensioning.ts +272 -0
  100. package/src/design-system/story-frames.ts +400 -0
  101. package/src/design-system/story-instance.ts +333 -0
  102. package/src/{story-layout.ts → design-system/story-layout.ts} +2 -2
  103. package/src/design-system/story-render-strategy.ts +150 -0
  104. package/src/design-system/story-tree-search.ts +110 -0
  105. package/src/design-system/symbol-fallback.ts +89 -0
  106. package/src/design-system/symbol-source.ts +172 -0
  107. package/src/design-system/table-helpers.ts +56 -0
  108. package/src/design-system/tag-predicates.ts +99 -0
  109. package/src/design-system/theme-context.ts +52 -0
  110. package/src/design-system/typography.ts +100 -0
  111. package/src/design-system/ui-builder.ts +2676 -0
  112. package/src/{clip-path-decorative.ts → effects/clip-path-decorative.ts} +11 -11
  113. package/src/effects/icon-builder.ts +1074 -0
  114. package/src/effects/index.ts +5 -0
  115. package/src/effects/portal-panel.ts +369 -0
  116. package/src/{radial-gradient.ts → effects/radial-gradient.ts} +1 -1
  117. package/src/framework-adapters/index.ts +47 -0
  118. package/src/framework-adapters/shadcn.ts +541 -0
  119. package/src/{github.ts → github/github.ts} +46 -21
  120. package/src/github/index.ts +1 -0
  121. package/src/layout/deferred-layout.ts +1556 -0
  122. package/src/layout/index.ts +24 -0
  123. package/src/layout/layout-parser.ts +375 -0
  124. package/src/{layout-utils.ts → layout/layout-utils.ts} +23 -17
  125. package/src/layout/parser/alignment.ts +54 -0
  126. package/src/layout/parser/flex.ts +59 -0
  127. package/src/layout/parser/index.ts +65 -0
  128. package/src/layout/parser/ir.ts +80 -0
  129. package/src/layout/parser/layout-mode.ts +57 -0
  130. package/src/layout/parser/sizing.ts +241 -0
  131. package/src/layout/parser/spacing-scale.ts +78 -0
  132. package/src/layout/parser/spacing.ts +134 -0
  133. package/src/layout/ring-utils.ts +120 -0
  134. package/src/layout/size-utils.ts +143 -0
  135. package/src/layout/text-resize-decision.ts +51 -0
  136. package/src/{width-solver.ts → layout/width-solver.ts} +168 -37
  137. package/src/main.ts +444 -162
  138. package/src/{config.ts → plugin/config.ts} +12 -12
  139. package/src/{dev-server.ts → plugin/dev-server.ts} +3 -3
  140. package/src/plugin/image-src-collector.ts +52 -0
  141. package/src/plugin/index.ts +3 -0
  142. package/src/plugin/packs/index.ts +2 -0
  143. package/src/{pack-provider.ts → plugin/packs/pack-provider.ts} +12 -12
  144. package/src/{packs.ts → plugin/packs/packs.ts} +22 -17
  145. package/src/render-engine-version.ts +2 -0
  146. package/src/tailwind/adapter-utils.ts +137 -0
  147. package/src/{class-utils.ts → tailwind/class-utils.ts} +33 -6
  148. package/src/tailwind/index.ts +8 -0
  149. package/src/tailwind/jsx-utils.ts +319 -0
  150. package/src/{node-ir.ts → tailwind/node-ir.ts} +208 -19
  151. package/src/{responsive-analyzer.ts → tailwind/responsive-analyzer.ts} +32 -2
  152. package/src/{state-analyzer.ts → tailwind/state-analyzer.ts} +71 -5
  153. package/src/{tailwind.ts → tailwind/tailwind.ts} +423 -674
  154. package/src/{utility-resolver.ts → tailwind/utility-resolver.ts} +27 -6
  155. package/src/{font-style-resolver.ts → text/font-style-resolver.ts} +0 -2
  156. package/src/text/index.ts +4 -0
  157. package/src/{inline-text.ts → text/inline-text.ts} +13 -13
  158. package/src/{text-builder.ts → text/text-builder.ts} +24 -7
  159. package/src/{text-line.ts → text/text-line.ts} +2 -2
  160. package/src/{change-detection.ts → tokens/change-detection.ts} +12 -12
  161. package/src/{color-resolver.ts → tokens/color-resolver.ts} +1 -6
  162. package/src/{colors.ts → tokens/colors.ts} +13 -6
  163. package/src/tokens/index.ts +6 -0
  164. package/src/{token-source.ts → tokens/token-source.ts} +4 -1
  165. package/src/{tokens.ts → tokens/tokens.ts} +116 -20
  166. package/src/{variables.ts → tokens/variables.ts} +447 -102
  167. package/templates/patch-tokens-route.ts +25 -6
  168. package/templates/scan-components-route.ts +26 -5
  169. package/ui.html +485 -37
  170. package/src/component-lookup.ts +0 -82
  171. package/src/design-system.ts +0 -59
  172. package/src/icon-builder.ts +0 -607
  173. package/src/layout-parser.ts +0 -667
  174. package/src/story-builder.ts +0 -1706
  175. package/src/ui-builder.ts +0 -1996
  176. /package/src/{image-cache.ts → cache/image-cache.ts} +0 -0
  177. /package/src/{blob-placement.ts → effects/blob-placement.ts} +0 -0
  178. /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
+ }