Spaces:
Runtime error
fix: typography overlap + semantic colors on separate rows
Browse filesTypography:
- Set textAutoResize='HEIGHT' and resize BEFORE setting characters
so Figma calculates actual wrapped text height
- Row height now uses actual sample.height instead of just fontSize
- Eliminates all text overlapping between typography rows
Colors β semantic categories (brand, text, bg, border, feedback):
- Sub-grouped by 2nd path segment (primary, secondary, error, info, etc.)
- Each sub-group gets its own row with label on the left
- e.g. brand: "primary" row with DEFAULT + 50/200/800/950 variants
feedback: "error" row, "info" row, "warning" row
Colors β palette hues (blue, green, neutral):
- Unchanged β horizontal grid layout for 50-900 ramp
Frames auto-resize to actual content height (no more estimates).
Copied to v2 directory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@@ -915,14 +915,9 @@ figma.ui.onmessage = async function(msg) {
|
|
| 915 |
for (var so = 0; so < semOrder.length; so++) { if (colorGroups[semOrder[so]]) sortedGroups.push(semOrder[so]); }
|
| 916 |
for (var go = 0; go < groupOrder.length; go++) { if (sortedGroups.indexOf(groupOrder[go]) === -1) sortedGroups.push(groupOrder[go]); }
|
| 917 |
|
| 918 |
-
// Calculate height
|
| 919 |
-
var colorsPerRow = Math.floor((CONTENT_W + SWATCH_GAP) / (SWATCH_W + SWATCH_GAP));
|
| 920 |
-
var totalColorH =
|
| 921 |
-
for (var cgi = 0; cgi < sortedGroups.length; cgi++) {
|
| 922 |
-
var rows = Math.ceil(colorGroups[sortedGroups[cgi]].length / colorsPerRow);
|
| 923 |
-
totalColorH += 40 + rows * (SWATCH_H_COLOR + SWATCH_META_H + ITEM_GAP) + 16;
|
| 924 |
-
}
|
| 925 |
-
totalColorH += SECTION_PAD;
|
| 926 |
|
| 927 |
var colorFrame = createSectionFrame('Colors', totalColorH);
|
| 928 |
var cy = SECTION_PAD;
|
|
@@ -931,38 +926,22 @@ figma.ui.onmessage = async function(msg) {
|
|
| 931 |
addText(colorFrame, 'COLORS', boldFont, 32, MARGIN, cy, DARK);
|
| 932 |
cy += 48;
|
| 933 |
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
var gColors = colorGroups[gName];
|
| 937 |
-
|
| 938 |
-
// Group heading
|
| 939 |
-
addText(colorFrame, gName.charAt(0).toUpperCase() + gName.slice(1), boldFont, 18, MARGIN, cy, DARK);
|
| 940 |
-
cy += 36;
|
| 941 |
-
|
| 942 |
-
for (var ci2 = 0; ci2 < gColors.length; ci2++) {
|
| 943 |
-
var ct = gColors[ci2];
|
| 944 |
-
var col = ci2 % colorsPerRow;
|
| 945 |
-
var row = Math.floor(ci2 / colorsPerRow);
|
| 946 |
-
var sx = MARGIN + col * (SWATCH_W + SWATCH_GAP);
|
| 947 |
-
var sy = cy + row * (SWATCH_H_COLOR + SWATCH_META_H + ITEM_GAP);
|
| 948 |
-
|
| 949 |
-
// Color fill rectangle (top part of card)
|
| 950 |
var swatch = figma.createRectangle();
|
| 951 |
swatch.resize(SWATCH_W, SWATCH_H_COLOR);
|
| 952 |
swatch.x = sx; swatch.y = sy;
|
| 953 |
-
|
| 954 |
-
swatch.topLeftRadius = tl; swatch.topRightRadius = tr;
|
| 955 |
-
swatch.bottomLeftRadius = bl; swatch.bottomRightRadius = br;
|
| 956 |
swatch.fills = [{ type: 'SOLID', color: hexToRgb(ct.value) }];
|
| 957 |
-
|
| 958 |
|
| 959 |
-
// AA badge
|
| 960 |
var aa = getAAResult(ct.value);
|
| 961 |
-
var badgeText = aa.passAA ? 'AA β
|
| 962 |
var badgeColor = aa.bestOn === 'white' ? { r: 1, g: 1, b: 1 } : { r: 0, g: 0, b: 0 };
|
| 963 |
-
addText(
|
| 964 |
|
| 965 |
-
// Metadata area
|
| 966 |
var metaBg = figma.createRectangle();
|
| 967 |
metaBg.resize(SWATCH_W, SWATCH_META_H);
|
| 968 |
metaBg.x = sx; metaBg.y = sy + SWATCH_H_COLOR;
|
|
@@ -970,25 +949,78 @@ figma.ui.onmessage = async function(msg) {
|
|
| 970 |
metaBg.fills = [{ type: 'SOLID', color: { r: 0.97, g: 0.97, b: 0.98 } }];
|
| 971 |
metaBg.strokes = [{ type: 'SOLID', color: { r: 0.9, g: 0.9, b: 0.9 } }];
|
| 972 |
metaBg.strokeWeight = 1;
|
| 973 |
-
|
| 974 |
|
| 975 |
-
// Token name
|
| 976 |
var nameParts = ct.name.split('/');
|
| 977 |
var displayName = nameParts.filter(function(p) { return p !== 'DEFAULT'; }).join('/');
|
| 978 |
-
addText(
|
|
|
|
| 979 |
|
| 980 |
-
// Hex value
|
| 981 |
-
addText(colorFrame, ct.value.toUpperCase(), labelFont, 11, sx + 10, sy + SWATCH_H_COLOR + 26, MUTED);
|
| 982 |
-
|
| 983 |
-
// Contrast info
|
| 984 |
var contrastText = aa.passAA ? 'AA Pass on ' + aa.bestOn : 'AA Fail (' + aa.ratio + ':1)';
|
| 985 |
var contrastColor = aa.passAA ? { r: 0.13, g: 0.55, b: 0.13 } : { r: 0.85, g: 0.18, b: 0.18 };
|
| 986 |
-
addText(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 987 |
}
|
| 988 |
|
| 989 |
-
|
| 990 |
-
cy += gRows * (SWATCH_H_COLOR + SWATCH_META_H + ITEM_GAP) + 16;
|
| 991 |
}
|
|
|
|
|
|
|
|
|
|
| 992 |
}
|
| 993 |
|
| 994 |
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -1017,13 +1049,8 @@ figma.ui.onmessage = async function(msg) {
|
|
| 1017 |
function renderTypoFrame(typoList, frameName) {
|
| 1018 |
if (typoList.length === 0) return;
|
| 1019 |
|
| 1020 |
-
//
|
| 1021 |
-
var estH =
|
| 1022 |
-
for (var i = 0; i < typoList.length; i++) {
|
| 1023 |
-
var fs = parseNumericValue(typoList[i].value.fontSize) || 16;
|
| 1024 |
-
estH += Math.max(Math.min(fs, 56), 24) + 64; // sample + labels + gap
|
| 1025 |
-
}
|
| 1026 |
-
estH += SECTION_PAD;
|
| 1027 |
|
| 1028 |
var frame = createSectionFrame(frameName, estH);
|
| 1029 |
var fy = SECTION_PAD;
|
|
@@ -1062,17 +1089,21 @@ figma.ui.onmessage = async function(msg) {
|
|
| 1062 |
var tierName = tierParts.filter(function(p) { return p !== 'desktop' && p !== 'mobile'; }).join('.');
|
| 1063 |
addText(frame, tierName, boldFont, 13, COL_NAME, fy + 4, DARK);
|
| 1064 |
|
| 1065 |
-
// Sample text in actual font
|
| 1066 |
var useBold = (tierName.indexOf('display') > -1 || tierName.indexOf('heading') > -1);
|
| 1067 |
var sample = figma.createText();
|
| 1068 |
sample.fontName = useBold ? sampleFontBold : sampleFont;
|
| 1069 |
sample.fontSize = displaySize;
|
|
|
|
|
|
|
|
|
|
| 1070 |
sample.characters = getSampleText(tt.name);
|
| 1071 |
sample.x = COL_SAMPLE; sample.y = fy;
|
| 1072 |
-
sample.resize(540, sample.height);
|
| 1073 |
-
sample.textAutoResize = 'HEIGHT';
|
| 1074 |
frame.appendChild(sample);
|
| 1075 |
|
|
|
|
|
|
|
|
|
|
| 1076 |
// Specs column β stacked chips
|
| 1077 |
var specY = fy;
|
| 1078 |
addText(frame, 'Size: ' + fSize + 'px', boldFont, 12, COL_SPECS, specY, DARK);
|
|
@@ -1083,7 +1114,9 @@ figma.ui.onmessage = async function(msg) {
|
|
| 1083 |
specY += 18;
|
| 1084 |
addText(frame, 'Font: ' + fFamily, labelFont, 12, COL_SPECS, specY, BLUE);
|
| 1085 |
|
| 1086 |
-
|
|
|
|
|
|
|
| 1087 |
fy += rowH + 8;
|
| 1088 |
|
| 1089 |
// Row separator
|
|
|
|
| 915 |
for (var so = 0; so < semOrder.length; so++) { if (colorGroups[semOrder[so]]) sortedGroups.push(semOrder[so]); }
|
| 916 |
for (var go = 0; go < groupOrder.length; go++) { if (sortedGroups.indexOf(groupOrder[go]) === -1) sortedGroups.push(groupOrder[go]); }
|
| 917 |
|
| 918 |
+
// Calculate initial height estimate (will be resized at end)
|
| 919 |
+
var colorsPerRow = Math.floor((CONTENT_W - 140 + SWATCH_GAP) / (SWATCH_W + SWATCH_GAP));
|
| 920 |
+
var totalColorH = 2000; // generous initial estimate, resized later
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 921 |
|
| 922 |
var colorFrame = createSectionFrame('Colors', totalColorH);
|
| 923 |
var cy = SECTION_PAD;
|
|
|
|
| 926 |
addText(colorFrame, 'COLORS', boldFont, 32, MARGIN, cy, DARK);
|
| 927 |
cy += 48;
|
| 928 |
|
| 929 |
+
// Helper to render a single color card at position
|
| 930 |
+
function renderColorCard(frame, ct, sx, sy) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 931 |
var swatch = figma.createRectangle();
|
| 932 |
swatch.resize(SWATCH_W, SWATCH_H_COLOR);
|
| 933 |
swatch.x = sx; swatch.y = sy;
|
| 934 |
+
swatch.topLeftRadius = 8; swatch.topRightRadius = 8;
|
|
|
|
|
|
|
| 935 |
swatch.fills = [{ type: 'SOLID', color: hexToRgb(ct.value) }];
|
| 936 |
+
frame.appendChild(swatch);
|
| 937 |
|
| 938 |
+
// AA badge
|
| 939 |
var aa = getAAResult(ct.value);
|
| 940 |
+
var badgeText = aa.passAA ? 'AA β ' + aa.ratio + ':1' : 'AA β ' + aa.ratio + ':1';
|
| 941 |
var badgeColor = aa.bestOn === 'white' ? { r: 1, g: 1, b: 1 } : { r: 0, g: 0, b: 0 };
|
| 942 |
+
addText(frame, badgeText, labelFont, 10, sx + 8, sy + SWATCH_H_COLOR - 18, badgeColor);
|
| 943 |
|
| 944 |
+
// Metadata area
|
| 945 |
var metaBg = figma.createRectangle();
|
| 946 |
metaBg.resize(SWATCH_W, SWATCH_META_H);
|
| 947 |
metaBg.x = sx; metaBg.y = sy + SWATCH_H_COLOR;
|
|
|
|
| 949 |
metaBg.fills = [{ type: 'SOLID', color: { r: 0.97, g: 0.97, b: 0.98 } }];
|
| 950 |
metaBg.strokes = [{ type: 'SOLID', color: { r: 0.9, g: 0.9, b: 0.9 } }];
|
| 951 |
metaBg.strokeWeight = 1;
|
| 952 |
+
frame.appendChild(metaBg);
|
| 953 |
|
|
|
|
| 954 |
var nameParts = ct.name.split('/');
|
| 955 |
var displayName = nameParts.filter(function(p) { return p !== 'DEFAULT'; }).join('/');
|
| 956 |
+
addText(frame, displayName, boldFont, 11, sx + 10, sy + SWATCH_H_COLOR + 8, DARK);
|
| 957 |
+
addText(frame, ct.value.toUpperCase(), labelFont, 11, sx + 10, sy + SWATCH_H_COLOR + 26, MUTED);
|
| 958 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 959 |
var contrastText = aa.passAA ? 'AA Pass on ' + aa.bestOn : 'AA Fail (' + aa.ratio + ':1)';
|
| 960 |
var contrastColor = aa.passAA ? { r: 0.13, g: 0.55, b: 0.13 } : { r: 0.85, g: 0.18, b: 0.18 };
|
| 961 |
+
addText(frame, contrastText, labelFont, 10, sx + 10, sy + SWATCH_H_COLOR + 44, contrastColor);
|
| 962 |
+
}
|
| 963 |
+
|
| 964 |
+
var isSemantic = { 'brand': 1, 'text': 1, 'bg': 1, 'background': 1, 'border': 1, 'feedback': 1 };
|
| 965 |
+
var CARD_H = SWATCH_H_COLOR + SWATCH_META_H + ITEM_GAP;
|
| 966 |
+
|
| 967 |
+
for (var gi = 0; gi < sortedGroups.length; gi++) {
|
| 968 |
+
var gName = sortedGroups[gi];
|
| 969 |
+
var gColors = colorGroups[gName];
|
| 970 |
+
|
| 971 |
+
// Group heading
|
| 972 |
+
addText(colorFrame, gName.charAt(0).toUpperCase() + gName.slice(1), boldFont, 18, MARGIN, cy, DARK);
|
| 973 |
+
cy += 36;
|
| 974 |
+
|
| 975 |
+
if (isSemantic[gName]) {
|
| 976 |
+
// ββ SEMANTIC LAYOUT: sub-group by 2nd path segment, each on own row ββ
|
| 977 |
+
// e.g. brand/primary/DEFAULT, brand/primary/50 β sub-group "primary"
|
| 978 |
+
// feedback/error/DEFAULT, feedback/info/DEFAULT β sub-groups "error", "info"
|
| 979 |
+
var subGroups = {};
|
| 980 |
+
var subOrder = [];
|
| 981 |
+
for (var si = 0; si < gColors.length; si++) {
|
| 982 |
+
var sp = gColors[si].name.split('/');
|
| 983 |
+
var subKey = sp.length > 1 ? sp[1] : sp[0];
|
| 984 |
+
if (!subGroups[subKey]) { subGroups[subKey] = []; subOrder.push(subKey); }
|
| 985 |
+
subGroups[subKey].push(gColors[si]);
|
| 986 |
+
}
|
| 987 |
+
|
| 988 |
+
for (var ski = 0; ski < subOrder.length; ski++) {
|
| 989 |
+
var subName = subOrder[ski];
|
| 990 |
+
var subColors = subGroups[subName];
|
| 991 |
+
|
| 992 |
+
// Row label on the left
|
| 993 |
+
addText(colorFrame, subName, boldFont, 13, MARGIN, cy + 30, MUTED);
|
| 994 |
+
|
| 995 |
+
// Swatches start after label
|
| 996 |
+
var labelW = 140;
|
| 997 |
+
for (var sci = 0; sci < subColors.length; sci++) {
|
| 998 |
+
var scx = MARGIN + labelW + sci * (SWATCH_W + SWATCH_GAP);
|
| 999 |
+
renderColorCard(colorFrame, subColors[sci], scx, cy);
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
cy += CARD_H + 8;
|
| 1003 |
+
}
|
| 1004 |
+
} else {
|
| 1005 |
+
// ββ PALETTE LAYOUT: horizontal grid (50-900 ramp) ββ
|
| 1006 |
+
for (var ci2 = 0; ci2 < gColors.length; ci2++) {
|
| 1007 |
+
var ct = gColors[ci2];
|
| 1008 |
+
var col = ci2 % colorsPerRow;
|
| 1009 |
+
var row = Math.floor(ci2 / colorsPerRow);
|
| 1010 |
+
var sx = MARGIN + col * (SWATCH_W + SWATCH_GAP);
|
| 1011 |
+
var sy = cy + row * CARD_H;
|
| 1012 |
+
renderColorCard(colorFrame, ct, sx, sy);
|
| 1013 |
+
}
|
| 1014 |
+
|
| 1015 |
+
var gRows = Math.ceil(gColors.length / colorsPerRow);
|
| 1016 |
+
cy += gRows * CARD_H + 16;
|
| 1017 |
}
|
| 1018 |
|
| 1019 |
+
cy += 8; // gap between groups
|
|
|
|
| 1020 |
}
|
| 1021 |
+
|
| 1022 |
+
// Resize color frame to actual content
|
| 1023 |
+
colorFrame.resize(FRAME_W, cy + SECTION_PAD);
|
| 1024 |
}
|
| 1025 |
|
| 1026 |
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 1049 |
function renderTypoFrame(typoList, frameName) {
|
| 1050 |
if (typoList.length === 0) return;
|
| 1051 |
|
| 1052 |
+
// Use generous initial height β will be resized to actual content
|
| 1053 |
+
var estH = 3000;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1054 |
|
| 1055 |
var frame = createSectionFrame(frameName, estH);
|
| 1056 |
var fy = SECTION_PAD;
|
|
|
|
| 1089 |
var tierName = tierParts.filter(function(p) { return p !== 'desktop' && p !== 'mobile'; }).join('.');
|
| 1090 |
addText(frame, tierName, boldFont, 13, COL_NAME, fy + 4, DARK);
|
| 1091 |
|
| 1092 |
+
// Sample text in actual font β use fixed width so it wraps, then measure height
|
| 1093 |
var useBold = (tierName.indexOf('display') > -1 || tierName.indexOf('heading') > -1);
|
| 1094 |
var sample = figma.createText();
|
| 1095 |
sample.fontName = useBold ? sampleFontBold : sampleFont;
|
| 1096 |
sample.fontSize = displaySize;
|
| 1097 |
+
// Set fixed width BEFORE setting characters so text wraps properly
|
| 1098 |
+
sample.textAutoResize = 'HEIGHT';
|
| 1099 |
+
sample.resize(540, displaySize * 2);
|
| 1100 |
sample.characters = getSampleText(tt.name);
|
| 1101 |
sample.x = COL_SAMPLE; sample.y = fy;
|
|
|
|
|
|
|
| 1102 |
frame.appendChild(sample);
|
| 1103 |
|
| 1104 |
+
// Actual sample height after text rendering
|
| 1105 |
+
var sampleH = sample.height;
|
| 1106 |
+
|
| 1107 |
// Specs column β stacked chips
|
| 1108 |
var specY = fy;
|
| 1109 |
addText(frame, 'Size: ' + fSize + 'px', boldFont, 12, COL_SPECS, specY, DARK);
|
|
|
|
| 1114 |
specY += 18;
|
| 1115 |
addText(frame, 'Font: ' + fFamily, labelFont, 12, COL_SPECS, specY, BLUE);
|
| 1116 |
|
| 1117 |
+
// Row height = max of (sample text height, spec labels height, minimum 40px)
|
| 1118 |
+
var specsH = specY - fy + 24;
|
| 1119 |
+
var rowH = Math.max(sampleH + 12, specsH, 40);
|
| 1120 |
fy += rowH + 8;
|
| 1121 |
|
| 1122 |
// Row separator
|