Skip to content

Commit

Permalink
Allow setting multiple block styles.
Browse files Browse the repository at this point in the history
  • Loading branch information
richrd committed Apr 19, 2020
1 parent 768611b commit a2db390
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 63 deletions.
76 changes: 50 additions & 26 deletions packages/block-editor/src/components/block-styles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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();
Expand All @@ -107,18 +132,14 @@ function BlockStyles( {
return (
<div className="block-editor-block-styles">
{ styles.map( ( style ) => {
const styleClassName = replaceActiveStyle(
className,
activeStyle,
style
);
const styleClassName = 'is-style-' + style.name;
return (
<div
key={ style.name }
className={ classnames(
'block-editor-block-styles__item',
{
'is-active': activeStyle === style,
'is-active': activeStyles.includes( style ),
}
) }
onClick={ () => updateClassName( style ) }
Expand Down Expand Up @@ -157,6 +178,9 @@ function BlockStyles( {
} )
}
/>
{ activeStyles.includes( style ) ? (
<Icon icon={ check } />
) : null }
</div>
<div className="block-editor-block-styles__item-label">
{ style.label || style.name }
Expand Down
11 changes: 11 additions & 0 deletions packages/block-editor/src/components/block-styles/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
80 changes: 43 additions & 37 deletions packages/block-editor/src/components/block-styles/test/index.js
Original file line number Diff line number Diff line change
@@ -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' );
} );
} );

0 comments on commit a2db390

Please sign in to comment.