From de9d3047eb67a12a14bcdf93e40fc8138dc0b02a Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Fri, 28 Oct 2022 06:11:15 +0900 Subject: [PATCH] Refactor --- .../components/src/theme/color-algorithms.ts | 114 ++++++++++++++++++ packages/components/src/theme/index.tsx | 20 +-- packages/components/src/theme/styles.ts | 98 +++------------ packages/components/src/theme/types.ts | 31 ++++- .../components/src/utils/theme-variables.scss | 4 + 5 files changed, 169 insertions(+), 98 deletions(-) create mode 100644 packages/components/src/theme/color-algorithms.ts diff --git a/packages/components/src/theme/color-algorithms.ts b/packages/components/src/theme/color-algorithms.ts new file mode 100644 index 00000000000000..7c96a8d45cf1b1 --- /dev/null +++ b/packages/components/src/theme/color-algorithms.ts @@ -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' ]; +} diff --git a/packages/components/src/theme/index.tsx b/packages/components/src/theme/index.tsx index 7504c7d5362909..67d0b5eb864d60 100644 --- a/packages/components/src/theme/index.tsx +++ b/packages/components/src/theme/index.tsx @@ -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. @@ -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 ; + return ; } export default Theme; diff --git a/packages/components/src/theme/styles.ts b/packages/components/src/theme/styles.ts index 9c15d6fc97b49f..fbe7c15413663d 100644 --- a/packages/components/src/theme/styles.ts +++ b/packages/components/src/theme/styles.ts @@ -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 } `; diff --git a/packages/components/src/theme/types.ts b/packages/components/src/theme/types.ts index 49dc51538862c0..7426fc7019b80c 100644 --- a/packages/components/src/theme/types.ts +++ b/packages/components/src/theme/types.ts @@ -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). * @@ -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. */ diff --git a/packages/components/src/utils/theme-variables.scss b/packages/components/src/utils/theme-variables.scss index 6f23bb7afd33a6..872b42c84368a7 100644 --- a/packages/components/src/utils/theme-variables.scss +++ b/packages/components/src/utils/theme-variables.scss @@ -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);