Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
mirka committed Oct 27, 2022
1 parent 5971042 commit de9d304
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 98 deletions.
114 changes: 114 additions & 0 deletions packages/components/src/theme/color-algorithms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* External dependencies
*/
import { colord, extend } from 'colord';
import a11yPlugin from 'colord/plugins/a11y';
import namesPlugin from 'colord/plugins/names';

/**
* Internal dependencies
*/
import type { ThemeInputValues, ThemeOutputValues } from './types';
import { COLORS } from '../utils';

extend( [ namesPlugin, a11yPlugin ] );

export function generateThemeVariables(
inputs: ThemeInputValues
): ThemeOutputValues {
validateInputs( inputs );

return {
colors: {
...generateAccentDependentColors( inputs.accent ),
...generateBackgroundDependentColors( inputs.background ),
},
};
}

// TODO: Add unit tests
function validateInputs( inputs: ThemeInputValues ) {
for ( const [ key, value ] of Object.entries( inputs ) ) {
if ( ! colord( value ).isValid() ) {
// eslint-disable-next-line no-console
console.warn(
`wp.components.Theme: "${ value }" is not a valid color value for the '${ key }' prop.`
);
}
}

if (
inputs.background &&
! colord( inputs.background ).isReadable(
inputs.accent || COLORS.ui.theme
)
) {
// eslint-disable-next-line no-console
console.warn(
`wp.components.Theme: The background color provided ("${ inputs.background }") does not have sufficient contrast against the accent color ("${ inputs.accent }").`
);
}
}

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 ];
}

// TODO: Add unit test so the result of this matches the default case (#fff to #1e1e1e)
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 ThemeOutputValues[ 'colors' ][ 'gray' ];
}
20 changes: 3 additions & 17 deletions packages/components/src/theme/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
/**
* External dependencies
*/
import { colord, extend } from 'colord';
import a11yPlugin from 'colord/plugins/a11y';
import namesPlugin from 'colord/plugins/names';

/**
* Internal dependencies
*/
import type { ThemeProps } from './types';
import type { WordPressComponentProps } from '../ui/context';
import { Wrapper } from './styles';

extend( [ namesPlugin, a11yPlugin ] );
import { generateThemeVariables } from './color-algorithms';

/**
* `Theme` allows defining theme variables for components in the `@wordpress/components` package.
Expand All @@ -37,15 +29,9 @@ extend( [ namesPlugin, a11yPlugin ] );
* ```
*/
function Theme( props: WordPressComponentProps< ThemeProps, 'div', true > ) {
const { accent } = props;
if ( accent && ! colord( accent ).isValid() ) {
// eslint-disable-next-line no-console
console.warn(
`wp.components.Theme: "${ accent }" is not a valid color value for the 'accent' prop.`
);
}
const themeVariables = generateThemeVariables( props );

return <Wrapper { ...props } />;
return <Wrapper { ...themeVariables } />;
}

export default Theme;
98 changes: 18 additions & 80 deletions packages/components/src/theme/styles.ts
Original file line number Diff line number Diff line change
@@ -1,97 +1,35 @@
/**
* External dependencies
*/
import { colord } from 'colord';
import styled from '@emotion/styled';
import { css } from '@emotion/react';

/**
* Internal dependencies
*/
import type { ThemeProps } from './types';
import { COLORS } from '../utils';
import type { ThemeOutputValues } from './types';

const getForegroundForColor = ( color: string ) =>
colord( color ).isDark() ? COLORS.white : COLORS.gray[ 900 ];

const accentColor = ( { accent }: ThemeProps ) =>
accent
? css`
--wp-components-color-accent: ${ accent };
--wp-components-color-accent-darker-10: ${ colord( accent )
.darken( 0.1 )
.toHex() };
--wp-components-color-accent-darker-20: ${ colord( accent )
.darken( 0.2 )
.toHex() };
--wp-components-color-accent-inverted: ${ getForegroundForColor(
accent
) };
`
: undefined;

