diff --git a/packages/style-engine/src/styles/border/index.ts b/packages/style-engine/src/styles/border/index.ts index 77ee189a5d92c3..465e78a09aa5fc 100644 --- a/packages/style-engine/src/styles/border/index.ts +++ b/packages/style-engine/src/styles/border/index.ts @@ -1,31 +1,45 @@ /** * Internal dependencies */ -import type { - BorderIndividualStyles, - BorderIndividualProperty, - GeneratedCSSRule, - Style, - StyleDefinition, - StyleOptions, -} from '../../types'; -import { generateRule, generateBoxRules, upperFirst } from '../utils'; +import type { BoxEdge, GenerateFunction, StyleDefinition } from '../../types'; +import { generateRule, generateBoxRules, camelCaseJoin } from '../utils'; -const color = { +/** + * Creates a function for generating CSS rules when the style path is the same as the camelCase CSS property used in React. + * + * @param path An array of strings representing the path to the style value in the style object. + * + * @return A function that generates CSS rules. + */ +function createBorderGenerateFunction( path: string[] ): GenerateFunction { + return ( style, options ) => + generateRule( style, options, path, camelCaseJoin( path ) ); +} + +/** + * Creates a function for generating border-{top,bottom,left,right}-{color,style,width} CSS rules. + * + * @param edge The edge to create CSS rules for. + * + * @return A function that generates CSS rules. + */ +function createBorderEdgeGenerateFunction( edge: BoxEdge ): GenerateFunction { + return ( style, options ) => { + return [ 'color', 'style', 'width' ].flatMap( ( key ) => { + const path = [ 'border', edge, key ]; + return createBorderGenerateFunction( path )( style, options ); + } ); + }; +} + +const color: StyleDefinition = { name: 'color', - generate: ( - style: Style, - options: StyleOptions, - path: string[] = [ 'border', 'color' ], - ruleKey: string = 'borderColor' - ): GeneratedCSSRule[] => { - return generateRule( style, options, path, ruleKey ); - }, + generate: createBorderGenerateFunction( [ 'border', 'color' ] ), }; -const radius = { +const radius: StyleDefinition = { name: 'radius', - generate: ( style: Style, options: StyleOptions ): GeneratedCSSRule[] => { + generate: ( style, options ) => { return generateBoxRules( style, options, @@ -39,104 +53,40 @@ const radius = { }, }; -const borderStyle = { +const borderStyle: StyleDefinition = { name: 'style', - generate: ( - style: Style, - options: StyleOptions, - path: string[] = [ 'border', 'style' ], - ruleKey: string = 'borderStyle' - ): GeneratedCSSRule[] => { - return generateRule( style, options, path, ruleKey ); - }, + generate: createBorderGenerateFunction( [ 'border', 'style' ] ), }; -const width = { +const width: StyleDefinition = { name: 'width', - generate: ( - style: Style, - options: StyleOptions, - path: string[] = [ 'border', 'width' ], - ruleKey: string = 'borderWidth' - ): GeneratedCSSRule[] => { - return generateRule( style, options, path, ruleKey ); - }, + generate: createBorderGenerateFunction( [ 'border', 'width' ] ), }; -const borderDefinitionsWithIndividualStyles: StyleDefinition[] = [ - color, - borderStyle, - width, -]; - -/** - * Returns a curried generator function with the individual border property ('top' | 'right' | 'bottom' | 'left') baked in. - * - * @param individualProperty Individual border property ('top' | 'right' | 'bottom' | 'left'). - * - * @return StyleDefinition[ 'generate' ] - */ -const createBorderGenerateFunction = - ( individualProperty: BorderIndividualProperty ) => - ( style: Style, options: StyleOptions ) => { - const styleValue: - | BorderIndividualStyles< typeof individualProperty > - | undefined = style?.border?.[ individualProperty ]; - - if ( ! styleValue ) { - return []; - } - - return borderDefinitionsWithIndividualStyles.reduce( - ( - acc: GeneratedCSSRule[], - borderDefinition: StyleDefinition - ): GeneratedCSSRule[] => { - const key = borderDefinition.name; - if ( - styleValue.hasOwnProperty( key ) && - typeof borderDefinition.generate === 'function' - ) { - const ruleKey = `border${ upperFirst( - individualProperty - ) }${ upperFirst( key ) }`; - acc.push( - ...borderDefinition.generate( - style, - options, - [ 'border', individualProperty, key ], - ruleKey - ) - ); - } - return acc; - }, - [] - ); - }; - -const borderTop = { +const borderTop: StyleDefinition = { name: 'borderTop', - generate: createBorderGenerateFunction( 'top' ), + generate: createBorderEdgeGenerateFunction( 'top' ), }; -const borderRight = { +const borderRight: StyleDefinition = { name: 'borderRight', - generate: createBorderGenerateFunction( 'right' ), + generate: createBorderEdgeGenerateFunction( 'right' ), }; -const borderBottom = { +const borderBottom: StyleDefinition = { name: 'borderBottom', - generate: createBorderGenerateFunction( 'bottom' ), + generate: createBorderEdgeGenerateFunction( 'bottom' ), }; -const borderLeft = { +const borderLeft: StyleDefinition = { name: 'borderLeft', - generate: createBorderGenerateFunction( 'left' ), + generate: createBorderEdgeGenerateFunction( 'left' ), }; export default [ - ...borderDefinitionsWithIndividualStyles, + color, + borderStyle, + width, radius, borderTop, borderRight, diff --git a/packages/style-engine/src/styles/utils.ts b/packages/style-engine/src/styles/utils.ts index 64a761ae5d359f..f0c3112d230b34 100644 --- a/packages/style-engine/src/styles/utils.ts +++ b/packages/style-engine/src/styles/utils.ts @@ -34,7 +34,7 @@ export function generateRule( options: StyleOptions, path: string[], ruleKey: string -) { +): GeneratedCSSRule[] { const styleValue: string | undefined = get( style, path ); return styleValue @@ -128,10 +128,23 @@ export function getCSSVarFromStyleValue( styleValue: string ): string { /** * Capitalizes the first letter in a string. * - * @param {string} str The string whose first letter the function will capitalize. + * @param string The string whose first letter the function will capitalize. * - * @return string A CSS var value. + * @return String with the first letter capitalized. */ -export function upperFirst( [ firstLetter, ...rest ]: string ) { +export function upperFirst( string: string ): string { + const [ firstLetter, ...rest ] = string; return firstLetter.toUpperCase() + rest.join( '' ); } + +/** + * Converts an array of strings into a camelCase string. + * + * @param strings The strings to join into a camelCase string. + * + * @return camelCase string. + */ +export function camelCaseJoin( strings: string[] ): string { + const [ firstItem, ...rest ] = strings; + return firstItem.toLowerCase() + rest.map( upperFirst ).join( '' ); +} diff --git a/packages/style-engine/src/test/utils.js b/packages/style-engine/src/test/utils.js index 7fccd013594420..62dec772979060 100644 --- a/packages/style-engine/src/test/utils.js +++ b/packages/style-engine/src/test/utils.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { upperFirst } from '../styles/utils'; +import { camelCaseJoin, upperFirst } from '../styles/utils'; describe( 'utils', () => { describe( 'upperFirst()', () => { @@ -9,4 +9,9 @@ describe( 'utils', () => { expect( upperFirst( 'toontown' ) ).toEqual( 'Toontown' ); } ); } ); + describe( 'camelCaseJoin()', () => { + it( 'should return a camelCase string', () => { + expect( camelCaseJoin( [ 'toon', 'town' ] ) ).toEqual( 'toonTown' ); + } ); + } ); } ); diff --git a/packages/style-engine/src/types.ts b/packages/style-engine/src/types.ts index a16430745f99ac..a700816f218ce8 100644 --- a/packages/style-engine/src/types.ts +++ b/packages/style-engine/src/types.ts @@ -3,21 +3,22 @@ */ import type { CSSProperties } from 'react'; -type BoxVariants = 'margin' | 'padding' | undefined; -export type Box< T extends BoxVariants = undefined > = { +type BoxVariant = 'margin' | 'padding'; +export interface Box< T extends BoxVariant | undefined = undefined > { top?: CSSProperties[ T extends undefined ? 'top' : `${ T }Top` ]; right?: CSSProperties[ T extends undefined ? 'right' : `${ T }Right` ]; bottom?: CSSProperties[ T extends undefined ? 'bottom' : `${ T }Bottom` ]; left?: CSSProperties[ T extends undefined ? 'left' : `${ T }Left` ]; -}; +} + +export type BoxEdge = 'top' | 'right' | 'bottom' | 'left'; -export type BorderIndividualProperty = 'top' | 'right' | 'bottom' | 'left'; // `T` is one of the values in `BorderIndividualProperty`. The expected CSSProperties key is something like `borderTopColor`. -export type BorderIndividualStyles< T extends BorderIndividualProperty > = { - color?: CSSProperties[ `border${ Capitalize< string & T > }Color` ]; - style?: CSSProperties[ `border${ Capitalize< string & T > }Style` ]; - width?: CSSProperties[ `border${ Capitalize< string & T > }Width` ]; -}; +export interface BorderIndividualStyles< T extends BoxEdge > { + color?: CSSProperties[ `border${ Capitalize< T > }Color` ]; + style?: CSSProperties[ `border${ Capitalize< T > }Style` ]; + width?: CSSProperties[ `border${ Capitalize< T > }Width` ]; +} export interface Style { border?: { @@ -65,16 +66,19 @@ export interface Style { }; } -export type CssRulesKeys = { default: string; individual: string }; +export interface CssRulesKeys { + default: string; + individual: string; +} -export type StyleOptions = { +export interface StyleOptions { /** * CSS selector for the generated style. */ selector?: string; -}; +} -export type GeneratedCSSRule = { +export interface GeneratedCSSRule { selector?: string; value: string; /** @@ -82,14 +86,13 @@ export type GeneratedCSSRule = { * E.g. `paddingTop` instead of `padding-top`. */ key: string; -}; +} + +export interface GenerateFunction { + ( style: Style, options: StyleOptions ): GeneratedCSSRule[]; +} export interface StyleDefinition { name: string; - generate?: ( - style: Style, - options: StyleOptions | {}, - path?: string[], - ruleKey?: string - ) => GeneratedCSSRule[]; + generate?: GenerateFunction; }