From 36479894c09decd541a4d9599b5c3bd8d2fe144f Mon Sep 17 00:00:00 2001 From: Jorge Date: Mon, 26 Feb 2018 22:43:33 +0000 Subject: [PATCH] Implemented mechanism to use classes for configured colors instead of inline styles. --- blocks/color-palette/index.js | 2 +- blocks/color-palette/test/index.js | 2 +- blocks/colors/index.js | 2 + blocks/colors/style.scss | 87 +++++++++++++++++++++++++++++ blocks/colors/utils.js | 59 +++++++++++++++++++ blocks/colors/with-colors.js | 43 ++++++++++++++ blocks/editor-settings/index.js | 54 ++++++++++++++---- blocks/index.js | 1 + blocks/library/paragraph/index.js | 83 ++++++++++++++++++--------- docs/extensibility/theme-support.md | 54 +++++++++++++++--- edit-post/index.js | 21 ++++++- 11 files changed, 359 insertions(+), 49 deletions(-) create mode 100644 blocks/colors/index.js create mode 100644 blocks/colors/style.scss create mode 100644 blocks/colors/utils.js create mode 100644 blocks/colors/with-colors.js diff --git a/blocks/color-palette/index.js b/blocks/color-palette/index.js index 22f6cdc8eabd3..cb1d9417d1377 100644 --- a/blocks/color-palette/index.js +++ b/blocks/color-palette/index.js @@ -24,7 +24,7 @@ export function ColorPalette( { colors, disableCustomColors = false, value, onCh return (
- { map( colors, ( color ) => { + { map( colors, ( { color } ) => { const style = { color: color }; const className = classnames( 'blocks-color-palette__item', { 'is-active': value === color } ); diff --git a/blocks/color-palette/test/index.js b/blocks/color-palette/test/index.js index 96ddc339cdf94..f8bc82d7ce8e7 100644 --- a/blocks/color-palette/test/index.js +++ b/blocks/color-palette/test/index.js @@ -9,7 +9,7 @@ import { shallow } from 'enzyme'; import { ColorPalette } from '../'; describe( 'ColorPalette', () => { - const colors = [ 'red', 'white', 'blue' ]; + const colors = [ { name: 'red', color: 'red' }, { name: 'white', color: 'white' }, { name: 'blue', color: 'blue' } ]; const currentColor = 'red'; const onChange = jest.fn(); diff --git a/blocks/colors/index.js b/blocks/colors/index.js new file mode 100644 index 0000000000000..29b52a42d88e5 --- /dev/null +++ b/blocks/colors/index.js @@ -0,0 +1,2 @@ +export { getColorClass } from './utils'; +export { default as withColors } from './with-colors'; diff --git a/blocks/colors/style.scss b/blocks/colors/style.scss new file mode 100644 index 0000000000000..fa6cef63fb6cb --- /dev/null +++ b/blocks/colors/style.scss @@ -0,0 +1,87 @@ +.has-pale-pink-background-color { + background-color: #f78da7; +} + +.has-vivid-red-background-color { + background-color: #cf2e2e; +} + +.has-luminous-vivid-orange-background-color { + background-color: #ff6900; +} + +.has-luminous-vivid-amber-background-color { + background-color: #fcb900; +} + +.has-light-green-cyan-background-color { + background-color: #7bdcb5; +} + +.has-vivid-green-cyan-background-color { + background-color: #00d084; +} + +.has-pale-cyan-blue-background-color { + background-color: #8ed1fc; +} + +.has-vivid-cyan-blue-background-color { + background-color: #0693e3; +} + +.has-very-light-gray-background-color { + background-color: #eeeeee; +} + +.has-cyan-bluish-gray-background-color { + background-color: #abb8c3; +} + +.has-very-dark-gray-background-color { + background-color: #313131; +} + +.has-pale-pink-color { + color: #f78da7; +} + +.has-vivid-red-color { + color: #cf2e2e; +} + +.has-luminous-vivid-orange-color { + color: #ff6900; +} + +.has-luminous-vivid-amber-color { + color: #fcb900; +} + +.has-light-green-cyan-color { + color: #7bdcb5; +} + +.has-vivid-green-cyan-color { + color: #00d084; +} + +.has-pale-cyan-blue-color { + color: #8ed1fc; +} + +.has-vivid-cyan-blue-color { + color: #0693e3; +} + +.has-very-light-gray-color { + color: #eeeeee; +} + +.has-cyan-bluish-gray-color { + color: #abb8c3; +} + +.has-very-dark-gray-color { + color: #313131; +} diff --git a/blocks/colors/utils.js b/blocks/colors/utils.js new file mode 100644 index 0000000000000..6c7d7b8814b41 --- /dev/null +++ b/blocks/colors/utils.js @@ -0,0 +1,59 @@ +/** + * External dependencies + */ +import { find, kebabCase } from 'lodash'; + +/** + * Returns the color value based on an array of named colors and the namedColor or the customColor value. + * + * @param {Array} colors Array of color objects containing the "name" and "color" value as properties. + * @param {?string} namedColor A string containing the color name. + * @param {?string} customColor A string containing the customColor value. + * + * @return {?string} If namedColor is passed and the name is found in colors it returns the color for that name. + * Otherwise, the customColor parameter is returned. + */ +export const getColorValue = ( colors, namedColor, customColor ) => { + if ( namedColor ) { + const colorObj = find( colors, { name: namedColor } ); + return colorObj && colorObj.color; + } + if ( customColor ) { + return customColor; + } +}; + +/** + * Returns a function that receives the color value and sets it using the attribute for named colors or for custom colors. + * + * @param {Array} colors Array of color objects containing the "name" and "color" value as properties. + * @param {string} colorAttributeName Name of the attribute where named colors are stored. + * @param {string} customColorAttributeName Name of the attribute where custom colors are stored. + * @param {string} setAttributes A function that receives an object with the attributes to set. + * + * @return {function} A function that receives the color value and sets the attributes necessary to correctly store it. + */ +export const setColorValue = ( colors, colorAttributeName, customColorAttributeName, setAttributes ) => + ( colorValue ) => { + const colorObj = find( colors, { color: colorValue } ); + setAttributes( { + [ colorAttributeName ]: colorObj && colorObj.name ? colorObj.name : undefined, + [ customColorAttributeName ]: colorObj && colorObj.name ? undefined : colorValue, + } ); + }; + +/** + * Returns a class based on the context a color is being used and its name. + * + * @param {string} colorContextName Context/place where color is being used e.g: background, text etc... + * @param {string} colorName Name of the color. + * + * @return {string} String with the class corresponding to the color in the provided context. + */ +export function getColorClass( colorContextName, colorName ) { + if ( ! colorContextName || ! colorName ) { + return; + } + + return `has-${ kebabCase( colorName ) }-${ colorContextName }`; +} diff --git a/blocks/colors/with-colors.js b/blocks/colors/with-colors.js new file mode 100644 index 0000000000000..fbfe031377603 --- /dev/null +++ b/blocks/colors/with-colors.js @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createHigherOrderComponent } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { getColorValue, getColorClass, setColorValue } from './utils'; +import { withEditorSettings } from '../editor-settings'; +import './style.scss'; + +/** + * Higher-order component, which handles color logic for class generation + * color value, retrieval and color attribute setting. + * + * @param {WPElement} WrappedComponent The wrapped component. + * + * @return {Component} Component with a new colors prop. + */ +export default createHigherOrderComponent( + withEditorSettings( + ( settings, props ) => { + const colors = get( settings, [ 'colors' ], [] ); + return { + initializeColor: ( { colorContext, colorAttribute, customColorAttribute } ) => ( { + value: getColorValue( + colors, + props.attributes[ colorAttribute ], + props.attributes[ customColorAttribute ] + ), + class: getColorClass( colorContext, props.attributes[ colorAttribute ] ), + set: setColorValue( colors, colorAttribute, customColorAttribute, props.setAttributes ), + } ), + }; + } ), + 'withColors' +); diff --git a/blocks/editor-settings/index.js b/blocks/editor-settings/index.js index 72639239f4836..d6e248a16772e 100644 --- a/blocks/editor-settings/index.js +++ b/blocks/editor-settings/index.js @@ -15,17 +15,49 @@ import { createContext, createHigherOrderComponent } from '@wordpress/element'; const DEFAULT_SETTINGS = { alignWide: false, colors: [ - '#f78da7', - '#cf2e2e', - '#ff6900', - '#fcb900', - '#7bdcb5', - '#00d084', - '#8ed1fc', - '#0693e3', - '#eee', - '#abb8c3', - '#313131', + { + name: 'pale pink', + color: '#f78da7', + }, + { name: 'vivid red', + color: '#cf2e2e', + }, + { + name: 'luminous vivid orange', + color: '#ff6900', + }, + { + name: 'luminous vivid amber', + color: '#fcb900', + }, + { + name: 'light green cyan', + color: '#7bdcb5', + }, + { + name: 'vivid green cyan', + color: '#00d084', + }, + { + name: 'pale cyan blue', + color: '#8ed1fc', + }, + { + name: 'vivid cyan blue', + color: '#0693e3', + }, + { + name: 'very light gray', + color: '#eeeeee', + }, + { + name: 'cyan bluish gray', + color: '#abb8c3', + }, + { + name: 'very dark gray', + color: '#313131', + }, ], // This is current max width of the block inner area diff --git a/blocks/index.js b/blocks/index.js index e92c66d0a7281..c10cd640db4e2 100644 --- a/blocks/index.js +++ b/blocks/index.js @@ -13,6 +13,7 @@ import './hooks'; // Blocks are inferred from the HTML source of a post through a parsing mechanism // and then stored as objects in state, from which it is then rendered for editing. export * from './api'; +export * from './colors'; export { registerCoreBlocks } from './library'; export { default as AlignmentToolbar } from './alignment-toolbar'; export { default as Autocomplete } from './autocomplete'; diff --git a/blocks/library/paragraph/index.js b/blocks/library/paragraph/index.js index 0c2bbed209266..48def60ea4f8c 100644 --- a/blocks/library/paragraph/index.js +++ b/blocks/library/paragraph/index.js @@ -11,6 +11,7 @@ import { __ } from '@wordpress/i18n'; import { concatChildren, Component, + compose, Fragment, RawHTML, } from '@wordpress/element'; @@ -39,6 +40,7 @@ import RichText from '../../rich-text'; import InspectorControls from '../../inspector-controls'; import ColorPalette from '../../color-palette'; import ContrastChecker from '../../contrast-checker'; +import { getColorClass, withColors } from '../../colors'; const { getComputedStyle } = window; @@ -126,6 +128,7 @@ class ParagraphBlock extends Component { mergeBlocks, onReplace, className, + initializeColor, fallbackBackgroundColor, fallbackTextColor, fallbackFontSize, @@ -136,12 +139,20 @@ class ParagraphBlock extends Component { content, dropCap, placeholder, - backgroundColor, - textColor, width, } = attributes; const fontSize = this.getFontSize(); + const textColor = initializeColor( { + colorContext: 'color', + colorAttribute: 'textColor', + customColorAttribute: 'customTextColor', + } ); + const backgroundColor = initializeColor( { + colorContext: 'background-color', + colorAttribute: 'backgroundColor', + customColorAttribute: 'customBackgroundColor', + } ); return ( @@ -198,22 +209,22 @@ class ParagraphBlock extends Component { onChange={ this.toggleDropCap } /> - + setAttributes( { backgroundColor: colorValue } ) } + value={ backgroundColor.value } + onChange={ backgroundColor.set } /> - + setAttributes( { textColor: colorValue } ) } + value={ textColor.value } + onChange={ textColor.set } /> { content }

; }, migrate( attributes ) { - if ( isFinite( attributes.fontSize ) ) { - return omit( { - ...attributes, - customFontSize: attributes.fontSize, - }, 'fontSize' ); - } - return attributes; + return omit( { + ...attributes, + customFontSize: isFinite( attributes.fontSize ) ? attributes.fontSize : undefined, + customTextColor: attributes.textColor && '#' === attributes.textColor[ 0 ] ? attributes.textColor : undefined, + customBackgroundColor: attributes.backgroundColor && '#' === attributes.backgroundColor[ 0 ] ? attributes.backgroundColor : undefined, + }, [ 'fontSize', 'textColor', 'backgroundColor' ] ); }, }, { @@ -406,7 +424,10 @@ export const settings = { } }, - edit: FallbackStyles( ParagraphBlock ), + edit: compose( + withColors, + FallbackStyles, + )( ParagraphBlock ), save( { attributes } ) { const { @@ -416,21 +437,29 @@ export const settings = { dropCap, backgroundColor, textColor, + customBackgroundColor, + customTextColor, fontSize, customFontSize, } = attributes; + const textClass = getColorClass( 'color', textColor ); + const backgroundClass = getColorClass( 'background-color', backgroundColor ); + const fontSizeClass = fontSize && FONT_SIZES[ fontSize ] && `is-${ fontSize }-text`; + const className = classnames( { [ `align${ width }` ]: width, - 'has-background': backgroundColor, + 'has-background': backgroundColor || customBackgroundColor, 'has-drop-cap': dropCap, - [ `is-${ fontSize }-text` ]: fontSize && FONT_SIZES[ fontSize ], + [ fontSizeClass ]: fontSizeClass, + [ textClass ]: textClass, + [ backgroundClass ]: backgroundClass, } ); const styles = { - backgroundColor: backgroundColor, - color: textColor, - fontSize: ! fontSize && customFontSize ? customFontSize : undefined, + backgroundColor: backgroundClass ? undefined : customBackgroundColor, + color: textClass ? undefined : customTextColor, + fontSize: fontSizeClass ? undefined : customFontSize, textAlign: align, }; diff --git a/docs/extensibility/theme-support.md b/docs/extensibility/theme-support.md index 353626a856c55..07d8423c9569f 100644 --- a/docs/extensibility/theme-support.md +++ b/docs/extensibility/theme-support.md @@ -9,10 +9,22 @@ To opt-in for one of these features, call `add_theme_support` in the `functions. ```php function mytheme_setup_theme_supported_features() { add_theme_support( 'editor-color-palette', - '#a156b4', - '#d0a5db', - '#eee', - '#444' + array( + 'name' => 'strong magenta', + 'color' => '#a156b4', + ), + array( + 'name' => 'light grayish magenta', + 'color' => '#d0a5db', + ), + array( + 'name' => 'very light gray', + 'color' => '#eee', + ), + array( + 'name' => 'very dark gray', + 'color' => '#444', + ) ); } @@ -35,15 +47,41 @@ Different blocks have the possibility of customizing colors. Gutenberg provides ```php add_theme_support( 'editor-color-palette', - '#a156b4', - '#d0a5db', - '#eee', - '#444' + array( + 'name' => 'strong magenta', + 'color' => '#a156b4', + ), + array( + 'name' => 'light grayish magenta', + 'color' => '#d0a5db', + ), + array( + 'name' => 'very light gray', + 'color' => '#eee', + ), + array( + 'name' => 'very dark gray', + 'color' => '#444', + ) ); ``` The colors will be shown in order on the palette, and there's no limit to how many can be specified. +Themes are responsible for creating the classes that apply the colors in different contexts. Core blocks use "color" and "background-color" contexts. So to correctly apply "strong magenta" to all contexts of core blocks a theme should implement the following classes: + +```css +.has-strong-magenta-background-color { + background-color: #313131; +} + +.has-strong-magenta-color { + color: #f78da7; +} +``` + +The class name is built appending 'has-', followed by the class name *using* kebab case and ending with the context name. + ### Disabling custom colors in block Color Palettes By default, the color palette offered to blocks, allows the user to select a custom color different from the editor or theme default colors. diff --git a/edit-post/index.js b/edit-post/index.js index 9ef27883d225a..19cfe5d6b024f 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -1,7 +1,13 @@ +/** + * External dependencies + */ +import { get, isString, some } from 'lodash'; + /** * WordPress dependencies */ import { render, unmountComponentAtNode } from '@wordpress/element'; +import { deprecated } from '@wordpress/utils'; /** * Internal dependencies @@ -65,8 +71,21 @@ export function initializeEditor( id, post, settings ) { ); } + let migratedSettings; + const colors = get( settings, [ 'colors' ] ); + if ( some( colors, isString ) ) { + migratedSettings = { + ...settings, + colors: colors.map( ( color ) => isString( color ) ? { color } : color ), + }; + deprecated( 'Setting theme colors without names', { + version: '2.9', + alternative: 'add_theme_support( \'colors\', array( \'name\' => \'my-color\', \'color\': \'#ff0\' );' } + ); + } + render( - , + , target );