// TODO: Add unit test so the result of this matches the default case (#fff to #1e1e1e)
function generateShades( background: string, foreground: string ) {
// Start from #fff (background)
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';
const range =
Math.abs(
colord( background ).toHsl().l - colord( foreground ).toHsl().l
) / 100;

const result: Record< string, string > = {};

Object.entries( shades ).forEach( ( [ k, v ] ) => {
result[ k ] = colord( background )
[ direction ]( ( v / limit ) * range )
.toHex();
} );

return result;
}

const backgroundColor = ( { accent, background }: ThemeProps ) => {
if ( ! background ) return;

const foreground = getForegroundForColor( background );

// TODO: Add unit test
if ( ! colord( background ).isReadable( accent || COLORS.ui.theme ) ) {
// eslint-disable-next-line no-console
console.warn(
`wp.components.Theme: The background color provided ("${ background }") does not have sufficient contrast against the accent color ("${ accent }").`
);
}

const shades = Object.entries( generateShades( background, foreground ) )
const colorVariables = ( { colors }: ThemeOutputValues ) => {
const shades = Object.entries( colors.gray || {} )
.map( ( [ k, v ] ) => `--wp-components-color-gray-${ k }: ${ v };` )
.join( '' );

return css`
--wp-components-color-background: ${ background };
--wp-components-color-foreground: ${ foreground };
--wp-components-color-foreground-inverted: ${ getForegroundForColor(
foreground
) };
return [
css`
--wp-components-color-accent: ${ colors.accent };
--wp-components-color-accent-darker-10: ${ colors.accentDarker10 };
--wp-components-color-accent-darker-20: ${ colors.accentDarker20 };
--wp-components-color-accent-inverted: ${ colors.accentInverted };
--wp-components-color-background: ${ colors.background };
--wp-components-color-foreground: ${ colors.foreground };
--wp-components-color-foreground-inverted: ${ colors.foregroundInverted };
${ shades }
`;
${ shades }
`,
];
};

export const Wrapper = styled.div< ThemeProps >`
${ accentColor }
${ backgroundColor }
export const Wrapper = styled.div< ThemeOutputValues >`
${ colorVariables }
`;
31 changes: 30 additions & 1 deletion packages/components/src/theme/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import type { ReactNode } from 'react';

export type ThemeProps = {
export type ThemeInputValues = {
/**
* Used to set the accent color (used by components as the primary color).
*
Expand All @@ -14,7 +14,36 @@ export type ThemeProps = {
* `var(--my-custom-property)`) are _not_ supported values for this property.
*/
accent?: string;
/**
* TODO: Write description
*/
background?: string;
};

export type ThemeOutputValues = {
colors: Partial< {
accent: string;
accentDarker10: string;
accentDarker20: string;
/** Foreground color to use when accent color is the background. */
accentInverted: string;
background: string;
foreground: string;
/** Foreground color to use when foreground color is the background. */
foregroundInverted: string;
gray: {
100: string;
200: string;
300: string;
400: string;
600: string;
700: string;
800: string;
};
} >;
};

export type ThemeProps = ThemeInputValues & {
/**
* The children elements.
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/components/src/utils/theme-variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ $components-color-foreground: var(--wp-components-color-foreground, $gray-900);
// Used when placing text on the foreground color.
$components-color-foreground-inverted: var(--wp-components-color-foreground-inverted, $white);

$components-color-gray-100: var(--wp-components-color-gray-100, $gray-100);
$components-color-gray-200: var(--wp-components-color-gray-200, $gray-200);
$components-color-gray-300: var(--wp-components-color-gray-300, $gray-300);
$components-color-gray-400: var(--wp-components-color-gray-400, $gray-400);
$components-color-gray-600: var(--wp-components-color-gray-600, $gray-600);
$components-color-gray-700: var(--wp-components-color-gray-700, $gray-700);
$components-color-gray-800: var(--wp-components-color-gray-800, $gray-800);

0 comments on commit de9d304

Please sign in to comment.