@atlaskit/tokens 1.11.3 → 1.12.0

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 (102) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/artifacts/atlassian-dark-token-value-for-contrast-check.js +24 -0
  3. package/dist/cjs/artifacts/atlassian-light-token-value-for-contrast-check.js +24 -0
  4. package/dist/cjs/constants.js +3 -1
  5. package/dist/cjs/custom-theme.js +108 -0
  6. package/dist/cjs/get-token-value.js +1 -1
  7. package/dist/cjs/get-token.js +1 -1
  8. package/dist/cjs/set-global-theme.js +156 -59
  9. package/dist/cjs/utils/color-utils.js +178 -0
  10. package/dist/cjs/utils/custom-theme-loading-utils.js +47 -0
  11. package/dist/cjs/utils/custom-theme-token-contrast-check.js +74 -0
  12. package/dist/cjs/utils/generate-custom-color-ramp.js +213 -0
  13. package/dist/cjs/utils/hash.js +17 -0
  14. package/dist/cjs/utils/hct-color-utils/color-utils.js +310 -0
  15. package/dist/cjs/utils/hct-color-utils/contrast.js +188 -0
  16. package/dist/cjs/utils/hct-color-utils/hct.js +1036 -0
  17. package/dist/cjs/utils/hct-color-utils/index.js +32 -0
  18. package/dist/cjs/utils/hct-color-utils/math-utils.js +159 -0
  19. package/dist/cjs/utils/theme-loading.js +1 -1
  20. package/dist/cjs/utils/theme-state-transformer.js +1 -1
  21. package/dist/cjs/version.json +1 -1
  22. package/dist/es2019/artifacts/atlassian-dark-token-value-for-contrast-check.js +17 -0
  23. package/dist/es2019/artifacts/atlassian-light-token-value-for-contrast-check.js +17 -0
  24. package/dist/es2019/constants.js +1 -0
  25. package/dist/es2019/custom-theme.js +77 -0
  26. package/dist/es2019/get-token-value.js +1 -1
  27. package/dist/es2019/get-token.js +1 -1
  28. package/dist/es2019/set-global-theme.js +67 -13
  29. package/dist/es2019/utils/color-utils.js +154 -0
  30. package/dist/es2019/utils/custom-theme-loading-utils.js +31 -0
  31. package/dist/es2019/utils/custom-theme-token-contrast-check.js +68 -0
  32. package/dist/es2019/utils/generate-custom-color-ramp.js +187 -0
  33. package/dist/es2019/utils/hash.js +10 -0
  34. package/dist/es2019/utils/hct-color-utils/color-utils.js +286 -0
  35. package/dist/es2019/utils/hct-color-utils/contrast.js +161 -0
  36. package/dist/es2019/utils/hct-color-utils/hct.js +931 -0
  37. package/dist/es2019/utils/hct-color-utils/index.js +3 -0
  38. package/dist/es2019/utils/hct-color-utils/math-utils.js +145 -0
  39. package/dist/es2019/utils/theme-loading.js +2 -2
  40. package/dist/es2019/utils/theme-state-transformer.js +3 -1
  41. package/dist/es2019/version.json +1 -1
  42. package/dist/esm/artifacts/atlassian-dark-token-value-for-contrast-check.js +17 -0
  43. package/dist/esm/artifacts/atlassian-light-token-value-for-contrast-check.js +17 -0
  44. package/dist/esm/constants.js +1 -0
  45. package/dist/esm/custom-theme.js +98 -0
  46. package/dist/esm/get-token-value.js +1 -1
  47. package/dist/esm/get-token.js +1 -1
  48. package/dist/esm/set-global-theme.js +149 -60
  49. package/dist/esm/utils/color-utils.js +162 -0
  50. package/dist/esm/utils/custom-theme-loading-utils.js +38 -0
  51. package/dist/esm/utils/custom-theme-token-contrast-check.js +65 -0
  52. package/dist/esm/utils/generate-custom-color-ramp.js +202 -0
  53. package/dist/esm/utils/hash.js +10 -0
  54. package/dist/esm/utils/hct-color-utils/color-utils.js +285 -0
  55. package/dist/esm/utils/hct-color-utils/contrast.js +181 -0
  56. package/dist/esm/utils/hct-color-utils/hct.js +1029 -0
  57. package/dist/esm/utils/hct-color-utils/index.js +3 -0
  58. package/dist/esm/utils/hct-color-utils/math-utils.js +145 -0
  59. package/dist/esm/utils/theme-loading.js +2 -2
  60. package/dist/esm/utils/theme-state-transformer.js +1 -1
  61. package/dist/esm/version.json +1 -1
  62. package/dist/types/artifacts/atlassian-dark-token-value-for-contrast-check.d.ts +17 -0
  63. package/dist/types/artifacts/atlassian-light-token-value-for-contrast-check.d.ts +17 -0
  64. package/dist/types/constants.d.ts +1 -0
  65. package/dist/types/custom-theme.d.ts +30 -0
  66. package/dist/types/index.d.ts +1 -0
  67. package/dist/types/set-global-theme.d.ts +9 -3
  68. package/dist/types/tokens/atlassian-dark/utility/utility.d.ts +1 -1
  69. package/dist/types/tokens/atlassian-light/utility/utility.d.ts +1 -1
  70. package/dist/types/tokens/default/utility/utility.d.ts +1 -1
  71. package/dist/types/utils/color-utils.d.ts +10 -0
  72. package/dist/types/utils/custom-theme-loading-utils.d.ts +11 -0
  73. package/dist/types/utils/custom-theme-token-contrast-check.d.ts +20 -0
  74. package/dist/types/utils/generate-custom-color-ramp.d.ts +19 -0
  75. package/dist/types/utils/hash.d.ts +1 -0
  76. package/dist/types/utils/hct-color-utils/color-utils.d.ts +131 -0
  77. package/dist/types/utils/hct-color-utils/contrast.d.ts +78 -0
  78. package/dist/types/utils/hct-color-utils/hct.d.ts +137 -0
  79. package/dist/types/utils/hct-color-utils/index.d.ts +3 -0
  80. package/dist/types/utils/hct-color-utils/math-utils.d.ts +86 -0
  81. package/dist/types-ts4.5/artifacts/atlassian-dark-token-value-for-contrast-check.d.ts +17 -0
  82. package/dist/types-ts4.5/artifacts/atlassian-light-token-value-for-contrast-check.d.ts +17 -0
  83. package/dist/types-ts4.5/constants.d.ts +1 -0
  84. package/dist/types-ts4.5/custom-theme.d.ts +30 -0
  85. package/dist/types-ts4.5/index.d.ts +1 -0
  86. package/dist/types-ts4.5/set-global-theme.d.ts +9 -3
  87. package/dist/types-ts4.5/tokens/atlassian-dark/utility/utility.d.ts +1 -1
  88. package/dist/types-ts4.5/tokens/atlassian-light/utility/utility.d.ts +1 -1
  89. package/dist/types-ts4.5/tokens/default/utility/utility.d.ts +1 -1
  90. package/dist/types-ts4.5/utils/color-utils.d.ts +27 -0
  91. package/dist/types-ts4.5/utils/custom-theme-loading-utils.d.ts +11 -0
  92. package/dist/types-ts4.5/utils/custom-theme-token-contrast-check.d.ts +20 -0
  93. package/dist/types-ts4.5/utils/generate-custom-color-ramp.d.ts +19 -0
  94. package/dist/types-ts4.5/utils/hash.d.ts +1 -0
  95. package/dist/types-ts4.5/utils/hct-color-utils/color-utils.d.ts +131 -0
  96. package/dist/types-ts4.5/utils/hct-color-utils/contrast.d.ts +78 -0
  97. package/dist/types-ts4.5/utils/hct-color-utils/hct.d.ts +137 -0
  98. package/dist/types-ts4.5/utils/hct-color-utils/index.d.ts +3 -0
  99. package/dist/types-ts4.5/utils/hct-color-utils/math-utils.d.ts +86 -0
  100. package/package.json +35 -36
  101. package/report.api.md +24 -1
  102. package/tmp/api-report-tmp.d.ts +0 -1132
