From 128a8a95b7e938dc2b9f36625dfe05e30bf5aeca Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 26 Apr 2023 16:13:09 +0100 Subject: [PATCH] Style Props Hook --- assets/js/base/hooks/index.js | 4 +- assets/js/base/hooks/use-style-props.ts | 74 ++++++++ assets/js/base/utils/get-inline-styles.ts | 163 ++++++++++++++++++ assets/js/base/utils/index.js | 1 + .../mini-cart-cart-button-block/edit.tsx | 1 + 5 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 assets/js/base/hooks/use-style-props.ts create mode 100644 assets/js/base/utils/get-inline-styles.ts diff --git a/assets/js/base/hooks/index.js b/assets/js/base/hooks/index.js index 96e2f8d97b0..89e29647847 100644 --- a/assets/js/base/hooks/index.js +++ b/assets/js/base/hooks/index.js @@ -4,9 +4,7 @@ export * from './use-position-relative-to-viewport'; export * from './use-previous'; export * from './use-shallow-equal'; export * from './use-throw-error'; -export * from './use-spacing-props'; export * from './use-typography-props'; -export * from './use-color-props'; -export * from './use-border-props'; export * from './use-is-mounted'; export * from './use-spoken-message'; +export * from './use-style-props'; diff --git a/assets/js/base/hooks/use-style-props.ts b/assets/js/base/hooks/use-style-props.ts new file mode 100644 index 00000000000..15425d62e36 --- /dev/null +++ b/assets/js/base/hooks/use-style-props.ts @@ -0,0 +1,74 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { isObject } from '@woocommerce/types'; +import { parseStyle } from '@woocommerce/base-utils'; + +/** + * Internal dependencies + */ +import { useTypographyProps } from './use-typography-props'; +import { + getColorClassesAndStyles, + getBorderClassesAndStyles, + getSpacingClassesAndStyles, + WithStyle, +} from '../utils'; + +type StyleProps = { + className: string; + style: Record< string, unknown >; +}; + +/** + * Parse the style object saved with blocks and returns the CSS class names and inline styles. + * + * This hook (and its utilities) borrow functionality from the Gutenberg Block Editor package--something we don't want + * to import on the frontend. + */ +export const useStyleProps = ( attributes: unknown ): StyleProps => { + const attributesObject = isObject( attributes ) ? attributes : {}; + + const typographyProps = useTypographyProps( attributesObject ); + const style = parseStyle( attributesObject.style ); + + const colorProps = getColorClassesAndStyles( { + style, + } as WithStyle ); + + const borderProps = getBorderClassesAndStyles( { + style, + } as WithStyle ); + + const spacingProps = getSpacingClassesAndStyles( { + style, + } as WithStyle ); + + /* TODO + const { borderColor } = attributesObject; + if ( borderColor ) { + const borderColorObject = getMultiOriginColor( { + colors, + namedColor: borderColor, + } ); + + borderProps.style.borderColor = borderColorObject.color; + } + */ + + return { + className: classnames( + typographyProps.className, + colorProps.className, + borderProps.className, + spacingProps.className + ), + style: { + ...typographyProps.style, + ...colorProps.style, + ...borderProps.style, + ...spacingProps.style, + }, + }; +}; diff --git a/assets/js/base/utils/get-inline-styles.ts b/assets/js/base/utils/get-inline-styles.ts new file mode 100644 index 00000000000..5895da01839 --- /dev/null +++ b/assets/js/base/utils/get-inline-styles.ts @@ -0,0 +1,163 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { kebabCase } from 'lodash'; +import { getCSSRules } from '@wordpress/style-engine'; +import { isObject } from '@woocommerce/types'; + +type StyleValue = string | NestedStyle; + +interface NestedStyle { + [ key: string ]: StyleValue | undefined; +} + +export type WithStyle = { + style: Record< string, NestedStyle > | undefined; +}; + +/** + * Returns the inline styles to add depending on the style object + * + * @param {Object} styles Styles configuration. + * @return {Object} Flattened CSS variables declaration. + */ +function getInlineStyles( styles = {} ) { + const output = {} as Record< string, unknown >; + + getCSSRules( styles, { selector: '' } ).forEach( ( rule ) => { + output[ rule.key ] = rule.value; + } ); + + return output; +} + +/** + * Get the classname for a given color. + */ +function getColorClassName( + colorContextName: string | undefined, + colorSlug: string | undefined +): string { + if ( ! colorContextName || ! colorSlug ) { + return ''; + } + return `has-${ kebabCase( colorSlug ) }-${ colorContextName }`; +} + +/** + * Generates a CSS class name consisting of all the applicable border color + * classes given the current block attributes. + */ +function getBorderClassName( + attributes: WithStyle & { + borderColor?: string; + } +) { + const { borderColor, style } = attributes; + const borderColorClass = borderColor + ? getColorClassName( 'border-color', borderColor ) + : ''; + + return classnames( { + 'has-border-color': borderColor || style?.border?.color, + borderColorClass, + } ); +} + +function getGradientClassName( gradientSlug: string | undefined ) { + if ( ! gradientSlug ) { + return undefined; + } + return `has-${ gradientSlug }-gradient-background`; +} + +/** + * Provides the CSS class names and inline styles for a block's color support + * attributes. + * + * @param {Object} attributes Block attributes. + * + * @return {Object} Color block support derived CSS classes & styles. + */ +export function getColorClassesAndStyles( + attributes: WithStyle & { + backgroundColor?: string | undefined; + textColor?: string | undefined; + gradient?: string | undefined; + } +) { + const { backgroundColor, textColor, gradient, style } = attributes; + + // Collect color CSS classes. + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); + const textClass = getColorClassName( 'color', textColor ); + + const gradientClass = getGradientClassName( gradient ); + const hasGradient = gradientClass || style?.color?.gradient; + + // Determine color CSS class name list. + const className = classnames( textClass, gradientClass, { + // Don't apply the background class if there's a gradient. + [ backgroundClass ]: ! hasGradient && !! backgroundClass, + 'has-text-color': textColor || style?.color?.text, + 'has-background': + backgroundColor || + style?.color?.background || + gradient || + style?.color?.gradient, + 'has-link-color': isObject( style?.elements?.link ) + ? style?.elements?.link?.color + : undefined, + } ); + + // Collect inline styles for colors. + const colorStyles = style?.color || {}; + const styleProp = getInlineStyles( { color: colorStyles } ); + + return { + className: className || undefined, + style: styleProp, + }; +} + +/** + * Provides the CSS class names and inline styles for a block's border support + * attributes. + * + * @param {Object} attributes Block attributes. + * @return {Object} Border block support derived CSS classes & styles. + */ +export function getBorderClassesAndStyles( attributes: WithStyle ) { + const border = attributes.style?.border || {}; + const className = getBorderClassName( attributes ); + + return { + className: className || undefined, + style: getInlineStyles( { border } ), + }; +} + +/** + * Provides the CSS class names and inline styles for a block's spacing support + * attributes. + * + * @param {Object} attributes Block attributes. + * + * @return {Object} Spacing block support derived CSS classes & styles. + */ +export function getSpacingClassesAndStyles( attributes: WithStyle ) { + const { style } = attributes; + + // Collect inline styles for spacing. + const spacingStyles = style?.spacing || {}; + const styleProp = getInlineStyles( { spacing: spacingStyles } ); + + return { + className: undefined, + style: styleProp, + }; +} diff --git a/assets/js/base/utils/index.js b/assets/js/base/utils/index.js index 8d6140a9dbb..ef220b0ff25 100644 --- a/assets/js/base/utils/index.js +++ b/assets/js/base/utils/index.js @@ -15,3 +15,4 @@ export * from './camel-case-keys'; export * from './snake-case-keys'; export * from './debounce'; export * from './keyby'; +export * from './get-inline-styles'; diff --git a/assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/mini-cart-cart-button-block/edit.tsx b/assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/mini-cart-cart-button-block/edit.tsx index 6652ce25c2c..cb8be3f5cd4 100644 --- a/assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/mini-cart-cart-button-block/edit.tsx +++ b/assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/mini-cart-cart-button-block/edit.tsx @@ -35,6 +35,7 @@ export const Edit = ( { cartButtonLabel: content, } ); } } + style={ blockProps.style } /> );