/
color-algorithms.ts
138 lines (114 loc) · 3.86 KB
/
color-algorithms.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/**
* External dependencies
*/
import { colord, extend } from 'colord';
import a11yPlugin from 'colord/plugins/a11y';
import namesPlugin from 'colord/plugins/names';
/**
* WordPress dependencies
*/
import warning from '@wordpress/warning';
/**
* Internal dependencies
*/
import type { ThemeInputValues, ThemeOutputValues } from './types';
import { COLORS } from '../utils';
extend( [ namesPlugin, a11yPlugin ] );
export function generateThemeVariables(
inputs: ThemeInputValues
): ThemeOutputValues {
validateInputs( inputs );
const generatedColors = {
...generateAccentDependentColors( inputs.accent ),
...generateBackgroundDependentColors( inputs.background ),
};
warnContrastIssues( checkContrasts( inputs, generatedColors ) );
return { colors: generatedColors };
}
function validateInputs( inputs: ThemeInputValues ) {
for ( const [ key, value ] of Object.entries( inputs ) ) {
if ( typeof value !== 'undefined' && ! colord( value ).isValid() ) {
warning(
`wp.components.Theme: "${ value }" is not a valid color value for the '${ key }' prop.`
);
}
}
}
export function checkContrasts(
inputs: ThemeInputValues,
outputs: ThemeOutputValues[ 'colors' ]
) {
const background = inputs.background || COLORS.white;
const accent = inputs.accent || '#3858e9';
const foreground = outputs.foreground || COLORS.gray[ 900 ];
const gray = outputs.gray || COLORS.gray;
return {
accent: colord( background ).isReadable( accent )
? undefined
: `The background color ("${ background }") does not have sufficient contrast against the accent color ("${ accent }").`,
foreground: colord( background ).isReadable( foreground )
? undefined
: `The background color provided ("${ background }") does not have sufficient contrast against the standard foreground colors.`,
grays:
colord( background ).contrast( gray[ 600 ] ) >= 3 &&
colord( background ).contrast( gray[ 700 ] ) >= 4.5
? undefined
: `The background color provided ("${ background }") cannot generate a set of grayscale foreground colors with sufficient contrast. Try adjusting the color to be lighter or darker.`,
};
}
function warnContrastIssues( issues: ReturnType< typeof checkContrasts > ) {
for ( const error of Object.values( issues ) ) {
if ( error ) {
warning( 'wp.components.Theme: ' + error );
}
}
}
function generateAccentDependentColors( accent?: string ) {
if ( ! accent ) return {};
return {
accent,
accentDarker10: colord( accent ).darken( 0.1 ).toHex(),
accentDarker20: colord( accent ).darken( 0.2 ).toHex(),
accentInverted: getForegroundForColor( accent ),
};
}
function generateBackgroundDependentColors( background?: string ) {
if ( ! background ) return {};
const foreground = getForegroundForColor( background );
return {
background,
foreground,
foregroundInverted: getForegroundForColor( foreground ),
gray: generateShades( background, foreground ),
};
}
function getForegroundForColor( color: string ) {
return colord( color ).isDark() ? COLORS.white : COLORS.gray[ 900 ];
}
export function generateShades( background: string, foreground: string ) {
// How much darkness you need to add to #fff to get the COLORS.gray[n] color
const SHADES = {
100: 0.06,
200: 0.121,
300: 0.132,
400: 0.2,
600: 0.42,
700: 0.543,
800: 0.821,
};
// Darkness of COLORS.gray[ 900 ], relative to #fff
const limit = 0.884;
const direction = colord( background ).isDark() ? 'lighten' : 'darken';
// Lightness delta between the background and foreground colors
const range =
Math.abs(
colord( background ).toHsl().l - colord( foreground ).toHsl().l
) / 100;
const result: Record< number, string > = {};
Object.entries( SHADES ).forEach( ( [ key, value ] ) => {
result[ parseInt( key ) ] = colord( background )
[ direction ]( ( value / limit ) * range )
.toHex();
} );
return result as NonNullable< ThemeOutputValues[ 'colors' ][ 'gray' ] >;
}