@@ -0,0 +1,154 @@
1
+ // valid hex color with 6 digits
2
+ export const isValidBrandHex = hex => /^#[0-9A-F]{6}$/i.test(hex);
3
+
4
+ // valid hex color with 4, 6 or 8 digits
5
+ const isValidHex = hex => /^#([A-Fa-f0-9]{3,4}){1,2}$/.test(hex);
6
+ export function rgbToHex(r, g, b) {
7
+ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
8
+ }
9
+ export function getAlpha(hex) {
10
+ if (hex.length === 9) {
11
+ const int = parseInt(hex.slice(7, 9), 16) / 255;
12
+ return Number(parseFloat(int.toString()).toFixed(2));
13
+ }
14
+ return 1;
15
+ }
16
+ export function hexToRgbA(hex) {
17
+ if (!isValidHex(hex)) {
18
+ throw new Error('Invalid HEX');
19
+ }
20
+ let c;
21
+ c = hex.substring(1).split('');
22
+ if (c.length === 3) {
23
+ c = [c[0], c[0], c[1], c[1], c[2], c[2]];
24
+ }
25
+ c = '0x' + c.join('');
26
+ return [c >> 16 & 255, c >> 8 & 255, c & 255, getAlpha(hex)];
27
+ }
28
+ export function hexToRgb(hex) {
29
+ if (!isValidHex(hex)) {
30
+ throw new Error('Invalid HEX');
31
+ }
32
+ let c;
33
+ c = hex.substring(1).split('');
34
+ if (c.length === 3) {
35
+ c = [c[0], c[0], c[1], c[1], c[2], c[2]];
36
+ }
37
+ c = '0x' + c.join('');
38
+ return [c >> 16 & 255, c >> 8 & 255, c & 255];
39
+ }
40
+ export function hexToHSL(hex) {
41
+ if (!isValidHex(hex)) {
42
+ throw new Error('Invalid HEX');
43
+ }
44
+ let r = 0,
45
+ g = 0,
46
+ b = 0;
47
+ if (hex.length === 4) {
48
+ r = '0x' + hex[1] + hex[1];
49
+ g = '0x' + hex[2] + hex[2];
50
+ b = '0x' + hex[3] + hex[3];
51
+ } else if (hex.length === 7) {
52
+ r = '0x' + hex[1] + hex[2];
53
+ g = '0x' + hex[3] + hex[4];
54
+ b = '0x' + hex[5] + hex[6];
55
+ }
56
+ // Then to HSL
57
+ r /= 255;
58
+ g /= 255;
59
+ b /= 255;
60
+ let cmin = Math.min(r, g, b),
61
+ cmax = Math.max(r, g, b),
62
+ delta = cmax - cmin,
63
+ h = 0,
64
+ s = 0,
65
+ l = 0;
66
+ if (delta === 0) {
67
+ h = 0;
68
+ } else if (cmax === r) {
69
+ h = (g - b) / delta % 6;
70
+ } else if (cmax === g) {
71
+ h = (b - r) / delta + 2;
72
+ } else {
73
+ h = (r - g) / delta + 4;
74
+ }
75
+ h = Math.round(h * 60);
76
+ if (h < 0) {
77
+ h += 360;
78
+ }
79
+ l = (cmax + cmin) / 2;
80
+ s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
81
+ s = +(s * 100).toFixed(1);
82
+ l = +(l * 100).toFixed(1);
83
+ return [h, s, l];
84
+ }
85
+ export function HSLToRGB(h, s, l) {
86
+ s /= 100;
87
+ l /= 100;
88
+ const k = n => (n + h / 30) % 12;
89
+ const a = s * Math.min(l, 1 - l);
90
+ const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
91
+ return [255 * f(0), 255 * f(8), 255 * f(4)];
92
+ }
93
+ export function relativeLuminanceW3C(r, g, b) {
94
+ const RsRGB = r / 255;
95
+ const GsRGB = g / 255;
96
+ const BsRGB = b / 255;
97
+ const R = RsRGB <= 0.03928 ? RsRGB / 12.92 : Math.pow((RsRGB + 0.055) / 1.055, 2.4);
98
+ const G = GsRGB <= 0.03928 ? GsRGB / 12.92 : Math.pow((GsRGB + 0.055) / 1.055, 2.4);
99
+ const B = BsRGB <= 0.03928 ? BsRGB / 12.92 : Math.pow((BsRGB + 0.055) / 1.055, 2.4);
100
+
101
+ // For the sRGB colorspace, the relative luminance of a color is defined as:
102
+ const L = 0.2126 * R + 0.7152 * G + 0.0722 * B;
103
+ return L;
104
+ }
105
+ export function getContrastRatio(foreground, background) {
106
+ if (!isValidHex(foreground) || !isValidHex(background)) {
107
+ throw new Error('Invalid HEX');
108
+ }
109
+ const foregroundRgb = hexToRgb(foreground);
110
+ const backgroundRgb = hexToRgb(background);
111
+ const foregroundLuminance = relativeLuminanceW3C(foregroundRgb[0], foregroundRgb[1], foregroundRgb[2]);
112
+ const backgroundLuminance = relativeLuminanceW3C(backgroundRgb[0], backgroundRgb[1], backgroundRgb[2]);
113
+ // calculate the color contrast ratio
114
+ var brightest = Math.max(foregroundLuminance, backgroundLuminance);
115
+ var darkest = Math.min(foregroundLuminance, backgroundLuminance);
116
+ return (brightest + 0.05) / (darkest + 0.05);
117
+ }
118
+ export function deltaE(rgbA, rgbB) {
119
+ let labA = rgbToLab(rgbA);
120
+ let labB = rgbToLab(rgbB);
121
+ let deltaL = labA[0] - labB[0];
122
+ let deltaA = labA[1] - labB[1];
123
+ let deltaB = labA[2] - labB[2];
124
+ let c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
125
+ let c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
126
+ let deltaC = c1 - c2;
127
+ let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
128
+ deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
129
+ let sc = 1.0 + 0.045 * c1;
130
+ let sh = 1.0 + 0.015 * c1;
131
+ let deltaLKlsl = deltaL / 1.0;
132
+ let deltaCkcsc = deltaC / sc;
133
+ let deltaHkhsh = deltaH / sh;
134
+ let i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh;
135
+ return i < 0 ? 0 : Math.sqrt(i);
136
+ }
137
+ function rgbToLab(rgb) {
138
+ let r = rgb[0] / 255,
139
+ g = rgb[1] / 255,
140
+ b = rgb[2] / 255,
141
+ x,
142
+ y,
143
+ z;
144
+ r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
145
+ g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
146
+ b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
147
+ x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
148
+ y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.0;
149
+ z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
150
+ x = x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787 * x + 16 / 116;
151
+ y = y > 0.008856 ? Math.pow(y, 1 / 3) : 7.787 * y + 16 / 116;
152
+ z = z > 0.008856 ? Math.pow(z, 1 / 3) : 7.787 * z + 16 / 116;
153
+ return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
154
+ }
@@ -0,0 +1,31 @@
1
+ import tokens from '../artifacts/token-names';
2
+ import { CUSTOM_THEME_ATTRIBUTE, THEME_DATA_ATTRIBUTE } from '../constants';
3
+ import { hash } from './hash';
4
+ export function findMissingCustomStyleElements(UNSAFE_themeOptions, mode) {
5
+ const optionString = JSON.stringify(UNSAFE_themeOptions);
6
+ const uniqueId = hash(optionString);
7
+ const attrOfMissingCustomStyles = [];
8
+ (mode === 'auto' ? ['light', 'dark'] : [mode]).forEach(themeId => {
9
+ const element = document.head.querySelector(`style[${CUSTOM_THEME_ATTRIBUTE}="${uniqueId}"][${THEME_DATA_ATTRIBUTE}="${themeId}"]`);
10
+ if (element) {
11
+ // Append the existing custom styles to take precedence over others
12
+ document.head.appendChild(element);
13
+ } else {
14
+ attrOfMissingCustomStyles.push(themeId);
15
+ }
16
+ });
17
+ return attrOfMissingCustomStyles;
18
+ }
19
+ export function limitSizeOfCustomStyleElements(sizeThreshold) {
20
+ const styleTags = [...document.head.querySelectorAll(`style[${CUSTOM_THEME_ATTRIBUTE}][${THEME_DATA_ATTRIBUTE}]`)];
21
+ if (styleTags.length < sizeThreshold) {
22
+ return;
23
+ }
24
+ styleTags.slice(0, styleTags.length - (sizeThreshold - 1)).forEach(element => element.remove());
25
+ }
26
+ export function reduceTokenMap(tokenMap, themeRamp) {
27
+ return Object.entries(tokenMap).reduce((acc, [key, value]) => {
28
+ const cssVar = tokens[key];
29
+ return cssVar ? `${acc}\n ${cssVar}: ${themeRamp[value]};` : acc;
30
+ }, '');
31
+ }
@@ -0,0 +1,68 @@
1
+ import tokenValuesDark from '../artifacts/atlassian-dark-token-value-for-contrast-check';
2
+ import tokenValuesLight from '../artifacts/atlassian-light-token-value-for-contrast-check';
3
+ import { getContrastRatio } from './color-utils';
4
+ export const additionalChecks = [{
5
+ foreground: 'color.text.brand',
6
+ backgroundLight: 'elevation.surface.sunken',
7
+ backgroundDark: 'elevation.surface.overlay',
8
+ desiredContrast: 4.5,
9
+ updatedTokens: [
10
+ // In light mode: darken the following tokens by one base token
11
+ // In dark mode: lighten the following tokens by one base token
12
+ 'color.text.brand', 'color.text.selected', 'color.link', 'color.link.pressed', 'color.icon.brand', 'color.icon.selected']
13
+ }, {
14
+ foreground: 'color.text.selected',
15
+ backgroundLight: 'color.background.selected',
16
+ backgroundDark: 'color.background.selected',
17
+ desiredContrast: 4.5,
18
+ // In light mode: darken the following tokens by one base token
19
+ // In dark mode: lighten the following tokens by one base token
20
+ updatedTokens: ['color.text.selected', 'color.icon.selected']
21
+ }, {
22
+ foreground: 'color.border.brand',
23
+ backgroundLight: 'elevation.surface.sunken',
24
+ backgroundDark: 'elevation.surface.overlay',
25
+ desiredContrast: 3,
26
+ // In light mode: darken the following tokens by one base token
27
+ // In dark mode: lighten the following tokens by one base toke
28
+ updatedTokens: ['color.border.brand', 'color.border.selected']
29
+ }, {
30
+ foreground: 'color.chart.brand',
31
+ backgroundLight: 'elevation.surface.sunken',
32
+ backgroundDark: 'elevation.surface.overlay',
33
+ desiredContrast: 3,
34
+ // In light mode: darken the following tokens by one base token
35
+ // In dark mode: lighten the following tokens by one base token
36
+ updatedTokens: ['color.chart.brand', 'color.chart.brand.hovered']
37
+ }];
38
+ const getColorFromTokenRaw = (tokenName, mode) => {
39
+ return mode === 'light' ? tokenValuesLight[tokenName] : tokenValuesDark[tokenName];
40
+ };
41
+ export const additionalContrastChecker = ({
42
+ customThemeTokenMap,
43
+ mode,
44
+ themeRamp
45
+ }) => {
46
+ const updatedCustomThemeTokenMap = {};
47
+ const brandTokens = Object.keys(customThemeTokenMap);
48
+ additionalChecks.forEach(pairing => {
49
+ const {
50
+ backgroundLight,
51
+ backgroundDark,
52
+ foreground,
53
+ desiredContrast,
54
+ updatedTokens
55
+ } = pairing;
56
+ const background = mode === 'light' ? backgroundLight : backgroundDark;
57
+ const foregroundColor = brandTokens.includes(foreground) ? themeRamp[customThemeTokenMap[foreground]] : getColorFromTokenRaw(foreground, mode);
58
+ const backgroundColor = brandTokens.includes(background) ? themeRamp[customThemeTokenMap[background]] : getColorFromTokenRaw(background, mode);
59
+ const contrast = getContrastRatio(foregroundColor, backgroundColor);
60
+ if (contrast <= desiredContrast) {
61
+ updatedTokens.forEach(token => {
62
+ const rampValue = customThemeTokenMap[token];
63
+ updatedCustomThemeTokenMap[token] = mode === 'light' ? rampValue + 1 : rampValue - 1;
64
+ });
65
+ }
66
+ });
67
+ return updatedCustomThemeTokenMap;
68
+ };
@@ -0,0 +1,187 @@
1
+ import rawTokensDark from '../artifacts/tokens-raw/atlassian-dark';
2
+ import { deltaE, getContrastRatio, hexToHSL, hexToRgb, hexToRgbA, HSLToRGB, relativeLuminanceW3C, rgbToHex } from './color-utils';
3
+ import { additionalContrastChecker } from './custom-theme-token-contrast-check';
4
+ import { argbFromRgba, Contrast, Hct, rgbaFromArgb } from './hct-color-utils';
5
+ const lowLuminanceContrastRatios = [1.12, 1.33, 2.03, 2.73, 3.33, 4.27, 5.2, 6.62, 12.46, 15.98];
6
+ const highLuminanceContrastRatios = [1.08, 1.24, 1.55, 1.99, 2.45, 3.34, 4.64, 6.1, 10.19, 13.43];
7
+ export const getClosestColorIndex = (themeRamp, brandColor) => {
8
+ // Iterate over themeRamp and find whichever color is closest to brandColor
9
+ let closestColorIndex = 0;
10
+ let closestColorDistance = null;
11
+ themeRamp.forEach((value, index) => {
12
+ const distance = deltaE(hexToRgb(value), hexToRgb(brandColor));
13
+ if (closestColorDistance === null || distance < closestColorDistance) {
14
+ closestColorIndex = index;
15
+ closestColorDistance = distance;
16
+ }
17
+ });
18
+ return closestColorIndex;
19
+ };
20
+ export const generateColors = brandColor => {
21
+ // Determine luminance
22
+ const HSLBrandColorHue = hexToHSL(brandColor)[0];
23
+ const baseRgb = HSLToRGB(HSLBrandColorHue, 100, 60);
24
+ const isLowLuminance = relativeLuminanceW3C(baseRgb[0], baseRgb[1], baseRgb[2]) < 0.4;
25
+ // Choose right palette
26
+ const themeRatios = isLowLuminance ? lowLuminanceContrastRatios : highLuminanceContrastRatios;
27
+ const brandRgba = hexToRgbA(brandColor);
28
+ const hctColor = Hct.fromInt(argbFromRgba({
29
+ r: brandRgba[0],
30
+ g: brandRgba[1],
31
+ b: brandRgba[2],
32
+ a: brandRgba[3]
33
+ }));
34
+ const themeRamp = themeRatios.map(contrast => {
35
+ const rgbaColor = rgbaFromArgb(Hct.from(hctColor.hue, hctColor.chroma, Contrast.darker(100, contrast) + 0.25 // Material's utils provide an offset
36
+ ).toInt());
37
+ return rgbToHex(rgbaColor.r, rgbaColor.g, rgbaColor.b);
38
+ });
39
+ const closestColorIndex = getClosestColorIndex(themeRamp, brandColor);
40
+
41
+ // Replace closet color with brandColor
42
+ const updatedThemeRamp = [...themeRamp];
43
+ updatedThemeRamp[closestColorIndex] = brandColor;
44
+ return updatedThemeRamp;
45
+ };
46
+
47
+ /**
48
+ * Return the interaction tokens for a color, given its ramp position and the number of
49
+ * needed interaction states. Use higher-indexed colors (i.e. darker colors) if possible;
50
+ * if there's not enough room to shift up for the required number of interaction tokens,
51
+ * it goes as far as it can, then returns lighter colors lower down the ramp instead.
52
+ *
53
+ * Returns an array of the resulting colors
54
+ */
55
+ function getInteractionStates(rampPosition, number, colors) {
56
+ const result = [];
57
+ for (let i = 1; i <= number; i++) {
58
+ if (rampPosition + i < colors.length) {
59
+ result.push(rampPosition + i);
60
+ } else {
61
+ result.push(rampPosition - (i - (colors.length - 1 - rampPosition)));
62
+ }
63
+ }
64
+ return result;
65
+ }
66
+ export const generateTokenMap = (brandColor, mode, themeRamp) => {
67
+ const colors = themeRamp || generateColors(brandColor);
68
+ const closestColorIndex = getClosestColorIndex(colors, brandColor);
69
+ let customThemeTokenMapLight = {};
70
+ let customThemeTokenMapDark = {};
71
+ const inputContrast = getContrastRatio(brandColor, '#FFFFFF');
72
+ // Branch based on brandColor's contrast against white
73
+ if (inputContrast >= 4.5) {
74
+ /**
75
+ * Generate interaction tokens for
76
+ * - color.background.brand.bold
77
+ * - color.background.selected.bold
78
+ */
79
+ const [brandBoldSelectedHoveredIndex, brandBoldSelectedPressedIndex] = getInteractionStates(closestColorIndex, 2, colors);
80
+
81
+ /**
82
+ * Generate interaction token for color.link:
83
+ * If inputted color replaces X1000
84
+ * - Pressed = X900
85
+ *
86
+ * If inputted color replaces X700-X900
87
+ * - Shift one 1 step darker
88
+ */
89
+ const [linkPressed] = getInteractionStates(closestColorIndex, 1, colors);
90
+ customThemeTokenMapLight = {
91
+ 'color.text.brand': closestColorIndex,
92
+ 'color.icon.brand': closestColorIndex,
93
+ 'color.background.brand.bold': closestColorIndex,
94
+ 'color.background.brand.bold.hovered': brandBoldSelectedHoveredIndex,
95
+ 'color.background.brand.bold.pressed': brandBoldSelectedPressedIndex,
96
+ 'color.border.brand': closestColorIndex,
97
+ 'color.text.selected': closestColorIndex,
98
+ 'color.icon.selected': closestColorIndex,
99
+ 'color.background.selected.bold': closestColorIndex,
100
+ 'color.background.selected.bold.hovered': brandBoldSelectedHoveredIndex,
101
+ 'color.background.selected.bold.pressed': brandBoldSelectedPressedIndex,
102
+ 'color.border.selected': closestColorIndex,
103
+ 'color.link': closestColorIndex,
104
+ 'color.link.pressed': linkPressed,
105
+ 'color.chart.brand': 5,
106
+ 'color.chart.brand.hovered': 6,
107
+ 'color.background.selected': 0,
108
+ 'color.background.selected.hovered': 1,
109
+ 'color.background.selected.pressed': 2
110
+ };
111
+ } else {
112
+ customThemeTokenMapLight = {
113
+ 'color.background.brand.bold': 6,
114
+ 'color.background.brand.bold.hovered': 7,
115
+ 'color.background.brand.bold.pressed': 8,
116
+ 'color.border.brand': 6,
117
+ 'color.background.selected.bold': 6,
118
+ 'color.background.selected.bold.hovered': 7,
119
+ 'color.background.selected.bold.pressed': 8,
120
+ 'color.text.brand': 6,
121
+ 'color.icon.brand': 6,
122
+ 'color.chart.brand': 5,
123
+ 'color.chart.brand.hovered': 6,
124
+ 'color.text.selected': 6,
125
+ 'color.icon.selected': 6,
126
+ 'color.border.selected': 6,
127
+ 'color.background.selected': 0,
128
+ 'color.background.selected.hovered': 1,
129
+ 'color.background.selected.pressed': 2,
130
+ 'color.link': 6,
131
+ 'color.link.pressed': 7
132
+ };
133
+ }
134
+ if (mode === 'light') {
135
+ return {
136
+ light: customThemeTokenMapLight
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Generate dark mode values using rule of symmetry
142
+ */
143
+ Object.entries(customThemeTokenMapLight).forEach(([key, value]) => {
144
+ customThemeTokenMapDark[key] = 9 - value;
145
+ });
146
+
147
+ /**
148
+ * If the input brand color < 4.5, and it meets 4.5 contrast again inverse text color
149
+ * in dark mode, shift color.background.brand.bold to the brand color
150
+ */
151
+ if (inputContrast < 4.5) {
152
+ var _rawTokensDark$find;
153
+ const inverseTextColor = (_rawTokensDark$find = rawTokensDark.find(token => token.cleanName === 'color.text.inverse')) === null || _rawTokensDark$find === void 0 ? void 0 : _rawTokensDark$find.value;
154
+ if (getContrastRatio(inverseTextColor, brandColor) >= 4.5 && closestColorIndex >= 2) {
155
+ customThemeTokenMapDark['color.background.brand.bold'] = closestColorIndex;
156
+ customThemeTokenMapDark['color.background.brand.bold.hovered'] = closestColorIndex - 1;
157
+ customThemeTokenMapDark['color.background.brand.bold.pressed'] = closestColorIndex - 2;
158
+ }
159
+ }
160
+ if (mode === 'dark') {
161
+ return {
162
+ dark: customThemeTokenMapDark
163
+ };
164
+ }
165
+ return {
166
+ light: customThemeTokenMapLight,
167
+ dark: customThemeTokenMapDark
168
+ };
169
+ };
170
+ export const generateTokenMapWithContrastCheck = (brandColor, mode, themeRamp) => {
171
+ const colors = themeRamp || generateColors(brandColor);
172
+ const tokenMaps = generateTokenMap(brandColor, mode, colors);
173
+ const result = {};
174
+ Object.entries(tokenMaps).forEach(([mode, map]) => {
175
+ if (mode === 'light' || mode === 'dark') {
176
+ result[mode] = {
177
+ ...map,
178
+ ...additionalContrastChecker({
179
+ customThemeTokenMap: map,
180
+ mode,
181
+ themeRamp: colors
182
+ })
183
+ };
184
+ }
185
+ });
186
+ return result;
187
+ };
@@ -0,0 +1,10 @@
1
+ export const hash = str => {
2
+ let hash = 0;
3
+ for (let i = 0; i < str.length; i++) {
4
+ const char = str.charCodeAt(i);
5
+ hash = (hash << 5) - hash + char;
6
+ hash &= hash; // Convert to 32bit integer
7
+ }
8
+
9
+ return new Uint32Array([hash])[0].toString(36);
10
+ };