@atlaskit/tokens
Version:
Design tokens are the single source of truth to name and store design decisions.
155 lines (152 loc) • 6.22 kB
JavaScript
import rawTokensDark from '../artifacts/atlassian-dark-token-value-for-contrast-check';
import { generateColors } from './generate-colors';
import { getClosestColorIndex } from './get-closest-color-index';
import { getContrastRatio } from './get-contrast-ratio';
/**
* Return the interaction tokens for a color, given its ramp position and the number of
* needed interaction states. Use higher-indexed colors (i.e. darker colors) if possible;
* if there's not enough room to shift up for the required number of interaction tokens,
* it goes as far as it can, then returns lighter colors lower down the ramp instead.
*
* Returns an array of the resulting colors
*/
function getInteractionStates(rampPosition, number, colors) {
const result = [];
for (let i = 1; i <= number; i++) {
if (rampPosition + i < colors.length) {
result.push(rampPosition + i);
} else {
result.push(rampPosition - (i - (colors.length - 1 - rampPosition)));
}
}
return result;
}
export const generateTokenMap = (brandColor, mode, themeRamp) => {
const {
ramp,
replacedColor
} = generateColors(brandColor);
const colors = themeRamp || ramp;
const closestColorIndex = getClosestColorIndex(colors, brandColor);
let customThemeTokenMapLight = {};
let customThemeTokenMapDark = {};
const inputContrast = getContrastRatio(brandColor, '#FFFFFF');
// Branch based on brandColor's contrast against white
if (inputContrast >= 4.5) {
/**
* Generate interaction tokens for
* - color.background.brand.bold
* - color.background.selected.bold
*/
const [brandBoldSelectedHoveredIndex, brandBoldSelectedPressedIndex] = getInteractionStates(closestColorIndex, 2, colors);
let brandTextIndex = closestColorIndex;
if (inputContrast < 5.4 && inputContrast >= 4.8 && closestColorIndex === 6) {
// Use the one-level darker closest color (X800) for color.text.brand
// and color.link to avoid contrast breaches
brandTextIndex = closestColorIndex + 1;
}
/**
* Generate interaction token for color.link:
* If inputted color replaces X1000
* - Pressed = X900
*
* If inputted color replaces X700-X900
* - Shift one 1 step darker
*/
const [linkPressed] = getInteractionStates(brandTextIndex, 1, colors);
customThemeTokenMapLight = {
'color.text.brand': brandTextIndex,
'color.icon.brand': closestColorIndex,
'color.background.brand.subtlest': 0,
'color.background.brand.subtlest.hovered': 1,
'color.background.brand.subtlest.pressed': 2,
'color.background.brand.bold': closestColorIndex,
'color.background.brand.bold.hovered': brandBoldSelectedHoveredIndex,
'color.background.brand.bold.pressed': brandBoldSelectedPressedIndex,
'color.background.brand.boldest': 9,
'color.background.brand.boldest.hovered': 8,
'color.background.brand.boldest.pressed': 7,
'color.border.brand': closestColorIndex,
'color.text.selected': brandTextIndex,
'color.icon.selected': closestColorIndex,
'color.background.selected.bold': closestColorIndex,
'color.background.selected.bold.hovered': brandBoldSelectedHoveredIndex,
'color.background.selected.bold.pressed': brandBoldSelectedPressedIndex,
'color.border.selected': closestColorIndex,
'color.link': brandTextIndex,
'color.link.pressed': linkPressed,
'color.chart.brand': 5,
'color.chart.brand.hovered': 6,
'color.background.selected': 0,
'color.background.selected.hovered': 1,
'color.background.selected.pressed': 2
};
} else {
let brandBackgroundIndex = 6;
if (inputContrast < 4.5 && inputContrast >= 4 && closestColorIndex === 6) {
// Use the generated closest color instead of the input brand color for
// color.background.selected.bold and color.background.brand.bold
// to avoid contrast breaches
brandBackgroundIndex = replacedColor;
}
customThemeTokenMapLight = {
'color.background.brand.subtlest': 0,
'color.background.brand.subtlest.hovered': 1,
'color.background.brand.subtlest.pressed': 2,
'color.background.brand.bold': brandBackgroundIndex,
'color.background.brand.bold.hovered': 7,
'color.background.brand.bold.pressed': 8,
'color.background.brand.boldest': 9,
'color.background.brand.boldest.hovered': 8,
'color.background.brand.boldest.pressed': 7,
'color.border.brand': 6,
'color.background.selected.bold': brandBackgroundIndex,
'color.background.selected.bold.hovered': 7,
'color.background.selected.bold.pressed': 8,
'color.text.brand': 6,
'color.icon.brand': 6,
'color.chart.brand': 5,
'color.chart.brand.hovered': 6,
'color.text.selected': 6,
'color.icon.selected': 6,
'color.border.selected': 6,
'color.background.selected': 0,
'color.background.selected.hovered': 1,
'color.background.selected.pressed': 2,
'color.link': 6,
'color.link.pressed': 7
};
}
if (mode === 'light') {
return {
light: customThemeTokenMapLight
};
}
/**
* Generate dark mode values using rule of symmetry
*/
Object.entries(customThemeTokenMapLight).forEach(([key, value]) => {
customThemeTokenMapDark[key] = 9 - (typeof value === 'string' ? closestColorIndex : value);
});
/**
* If the input brand color < 4.5, and it meets 4.5 contrast again inverse text color
* in dark mode, shift color.background.brand.bold to the brand color
*/
if (inputContrast < 4.5) {
const inverseTextColor = rawTokensDark['color.text.inverse'];
if (getContrastRatio(inverseTextColor, brandColor) >= 4.5 && closestColorIndex >= 2) {
customThemeTokenMapDark['color.background.brand.bold'] = closestColorIndex;
customThemeTokenMapDark['color.background.brand.bold.hovered'] = closestColorIndex - 1;
customThemeTokenMapDark['color.background.brand.bold.pressed'] = closestColorIndex - 2;
}
}
if (mode === 'dark') {
return {
dark: customThemeTokenMapDark
};
}
return {
light: customThemeTokenMapLight,
dark: customThemeTokenMapDark
};
};