From 54cd5846a01c8734ccaca7f67e481735bd124518 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 20 Mar 2020 15:07:45 -0400 Subject: [PATCH 01/35] Add initial AlignmentControl --- .../components/src/alignment-control/index.js | 159 ++++++++++++++++++ .../src/alignment-control/stories/index.js | 13 ++ .../components/src/alignment-control/utils.js | 125 ++++++++++++++ 3 files changed, 297 insertions(+) create mode 100644 packages/components/src/alignment-control/index.js create mode 100644 packages/components/src/alignment-control/stories/index.js create mode 100644 packages/components/src/alignment-control/utils.js diff --git a/packages/components/src/alignment-control/index.js b/packages/components/src/alignment-control/index.js new file mode 100644 index 00000000000000..50e559e6a6403f --- /dev/null +++ b/packages/components/src/alignment-control/index.js @@ -0,0 +1,159 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; +import styled from '@emotion/styled'; +import { css } from '@emotion/core'; + +/** + * WordPress dependencies + */ +import { useRef } from '@wordpress/element'; +import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import { + ALIGNMENTS, + DIRECTION, + useControlledState, + getIndexFromAlign, + getAlignFromIndex, + getNextIndexFromDirection, +} from './utils'; +import { color, reduceMotion } from '../utils/style-mixins'; + +export default function AlignmentControl( { + alignment: alignmentProp = 'center', + onChange = noop, +} ) { + const [ alignIndex, setAlignIndex ] = useControlledState( + getIndexFromAlign( alignmentProp ) + ); + const nodeRef = useRef(); + + const handleOnChange = ( nextIndex ) => { + const alignName = getAlignFromIndex( nextIndex ); + setAlignIndex( nextIndex ); + onChange( alignName, { flexProps: alignmentProp[ nextIndex ] } ); + }; + + const handleOnKeyDown = ( event ) => { + const { keyCode } = event; + let nextIndex; + + switch ( keyCode ) { + case UP: + event.preventDefault(); + nextIndex = getNextIndexFromDirection( + alignIndex, + DIRECTION.UP + ); + handleOnChange( nextIndex ); + break; + case DOWN: + event.preventDefault(); + nextIndex = getNextIndexFromDirection( + alignIndex, + DIRECTION.DOWN + ); + handleOnChange( nextIndex ); + break; + case LEFT: + event.preventDefault(); + nextIndex = getNextIndexFromDirection( + alignIndex, + DIRECTION.LEFT + ); + handleOnChange( nextIndex ); + break; + case RIGHT: + event.preventDefault(); + nextIndex = getNextIndexFromDirection( + alignIndex, + DIRECTION.RIGHT + ); + handleOnChange( nextIndex ); + break; + default: + break; + } + }; + + const createHandleOnClick = ( index ) => ( event ) => { + nodeRef.current.focus(); + event.preventDefault(); + handleOnChange( index ); + }; + + return ( + + { ALIGNMENTS.map( ( align, index ) => { + const isActive = alignIndex === index; + return ( + + + + ); + } ) } + + ); +} + +const RootView = styled.div` + --maxWidth: 92px; + border: 1px solid transparent; + border-radius: 2px; + max-width: var( --maxWidth ); + display: grid; + grid-template-columns: repeat( 3, 1fr ); + grid-template-rows: repeat( 3, calc( var( --maxWidth ) / 3 ) ); + outline: none; + + &:active, + &:focus { + border: 1px solid ${color( 'blue.medium.focus' )}; + } +`; + +const DotView = styled.div` + width: 6px; + height: 6px; + margin: auto; + transition: all 120ms linear; + background: currentColor; + ${reduceMotion( 'transition' )}; + + ${( { isActive } ) => + css( { + boxShadow: isActive ? `0 0 0 2px ${ color( 'black' ) }` : null, + color: isActive ? color( 'black' ) : color( 'lightGray.800' ), + } )} + + *:hover > & { + ${( { isActive } ) => + css( { + color: isActive + ? color( 'black' ) + : color( 'blue.medium.focus' ), + } )} + } +`; + +const CellView = styled.div` + appearance: none; + border: none; + margin: 0; + display: flex; + position: relative; + outline: none; + cursor: pointer; + align-items: center; + justify-content: center; + padding: 0; +`; diff --git a/packages/components/src/alignment-control/stories/index.js b/packages/components/src/alignment-control/stories/index.js new file mode 100644 index 00000000000000..79710261d51367 --- /dev/null +++ b/packages/components/src/alignment-control/stories/index.js @@ -0,0 +1,13 @@ +/** + * Internal dependencies + */ +import AlignmentControl from '../'; + +export default { + title: 'Components/AligmentControl', + component: AlignmentControl, +}; + +export const _default = () => { + return ; +}; diff --git a/packages/components/src/alignment-control/utils.js b/packages/components/src/alignment-control/utils.js new file mode 100644 index 00000000000000..229a6becb8b486 --- /dev/null +++ b/packages/components/src/alignment-control/utils.js @@ -0,0 +1,125 @@ +/** + * WordPress dependencies + */ +import { useState, useEffect, useRef } from '@wordpress/element'; +/** + * External dependencies + */ +import { isUndefined } from 'lodash'; + +export const DIRECTION = { + UP: 'up', + DOWN: 'down', + LEFT: 'left', + RIGHT: 'right', +}; + +export const BASE_ALIGNMENTS = [ + [ 'top', 'left' ], + [ 'top', 'center' ], + [ 'top', 'right' ], + [ 'center', 'left' ], + [ 'center' ], + [ 'center', 'right' ], + [ 'bottom', 'left' ], + [ 'bottom', 'center' ], + [ 'bottom', 'right' ], +]; + +export const ALIGNMENTS = BASE_ALIGNMENTS.map( normalizeAlignments ); +export const ALIGNMENT_VALUES = ALIGNMENTS.map( ( a ) => a.join( ' ' ) ); +export const ALIGNMENT_MATRIX = [ + [ 0, 1, 2 ], + [ 3, 4, 5 ], + [ 6, 7, 8 ], +]; + +export function useControlledState( initialState ) { + const [ state, setState ] = useState( initialState ); + const stateRef = useRef( initialState ); + + useEffect( () => { + if ( initialState !== stateRef.current ) { + setState( initialState ); + stateRef.current = initialState; + } + }, [ initialState ] ); + + return [ state, setState ]; +} + +function normalizeAlignments( alignments = [] ) { + const a = + typeof alignments === 'string' + ? alignments.split( /\ |-/g ) + : alignments; + return a.sort(); +} + +export function alignmentMatches( align = 'center' ) { + const a = normalizeAlignments( align ); + return ( nextAlignment ) => { + return JSON.stringify( nextAlignment ) === JSON.stringify( a ); + }; +} + +export function getAlignFromIndex( index = 0 ) { + const align = ALIGNMENTS[ index ]; + return align.join( ' ' ); +} + +export function getIndexFromAlign( align = 'center' ) { + const item = ALIGNMENTS.find( alignmentMatches( align ) ); + const index = ALIGNMENTS.indexOf( item ); + const fallbackIndex = 4; //center + + return index > -1 ? index : fallbackIndex; +} + +export function getCoordsFromIndex( index = 0 ) { + const coords = [ 0, 0 ]; + + ALIGNMENT_MATRIX.forEach( ( x1, x ) => { + x1.forEach( ( v, y ) => { + if ( v === index ) { + coords[ 0 ] = x; + coords[ 1 ] = y; + } + } ); + } ); + + return coords; +} + +export function getValueFromCoords( coords, fallback = 0 ) { + const [ x, y ] = coords; + const exists = + ! isUndefined( ALIGNMENT_MATRIX[ x ] ) && + ! isUndefined( ALIGNMENT_MATRIX[ x ][ y ] ); + return exists ? ALIGNMENT_MATRIX[ x ][ y ] : fallback; +} + +export function getNextIndexFromDirection( currentIndex, direction ) { + if ( ! direction ) return currentIndex; + let nextIndex = currentIndex; + const [ x, y ] = getCoordsFromIndex( currentIndex ); + + switch ( direction ) { + case DIRECTION.UP: + nextIndex = getValueFromCoords( [ x - 1, y ], currentIndex ); + break; + case DIRECTION.DOWN: + nextIndex = getValueFromCoords( [ x + 1, y ], currentIndex ); + break; + case DIRECTION.LEFT: + nextIndex = getValueFromCoords( [ x, y - 1 ], currentIndex ); + break; + case DIRECTION.RIGHT: + nextIndex = getValueFromCoords( [ x, y + 1 ], currentIndex ); + break; + default: + break; + } + + return nextIndex; +} From 0d1560bbbb1e7067571f06d1276692bc5fd0a7a5 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 20 Mar 2020 16:53:29 -0400 Subject: [PATCH 02/35] Refactor AlignmentControl utils. Add basic tests for utils --- .../components/src/alignment-control/index.js | 16 +- .../src/alignment-control/stories/index.js | 2 +- .../src/alignment-control/test/utils.test.js | 36 ++++ .../components/src/alignment-control/utils.js | 160 +++++++++++++----- 4 files changed, 163 insertions(+), 51 deletions(-) create mode 100644 packages/components/src/alignment-control/test/utils.test.js diff --git a/packages/components/src/alignment-control/index.js b/packages/components/src/alignment-control/index.js index 50e559e6a6403f..058bddeb28802c 100644 --- a/packages/components/src/alignment-control/index.js +++ b/packages/components/src/alignment-control/index.js @@ -17,26 +17,28 @@ import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; import { ALIGNMENTS, DIRECTION, - useControlledState, - getIndexFromAlign, - getAlignFromIndex, + FLEX_ALIGNMENT_PROPS, + getAlignmentIndex, + getAlignmentValueFromIndex, getNextIndexFromDirection, } from './utils'; import { color, reduceMotion } from '../utils/style-mixins'; +import { useControlledState } from '../utils/hooks'; export default function AlignmentControl( { alignment: alignmentProp = 'center', onChange = noop, } ) { const [ alignIndex, setAlignIndex ] = useControlledState( - getIndexFromAlign( alignmentProp ) + getAlignmentIndex( alignmentProp ) ); const nodeRef = useRef(); const handleOnChange = ( nextIndex ) => { - const alignName = getAlignFromIndex( nextIndex ); + const alignName = getAlignmentValueFromIndex( nextIndex ); + setAlignIndex( nextIndex ); - onChange( alignName, { flexProps: alignmentProp[ nextIndex ] } ); + onChange( alignName ); }; const handleOnKeyDown = ( event ) => { @@ -84,7 +86,7 @@ export default function AlignmentControl( { const createHandleOnClick = ( index ) => ( event ) => { nodeRef.current.focus(); event.preventDefault(); - handleOnChange( index ); + handleOnChange( index, { flexProps: FLEX_ALIGNMENT_PROPS[ index ] } ); }; return ( diff --git a/packages/components/src/alignment-control/stories/index.js b/packages/components/src/alignment-control/stories/index.js index 79710261d51367..cf39e71894ff2e 100644 --- a/packages/components/src/alignment-control/stories/index.js +++ b/packages/components/src/alignment-control/stories/index.js @@ -4,7 +4,7 @@ import AlignmentControl from '../'; export default { - title: 'Components/AligmentControl', + title: 'Components/AlignmentControl', component: AlignmentControl, }; diff --git a/packages/components/src/alignment-control/test/utils.test.js b/packages/components/src/alignment-control/test/utils.test.js new file mode 100644 index 00000000000000..4bed9166ae6a35 --- /dev/null +++ b/packages/components/src/alignment-control/test/utils.test.js @@ -0,0 +1,36 @@ +/** + * Internal dependencies + */ +import { isAlignmentValid } from '../utils'; + +describe( 'AlignmentControl', () => { + describe( 'isAlignmentValid', () => { + it( 'should return true for correctly matching alignment values', () => { + // x and y values + expect( isAlignmentValid( 'center top' ) ).toBe( true ); + expect( isAlignmentValid( 'top center' ) ).toBe( true ); + expect( isAlignmentValid( 'center bottom' ) ).toBe( true ); + // single center value + expect( isAlignmentValid( 'center' ) ).toBe( true ); + // x + y center value + expect( isAlignmentValid( 'center center' ) ).toBe( true ); + } ); + + it( 'should return false for invalid alignment values', () => { + expect( isAlignmentValid( 'top bottom' ) ).toBe( false ); + expect( isAlignmentValid( 'left right' ) ).toBe( false ); + expect( isAlignmentValid( 'near middle' ) ).toBe( false ); + expect( isAlignmentValid( 'middle middle' ) ).toBe( false ); + expect( isAlignmentValid( 'middle' ) ).toBe( false ); + } ); + + it( 'should only consider first 2 (max) values', () => { + expect( isAlignmentValid( 'center top right bottom' ) ).toBe( + false + ); + expect( isAlignmentValid( 'top center left right bottom' ) ).toBe( + false + ); + } ); + } ); +} ); diff --git a/packages/components/src/alignment-control/utils.js b/packages/components/src/alignment-control/utils.js index 229a6becb8b486..95a987e100a7ed 100644 --- a/packages/components/src/alignment-control/utils.js +++ b/packages/components/src/alignment-control/utils.js @@ -1,7 +1,3 @@ -/** - * WordPress dependencies - */ -import { useState, useEffect, useRef } from '@wordpress/element'; /** * External dependencies */ @@ -26,7 +22,7 @@ export const BASE_ALIGNMENTS = [ [ 'bottom', 'right' ], ]; -export const ALIGNMENTS = BASE_ALIGNMENTS.map( normalizeAlignments ); +export const ALIGNMENTS = BASE_ALIGNMENTS.map( transformAlignment ); export const ALIGNMENT_VALUES = ALIGNMENTS.map( ( a ) => a.join( ' ' ) ); export const ALIGNMENT_MATRIX = [ [ 0, 1, 2 ], @@ -34,53 +30,112 @@ export const ALIGNMENT_MATRIX = [ [ 6, 7, 8 ], ]; -export function useControlledState( initialState ) { - const [ state, setState ] = useState( initialState ); - const stateRef = useRef( initialState ); +export const FLEX_ALIGNMENT_PROPS = [ + [ 'flex-start', 'flex-start' ], + [ 'flex-start', 'center' ], + [ 'flex-start', 'flex-end' ], + [ 'center', 'flex-start' ], + [ 'center', 'center' ], + [ 'center', 'flex-end' ], + [ 'flex-end', 'flex-start' ], + [ 'flex-end', 'center' ], + [ 'flex-end', 'flex-end' ], +]; - useEffect( () => { - if ( initialState !== stateRef.current ) { - setState( initialState ); - stateRef.current = initialState; - } - }, [ initialState ] ); +/** + * Transforms an alignment value to an [x, y] alignment data. + * + * @param {Array|string} alignments Value to transform. + * + * @return {Array} The [x, y] alignment array, if applicable. + */ +export function transformAlignment( alignments = [] ) { + let value = alignments; - return [ state, setState ]; -} + if ( typeof alignments === 'string' ) { + value = alignments.split( / |-/g ); + } + + if ( ! Array.isArray( value ) ) { + value = []; + } -function normalizeAlignments( alignments = [] ) { - const a = - typeof alignments === 'string' - ? alignments.split( /\ |-/g ) - : alignments; - return a.sort(); + return value.sort(); } -export function alignmentMatches( align = 'center' ) { - const a = normalizeAlignments( align ); - return ( nextAlignment ) => { - return JSON.stringify( nextAlignment ) === JSON.stringify( a ); - }; +/** + * Checks if a value is a valid alignment. + * + * @param {string} alignment Value to check. + * + * @return {boolean} Whether alignment is valid. + */ +export function isAlignmentValid( alignment = 'center' ) { + const match = getAlignmentValues( alignment ); + + return !! match; } -export function getAlignFromIndex( index = 0 ) { - const align = ALIGNMENTS[ index ]; - return align.join( ' ' ); +/** + * Retrieves a matching alignment [x, y] data from a value. + * + * @param {string} alignment Value to check. + * + * @return {Array} Matching alignment data. + */ +export function getAlignmentValues( alignment = 'center' ) { + const values = transformAlignment( alignment ); + + if ( values.length > 2 ) { + return false; + } + + const match = ALIGNMENTS.find( ( a ) => { + return a.every( ( v ) => values.includes( v ) ); + } ); + + return match; } -export function getIndexFromAlign( align = 'center' ) { - const item = ALIGNMENTS.find( alignmentMatches( align ) ); +/** + * Retrieves the [x, y] alignment data index from a value. + * + * @param {string} alignment Value to check. + * + * @return {number} The index of a matching alignment. + */ +export function getAlignmentIndex( alignment = 'center' ) { + const item = getAlignmentValues( alignment ); const index = ALIGNMENTS.indexOf( item ); - const fallbackIndex = 4; //center - return index > -1 ? index : fallbackIndex; + return index > -1 ? index : undefined; +} + +/** + * Retrieves the alignment value from an index. + * + * @param {number} index An alignment index. + * + * @return {string} The alignment value + */ +export function getAlignmentValueFromIndex( index = 0 ) { + const align = ALIGNMENTS[ index ] || []; + + return align.join( ' ' ); } +/** + * Retrieves the [x, y] coordinates from the alignment matrix from an index. + * + * @param {number} index The index to reference. + * + * @return {Array} The [x, y] coordinates. + */ export function getCoordsFromIndex( index = 0 ) { const coords = [ 0, 0 ]; - ALIGNMENT_MATRIX.forEach( ( x1, x ) => { - x1.forEach( ( v, y ) => { + ALIGNMENT_MATRIX.forEach( ( xv, x ) => { + xv.forEach( ( v, y ) => { if ( v === index ) { coords[ 0 ] = x; coords[ 1 ] = y; @@ -91,35 +146,54 @@ export function getCoordsFromIndex( index = 0 ) { return coords; } -export function getValueFromCoords( coords, fallback = 0 ) { +/** + * Retrieves the alignment index given an [x, y] alignment matrix coordinate. + * + * @param {Array} coords The [x, y] matrix coordinates. + * @param {number} fallback The fallback index. + * + * @return {number} The index given a [x, y] coordinate. + */ +export function getIndexFromCoords( coords, fallback = 0 ) { const [ x, y ] = coords; const exists = ! isUndefined( ALIGNMENT_MATRIX[ x ] ) && ! isUndefined( ALIGNMENT_MATRIX[ x ][ y ] ); + return exists ? ALIGNMENT_MATRIX[ x ][ y ] : fallback; } +/** + * Retrieves the next alignment index, given a direction movement. + * + * @param {number} currentIndex The current index to start from. + * @param {string} direction The movement direction. + * + * @return {number} The next alignment index. + */ export function getNextIndexFromDirection( currentIndex, direction ) { if ( ! direction ) return currentIndex; - let nextIndex = currentIndex; + const [ x, y ] = getCoordsFromIndex( currentIndex ); + let moveX = x; + let moveY = y; switch ( direction ) { case DIRECTION.UP: - nextIndex = getValueFromCoords( [ x - 1, y ], currentIndex ); + moveX = x - 1; break; case DIRECTION.DOWN: - nextIndex = getValueFromCoords( [ x + 1, y ], currentIndex ); + moveX = x + 1; break; case DIRECTION.LEFT: - nextIndex = getValueFromCoords( [ x, y - 1 ], currentIndex ); + moveY = y - 1; break; case DIRECTION.RIGHT: - nextIndex = getValueFromCoords( [ x, y + 1 ], currentIndex ); + moveY = y + 1; break; default: break; } - return nextIndex; + return getIndexFromCoords( [ moveX, moveY ], currentIndex ); } From 569e33d11f969d0b625e6de8b0c7354f17c661a6 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 20 Mar 2020 17:01:51 -0400 Subject: [PATCH 03/35] Move AlignmentControl styled components to dedicated file --- .../components/src/alignment-control/index.js | 77 ++++--------------- .../styles/alignment-control-styles.js | 63 +++++++++++++++ 2 files changed, 79 insertions(+), 61 deletions(-) create mode 100644 packages/components/src/alignment-control/styles/alignment-control-styles.js diff --git a/packages/components/src/alignment-control/index.js b/packages/components/src/alignment-control/index.js index 058bddeb28802c..ba2c0ad130c481 100644 --- a/packages/components/src/alignment-control/index.js +++ b/packages/components/src/alignment-control/index.js @@ -2,8 +2,6 @@ * External dependencies */ import { noop } from 'lodash'; -import styled from '@emotion/styled'; -import { css } from '@emotion/core'; /** * WordPress dependencies @@ -22,12 +20,15 @@ import { getAlignmentValueFromIndex, getNextIndexFromDirection, } from './utils'; -import { color, reduceMotion } from '../utils/style-mixins'; +import { Root, Cell, Point } from './styles/alignment-control-styles'; import { useControlledState } from '../utils/hooks'; +// TODO: Account for RTL alignments export default function AlignmentControl( { alignment: alignmentProp = 'center', onChange = noop, + onKeyDown = noop, + ...props } ) { const [ alignIndex, setAlignIndex ] = useControlledState( getAlignmentIndex( alignmentProp ) @@ -45,6 +46,8 @@ export default function AlignmentControl( { const { keyCode } = event; let nextIndex; + onKeyDown( event ); + switch ( keyCode ) { case UP: event.preventDefault(); @@ -90,72 +93,24 @@ export default function AlignmentControl( { }; return ( - + { ALIGNMENTS.map( ( align, index ) => { const isActive = alignIndex === index; return ( - - - + + ); } ) } - + ); } - -const RootView = styled.div` - --maxWidth: 92px; - border: 1px solid transparent; - border-radius: 2px; - max-width: var( --maxWidth ); - display: grid; - grid-template-columns: repeat( 3, 1fr ); - grid-template-rows: repeat( 3, calc( var( --maxWidth ) / 3 ) ); - outline: none; - - &:active, - &:focus { - border: 1px solid ${color( 'blue.medium.focus' )}; - } -`; - -const DotView = styled.div` - width: 6px; - height: 6px; - margin: auto; - transition: all 120ms linear; - background: currentColor; - ${reduceMotion( 'transition' )}; - - ${( { isActive } ) => - css( { - boxShadow: isActive ? `0 0 0 2px ${ color( 'black' ) }` : null, - color: isActive ? color( 'black' ) : color( 'lightGray.800' ), - } )} - - *:hover > & { - ${( { isActive } ) => - css( { - color: isActive - ? color( 'black' ) - : color( 'blue.medium.focus' ), - } )} - } -`; - -const CellView = styled.div` - appearance: none; - border: none; - margin: 0; - display: flex; - position: relative; - outline: none; - cursor: pointer; - align-items: center; - justify-content: center; - padding: 0; -`; diff --git a/packages/components/src/alignment-control/styles/alignment-control-styles.js b/packages/components/src/alignment-control/styles/alignment-control-styles.js new file mode 100644 index 00000000000000..6707aa8609c1f7 --- /dev/null +++ b/packages/components/src/alignment-control/styles/alignment-control-styles.js @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; +import { css } from '@emotion/core'; + +/** + * Internal dependencies + */ +import { color, reduceMotion } from '../../utils/style-mixins'; + +export const Root = styled.div` + --maxWidth: 92px; + border: 1px solid transparent; + border-radius: 2px; + max-width: var( --maxWidth ); + display: grid; + grid-template-columns: repeat( 3, 1fr ); + grid-template-rows: repeat( 3, calc( var( --maxWidth ) / 3 ) ); + outline: none; + + &:active, + &:focus { + border: 1px solid ${color( 'blue.medium.focus' )}; + } +`; + +export const Point = styled.div` + width: 6px; + height: 6px; + margin: auto; + transition: all 120ms linear; + background: currentColor; + ${reduceMotion( 'transition' )}; + + ${( { isActive } ) => + css( { + boxShadow: isActive ? `0 0 0 2px ${ color( 'black' ) }` : null, + color: isActive ? color( 'black' ) : color( 'lightGray.800' ), + } )} + + *:hover > & { + ${( { isActive } ) => + css( { + color: isActive + ? color( 'black' ) + : color( 'blue.medium.focus' ), + } )} + } +`; + +export const Cell = styled.div` + appearance: none; + border: none; + margin: 0; + display: flex; + position: relative; + outline: none; + cursor: pointer; + align-items: center; + justify-content: center; + padding: 0; +`; From 49e11ad02078f3f319db7d83088bfb0900d2e366 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Mon, 23 Mar 2020 16:36:01 -0400 Subject: [PATCH 04/35] Add AlignControl icon. Refactor and share styles between main Control and Icon --- .../src/alignment-control/README.md | 18 +++++ .../components/src/alignment-control/icon.js | 29 ++++++++ .../components/src/alignment-control/index.js | 3 + .../src/alignment-control/stories/index.js | 26 +++++++ .../styles/alignment-control-icon-styles.js | 56 +++++++++++++++ .../styles/alignment-control-styles.js | 72 ++++++++++++------- .../src/alignment-control/test/utils.test.js | 27 +++++-- .../components/src/alignment-control/utils.js | 20 ++++-- 8 files changed, 215 insertions(+), 36 deletions(-) create mode 100644 packages/components/src/alignment-control/README.md create mode 100644 packages/components/src/alignment-control/icon.js create mode 100644 packages/components/src/alignment-control/styles/alignment-control-icon-styles.js diff --git a/packages/components/src/alignment-control/README.md b/packages/components/src/alignment-control/README.md new file mode 100644 index 00000000000000..9f64b237647404 --- /dev/null +++ b/packages/components/src/alignment-control/README.md @@ -0,0 +1,18 @@ +# AlignmentControl + +AlignmentControl components let adjust horizontal and vertical alignments for UI. + +## Usage + +```jsx +import { AlignmentControl } from '@wordpress/components'; +import { useState } from '@wordpress/elememt'; + +const Example = () => { + const [ alignment, setAlignment ] = useState( 'center' ); + + return ( + + ); +}; +``` diff --git a/packages/components/src/alignment-control/icon.js b/packages/components/src/alignment-control/icon.js new file mode 100644 index 00000000000000..015c06fa902891 --- /dev/null +++ b/packages/components/src/alignment-control/icon.js @@ -0,0 +1,29 @@ +/** + * Internal dependencies + */ +import { ALIGNMENTS, getAlignmentIndex } from './utils'; +import { Root, Cell, Point } from './styles/alignment-control-icon-styles'; + +export default function AlignmentControlIcon( { + alignment = 'center', + size: sizeProp = 24, + width, + height, + ...props +} ) { + const alignIndex = getAlignmentIndex( alignment ); + const size = sizeProp || width || height; + + return ( + + { ALIGNMENTS.map( ( align, index ) => { + const isActive = alignIndex === index; + return ( + + + + ); + } ) } + + ); +} diff --git a/packages/components/src/alignment-control/index.js b/packages/components/src/alignment-control/index.js index ba2c0ad130c481..a1f327276a0135 100644 --- a/packages/components/src/alignment-control/index.js +++ b/packages/components/src/alignment-control/index.js @@ -22,6 +22,7 @@ import { } from './utils'; import { Root, Cell, Point } from './styles/alignment-control-styles'; import { useControlledState } from '../utils/hooks'; +import AlignmentControlIcon from './icon'; // TODO: Account for RTL alignments export default function AlignmentControl( { @@ -114,3 +115,5 @@ export default function AlignmentControl( { ); } + +AlignmentControl.icon = ; diff --git a/packages/components/src/alignment-control/stories/index.js b/packages/components/src/alignment-control/stories/index.js index cf39e71894ff2e..9ef48cb2361f81 100644 --- a/packages/components/src/alignment-control/stories/index.js +++ b/packages/components/src/alignment-control/stories/index.js @@ -1,7 +1,20 @@ +/** + * External dependencies + */ +import { number, select } from '@storybook/addon-knobs'; +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/icons'; /** * Internal dependencies */ import AlignmentControl from '../'; +import { ALIGNMENT_VALUES } from '../utils'; + +const alignmentOptions = ALIGNMENT_VALUES.reduce( ( options, item ) => { + return { ...options, [ item ]: item }; +}, {} ); export default { title: 'Components/AlignmentControl', @@ -11,3 +24,16 @@ export default { export const _default = () => { return ; }; + +export const alignControlIcon = () => { + const props = { + alignment: select( 'alignment', alignmentOptions, 'center' ), + size: number( 'size', 24 ), + }; + + return ( + <> + + + ); +}; diff --git a/packages/components/src/alignment-control/styles/alignment-control-icon-styles.js b/packages/components/src/alignment-control/styles/alignment-control-icon-styles.js new file mode 100644 index 00000000000000..8060fef9bb010f --- /dev/null +++ b/packages/components/src/alignment-control/styles/alignment-control-icon-styles.js @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; +import { css } from '@emotion/core'; + +/** + * Internal dependencies + */ +import { + rootBase, + pointBase, + Cell as CellBase, +} from './alignment-control-styles'; +import { color } from '../../utils/style-mixins'; + +const rootSize = ( { size } ) => { + return css( { + gridTemplateRows: `repeat( 3, calc( ${ size }px / 3))`, + maxWidth: size, + } ); +}; + +export const Root = styled.div` + padding: 1px; + + ${rootBase}; + ${rootSize}; +`; + +const pointActive = ( { isActive } ) => { + const boxShadow = isActive + ? `0 0 0 1px ${ color( 'blue.medium.focus' ) }` + : null; + const pointColor = isActive ? color( 'blue.medium.focus' ) : 'currentColor'; + + return css` + box-shadow: ${boxShadow}; + color: ${pointColor}; + + *:hover > & { + color: ${pointColor}; + } + `; +}; + +export const Point = styled.div` + height: 2px; + width: 2px; + ${pointBase}; + ${pointActive}; +`; + +export const Cell = styled( CellBase )` + cursor: initial; +`; diff --git a/packages/components/src/alignment-control/styles/alignment-control-styles.js b/packages/components/src/alignment-control/styles/alignment-control-styles.js index 6707aa8609c1f7..10f05f9ee6378f 100644 --- a/packages/components/src/alignment-control/styles/alignment-control-styles.js +++ b/packages/components/src/alignment-control/styles/alignment-control-styles.js @@ -9,49 +9,69 @@ import { css } from '@emotion/core'; */ import { color, reduceMotion } from '../../utils/style-mixins'; +export const rootBase = () => { + return css` + border-radius: 2px; + box-sizing: border-box; + display: grid; + grid-template-columns: repeat( 3, 1fr ); + outline: none; + `; +}; + export const Root = styled.div` --maxWidth: 92px; - border: 1px solid transparent; - border-radius: 2px; - max-width: var( --maxWidth ); - display: grid; - grid-template-columns: repeat( 3, 1fr ); grid-template-rows: repeat( 3, calc( var( --maxWidth ) / 3 ) ); - outline: none; + max-width: var( --maxWidth ); &:active, &:focus { border: 1px solid ${color( 'blue.medium.focus' )}; } + + ${rootBase}; `; +const pointActive = ( { isActive } ) => { + const boxShadow = isActive ? `0 0 0 2px ${ color( 'black' ) }` : null; + const pointColor = isActive ? color( 'black' ) : color( 'lightGray.800' ); + const pointColorHover = isActive + ? color( 'black' ) + : color( 'blue.medium.focus' ); + + return css` + box-shadow: ${boxShadow}; + color: ${pointColor}; + + *:hover > & { + color: ${pointColorHover}; + } + `; +}; + +export const pointBase = ( props ) => { + return css` + background: currentColor; + box-sizing: border-box; + display: grid; + margin: auto; + transition: all 120ms linear; + + ${reduceMotion( 'transition' )}; + ${pointActive( props )}; + `; +}; + export const Point = styled.div` - width: 6px; height: 6px; - margin: auto; - transition: all 120ms linear; - background: currentColor; - ${reduceMotion( 'transition' )}; - - ${( { isActive } ) => - css( { - boxShadow: isActive ? `0 0 0 2px ${ color( 'black' ) }` : null, - color: isActive ? color( 'black' ) : color( 'lightGray.800' ), - } )} - - *:hover > & { - ${( { isActive } ) => - css( { - color: isActive - ? color( 'black' ) - : color( 'blue.medium.focus' ), - } )} - } + width: 6px; + ${pointBase}; `; export const Cell = styled.div` appearance: none; border: none; + box-sizing: border-box; margin: 0; display: flex; position: relative; diff --git a/packages/components/src/alignment-control/test/utils.test.js b/packages/components/src/alignment-control/test/utils.test.js index 4bed9166ae6a35..57883a015422fc 100644 --- a/packages/components/src/alignment-control/test/utils.test.js +++ b/packages/components/src/alignment-control/test/utils.test.js @@ -1,7 +1,11 @@ /** * Internal dependencies */ -import { isAlignmentValid } from '../utils'; +import { + ALIGNMENT_VALUES, + getAlignmentIndex, + isAlignmentValid, +} from '../utils'; describe( 'AlignmentControl', () => { describe( 'isAlignmentValid', () => { @@ -19,9 +23,9 @@ describe( 'AlignmentControl', () => { it( 'should return false for invalid alignment values', () => { expect( isAlignmentValid( 'top bottom' ) ).toBe( false ); expect( isAlignmentValid( 'left right' ) ).toBe( false ); - expect( isAlignmentValid( 'near middle' ) ).toBe( false ); - expect( isAlignmentValid( 'middle middle' ) ).toBe( false ); - expect( isAlignmentValid( 'middle' ) ).toBe( false ); + expect( isAlignmentValid( 'near mid' ) ).toBe( false ); + expect( isAlignmentValid( 'mid mid' ) ).toBe( false ); + expect( isAlignmentValid( 'mid' ) ).toBe( false ); } ); it( 'should only consider first 2 (max) values', () => { @@ -32,5 +36,20 @@ describe( 'AlignmentControl', () => { false ); } ); + + it( 'should remap middle to center', () => { + expect( isAlignmentValid( 'middle' ) ).toBe( true ); + expect( isAlignmentValid( 'middle middle' ) ).toBe( true ); + expect( isAlignmentValid( 'top middle' ) ).toBe( true ); + expect( isAlignmentValid( 'middle bottom' ) ).toBe( true ); + } ); + } ); + + describe( 'getAlignmentIndex', () => { + it( 'should return correct index given an alignment value', () => { + ALIGNMENT_VALUES.forEach( ( alignment, index ) => { + expect( getAlignmentIndex( alignment ) ).toBe( index ); + } ); + } ); } ); } ); diff --git a/packages/components/src/alignment-control/utils.js b/packages/components/src/alignment-control/utils.js index 95a987e100a7ed..80c9230d50289f 100644 --- a/packages/components/src/alignment-control/utils.js +++ b/packages/components/src/alignment-control/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { isUndefined } from 'lodash'; +import { isEqual, isUndefined } from 'lodash'; export const DIRECTION = { UP: 'up', @@ -15,7 +15,7 @@ export const BASE_ALIGNMENTS = [ [ 'top', 'center' ], [ 'top', 'right' ], [ 'center', 'left' ], - [ 'center' ], + [ 'center', 'center' ], [ 'center', 'right' ], [ 'bottom', 'left' ], [ 'bottom', 'center' ], @@ -60,6 +60,16 @@ export function transformAlignment( alignments = [] ) { value = []; } + value = value + .map( ( v ) => v.toLowerCase() ) + // Supports remapping of 'middle' to 'center' + .map( ( v ) => v.replace( 'middle', 'center' ) ); + + // Handles cases were only 'center' or ['center'] is provided + if ( value.length === 1 && value[ 0 ] === 'center' ) { + value.push( 'center' ); + } + return value.sort(); } @@ -87,12 +97,10 @@ export function getAlignmentValues( alignment = 'center' ) { const values = transformAlignment( alignment ); if ( values.length > 2 ) { - return false; + return undefined; } - const match = ALIGNMENTS.find( ( a ) => { - return a.every( ( v ) => values.includes( v ) ); - } ); + const match = ALIGNMENTS.find( ( a ) => isEqual( values, a ) ); return match; } From 4ab514f87da98b7216ffa4fb401c30f2fd9c2ce1 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Mon, 23 Mar 2020 16:39:06 -0400 Subject: [PATCH 05/35] Update Docs Manifest --- docs/manifest.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/manifest.json b/docs/manifest.json index fbd0a960597ba4..931d673d7d6051 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -629,6 +629,12 @@ "markdown_source": "../packages/components/README.md", "parent": null }, + { + "title": "AlignmentControl", + "slug": "alignment-control", + "markdown_source": "../packages/components/src/alignment-control/README.md", + "parent": "components" + }, { "title": "AnglePickerControl", "slug": "angle-picker-control", From ccd4cde3ad89ac501e24c286f35ad21f4db1ae7b Mon Sep 17 00:00:00 2001 From: Jon Q Date: Mon, 23 Mar 2020 16:39:49 -0400 Subject: [PATCH 06/35] Export AlignControl as __experimentalAlignControl --- packages/components/src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 22010927940e1b..39d840bbb10a58 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -11,6 +11,7 @@ export { } from '@wordpress/primitives'; // Components +export { default as __experimentalAlignControl } from './align-control'; export { default as Animate } from './animate'; export { default as AnglePickerControl } from './angle-picker-control'; export { default as Autocomplete } from './autocomplete'; From a1d4c8e61f76c2a084555786b5f71c4b1cc83b04 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Mon, 23 Mar 2020 16:49:39 -0400 Subject: [PATCH 07/35] Fix export for AlignmentControl (__experimentalAlignmentControl) --- packages/components/src/alignment-control/stories/index.js | 2 +- packages/components/src/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/alignment-control/stories/index.js b/packages/components/src/alignment-control/stories/index.js index 9ef48cb2361f81..529a4279713beb 100644 --- a/packages/components/src/alignment-control/stories/index.js +++ b/packages/components/src/alignment-control/stories/index.js @@ -25,7 +25,7 @@ export const _default = () => { return ; }; -export const alignControlIcon = () => { +export const alignmentControlIcon = () => { const props = { alignment: select( 'alignment', alignmentOptions, 'center' ), size: number( 'size', 24 ), diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 39d840bbb10a58..3b82dcf1f8ec20 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -11,7 +11,7 @@ export { } from '@wordpress/primitives'; // Components -export { default as __experimentalAlignControl } from './align-control'; +export { default as __experimentalAlignmentControl } from './alignment-control'; export { default as Animate } from './animate'; export { default as AnglePickerControl } from './angle-picker-control'; export { default as Autocomplete } from './autocomplete'; From 5a747a0925a07e8bb4370ff448cc600653bebdea Mon Sep 17 00:00:00 2001 From: Jon Quach Date: Tue, 24 Mar 2020 09:09:37 -0400 Subject: [PATCH 08/35] Update packages/components/src/alignment-control/utils.js Co-Authored-By: Zebulan Stanphill --- packages/components/src/alignment-control/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/alignment-control/utils.js b/packages/components/src/alignment-control/utils.js index 80c9230d50289f..534bba6557cbe1 100644 --- a/packages/components/src/alignment-control/utils.js +++ b/packages/components/src/alignment-control/utils.js @@ -165,8 +165,8 @@ export function getCoordsFromIndex( index = 0 ) { export function getIndexFromCoords( coords, fallback = 0 ) { const [ x, y ] = coords; const exists = - ! isUndefined( ALIGNMENT_MATRIX[ x ] ) && - ! isUndefined( ALIGNMENT_MATRIX[ x ][ y ] ); + ALIGNMENT_MATRIX[ x ] !== undefined && + ALIGNMENT_MATRIX[ x ][ y ] !== undefined; return exists ? ALIGNMENT_MATRIX[ x ][ y ] : fallback; } From d5bc9c33c691b1e320d205c3b021226189ce0407 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 25 Mar 2020 17:32:46 -0400 Subject: [PATCH 09/35] Integrate AlignmentControl with Cover block Renamed AlignmentControl to AlignmentMatrixControl. --- .../block-alignment-matrix-toolbar/index.js | 49 +++++++++++++++++++ .../block-alignment-matrix-toolbar/style.scss | 7 +++ packages/block-editor/src/components/index.js | 1 + packages/block-editor/src/style.scss | 1 + packages/block-library/src/cover/block.json | 3 ++ packages/block-library/src/cover/edit.js | 34 +++++++++++++ packages/block-library/src/cover/editor.scss | 17 +++++++ .../src/alignment-control/README.md | 18 ------- .../src/alignment-matrix-control/README.md | 18 +++++++ .../icon.js | 14 ++++-- .../index.js | 40 +++++++++++---- .../stories/index.js | 16 +++--- .../alignment-matrix-control-icon-styles.js} | 10 ++-- .../alignment-matrix-control-styles.js} | 30 +++++++----- .../test/utils.test.js | 2 +- .../utils.js | 7 ++- packages/components/src/index.js | 2 +- 17 files changed, 209 insertions(+), 60 deletions(-) create mode 100644 packages/block-editor/src/components/block-alignment-matrix-toolbar/index.js create mode 100644 packages/block-editor/src/components/block-alignment-matrix-toolbar/style.scss delete mode 100644 packages/components/src/alignment-control/README.md create mode 100644 packages/components/src/alignment-matrix-control/README.md rename packages/components/src/{alignment-control => alignment-matrix-control}/icon.js (68%) rename packages/components/src/{alignment-control => alignment-matrix-control}/index.js (67%) rename packages/components/src/{alignment-control => alignment-matrix-control}/stories/index.js (56%) rename packages/components/src/{alignment-control/styles/alignment-control-icon-styles.js => alignment-matrix-control/styles/alignment-matrix-control-icon-styles.js} (85%) rename packages/components/src/{alignment-control/styles/alignment-control-styles.js => alignment-matrix-control/styles/alignment-matrix-control-styles.js} (76%) rename packages/components/src/{alignment-control => alignment-matrix-control}/test/utils.test.js (97%) rename packages/components/src/{alignment-control => alignment-matrix-control}/utils.js (96%) diff --git a/packages/block-editor/src/components/block-alignment-matrix-toolbar/index.js b/packages/block-editor/src/components/block-alignment-matrix-toolbar/index.js new file mode 100644 index 00000000000000..2c584ab374a87b --- /dev/null +++ b/packages/block-editor/src/components/block-alignment-matrix-toolbar/index.js @@ -0,0 +1,49 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + Toolbar, + __experimentalAlignmentMatrixControl as AlignmentMatrixControl, +} from '@wordpress/components'; + +const POPOVER_PROPS = { + className: 'block-editor-block-alignment-matrix-toolbar', + position: 'bottom right', +}; + +export function BlockAlignmentMatrixToolbar( props ) { + const { + isCollapsed = true, + label = __( 'Change matrix alignment' ), + onChange = noop, + value = 'center', + } = props; + + const icon = ; + + return ( + + { () => { + return ( + + ); + } } + + ); +} + +export default BlockAlignmentMatrixToolbar; diff --git a/packages/block-editor/src/components/block-alignment-matrix-toolbar/style.scss b/packages/block-editor/src/components/block-alignment-matrix-toolbar/style.scss new file mode 100644 index 00000000000000..aa255c6b647f43 --- /dev/null +++ b/packages/block-editor/src/components/block-alignment-matrix-toolbar/style.scss @@ -0,0 +1,7 @@ +.block-editor-block-alignment-matrix-toolbar { + .components-popover__content { + min-width: 0; + padding: $grid-unit; + width: auto; + } +} diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index e0c1ecd7901617..0c16514a59a8d0 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -8,6 +8,7 @@ export * from './font-sizes'; export { default as AlignmentToolbar } from './alignment-toolbar'; export { default as Autocomplete } from './autocomplete'; export { default as BlockAlignmentToolbar } from './block-alignment-toolbar'; +export { default as __experimentalBlockAlignmentMatrixToolbar } from './block-alignment-matrix-toolbar'; export { default as BlockBreadcrumb } from './block-breadcrumb'; export { BlockContextProvider } from './block-context'; export { default as BlockControls } from './block-controls'; diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index a04b2cc8cd26be..55419603a6d1e8 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -6,6 +6,7 @@ // Only add styles for components that are used inside the editing canvas here: @import "./autocompleters/style.scss"; +@import "./components/block-alignment-matrix-toolbar/style.scss"; @import "./components/block-icon/style.scss"; @import "./components/block-inspector/style.scss"; @import "./components/block-list/style.scss"; diff --git a/packages/block-library/src/cover/block.json b/packages/block-library/src/cover/block.json index c2b3098b16c23e..630bb1aeed8430 100644 --- a/packages/block-library/src/cover/block.json +++ b/packages/block-library/src/cover/block.json @@ -40,6 +40,9 @@ }, "customGradient": { "type": "string" + }, + "contentPosition": { + "type": "string" } } } diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 5fe5db7a206757..dc327e39ec8f82 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -19,6 +19,7 @@ import { ResizableBox, ToggleControl, withNotices, + __experimentalAlignmentMatrixControl as AlignmentMatrixControl, } from '@wordpress/components'; import { compose, withInstanceId, useInstanceId } from '@wordpress/compose'; import { @@ -33,6 +34,7 @@ import { __experimentalUseGradient, __experimentalPanelColorGradientSettings as PanelColorGradientSettings, __experimentalUnitControl as UnitControl, + __experimentalBlockAlignmentMatrixToolbar as BlockAlignmentMatrixToolbar, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { withDispatch } from '@wordpress/data'; @@ -66,6 +68,29 @@ const INNER_BLOCKS_TEMPLATE = [ ], ]; +const { + __getAlignmentFlexProps: getAlignmentFlexProps, +} = AlignmentMatrixControl; + +function getAlignmentFlexStyles( contentPosition ) { + const [ alignItems, justifyContent ] = getAlignmentFlexProps( + contentPosition + ); + + return { + alignItems, + justifyContent, + }; +} + +function isContentPositionCenter( contentPosition ) { + return ( + ! contentPosition || + contentPosition === 'center center' || + contentPosition === 'center' + ); +} + function retrieveFastAverageColor() { if ( ! retrieveFastAverageColor.fastAverageColor ) { retrieveFastAverageColor.fastAverageColor = new FastAverageColor(); @@ -233,6 +258,7 @@ function CoverEdit( { noticeOperations, } ) { const { + contentPosition, id, backgroundType, dimRatio, @@ -277,6 +303,7 @@ function CoverEdit( { : {} ), backgroundColor: overlayColor.color, minHeight: temporaryMinHeight || minHeightWithUnit || undefined, + ...getAlignmentFlexStyles( contentPosition ), }; if ( gradientValue && ! url ) { @@ -293,6 +320,12 @@ function CoverEdit( { const controls = ( <> + + setAttributes( { contentPosition: nextPosition } ) + } + /> { hasBackground && ( .wp-block-cover, [data-align="right"] > .wp-block-cover, [data-align="left"] > .wp-block-cover-image, [data-align="right"] > .wp-block-cover-image { max-width: $content-width / 2; width: 100%; +======= + // Apply max-width to floated items that have no intrinsic width. + [data-align="left"] &, + [data-align="right"] & { + max-width: $content-width / 2; + width: 100%; + } + + &.has-custom-content-position.has-custom-content-position { + .wp-block-cover__inner-container { + margin: 0; + padding: 0 $grid-unit-40; + width: auto; + } + } +>>>>>>> Integrate AlignmentControl with Cover block } .block-library-cover__reset-button { diff --git a/packages/components/src/alignment-control/README.md b/packages/components/src/alignment-control/README.md deleted file mode 100644 index 9f64b237647404..00000000000000 --- a/packages/components/src/alignment-control/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# AlignmentControl - -AlignmentControl components let adjust horizontal and vertical alignments for UI. - -## Usage - -```jsx -import { AlignmentControl } from '@wordpress/components'; -import { useState } from '@wordpress/elememt'; - -const Example = () => { - const [ alignment, setAlignment ] = useState( 'center' ); - - return ( - - ); -}; -``` diff --git a/packages/components/src/alignment-matrix-control/README.md b/packages/components/src/alignment-matrix-control/README.md new file mode 100644 index 00000000000000..22a2970447a804 --- /dev/null +++ b/packages/components/src/alignment-matrix-control/README.md @@ -0,0 +1,18 @@ +# AlignmentMatrixControl + +AlignmentMatrixControl components let adjust horizontal and vertical alignments for UI. + +## Usage + +```jsx +import { AlignmentMatrixControl } from '@wordpress/components'; +import { useState } from '@wordpress/elememt'; + +const Example = () => { + const [ alignment, setAlignment ] = useState( 'center' ); + + return ( + + ); +}; +``` diff --git a/packages/components/src/alignment-control/icon.js b/packages/components/src/alignment-matrix-control/icon.js similarity index 68% rename from packages/components/src/alignment-control/icon.js rename to packages/components/src/alignment-matrix-control/icon.js index 015c06fa902891..c747394815a930 100644 --- a/packages/components/src/alignment-control/icon.js +++ b/packages/components/src/alignment-matrix-control/icon.js @@ -2,16 +2,20 @@ * Internal dependencies */ import { ALIGNMENTS, getAlignmentIndex } from './utils'; -import { Root, Cell, Point } from './styles/alignment-control-icon-styles'; +import { + Root, + Cell, + Point, +} from './styles/alignment-matrix-control-icon-styles'; -export default function AlignmentControlIcon( { - alignment = 'center', +export default function AlignmentMatrixControlIcon( { + height, size: sizeProp = 24, + value = 'center', width, - height, ...props } ) { - const alignIndex = getAlignmentIndex( alignment ); + const alignIndex = getAlignmentIndex( value ); const size = sizeProp || width || height; return ( diff --git a/packages/components/src/alignment-control/index.js b/packages/components/src/alignment-matrix-control/index.js similarity index 67% rename from packages/components/src/alignment-control/index.js rename to packages/components/src/alignment-matrix-control/index.js index a1f327276a0135..1bdfb6a2f2aeac 100644 --- a/packages/components/src/alignment-control/index.js +++ b/packages/components/src/alignment-matrix-control/index.js @@ -6,7 +6,7 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { useRef } from '@wordpress/element'; +import { useEffect, useRef } from '@wordpress/element'; import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; /** @@ -18,21 +18,23 @@ import { FLEX_ALIGNMENT_PROPS, getAlignmentIndex, getAlignmentValueFromIndex, + getAlignmentFlexProps, getNextIndexFromDirection, } from './utils'; -import { Root, Cell, Point } from './styles/alignment-control-styles'; +import { Root, Cell, Point } from './styles/alignment-matrix-control-styles'; import { useControlledState } from '../utils/hooks'; -import AlignmentControlIcon from './icon'; +import AlignmentMatrixControlIcon from './icon'; // TODO: Account for RTL alignments -export default function AlignmentControl( { - alignment: alignmentProp = 'center', +export default function AlignmentMatrixControl( { + hasFocusBorder = true, onChange = noop, onKeyDown = noop, + value = 'center', ...props } ) { const [ alignIndex, setAlignIndex ] = useControlledState( - getAlignmentIndex( alignmentProp ) + getAlignmentIndex( value ) ); const nodeRef = useRef(); @@ -93,12 +95,30 @@ export default function AlignmentControl( { handleOnChange( index, { flexProps: FLEX_ALIGNMENT_PROPS[ index ] } ); }; + /** + * Keydown event is handled on the native node element rather than + * on the React Element. This resolves interaction conflicts when + * integrated with other components, such as Toolbar. + */ + useEffect( () => { + const node = nodeRef.current; + if ( node ) { + node.addEventListener( 'keydown', handleOnKeyDown ); + } + + return () => { + if ( node ) { + node.removeEventListener( 'keydown', handleOnKeyDown ); + } + }; + }, [ handleOnKeyDown ] ); + return ( { ALIGNMENTS.map( ( align, index ) => { const isActive = alignIndex === index; @@ -116,4 +136,6 @@ export default function AlignmentControl( { ); } -AlignmentControl.icon = ; +AlignmentMatrixControl.Icon = AlignmentMatrixControlIcon; +AlignmentMatrixControl.icon = ; +AlignmentMatrixControl.__getAlignmentFlexProps = getAlignmentFlexProps; diff --git a/packages/components/src/alignment-control/stories/index.js b/packages/components/src/alignment-matrix-control/stories/index.js similarity index 56% rename from packages/components/src/alignment-control/stories/index.js rename to packages/components/src/alignment-matrix-control/stories/index.js index 529a4279713beb..d5af3422177edb 100644 --- a/packages/components/src/alignment-control/stories/index.js +++ b/packages/components/src/alignment-matrix-control/stories/index.js @@ -5,11 +5,11 @@ import { number, select } from '@storybook/addon-knobs'; /** * WordPress dependencies */ -import { Icon } from '@wordpress/icons'; +import { Icon as BaseIcon } from '@wordpress/icons'; /** * Internal dependencies */ -import AlignmentControl from '../'; +import AlignmentMatrixControl from '../'; import { ALIGNMENT_VALUES } from '../utils'; const alignmentOptions = ALIGNMENT_VALUES.reduce( ( options, item ) => { @@ -17,23 +17,23 @@ const alignmentOptions = ALIGNMENT_VALUES.reduce( ( options, item ) => { }, {} ); export default { - title: 'Components/AlignmentControl', - component: AlignmentControl, + title: 'Components/AlignmentMatrixControl', + component: AlignmentMatrixControl, }; export const _default = () => { - return ; + return ; }; -export const alignmentControlIcon = () => { +export const icon = () => { const props = { - alignment: select( 'alignment', alignmentOptions, 'center' ), + value: select( 'value', alignmentOptions, 'center' ), size: number( 'size', 24 ), }; return ( <> - + ); }; diff --git a/packages/components/src/alignment-control/styles/alignment-control-icon-styles.js b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.js similarity index 85% rename from packages/components/src/alignment-control/styles/alignment-control-icon-styles.js rename to packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.js index 8060fef9bb010f..fded88a40d2747 100644 --- a/packages/components/src/alignment-control/styles/alignment-control-icon-styles.js +++ b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-icon-styles.js @@ -11,7 +11,7 @@ import { rootBase, pointBase, Cell as CellBase, -} from './alignment-control-styles'; +} from './alignment-matrix-control-styles'; import { color } from '../../utils/style-mixins'; const rootSize = ( { size } ) => { @@ -22,7 +22,7 @@ const rootSize = ( { size } ) => { }; export const Root = styled.div` - padding: 1px; + width: 100%; ${rootBase}; ${rootSize}; @@ -44,13 +44,11 @@ const pointActive = ( { isActive } ) => { `; }; -export const Point = styled.div` +export const Point = styled.span` height: 2px; width: 2px; ${pointBase}; ${pointActive}; `; -export const Cell = styled( CellBase )` - cursor: initial; -`; +export const Cell = CellBase; diff --git a/packages/components/src/alignment-control/styles/alignment-control-styles.js b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js similarity index 76% rename from packages/components/src/alignment-control/styles/alignment-control-styles.js rename to packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js index 10f05f9ee6378f..8c17e1673db443 100644 --- a/packages/components/src/alignment-control/styles/alignment-control-styles.js +++ b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js @@ -19,17 +19,26 @@ export const rootBase = () => { `; }; -export const Root = styled.div` - --maxWidth: 92px; - grid-template-rows: repeat( 3, calc( var( --maxWidth ) / 3 ) ); - max-width: var( --maxWidth ); +const rootBorder = ( { hasFocusBorder } ) => { + if ( ! hasFocusBorder ) return ''; - &:active, - &:focus { - border: 1px solid ${color( 'blue.medium.focus' )}; - } + return css` + &:active, + &:focus { + border-color: ${color( 'blue.medium.focus' )}; + } + `; +}; + +export const Root = styled.div` + --width: 92px; + border: 1px solid transparent; + cursor: pointer; + grid-template-rows: repeat( 3, calc( var( --width ) / 3 ) ); + width: var( --width, 92px ); ${rootBase}; + ${rootBorder}; `; const pointActive = ( { isActive } ) => { @@ -62,13 +71,13 @@ export const pointBase = ( props ) => { `; }; -export const Point = styled.div` +export const Point = styled.span` height: 6px; width: 6px; ${pointBase}; `; -export const Cell = styled.div` +export const Cell = styled.span` appearance: none; border: none; box-sizing: border-box; @@ -76,7 +85,6 @@ export const Cell = styled.div` display: flex; position: relative; outline: none; - cursor: pointer; align-items: center; justify-content: center; padding: 0; diff --git a/packages/components/src/alignment-control/test/utils.test.js b/packages/components/src/alignment-matrix-control/test/utils.test.js similarity index 97% rename from packages/components/src/alignment-control/test/utils.test.js rename to packages/components/src/alignment-matrix-control/test/utils.test.js index 57883a015422fc..65373269e1c8fe 100644 --- a/packages/components/src/alignment-control/test/utils.test.js +++ b/packages/components/src/alignment-matrix-control/test/utils.test.js @@ -7,7 +7,7 @@ import { isAlignmentValid, } from '../utils'; -describe( 'AlignmentControl', () => { +describe( 'AlignmentMatrixControl', () => { describe( 'isAlignmentValid', () => { it( 'should return true for correctly matching alignment values', () => { // x and y values diff --git a/packages/components/src/alignment-control/utils.js b/packages/components/src/alignment-matrix-control/utils.js similarity index 96% rename from packages/components/src/alignment-control/utils.js rename to packages/components/src/alignment-matrix-control/utils.js index 534bba6557cbe1..a27a1de580e066 100644 --- a/packages/components/src/alignment-control/utils.js +++ b/packages/components/src/alignment-matrix-control/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { isEqual, isUndefined } from 'lodash'; +import { isEqual } from 'lodash'; export const DIRECTION = { UP: 'up', @@ -205,3 +205,8 @@ export function getNextIndexFromDirection( currentIndex, direction ) { return getIndexFromCoords( [ moveX, moveY ], currentIndex ); } + +export function getAlignmentFlexProps( alignment = 'center' ) { + const index = getAlignmentIndex( alignment ); + return FLEX_ALIGNMENT_PROPS[ index ]; +} diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 3b82dcf1f8ec20..293eff0c10bf11 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -11,7 +11,7 @@ export { } from '@wordpress/primitives'; // Components -export { default as __experimentalAlignmentControl } from './alignment-control'; +export { default as __experimentalAlignmentMatrixControl } from './alignment-matrix-control'; export { default as Animate } from './animate'; export { default as AnglePickerControl } from './angle-picker-control'; export { default as Autocomplete } from './autocomplete'; From 37f4eb8daf3a27bd3e482aceaab5d10bfc8a0284 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 25 Mar 2020 17:35:04 -0400 Subject: [PATCH 10/35] Update Docs manifest and Storyshot --- docs/manifest.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/manifest.json b/docs/manifest.json index 931d673d7d6051..c9e4d0b8b32d7c 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -630,9 +630,9 @@ "parent": null }, { - "title": "AlignmentControl", - "slug": "alignment-control", - "markdown_source": "../packages/components/src/alignment-control/README.md", + "title": "AlignmentMatrixControl", + "slug": "alignment-matrix-control", + "markdown_source": "../packages/components/src/alignment-matrix-control/README.md", "parent": "components" }, { From 4651a81d35eddc96913f83c0f5b4695516af4281 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 25 Mar 2020 18:01:06 -0400 Subject: [PATCH 11/35] Improve AlignmentMatrix rendering in Edit and Save functions Remove unused code from base AlignmentMatrix component --- packages/block-library/src/cover/edit.js | 52 +++++++------------ packages/block-library/src/cover/editor.scss | 3 +- packages/block-library/src/cover/save.js | 9 +++- packages/block-library/src/cover/shared.js | 33 ++++++++++++ packages/block-library/src/cover/style.scss | 48 +++++++++++++++++ .../src/alignment-matrix-control/index.js | 19 ++++--- .../src/alignment-matrix-control/utils.js | 17 ------ 7 files changed, 117 insertions(+), 64 deletions(-) diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index dc327e39ec8f82..510afd1a63e47f 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -19,7 +19,6 @@ import { ResizableBox, ToggleControl, withNotices, - __experimentalAlignmentMatrixControl as AlignmentMatrixControl, } from '@wordpress/components'; import { compose, withInstanceId, useInstanceId } from '@wordpress/compose'; import { @@ -51,6 +50,8 @@ import { CSS_UNITS, backgroundImageStyles, dimRatioToClass, + isContentPositionCenter, + getPositionClassName, } from './shared'; /** @@ -68,29 +69,6 @@ const INNER_BLOCKS_TEMPLATE = [ ], ]; -const { - __getAlignmentFlexProps: getAlignmentFlexProps, -} = AlignmentMatrixControl; - -function getAlignmentFlexStyles( contentPosition ) { - const [ alignItems, justifyContent ] = getAlignmentFlexProps( - contentPosition - ); - - return { - alignItems, - justifyContent, - }; -} - -function isContentPositionCenter( contentPosition ) { - return ( - ! contentPosition || - contentPosition === 'center center' || - contentPosition === 'center' - ); -} - function retrieveFastAverageColor() { if ( ! retrieveFastAverageColor.fastAverageColor ) { retrieveFastAverageColor.fastAverageColor = new FastAverageColor(); @@ -303,7 +281,6 @@ function CoverEdit( { : {} ), backgroundColor: overlayColor.color, minHeight: temporaryMinHeight || minHeightWithUnit || undefined, - ...getAlignmentFlexStyles( contentPosition ), }; if ( gradientValue && ! url ) { @@ -472,15 +449,22 @@ function CoverEdit( { ); } - const classes = classnames( className, dimRatioToClass( dimRatio ), { - 'is-dark-theme': isDark, - 'has-background-dim': dimRatio !== 0, - 'has-parallax': hasParallax, - [ overlayColor.class ]: overlayColor.class, - 'has-background-gradient': gradientValue, - [ gradientClass ]: ! url && gradientClass, - 'has-custom-content-position': isContentPositionCenter(), - } ); + const classes = classnames( + className, + dimRatioToClass( dimRatio ), + { + 'is-dark-theme': isDark, + 'has-background-dim': dimRatio !== 0, + 'has-parallax': hasParallax, + [ overlayColor.class ]: overlayColor.class, + 'has-background-gradient': gradientValue, + [ gradientClass ]: ! url && gradientClass, + 'has-custom-content-position': ! isContentPositionCenter( + contentPosition + ), + }, + getPositionClassName( contentPosition ) + ); return ( <> diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index c3484dc7842dc0..ce1e38d05b085c 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -39,11 +39,10 @@ width: 100%; } + &.has-custom-content-position.has-custom-content-position { .wp-block-cover__inner-container { - margin: 0; padding: 0 $grid-unit-40; - width: auto; } } >>>>>>> Integrate AlignmentControl with Cover block diff --git a/packages/block-library/src/cover/save.js b/packages/block-library/src/cover/save.js index 4e45fa41347b58..0e4fdbdd176909 100644 --- a/packages/block-library/src/cover/save.js +++ b/packages/block-library/src/cover/save.js @@ -20,12 +20,15 @@ import { VIDEO_BACKGROUND_TYPE, backgroundImageStyles, dimRatioToClass, + isContentPositionCenter, + getPositionClassName, } from './shared'; export default function save( { attributes } ) { const { backgroundType, gradient, + contentPosition, customGradient, customOverlayColor, dimRatio, @@ -70,7 +73,11 @@ export default function save( { attributes } ) { 'has-parallax': hasParallax, 'has-background-gradient': gradient || customGradient, [ gradientClass ]: ! url && gradientClass, - } + 'has-custom-content-position': ! isContentPositionCenter( + contentPosition + ), + }, + getPositionClassName( contentPosition ) ); return ( diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index 1fba24ccd1c68f..150f065641dc29 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -1,3 +1,22 @@ +/** + * WordPress dependencies + */ +import { __experimentalAlignmentMatrixControl as AlignmentMatrixControl } from '@wordpress/components'; + +const { __getAlignmentIndex: getAlignmentIndex } = AlignmentMatrixControl; + +const POSITION_CLASSNAMES = [ + 'is-position-top-left', + 'is-position-top-center', + 'is-position-top-right', + 'is-position-center-left', + 'is-position-center-center', + 'is-position-center-right', + 'is-position-bottom-left', + 'is-position-bottom-center', + 'is-position-bottom-right', +]; + export const IMAGE_BACKGROUND_TYPE = 'image'; export const VIDEO_BACKGROUND_TYPE = 'video'; export const COVER_MIN_HEIGHT = 50; @@ -56,3 +75,17 @@ export function attributesFromMedia( setAttributes ) { } ); }; } + +export function getPositionClassName( contentPosition ) { + const index = getAlignmentIndex( contentPosition ); + + return POSITION_CLASSNAMES[ index ]; +} + +export function isContentPositionCenter( contentPosition ) { + return ( + ! contentPosition || + contentPosition === 'center center' || + contentPosition === 'center' + ); +} diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss index 1742bf45f9de5a..af56d7cc11447e 100644 --- a/packages/block-library/src/cover/style.scss +++ b/packages/block-library/src/cover/style.scss @@ -108,6 +108,54 @@ color: inherit; } } + + // Position: Top + &.is-position-top-left { + align-items: flex-start; + justify-content: flex-start; + } + &.is-position-top-center { + align-items: flex-start; + justify-content: center; + } + &.is-position-top-right { + align-items: flex-start; + justify-content: flex-end; + } + // Position: Center + &.is-position-center-left { + align-items: center; + justify-content: flex-start; + } + &.is-position-center-center { + align-items: center; + justify-content: center; + } + &.is-position-center-right { + align-items: center; + justify-content: flex-end; + } + // Position: Bottom + &.is-position-bottom-left { + align-items: flex-end; + justify-content: flex-start; + } + &.is-position-bottom-center { + align-items: flex-end; + justify-content: center; + } + &.is-position-bottom-right { + align-items: flex-end; + justify-content: flex-end; + } + + &.has-custom-content-position.has-custom-content-position { + .wp-block-cover__inner-container { + margin: 0; + padding: $grid-unit-40; + width: auto; + } + } } .wp-block-cover__video-background { diff --git a/packages/components/src/alignment-matrix-control/index.js b/packages/components/src/alignment-matrix-control/index.js index 1bdfb6a2f2aeac..2b5e00bbab9704 100644 --- a/packages/components/src/alignment-matrix-control/index.js +++ b/packages/components/src/alignment-matrix-control/index.js @@ -15,10 +15,8 @@ import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; import { ALIGNMENTS, DIRECTION, - FLEX_ALIGNMENT_PROPS, getAlignmentIndex, getAlignmentValueFromIndex, - getAlignmentFlexProps, getNextIndexFromDirection, } from './utils'; import { Root, Cell, Point } from './styles/alignment-matrix-control-styles'; @@ -38,11 +36,11 @@ export default function AlignmentMatrixControl( { ); const nodeRef = useRef(); - const handleOnChange = ( nextIndex ) => { + const handleOnChange = ( nextIndex, changeProps ) => { const alignName = getAlignmentValueFromIndex( nextIndex ); setAlignIndex( nextIndex ); - onChange( alignName ); + onChange( alignName, changeProps ); }; const handleOnKeyDown = ( event ) => { @@ -58,7 +56,7 @@ export default function AlignmentMatrixControl( { alignIndex, DIRECTION.UP ); - handleOnChange( nextIndex ); + handleOnChange( nextIndex, { event } ); break; case DOWN: event.preventDefault(); @@ -66,7 +64,7 @@ export default function AlignmentMatrixControl( { alignIndex, DIRECTION.DOWN ); - handleOnChange( nextIndex ); + handleOnChange( nextIndex, { event } ); break; case LEFT: event.preventDefault(); @@ -74,7 +72,7 @@ export default function AlignmentMatrixControl( { alignIndex, DIRECTION.LEFT ); - handleOnChange( nextIndex ); + handleOnChange( nextIndex, { event } ); break; case RIGHT: event.preventDefault(); @@ -82,7 +80,7 @@ export default function AlignmentMatrixControl( { alignIndex, DIRECTION.RIGHT ); - handleOnChange( nextIndex ); + handleOnChange( nextIndex, { event } ); break; default: break; @@ -92,7 +90,7 @@ export default function AlignmentMatrixControl( { const createHandleOnClick = ( index ) => ( event ) => { nodeRef.current.focus(); event.preventDefault(); - handleOnChange( index, { flexProps: FLEX_ALIGNMENT_PROPS[ index ] } ); + handleOnChange( index, { event } ); }; /** @@ -138,4 +136,5 @@ export default function AlignmentMatrixControl( { AlignmentMatrixControl.Icon = AlignmentMatrixControlIcon; AlignmentMatrixControl.icon = ; -AlignmentMatrixControl.__getAlignmentFlexProps = getAlignmentFlexProps; + +AlignmentMatrixControl.__getAlignmentIndex = getAlignmentIndex; diff --git a/packages/components/src/alignment-matrix-control/utils.js b/packages/components/src/alignment-matrix-control/utils.js index a27a1de580e066..dbb97e06966fb9 100644 --- a/packages/components/src/alignment-matrix-control/utils.js +++ b/packages/components/src/alignment-matrix-control/utils.js @@ -30,18 +30,6 @@ export const ALIGNMENT_MATRIX = [ [ 6, 7, 8 ], ]; -export const FLEX_ALIGNMENT_PROPS = [ - [ 'flex-start', 'flex-start' ], - [ 'flex-start', 'center' ], - [ 'flex-start', 'flex-end' ], - [ 'center', 'flex-start' ], - [ 'center', 'center' ], - [ 'center', 'flex-end' ], - [ 'flex-end', 'flex-start' ], - [ 'flex-end', 'center' ], - [ 'flex-end', 'flex-end' ], -]; - /** * Transforms an alignment value to an [x, y] alignment data. * @@ -205,8 +193,3 @@ export function getNextIndexFromDirection( currentIndex, direction ) { return getIndexFromCoords( [ moveX, moveY ], currentIndex ); } - -export function getAlignmentFlexProps( alignment = 'center' ) { - const index = getAlignmentIndex( alignment ); - return FLEX_ALIGNMENT_PROPS[ index ]; -} From 1c27b72ef65f22ffc05e53f5fbbfe7941130ee81 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 25 Mar 2020 18:06:39 -0400 Subject: [PATCH 12/35] Prevent rendering of position className if undefined --- packages/block-library/src/cover/shared.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index 150f065641dc29..145c8841283df0 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -77,6 +77,8 @@ export function attributesFromMedia( setAttributes ) { } export function getPositionClassName( contentPosition ) { + if ( contentPosition === undefined ) return ''; + const index = getAlignmentIndex( contentPosition ); return POSITION_CLASSNAMES[ index ]; From 69fe8024e23dc168b1e88f1dd1d46425e6c9c151 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 27 Mar 2020 10:16:52 -0400 Subject: [PATCH 13/35] Update Cover content position control label --- packages/block-library/src/cover/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 510afd1a63e47f..70c0d1b350dccc 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -298,6 +298,7 @@ function CoverEdit( { <> setAttributes( { contentPosition: nextPosition } ) From 22073f6b6e47b3a0fc1391f390e71c050998fe07 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 27 Mar 2020 10:17:42 -0400 Subject: [PATCH 14/35] Add padding to Cover (rather than inner content) --- packages/block-library/src/cover/editor.scss | 18 +++--------------- packages/block-library/src/cover/style.scss | 5 ++++- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index ce1e38d05b085c..3a2078f26300f7 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -24,28 +24,16 @@ } } -<<<<<<< HEAD [data-align="left"] > .wp-block-cover, [data-align="right"] > .wp-block-cover, [data-align="left"] > .wp-block-cover-image, [data-align="right"] > .wp-block-cover-image { max-width: $content-width / 2; width: 100%; -======= - // Apply max-width to floated items that have no intrinsic width. - [data-align="left"] &, - [data-align="right"] & { - max-width: $content-width / 2; - width: 100%; - } - +} - &.has-custom-content-position.has-custom-content-position { - .wp-block-cover__inner-container { - padding: 0 $grid-unit-40; - } - } ->>>>>>> Integrate AlignmentControl with Cover block +.wp-block-cover { + padding: $grid-unit-20 $grid-unit-40; } .block-library-cover__reset-button { diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss index af56d7cc11447e..b8a5d1a11d9e2c 100644 --- a/packages/block-library/src/cover/style.scss +++ b/packages/block-library/src/cover/style.scss @@ -152,12 +152,15 @@ &.has-custom-content-position.has-custom-content-position { .wp-block-cover__inner-container { margin: 0; - padding: $grid-unit-40; width: auto; } } } +.wp-block-cover { + padding: $grid-unit-20; +} + .wp-block-cover__video-background { position: absolute; top: 50%; From eaa7cc4149df19dbe5b98ef4ec2312d6ce6a6c83 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 27 Mar 2020 11:52:17 -0400 Subject: [PATCH 15/35] Adding a11y and RTL support to AlignmentMatrixControl Also refactors isRTL / useRTL from the rtl utils. --- .../src/alignment-matrix-control/cell.js | 21 +++ .../src/alignment-matrix-control/icon.js | 21 ++- .../src/alignment-matrix-control/index.js | 70 +++++--- .../styles/alignment-matrix-control-styles.js | 10 +- .../alignment-matrix-control/test/index.js | 40 +++++ .../alignment-matrix-control/test/utils.js | 160 ++++++++++++++++++ .../test/utils.test.js | 55 ------ .../src/alignment-matrix-control/utils.js | 31 +++- .../components/src/range-control/index.js | 4 +- packages/components/src/utils/rtl.js | 12 +- 10 files changed, 331 insertions(+), 93 deletions(-) create mode 100644 packages/components/src/alignment-matrix-control/cell.js create mode 100644 packages/components/src/alignment-matrix-control/test/index.js create mode 100644 packages/components/src/alignment-matrix-control/test/utils.js delete mode 100644 packages/components/src/alignment-matrix-control/test/utils.test.js diff --git a/packages/components/src/alignment-matrix-control/cell.js b/packages/components/src/alignment-matrix-control/cell.js new file mode 100644 index 00000000000000..091b1deede7193 --- /dev/null +++ b/packages/components/src/alignment-matrix-control/cell.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import { + Cell as CellView, + Point, +} from './styles/alignment-matrix-control-styles'; + +export default function Cell( { isActive = false, value, ...props } ) { + return ( + + + + ); +} diff --git a/packages/components/src/alignment-matrix-control/icon.js b/packages/components/src/alignment-matrix-control/icon.js index c747394815a930..c6fd7d66434ab8 100644 --- a/packages/components/src/alignment-matrix-control/icon.js +++ b/packages/components/src/alignment-matrix-control/icon.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * Internal dependencies */ @@ -9,6 +14,7 @@ import { } from './styles/alignment-matrix-control-icon-styles'; export default function AlignmentMatrixControlIcon( { + className, height, size: sizeProp = 24, value = 'center', @@ -18,12 +24,23 @@ export default function AlignmentMatrixControlIcon( { const alignIndex = getAlignmentIndex( value ); const size = sizeProp || width || height; + const classes = classnames( + 'component-alignment-matrix-control-icon', + className + ); + return ( - + { ALIGNMENTS.map( ( align, index ) => { const isActive = alignIndex === index; + return ( - + ); diff --git a/packages/components/src/alignment-matrix-control/index.js b/packages/components/src/alignment-matrix-control/index.js index 2b5e00bbab9704..12c923a6b91150 100644 --- a/packages/components/src/alignment-matrix-control/index.js +++ b/packages/components/src/alignment-matrix-control/index.js @@ -2,10 +2,13 @@ * External dependencies */ import { noop } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; +import { useInstanceId } from '@wordpress/compose'; import { useEffect, useRef } from '@wordpress/element'; import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; @@ -19,22 +22,27 @@ import { getAlignmentValueFromIndex, getNextIndexFromDirection, } from './utils'; -import { Root, Cell, Point } from './styles/alignment-matrix-control-styles'; +import Cell from './cell'; +import { Root } from './styles/alignment-matrix-control-styles'; import { useControlledState } from '../utils/hooks'; +import { useRTL } from '../utils/rtl'; import AlignmentMatrixControlIcon from './icon'; -// TODO: Account for RTL alignments export default function AlignmentMatrixControl( { + className, + label = __( 'Alignment Matrix Control' ), hasFocusBorder = true, onChange = noop, onKeyDown = noop, value = 'center', ...props } ) { + const isRTL = useRTL(); const [ alignIndex, setAlignIndex ] = useControlledState( getAlignmentIndex( value ) ); const nodeRef = useRef(); + const instanceId = useInstanceId( AlignmentMatrixControl ); const handleOnChange = ( nextIndex, changeProps ) => { const alignName = getAlignmentValueFromIndex( nextIndex ); @@ -46,41 +54,42 @@ export default function AlignmentMatrixControl( { const handleOnKeyDown = ( event ) => { const { keyCode } = event; let nextIndex; + let direction; onKeyDown( event ); switch ( keyCode ) { case UP: event.preventDefault(); - nextIndex = getNextIndexFromDirection( - alignIndex, - DIRECTION.UP - ); + direction = DIRECTION.UP; + + nextIndex = getNextIndexFromDirection( alignIndex, direction ); handleOnChange( nextIndex, { event } ); + break; case DOWN: event.preventDefault(); - nextIndex = getNextIndexFromDirection( - alignIndex, - DIRECTION.DOWN - ); + direction = DIRECTION.DOWN; + + nextIndex = getNextIndexFromDirection( alignIndex, direction ); handleOnChange( nextIndex, { event } ); + break; case LEFT: event.preventDefault(); - nextIndex = getNextIndexFromDirection( - alignIndex, - DIRECTION.LEFT - ); + direction = isRTL ? DIRECTION.RIGHT : DIRECTION.LEFT; + + nextIndex = getNextIndexFromDirection( alignIndex, direction ); handleOnChange( nextIndex, { event } ); + break; case RIGHT: event.preventDefault(); - nextIndex = getNextIndexFromDirection( - alignIndex, - DIRECTION.RIGHT - ); + direction = isRTL ? DIRECTION.LEFT : DIRECTION.RIGHT; + + nextIndex = getNextIndexFromDirection( alignIndex, direction ); handleOnChange( nextIndex, { event } ); + break; default: break; @@ -111,23 +120,40 @@ export default function AlignmentMatrixControl( { }; }, [ handleOnKeyDown ] ); + const id = `alignment-matrix-control-${ instanceId }`; + const activeCellId = `${ id }-${ alignIndex }`; + + const classes = classnames( + 'component-alignment-matrix-control', + className + ); + return ( { ALIGNMENTS.map( ( align, index ) => { const isActive = alignIndex === index; + const cellId = `${ id }-${ index }`; + const cellValue = getAlignmentValueFromIndex( index ); + return ( - - + value={ cellValue } + /> ); } ) } diff --git a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js index 8c17e1673db443..faf82d20ccfa01 100644 --- a/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js +++ b/packages/components/src/alignment-matrix-control/styles/alignment-matrix-control-styles.js @@ -37,8 +37,8 @@ export const Root = styled.div` grid-template-rows: repeat( 3, calc( var( --width ) / 3 ) ); width: var( --width, 92px ); - ${rootBase}; - ${rootBorder}; + ${rootBase} + ${rootBorder} `; const pointActive = ( { isActive } ) => { @@ -66,15 +66,15 @@ export const pointBase = ( props ) => { margin: auto; transition: all 120ms linear; - ${reduceMotion( 'transition' )}; - ${pointActive( props )}; + ${reduceMotion( 'transition' )} + ${pointActive( props )} `; }; export const Point = styled.span` height: 6px; width: 6px; - ${pointBase}; + ${pointBase} `; export const Cell = styled.span` diff --git a/packages/components/src/alignment-matrix-control/test/index.js b/packages/components/src/alignment-matrix-control/test/index.js new file mode 100644 index 00000000000000..05be599d4897e2 --- /dev/null +++ b/packages/components/src/alignment-matrix-control/test/index.js @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import { render, unmountComponentAtNode } from 'react-dom'; +import { act } from 'react-dom/test-utils'; + +/** + * Internal dependencies + */ +import AlignmentMatrixControl from '../'; + +let container = null; + +beforeEach( () => { + container = document.createElement( 'div' ); + document.body.appendChild( container ); +} ); + +afterEach( () => { + unmountComponentAtNode( container ); + container.remove(); + container = null; +} ); + +const getControl = () => { + return container.querySelector( '.component-alignment-matrix-control' ); +}; + +describe( 'AlignmentMatrixControl', () => { + describe( 'Basic rendering', () => { + it( 'should render', () => { + act( () => { + render( , container ); + } ); + const control = getControl(); + + expect( control ).toBeTruthy(); + } ); + } ); +} ); diff --git a/packages/components/src/alignment-matrix-control/test/utils.js b/packages/components/src/alignment-matrix-control/test/utils.js new file mode 100644 index 00000000000000..b44c6b1d92e1c3 --- /dev/null +++ b/packages/components/src/alignment-matrix-control/test/utils.js @@ -0,0 +1,160 @@ +/** + * Internal dependencies + */ +import { + ALIGNMENT_VALUES, + DIRECTION, + getAlignmentIndex, + isAlignmentValid, + getAlignmentValueFromIndex, + getNextIndexFromDirection, +} from '../utils'; + +describe( 'AlignmentMatrixControl', () => { + describe( 'isAlignmentValid', () => { + it( 'should return true for correctly matching alignment values', () => { + // x and y values + expect( isAlignmentValid( 'center top' ) ).toBe( true ); + expect( isAlignmentValid( 'top center' ) ).toBe( true ); + expect( isAlignmentValid( 'center bottom' ) ).toBe( true ); + // single center value + expect( isAlignmentValid( 'center' ) ).toBe( true ); + // x + y center value + expect( isAlignmentValid( 'center center' ) ).toBe( true ); + } ); + + it( 'should return false for invalid alignment values', () => { + expect( isAlignmentValid( 'top bottom' ) ).toBe( false ); + expect( isAlignmentValid( 'left right' ) ).toBe( false ); + expect( isAlignmentValid( 'near mid' ) ).toBe( false ); + expect( isAlignmentValid( 'mid mid' ) ).toBe( false ); + expect( isAlignmentValid( 'mid' ) ).toBe( false ); + } ); + + it( 'should only consider first 2 (max) values', () => { + expect( isAlignmentValid( 'center top right bottom' ) ).toBe( + false + ); + expect( isAlignmentValid( 'top center left right bottom' ) ).toBe( + false + ); + } ); + + it( 'should remap middle to center', () => { + expect( isAlignmentValid( 'middle' ) ).toBe( true ); + expect( isAlignmentValid( 'middle middle' ) ).toBe( true ); + expect( isAlignmentValid( 'top middle' ) ).toBe( true ); + expect( isAlignmentValid( 'middle bottom' ) ).toBe( true ); + } ); + } ); + + describe( 'getAlignmentIndex', () => { + it( 'should return correct index given an alignment value', () => { + ALIGNMENT_VALUES.forEach( ( alignment, index ) => { + expect( getAlignmentIndex( alignment ) ).toBe( index ); + } ); + } ); + } ); + + describe( 'getAlignmentValueFromIndex', () => { + it( 'should return correct value based on index', () => { + ALIGNMENT_VALUES.forEach( ( alignment, index ) => { + expect( getAlignmentValueFromIndex( index ) ).toBe( alignment ); + } ); + } ); + } ); + + describe( 'getNextIndexFromDirection', () => { + describe( 'Basic movements', () => { + it( 'should move index "up"', () => { + const currentIndex = 4; + + const nextDirection = getNextIndexFromDirection( + currentIndex, + DIRECTION.UP + ); + + expect( nextDirection ).toBe( 1 ); + } ); + + it( 'should move index "DOWN"', () => { + const currentIndex = 4; + + const nextDirection = getNextIndexFromDirection( + currentIndex, + DIRECTION.DOWN + ); + + expect( nextDirection ).toBe( 7 ); + } ); + + it( 'should move index "LEFT"', () => { + const currentIndex = 4; + + const nextDirection = getNextIndexFromDirection( + currentIndex, + DIRECTION.LEFT + ); + + expect( nextDirection ).toBe( 3 ); + } ); + + it( 'should move index "RIGHT"', () => { + const currentIndex = 4; + + const nextDirection = getNextIndexFromDirection( + currentIndex, + DIRECTION.RIGHT + ); + + expect( nextDirection ).toBe( 5 ); + } ); + } ); + + describe( 'Edge movements', () => { + it( 'should not move index "up" at at an edge', () => { + const currentIndex = 2; + + const nextDirection = getNextIndexFromDirection( + currentIndex, + DIRECTION.UP + ); + + expect( nextDirection ).toBe( 2 ); + } ); + + it( 'should not move index "down" at at an edge', () => { + const currentIndex = 7; + + const nextDirection = getNextIndexFromDirection( + currentIndex, + DIRECTION.DOWN + ); + + expect( nextDirection ).toBe( 7 ); + } ); + + it( 'should not move index "LEFT" at at an edge', () => { + const currentIndex = 3; + + const nextDirection = getNextIndexFromDirection( + currentIndex, + DIRECTION.LEFT + ); + + expect( nextDirection ).toBe( 3 ); + } ); + + it( 'should not move index "RIGHT" at at an edge', () => { + const currentIndex = 5; + + const nextDirection = getNextIndexFromDirection( + currentIndex, + DIRECTION.RIGHT + ); + + expect( nextDirection ).toBe( 5 ); + } ); + } ); + } ); +} ); diff --git a/packages/components/src/alignment-matrix-control/test/utils.test.js b/packages/components/src/alignment-matrix-control/test/utils.test.js deleted file mode 100644 index 65373269e1c8fe..00000000000000 --- a/packages/components/src/alignment-matrix-control/test/utils.test.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Internal dependencies - */ -import { - ALIGNMENT_VALUES, - getAlignmentIndex, - isAlignmentValid, -} from '../utils'; - -describe( 'AlignmentMatrixControl', () => { - describe( 'isAlignmentValid', () => { - it( 'should return true for correctly matching alignment values', () => { - // x and y values - expect( isAlignmentValid( 'center top' ) ).toBe( true ); - expect( isAlignmentValid( 'top center' ) ).toBe( true ); - expect( isAlignmentValid( 'center bottom' ) ).toBe( true ); - // single center value - expect( isAlignmentValid( 'center' ) ).toBe( true ); - // x + y center value - expect( isAlignmentValid( 'center center' ) ).toBe( true ); - } ); - - it( 'should return false for invalid alignment values', () => { - expect( isAlignmentValid( 'top bottom' ) ).toBe( false ); - expect( isAlignmentValid( 'left right' ) ).toBe( false ); - expect( isAlignmentValid( 'near mid' ) ).toBe( false ); - expect( isAlignmentValid( 'mid mid' ) ).toBe( false ); - expect( isAlignmentValid( 'mid' ) ).toBe( false ); - } ); - - it( 'should only consider first 2 (max) values', () => { - expect( isAlignmentValid( 'center top right bottom' ) ).toBe( - false - ); - expect( isAlignmentValid( 'top center left right bottom' ) ).toBe( - false - ); - } ); - - it( 'should remap middle to center', () => { - expect( isAlignmentValid( 'middle' ) ).toBe( true ); - expect( isAlignmentValid( 'middle middle' ) ).toBe( true ); - expect( isAlignmentValid( 'top middle' ) ).toBe( true ); - expect( isAlignmentValid( 'middle bottom' ) ).toBe( true ); - } ); - } ); - - describe( 'getAlignmentIndex', () => { - it( 'should return correct index given an alignment value', () => { - ALIGNMENT_VALUES.forEach( ( alignment, index ) => { - expect( getAlignmentIndex( alignment ) ).toBe( index ); - } ); - } ); - } ); -} ); diff --git a/packages/components/src/alignment-matrix-control/utils.js b/packages/components/src/alignment-matrix-control/utils.js index dbb97e06966fb9..b21eeac4f2e868 100644 --- a/packages/components/src/alignment-matrix-control/utils.js +++ b/packages/components/src/alignment-matrix-control/utils.js @@ -3,6 +3,11 @@ */ import { isEqual } from 'lodash'; +/** + * Internal dependencies + */ +import { getRTL } from '../utils/rtl'; + export const DIRECTION = { UP: 'up', DOWN: 'down', @@ -51,7 +56,9 @@ export function transformAlignment( alignments = [] ) { value = value .map( ( v ) => v.toLowerCase() ) // Supports remapping of 'middle' to 'center' - .map( ( v ) => v.replace( 'middle', 'center' ) ); + .map( ( v ) => v.replace( 'middle', 'center' ) ) + // Flips for RTL + .map( transformAlignmentRTL ); // Handles cases were only 'center' or ['center'] is provided if ( value.length === 1 && value[ 0 ] === 'center' ) { @@ -60,6 +67,28 @@ export function transformAlignment( alignments = [] ) { return value.sort(); } +/** + * Transforms alignment values to respect RTL. + * + * @param {string} value The alignment value to transform. + * + * @return {string} The flipped alignment value (if RTL). + */ +function transformAlignmentRTL( value ) { + const isRTL = getRTL(); + const token = '$TMP'; + + if ( ! isRTL ) return value; + + return ( + value + // Temporarily swap left for token + .replace( 'left', token ) + .replace( 'right', 'left' ) + // Swap tokenized left for actual value + .replace( token, 'right' ) + ); +} /** * Checks if a value is a valid alignment. diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index c09a86b3cf6781..7622ba461913e0 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -37,7 +37,7 @@ import { Wrapper, } from './styles/range-control-styles'; import InputField from './input-field'; -import { useRtl } from '../utils/rtl'; +import { useRTL } from '../utils/rtl'; const BaseRangeControl = forwardRef( ( @@ -71,7 +71,7 @@ const BaseRangeControl = forwardRef( }, ref ) => { - const isRTL = useRtl(); + const isRTL = useRTL(); const sliderValue = valueProp !== undefined ? valueProp : initialPosition; diff --git a/packages/components/src/utils/rtl.js b/packages/components/src/utils/rtl.js index bc312455baa3a6..88a3f651af96c1 100644 --- a/packages/components/src/utils/rtl.js +++ b/packages/components/src/utils/rtl.js @@ -14,7 +14,7 @@ const UPPER_RIGHT_REGEXP = new RegExp( /Right/g ); * * @return {boolean} Whether document is RTL. */ -function getRtl() { +export function getRTL() { return !! ( document && document.documentElement.dir === 'rtl' ); } @@ -23,8 +23,8 @@ function getRtl() { * * @return {boolean} Whether document is RTL. */ -export function useRtl() { - return getRtl(); +export function useRTL() { + return getRTL(); } /** @@ -83,12 +83,12 @@ export const convertLTRToRTL = ( ltrStyles = {} ) => { */ export function rtl( ltrStyles = {}, rtlStyles ) { return () => { - const isRtl = getRtl(); + const isRTL = getRTL(); if ( rtlStyles ) { - return isRtl ? css( rtlStyles ) : css( ltrStyles ); + return isRTL ? css( rtlStyles ) : css( ltrStyles ); } - return isRtl ? css( convertLTRToRTL( ltrStyles ) ) : css( ltrStyles ); + return isRTL ? css( convertLTRToRTL( ltrStyles ) ) : css( ltrStyles ); }; } From 74e0f950be2e6fc6228d3da836e7cc1f477aa7a6 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 27 Mar 2020 12:37:14 -0400 Subject: [PATCH 16/35] Add basic tests for keyboard movements --- .../alignment-matrix-control/test/index.js | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/packages/components/src/alignment-matrix-control/test/index.js b/packages/components/src/alignment-matrix-control/test/index.js index 05be599d4897e2..a42a3a7e1053d3 100644 --- a/packages/components/src/alignment-matrix-control/test/index.js +++ b/packages/components/src/alignment-matrix-control/test/index.js @@ -4,6 +4,11 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; +/** + * WordPress dependencies + */ +import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; + /** * Internal dependencies */ @@ -11,6 +16,14 @@ import AlignmentMatrixControl from '../'; let container = null; +function triggerKeyDown( el, { keyCode } ) { + // modern browsers, IE9+ + const e = document.createEvent( 'HTMLEvents' ); + e.keyCode = keyCode; + e.initEvent( 'keydown', false, true ); + el.dispatchEvent( e ); +} + beforeEach( () => { container = document.createElement( 'div' ); document.body.appendChild( container ); @@ -37,4 +50,94 @@ describe( 'AlignmentMatrixControl', () => { expect( control ).toBeTruthy(); } ); } ); + + describe( 'Keyboard movement', () => { + it( 'should move the next value up on UP press', () => { + const spy = jest.fn(); + + act( () => { + render( + , + container + ); + } ); + + const control = getControl(); + + act( () => { + triggerKeyDown( control, { keyCode: UP } ); + } ); + + expect( spy.mock.calls[ 0 ][ 0 ] ).toBe( 'center top' ); + } ); + + it( 'should move the next value down on DOWN press', () => { + const spy = jest.fn(); + + act( () => { + render( + , + container + ); + } ); + + const control = getControl(); + + act( () => { + triggerKeyDown( control, { keyCode: DOWN } ); + } ); + + expect( spy.mock.calls[ 0 ][ 0 ] ).toBe( 'bottom center' ); + } ); + + it( 'should move the next value left on LEFT press', () => { + const spy = jest.fn(); + + act( () => { + render( + , + container + ); + } ); + + const control = getControl(); + + act( () => { + triggerKeyDown( control, { keyCode: LEFT } ); + } ); + + expect( spy.mock.calls[ 0 ][ 0 ] ).toBe( 'center left' ); + } ); + + it( 'should move the next value right on RIGHT press', () => { + const spy = jest.fn(); + + act( () => { + render( + , + container + ); + } ); + + const control = getControl(); + + act( () => { + triggerKeyDown( control, { keyCode: RIGHT } ); + } ); + + expect( spy.mock.calls[ 0 ][ 0 ] ).toBe( 'center right' ); + } ); + } ); } ); From ccabb0a9c73521b8d70c254ef3ab609e87efc7f0 Mon Sep 17 00:00:00 2001 From: Jon Quach Date: Fri, 3 Apr 2020 10:08:38 -0400 Subject: [PATCH 17/35] Cover: Customizing Alignment with Reakit Composite (#21333) * WIP: Attempt to use Composite from Reakit * Fix issues and clean up composite code * Refactor Composite integration and update tests * Update snapshots * Fix keyboard navigation for AlignmentMatrixControl in Toolbar * Fix keyboard interaction to open Alignment position on keyboard down Co-authored-by: Haz --- .../block-alignment-matrix-toolbar/index.js | 57 +++-- packages/block-library/src/cover/shared.js | 33 +-- .../src/alignment-matrix-control/cell.js | 24 +- .../src/alignment-matrix-control/icon.js | 4 +- .../src/alignment-matrix-control/index.js | 189 ++++++--------- .../alignment-matrix-control/stories/index.js | 6 +- .../alignment-matrix-control-icon-styles.js | 7 + .../styles/alignment-matrix-control-styles.js | 9 +- .../alignment-matrix-control/test/index.js | 96 ++------ .../alignment-matrix-control/test/utils.js | 160 ------------- .../src/alignment-matrix-control/utils.js | 219 ++---------------- 11 files changed, 190 insertions(+), 614 deletions(-) delete mode 100644 packages/components/src/alignment-matrix-control/test/utils.js diff --git a/packages/block-editor/src/components/block-alignment-matrix-toolbar/index.js b/packages/block-editor/src/components/block-alignment-matrix-toolbar/index.js index 2c584ab374a87b..c989333bacbf15 100644 --- a/packages/block-editor/src/components/block-alignment-matrix-toolbar/index.js +++ b/packages/block-editor/src/components/block-alignment-matrix-toolbar/index.js @@ -6,43 +6,60 @@ import { noop } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { DOWN } from '@wordpress/keycodes'; import { - Toolbar, + Button, + Dropdown, + ToolbarGroup, __experimentalAlignmentMatrixControl as AlignmentMatrixControl, } from '@wordpress/components'; -const POPOVER_PROPS = { - className: 'block-editor-block-alignment-matrix-toolbar', - position: 'bottom right', -}; - export function BlockAlignmentMatrixToolbar( props ) { const { - isCollapsed = true, label = __( 'Change matrix alignment' ), onChange = noop, value = 'center', } = props; const icon = ; + const className = 'block-editor-block-alignment-matrix-toolbar'; return ( - - { () => { + { + const openOnArrowDown = ( event ) => { + if ( ! isOpen && event.keyCode === DOWN ) { + event.preventDefault(); + event.stopPropagation(); + onToggle(); + } + }; + return ( - + +