Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Style engine: add border to frontend #41803

Merged
merged 2 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/blocks/src/api/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = {
borderColor: {
value: [ 'border', 'color' ],
support: [ '__experimentalBorder', 'color' ],
useEngine: true,
},
borderRadius: {
value: [ 'border', 'radius' ],
Expand All @@ -43,62 +44,77 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = {
borderBottomLeftRadius: 'bottomLeft',
borderBottomRightRadius: 'bottomRight',
},
useEngine: true,
},
borderStyle: {
value: [ 'border', 'style' ],
support: [ '__experimentalBorder', 'style' ],
useEngine: true,
},
borderWidth: {
value: [ 'border', 'width' ],
support: [ '__experimentalBorder', 'width' ],
useEngine: true,
},
borderTopColor: {
value: [ 'border', 'top', 'color' ],
support: [ '__experimentalBorder', 'color' ],
useEngine: true,
},
borderTopStyle: {
value: [ 'border', 'top', 'style' ],
support: [ '__experimentalBorder', 'style' ],
useEngine: true,
},
borderTopWidth: {
value: [ 'border', 'top', 'width' ],
support: [ '__experimentalBorder', 'width' ],
useEngine: true,
},
borderRightColor: {
value: [ 'border', 'right', 'color' ],
support: [ '__experimentalBorder', 'color' ],
useEngine: true,
},
borderRightStyle: {
value: [ 'border', 'right', 'style' ],
support: [ '__experimentalBorder', 'style' ],
useEngine: true,
},
borderRightWidth: {
value: [ 'border', 'right', 'width' ],
support: [ '__experimentalBorder', 'width' ],
useEngine: true,
},
borderBottomColor: {
value: [ 'border', 'bottom', 'color' ],
support: [ '__experimentalBorder', 'color' ],
useEngine: true,
},
borderBottomStyle: {
value: [ 'border', 'bottom', 'style' ],
support: [ '__experimentalBorder', 'style' ],
useEngine: true,
},
borderBottomWidth: {
value: [ 'border', 'bottom', 'width' ],
support: [ '__experimentalBorder', 'width' ],
useEngine: true,
},
borderLeftColor: {
value: [ 'border', 'left', 'color' ],
support: [ '__experimentalBorder', 'color' ],
useEngine: true,
},
borderLeftStyle: {
value: [ 'border', 'left', 'style' ],
support: [ '__experimentalBorder', 'style' ],
useEngine: true,
},
borderLeftWidth: {
value: [ 'border', 'left', 'width' ],
support: [ '__experimentalBorder', 'width' ],
useEngine: true,
},
color: {
value: [ 'color', 'text' ],
Expand Down
145 changes: 145 additions & 0 deletions packages/style-engine/src/styles/border/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Internal dependencies
*/
import type {
BorderIndividualStyles,
BorderIndividualProperty,
GeneratedCSSRule,
Style,
StyleDefinition,
StyleOptions,
} from '../../types';
import { generateRule, generateBoxRules, upperFirst } from '../utils';

const color = {
name: 'color',
generate: (
style: Style,
options: StyleOptions,
path: string[] = [ 'border', 'color' ],
ruleKey: string = 'borderColor'
): GeneratedCSSRule[] => {
return generateRule( style, options, path, ruleKey );
},
};

const radius = {
name: 'radius',
generate: ( style: Style, options: StyleOptions ): GeneratedCSSRule[] => {
return generateBoxRules(
style,
options,
[ 'border', 'radius' ],
{
default: 'borderRadius',
individual: 'border%sRadius',
},
[ 'topLeft', 'topRight', 'bottomLeft', 'bottomRight' ]
);
},
};

const borderStyle = {
name: 'style',
generate: (
style: Style,
options: StyleOptions,
path: string[] = [ 'border', 'style' ],
ruleKey: string = 'borderStyle'
): GeneratedCSSRule[] => {
return generateRule( style, options, path, ruleKey );
},
};

const width = {
name: 'width',
generate: (
style: Style,
options: StyleOptions,
path: string[] = [ 'border', 'width' ],
ruleKey: string = 'borderWidth'
): GeneratedCSSRule[] => {
return generateRule( style, options, path, ruleKey );
},
};

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 = {
name: 'borderTop',
generate: createBorderGenerateFunction( 'top' ),
};

const borderRight = {
name: 'borderRight',
generate: createBorderGenerateFunction( 'right' ),
};

const borderBottom = {
name: 'borderBottom',
generate: createBorderGenerateFunction( 'bottom' ),
};

const borderLeft = {
name: 'borderLeft',
generate: createBorderGenerateFunction( 'left' ),
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm starting to think that we could refactor the JS style engine to use properties from a master "definitions" constant, which is basically what we do in hooks/style.js anyway using __EXPERIMENTAL_STYLE_PROPERTY

It's how we approached it in the backend.

And would allow us to store custom paths and CSS rules keys such as border-$s-color without the need to build them on the fly.

Not sure yet. I'll test it later.


export default [
...borderDefinitionsWithIndividualStyles,
radius,
borderTop,
borderRight,
borderBottom,
borderLeft,
];
8 changes: 7 additions & 1 deletion packages/style-engine/src/styles/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
/**
* Internal dependencies
*/
import border from './border';
import color from './color';
import spacing from './spacing';
import typography from './typography';

export const styleDefinitions = [ ...color, ...spacing, ...typography ];
export const styleDefinitions = [
...border,
...color,
...spacing,
...typography,
];
10 changes: 4 additions & 6 deletions packages/style-engine/src/styles/spacing/margin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import { generateBoxRules } from '../utils';
const margin = {
name: 'margin',
generate: ( style: Style, options: StyleOptions ) => {
return generateBoxRules(
style,
options,
[ 'spacing', 'margin' ],
'margin'
);
return generateBoxRules( style, options, [ 'spacing', 'margin' ], {
default: 'margin',
individual: 'margin%s',
} );
},
};

Expand Down
10 changes: 4 additions & 6 deletions packages/style-engine/src/styles/spacing/padding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import { generateBoxRules } from '../utils';
const padding = {
name: 'padding',
generate: ( style: Style, options: StyleOptions ) => {
return generateBoxRules(
style,
options,
[ 'spacing', 'padding' ],
'padding'
);
return generateBoxRules( style, options, [ 'spacing', 'padding' ], {
default: 'padding',
individual: 'padding%s',
} );
},
};

Expand Down
44 changes: 33 additions & 11 deletions packages/style-engine/src/styles/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
/**
* External dependencies
*/
import { get, upperFirst } from 'lodash';
import { get } from 'lodash';

/**
* Internal dependencies
*/
import type { GeneratedCSSRule, Style, Box, StyleOptions } from '../types';
import type {
CssRulesKeys,
GeneratedCSSRule,
Style,
Box,
StyleOptions,
} from '../types';
import {
VARIABLE_REFERENCE_PREFIX,
VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE,
Expand Down Expand Up @@ -45,18 +51,20 @@ export function generateRule(
/**
* Returns a JSON representation of the generated CSS rules taking into account box model properties, top, right, bottom, left.
*
* @param style Style object.
* @param options Options object with settings to adjust how the styles are generated.
* @param path An array of strings representing the path to the style value in the style object.
* @param ruleKey A CSS property key.
* @param style Style object.
* @param options Options object with settings to adjust how the styles are generated.
* @param path An array of strings representing the path to the style value in the style object.
* @param ruleKeys An array of CSS property keys and patterns.
* @param individualProperties The "sides" or individual properties for which to generate rules.
*
* @return GeneratedCSSRule[] CSS rules.
* @return GeneratedCSSRule[] CSS rules.
*/
export function generateBoxRules(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a follow up, maybe this could assume the responsibility of generateRule, similar to what we do in the backend.

style: Style,
options: StyleOptions,
path: string[],
ruleKey: string
ruleKeys: CssRulesKeys,
individualProperties: string[] = [ 'top', 'right', 'bottom', 'left' ]
): GeneratedCSSRule[] {
const boxStyle: Box | string | undefined = get( style, path );
if ( ! boxStyle ) {
Expand All @@ -67,17 +75,20 @@ export function generateBoxRules(
if ( typeof boxStyle === 'string' ) {
rules.push( {
selector: options?.selector,
key: ruleKey,
key: ruleKeys.default,
value: boxStyle,
} );
} else {
const sideRules = [ 'top', 'right', 'bottom', 'left' ].reduce(
const sideRules = individualProperties.reduce(
( acc: GeneratedCSSRule[], side: string ) => {
const value: string | undefined = get( boxStyle, [ side ] );
if ( value ) {
acc.push( {
selector: options?.selector,
key: `${ ruleKey }${ upperFirst( side ) }`,
key: ruleKeys?.individual.replace(
'%s',
upperFirst( side )
),
value,
} );
}
Expand Down Expand Up @@ -111,3 +122,14 @@ export function getCSSVarFromStyleValue( styleValue: string ): string {
}
return styleValue;
}

/**
* Capitalizes the first letter in a string.
*
* @param {string} str The string whose first letter the function will capitalize.
*
* @return string A CSS var value.
*/
export function upperFirst( [ firstLetter, ...rest ]: string ) {
return firstLetter.toUpperCase() + rest.join( '' );
}
Loading