@vector-im/compound-design-tokens 10.2.2 → 10.2.4

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.
@@ -0,0 +1,510 @@
1
+ import * as Alphredo from "./alphredo.js";
2
+ import * as CompoundTheme from "./theme.js";
3
+ import "./styles.css";
4
+
5
+ // Color spaces for color interpolation
6
+ // CAM02, CAM02p, LCH, LAB, HSL, HSLuv, HSV, RGB, OKLAB, OKLCH
7
+
8
+ // OKLCH, false
9
+ // CAM02p, true
10
+ // LAB, true
11
+ // RGB, true
12
+
13
+ const leonardoConfig = CompoundTheme.leonardoConfig;
14
+
15
+ // Color playground (temporary overrides)
16
+
17
+ // A custom color to help generate color overrides.
18
+ // leonardoConfig.colors.custom = ["#571EFA"];
19
+
20
+ // leonardoConfig.colors.gray: [CompoundTheme.hslToHex(220, 8, 50)],
21
+ // leonardoConfig.colors.gray: [CompoundTheme.hslToHex(210, 10, 50)],
22
+ // leonardoConfig.colors.red = [CompoundTheme.hslToHex(2, 100, 50), CompoundTheme.hslToHex(350, 100, 30)];
23
+
24
+ function getHues(leonardoConfig) {
25
+ return Object.keys(leonardoConfig.colors);
26
+ }
27
+
28
+ function getAlphaColor(color, background) {
29
+ const alphaColor = Alphredo.getAlphaColor(color, background);
30
+ return `hsla(${alphaColor.h}, ${alphaColor.s}%, ${alphaColor.l}%, ${alphaColor.a})`;
31
+ }
32
+
33
+ function getAlphaColorOpacity(color, background) {
34
+ const alphaColor = Alphredo.getAlphaColor(color, background);
35
+ return alphaColor.a;
36
+ }
37
+
38
+ function renderKeyColorsHtml(leonardoConfig) {
39
+ let html = "";
40
+
41
+ for (const [key, colors] of Object.entries(leonardoConfig.colors)) {
42
+ // The custom color is shown in the picker below.
43
+ if (key === "custom") continue;
44
+
45
+ html += `<div class="key-colors__hue">`;
46
+ for (const color of colors) {
47
+ html += `<div class="key-colors__swatch" style="background-color: ${color}"></div>`;
48
+ }
49
+ html += "</div>";
50
+ }
51
+
52
+ const customColor = leonardoConfig.colors.custom?.[0] ?? "#000000";
53
+ html += `<div class="key-colors__picker">
54
+ <label for="color-picker" class="text-body-sm text--primary-{themeName}">🎨</label>
55
+ <input type="color" id="color-picker" name="color-picker" value="${customColor}" />
56
+ </div>`;
57
+
58
+ return html;
59
+ }
60
+
61
+ function renderThemeHtml(themeJson, themeName, alpha = false) {
62
+ let html = "";
63
+ const background = themeJson[0].background;
64
+
65
+ html += `<div class="theme theme--${themeName}" style="background-color: ${background}">`;
66
+
67
+ for (const color of themeJson) {
68
+ if (color.name) {
69
+ if (!alpha) {
70
+ html += `<a class="theme__action" href="${generateContrastGridUrl(
71
+ themesJson,
72
+ themeName,
73
+ color.name,
74
+ )}" target="_blank" rel="noopener">`;
75
+ }
76
+ html += `<div class="theme__hue">`;
77
+ for (const value of color.values) {
78
+ html += `<div class="theme__swatch" style="background-color: ${
79
+ alpha ? getAlphaColor(value.value, background) : value.value
80
+ }"></div>`;
81
+ }
82
+ html += "</div>";
83
+ if (!alpha) {
84
+ html += "</a>";
85
+ }
86
+ }
87
+ }
88
+
89
+ html += "</div>";
90
+
91
+ return html;
92
+ }
93
+
94
+ function renderThemesHtml(themesJson, alpha = false) {
95
+ const themesHtml = {};
96
+
97
+ for (const themeName of Object.keys(themesJson)) {
98
+ themesHtml[themeName] = renderThemeHtml(
99
+ themesJson[themeName],
100
+ themeName,
101
+ alpha,
102
+ );
103
+ }
104
+
105
+ return themesHtml;
106
+ }
107
+
108
+ function fromLeonardoColorToTokenStudio(leonardoColors) {
109
+ const background = leonardoColors[0].background;
110
+ const output = {};
111
+
112
+ output.color = leonardoColors.reduce((memo, entry) => {
113
+ if (entry.background) {
114
+ memo.theme = {
115
+ bg: {
116
+ value: entry.background,
117
+ type: "color",
118
+ description: "WCAG: 1",
119
+ },
120
+ };
121
+ } else if (entry.values) {
122
+ for (const value of entry.values) {
123
+ const [name, shade] = value.name.split(/(\d+)/);
124
+
125
+ memo[name] = {
126
+ ...(memo[name] || {}),
127
+ [shade]: {
128
+ value: value.value,
129
+ type: "color",
130
+ description: `WCAG: ${value.contrast}`,
131
+ },
132
+ };
133
+ }
134
+ }
135
+
136
+ return memo;
137
+ }, {});
138
+
139
+ output.color.alpha = leonardoColors.reduce((memo, entry) => {
140
+ if (entry.values) {
141
+ for (const value of entry.values) {
142
+ const [name, shade] = value.name.split(/(\d+)/);
143
+
144
+ memo[name] = {
145
+ ...(memo[name] || {}),
146
+ [shade]: {
147
+ value: getAlphaColor(value.value, background),
148
+ type: "color",
149
+ description: `WCAG: ${
150
+ value.contrast
151
+ } Opacity: ${getAlphaColorOpacity(value.value, background)}`,
152
+ },
153
+ };
154
+ }
155
+ }
156
+
157
+ return memo;
158
+ }, {});
159
+
160
+ return output;
161
+ }
162
+
163
+ function renderTokensStudioHtml(themesJson) {
164
+ return Object.keys(themesJson).reduce((html, theme) => {
165
+ return `${html}<details><summary>${theme}</summary><pre>${JSON.stringify(fromLeonardoColorToTokenStudio(themesJson[theme]))}</pre></details>`;
166
+ }, "");
167
+ }
168
+
169
+ function createCssVars(themesJson) {
170
+ let styles = ":root {\n";
171
+
172
+ for (const themeName of Object.keys(themesJson)) {
173
+ for (const group of themesJson[themeName]) {
174
+ if (group.background) {
175
+ styles += `--color-bg-${themeName}: ${group.background};\n`;
176
+ } else if (group.values) {
177
+ for (const value of group.values) {
178
+ styles += `--color-${group.name
179
+ .toLowerCase()
180
+ .replace(/\s+/g, "-")}-${value.name.match(/\d+/)}-${themeName}: ${
181
+ value.value
182
+ };\n`;
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ styles += "}";
189
+
190
+ return styles;
191
+ }
192
+
193
+ function addStylesToHead(styles) {
194
+ const styleEl = document.createElement("style");
195
+ styleEl.innerHTML = styles;
196
+ document.head.appendChild(styleEl);
197
+ }
198
+
199
+ function generateLeonardoUrl(leonardoConfig, theme) {
200
+ const config = {};
201
+
202
+ config.baseScale = leonardoConfig.backgroundColor;
203
+ config.contrast = leonardoConfig.themes[theme].contrast;
204
+ config.lightness = leonardoConfig.themes[theme].lightness;
205
+ config.saturation = leonardoConfig.themes[theme].saturation;
206
+ config.formula = leonardoConfig.formula;
207
+
208
+ config.colorScales = [];
209
+
210
+ for (const [key, value] of Object.entries(leonardoConfig.colors)) {
211
+ config.colorScales.push({
212
+ name: key,
213
+ colorKeys: value,
214
+ colorspace: leonardoConfig.colorSpace,
215
+ ratios: leonardoConfig.themes[theme].ratios,
216
+ smooth: leonardoConfig.colorSmoothing,
217
+ });
218
+ }
219
+
220
+ const encodedConfig = encodeURIComponent(JSON.stringify(config));
221
+ const leonardoUrl = `https://leonardocolor.io/theme.html?name=${theme}&config=${encodedConfig}`;
222
+
223
+ return leonardoUrl;
224
+ }
225
+
226
+ function renderLeonardoHtml(leonardoConfig) {
227
+ let html = "<ul>";
228
+
229
+ for (const themeName of Object.keys(leonardoConfig.themes)) {
230
+ html += `<li><a href="${generateLeonardoUrl(
231
+ leonardoConfig,
232
+ themeName,
233
+ )}" target="_blank" rel="noopener">${themeName} ↗</a></li>`;
234
+ themesHtml[themeName] = renderThemeHtml(themesJson[themeName], themeName);
235
+ }
236
+
237
+ html += "</ul>";
238
+
239
+ return html;
240
+ }
241
+
242
+ let themesJson = {};
243
+ let themesHtml = {};
244
+
245
+ function renderPage() {
246
+ themesJson = CompoundTheme.generateThemesJson(leonardoConfig);
247
+ themesHtml = renderThemesHtml(themesJson);
248
+
249
+ const keyColorsHtml = renderKeyColorsHtml(leonardoConfig);
250
+ const themesHtmlAlpha = renderThemesHtml(themesJson, true);
251
+ const tokensStudioHtml = renderTokensStudioHtml(themesJson);
252
+ const cssVars = createCssVars(themesJson);
253
+ const leonardoHtml = renderLeonardoHtml(leonardoConfig);
254
+
255
+ document.querySelector(".key-colors").innerHTML = keyColorsHtml;
256
+
257
+ document.querySelector(".themes").innerHTML =
258
+ Object.values(themesHtml).join("");
259
+
260
+ document.querySelector(".themes-alpha").innerHTML =
261
+ Object.values(themesHtmlAlpha).join("");
262
+
263
+ document.querySelector(".tokens-studio").innerHTML = tokensStudioHtml;
264
+
265
+ addStylesToHead(cssVars);
266
+ document.querySelector(".css-vars__output").innerHTML = cssVars;
267
+
268
+ document.querySelector(".leonardo-editor__links").innerHTML = leonardoHtml;
269
+
270
+ document.querySelector("#color-picker").addEventListener("input", (event) => {
271
+ // Update the custom colour and re-render.
272
+ leonardoConfig.colors.custom = [event.target.value];
273
+ renderPage();
274
+ });
275
+ }
276
+
277
+ renderPage();
278
+
279
+ const templateHtmlButtons = `
280
+ <div class="bg bg--{themeName}">
281
+ <div class="flex flex-row gap-0_5">
282
+ <div class="button button--gray-{themeName}">
283
+ Button
284
+ </div>
285
+ <div class="button button--blue-{themeName}">
286
+ Button
287
+ </div>
288
+ <div class="button button--red-{themeName}">
289
+ Button
290
+ </div>
291
+ </div>
292
+ </div>`;
293
+
294
+ const templateCssButtons = `
295
+ .button--{colorName}-{themeName} {
296
+ background-color: var(--color-{colorName}-900-{themeName});
297
+ color: var(--color-bg-{themeName});
298
+ }`;
299
+
300
+ const templateHtmlLabels = `
301
+ <div class="bg bg--{themeName}">
302
+ <div class="flex flex-row gap-0_5">
303
+ <div class="label label--gray-{themeName}">
304
+ Gray
305
+ </div>
306
+ <div class="label label--red-{themeName}">
307
+ Red
308
+ </div>
309
+ <div class="label label--orange-{themeName}">
310
+ Orange
311
+ </div>
312
+ <div class="label label--yellow-{themeName}">
313
+ Yellow
314
+ </div>
315
+ <div class="label label--lime-{themeName}">
316
+ Lime
317
+ </div>
318
+ <div class="label label--green-{themeName}">
319
+ Green
320
+ </div>
321
+ <div class="label label--cyan-{themeName}">
322
+ Cyan
323
+ </div>
324
+ <div class="label label--blue-{themeName}">
325
+ Blue
326
+ </div>
327
+ <div class="label label--purple-{themeName}">
328
+ Purple
329
+ </div>
330
+ <div class="label label--fuchsia-{themeName}">
331
+ Fuchsia
332
+ </div>
333
+ <div class="label label--pink-{themeName}">
334
+ Pink
335
+ </div>
336
+ </div>
337
+ </div>`;
338
+
339
+ const templateCssLabels = `
340
+ .label--{colorName}-{themeName} {
341
+ background-color: var(--color-{colorName}-300-{themeName});
342
+ border-color: var(--color-{colorName}-600-{themeName});
343
+ color: var(--color-{colorName}-900-{themeName});
344
+ }`;
345
+
346
+ const templateHtmlText = `
347
+ <div class="bg bg--{themeName}">
348
+ <div class="m-b-1">
349
+ <h2 class="text-heading text--primary-{themeName}">A Short Heading</h2>
350
+ <p class="text-body-md text--primary-{themeName}">Some body text.</p>
351
+ <p class="text-body-md text--secondary-{themeName}">
352
+ Some secondary text.
353
+ </p>
354
+ <p class="text-body-sm text--secondary-{themeName}">
355
+ And a footnote.
356
+ </p>
357
+ </div>
358
+ <div class="grid grid-cols-3 gap-1">
359
+ <div class="bg bg--01-gray-{themeName}">
360
+ <h2 class="text-heading text--primary-{themeName}">A Short Heading</h2>
361
+ <p class="text-body-md text--primary-{themeName}">Some body text.</p>
362
+ <p class="text-body-md text--secondary-{themeName}">
363
+ Some secondary text.
364
+ </p>
365
+ <p class="text-body-sm text--secondary-{themeName}">
366
+ And a footnote.
367
+ </p>
368
+ </div>
369
+ <div class="bg bg--02-gray-{themeName}">
370
+ <h2 class="text-heading text--primary-{themeName}">A Short Heading</h2>
371
+ <p class="text-body-md text--primary-{themeName}">Some body text.</p>
372
+ <p class="text-body-md text--secondary-{themeName}">
373
+ Some secondary text.
374
+ </p>
375
+ <p class="text-body-sm text--secondary-{themeName}">
376
+ And a footnote.
377
+ </p>
378
+ </div>
379
+ <div class="bg bg--03-gray-{themeName}">
380
+ <h2 class="text-heading text--primary-{themeName}">A Short Heading</h2>
381
+ <p class="text-body-md text--primary-{themeName}">Some body text.</p>
382
+ <p class="text-body-md text--secondary-{themeName}">
383
+ Some secondary text.
384
+ </p>
385
+ <p class="text-body-sm text--secondary-{themeName}">
386
+ And a footnote.
387
+ </p>
388
+ </div>
389
+ <div class="bg bg--04-gray-{themeName}">
390
+ <h2 class="text-heading text--primary-{themeName}">A Short Heading</h2>
391
+ <p class="text-body-md text--primary-{themeName}">Some body text.</p>
392
+ <p class="text-body-md text--secondary-strong-{themeName}">
393
+ Some secondary text.
394
+ </p>
395
+ <p class="text-body-sm text--secondary-strong-{themeName}">
396
+ And a footnote.
397
+ </p>
398
+ </div>
399
+ <div class="bg bg--05-gray-{themeName}">
400
+ <h2 class="text-heading text--primary-{themeName}">A Short Heading</h2>
401
+ <p class="text-body-md text--primary-{themeName}">Some body text.</p>
402
+ <p class="text-body-md text--secondary-strong-{themeName}">
403
+ Some secondary text.
404
+ </p>
405
+ <p class="text-body-sm text--secondary-strong-{themeName}">
406
+ And a footnote.
407
+ </p>
408
+ </div>
409
+ <div class="bg bg--06-gray-{themeName}">
410
+ <h2 class="text-heading text--primary-{themeName}">A Short Heading</h2>
411
+ <p class="text-body-md text--primary-{themeName}">Some body text.</p>
412
+ <p class="text-body-md text--secondary-strong-{themeName}">
413
+ Some secondary text.
414
+ </p>
415
+ <p class="text-body-sm text--secondary-strong-{themeName}">
416
+ And a footnote.
417
+ </p>
418
+ </div>
419
+ </div>
420
+ </div>`;
421
+
422
+ const templateCssText = `
423
+ .bg--01-{colorName}-{themeName} {
424
+ background-color: var(--color-{colorName}-100-{themeName});
425
+ }
426
+ .bg--02-{colorName}-{themeName} {
427
+ background-color: var(--color-{colorName}-200-{themeName});
428
+ }
429
+ .bg--03-{colorName}-{themeName} {
430
+ background-color: var(--color-{colorName}-300-{themeName});
431
+ }
432
+ .bg--04-{colorName}-{themeName} {
433
+ background-color: var(--color-{colorName}-400-{themeName});
434
+ }
435
+ .bg--05-{colorName}-{themeName} {
436
+ background-color: var(--color-{colorName}-500-{themeName});
437
+ }
438
+ .bg--06-{colorName}-{themeName} {
439
+ background-color: var(--color-{colorName}-600-{themeName});
440
+ }
441
+ .text--primary-{themeName} {
442
+ color: var(--color-{colorName}-1400-{themeName});
443
+ }
444
+ .text--secondary-{themeName} {
445
+ color: var(--color-{colorName}-900-{themeName});
446
+ }
447
+ .text--secondary-strong-{themeName} {
448
+ color: var(--color-{colorName}-1100-{themeName});
449
+ }`;
450
+
451
+ function renderTestHtml(template, themesJson) {
452
+ return Object.keys(themesJson).reduce((html, theme) => {
453
+ return html + template.replaceAll("{themeName}", theme);
454
+ }, "");
455
+ }
456
+
457
+ function addTestHtml(template, themesJson) {
458
+ const testContainer = document.createElement("div");
459
+ testContainer.className = "tests__case";
460
+ testContainer.innerHTML = renderTestHtml(template, themesJson);
461
+ document.querySelector(".tests").appendChild(testContainer);
462
+ }
463
+
464
+ function renderTestCss(template, themesJson, colors) {
465
+ return Object.keys(themesJson)
466
+ .flatMap((theme) =>
467
+ colors.map((color) =>
468
+ template
469
+ .replaceAll("{colorName}", color)
470
+ .replaceAll("{themeName}", theme),
471
+ ),
472
+ )
473
+ .join("\n");
474
+ }
475
+
476
+ function addTestCss(template, themesJson, colors) {
477
+ addStylesToHead(renderTestCss(template, themesJson, colors));
478
+ }
479
+
480
+ addTestHtml(templateHtmlText, themesJson);
481
+ addTestCss(templateCssText, themesJson, ["gray"]);
482
+
483
+ addTestHtml(templateHtmlButtons, themesJson);
484
+ // Gray is set manually in styles.css to use 1400 colors
485
+ addTestCss(templateCssButtons, themesJson, ["red", "blue"]);
486
+
487
+ addTestHtml(templateHtmlLabels, themesJson);
488
+ addTestCss(templateCssLabels, themesJson, getHues(leonardoConfig));
489
+
490
+ function generateContrastGridUrl(themesJson, themeName, colorName) {
491
+ const themeJson = themesJson[themeName];
492
+ let contrastGridColors = "";
493
+
494
+ for (const color of themeJson) {
495
+ if (color.name === colorName) {
496
+ for (const swatch of color.values) {
497
+ contrastGridColors += `${swatch.value}, ${swatch.name}\n`;
498
+ }
499
+ }
500
+ }
501
+
502
+ const contrastGridSettings =
503
+ "es-color-form__tile-size=compact&es-color-form__show-contrast=aaa&es-color-form__show-contrast=aa&es-color-form__show-contrast=aa18";
504
+
505
+ contrastGridColors = encodeURIComponent(contrastGridColors);
506
+
507
+ const contrastGridUrl = `https://contrast-grid.eightshapes.com/?version=1.1.0&${contrastGridSettings}&background-colors=${contrastGridColors}&foreground-colors=${contrastGridColors}`;
508
+
509
+ return contrastGridUrl;
510
+ }