inkbridge 0.1.0-beta.4 → 0.1.0-beta.6
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 +3 -1
- package/code.js +446 -102
- package/package.json +2 -7
- package/scanner/component-scanner.ts +95 -0
- package/scanner/css-token-reader.ts +20 -26
- package/src/token-source.ts +1 -1
- package/ui.html +29 -29
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/inkn9ne/inkbridge/main/public/inkbridge-logo.png" alt="Inkbridge" width="80" />
|
|
2
|
+
|
|
1
3
|
# Inkbridge
|
|
2
4
|
|
|
3
5
|
Generates native Figma frames from your Tailwind React components and syncs design tokens back to your codebase via GitHub PRs.
|
|
@@ -63,7 +65,7 @@ The plugin auto-discovers your server on ports `4000`, `3000`, and `5173`.
|
|
|
63
65
|
1. Start dev server: `pnpm figma:dev`
|
|
64
66
|
2. Open any Figma file
|
|
65
67
|
3. **Plugins → Development → Inkbridge → Generate Design System Page**
|
|
66
|
-
4. The plugin scans your Storybook stories and builds a "Design System" page
|
|
68
|
+
4. The plugin scans your Storybook stories and builds a "Design System" page with token tables, themed columns, grouped component sections, and responsive/state preview blocks where relevant
|
|
67
69
|
|
|
68
70
|
Component data is always scanned live on every run — never stale. Re-run at any time to pick up changes.
|
|
69
71
|
|
package/code.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
repo: "",
|
|
27
27
|
baseBranch: "main",
|
|
28
28
|
tokenPath: "design-tokens/tokens.dtcg.json",
|
|
29
|
-
tokenSourceMode: "
|
|
29
|
+
tokenSourceMode: "css",
|
|
30
30
|
cssTokenPath: "",
|
|
31
31
|
syncDtcgOnPush: false,
|
|
32
32
|
allowNewTokensFromFigma: false,
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
return tokenPath;
|
|
45
45
|
}
|
|
46
46
|
function normalizeTokenSourceMode(mode) {
|
|
47
|
-
if (mode === "
|
|
48
|
-
return "
|
|
47
|
+
if (mode === "dtcg") return "dtcg";
|
|
48
|
+
return "css";
|
|
49
49
|
}
|
|
50
50
|
function normalizeCssTokenPath(cssTokenPath) {
|
|
51
51
|
if (typeof cssTokenPath !== "string") return "";
|
|
@@ -1210,11 +1210,36 @@
|
|
|
1210
1210
|
function hasKeys(group) {
|
|
1211
1211
|
return !!group && Object.keys(group).length > 0;
|
|
1212
1212
|
}
|
|
1213
|
+
function enrichFontValue(value) {
|
|
1214
|
+
const parts = value.split(",");
|
|
1215
|
+
let hasLiteral = false;
|
|
1216
|
+
let insertAfterIdx = -1;
|
|
1217
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1218
|
+
const trimmed = parts[i].trim();
|
|
1219
|
+
if (/^var\(/.test(trimmed)) {
|
|
1220
|
+
if (insertAfterIdx === -1) insertAfterIdx = i;
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
const lower = trimmed.toLowerCase().replace(/^["']|["']$/g, "");
|
|
1224
|
+
if (SYSTEM_FONT_KEYWORDS.has(lower)) continue;
|
|
1225
|
+
if (trimmed.replace(/^["']|["']$/g, "").trim()) {
|
|
1226
|
+
hasLiteral = true;
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
if (hasLiteral || insertAfterIdx === -1) return value;
|
|
1231
|
+
const varMatch = parts[insertAfterIdx].trim().match(/^var\(--font-([a-z0-9-]+)\)/i);
|
|
1232
|
+
if (!varMatch) return value;
|
|
1233
|
+
const literal = varMatch[1].split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1234
|
+
const result = [...parts];
|
|
1235
|
+
result.splice(insertAfterIdx + 1, 0, ` "${literal}"`);
|
|
1236
|
+
return result.join(",");
|
|
1237
|
+
}
|
|
1213
1238
|
function buildTokenPatchFromScanned(map) {
|
|
1214
1239
|
const patch = {};
|
|
1215
1240
|
const primary = {};
|
|
1216
1241
|
if (hasKeys(map.colors)) primary.color = __spreadValues({}, map.colors);
|
|
1217
|
-
if (hasKeys(map.fonts)) primary.font =
|
|
1242
|
+
if (hasKeys(map.fonts)) primary.font = Object.fromEntries(Object.entries(map.fonts).map(([k, v]) => [k, enrichFontValue(String(v))]));
|
|
1218
1243
|
if (hasKeys(map.radius)) primary.radius = mapDimensionGroup(map.radius);
|
|
1219
1244
|
if (hasKeys(map.spacing)) primary.spacing = mapDimensionGroup(map.spacing);
|
|
1220
1245
|
if (hasKeys(map.fontSize)) primary.fontSize = mapDimensionGroup(map.fontSize);
|
|
@@ -1225,7 +1250,7 @@
|
|
|
1225
1250
|
if (!scanned) continue;
|
|
1226
1251
|
const themedPatch = {};
|
|
1227
1252
|
if (hasKeys(scanned.colors)) themedPatch.color = __spreadValues({}, scanned.colors);
|
|
1228
|
-
if (hasKeys(scanned.fonts)) themedPatch.font =
|
|
1253
|
+
if (hasKeys(scanned.fonts)) themedPatch.font = Object.fromEntries(Object.entries(scanned.fonts).map(([k, v]) => [k, enrichFontValue(v)]));
|
|
1229
1254
|
if (hasKeys(scanned.radius)) themedPatch.radius = mapDimensionGroup(scanned.radius);
|
|
1230
1255
|
if (hasKeys(scanned.spacing)) themedPatch.spacing = mapDimensionGroup(scanned.spacing);
|
|
1231
1256
|
if (hasKeys(scanned.fontSize)) themedPatch.fontSize = mapDimensionGroup(scanned.fontSize);
|
|
@@ -1301,6 +1326,54 @@
|
|
|
1301
1326
|
);
|
|
1302
1327
|
});
|
|
1303
1328
|
}
|
|
1329
|
+
var SYSTEM_FONT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
1330
|
+
// Generic families & system keywords
|
|
1331
|
+
"ui-sans-serif",
|
|
1332
|
+
"ui-serif",
|
|
1333
|
+
"ui-monospace",
|
|
1334
|
+
"system-ui",
|
|
1335
|
+
"sans-serif",
|
|
1336
|
+
"serif",
|
|
1337
|
+
"monospace",
|
|
1338
|
+
"-apple-system",
|
|
1339
|
+
"inherit",
|
|
1340
|
+
"initial",
|
|
1341
|
+
// Common web-safe fonts that appear as fallbacks, not as the intended design font
|
|
1342
|
+
"arial",
|
|
1343
|
+
"helvetica",
|
|
1344
|
+
"georgia",
|
|
1345
|
+
"verdana",
|
|
1346
|
+
"tahoma",
|
|
1347
|
+
"trebuchet ms",
|
|
1348
|
+
"times new roman",
|
|
1349
|
+
"courier new",
|
|
1350
|
+
"courier",
|
|
1351
|
+
"palatino",
|
|
1352
|
+
"garamond",
|
|
1353
|
+
"bookman",
|
|
1354
|
+
"comic sans ms",
|
|
1355
|
+
"impact",
|
|
1356
|
+
"lucida sans unicode"
|
|
1357
|
+
]);
|
|
1358
|
+
function extractFontName(raw) {
|
|
1359
|
+
const parts = String(raw || "").split(",");
|
|
1360
|
+
let varFallback = null;
|
|
1361
|
+
for (const part of parts) {
|
|
1362
|
+
const trimmed = part.trim();
|
|
1363
|
+
const varMatch = trimmed.match(/^var\(--font-([a-z0-9-]+)\)/i);
|
|
1364
|
+
if (varMatch) {
|
|
1365
|
+
if (!varFallback) {
|
|
1366
|
+
varFallback = varMatch[1].split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1367
|
+
}
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
const lower = trimmed.toLowerCase().replace(/^["']|["']$/g, "");
|
|
1371
|
+
if (SYSTEM_FONT_KEYWORDS.has(lower)) continue;
|
|
1372
|
+
const name = trimmed.replace(/^["']|["']$/g, "");
|
|
1373
|
+
if (name) return name;
|
|
1374
|
+
}
|
|
1375
|
+
return varFallback;
|
|
1376
|
+
}
|
|
1304
1377
|
function getThemeFontFamily(tokens, theme, role = "sans") {
|
|
1305
1378
|
const block = tokens[theme];
|
|
1306
1379
|
if (block && "font" in block) {
|
|
@@ -1308,8 +1381,8 @@
|
|
|
1308
1381
|
if (font) {
|
|
1309
1382
|
const raw = font[role] || (role !== "sans" ? font.sans : null) || Object.values(font)[0];
|
|
1310
1383
|
if (raw) {
|
|
1311
|
-
const
|
|
1312
|
-
if (
|
|
1384
|
+
const name = extractFontName(raw);
|
|
1385
|
+
if (name) return name;
|
|
1313
1386
|
}
|
|
1314
1387
|
}
|
|
1315
1388
|
}
|
|
@@ -1623,7 +1696,7 @@
|
|
|
1623
1696
|
function normalizeTokenMap(raw) {
|
|
1624
1697
|
if (!raw || !isPlainObject3(raw)) return void 0;
|
|
1625
1698
|
const mode = raw.mode === "css" || raw.mode === "dtcg" || raw.mode === "embedded" ? raw.mode : "embedded";
|
|
1626
|
-
const requestedMode = raw.requestedMode === "css" || raw.requestedMode === "dtcg"
|
|
1699
|
+
const requestedMode = raw.requestedMode === "css" || raw.requestedMode === "dtcg" ? raw.requestedMode : void 0;
|
|
1627
1700
|
const out = createEmptyScannedTokenMap(
|
|
1628
1701
|
mode,
|
|
1629
1702
|
typeof raw.source === "string" && raw.source ? raw.source : "embedded:tokens.ts",
|
|
@@ -1730,7 +1803,7 @@
|
|
|
1730
1803
|
const baseUrl = "http://localhost:" + port + "/" + path;
|
|
1731
1804
|
const query = [];
|
|
1732
1805
|
const requestedMode = config == null ? void 0 : config.tokenSourceMode;
|
|
1733
|
-
if (requestedMode === "
|
|
1806
|
+
if (requestedMode === "css" || requestedMode === "dtcg") {
|
|
1734
1807
|
query.push("tokenSourceMode=" + encodeURIComponent(requestedMode));
|
|
1735
1808
|
}
|
|
1736
1809
|
const cssTokenPath = ((config == null ? void 0 : config.cssTokenPath) || "").trim();
|
|
@@ -4329,7 +4402,7 @@
|
|
|
4329
4402
|
const modeRaw = raw && typeof raw === "object" ? raw.mode : null;
|
|
4330
4403
|
const mode = modeRaw === "css" || modeRaw === "dtcg" || modeRaw === "embedded" ? modeRaw : "embedded";
|
|
4331
4404
|
const requestedRaw = raw && typeof raw === "object" ? raw.requestedMode : null;
|
|
4332
|
-
const requestedMode = requestedRaw === "
|
|
4405
|
+
const requestedMode = requestedRaw === "css" || requestedRaw === "dtcg" ? requestedRaw : void 0;
|
|
4333
4406
|
return { source, mode, requestedMode };
|
|
4334
4407
|
}
|
|
4335
4408
|
async function loadTokenSourceInfo() {
|
|
@@ -4750,8 +4823,8 @@
|
|
|
4750
4823
|
return true;
|
|
4751
4824
|
}
|
|
4752
4825
|
function resolveConfiguredMode(mode) {
|
|
4753
|
-
if (mode === "
|
|
4754
|
-
return "
|
|
4826
|
+
if (mode === "dtcg") return "dtcg";
|
|
4827
|
+
return "css";
|
|
4755
4828
|
}
|
|
4756
4829
|
async function buildTokenCommitPlan(token) {
|
|
4757
4830
|
const tokensBeforeVariablePatch = JSON.parse(JSON.stringify(TOKENS));
|
|
@@ -4765,7 +4838,7 @@
|
|
|
4765
4838
|
const configuredMode = resolveConfiguredMode(GITHUB_CONFIG.tokenSourceMode);
|
|
4766
4839
|
const dtcgPath = normalizePath(GITHUB_CONFIG.tokenPath, DEFAULT_DTCG_PATH);
|
|
4767
4840
|
const dtcgExisting = await fetchFileContent(token, dtcgPath, GITHUB_CONFIG.baseBranch);
|
|
4768
|
-
const preferDtcg = configuredMode === "dtcg" || configuredMode === "
|
|
4841
|
+
const preferDtcg = configuredMode === "dtcg" || configuredMode === "css" && sourceInfo.mode === "dtcg";
|
|
4769
4842
|
if (preferDtcg) {
|
|
4770
4843
|
const dtcg = tokensToDTCG(workingTokens);
|
|
4771
4844
|
const dtcgContent = JSON.stringify(dtcg, null, 2) + "\n";
|
|
@@ -7212,6 +7285,62 @@
|
|
|
7212
7285
|
|
|
7213
7286
|
// src/story-builder.ts
|
|
7214
7287
|
var THEME_CONTEXT_CACHE = {};
|
|
7288
|
+
var BOARD_LAYOUT = {
|
|
7289
|
+
boardGap: 96,
|
|
7290
|
+
sectionPaddingX: 64,
|
|
7291
|
+
sectionPaddingY: 64,
|
|
7292
|
+
columnsGap: 320,
|
|
7293
|
+
columnGap: 144,
|
|
7294
|
+
columnPaddingX: 0,
|
|
7295
|
+
columnPaddingY: 160,
|
|
7296
|
+
sectionGroupGap: 176,
|
|
7297
|
+
sectionTitleGap: 48,
|
|
7298
|
+
componentBlockGap: 128,
|
|
7299
|
+
componentTitleGap: 48,
|
|
7300
|
+
storyGap: 32,
|
|
7301
|
+
showcaseGap: 128,
|
|
7302
|
+
stateMatrixGap: 40,
|
|
7303
|
+
stateMatrixAxisGap: 64,
|
|
7304
|
+
responsiveBlockGap: 32,
|
|
7305
|
+
responsiveColumnGap: 64,
|
|
7306
|
+
responsiveLabelGap: 20
|
|
7307
|
+
};
|
|
7308
|
+
var COMPONENT_SECTION_ORDER = [
|
|
7309
|
+
{
|
|
7310
|
+
key: "actions-forms",
|
|
7311
|
+
title: "Actions & Forms",
|
|
7312
|
+
description: "Buttons, fields, feedback, and overlay primitives."
|
|
7313
|
+
},
|
|
7314
|
+
{
|
|
7315
|
+
key: "content-data",
|
|
7316
|
+
title: "Content & Data",
|
|
7317
|
+
description: "Cards, tables, media, and information-heavy components."
|
|
7318
|
+
},
|
|
7319
|
+
{
|
|
7320
|
+
key: "navigation-shell",
|
|
7321
|
+
title: "Navigation & Shell",
|
|
7322
|
+
description: "Headers, footers, and navigation patterns."
|
|
7323
|
+
},
|
|
7324
|
+
{
|
|
7325
|
+
key: "marketing-sections",
|
|
7326
|
+
title: "Marketing Sections",
|
|
7327
|
+
description: "Landing-page compositions and promotional blocks."
|
|
7328
|
+
},
|
|
7329
|
+
{
|
|
7330
|
+
key: "pricing",
|
|
7331
|
+
title: "Pricing",
|
|
7332
|
+
description: "Pricing components and plan comparisons."
|
|
7333
|
+
},
|
|
7334
|
+
{
|
|
7335
|
+
key: "other",
|
|
7336
|
+
title: "Additional Components",
|
|
7337
|
+
description: "Everything else that does not fit the main groups."
|
|
7338
|
+
}
|
|
7339
|
+
];
|
|
7340
|
+
var COMPONENT_SECTION_BY_KEY = COMPONENT_SECTION_ORDER.reduce(function(map, section) {
|
|
7341
|
+
map[section.key] = section;
|
|
7342
|
+
return map;
|
|
7343
|
+
}, {});
|
|
7215
7344
|
function getThemeContext(theme) {
|
|
7216
7345
|
if (THEME_CONTEXT_CACHE[theme]) return THEME_CONTEXT_CACHE[theme];
|
|
7217
7346
|
const colorGroup = getThemeColors(TOKENS, theme);
|
|
@@ -7224,6 +7353,100 @@
|
|
|
7224
7353
|
THEME_CONTEXT_CACHE[theme] = ctx;
|
|
7225
7354
|
return ctx;
|
|
7226
7355
|
}
|
|
7356
|
+
function isTextNode(node) {
|
|
7357
|
+
return !!node && node.type === "TEXT";
|
|
7358
|
+
}
|
|
7359
|
+
function removeDirectTextChildren(parent, names) {
|
|
7360
|
+
if (!parent || !Array.isArray(parent.children)) return;
|
|
7361
|
+
const stale = parent.children.filter(function(child) {
|
|
7362
|
+
if (!isTextNode(child)) return false;
|
|
7363
|
+
if (!names || names.length === 0) return true;
|
|
7364
|
+
return names.indexOf(child.name) !== -1 || names.indexOf(child.characters) !== -1;
|
|
7365
|
+
});
|
|
7366
|
+
for (let i = 0; i < stale.length; i++) {
|
|
7367
|
+
stale[i].remove();
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
function ensureHeaderBlock(parent, frameName, title, description, opts) {
|
|
7371
|
+
let frame = findChildByName(parent, frameName);
|
|
7372
|
+
if (!frame) {
|
|
7373
|
+
frame = figma.createFrame();
|
|
7374
|
+
frame.name = frameName;
|
|
7375
|
+
}
|
|
7376
|
+
frame.layoutMode = "VERTICAL";
|
|
7377
|
+
frame.primaryAxisSizingMode = "AUTO";
|
|
7378
|
+
frame.counterAxisSizingMode = "AUTO";
|
|
7379
|
+
frame.counterAxisAlignItems = "MIN";
|
|
7380
|
+
frame.itemSpacing = description ? 10 : 0;
|
|
7381
|
+
frame.fills = [];
|
|
7382
|
+
const titleNode = createTextNode(title, {
|
|
7383
|
+
fontSize: opts && opts.titleSize ? opts.titleSize : 20,
|
|
7384
|
+
lineHeight: opts && opts.titleLineHeight ? opts.titleLineHeight : void 0,
|
|
7385
|
+
bold: true
|
|
7386
|
+
});
|
|
7387
|
+
titleNode.name = "Title";
|
|
7388
|
+
frame.children.slice().forEach(function(child) {
|
|
7389
|
+
child.remove();
|
|
7390
|
+
});
|
|
7391
|
+
frame.appendChild(titleNode);
|
|
7392
|
+
if (description) {
|
|
7393
|
+
const descriptionNode = createTextNode(description, {
|
|
7394
|
+
fontSize: opts && opts.descriptionSize ? opts.descriptionSize : 14,
|
|
7395
|
+
lineHeight: 20,
|
|
7396
|
+
opacity: 0.62
|
|
7397
|
+
});
|
|
7398
|
+
descriptionNode.name = "Description";
|
|
7399
|
+
frame.appendChild(descriptionNode);
|
|
7400
|
+
}
|
|
7401
|
+
return frame;
|
|
7402
|
+
}
|
|
7403
|
+
function getComponentSectionName(section) {
|
|
7404
|
+
return "Section / " + section.title;
|
|
7405
|
+
}
|
|
7406
|
+
function inferComponentSection(def) {
|
|
7407
|
+
const name = String(def && def.name ? def.name : "").toLowerCase();
|
|
7408
|
+
const filePath = String(def && def.filePath ? def.filePath : "").toLowerCase();
|
|
7409
|
+
if (filePath.includes("/feature/marketing/") || ["comparison-table", "compatibility-bar", "cta", "faq-section", "features", "hero-section", "how-it-works"].includes(name)) {
|
|
7410
|
+
return COMPONENT_SECTION_BY_KEY["marketing-sections"];
|
|
7411
|
+
}
|
|
7412
|
+
if (filePath.includes("/feature/pricing/") || name === "plan-card") {
|
|
7413
|
+
return COMPONENT_SECTION_BY_KEY.pricing;
|
|
7414
|
+
}
|
|
7415
|
+
if (["navbar", "footer", "hero", "mobile-nav-closed", "mobile-nav-open"].includes(name)) {
|
|
7416
|
+
return COMPONENT_SECTION_BY_KEY["navigation-shell"];
|
|
7417
|
+
}
|
|
7418
|
+
if (["button", "badge", "alert", "input", "select", "dialog", "dropdown-menu", "sheet"].includes(name) || filePath.includes("/components/ui/") && ["button", "badge", "alert", "input", "select", "dialog", "dropdown-menu", "sheet"].some(function(token) {
|
|
7419
|
+
return name === token;
|
|
7420
|
+
})) {
|
|
7421
|
+
return COMPONENT_SECTION_BY_KEY["actions-forms"];
|
|
7422
|
+
}
|
|
7423
|
+
if (["card", "table", "separator", "media-card", "skeleton", "text-truncation"].includes(name) || filePath.includes("/components/")) {
|
|
7424
|
+
return COMPONENT_SECTION_BY_KEY["content-data"];
|
|
7425
|
+
}
|
|
7426
|
+
return COMPONENT_SECTION_BY_KEY.other;
|
|
7427
|
+
}
|
|
7428
|
+
function groupComponentDefs(defs) {
|
|
7429
|
+
const grouped = {};
|
|
7430
|
+
for (let i = 0; i < defs.length; i++) {
|
|
7431
|
+
const def = defs[i];
|
|
7432
|
+
const section = inferComponentSection(def);
|
|
7433
|
+
if (!grouped[section.key]) grouped[section.key] = [];
|
|
7434
|
+
grouped[section.key].push(def);
|
|
7435
|
+
}
|
|
7436
|
+
const out = [];
|
|
7437
|
+
for (let i = 0; i < COMPONENT_SECTION_ORDER.length; i++) {
|
|
7438
|
+
const section = COMPONENT_SECTION_ORDER[i];
|
|
7439
|
+
const entries = grouped[section.key];
|
|
7440
|
+
if (!entries || entries.length === 0) continue;
|
|
7441
|
+
out.push({
|
|
7442
|
+
section,
|
|
7443
|
+
defs: entries.sort(function(a, b) {
|
|
7444
|
+
return a.name.localeCompare(b.name);
|
|
7445
|
+
})
|
|
7446
|
+
});
|
|
7447
|
+
}
|
|
7448
|
+
return out;
|
|
7449
|
+
}
|
|
7227
7450
|
function applyStyleToFrame(frame, style, theme) {
|
|
7228
7451
|
if (!style) return;
|
|
7229
7452
|
if (style.bg) {
|
|
@@ -7641,7 +7864,7 @@
|
|
|
7641
7864
|
const block = figma.createFrame();
|
|
7642
7865
|
block.name = "States";
|
|
7643
7866
|
block.layoutMode = "VERTICAL";
|
|
7644
|
-
block.itemSpacing =
|
|
7867
|
+
block.itemSpacing = BOARD_LAYOUT.stateMatrixGap;
|
|
7645
7868
|
block.primaryAxisSizingMode = "AUTO";
|
|
7646
7869
|
block.counterAxisSizingMode = "AUTO";
|
|
7647
7870
|
block.fills = [];
|
|
@@ -7736,7 +7959,7 @@
|
|
|
7736
7959
|
const axesRow = figma.createFrame();
|
|
7737
7960
|
axesRow.name = "State Axes";
|
|
7738
7961
|
axesRow.layoutMode = "HORIZONTAL";
|
|
7739
|
-
axesRow.itemSpacing =
|
|
7962
|
+
axesRow.itemSpacing = BOARD_LAYOUT.stateMatrixAxisGap;
|
|
7740
7963
|
axesRow.primaryAxisSizingMode = "AUTO";
|
|
7741
7964
|
axesRow.counterAxisSizingMode = "AUTO";
|
|
7742
7965
|
axesRow.fills = [];
|
|
@@ -7747,7 +7970,7 @@
|
|
|
7747
7970
|
const table = figma.createFrame();
|
|
7748
7971
|
table.name = "State Table";
|
|
7749
7972
|
table.layoutMode = "VERTICAL";
|
|
7750
|
-
table.itemSpacing =
|
|
7973
|
+
table.itemSpacing = BOARD_LAYOUT.stateMatrixGap;
|
|
7751
7974
|
table.primaryAxisSizingMode = "AUTO";
|
|
7752
7975
|
table.counterAxisSizingMode = "AUTO";
|
|
7753
7976
|
table.fills = [];
|
|
@@ -7755,7 +7978,7 @@
|
|
|
7755
7978
|
const tableHeader = figma.createFrame();
|
|
7756
7979
|
tableHeader.name = "State Table Header";
|
|
7757
7980
|
tableHeader.layoutMode = "HORIZONTAL";
|
|
7758
|
-
tableHeader.itemSpacing =
|
|
7981
|
+
tableHeader.itemSpacing = BOARD_LAYOUT.stateMatrixAxisGap;
|
|
7759
7982
|
tableHeader.primaryAxisSizingMode = "AUTO";
|
|
7760
7983
|
tableHeader.counterAxisSizingMode = "AUTO";
|
|
7761
7984
|
tableHeader.fills = [];
|
|
@@ -7779,7 +8002,7 @@
|
|
|
7779
8002
|
const row = figma.createFrame();
|
|
7780
8003
|
row.name = "State Row/" + stateNames[si];
|
|
7781
8004
|
row.layoutMode = "HORIZONTAL";
|
|
7782
|
-
row.itemSpacing =
|
|
8005
|
+
row.itemSpacing = BOARD_LAYOUT.stateMatrixAxisGap;
|
|
7783
8006
|
row.primaryAxisSizingMode = "AUTO";
|
|
7784
8007
|
row.counterAxisSizingMode = "AUTO";
|
|
7785
8008
|
row.counterAxisAlignItems = "CENTER";
|
|
@@ -7866,7 +8089,7 @@
|
|
|
7866
8089
|
const block = figma.createFrame();
|
|
7867
8090
|
block.name = "Responsive";
|
|
7868
8091
|
block.layoutMode = "VERTICAL";
|
|
7869
|
-
block.itemSpacing =
|
|
8092
|
+
block.itemSpacing = BOARD_LAYOUT.responsiveBlockGap;
|
|
7870
8093
|
block.primaryAxisSizingMode = "AUTO";
|
|
7871
8094
|
block.counterAxisSizingMode = "AUTO";
|
|
7872
8095
|
block.fills = [];
|
|
@@ -7875,7 +8098,7 @@
|
|
|
7875
8098
|
const row = figma.createFrame();
|
|
7876
8099
|
row.name = "Responsive Strip";
|
|
7877
8100
|
row.layoutMode = "HORIZONTAL";
|
|
7878
|
-
row.itemSpacing =
|
|
8101
|
+
row.itemSpacing = BOARD_LAYOUT.responsiveColumnGap;
|
|
7879
8102
|
row.primaryAxisSizingMode = "AUTO";
|
|
7880
8103
|
row.counterAxisSizingMode = "AUTO";
|
|
7881
8104
|
row.fills = [];
|
|
@@ -7887,7 +8110,7 @@
|
|
|
7887
8110
|
const col = figma.createFrame();
|
|
7888
8111
|
col.name = getBreakpointLabel(bp.name, bp.minWidth);
|
|
7889
8112
|
col.layoutMode = "VERTICAL";
|
|
7890
|
-
col.itemSpacing =
|
|
8113
|
+
col.itemSpacing = BOARD_LAYOUT.responsiveLabelGap;
|
|
7891
8114
|
col.primaryAxisSizingMode = "AUTO";
|
|
7892
8115
|
col.counterAxisSizingMode = "AUTO";
|
|
7893
8116
|
col.fills = [];
|
|
@@ -8286,24 +8509,40 @@
|
|
|
8286
8509
|
debug("createUIComponents start", { opts: options });
|
|
8287
8510
|
const sectionTitle = options.sectionTitle || "UI Components";
|
|
8288
8511
|
const tokenHash = typeof options.tokenHash === "string" ? options.tokenHash : "";
|
|
8512
|
+
const showSectionHeader = options.showSectionHeader !== false;
|
|
8513
|
+
const sectionPaddingX = typeof options.sectionPaddingX === "number" ? options.sectionPaddingX : showSectionHeader ? BOARD_LAYOUT.sectionPaddingX : 0;
|
|
8514
|
+
const sectionPaddingY = typeof options.sectionPaddingY === "number" ? options.sectionPaddingY : showSectionHeader ? BOARD_LAYOUT.sectionPaddingY : 0;
|
|
8289
8515
|
let section = findChildByName(parent, sectionTitle);
|
|
8290
8516
|
if (!section) {
|
|
8291
8517
|
section = figma.createFrame();
|
|
8292
8518
|
section.name = sectionTitle;
|
|
8293
|
-
section.layoutMode = "VERTICAL";
|
|
8294
|
-
section.itemSpacing = 32;
|
|
8295
|
-
section.primaryAxisSizingMode = "AUTO";
|
|
8296
|
-
section.counterAxisSizingMode = "AUTO";
|
|
8297
|
-
section.paddingLeft = section.paddingRight = 24;
|
|
8298
|
-
section.paddingTop = section.paddingBottom = 24;
|
|
8299
|
-
section.fills = [];
|
|
8300
|
-
ctx.applyClipBehavior(section, []);
|
|
8301
8519
|
const offsetY = typeof options.yOffset === "number" ? options.yOffset : 0;
|
|
8302
8520
|
const offsetX = typeof options.xOffset === "number" ? options.xOffset : 0;
|
|
8303
8521
|
section.x = offsetX;
|
|
8304
8522
|
section.y = offsetY;
|
|
8305
8523
|
parent.appendChild(section);
|
|
8306
|
-
|
|
8524
|
+
}
|
|
8525
|
+
section.layoutMode = "VERTICAL";
|
|
8526
|
+
section.itemSpacing = showSectionHeader ? BOARD_LAYOUT.boardGap : 0;
|
|
8527
|
+
section.primaryAxisSizingMode = "AUTO";
|
|
8528
|
+
section.counterAxisSizingMode = "AUTO";
|
|
8529
|
+
section.paddingLeft = section.paddingRight = sectionPaddingX;
|
|
8530
|
+
section.paddingTop = section.paddingBottom = sectionPaddingY;
|
|
8531
|
+
section.counterAxisAlignItems = "MIN";
|
|
8532
|
+
section.fills = [];
|
|
8533
|
+
ctx.applyClipBehavior(section, []);
|
|
8534
|
+
removeDirectTextChildren(section);
|
|
8535
|
+
const existingSectionHeader = findChildByName(section, "Section Header");
|
|
8536
|
+
if (showSectionHeader) {
|
|
8537
|
+
const sectionHeader = ensureHeaderBlock(section, "Section Header", sectionTitle, null, {
|
|
8538
|
+
titleSize: 32,
|
|
8539
|
+
titleLineHeight: 38
|
|
8540
|
+
});
|
|
8541
|
+
if (findChildIndexByName(section, "Section Header") !== 0) {
|
|
8542
|
+
section.insertChild(0, sectionHeader);
|
|
8543
|
+
}
|
|
8544
|
+
} else if (existingSectionHeader) {
|
|
8545
|
+
existingSectionHeader.remove();
|
|
8307
8546
|
}
|
|
8308
8547
|
function formatThemeLabel(theme) {
|
|
8309
8548
|
if (!theme) return "Theme";
|
|
@@ -8316,25 +8555,26 @@
|
|
|
8316
8555
|
const block = figma.createFrame();
|
|
8317
8556
|
block.name = def.name;
|
|
8318
8557
|
block.layoutMode = "VERTICAL";
|
|
8319
|
-
block.itemSpacing =
|
|
8558
|
+
block.itemSpacing = BOARD_LAYOUT.componentTitleGap;
|
|
8320
8559
|
block.primaryAxisSizingMode = "AUTO";
|
|
8321
8560
|
block.counterAxisSizingMode = "AUTO";
|
|
8561
|
+
block.counterAxisAlignItems = "MIN";
|
|
8322
8562
|
block.fills = [];
|
|
8323
8563
|
ctx.applyClipBehavior(block, []);
|
|
8324
|
-
block.appendChild(createTextNode(def.name, { fontSize: 16, bold: true }));
|
|
8564
|
+
block.appendChild(createTextNode(def.name, { fontSize: 16, lineHeight: 22, bold: true }));
|
|
8325
8565
|
const storyList = def.stories || [];
|
|
8326
8566
|
for (let storyIndex = 0; storyIndex < storyList.length; storyIndex++) {
|
|
8327
8567
|
const story = storyList[storyIndex];
|
|
8328
8568
|
const storyWrap = figma.createFrame();
|
|
8329
8569
|
storyWrap.name = story.name;
|
|
8330
8570
|
storyWrap.layoutMode = "VERTICAL";
|
|
8331
|
-
storyWrap.itemSpacing =
|
|
8571
|
+
storyWrap.itemSpacing = BOARD_LAYOUT.storyGap;
|
|
8332
8572
|
storyWrap.primaryAxisSizingMode = "AUTO";
|
|
8333
8573
|
storyWrap.counterAxisSizingMode = "AUTO";
|
|
8334
8574
|
storyWrap.counterAxisAlignItems = "MIN";
|
|
8335
8575
|
storyWrap.fills = [];
|
|
8336
8576
|
ctx.applyClipBehavior(storyWrap, []);
|
|
8337
|
-
storyWrap.appendChild(createTextNode(story.name, { fontSize:
|
|
8577
|
+
storyWrap.appendChild(createTextNode(story.name, { fontSize: 14, lineHeight: 20, opacity: 0.6, textAlignHorizontal: "LEFT" }));
|
|
8338
8578
|
const layout = figma.createFrame();
|
|
8339
8579
|
layout.name = "Story Layout";
|
|
8340
8580
|
layout.primaryAxisSizingMode = "AUTO";
|
|
@@ -8397,7 +8637,7 @@
|
|
|
8397
8637
|
layout.counterAxisSizingMode = "AUTO";
|
|
8398
8638
|
}
|
|
8399
8639
|
let added = 0;
|
|
8400
|
-
if ((def.type === "compound" || def.type === "simple") && story.jsxTree) {
|
|
8640
|
+
if ((def.type === "compound" || def.type === "simple" || def.type === "cva") && story.jsxTree) {
|
|
8401
8641
|
let rendered = false;
|
|
8402
8642
|
const allowAbsolute = ctx.hasExplicitHeight(layoutClasses);
|
|
8403
8643
|
const rootNode = story.jsxTree;
|
|
@@ -8537,13 +8777,26 @@
|
|
|
8537
8777
|
if (!colFrame) {
|
|
8538
8778
|
colFrame = figma.createFrame();
|
|
8539
8779
|
colFrame.name = label + " Column";
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8780
|
+
}
|
|
8781
|
+
colFrame.layoutMode = "VERTICAL";
|
|
8782
|
+
colFrame.itemSpacing = BOARD_LAYOUT.columnGap;
|
|
8783
|
+
colFrame.primaryAxisSizingMode = "AUTO";
|
|
8784
|
+
colFrame.counterAxisSizingMode = "AUTO";
|
|
8785
|
+
colFrame.counterAxisAlignItems = "MIN";
|
|
8786
|
+
colFrame.paddingLeft = colFrame.paddingRight = BOARD_LAYOUT.columnPaddingX;
|
|
8787
|
+
colFrame.paddingTop = colFrame.paddingBottom = BOARD_LAYOUT.columnPaddingY;
|
|
8788
|
+
colFrame.fills = [];
|
|
8789
|
+
ctx.applyClipBehavior(colFrame, []);
|
|
8790
|
+
removeDirectTextChildren(colFrame);
|
|
8791
|
+
const themeHeader = ensureHeaderBlock(
|
|
8792
|
+
colFrame,
|
|
8793
|
+
"Theme Header",
|
|
8794
|
+
label + " Theme",
|
|
8795
|
+
"Generated component board with grouped stories and larger comparison spacing.",
|
|
8796
|
+
{ titleSize: 32, titleLineHeight: 38, descriptionSize: 14 }
|
|
8797
|
+
);
|
|
8798
|
+
if (findChildIndexByName(colFrame, "Theme Header") !== 0) {
|
|
8799
|
+
colFrame.insertChild(0, themeHeader);
|
|
8547
8800
|
}
|
|
8548
8801
|
const pack = getActivePack();
|
|
8549
8802
|
const packStories = pack && pack.stories ? pack.stories : [];
|
|
@@ -8554,52 +8807,63 @@
|
|
|
8554
8807
|
return tags.indexOf(storyTagFilter) !== -1;
|
|
8555
8808
|
});
|
|
8556
8809
|
const packStoriesHash = hashString(JSON.stringify(filteredStories) + theme);
|
|
8557
|
-
let packStoriesFrame = findChildByName(colFrame, "
|
|
8810
|
+
let packStoriesFrame = findChildByName(colFrame, "Showcase Stories");
|
|
8558
8811
|
if (!packStoriesFrame || getFrameHash(packStoriesFrame) !== packStoriesHash) {
|
|
8559
8812
|
if (packStoriesFrame) packStoriesFrame.remove();
|
|
8813
|
+
const stalePackFrame = findChildByName(colFrame, "Pack Stories");
|
|
8814
|
+
if (stalePackFrame) stalePackFrame.remove();
|
|
8560
8815
|
if (filteredStories.length > 0) {
|
|
8561
8816
|
packStoriesFrame = figma.createFrame();
|
|
8562
|
-
packStoriesFrame.name = "
|
|
8817
|
+
packStoriesFrame.name = "Showcase Stories";
|
|
8563
8818
|
packStoriesFrame.layoutMode = "VERTICAL";
|
|
8564
|
-
packStoriesFrame.itemSpacing =
|
|
8819
|
+
packStoriesFrame.itemSpacing = BOARD_LAYOUT.showcaseGap;
|
|
8565
8820
|
packStoriesFrame.primaryAxisSizingMode = "AUTO";
|
|
8566
8821
|
packStoriesFrame.counterAxisSizingMode = "AUTO";
|
|
8822
|
+
packStoriesFrame.counterAxisAlignItems = "MIN";
|
|
8567
8823
|
packStoriesFrame.fills = [];
|
|
8568
8824
|
ctx.applyClipBehavior(packStoriesFrame, []);
|
|
8825
|
+
packStoriesFrame.appendChild(ensureHeaderBlock(
|
|
8826
|
+
packStoriesFrame,
|
|
8827
|
+
"Section Header",
|
|
8828
|
+
"Showcase Stories",
|
|
8829
|
+
"Larger composite stories rendered before the component catalog.",
|
|
8830
|
+
{ titleSize: 20, titleLineHeight: 26, descriptionSize: 14 }
|
|
8831
|
+
));
|
|
8569
8832
|
for (let si = 0; si < filteredStories.length; si++) {
|
|
8570
8833
|
const story = filteredStories[si];
|
|
8571
8834
|
const storyBlock = figma.createFrame();
|
|
8572
8835
|
storyBlock.name = story.name || "Story";
|
|
8573
8836
|
storyBlock.layoutMode = "VERTICAL";
|
|
8574
|
-
storyBlock.itemSpacing =
|
|
8837
|
+
storyBlock.itemSpacing = BOARD_LAYOUT.componentTitleGap;
|
|
8575
8838
|
storyBlock.primaryAxisSizingMode = "AUTO";
|
|
8576
8839
|
storyBlock.counterAxisSizingMode = "AUTO";
|
|
8840
|
+
storyBlock.counterAxisAlignItems = "MIN";
|
|
8577
8841
|
storyBlock.fills = [];
|
|
8578
8842
|
ctx.applyClipBehavior(storyBlock, []);
|
|
8579
|
-
storyBlock.appendChild(createTextNode(story.name || "Story", { fontSize: 16, bold: true }));
|
|
8843
|
+
storyBlock.appendChild(createTextNode(story.name || "Story", { fontSize: 16, lineHeight: 22, bold: true }));
|
|
8580
8844
|
storyBlock.appendChild(renderStandaloneStory(story, theme, ctx));
|
|
8581
8845
|
packStoriesFrame.appendChild(storyBlock);
|
|
8582
8846
|
}
|
|
8583
|
-
|
|
8584
|
-
if (headerExists) {
|
|
8585
|
-
colFrame.insertChild(1, packStoriesFrame);
|
|
8586
|
-
} else {
|
|
8587
|
-
colFrame.appendChild(packStoriesFrame);
|
|
8588
|
-
}
|
|
8847
|
+
colFrame.appendChild(packStoriesFrame);
|
|
8589
8848
|
setFrameHash(packStoriesFrame, packStoriesHash);
|
|
8590
8849
|
}
|
|
8591
8850
|
}
|
|
8851
|
+
packStoriesFrame = findChildByName(colFrame, "Showcase Stories");
|
|
8852
|
+
if (packStoriesFrame && findChildIndexByName(colFrame, "Showcase Stories") !== 1) {
|
|
8853
|
+
colFrame.insertChild(1, packStoriesFrame);
|
|
8854
|
+
}
|
|
8592
8855
|
let componentList = findChildByName(colFrame, "Component Blocks");
|
|
8593
8856
|
if (!componentList) {
|
|
8594
8857
|
componentList = figma.createFrame();
|
|
8595
8858
|
componentList.name = "Component Blocks";
|
|
8596
8859
|
componentList.layoutMode = "VERTICAL";
|
|
8597
|
-
componentList.primaryAxisSizingMode = "AUTO";
|
|
8598
|
-
componentList.counterAxisSizingMode = "AUTO";
|
|
8599
|
-
componentList.itemSpacing = colFrame.itemSpacing * 6;
|
|
8600
|
-
componentList.fills = [];
|
|
8601
|
-
ctx.applyClipBehavior(componentList, []);
|
|
8602
8860
|
}
|
|
8861
|
+
componentList.primaryAxisSizingMode = "AUTO";
|
|
8862
|
+
componentList.counterAxisSizingMode = "AUTO";
|
|
8863
|
+
componentList.counterAxisAlignItems = "MIN";
|
|
8864
|
+
componentList.itemSpacing = BOARD_LAYOUT.sectionGroupGap;
|
|
8865
|
+
componentList.fills = [];
|
|
8866
|
+
ctx.applyClipBehavior(componentList, []);
|
|
8603
8867
|
const defsRaw = COMPONENT_DEFS && COMPONENT_DEFS.components ? COMPONENT_DEFS.components : [];
|
|
8604
8868
|
const onlyComponents = options.onlyComponents;
|
|
8605
8869
|
const excludeComponents = options.excludeComponents;
|
|
@@ -8611,57 +8875,122 @@
|
|
|
8611
8875
|
}).sort(function(a, b) {
|
|
8612
8876
|
return a.name.localeCompare(b.name);
|
|
8613
8877
|
});
|
|
8614
|
-
const
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
}
|
|
8625
|
-
}
|
|
8626
|
-
for (let
|
|
8627
|
-
const
|
|
8628
|
-
const
|
|
8629
|
-
|
|
8630
|
-
if (
|
|
8631
|
-
|
|
8632
|
-
|
|
8878
|
+
const groupedDefs = groupComponentDefs(defs);
|
|
8879
|
+
const expectedSectionNames = {};
|
|
8880
|
+
for (let gi = 0; gi < groupedDefs.length; gi++) {
|
|
8881
|
+
expectedSectionNames[getComponentSectionName(groupedDefs[gi].section)] = true;
|
|
8882
|
+
}
|
|
8883
|
+
const existingGroups = componentList.children ? Array.from(componentList.children) : [];
|
|
8884
|
+
for (let gi = 0; gi < existingGroups.length; gi++) {
|
|
8885
|
+
const existingGroup = existingGroups[gi];
|
|
8886
|
+
if (!expectedSectionNames[existingGroup.name]) {
|
|
8887
|
+
existingGroup.remove();
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
for (let gi = 0; gi < groupedDefs.length; gi++) {
|
|
8891
|
+
const group = groupedDefs[gi];
|
|
8892
|
+
const sectionName = getComponentSectionName(group.section);
|
|
8893
|
+
let sectionFrame = findChildByName(componentList, sectionName);
|
|
8894
|
+
if (!sectionFrame) {
|
|
8895
|
+
sectionFrame = figma.createFrame();
|
|
8896
|
+
sectionFrame.name = sectionName;
|
|
8897
|
+
}
|
|
8898
|
+
sectionFrame.layoutMode = "VERTICAL";
|
|
8899
|
+
sectionFrame.primaryAxisSizingMode = "AUTO";
|
|
8900
|
+
sectionFrame.counterAxisSizingMode = "AUTO";
|
|
8901
|
+
sectionFrame.counterAxisAlignItems = "MIN";
|
|
8902
|
+
sectionFrame.itemSpacing = BOARD_LAYOUT.sectionTitleGap;
|
|
8903
|
+
sectionFrame.fills = [];
|
|
8904
|
+
ctx.applyClipBehavior(sectionFrame, []);
|
|
8905
|
+
const groupHeader = ensureHeaderBlock(
|
|
8906
|
+
sectionFrame,
|
|
8907
|
+
"Section Header",
|
|
8908
|
+
group.section.title,
|
|
8909
|
+
group.section.description,
|
|
8910
|
+
{ titleSize: 20, titleLineHeight: 26, descriptionSize: 14 }
|
|
8911
|
+
);
|
|
8912
|
+
if (findChildIndexByName(sectionFrame, "Section Header") !== 0) {
|
|
8913
|
+
sectionFrame.insertChild(0, groupHeader);
|
|
8914
|
+
}
|
|
8915
|
+
let groupBlocks = findChildByName(sectionFrame, "Blocks");
|
|
8916
|
+
if (!groupBlocks) {
|
|
8917
|
+
groupBlocks = figma.createFrame();
|
|
8918
|
+
groupBlocks.name = "Blocks";
|
|
8919
|
+
sectionFrame.appendChild(groupBlocks);
|
|
8920
|
+
}
|
|
8921
|
+
groupBlocks.layoutMode = "VERTICAL";
|
|
8922
|
+
groupBlocks.primaryAxisSizingMode = "AUTO";
|
|
8923
|
+
groupBlocks.counterAxisSizingMode = "AUTO";
|
|
8924
|
+
groupBlocks.counterAxisAlignItems = "MIN";
|
|
8925
|
+
groupBlocks.itemSpacing = BOARD_LAYOUT.componentBlockGap;
|
|
8926
|
+
groupBlocks.fills = [];
|
|
8927
|
+
ctx.applyClipBehavior(groupBlocks, []);
|
|
8928
|
+
const expectedNames = {};
|
|
8929
|
+
for (let di = 0; di < group.defs.length; di++) {
|
|
8930
|
+
expectedNames[group.defs[di].name] = true;
|
|
8931
|
+
}
|
|
8932
|
+
const existingBlocks = groupBlocks.children ? Array.from(groupBlocks.children) : [];
|
|
8933
|
+
for (let ei = 0; ei < existingBlocks.length; ei++) {
|
|
8934
|
+
const existingBlock = existingBlocks[ei];
|
|
8935
|
+
if (!expectedNames[existingBlock.name]) {
|
|
8936
|
+
existingBlock.remove();
|
|
8937
|
+
debug("Removed stale component block", { name: existingBlock.name });
|
|
8938
|
+
}
|
|
8633
8939
|
}
|
|
8634
|
-
let
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
existingBlock.
|
|
8638
|
-
|
|
8940
|
+
for (let di = 0; di < group.defs.length; di++) {
|
|
8941
|
+
const def = group.defs[di];
|
|
8942
|
+
const blockHash = hashDef(def) + ":" + tokenHash;
|
|
8943
|
+
const existingBlock = findChildByName(groupBlocks, def.name);
|
|
8944
|
+
if (existingBlock && getFrameHash(existingBlock) === blockHash) {
|
|
8945
|
+
const currentIndex = findChildIndexByName(groupBlocks, def.name);
|
|
8946
|
+
if (currentIndex !== di) {
|
|
8947
|
+
groupBlocks.insertChild(di, existingBlock);
|
|
8948
|
+
}
|
|
8949
|
+
debug("Component block unchanged \u2014 skipped", { name: def.name });
|
|
8950
|
+
continue;
|
|
8951
|
+
}
|
|
8952
|
+
let insertIndex = di;
|
|
8953
|
+
if (existingBlock) {
|
|
8954
|
+
existingBlock.remove();
|
|
8955
|
+
debug("Component block changed \u2014 rebuilding", { name: def.name });
|
|
8956
|
+
}
|
|
8957
|
+
const newBlock = buildComponentBlock(def, theme, colFrame);
|
|
8958
|
+
setFrameHash(newBlock, blockHash);
|
|
8959
|
+
if (insertIndex >= 0 && insertIndex < groupBlocks.children.length) {
|
|
8960
|
+
groupBlocks.insertChild(insertIndex, newBlock);
|
|
8961
|
+
} else {
|
|
8962
|
+
groupBlocks.appendChild(newBlock);
|
|
8963
|
+
}
|
|
8639
8964
|
}
|
|
8640
|
-
const
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
componentList.insertChild(insertIndex, newBlock);
|
|
8644
|
-
} else {
|
|
8645
|
-
componentList.appendChild(newBlock);
|
|
8965
|
+
const currentSectionIndex = findChildIndexByName(componentList, sectionName);
|
|
8966
|
+
if (currentSectionIndex !== gi) {
|
|
8967
|
+
componentList.insertChild(gi, sectionFrame);
|
|
8646
8968
|
}
|
|
8647
8969
|
}
|
|
8648
8970
|
if (componentList.children.length > 0 && !findChildByName(colFrame, "Component Blocks")) {
|
|
8649
8971
|
colFrame.appendChild(componentList);
|
|
8650
8972
|
}
|
|
8973
|
+
if (findChildByName(colFrame, "Component Blocks")) {
|
|
8974
|
+
const componentListIndex = packStoriesFrame ? 2 : 1;
|
|
8975
|
+
if (findChildIndexByName(colFrame, "Component Blocks") !== componentListIndex) {
|
|
8976
|
+
colFrame.insertChild(componentListIndex, componentList);
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8651
8979
|
return colFrame;
|
|
8652
8980
|
}
|
|
8653
8981
|
let columns = findChildByName(section, "Columns");
|
|
8654
8982
|
if (!columns) {
|
|
8655
8983
|
columns = figma.createFrame();
|
|
8656
8984
|
columns.name = "Columns";
|
|
8657
|
-
columns.layoutMode = "HORIZONTAL";
|
|
8658
|
-
columns.itemSpacing = 32;
|
|
8659
|
-
columns.primaryAxisSizingMode = "AUTO";
|
|
8660
|
-
columns.counterAxisSizingMode = "AUTO";
|
|
8661
|
-
columns.fills = [];
|
|
8662
|
-
ctx.applyClipBehavior(columns, []);
|
|
8663
8985
|
section.appendChild(columns);
|
|
8664
8986
|
}
|
|
8987
|
+
columns.layoutMode = "HORIZONTAL";
|
|
8988
|
+
columns.itemSpacing = BOARD_LAYOUT.columnsGap;
|
|
8989
|
+
columns.primaryAxisSizingMode = "AUTO";
|
|
8990
|
+
columns.counterAxisSizingMode = "AUTO";
|
|
8991
|
+
columns.counterAxisAlignItems = "MIN";
|
|
8992
|
+
columns.fills = [];
|
|
8993
|
+
ctx.applyClipBehavior(columns, []);
|
|
8665
8994
|
const requestedThemes = Array.isArray(options.themeNames) ? options.themeNames.filter(function(theme, index, list) {
|
|
8666
8995
|
return typeof theme === "string" && theme.trim().length > 0 && list.indexOf(theme) === index;
|
|
8667
8996
|
}) : [];
|
|
@@ -8673,16 +9002,17 @@
|
|
|
8673
9002
|
if (multiMode) {
|
|
8674
9003
|
const activeTheme = requestedThemes[0] || "primary";
|
|
8675
9004
|
const singleCol = addColumn("Theme", activeTheme);
|
|
8676
|
-
if (
|
|
8677
|
-
columns.
|
|
9005
|
+
if (findChildIndexByName(columns, "Theme Column") !== 0) {
|
|
9006
|
+
columns.insertChild(0, singleCol);
|
|
8678
9007
|
}
|
|
8679
9008
|
setThemeMode(singleCol, activeTheme);
|
|
8680
9009
|
} else {
|
|
8681
9010
|
for (let ti = 0; ti < requestedThemes.length; ti++) {
|
|
8682
9011
|
const themeName = requestedThemes[ti];
|
|
8683
9012
|
const themeCol = addColumn(formatThemeLabel(themeName), themeName);
|
|
8684
|
-
|
|
8685
|
-
|
|
9013
|
+
const columnName = formatThemeLabel(themeName) + " Column";
|
|
9014
|
+
if (findChildIndexByName(columns, columnName) !== ti) {
|
|
9015
|
+
columns.insertChild(ti, themeCol);
|
|
8686
9016
|
}
|
|
8687
9017
|
setThemeMode(themeCol, themeName);
|
|
8688
9018
|
}
|
|
@@ -10235,6 +10565,7 @@
|
|
|
10235
10565
|
function normalizeComponentDef(raw) {
|
|
10236
10566
|
if (!raw || !raw.analysis) return raw;
|
|
10237
10567
|
return __spreadProps(__spreadValues({}, raw.analysis), {
|
|
10568
|
+
filePath: raw.analysis.filePath || raw.filePath,
|
|
10238
10569
|
stories: Array.isArray(raw.analysis.stories) ? raw.analysis.stories : Array.isArray(raw.stories) ? raw.stories : [],
|
|
10239
10570
|
hasStory: typeof raw.analysis.hasStory === "boolean" ? raw.analysis.hasStory : !!raw.hasStory,
|
|
10240
10571
|
layout: raw.layout,
|
|
@@ -11342,6 +11673,16 @@
|
|
|
11342
11673
|
|
|
11343
11674
|
// src/design-system.ts
|
|
11344
11675
|
var EFFECTS_COMPONENTS = ["Gradient-showcase"];
|
|
11676
|
+
function removeStalePageLabels(page, labels) {
|
|
11677
|
+
if (!page || !Array.isArray(page.children)) return;
|
|
11678
|
+
const targets = new Set(labels);
|
|
11679
|
+
const staleNodes = page.children.filter(function(node) {
|
|
11680
|
+
return node && node.type === "TEXT" && typeof node.characters === "string" && targets.has(node.characters);
|
|
11681
|
+
});
|
|
11682
|
+
for (let i = 0; i < staleNodes.length; i++) {
|
|
11683
|
+
staleNodes[i].remove();
|
|
11684
|
+
}
|
|
11685
|
+
}
|
|
11345
11686
|
function buildDesignSystemSinglePage() {
|
|
11346
11687
|
let ds = figma.root.children.find((p) => p.name === "Design System");
|
|
11347
11688
|
if (!ds) {
|
|
@@ -11349,6 +11690,7 @@
|
|
|
11349
11690
|
ds.name = "Design System";
|
|
11350
11691
|
}
|
|
11351
11692
|
figma.currentPage = ds;
|
|
11693
|
+
removeStalePageLabels(ds, ["Design Tokens", "UI Components", "Effects"]);
|
|
11352
11694
|
const themeNames = getThemeNames(TOKENS);
|
|
11353
11695
|
const tokenHash = hashString(JSON.stringify(TOKENS));
|
|
11354
11696
|
let tokensRow = findChildByName(ds, "Design Tokens");
|
|
@@ -11384,7 +11726,8 @@
|
|
|
11384
11726
|
yOffset: defaultUiY,
|
|
11385
11727
|
xOffset: 48,
|
|
11386
11728
|
excludeComponents: EFFECTS_COMPONENTS,
|
|
11387
|
-
tokenHash
|
|
11729
|
+
tokenHash,
|
|
11730
|
+
showSectionHeader: false
|
|
11388
11731
|
});
|
|
11389
11732
|
const uiSection = findChildByName(ds, "UI Components");
|
|
11390
11733
|
const defaultEffectsY = uiSection ? uiSection.y + uiSection.height + 80 : defaultUiY + 800;
|
|
@@ -11394,7 +11737,8 @@
|
|
|
11394
11737
|
xOffset: 48,
|
|
11395
11738
|
sectionTitle: "Effects",
|
|
11396
11739
|
onlyComponents: EFFECTS_COMPONENTS,
|
|
11397
|
-
tokenHash
|
|
11740
|
+
tokenHash,
|
|
11741
|
+
showSectionHeader: false
|
|
11398
11742
|
});
|
|
11399
11743
|
}
|
|
11400
11744
|
|
|
@@ -11416,7 +11760,7 @@
|
|
|
11416
11760
|
}
|
|
11417
11761
|
function coerceTokenSourceInfo(raw) {
|
|
11418
11762
|
const mode = raw && (raw.mode === "css" || raw.mode === "dtcg" || raw.mode === "embedded") ? raw.mode : "embedded";
|
|
11419
|
-
const requestedMode = raw && (raw.requestedMode === "
|
|
11763
|
+
const requestedMode = raw && (raw.requestedMode === "css" || raw.requestedMode === "dtcg") ? raw.requestedMode : void 0;
|
|
11420
11764
|
const source = raw && typeof raw.source === "string" && raw.source.trim() ? raw.source.trim() : "embedded:tokens.ts";
|
|
11421
11765
|
return { source, mode, requestedMode };
|
|
11422
11766
|
}
|
|
@@ -11757,7 +12101,7 @@
|
|
|
11757
12101
|
repo: msg.repo || "",
|
|
11758
12102
|
baseBranch: msg.baseBranch || "main",
|
|
11759
12103
|
tokenPath: msg.tokenPath || "design-tokens/tokens.dtcg.json",
|
|
11760
|
-
tokenSourceMode: msg.tokenSourceMode || "
|
|
12104
|
+
tokenSourceMode: msg.tokenSourceMode || "css",
|
|
11761
12105
|
cssTokenPath: msg.cssTokenPath || "",
|
|
11762
12106
|
syncDtcgOnPush: msg.syncDtcgOnPush === true,
|
|
11763
12107
|
allowNewTokensFromFigma: msg.allowNewTokensFromFigma === true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inkbridge",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.6",
|
|
4
4
|
"description": "Figma plugin that generates a pixel-accurate design system from your Tailwind React components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,10 +42,5 @@
|
|
|
42
42
|
"storybook"
|
|
43
43
|
],
|
|
44
44
|
"license": "MIT",
|
|
45
|
-
"homepage": "https://inkbridge.
|
|
46
|
-
"repository": {
|
|
47
|
-
"type": "git",
|
|
48
|
-
"url": "https://github.com/inkn9ne/inkbridge.git",
|
|
49
|
-
"directory": "tools/figma-plugin-tailwind-tokens"
|
|
50
|
-
}
|
|
45
|
+
"homepage": "https://inkbridge.ink"
|
|
51
46
|
}
|
|
@@ -2457,10 +2457,105 @@ export class ComponentScanner {
|
|
|
2457
2457
|
}
|
|
2458
2458
|
return parts.join(' ').trim();
|
|
2459
2459
|
}
|
|
2460
|
+
// Try to resolve as a CVA variant function call (e.g., alertVariants({ variant }))
|
|
2461
|
+
const cvaResult = this.resolveCvaFunctionCall(expr, propsContext);
|
|
2462
|
+
if (cvaResult !== null) return cvaResult;
|
|
2460
2463
|
}
|
|
2461
2464
|
return '';
|
|
2462
2465
|
}
|
|
2463
2466
|
|
|
2467
|
+
/**
|
|
2468
|
+
* Resolve a CVA variant function call like `alertVariants({ variant })` to its
|
|
2469
|
+
* combined class string. Looks up the function definition in the same source file,
|
|
2470
|
+
* then merges base classes with the appropriate variant classes.
|
|
2471
|
+
*/
|
|
2472
|
+
private resolveCvaFunctionCall(callExpr: Node, propsContext: Map<string, any>): string | null {
|
|
2473
|
+
if (!Node.isCallExpression(callExpr)) return null;
|
|
2474
|
+
const funcName = callExpr.getExpression().getText();
|
|
2475
|
+
const sourceFile = callExpr.getSourceFile();
|
|
2476
|
+
|
|
2477
|
+
// Find `const funcName = cva(...)` in the source file
|
|
2478
|
+
for (const varStmt of sourceFile.getVariableStatements()) {
|
|
2479
|
+
for (const decl of varStmt.getDeclarationList().getDeclarations()) {
|
|
2480
|
+
if (decl.getName() !== funcName) continue;
|
|
2481
|
+
const init = decl.getInitializer();
|
|
2482
|
+
if (!init || !Node.isCallExpression(init)) continue;
|
|
2483
|
+
if (init.getExpression().getText() !== 'cva') continue;
|
|
2484
|
+
|
|
2485
|
+
const cvaArgs = init.getArguments();
|
|
2486
|
+
if (cvaArgs.length === 0) return '';
|
|
2487
|
+
|
|
2488
|
+
const baseClasses = this.extractStringValue(cvaArgs[0]);
|
|
2489
|
+
const classes: string[] = baseClasses ? [baseClasses] : [];
|
|
2490
|
+
|
|
2491
|
+
if (cvaArgs.length >= 2 && Node.isObjectLiteralExpression(cvaArgs[1])) {
|
|
2492
|
+
const configObj = cvaArgs[1];
|
|
2493
|
+
|
|
2494
|
+
// Parse the call argument object e.g. { variant } or { variant: "destructive" }
|
|
2495
|
+
const callArgs = callExpr.getArguments();
|
|
2496
|
+
const requestedVariants: Record<string, string> = {};
|
|
2497
|
+
if (callArgs.length > 0 && Node.isObjectLiteralExpression(callArgs[0])) {
|
|
2498
|
+
for (const prop of callArgs[0].getProperties()) {
|
|
2499
|
+
if (Node.isPropertyAssignment(prop)) {
|
|
2500
|
+
const key = prop.getName();
|
|
2501
|
+
const valInit = prop.getInitializer();
|
|
2502
|
+
if (!valInit) continue;
|
|
2503
|
+
const resolved = this.resolveExpressionValue(valInit, propsContext);
|
|
2504
|
+
if (typeof resolved === 'string') requestedVariants[key] = resolved;
|
|
2505
|
+
else if (Node.isStringLiteral(valInit)) requestedVariants[key] = valInit.getLiteralValue();
|
|
2506
|
+
} else if (Node.isShorthandPropertyAssignment(prop)) {
|
|
2507
|
+
// { variant } shorthand — look up value from propsContext
|
|
2508
|
+
const key = prop.getName();
|
|
2509
|
+
const resolved = propsContext.get(key);
|
|
2510
|
+
if (typeof resolved === 'string') requestedVariants[key] = resolved;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// Extract defaultVariants
|
|
2516
|
+
const defaultVariants: Record<string, string> = {};
|
|
2517
|
+
const defaultVariantsProp = configObj.getProperty('defaultVariants');
|
|
2518
|
+
if (defaultVariantsProp && Node.isPropertyAssignment(defaultVariantsProp)) {
|
|
2519
|
+
const defaultObj = defaultVariantsProp.getInitializer();
|
|
2520
|
+
if (defaultObj && Node.isObjectLiteralExpression(defaultObj)) {
|
|
2521
|
+
for (const prop of defaultObj.getProperties()) {
|
|
2522
|
+
if (!Node.isPropertyAssignment(prop)) continue;
|
|
2523
|
+
const val = this.extractStringValue(prop.getInitializer()!);
|
|
2524
|
+
defaultVariants[prop.getName()] = val.replace(/['"]/g, '');
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
// Look up each variant and add its classes
|
|
2530
|
+
const variantsProp = configObj.getProperty('variants');
|
|
2531
|
+
if (variantsProp && Node.isPropertyAssignment(variantsProp)) {
|
|
2532
|
+
const variantsObj = variantsProp.getInitializer();
|
|
2533
|
+
if (variantsObj && Node.isObjectLiteralExpression(variantsObj)) {
|
|
2534
|
+
for (const variantProp of variantsObj.getProperties()) {
|
|
2535
|
+
if (!Node.isPropertyAssignment(variantProp)) continue;
|
|
2536
|
+
const variantName = variantProp.getName();
|
|
2537
|
+
const selectedValue = requestedVariants[variantName] ?? defaultVariants[variantName];
|
|
2538
|
+
if (!selectedValue) continue;
|
|
2539
|
+
|
|
2540
|
+
const variantValuesObj = variantProp.getInitializer();
|
|
2541
|
+
if (!variantValuesObj || !Node.isObjectLiteralExpression(variantValuesObj)) continue;
|
|
2542
|
+
|
|
2543
|
+
const matchedProp = variantValuesObj.getProperty(selectedValue);
|
|
2544
|
+
if (matchedProp && Node.isPropertyAssignment(matchedProp)) {
|
|
2545
|
+
const variantClassStr = this.extractStringValue(matchedProp.getInitializer()!);
|
|
2546
|
+
if (variantClassStr) classes.push(variantClassStr);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
return classes.filter(Boolean).join(' ').trim();
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
return null;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2464
2559
|
/**
|
|
2465
2560
|
* Extract props from a JSX node (opening element or self-closing element).
|
|
2466
2561
|
* Uses getDescendantsOfKind to safely get only JsxAttribute nodes,
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
} from '../src/token-source';
|
|
10
10
|
|
|
11
11
|
const CSS_DISCOVERY_PATHS = [
|
|
12
|
-
'src/app/tokens.css',
|
|
13
12
|
'src/app/globals.css',
|
|
14
13
|
'app/globals.css',
|
|
15
14
|
'styles/globals.css',
|
|
15
|
+
'src/app/tokens.css',
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
const DEFAULT_DTCG_PATH = 'design-tokens/tokens.dtcg.json';
|
|
@@ -84,6 +84,10 @@ function resolveImportedCssPath(baseFilePath: string, params: string): string |
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function cssHasTokenDeclarations(cssText: string): boolean {
|
|
87
|
+
// Only count declarations inside :root {} or .[theme] {} rules — the same
|
|
88
|
+
// selectors that patchCssVariables targets. We intentionally skip @theme
|
|
89
|
+
// at-rules (Tailwind v4 utility mappings) because those contain var()
|
|
90
|
+
// references, not source values, and the patcher cannot update them.
|
|
87
91
|
try {
|
|
88
92
|
const root = postcss.parse(cssText);
|
|
89
93
|
let found = false;
|
|
@@ -91,16 +95,9 @@ function cssHasTokenDeclarations(cssText: string): boolean {
|
|
|
91
95
|
if (found) return;
|
|
92
96
|
if (!decl.prop || !decl.prop.startsWith('--')) return;
|
|
93
97
|
const parent = decl.parent;
|
|
94
|
-
if (!parent) return;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if ((at.name || '').toLowerCase() === 'theme') found = true;
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (parent.type === 'rule') {
|
|
101
|
-
const selector = (parent as Rule).selector || '';
|
|
102
|
-
if (parseThemeSelectors(selector).length > 0) found = true;
|
|
103
|
-
}
|
|
98
|
+
if (!parent || parent.type !== 'rule') return;
|
|
99
|
+
const selector = (parent as Rule).selector || '';
|
|
100
|
+
if (parseThemeSelectors(selector).length > 0) found = true;
|
|
104
101
|
});
|
|
105
102
|
return found;
|
|
106
103
|
} catch {
|
|
@@ -108,7 +105,7 @@ function cssHasTokenDeclarations(cssText: string): boolean {
|
|
|
108
105
|
}
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
function resolveCssTokenPathFromImports(filePath: string, visited: Set<string> = new Set()): string {
|
|
108
|
+
export function resolveCssTokenPathFromImports(filePath: string, visited: Set<string> = new Set()): string {
|
|
112
109
|
const absolute = path.resolve(filePath);
|
|
113
110
|
if (visited.has(absolute)) return absolute;
|
|
114
111
|
visited.add(absolute);
|
|
@@ -179,12 +176,11 @@ function readCssWithImports(filePath: string, visited: Set<string> = new Set()):
|
|
|
179
176
|
|
|
180
177
|
export function discoverCssTokenPath(projectRoot: string, explicitPath?: string): string | null {
|
|
181
178
|
if (explicitPath && explicitPath.trim()) {
|
|
182
|
-
|
|
183
|
-
return explicit ? resolveCssTokenPathFromImports(explicit) : null;
|
|
179
|
+
return discoverFilePath(projectRoot, explicitPath.trim());
|
|
184
180
|
}
|
|
185
181
|
for (const rel of CSS_DISCOVERY_PATHS) {
|
|
186
182
|
const found = discoverFilePath(projectRoot, rel);
|
|
187
|
-
if (found) return
|
|
183
|
+
if (found) return found;
|
|
188
184
|
}
|
|
189
185
|
return null;
|
|
190
186
|
}
|
|
@@ -384,7 +380,7 @@ function walkCssNodes(map: ScannedTokenMap, container: postcss.Container): void
|
|
|
384
380
|
}
|
|
385
381
|
}
|
|
386
382
|
|
|
387
|
-
export function parseCssTokenMap(cssText: string, source: string, requestedMode: TokenSourceMode = '
|
|
383
|
+
export function parseCssTokenMap(cssText: string, source: string, requestedMode: TokenSourceMode = 'css'): ScannedTokenMap {
|
|
388
384
|
const map = createEmptyScannedTokenMap('css', source, requestedMode);
|
|
389
385
|
const root = postcss.parse(cssText);
|
|
390
386
|
walkCssNodes(map, root);
|
|
@@ -425,7 +421,7 @@ function applyDtcgDimensionGroup(
|
|
|
425
421
|
function parseDtcgTokenMap(
|
|
426
422
|
dtcgJson: unknown,
|
|
427
423
|
source: string,
|
|
428
|
-
requestedMode: TokenSourceMode = '
|
|
424
|
+
requestedMode: TokenSourceMode = 'dtcg'
|
|
429
425
|
): ScannedTokenMap {
|
|
430
426
|
const map = createEmptyScannedTokenMap('dtcg', source, requestedMode);
|
|
431
427
|
if (!isPlainObject(dtcgJson)) return map;
|
|
@@ -453,16 +449,10 @@ function parseDtcgTokenMap(
|
|
|
453
449
|
|
|
454
450
|
export function readTokenSourceMap(options: ReadTokenSourceOptions): ScannedTokenMap {
|
|
455
451
|
const projectRoot = path.resolve(options.projectRoot);
|
|
456
|
-
const requestedMode = options.tokenSourceMode
|
|
452
|
+
const requestedMode: TokenSourceMode = options.tokenSourceMode === 'dtcg' ? 'dtcg' : 'css';
|
|
457
453
|
const cssPath = discoverCssTokenPath(projectRoot, options.cssTokenPath);
|
|
458
454
|
const dtcgPath = discoverDtcgTokenPath(projectRoot, options.dtcgTokenPath);
|
|
459
455
|
|
|
460
|
-
if (requestedMode === 'css') {
|
|
461
|
-
if (!cssPath) return createEmptyScannedTokenMap('embedded', 'embedded:tokens.ts', requestedMode);
|
|
462
|
-
const cssText = readCssWithImports(cssPath);
|
|
463
|
-
return parseCssTokenMap(cssText, toDisplayPath(projectRoot, cssPath), requestedMode);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
456
|
if (requestedMode === 'dtcg') {
|
|
467
457
|
if (!dtcgPath) return createEmptyScannedTokenMap('embedded', 'embedded:tokens.ts', requestedMode);
|
|
468
458
|
const dtcgText = fs.readFileSync(dtcgPath, 'utf-8');
|
|
@@ -470,10 +460,14 @@ export function readTokenSourceMap(options: ReadTokenSourceOptions): ScannedToke
|
|
|
470
460
|
return parseDtcgTokenMap(dtcgJson, toDisplayPath(projectRoot, dtcgPath), requestedMode);
|
|
471
461
|
}
|
|
472
462
|
|
|
473
|
-
//
|
|
463
|
+
// css mode: CSS preferred; fall back to DTCG if no CSS file found, then embedded.
|
|
474
464
|
if (cssPath) {
|
|
475
465
|
const cssText = readCssWithImports(cssPath);
|
|
476
|
-
|
|
466
|
+
// Use the file that actually contains the declarations as source so the
|
|
467
|
+
// write-back path (PR/patch) targets the right file even when globals.css
|
|
468
|
+
// delegates token definitions to an imported file like tokens.css.
|
|
469
|
+
const writeTarget = resolveCssTokenPathFromImports(cssPath);
|
|
470
|
+
return parseCssTokenMap(cssText, toDisplayPath(projectRoot, writeTarget), requestedMode);
|
|
477
471
|
}
|
|
478
472
|
if (dtcgPath) {
|
|
479
473
|
const dtcgText = fs.readFileSync(dtcgPath, 'utf-8');
|
package/src/token-source.ts
CHANGED
package/ui.html
CHANGED
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
</div>
|
|
206
206
|
<div id="tokenSourceInfoPush" class="repo-display" style="display:none;padding:6px 8px;">
|
|
207
207
|
Last scan source: <strong id="tokenSourceLabelPush"></strong><br>
|
|
208
|
-
Configured mode: <strong id="configuredTokenSourceModePush">
|
|
208
|
+
Configured mode: <strong id="configuredTokenSourceModePush">css</strong>
|
|
209
209
|
</div>
|
|
210
210
|
|
|
211
211
|
<div class="field">
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
|
|
251
251
|
<div id="tokenSourceInfoSettings" class="repo-display" style="display:none;padding:6px 8px;">
|
|
252
252
|
Last scan source: <strong id="tokenSourceLabelSettings"></strong><br>
|
|
253
|
-
Configured mode: <strong id="configuredTokenSourceModeSettings">
|
|
253
|
+
Configured mode: <strong id="configuredTokenSourceModeSettings">css</strong>
|
|
254
254
|
</div>
|
|
255
255
|
|
|
256
256
|
<div class="field">
|
|
@@ -271,11 +271,10 @@
|
|
|
271
271
|
<div class="field">
|
|
272
272
|
<label>Token Source Mode</label>
|
|
273
273
|
<select id="settingsTokenSourceMode">
|
|
274
|
-
<option value="
|
|
275
|
-
<option value="
|
|
276
|
-
<option value="dtcg">dtcg (force DTCG only)</option>
|
|
274
|
+
<option value="css">css (auto-discover CSS, fallback to DTCG)</option>
|
|
275
|
+
<option value="dtcg">dtcg (force DTCG file only)</option>
|
|
277
276
|
</select>
|
|
278
|
-
<p class="hint"
|
|
277
|
+
<p class="hint">CSS mode auto-discovers your globals.css and falls back to DTCG if not found. Use DTCG to force the legacy token file.</p>
|
|
279
278
|
</div>
|
|
280
279
|
|
|
281
280
|
<div class="field" id="tokenPathField">
|
|
@@ -291,25 +290,25 @@
|
|
|
291
290
|
</div>
|
|
292
291
|
|
|
293
292
|
<div class="field" id="syncDtcgOnPushField">
|
|
294
|
-
<
|
|
295
|
-
<input type="checkbox" id="settingsSyncDtcgOnPush">
|
|
296
|
-
Also update DTCG on Push to Code
|
|
297
|
-
</
|
|
293
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
294
|
+
<input type="checkbox" id="settingsSyncDtcgOnPush" style="width:auto;flex-shrink:0;">
|
|
295
|
+
<label for="settingsSyncDtcgOnPush" style="display:inline;margin:0;cursor:pointer;">Also update DTCG on Push to Code</label>
|
|
296
|
+
</div>
|
|
298
297
|
<p class="hint">When enabled, the plugin also commits <code>tokens.dtcg.json</code> as a generated artifact.</p>
|
|
299
298
|
</div>
|
|
300
299
|
|
|
301
300
|
<div class="field">
|
|
302
|
-
<
|
|
303
|
-
<input type="checkbox" id="settingsAllowNewTokensFromFigma">
|
|
304
|
-
Allow New Tokens from Figma
|
|
305
|
-
</
|
|
301
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
302
|
+
<input type="checkbox" id="settingsAllowNewTokensFromFigma" style="width:auto;flex-shrink:0;">
|
|
303
|
+
<label for="settingsAllowNewTokensFromFigma" style="display:inline;margin:0;cursor:pointer;">Allow New Tokens from Figma</label>
|
|
304
|
+
</div>
|
|
306
305
|
<p class="hint">Disabled by default. When enabled, new token keys can be added to code on push.</p>
|
|
307
306
|
</div>
|
|
308
307
|
|
|
309
|
-
<div class="field">
|
|
308
|
+
<div class="field" id="newTokenPrefixesField" style="display:none;">
|
|
310
309
|
<label>New Token Prefixes (optional)</label>
|
|
311
310
|
<input type="text" id="settingsNewTokenPrefixes" placeholder="chart-, brand-">
|
|
312
|
-
<p class="hint">Comma-separated.
|
|
311
|
+
<p class="hint">Comma-separated. Only tokens with these prefixes can be added. Leave empty to allow all new tokens.</p>
|
|
313
312
|
</div>
|
|
314
313
|
|
|
315
314
|
<div class="divider"></div>
|
|
@@ -410,7 +409,7 @@
|
|
|
410
409
|
</div>
|
|
411
410
|
<div id="tokenSourceInfoSync" class="repo-display" style="display:none;padding:6px 8px;">
|
|
412
411
|
Last scan source: <strong id="tokenSourceLabelSync"></strong><br>
|
|
413
|
-
Configured mode: <strong id="configuredTokenSourceModeSync">
|
|
412
|
+
Configured mode: <strong id="configuredTokenSourceModeSync">css</strong>
|
|
414
413
|
</div>
|
|
415
414
|
|
|
416
415
|
<p style="font-size: 11px; color: #666; margin-bottom: 12px;">
|
|
@@ -531,6 +530,7 @@
|
|
|
531
530
|
var syncDtcgOnPushField = document.getElementById('syncDtcgOnPushField');
|
|
532
531
|
var settingsAllowNewTokensFromFigma = document.getElementById('settingsAllowNewTokensFromFigma');
|
|
533
532
|
var settingsNewTokenPrefixes = document.getElementById('settingsNewTokenPrefixes');
|
|
533
|
+
var newTokenPrefixesField = document.getElementById('newTokenPrefixesField');
|
|
534
534
|
var settingsToken = document.getElementById('settingsToken');
|
|
535
535
|
var settingsProjectName = document.getElementById('settingsProjectName');
|
|
536
536
|
var settingsLicenseKey = document.getElementById('settingsLicenseKey');
|
|
@@ -538,7 +538,7 @@
|
|
|
538
538
|
// Current detected changes
|
|
539
539
|
var detectedChanges = { tokens: true, components: [] };
|
|
540
540
|
var lastTokenSourceInfo = null;
|
|
541
|
-
var configuredTokenSourceMode = '
|
|
541
|
+
var configuredTokenSourceMode = 'css';
|
|
542
542
|
|
|
543
543
|
function resizeToContent() {
|
|
544
544
|
// Wait for layout to settle before measuring
|
|
@@ -580,25 +580,20 @@
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
function normalizeConfiguredMode(mode) {
|
|
583
|
-
if (mode === '
|
|
584
|
-
return '
|
|
583
|
+
if (mode === 'dtcg') return 'dtcg';
|
|
584
|
+
return 'css';
|
|
585
585
|
}
|
|
586
586
|
|
|
587
587
|
function applyTokenSourceModeVisibility(mode) {
|
|
588
588
|
var normalized = normalizeConfiguredMode(mode);
|
|
589
|
-
if (normalized === 'css') {
|
|
590
|
-
tokenPathField.style.display = 'none';
|
|
591
|
-
cssTokenPathField.style.display = 'block';
|
|
592
|
-
syncDtcgOnPushField.style.display = 'block';
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
589
|
if (normalized === 'dtcg') {
|
|
596
590
|
tokenPathField.style.display = 'block';
|
|
597
591
|
cssTokenPathField.style.display = 'none';
|
|
598
592
|
syncDtcgOnPushField.style.display = 'none';
|
|
599
593
|
return;
|
|
600
594
|
}
|
|
601
|
-
|
|
595
|
+
// css mode
|
|
596
|
+
tokenPathField.style.display = 'none';
|
|
602
597
|
cssTokenPathField.style.display = 'block';
|
|
603
598
|
syncDtcgOnPushField.style.display = 'block';
|
|
604
599
|
}
|
|
@@ -980,7 +975,8 @@
|
|
|
980
975
|
settingsCssTokenPath.value = config.cssTokenPath || '';
|
|
981
976
|
settingsSyncDtcgOnPush.checked = config.syncDtcgOnPush === true;
|
|
982
977
|
settingsAllowNewTokensFromFigma.checked = config.allowNewTokensFromFigma === true;
|
|
983
|
-
settingsNewTokenPrefixes.value = Array.isArray(config.newTokenPrefixes) ? config.newTokenPrefixes.join(', ') : '';
|
|
978
|
+
settingsNewTokenPrefixes.value = Array.isArray(config.newTokenPrefixes) ? config.newTokenPrefixes.join(', ') : (typeof config.newTokenPrefixes === 'string' ? config.newTokenPrefixes : '');
|
|
979
|
+
newTokenPrefixesField.style.display = config.allowNewTokensFromFigma === true ? 'block' : 'none';
|
|
984
980
|
settingsProjectName.value = config.projectName || '';
|
|
985
981
|
configuredTokenSourceMode = tokenSourceMode;
|
|
986
982
|
applyTokenSourceModeVisibility(tokenSourceMode);
|
|
@@ -1197,7 +1193,7 @@
|
|
|
1197
1193
|
repo: repo,
|
|
1198
1194
|
baseBranch: settingsBranch.value.trim() || 'main',
|
|
1199
1195
|
tokenPath: settingsTokenPath.value.trim() || 'design-tokens/tokens.dtcg.json',
|
|
1200
|
-
tokenSourceMode: settingsTokenSourceMode.value || '
|
|
1196
|
+
tokenSourceMode: settingsTokenSourceMode.value || 'css',
|
|
1201
1197
|
cssTokenPath: settingsCssTokenPath.value.trim(),
|
|
1202
1198
|
syncDtcgOnPush: settingsSyncDtcgOnPush.checked === true,
|
|
1203
1199
|
allowNewTokensFromFigma: settingsAllowNewTokensFromFigma.checked === true,
|
|
@@ -1217,6 +1213,10 @@
|
|
|
1217
1213
|
applyTokenSourceModeVisibility(configuredTokenSourceMode);
|
|
1218
1214
|
setTokenSourceInfo(lastTokenSourceInfo);
|
|
1219
1215
|
};
|
|
1216
|
+
|
|
1217
|
+
settingsAllowNewTokensFromFigma.onchange = function() {
|
|
1218
|
+
newTokenPrefixesField.style.display = settingsAllowNewTokensFromFigma.checked ? 'block' : 'none';
|
|
1219
|
+
};
|
|
1220
1220
|
</script>
|
|
1221
1221
|
</body>
|
|
1222
1222
|
</html>
|