@wordpress/components
Version:
UI components for WordPress.
143 lines (118 loc) • 3.97 kB
text/typescript
/**
* 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' ] >;
}