@vettvangur/design-system 2.0.24 → 2.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +68 -218
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -551,7 +551,7 @@ async function jget(url) {
551
551
  throw err;
552
552
  }
553
553
 
554
- const toKebab$1 = s => String(s || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
554
+ const toKebab = s => String(s || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
555
555
  const hex2 = n => n.toString(16).padStart(2, '0');
556
556
  function rgba255(c) {
557
557
  const a = typeof c?.a === 'number' ? c.a : 1;
@@ -586,14 +586,14 @@ async function fetchVariablesLocal(fileKey) {
586
586
  const byCollection = {};
587
587
  for (const colId of Object.keys(cols)) {
588
588
  const c = cols[colId];
589
- const colKey = toKebab$1(c.name);
589
+ const colKey = toKebab(c.name);
590
590
  byCollection[colKey] = {
591
591
  id: c.id,
592
592
  name: c.name,
593
593
  modes: (c.modes || []).map(m => ({
594
594
  id: m.modeId,
595
595
  name: m.name,
596
- key: toKebab$1(m.name)
596
+ key: toKebab(m.name)
597
597
  })),
598
598
  tokens: {}
599
599
  };
@@ -614,8 +614,8 @@ async function fetchVariablesLocal(fileKey) {
614
614
  const v = vars[vId];
615
615
  const coll = cols[v.variableCollectionId];
616
616
  if (!coll) continue;
617
- const colKey = toKebab$1(coll.name);
618
- const nameKey = toKebab$1(v.name);
617
+ const colKey = toKebab(coll.name);
618
+ const nameKey = toKebab(v.name);
619
619
  const token = {
620
620
  id: v.id,
621
621
  name: v.name,
@@ -644,7 +644,7 @@ async function fetchVariablesLocal(fileKey) {
644
644
  modes: (coll.modes || []).map(m => ({
645
645
  id: m.modeId,
646
646
  name: m.name,
647
- key: toKebab$1(m.name)
647
+ key: toKebab(m.name)
648
648
  })),
649
649
  tokens: {}
650
650
  };
@@ -780,13 +780,13 @@ function emitComponentPropsJson(nodes) {
780
780
  const props = {};
781
781
  for (const propId of Object.keys(defs)) {
782
782
  const def = defs[propId];
783
- const key = toKebab$1(def?.name ?? propId);
783
+ const key = toKebab(def?.name ?? propId);
784
784
  props[key] = {
785
785
  id: propId,
786
786
  ...normalizeComponentPropDef(def)
787
787
  };
788
788
  }
789
- const key = toKebab$1(n.name || n.id);
789
+ const key = toKebab(n.name || n.id);
790
790
  out[key] = {
791
791
  id: n.id,
792
792
  name: n.name,
@@ -979,7 +979,7 @@ function emitEffectStylesJson(effectMetas, effectNodes) {
979
979
  if (list.length) {
980
980
  // If meta.name is empty (node-id refs), use node.name
981
981
  const displayName = meta.name || n.name || meta.key || meta.node_id;
982
- const k = toKebab$1(displayName);
982
+ const k = toKebab(displayName);
983
983
  out[k] = {
984
984
  id: meta.key || meta.node_id,
985
985
  name: displayName,
@@ -1051,136 +1051,6 @@ async function figmaSource(fileKey) {
1051
1051
  };
1052
1052
  }
1053
1053
 
1054
- // src/tokens/naming.mjs
1055
- function toKebab(s) {
1056
- return String(s || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
1057
- }
1058
- function toCamel(s) {
1059
- const raw = String(s || '').trim();
1060
- if (!raw) return '';
1061
-
1062
- // If it's already a single token (e.g. "Desktop"), just lowercase the first char.
1063
- if (!/[^a-zA-Z0-9]/.test(raw)) {
1064
- return raw.charAt(0).toLowerCase() + raw.slice(1);
1065
- }
1066
- const parts = raw.split(/[^a-zA-Z0-9]+/g).filter(Boolean);
1067
- if (!parts.length) return '';
1068
- const [first, ...rest] = parts;
1069
- return first.charAt(0).toLowerCase() + first.slice(1) + rest.map(p => p ? p.charAt(0).toUpperCase() + p.slice(1) : '').join('');
1070
- }
1071
-
1072
- /**
1073
- * Figma token names often come in "Group / Sub / Name" form.
1074
- * Convert to a stable token key path:
1075
- * "Primary / Blue / 100" -> "primary-blue-100"
1076
- */
1077
- function tokenNameToKey(name) {
1078
- const raw = String(name || '');
1079
- // split on common separators but keep it conservative
1080
- const parts = raw.split('/').map(s => s.trim()).filter(Boolean);
1081
- if (parts.length) return toKebab(parts.join('-'));
1082
- return toKebab(raw);
1083
- }
1084
-
1085
- /**
1086
- * Normalize keys inside token "values".
1087
- * - Figma modes like "Mode 1" -> "mode-1" (kebab)
1088
- * - Device-ish keys like "Desktop" / "Mobile" -> "desktop" / "mobile" (camel)
1089
- */
1090
- function valueKeyToKey(name) {
1091
- const raw = String(name || '').trim();
1092
- if (!raw) return '';
1093
-
1094
- // "Mode 1" (or "mode1") must become "mode-1"
1095
- const m = raw.match(/^mode\s*(\d+)$/i);
1096
- if (m) return `mode-${m[1]}`;
1097
-
1098
- // Anything with whitespace is treated like a mode label.
1099
- if (/\s/.test(raw)) return toKebab(raw);
1100
-
1101
- // Default: camelCase for simple keys (Desktop/Mobile/etc)
1102
- return toCamel(raw);
1103
- }
1104
- function collectionNameToKey(name) {
1105
- return toKebab(name);
1106
- }
1107
-
1108
- function normalizeTokens(figmaCollections) {
1109
- message('normalizing tokens...');
1110
- const variables = {};
1111
- for (const [colKeyRaw, col] of Object.entries(figmaCollections || {})) {
1112
- const key = collectionNameToKey(col?.name || colKeyRaw);
1113
- const tokens = {};
1114
- for (const [tokKeyRaw, t] of Object.entries(col?.tokens || {})) {
1115
- const tKey = tokenNameToKey(t?.name || tokKeyRaw);
1116
- const rawValues = t?.values && typeof t.values === 'object' ? t.values : null;
1117
- const modeNames = rawValues ? Object.keys(rawValues) : [];
1118
- let values;
1119
-
1120
- // Single-mode: keep the simple shape you asked for
1121
- if (!rawValues || modeNames.length <= 1) {
1122
- const firstValue = rawValues ? rawValues[modeNames[0]] : null;
1123
- values = normalizeValueByType(t?.type, firstValue);
1124
- } else {
1125
- // Multi-mode: keep all columns (Desktop/Tablet/Mobile etc)
1126
- const map = {};
1127
- for (const modeName of modeNames) {
1128
- const modeKey = valueKeyToKey(modeName);
1129
- map[modeKey] = normalizeValueByType(t?.type, rawValues[modeName]);
1130
- }
1131
- values = map;
1132
- }
1133
- tokens[tKey] = {
1134
- id: String(t?.id || ''),
1135
- key: tKey,
1136
- name: String(t?.name || ''),
1137
- type: (/** @type {any} */String(t?.type || 'STRING')),
1138
- values
1139
- };
1140
- }
1141
- variables[key] = {
1142
- id: String(col?.id || ''),
1143
- key,
1144
- name: String(col?.name || colKeyRaw),
1145
- tokens
1146
- };
1147
- }
1148
- message('finished normalizing tokens');
1149
- return {
1150
- variables
1151
- };
1152
- }
1153
- function normalizeValueByType(type, v) {
1154
- const t = String(type || '').toUpperCase();
1155
- if (t === 'COLOR') {
1156
- if (v && typeof v === 'object' && v.hex) {
1157
- return {
1158
- raw: v,
1159
- color: v
1160
- };
1161
- }
1162
- return {
1163
- raw: v ?? null
1164
- };
1165
- }
1166
- if (t === 'BOOLEAN') {
1167
- return {
1168
- raw: v ?? null,
1169
- boolean: typeof v === 'boolean' ? v : null
1170
- };
1171
- }
1172
- if (t === 'FLOAT') {
1173
- return {
1174
- raw: v ?? null,
1175
- number: typeof v === 'number' ? v : null
1176
- };
1177
- }
1178
- return {
1179
- raw: v ?? null,
1180
- string: typeof v === 'string' ? v : v != null ? String(v) : null
1181
- };
1182
- }
1183
-
1184
1054
  /**
1185
1055
  * Return an array of components whose key or name starts with `prefix`.
1186
1056
  * Example: prefix "button-" returns all button-* components.
@@ -1211,7 +1081,7 @@ function groupButtons(components) {
1211
1081
  return buttons;
1212
1082
  }
1213
1083
 
1214
- function entries$3(tokens) {
1084
+ function entries$2(tokens) {
1215
1085
  return Object.entries(tokens || {});
1216
1086
  }
1217
1087
 
@@ -1232,24 +1102,30 @@ function entries$3(tokens) {
1232
1102
  */
1233
1103
  function groupColors(variableSet, collectionKey = 'colour') {
1234
1104
  message('grouping colors...');
1235
- const col = variableSet.colour;
1105
+ // `variableSet` is the object returned by src/figma/index.mjs (byCollection).
1106
+ // Keys are kebab-cased collection names.
1107
+ const col = variableSet?.[collectionKey] || null;
1236
1108
  const toks = col?.tokens || {};
1237
1109
 
1238
1110
  /** @type {Record<string, Record<string, any>>} */
1239
1111
  const out = {};
1240
- for (const [key, token] of entries$3(toks)) {
1112
+ for (const [key, token] of entries$2(toks)) {
1241
1113
  const parts = String(key).split('-').filter(Boolean);
1242
- if (parts.length === 0) continue;
1114
+ if (parts.length === 0) {
1115
+ continue;
1116
+ }
1243
1117
  const head = parts[0];
1244
1118
  const tail = parts.slice(1).join('-') || head;
1245
- if (!out[head]) out[head] = {};
1119
+ if (!out[head]) {
1120
+ out[head] = {};
1121
+ }
1246
1122
  out[head][tail] = token;
1247
1123
  }
1248
1124
  message('finished grouping colors');
1249
1125
  return out;
1250
1126
  }
1251
1127
 
1252
- function entries$2(tokens) {
1128
+ function entries$1(tokens) {
1253
1129
  return Object.entries(tokens || {});
1254
1130
  }
1255
1131
 
@@ -1276,7 +1152,7 @@ function groupTypography(variableSet) {
1276
1152
  lineHeights: {},
1277
1153
  other: {}
1278
1154
  };
1279
- for (const [key, token] of entries$2(toks)) {
1155
+ for (const [key, token] of entries$1(toks)) {
1280
1156
  if (key.startsWith('font-family-')) {
1281
1157
  out.families[key] = token;
1282
1158
  continue;
@@ -1299,7 +1175,7 @@ function groupTypography(variableSet) {
1299
1175
  return out;
1300
1176
  }
1301
1177
 
1302
- function entries$1(tokens) {
1178
+ function entries(tokens) {
1303
1179
  return Object.entries(tokens || {});
1304
1180
  }
1305
1181
  function isNumericKey(k) {
@@ -1328,7 +1204,7 @@ function groupScales(variableSet, collectionKey = 'scales') {
1328
1204
 
1329
1205
  /** @type {Record<string, Record<string, any>>} */
1330
1206
  const groups = {};
1331
- for (const [key, token] of entries$1(toks)) {
1207
+ for (const [key, token] of entries(toks)) {
1332
1208
  const k = String(key);
1333
1209
  if (isNumericKey(k)) {
1334
1210
  other[k] = token;
@@ -1351,38 +1227,6 @@ function groupScales(variableSet, collectionKey = 'scales') {
1351
1227
  };
1352
1228
  }
1353
1229
 
1354
- function entries(obj) {
1355
- return Object.entries(obj || {});
1356
- }
1357
- function normalizeKey(k) {
1358
- return String(k).trim().replace(/\s+/g, '-').replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
1359
- }
1360
-
1361
- /**
1362
- * Groups only icon components.
1363
- * Output:
1364
- * {
1365
- * 'icon-add': Component,
1366
- * 'icon-search': Component,
1367
- * ...
1368
- * }
1369
- */
1370
- function groupIcons(componentSet) {
1371
- message('grouping icons...');
1372
- const toks = componentSet?.components?.tokens || componentSet?.components || componentSet || {};
1373
- const icons = {};
1374
- for (const [rawKey, token] of entries(toks)) {
1375
- const key = normalizeKey(rawKey);
1376
- const name = normalizeKey(token?.name);
1377
- const isIcon = key.startsWith('icon-') || name.startsWith('icon=') || name.startsWith('icon-');
1378
- if (!isIcon) continue;
1379
- const iconKey = key.startsWith('icon-') ? key : name.startsWith('icon=') ? `icon-${name.slice('icon='.length)}` : name.startsWith('icon-') ? name : key;
1380
- icons[iconKey] = token;
1381
- }
1382
- message('finished grouping icons');
1383
- return icons;
1384
- }
1385
-
1386
1230
  function normalizeWeightKey(rawKey) {
1387
1231
  // enforce "font-weight-x"
1388
1232
  if (rawKey.startsWith('font-weight-')) return rawKey;
@@ -1540,12 +1384,12 @@ function pickHex(values, preferredMode) {
1540
1384
  if (!values || typeof values !== 'object') {
1541
1385
  return null;
1542
1386
  }
1543
- if (preferredMode && values[preferredMode]?.hex) {
1387
+ if (values[preferredMode]?.hex) {
1544
1388
  return values[preferredMode].hex;
1545
1389
  }
1546
1390
 
1547
1391
  // if already normalized upstream
1548
- if (preferredMode && values[modeKey$1(preferredMode)]?.hex) {
1392
+ if (values[modeKey$1(preferredMode)]?.hex) {
1549
1393
  return values[modeKey$1(preferredMode)].hex;
1550
1394
  }
1551
1395
 
@@ -1554,19 +1398,32 @@ function pickHex(values, preferredMode) {
1554
1398
  return first?.hex ?? null;
1555
1399
  }
1556
1400
  function collectColorVars(variableSet, {
1557
- includeDefaults = false,
1558
- preferredMode = 'Mode 1'
1401
+ excludeGroups = []
1559
1402
  } = {}) {
1560
- const map = new Map();
1561
- if (includeDefaults) {
1562
- // Optional legacy colors (kept for backwards compatibility)
1563
- map.set('--color-transparent', 'transparent');
1564
- map.set('--color-white', '#fff');
1565
- map.set('--color-black', '#111');
1566
- map.set('--color-umbraco-blue', '#283a97');
1567
- map.set('--color-umbraco-pink', '#f5c1bc');
1568
- }
1403
+ // Colors not present in Figma variables, but required by consumers.
1404
+ // These must come first in the generated output.
1405
+ const out = [{
1406
+ varName: '--color-transparent',
1407
+ hex: 'transparent'
1408
+ }, {
1409
+ varName: '--color-white',
1410
+ hex: '#fff'
1411
+ }, {
1412
+ varName: '--color-black',
1413
+ hex: '#111'
1414
+ }, {
1415
+ varName: '--color-umbraco-blue',
1416
+ hex: '#283a97'
1417
+ }, {
1418
+ varName: '--color-umbraco-pink',
1419
+ hex: '#f5c1bc'
1420
+ }];
1421
+ const seen = new Set(out.map(x => x.varName));
1422
+ const skipGroups = new Set((excludeGroups || []).map(x => kebab$2(x)));
1569
1423
  for (const [groupKey, group] of Object.entries(variableSet ?? {})) {
1424
+ if (skipGroups.has(kebab$2(groupKey))) {
1425
+ continue;
1426
+ }
1570
1427
  if (!group || typeof group !== 'object') {
1571
1428
  continue;
1572
1429
  }
@@ -1574,18 +1431,24 @@ function collectColorVars(variableSet, {
1574
1431
  if (!token || token.type !== 'COLOR') {
1575
1432
  continue;
1576
1433
  }
1577
- const hex = pickHex(token.values, preferredMode);
1434
+
1435
+ // Prefer "Mode 1" if present, else fallback to first mode
1436
+ const hex = pickHex(token.values, 'Mode 1');
1578
1437
  if (!hex) {
1579
1438
  continue;
1580
1439
  }
1581
1440
  const varName = `--color-${kebab$2(groupKey)}-${kebab$2(tokenKey)}`;
1582
- map.set(varName, hex);
1441
+ if (seen.has(varName)) {
1442
+ continue;
1443
+ }
1444
+ seen.add(varName);
1445
+ out.push({
1446
+ varName,
1447
+ hex
1448
+ });
1583
1449
  }
1584
1450
  }
1585
- return Array.from(map, ([varName, hex]) => ({
1586
- varName,
1587
- hex
1588
- }));
1451
+ return out;
1589
1452
  }
1590
1453
  function renderColors(vars) {
1591
1454
  const lines = vars.map(v => ` ${v.varName}: ${v.hex};`);
@@ -1593,11 +1456,9 @@ function renderColors(vars) {
1593
1456
  }
1594
1457
  async function generateColors$1(variableSet, config) {
1595
1458
  message('generating colors...');
1596
- const includeDefaults = config?.raw?.colors?.includeDefaults ?? false;
1597
- const preferredMode = config?.raw?.colors?.preferredMode ?? 'Mode 1';
1459
+ const excludeGroups = config?.raw?.colors?.excludeGroups ?? [];
1598
1460
  const vars = collectColorVars(variableSet, {
1599
- includeDefaults,
1600
- preferredMode
1461
+ excludeGroups
1601
1462
  });
1602
1463
  const css = '/* AUTO-GENERATED - DO NOT EDIT BY HAND */\n\n' + renderColors(vars);
1603
1464
  const outPath = path.join(config.paths.styles, 'config', 'color.css');
@@ -3048,28 +2909,17 @@ async function pullAndBuild({
3048
2909
  projectName
3049
2910
  }) {
3050
2911
  const raw = await figmaSource(config.figma.fileKey);
3051
-
3052
- // const outPath = path.join(process.cwd(), 'figma-raw.json')
3053
-
3054
- normalizeTokens(raw.variables);
3055
- // await fs.writeFile(
3056
- // outPath,
3057
- // JSON.stringify(tokens, null, 2),
3058
- // 'utf8'
3059
- // )
3060
-
3061
- const colors = groupColors(raw.variables);
2912
+ const colorCollectionKey = config?.raw?.colors?.collectionKey ?? 'colour';
2913
+ const availableColorCollections = Object.keys(raw.variables || {}).sort().join(', ');
2914
+ message(`colors: using variable collection "${colorCollectionKey}" (available: ${availableColorCollections || 'none'})`);
2915
+ const colors = groupColors(raw.variables, colorCollectionKey);
3062
2916
  const typography = groupTypography(raw.variables);
3063
2917
  const scales = groupScales(raw.variables);
3064
2918
  const buttons = groupButtons(raw.components);
3065
- groupIcons(raw.components);
3066
2919
 
3067
2920
  // console.log(colors)
3068
2921
 
3069
2922
  await generateTailwind(config, 'razor', typography, buttons, colors, scales, raw.effects);
3070
-
3071
- // await exportIcons(icons, config.figma.fileKey)
3072
-
3073
2923
  if (target === 'razor') {
3074
2924
  // console.dir(typography, { depth: null, maxArrayLength: null })
3075
2925
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vettvangur/design-system",
3
- "version": "2.0.24",
3
+ "version": "2.0.26",
4
4
  "description": "",
5
5
  "access": "public",
6
6
  "type": "module",