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,172 @@
|
|
|
1
|
+
import { splitClassName } from '../tailwind';
|
|
2
|
+
import { MASTER_ICON_NAME_KEY } from '../components/component-instance';
|
|
3
|
+
import { getThemeContext } from './theme-context';
|
|
4
|
+
import { buildCvaClassesWithSelection } from './cva-utils';
|
|
5
|
+
import {
|
|
6
|
+
normalizeComponentName,
|
|
7
|
+
findBestStoryElementForClasses,
|
|
8
|
+
getStoriesWithJsxTree,
|
|
9
|
+
countClassOverlap,
|
|
10
|
+
findMatchingInstance,
|
|
11
|
+
} from './story-tree-search';
|
|
12
|
+
import type { StoryBuilderContext } from './story-builder-context';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Builders for the source nodes that wrap each variant of a generated CVA
|
|
16
|
+
* component set. Given a story's JSX tree and a target variant selection,
|
|
17
|
+
* `buildCvaSymbolSourceNode` finds the best-matching element, overrides
|
|
18
|
+
* its className with the resolved variant classes (so the master renders
|
|
19
|
+
* the exact variant), and renders it through `ctx.renderJsxTree`.
|
|
20
|
+
*
|
|
21
|
+
* `buildNonCvaSymbolSourceNode` (state / simple / compound masters) still
|
|
22
|
+
* lives in story-builder.ts because it depends on createStateStoryFrame /
|
|
23
|
+
* createSimpleStoryFrame — those move out together in phase 6b.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
export function renderStoryElementAsSymbolSource(node: any, theme: string, ctx: StoryBuilderContext): any | null {
|
|
28
|
+
if (!node || node.type !== 'element') return null;
|
|
29
|
+
const themeContext = getThemeContext(theme);
|
|
30
|
+
const colorGroup = themeContext.colorGroup;
|
|
31
|
+
const radiusGroup = themeContext.radiusGroup;
|
|
32
|
+
return ctx.renderJsxTree(node, colorGroup, radiusGroup, theme, 0, {
|
|
33
|
+
parentLayout: 'VERTICAL',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
function getCvaVariantValueForStory(def: any, story: any, variantKey: string): string {
|
|
39
|
+
const instance = findMatchingInstance(def, story);
|
|
40
|
+
const props = instance && instance.props ? instance.props : {};
|
|
41
|
+
if (props[variantKey] != null && String(props[variantKey]) !== '') {
|
|
42
|
+
return String(props[variantKey]);
|
|
43
|
+
}
|
|
44
|
+
if (def && def.defaultVariants && def.defaultVariants[variantKey] != null) {
|
|
45
|
+
return String(def.defaultVariants[variantKey]);
|
|
46
|
+
}
|
|
47
|
+
const variants = def && def.variants ? def.variants : {};
|
|
48
|
+
if (variants[variantKey] && variants[variantKey][0] != null) {
|
|
49
|
+
return String(variants[variantKey][0]);
|
|
50
|
+
}
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
function storyMatchesCvaSelection(def: any, story: any, selection: Record<string, string>): boolean {
|
|
56
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
57
|
+
if (variantKeys.length === 0) return false;
|
|
58
|
+
for (let i = 0; i < variantKeys.length; i++) {
|
|
59
|
+
const key = variantKeys[i];
|
|
60
|
+
const storyValue = getCvaVariantValueForStory(def, story, key);
|
|
61
|
+
const selectionValue = selection[key] != null ? String(selection[key]) : '';
|
|
62
|
+
if (selectionValue && storyValue !== selectionValue) return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
function overrideClassNameForCvaTarget(node: any, def: any, targetClasses: string[]): any {
|
|
69
|
+
if (!node || node.type !== 'element') return node;
|
|
70
|
+
const existing = splitClassName(node.props && node.props.className);
|
|
71
|
+
const existingSet = new Set(existing);
|
|
72
|
+
let identical = existing.length === targetClasses.length;
|
|
73
|
+
if (identical) {
|
|
74
|
+
for (let i = 0; i < targetClasses.length; i++) {
|
|
75
|
+
if (!existingSet.has(targetClasses[i])) { identical = false; break; }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (identical) return node;
|
|
79
|
+
const nextProps = Object.assign({}, node.props || {});
|
|
80
|
+
nextProps.className = targetClasses.join(' ');
|
|
81
|
+
// Also strip CVA variant prop values from the scanned node — the target
|
|
82
|
+
// classes already encode the variant, so leaving stale variant props can
|
|
83
|
+
// confuse downstream class inference.
|
|
84
|
+
const variantKeys = Object.keys((def && def.variants) || {});
|
|
85
|
+
for (let i = 0; i < variantKeys.length; i++) delete nextProps[variantKeys[i]];
|
|
86
|
+
return Object.assign({}, node, { props: nextProps });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function buildCvaSymbolSourceNode(
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
91
|
+
def: any,
|
|
92
|
+
selection: Record<string, string>,
|
|
93
|
+
theme: string,
|
|
94
|
+
ctx: StoryBuilderContext
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
+
): any | null {
|
|
97
|
+
const stories = getStoriesWithJsxTree(def);
|
|
98
|
+
if (stories.length === 0) return null;
|
|
99
|
+
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
+
const orderedStories = stories.slice().sort(function(a: any, b: any): number {
|
|
102
|
+
const aScore = storyMatchesCvaSelection(def, a, selection) ? 0 : 1;
|
|
103
|
+
const bScore = storyMatchesCvaSelection(def, b, selection) ? 0 : 1;
|
|
104
|
+
return aScore - bScore;
|
|
105
|
+
});
|
|
106
|
+
const targetClasses = buildCvaClassesWithSelection(def, selection, '');
|
|
107
|
+
const targetSet = new Set(targetClasses);
|
|
108
|
+
const selfName = normalizeComponentName(def && def.name ? def.name : '');
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < orderedStories.length; i++) {
|
|
111
|
+
const story = orderedStories[i];
|
|
112
|
+
const tree = story && story.jsxTree;
|
|
113
|
+
if (!tree || tree.type !== 'element') continue;
|
|
114
|
+
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
+
let nodeToRender: any | null = null;
|
|
117
|
+
const candidate = findBestStoryElementForClasses(tree, targetClasses);
|
|
118
|
+
const candidateIsSelfComponent = !!(
|
|
119
|
+
candidate
|
|
120
|
+
&& candidate.type === 'element'
|
|
121
|
+
&& candidate.isComponent
|
|
122
|
+
&& normalizeComponentName(candidate.tagName) === selfName
|
|
123
|
+
);
|
|
124
|
+
if (candidate && candidate.type === 'element' && !candidateIsSelfComponent) {
|
|
125
|
+
nodeToRender = candidate;
|
|
126
|
+
} else {
|
|
127
|
+
const rootClasses = splitClassName(tree.props && tree.props.className);
|
|
128
|
+
const overlap = countClassOverlap(rootClasses, targetSet);
|
|
129
|
+
const minOverlap = Math.max(1, Math.min(4, Math.floor(targetClasses.length * 0.35)));
|
|
130
|
+
if (overlap >= minOverlap) {
|
|
131
|
+
nodeToRender = tree;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!nodeToRender) continue;
|
|
135
|
+
|
|
136
|
+
// If the picked element isn't an exact match for this variant+size
|
|
137
|
+
// selection (e.g. Outline button from the Variants story, which is at
|
|
138
|
+
// default size), override its className with the target variant's
|
|
139
|
+
// resolved classes so the master's component for this variant renders
|
|
140
|
+
// at the right size/style. Otherwise the structure comes from a
|
|
141
|
+
// mismatched source and the variant says "Sm" but renders at h-9.
|
|
142
|
+
const nodeForRender = overrideClassNameForCvaTarget(nodeToRender, def, targetClasses);
|
|
143
|
+
const sourceNode = renderStoryElementAsSymbolSource(nodeForRender, theme, ctx);
|
|
144
|
+
if (sourceNode) {
|
|
145
|
+
// Stamp the icon name on the rendered source node so the master
|
|
146
|
+
// variant Component (built in cva-master.ts) can propagate it. The
|
|
147
|
+
// CVA-instance creator reads this to decide whether per-instance
|
|
148
|
+
// element children (e.g. icons) match the master and can reuse it,
|
|
149
|
+
// or differ and need frame-rendering fall-back.
|
|
150
|
+
const iconName = findFirstElementChildTagName(nodeForRender);
|
|
151
|
+
if (iconName) {
|
|
152
|
+
try { sourceNode.setPluginData(MASTER_ICON_NAME_KEY, iconName); } catch (_e) { /* ignore */ }
|
|
153
|
+
}
|
|
154
|
+
return sourceNode;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
+
function findFirstElementChildTagName(node: any): string | null {
|
|
163
|
+
if (!node || typeof node !== 'object') return null;
|
|
164
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
165
|
+
for (let i = 0; i < children.length; i++) {
|
|
166
|
+
const c = children[i];
|
|
167
|
+
if (c && c.type === 'element' && typeof c.tagName === 'string' && c.tagName.length > 0) {
|
|
168
|
+
return c.tagName;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getBaseClass } from '../tailwind';
|
|
2
|
+
import type { NodeIR } from '../tailwind';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Helpers for rendering semantic HTML table primitives (table / thead /
|
|
6
|
+
* tbody / tfoot / tr / td / th) into Figma's auto-layout model. Borders
|
|
7
|
+
* are cleared selectively to avoid the double-border seam that two
|
|
8
|
+
* adjacent cells would otherwise produce.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* True when `classes` contains a width override that should bypass the
|
|
13
|
+
* default "fill the row" behaviour of a table cell — e.g. an explicit
|
|
14
|
+
* `w-32`, `flex-1`, or `min-w-*`.
|
|
15
|
+
*/
|
|
16
|
+
export function hasTableCellSizeOverride(classes: string[]): boolean {
|
|
17
|
+
for (const cls of classes) {
|
|
18
|
+
const base = getBaseClass(cls);
|
|
19
|
+
if (!base) continue;
|
|
20
|
+
if (base === 'flex-1' || base === 'grow' || base === 'grow-0') return true;
|
|
21
|
+
if (base.startsWith('w-') || base.startsWith('min-w-') || base.startsWith('max-w-')) return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getNumericColSpan(value: unknown): number {
|
|
27
|
+
if (typeof value === 'number' && Number.isFinite(value)) return Math.max(1, Math.floor(value));
|
|
28
|
+
if (typeof value === 'string') {
|
|
29
|
+
const parsed = parseInt(value, 10);
|
|
30
|
+
if (Number.isFinite(parsed)) return Math.max(1, parsed);
|
|
31
|
+
}
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function isSemanticTableContainer(node: NodeIR): boolean {
|
|
36
|
+
if (node.kind !== 'element' && node.kind !== 'component') return false;
|
|
37
|
+
return node.tagLower === 'table' || node.tagLower === 'thead' || node.tagLower === 'tbody' || node.tagLower === 'tfoot';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function clearTopBorder(node: SceneNode): void {
|
|
41
|
+
if (!('strokeTopWeight' in node)) return;
|
|
42
|
+
try {
|
|
43
|
+
node.strokeTopWeight = 0;
|
|
44
|
+
} catch (_err) {
|
|
45
|
+
// ignore if unsupported on this specific subtype
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function clearBottomBorder(node: SceneNode): void {
|
|
50
|
+
if (!('strokeBottomWeight' in node)) return;
|
|
51
|
+
try {
|
|
52
|
+
node.strokeBottomWeight = 0;
|
|
53
|
+
} catch (_err) {
|
|
54
|
+
// ignore if unsupported on this specific subtype
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure tag-name predicates for the Radix / Base UI primitive components
|
|
3
|
+
* the plugin recognises (Accordion, Tabs, Select, RadioGroup, Portal-rooted
|
|
4
|
+
* popover panels). Used by ui-builder and any future renderer that needs
|
|
5
|
+
* to branch on component family without inlining the string lists.
|
|
6
|
+
*
|
|
7
|
+
* No Figma dependencies, no shared state — pure string matching.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function isAccordionRootTag(tagName: string): boolean {
|
|
11
|
+
return tagName === 'Accordion' || tagName === 'AccordionPrimitive.Root';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isAccordionItemTag(tagName: string): boolean {
|
|
15
|
+
return tagName === 'AccordionItem' || tagName === 'AccordionPrimitive.Item';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isAccordionContentTag(tagName: string): boolean {
|
|
19
|
+
return tagName === 'AccordionContent' || tagName === 'AccordionPrimitive.Content';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isAccordionHeaderTag(tagName: string): boolean {
|
|
23
|
+
return tagName === 'AccordionHeader' || tagName === 'AccordionPrimitive.Header';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isRadioGroupRootTag(tagName: string): boolean {
|
|
27
|
+
return tagName === 'RadioGroup' || tagName === 'RadioGroupPrimitive' || tagName === 'RadioGroupPrimitive.Root';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isRadioGroupItemTag(tagName: string): boolean {
|
|
31
|
+
return tagName === 'RadioGroupItem' || tagName === 'RadioPrimitive.Root' || tagName === 'RadioGroupPrimitive.Item';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isRadioGroupIndicatorTag(tagName: string): boolean {
|
|
35
|
+
return tagName === 'RadioPrimitive.Indicator' || tagName === 'RadioGroupPrimitive.Indicator';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isTabsRootTag(tagName: string): boolean {
|
|
39
|
+
return tagName === 'Tabs' || tagName === 'TabsPrimitive.Root';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isTabsTriggerTag(tagName: string): boolean {
|
|
43
|
+
return tagName === 'TabsTrigger' || tagName === 'TabsPrimitive.Trigger' || tagName === 'TabsPrimitive.Tab';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isTabsListTag(tagName: string): boolean {
|
|
47
|
+
return tagName === 'TabsList' || tagName === 'TabsPrimitive.List';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isTabsContentTag(tagName: string): boolean {
|
|
51
|
+
return tagName === 'TabsContent' || tagName === 'TabsPrimitive.Content' || tagName === 'TabsPrimitive.Panel';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isSelectRootTag(tagName: string): boolean {
|
|
55
|
+
return tagName === 'Select' || tagName === 'SelectPrimitive.Root';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isSelectContentTag(tagName: string): boolean {
|
|
59
|
+
return tagName === 'SelectContent' || tagName === 'SelectPrimitive.Content';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function isSelectTriggerTag(tagName: string): boolean {
|
|
63
|
+
return tagName === 'SelectTrigger' || tagName === 'SelectPrimitive.Trigger';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isSelectValueTag(tagName: string): boolean {
|
|
67
|
+
return tagName === 'SelectValue' || tagName === 'SelectPrimitive.Value';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isSelectItemTag(tagName: string): boolean {
|
|
71
|
+
return tagName === 'SelectItem' || tagName === 'SelectPrimitive.Item';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function isSelectItemIndicatorTag(tagName: string): boolean {
|
|
75
|
+
return tagName === 'SelectPrimitive.ItemIndicator' || tagName === 'SelectItemIndicator';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Radix & Base UI portal components (Popover/Tooltip/DropdownMenu/HoverCard/Menu)
|
|
80
|
+
* expose an `Arrow` subcomponent that visually points at the trigger. In
|
|
81
|
+
* Figma the panel isn't anchored to the trigger, so the arrow is noise.
|
|
82
|
+
*/
|
|
83
|
+
export function isPortalArrowTag(tagName: string): boolean {
|
|
84
|
+
if (!tagName) return false;
|
|
85
|
+
const lower = tagName.toLowerCase();
|
|
86
|
+
return lower.endsWith('.arrow')
|
|
87
|
+
|| lower === 'popoverarrow'
|
|
88
|
+
|| lower === 'tooltiparrow'
|
|
89
|
+
|| lower === 'dropdownmenuarrow'
|
|
90
|
+
|| lower === 'hovercardarrow'
|
|
91
|
+
|| lower === 'menuarrow';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function isSelectScrollButtonTag(tagName: string): boolean {
|
|
95
|
+
return tagName === 'SelectScrollUpButton'
|
|
96
|
+
|| tagName === 'SelectPrimitive.ScrollUpButton'
|
|
97
|
+
|| tagName === 'SelectScrollDownButton'
|
|
98
|
+
|| tagName === 'SelectPrimitive.ScrollDownButton';
|
|
99
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { TOKENS, getThemeColors, getThemeRadius } from '../tokens';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-theme rendering context, cached so the design-system page builder
|
|
5
|
+
* doesn't re-resolve TOKENS on every story it renders.
|
|
6
|
+
*/
|
|
7
|
+
export type ThemeContext = {
|
|
8
|
+
theme: string;
|
|
9
|
+
colorGroup: Record<string, string>;
|
|
10
|
+
radiusGroup: Record<string, string> | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const THEME_CONTEXT_CACHE: Record<string, ThemeContext> = {};
|
|
14
|
+
|
|
15
|
+
export function getThemeContext(theme: string): ThemeContext {
|
|
16
|
+
if (THEME_CONTEXT_CACHE[theme]) return THEME_CONTEXT_CACHE[theme];
|
|
17
|
+
const colorGroup = getThemeColors(TOKENS, theme);
|
|
18
|
+
const radiusGroup = getThemeRadius(TOKENS, theme);
|
|
19
|
+
const ctx: ThemeContext = {
|
|
20
|
+
theme: theme,
|
|
21
|
+
colorGroup: colorGroup,
|
|
22
|
+
radiusGroup: radiusGroup,
|
|
23
|
+
};
|
|
24
|
+
THEME_CONTEXT_CACHE[theme] = ctx;
|
|
25
|
+
return ctx;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Spacing constants for the Design System page layout. Used by both
|
|
30
|
+
* story-builder and preview-builder so column widths, axis gaps, and
|
|
31
|
+
* matrix paddings stay consistent across boards.
|
|
32
|
+
*/
|
|
33
|
+
export const BOARD_LAYOUT = {
|
|
34
|
+
boardGap: 96,
|
|
35
|
+
sectionPaddingX: 64,
|
|
36
|
+
sectionPaddingY: 64,
|
|
37
|
+
columnsGap: 320,
|
|
38
|
+
columnGap: 144,
|
|
39
|
+
columnPaddingX: 0,
|
|
40
|
+
columnPaddingY: 160,
|
|
41
|
+
sectionGroupGap: 176,
|
|
42
|
+
sectionTitleGap: 48,
|
|
43
|
+
componentBlockGap: 128,
|
|
44
|
+
componentTitleGap: 48,
|
|
45
|
+
storyGap: 32,
|
|
46
|
+
showcaseGap: 128,
|
|
47
|
+
stateMatrixGap: 40,
|
|
48
|
+
stateMatrixAxisGap: 64,
|
|
49
|
+
responsiveBlockGap: 32,
|
|
50
|
+
responsiveColumnGap: 64,
|
|
51
|
+
responsiveLabelGap: 20,
|
|
52
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { parseUtilityClass } from '../tailwind';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tailwind text-* utility → Figma font-size (px). Shared lookup; importers
|
|
5
|
+
* read it directly when matching raw classes (the parsed-utility variants
|
|
6
|
+
* `getPreviewTypography` handles via `parseUtilityClass` first).
|
|
7
|
+
*/
|
|
8
|
+
export const TEXT_SIZE_PX: Record<string, number> = {
|
|
9
|
+
'text-xs': 12,
|
|
10
|
+
'text-sm': 14,
|
|
11
|
+
'text-base': 16,
|
|
12
|
+
'text-lg': 18,
|
|
13
|
+
'text-xl': 20,
|
|
14
|
+
'text-2xl': 24,
|
|
15
|
+
'text-3xl': 30,
|
|
16
|
+
'text-4xl': 36,
|
|
17
|
+
'text-5xl': 48,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Numeric ordering of `font-*` weight utilities. Used to decide
|
|
22
|
+
* "≥ semibold = bold" without enumerating the seven matching utilities.
|
|
23
|
+
*/
|
|
24
|
+
export const FONT_WEIGHT_RANK: Record<string, number> = {
|
|
25
|
+
'font-thin': 1,
|
|
26
|
+
'font-extralight': 2,
|
|
27
|
+
'font-light': 3,
|
|
28
|
+
'font-normal': 4,
|
|
29
|
+
'font-medium': 5,
|
|
30
|
+
'font-semibold': 6,
|
|
31
|
+
'font-bold': 7,
|
|
32
|
+
'font-extrabold': 8,
|
|
33
|
+
'font-black': 9,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Tailwind weight class → Figma font-style string. Used by the renderer
|
|
38
|
+
* when assigning a font's style face (`Inter:Medium`, `Open Sans:Bold`, …).
|
|
39
|
+
*/
|
|
40
|
+
export const FONT_WEIGHT_TO_STYLE: Record<string, string> = {
|
|
41
|
+
'font-thin': 'Thin',
|
|
42
|
+
'font-extralight': 'ExtraLight',
|
|
43
|
+
'font-light': 'Light',
|
|
44
|
+
'font-normal': 'Regular',
|
|
45
|
+
'font-medium': 'Medium',
|
|
46
|
+
'font-semibold': 'SemiBold',
|
|
47
|
+
'font-bold': 'Bold',
|
|
48
|
+
'font-extrabold': 'ExtraBold',
|
|
49
|
+
'font-black': 'Black',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolves Tailwind text-* and font-* classes to Figma typography defaults
|
|
54
|
+
* (font size in px, bold flag). Shared by preview-builder, cva-master and
|
|
55
|
+
* the story-instance render paths so they all surface identical typography
|
|
56
|
+
* for the same class set.
|
|
57
|
+
*/
|
|
58
|
+
export function getPreviewTypography(classes: string[]): { fontSize: number; bold: boolean } {
|
|
59
|
+
let fontSize = 14;
|
|
60
|
+
let bold = false;
|
|
61
|
+
for (let i = 0; i < classes.length; i++) {
|
|
62
|
+
const atom = parseUtilityClass(classes[i]);
|
|
63
|
+
const utility = atom.utility || '';
|
|
64
|
+
if (TEXT_SIZE_PX[utility] != null) fontSize = TEXT_SIZE_PX[utility];
|
|
65
|
+
if (utility === 'font-bold' || utility === 'font-semibold') bold = true;
|
|
66
|
+
if (utility === 'font-normal' || utility === 'font-light' || utility === 'font-extralight') bold = false;
|
|
67
|
+
}
|
|
68
|
+
return { fontSize: fontSize, bold: bold };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getFontStyleFromClasses(classes: string[], fallback?: string): string | undefined {
|
|
72
|
+
let found: string | undefined;
|
|
73
|
+
for (const cls of classes) {
|
|
74
|
+
const style = FONT_WEIGHT_TO_STYLE[cls];
|
|
75
|
+
if (style) { found = style; }
|
|
76
|
+
}
|
|
77
|
+
if (found == null && fallback == null) return undefined;
|
|
78
|
+
return found != null ? found : fallback;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getResolvedTextSizeFromClasses(classes: string[], fallback?: number): number | undefined {
|
|
82
|
+
let resolved = fallback;
|
|
83
|
+
for (const cls of classes) {
|
|
84
|
+
if (TEXT_SIZE_PX[cls] != null) resolved = TEXT_SIZE_PX[cls];
|
|
85
|
+
}
|
|
86
|
+
return resolved;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getResolvedBoldFromClasses(classes: string[], fallback?: boolean): boolean | undefined {
|
|
90
|
+
let rank = fallback ? 7 : 4;
|
|
91
|
+
let seenWeight = false;
|
|
92
|
+
for (const cls of classes) {
|
|
93
|
+
if (FONT_WEIGHT_RANK[cls] != null) {
|
|
94
|
+
rank = FONT_WEIGHT_RANK[cls];
|
|
95
|
+
seenWeight = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!seenWeight && fallback == null) return undefined;
|
|
99
|
+
return rank >= FONT_WEIGHT_RANK['font-semibold'];
|
|
100
|
+
}
|