From a2db39050c3901bd715212a53c06f18fd21116f1 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Sat, 18 Apr 2020 18:07:49 +0300 Subject: [PATCH] Allow setting multiple block styles. --- .../src/components/block-styles/index.js | 76 ++++++++++++------ .../src/components/block-styles/style.scss | 11 +++ .../src/components/block-styles/test/index.js | 80 ++++++++++--------- 3 files changed, 104 insertions(+), 63 deletions(-) diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index 0c4352d41530a..5668fe0e171e0 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -10,6 +10,9 @@ import classnames from 'classnames'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; import TokenList from '@wordpress/token-list'; +import { Icon } from '@wordpress/components'; +import { check } from '@wordpress/icons'; + import { ENTER, SPACE } from '@wordpress/keycodes'; import { _x } from '@wordpress/i18n'; import { @@ -24,14 +27,16 @@ import { import BlockPreview from '../block-preview'; /** - * Returns the active style from the given className. + * Returns the active styles from the given className. * - * @param {Array} styles Block style variations. + * @param {Array} styles Block style variations. * @param {string} className Class name * - * @return {Object?} The active style. + * @return {Array} The active styles. */ -export function getActiveStyle( styles, className ) { +export function getActiveStyles( styles, className ) { + const activeStyles = []; + for ( const style of new TokenList( className ).values() ) { if ( style.indexOf( 'is-style-' ) === -1 ) { continue; @@ -40,30 +45,51 @@ export function getActiveStyle( styles, className ) { const potentialStyleName = style.substring( 9 ); const activeStyle = find( styles, { name: potentialStyleName } ); if ( activeStyle ) { - return activeStyle; + activeStyles.push( activeStyle ); } } - return find( styles, 'isDefault' ); + if ( activeStyles.length ) { + return activeStyles; + } + + const defaultStyle = find( styles, 'isDefault' ); + + if ( defaultStyle ) { + return [ defaultStyle ]; + } + + return []; } /** - * Replaces the active style in the block's className. + * Removes the style from the block's className. * * @param {string} className Class name. - * @param {Object?} activeStyle The replaced style. - * @param {Object} newStyle The replacing style. + * @param {Object} style The style to remove. * * @return {string} The updated className. */ -export function replaceActiveStyle( className, activeStyle, newStyle ) { +export function removeStyle( className, style ) { const list = new TokenList( className ); - if ( activeStyle ) { - list.remove( 'is-style-' + activeStyle.name ); - } + list.remove( 'is-style-' + style.name ); + + return list.value; +} - list.add( 'is-style-' + newStyle.name ); +/** + * Adds the style to the block's className. + * + * @param {string} className Class name. + * @param {Object} style The style to add. + * + * @return {string} The updated className. + */ +export function addStyle( className, style ) { + const list = new TokenList( className ); + + list.add( 'is-style-' + style.name ); return list.value; } @@ -92,13 +118,12 @@ function BlockStyles( { ]; } - const activeStyle = getActiveStyle( styles, className ); + const activeStyles = getActiveStyles( styles, className ); + function updateClassName( style ) { - const updatedClassName = replaceActiveStyle( - className, - activeStyle, - style - ); + const action = activeStyles.includes( style ) ? removeStyle : addStyle; + const updatedClassName = action( className, style ); + onChangeClassName( updatedClassName ); onHoverClassName( null ); onSwitch(); @@ -107,18 +132,14 @@ function BlockStyles( { return (
{ styles.map( ( style ) => { - const styleClassName = replaceActiveStyle( - className, - activeStyle, - style - ); + const styleClassName = 'is-style-' + style.name; return (
updateClassName( style ) } @@ -157,6 +178,9 @@ function BlockStyles( { } ) } /> + { activeStyles.includes( style ) ? ( + + ) : null }
{ style.label || style.name } diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss index ab720c9495d99..490399c808ce8 100644 --- a/packages/block-editor/src/components/block-styles/style.scss +++ b/packages/block-editor/src/components/block-styles/style.scss @@ -49,6 +49,17 @@ align-items: center; flex-grow: 1; min-height: 80px; + position: relative; + + svg { + position: absolute; + top: 0; + right: 0; + border-bottom-left-radius: $radius-block-ui; + border-left: $border-width solid $dark-gray-primary; + border-bottom: $border-width solid $dark-gray-primary; + background: #fff; + } } .block-editor-block-styles__item-label { diff --git a/packages/block-editor/src/components/block-styles/test/index.js b/packages/block-editor/src/components/block-styles/test/index.js index 6b73864d08c94..dce020d04a7cf 100644 --- a/packages/block-editor/src/components/block-styles/test/index.js +++ b/packages/block-editor/src/components/block-styles/test/index.js @@ -1,76 +1,82 @@ /** * Internal dependencies */ -import { getActiveStyle, replaceActiveStyle } from '../'; +import { getActiveStyles, removeStyle, addStyle } from '../'; -describe( 'getActiveStyle', () => { - it( 'Should return the undefined if no active style', () => { +describe( 'getActiveStyles', () => { + it( 'Should return empty array if no active styles', () => { const styles = [ { name: 'small' }, { name: 'big' } ]; const className = 'custom-className'; - expect( getActiveStyle( styles, className ) ).toBeUndefined(); + expect( getActiveStyles( styles, className ) ).toEqual( [] ); } ); it( 'Should return the default style if no active style', () => { const styles = [ { name: 'small' }, { name: 'big', isDefault: true } ]; const className = 'custom-className'; - expect( getActiveStyle( styles, className ).name ).toBe( 'big' ); + expect( getActiveStyles( styles, className ) ).toEqual( [ + { name: 'big', isDefault: true }, + ] ); } ); - it( 'Should return the active style', () => { - const styles = [ { name: 'small' }, { name: 'big', isDefault: true } ]; - const className = 'this-is-custom is-style-small'; - - expect( getActiveStyle( styles, className ).name ).toBe( 'small' ); - } ); - - it( 'Should return the first active style', () => { + it( 'Should return all active styles', () => { const styles = [ { name: 'small' }, { name: 'big', isDefault: true } ]; const className = 'this-is-custom is-style-small is-style-big'; - expect( getActiveStyle( styles, className ).name ).toBe( 'small' ); + expect( + getActiveStyles( styles, className ).map( ( s ) => s.name ) + ).toEqual( [ 'small', 'big' ] ); } ); } ); -describe( 'replaceActiveStyle', () => { - it( 'Should add the new style if no active style', () => { - const activeStyle = undefined; - const newStyle = { name: 'small' }; +describe( 'addStyle', () => { + it( 'Should add the new style if no active styles', () => { + const style = { name: 'small' }; const className = 'custom-class'; - expect( replaceActiveStyle( className, activeStyle, newStyle ) ).toBe( + expect( addStyle( className, style ) ).toBe( 'custom-class is-style-small' ); } ); - it( 'Should add the new style if no active style (no existing class)', () => { - const activeStyle = undefined; - const newStyle = { name: 'small' }; + it( 'Should add the new style if no active style or class', () => { + const style = { name: 'small' }; const className = ''; - expect( replaceActiveStyle( className, activeStyle, newStyle ) ).toBe( - 'is-style-small' + expect( addStyle( className, style ) ).toBe( 'is-style-small' ); + } ); + + it( 'Should not add the new style if it is already active', () => { + const style = { name: 'small' }; + const className = 'custom-class is-style-small'; + + expect( addStyle( className, style ) ).toBe( + 'custom-class is-style-small' ); } ); +} ); + +describe( 'removeStyle', () => { + it( 'Should remove the style if it is an active style', () => { + const style = { name: 'small' }; + const className = 'custom-class is-style-small'; + + expect( removeStyle( className, style ) ).toBe( 'custom-class' ); + } ); - it( 'Should add the new style if no active style (unassigned default)', () => { - const activeStyle = { name: 'default' }; - const newStyle = { name: 'small' }; + it( "Should not do anything if the style isn't active", () => { + const style = { name: 'small' }; const className = ''; - expect( replaceActiveStyle( className, activeStyle, newStyle ) ).toBe( - 'is-style-small' - ); + expect( removeStyle( className, style ) ).toBe( '' ); } ); - it( 'Should replace the previous active style', () => { - const activeStyle = { name: 'large' }; - const newStyle = { name: 'small' }; - const className = 'custom-class is-style-large'; + it( 'Should remove the style if it is defined multiple times', () => { + const style = { name: 'small' }; + const className = + 'is-style-small custom-class is-style-small is-style-small'; - expect( replaceActiveStyle( className, activeStyle, newStyle ) ).toBe( - 'custom-class is-style-small' - ); + expect( removeStyle( className, style ) ).toBe( 'custom-class' ); } ); } );