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,267 @@
|
|
|
1
|
+
import { findChildByName, getFrameHash, setFrameHash, hashString, hashDef } from '../cache';
|
|
2
|
+
import { enforceFixedBoxSizingAfterLayout, applyRingIfPossible, getRingNode, markRingNode } from '../layout';
|
|
3
|
+
import { splitClassName } from '../tailwind';
|
|
4
|
+
import { RENDER_ENGINE_VERSION } from '../render-engine-version';
|
|
5
|
+
import { tagGeneratedNode } from './generated-node';
|
|
6
|
+
import { getThemeContext } from './theme-context';
|
|
7
|
+
import {
|
|
8
|
+
ENABLE_SYMBOL_MASTERS,
|
|
9
|
+
toFigmaVariantPropertyName,
|
|
10
|
+
toFigmaVariantPropertyValue,
|
|
11
|
+
ensureThemeComponentLibrary,
|
|
12
|
+
} from './master-shared';
|
|
13
|
+
import { isTruthyStateProp } from './state-utils';
|
|
14
|
+
import { createStateStoryFrame } from './story-frames';
|
|
15
|
+
import type { StoryBuilderContext } from './story-builder-context';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* State-master logic: creating Figma component sets that represent the
|
|
19
|
+
* default / hover / disabled / error / checked variants of state-typed
|
|
20
|
+
* components (Input, Checkbox, Switch, …).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export { isTruthyStateProp };
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
export function shouldCreateStateMaster(def: any): boolean {
|
|
27
|
+
if (!def || def.type !== 'state') return false;
|
|
28
|
+
if (def.symbolCandidate === false) return false;
|
|
29
|
+
const states = def && def.states ? def.states : {};
|
|
30
|
+
const stateNames = Object.keys(states);
|
|
31
|
+
return stateNames.length > 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
export function buildStateComponentSetHash(def: any, theme: string): string {
|
|
36
|
+
const themeContext = getThemeContext(theme);
|
|
37
|
+
return hashString(
|
|
38
|
+
hashDef(def)
|
|
39
|
+
+ ':'
|
|
40
|
+
+ theme
|
|
41
|
+
+ ':state-component-set-v3:'
|
|
42
|
+
+ RENDER_ENGINE_VERSION
|
|
43
|
+
+ ':'
|
|
44
|
+
+ JSON.stringify({
|
|
45
|
+
colorGroup: themeContext.colorGroup,
|
|
46
|
+
radiusGroup: themeContext.radiusGroup,
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
export function getStateVariantNames(def: any): string[] {
|
|
53
|
+
const out: string[] = ['default'];
|
|
54
|
+
const states = def && def.states ? def.states : {};
|
|
55
|
+
const names = Object.keys(states);
|
|
56
|
+
for (let i = 0; i < names.length; i++) {
|
|
57
|
+
const name = String(names[i] || '').trim();
|
|
58
|
+
if (!name || name === 'default') continue;
|
|
59
|
+
if (out.indexOf(name) !== -1) continue;
|
|
60
|
+
out.push(name);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
export function hasCheckedStateVariant(def: any): boolean {
|
|
67
|
+
const names = getStateVariantNames(def);
|
|
68
|
+
for (let i = 0; i < names.length; i++) {
|
|
69
|
+
if (names[i].toLowerCase() === 'checked') return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
export function getVisualStateVariantNames(def: any): string[] {
|
|
76
|
+
const all = getStateVariantNames(def);
|
|
77
|
+
const out: string[] = [];
|
|
78
|
+
for (let i = 0; i < all.length; i++) {
|
|
79
|
+
const name = all[i];
|
|
80
|
+
if (name.toLowerCase() === 'checked') continue;
|
|
81
|
+
out.push(name);
|
|
82
|
+
}
|
|
83
|
+
if (out.length === 0) out.push('default');
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
|
+
export function resolveStateVariantName(def: any, requested: unknown, includeChecked: boolean = true): string {
|
|
89
|
+
const requestedName = String(requested == null ? '' : requested).trim().toLowerCase();
|
|
90
|
+
const available = includeChecked ? getStateVariantNames(def) : getVisualStateVariantNames(def);
|
|
91
|
+
for (let i = 0; i < available.length; i++) {
|
|
92
|
+
if (available[i].toLowerCase() === requestedName) return available[i];
|
|
93
|
+
}
|
|
94
|
+
return 'default';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
export function isCheckedInstance(instance: any): boolean {
|
|
99
|
+
const props = instance && instance.props ? instance.props : {};
|
|
100
|
+
return isTruthyStateProp(props.checked)
|
|
101
|
+
|| isTruthyStateProp(props.defaultChecked)
|
|
102
|
+
|| isTruthyStateProp(props['aria-checked']);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
export function getStateVariantForInstance(def: any, instance: any): string {
|
|
107
|
+
const props = instance && instance.props ? instance.props : {};
|
|
108
|
+
const explicitState = props['data-state'] != null ? props['data-state'] : props.state;
|
|
109
|
+
if (explicitState != null && String(explicitState).trim() !== '') {
|
|
110
|
+
return resolveStateVariantName(def, explicitState, false);
|
|
111
|
+
}
|
|
112
|
+
if (isTruthyStateProp(props.disabled) || isTruthyStateProp(props['aria-disabled'])) {
|
|
113
|
+
return resolveStateVariantName(def, 'disabled', false);
|
|
114
|
+
}
|
|
115
|
+
if (isTruthyStateProp(props['aria-invalid'])) {
|
|
116
|
+
return resolveStateVariantName(def, 'error', false);
|
|
117
|
+
}
|
|
118
|
+
return 'default';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
+
export function buildStateVariantProps(def: any, stateName: string, checked: boolean): Record<string, any> {
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
124
|
+
const props: Record<string, any> = {};
|
|
125
|
+
const normalized = resolveStateVariantName(def, stateName, false);
|
|
126
|
+
const stateInfo = def && def.states && def.states[normalized] ? def.states[normalized] : null;
|
|
127
|
+
const stateClasses = stateInfo && Array.isArray(stateInfo.classes) ? stateInfo.classes : [];
|
|
128
|
+
const checkedState = def && def.states && def.states.checked ? def.states.checked : null;
|
|
129
|
+
const checkedClasses = checkedState && Array.isArray(checkedState.classes) ? checkedState.classes : [];
|
|
130
|
+
const mergedClasses = checked ? stateClasses.concat(checkedClasses) : stateClasses;
|
|
131
|
+
if (mergedClasses.length > 0) {
|
|
132
|
+
props.className = mergedClasses.join(' ');
|
|
133
|
+
}
|
|
134
|
+
if (checked) {
|
|
135
|
+
props.checked = 'true';
|
|
136
|
+
}
|
|
137
|
+
if (normalized === 'disabled') {
|
|
138
|
+
props.disabled = 'true';
|
|
139
|
+
} else if (normalized === 'error') {
|
|
140
|
+
props['aria-invalid'] = 'true';
|
|
141
|
+
} else if (normalized === 'open') {
|
|
142
|
+
props['data-state'] = 'open';
|
|
143
|
+
}
|
|
144
|
+
return props;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function ensureStateComponentSet(
|
|
148
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
149
|
+
def: any,
|
|
150
|
+
theme: string,
|
|
151
|
+
ctx: StoryBuilderContext
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
+
): any | null {
|
|
154
|
+
if (!ENABLE_SYMBOL_MASTERS) return null;
|
|
155
|
+
if (!shouldCreateStateMaster(def)) return null;
|
|
156
|
+
|
|
157
|
+
const stateNames = getVisualStateVariantNames(def);
|
|
158
|
+
if (stateNames.length === 0) return null;
|
|
159
|
+
const includeChecked = hasCheckedStateVariant(def);
|
|
160
|
+
|
|
161
|
+
const themeLibrary = ensureThemeComponentLibrary(theme);
|
|
162
|
+
if (!themeLibrary) return null;
|
|
163
|
+
|
|
164
|
+
const setName = def.name + ' [' + theme + ']';
|
|
165
|
+
const masterHash = buildStateComponentSetHash(def, theme);
|
|
166
|
+
const existing = findChildByName(themeLibrary, setName);
|
|
167
|
+
if (existing && existing.type === 'COMPONENT_SET' && getFrameHash(existing) === masterHash) {
|
|
168
|
+
return existing;
|
|
169
|
+
}
|
|
170
|
+
if (existing) {
|
|
171
|
+
existing.remove();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
175
|
+
const components: any[] = [];
|
|
176
|
+
const checkedValues = includeChecked ? [false, true] : [false];
|
|
177
|
+
for (let i = 0; i < stateNames.length; i++) {
|
|
178
|
+
const stateName = stateNames[i];
|
|
179
|
+
for (let ci = 0; ci < checkedValues.length; ci++) {
|
|
180
|
+
const checked = checkedValues[ci];
|
|
181
|
+
const stateVariantProps = buildStateVariantProps(def, stateName, checked);
|
|
182
|
+
const sourceNode = createStateStoryFrame(
|
|
183
|
+
def,
|
|
184
|
+
{ props: stateVariantProps },
|
|
185
|
+
theme,
|
|
186
|
+
ctx
|
|
187
|
+
);
|
|
188
|
+
themeLibrary.appendChild(sourceNode);
|
|
189
|
+
|
|
190
|
+
// `createStateStoryFrame` → `applyTailwindStylesToFrame` calls
|
|
191
|
+
// `markRingNode(sourceNode, ringInfo)` for ring classes. We must
|
|
192
|
+
// capture that ring info BEFORE `createComponentFromNode` — it
|
|
193
|
+
// creates a NEW node identity, so the `RING_NODES` WeakMap entry
|
|
194
|
+
// keyed on `sourceNode` is unreachable from the resulting component.
|
|
195
|
+
// Without this transfer the focus / error variants of state masters
|
|
196
|
+
// (Input, Textarea) render with no ring overlay — only the host's
|
|
197
|
+
// 1px border shows. Confirmed 2026-05-17 from a side-by-side
|
|
198
|
+
// Storybook (correct: 1px border + 3px halo) vs Figma (broken: 1px
|
|
199
|
+
// border alone) comparison.
|
|
200
|
+
const ringInfo = getRingNode(sourceNode);
|
|
201
|
+
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
203
|
+
let component: any = null;
|
|
204
|
+
try {
|
|
205
|
+
component = figma.createComponentFromNode(sourceNode);
|
|
206
|
+
} catch (_error) {
|
|
207
|
+
try {
|
|
208
|
+
sourceNode.remove();
|
|
209
|
+
} catch (_cleanupError) {
|
|
210
|
+
// ignore cleanup errors
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (!component) continue;
|
|
215
|
+
// Transfer the captured ring mark onto the new component identity.
|
|
216
|
+
if (ringInfo) {
|
|
217
|
+
markRingNode(component, ringInfo);
|
|
218
|
+
}
|
|
219
|
+
enforceFixedBoxSizingAfterLayout(
|
|
220
|
+
component,
|
|
221
|
+
splitClassName(stateVariantProps && stateVariantProps.className ? String(stateVariantProps.className) : '')
|
|
222
|
+
);
|
|
223
|
+
// Now that the component's dimensions are finalised by
|
|
224
|
+
// `enforceFixedBoxSizingAfterLayout`, build the ring overlay against
|
|
225
|
+
// those final dimensions. Idempotent: if the comp already has an
|
|
226
|
+
// `__inkbridge-ring__` child from an earlier pass, it's removed and
|
|
227
|
+
// recreated at the correct size.
|
|
228
|
+
applyRingIfPossible(component as unknown as FrameNode, themeLibrary);
|
|
229
|
+
|
|
230
|
+
const nameParts = [
|
|
231
|
+
toFigmaVariantPropertyName('state') + '=' + toFigmaVariantPropertyValue(stateName),
|
|
232
|
+
];
|
|
233
|
+
if (includeChecked) {
|
|
234
|
+
nameParts.push(toFigmaVariantPropertyName('checked') + '=' + toFigmaVariantPropertyValue(String(checked)));
|
|
235
|
+
}
|
|
236
|
+
component.name = nameParts.join(', ');
|
|
237
|
+
component.visible = false;
|
|
238
|
+
if (component.parent !== themeLibrary) {
|
|
239
|
+
themeLibrary.appendChild(component);
|
|
240
|
+
}
|
|
241
|
+
components.push(component);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (components.length === 0) return null;
|
|
246
|
+
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
248
|
+
let componentSet: any = null;
|
|
249
|
+
try {
|
|
250
|
+
componentSet = figma.combineAsVariants(components, themeLibrary);
|
|
251
|
+
} catch (_error) {
|
|
252
|
+
for (let i = 0; i < components.length; i++) {
|
|
253
|
+
try {
|
|
254
|
+
components[i].remove();
|
|
255
|
+
} catch (_cleanupError) {
|
|
256
|
+
// ignore cleanup errors
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
componentSet.name = setName;
|
|
263
|
+
componentSet.visible = false;
|
|
264
|
+
setFrameHash(componentSet, masterHash);
|
|
265
|
+
tagGeneratedNode(componentSet, 'state-component-set:' + def.name + ':' + theme);
|
|
266
|
+
return componentSet;
|
|
267
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predicate for "is this prop value truthy by HTML/JSX conventions?"
|
|
3
|
+
*
|
|
4
|
+
* Originally lived as two near-duplicates: `isTruthyStateProp` (in
|
|
5
|
+
* state-master) and `isTruthyBoolProp` (in ui-builder). They differed only
|
|
6
|
+
* in whether they trimmed whitespace; the trimming version is correct
|
|
7
|
+
* because attributes can carry leading/trailing whitespace through JSX.
|
|
8
|
+
* Both call sites now use this one function.
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
export function isTruthyStateProp(value: any): boolean {
|
|
12
|
+
if (value == null) return false;
|
|
13
|
+
const normalized = String(value).trim().toLowerCase();
|
|
14
|
+
return normalized !== '' && normalized !== 'false' && normalized !== '0' && normalized !== 'off';
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RGB } from '../tokens';
|
|
2
|
+
import type { JsxNode } from '../tailwind';
|
|
3
|
+
import type { ComponentDef } from '../components/scanner-types';
|
|
4
|
+
|
|
5
|
+
export type StoryRenderContext = {
|
|
6
|
+
maxWidth?: number;
|
|
7
|
+
/** Story-level viewport width for responsive breakpoint resolution. */
|
|
8
|
+
viewportWidth?: number;
|
|
9
|
+
textAlign?: 'LEFT' | 'CENTER' | 'RIGHT';
|
|
10
|
+
parentLayout?: 'HORIZONTAL' | 'VERTICAL';
|
|
11
|
+
textColor?: string | RGB | null;
|
|
12
|
+
textColorToken?: string;
|
|
13
|
+
parentCompoundDef?: unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type StoryBuilderContext = {
|
|
17
|
+
getComponentDefByName: (name: string) => ComponentDef | null;
|
|
18
|
+
normalizeComponentDef: (raw: ComponentDef) => ComponentDef;
|
|
19
|
+
applyClipBehavior: (frame: FrameNode, classes: string[] | undefined) => void;
|
|
20
|
+
applyLayoutClasses: (
|
|
21
|
+
frame: FrameNode,
|
|
22
|
+
classes: string[] | undefined,
|
|
23
|
+
colorGroup: Record<string, string>,
|
|
24
|
+
radiusGroup: Record<string, string> | null,
|
|
25
|
+
theme: string
|
|
26
|
+
) => void;
|
|
27
|
+
hasExplicitHeight: (classes: string[] | undefined) => boolean;
|
|
28
|
+
renderJsxTree: (
|
|
29
|
+
node: JsxNode,
|
|
30
|
+
colorGroup: Record<string, string>,
|
|
31
|
+
radiusGroup: Record<string, string> | null,
|
|
32
|
+
theme: string,
|
|
33
|
+
depth?: number,
|
|
34
|
+
context?: StoryRenderContext
|
|
35
|
+
) => SceneNode | null;
|
|
36
|
+
applyAbsoluteIfAllowed: (child: SceneNode, parent: FrameNode, allowAbsolute: boolean) => void;
|
|
37
|
+
applyGridColumnsWithReflow: (frame: FrameNode, widthOverride?: number, colsOverride?: number) => void;
|
|
38
|
+
hasWidthHintInClasses: (classes: string[]) => boolean;
|
|
39
|
+
propsContainWidthHint: (props: Record<string, unknown> | undefined) => boolean;
|
|
40
|
+
};
|