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);