From 66ec200991cdf5e7dd1b9682cf6f61bf13ca99b1 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Thu, 2 Apr 2020 17:17:04 -0400 Subject: [PATCH 001/103] Creating initial BoxControl component --- packages/components/src/box-control/index.js | 297 ++++++++++++++++++ .../src/box-control/stories/index.js | 10 + packages/components/src/unit-control/index.js | 4 +- 3 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 packages/components/src/box-control/index.js create mode 100644 packages/components/src/box-control/stories/index.js diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js new file mode 100644 index 00000000000000..fcf2f5cabd07f6 --- /dev/null +++ b/packages/components/src/box-control/index.js @@ -0,0 +1,297 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; +import { useRadioState, Radio, RadioGroup } from 'reakit/Radio'; +import styled from '@emotion/styled'; +/** + * WordPress dependencies + */ +import { useEffect, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import ButtonGroup from '../button-group'; +import Button from '../button'; +import BaseUnitControl from '../unit-control'; + +const types = [ 'all', 'pairs', 'custom' ]; +const defaultInputProps = { + min: 0, +}; +const typeOptions = { + all: 'All', + pairs: 'Pairs', + custom: 'Custom', +}; + +const defaultValueProps = { + top: [ 0, 'px' ], + right: [ 0, 'px' ], + bottom: [ 0, 'px' ], + left: [ 0, 'px' ], +}; + +const BoxControlComponent = { + all: BoxAllControl, + pairs: BoxPairsControl, + custom: BoxCustomControl, +}; + +const parseType = ( type ) => { + return types.includes( type ) ? type : 'all'; +}; + +export default function BoxControl( { + inputProps = defaultInputProps, + onChange = noop, + label = 'Box Control', + type: typeProp = 'pairs', + values: valuesProp, +} ) { + const [ type, setType ] = useState( parseType( typeProp ) ); + const [ values, setValues ] = useState( parseValues( valuesProp ) ); + const ControlComponent = BoxControlComponent[ type ]; + + const updateValues = ( nextValues ) => { + const mergedValues = { ...values, ...nextValues }; + setValues( mergedValues ); + onChange( mergedValues ); + }; + + const mixedLabel = __( 'Mixed' ); + + return ( +
+
+ { label } +
+ +
+
+ +
+ ); +} + +function BoxAllControl( { placeholder, onChange = noop, values, ...props } ) { + const [ value, unit ] = values.top; + const allValues = getValues( values, 'top', 'right', 'bottom', 'left' ); + const isMixed = ! allValues.every( ( v ) => v === value ); + + return ( + { + onChange( { + top: [ next, unit ], + right: [ next, unit ], + bottom: [ next, unit ], + left: [ next, unit ], + } ); + } } + onUnitChange={ ( next ) => { + onChange( { + top: [ value, next ], + right: [ value, next ], + bottom: [ value, next ], + left: [ value, next ], + } ); + } } + /> + ); +} + +function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { + const [ vertical, verticalUnit ] = values.top; + const [ horizontal, horizontalUnit ] = values.left; + + const isVerticalMixed = ! getValues( values, 'top', 'bottom' ).every( + ( v ) => v === vertical + ); + const isHorizontalMixed = ! getValues( values, 'left', 'right' ).every( + ( v ) => v === horizontal + ); + + return ( + + { + onChange( { + top: [ next, verticalUnit ], + bottom: [ next, verticalUnit ], + } ); + } } + onUnitChange={ ( next ) => { + onChange( { + top: [ vertical, next ], + bottom: [ vertical, next ], + } ); + } } + value={ isVerticalMixed ? '' : vertical } + unit={ verticalUnit } + /> + { + onChange( { + left: [ next, horizontalUnit ], + right: [ next, horizontalUnit ], + } ); + } } + onUnitChange={ ( next ) => { + onChange( { + left: [ horizontal, next ], + right: [ horizontal, next ], + } ); + } } + value={ isHorizontalMixed ? '' : horizontal } + unit={ horizontalUnit } + /> + + ); +} + +function BoxCustomControl( { onChange = noop, values, ...props } ) { + const unitControlProps = useCustomUnitControlProps( { + values, + onChange, + } ); + + return ( + + + + + + + ); +} + +function useCustomUnitControlProps( { values, onChange = noop } ) { + const valueKeys = Object.keys( values ); + const props = {}; + + valueKeys.forEach( ( key ) => { + const [ value, unit ] = values[ key ]; + + const handleOnChange = ( next ) => { + onChange( { [ key ]: [ next, unit ] } ); + }; + + const handleOnUnitChange = ( next ) => { + onChange( { [ key ]: [ value, next ] } ); + }; + + props[ key ] = { + value, + unit, + onChange: handleOnChange, + onUnitChange: handleOnUnitChange, + }; + } ); + + return props; +} + +function BoxTypeControl( { + label = 'Box Control', + onChange = noop, + type = 'all', +} ) { + const radio = useRadioState( { state: type } ); + + useEffect( () => { + onChange( radio.state ); + }, [ radio.state ] ); + + return ( + + { types.map( ( value ) => ( + + { typeOptions[ value ] } + + ) ) } + + ); +} + +function UnitControl( props ) { + return ( + + + + ); +} + +const UnitControlContainer = styled.div` + display: flex; +`; + +const UnitControlWrapper = styled.div` + max-width: 80px; + padding-right: 4px; +`; + +function parseValues( values = {} ) { + const nextValueProps = {}; + + Object.keys( defaultValueProps ).forEach( ( key ) => { + const defaultValue = defaultValueProps[ key ]; + const prop = values[ key ] || []; + + nextValueProps[ key ] = [ + prop?.[ 0 ] || defaultValue[ 0 ], + prop?.[ 1 ] || defaultValue[ 1 ], + ]; + } ); + + return nextValueProps; +} + +function getValues( values, ...args ) { + const nextValues = []; + args.forEach( ( key ) => { + nextValues.push( values[ key ][ 0 ] ); + } ); + + return nextValues; +} diff --git a/packages/components/src/box-control/stories/index.js b/packages/components/src/box-control/stories/index.js new file mode 100644 index 00000000000000..adea88ad550a5e --- /dev/null +++ b/packages/components/src/box-control/stories/index.js @@ -0,0 +1,10 @@ +/** + * Internal dependencies + */ +import BoxControl from '../'; + +export default { title: 'Components/BoxControl', component: BoxControl }; + +export const _default = () => { + return ; +}; diff --git a/packages/components/src/unit-control/index.js b/packages/components/src/unit-control/index.js index 7276b10141a55f..c88ea378c28c3c 100644 --- a/packages/components/src/unit-control/index.js +++ b/packages/components/src/unit-control/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { isUndefined, noop } from 'lodash'; +import { noop } from 'lodash'; import classnames from 'classnames'; /** * Internal dependencies @@ -28,7 +28,7 @@ export default function UnitControl( { const { data } = changeProps; onUnitChange( unitValue, changeProps ); - if ( isResetValueOnUnitChange && ! isUndefined( data.default ) ) { + if ( isResetValueOnUnitChange && ! data.default === undefined ) { onChange( data.default, changeProps ); } }; From dff4163ad84f3307f07a67d6a67f51965db4dd02 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 3 Apr 2020 12:08:15 -0400 Subject: [PATCH 002/103] Add BoxControlIcon --- packages/components/src/box-control/icon.js | 96 +++++++++++++++++++ packages/components/src/box-control/index.js | 80 ++++++++++------ .../src/box-control/stories/index.js | 19 +++- .../components/src/utils/colors-values.js | 1 + 4 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 packages/components/src/box-control/icon.js diff --git a/packages/components/src/box-control/icon.js b/packages/components/src/box-control/icon.js new file mode 100644 index 00000000000000..78aa39182242c1 --- /dev/null +++ b/packages/components/src/box-control/icon.js @@ -0,0 +1,96 @@ +/** + * External dependencies + */ +import { css } from '@emotion/core'; +import styled from '@emotion/styled'; +/** + * Internal dependencies + */ +import { color } from '../utils/style-mixins'; + +export default function BoxControlIcon( { sides = [ 'all' ] } ) { + const top = getSide( sides, 'top' ); + const right = getSide( sides, 'right' ); + const bottom = getSide( sides, 'bottom' ); + const left = getSide( sides, 'left' ); + + return ( + + + + + + + + + ); +} + +function getSide( sides, value ) { + const match = value.toLowerCase(); + + return sides.find( ( side ) => { + const sideValue = side.toLowerCase(); + return [ 'all', match ].includes( sideValue ); + } ); +} + +const Root = styled.span` + box-sizing: border-box; + display: block; + width: 24px; + height: 24px; + position: relative; + padding: 4px; +`; + +const Viewbox = styled.span` + box-sizing: border-box; + display: block; + position: relative; + width: 100%; + height: 100%; +`; + +const strokeFocus = ( { isFocused } ) => { + return css( { + backgroundColor: color( 'ui.border' ), + opacity: isFocused ? 1 : 0.3, + } ); +}; + +const Stroke = styled.span` + box-sizing: border-box; + display: block; + pointer-events: none; + position: absolute; + ${strokeFocus}; +`; + +const VerticalStroke = styled( Stroke )` + bottom: 3px; + top: 3px; + width: 2px; +`; + +const HorizontalStroke = styled( Stroke )` + height: 2px; + left: 3px; + right: 3px; +`; + +const TopStroke = styled( HorizontalStroke )` + top: 0; +`; + +const RightStroke = styled( VerticalStroke )` + right: 0; +`; + +const BottomStroke = styled( HorizontalStroke )` + bottom: 0; +`; + +const LeftStroke = styled( VerticalStroke )` + left: 0; +`; diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index fcf2f5cabd07f6..0059cfe881edf8 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -14,16 +14,17 @@ import { __ } from '@wordpress/i18n'; */ import ButtonGroup from '../button-group'; import Button from '../button'; +import BoxControlIcon from './icon'; import BaseUnitControl from '../unit-control'; const types = [ 'all', 'pairs', 'custom' ]; const defaultInputProps = { min: 0, }; -const typeOptions = { - all: 'All', - pairs: 'Pairs', - custom: 'Custom', +const typeIconSides = { + all: [ 'all' ], + pairs: [ 'top', 'bottom' ], + custom: [ 'top' ], }; const defaultValueProps = { @@ -49,6 +50,8 @@ export default function BoxControl( { label = 'Box Control', type: typeProp = 'pairs', values: valuesProp, + // Disable units for now + units = false, } ) { const [ type, setType ] = useState( parseType( typeProp ) ); const [ values, setValues ] = useState( parseValues( valuesProp ) ); @@ -79,6 +82,7 @@ export default function BoxControl( { placeholder={ mixedLabel } values={ values } onChange={ updateValues } + units={ units } /> ); @@ -90,32 +94,36 @@ function BoxAllControl( { placeholder, onChange = noop, values, ...props } ) { const isMixed = ! allValues.every( ( v ) => v === value ); return ( - { - onChange( { - top: [ next, unit ], - right: [ next, unit ], - bottom: [ next, unit ], - left: [ next, unit ], - } ); - } } - onUnitChange={ ( next ) => { - onChange( { - top: [ value, next ], - right: [ value, next ], - bottom: [ value, next ], - left: [ value, next ], - } ); - } } - /> + + + { + onChange( { + top: [ next, unit ], + right: [ next, unit ], + bottom: [ next, unit ], + left: [ next, unit ], + } ); + } } + onUnitChange={ ( next ) => { + onChange( { + top: [ value, next ], + right: [ value, next ], + bottom: [ value, next ], + left: [ value, next ], + } ); + } } + /> + ); } function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { + const [ selected, setSelected ] = useState( 'vertical' ); const [ vertical, verticalUnit ] = values.top; const [ horizontal, horizontalUnit ] = values.left; @@ -126,11 +134,20 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { ( v ) => v === horizontal ); + const iconSides = { + vertical: [ 'top', 'bottom' ], + horizontal: [ 'left', 'right' ], + }; + + const iconSide = iconSides[ selected ]; + return ( + setSelected( 'vertical' ) } onChange={ ( next ) => { onChange( { top: [ next, verticalUnit ], @@ -149,6 +166,7 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { setSelected( 'horizontal' ) } onChange={ ( next ) => { onChange( { left: [ next, horizontalUnit ], @@ -169,6 +187,7 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { } function BoxCustomControl( { onChange = noop, values, ...props } ) { + const [ selected, setSelected ] = useState( 'top' ); const unitControlProps = useCustomUnitControlProps( { values, onChange, @@ -176,24 +195,29 @@ function BoxCustomControl( { onChange = noop, values, ...props } ) { return ( + setSelected( 'top' ) } placeholder="" /> setSelected( 'right' ) } placeholder="" /> setSelected( 'bottom' ) } placeholder="" /> setSelected( 'left' ) } placeholder="" /> @@ -247,7 +271,7 @@ function BoxTypeControl( { key={ value } value={ value } > - { typeOptions[ value ] } + ) ) } @@ -263,10 +287,12 @@ function UnitControl( props ) { } const UnitControlContainer = styled.div` + box-sizing: border-box; display: flex; `; const UnitControlWrapper = styled.div` + box-sizing: border-box; max-width: 80px; padding-right: 4px; `; diff --git a/packages/components/src/box-control/stories/index.js b/packages/components/src/box-control/stories/index.js index adea88ad550a5e..ef9a00021aeb75 100644 --- a/packages/components/src/box-control/stories/index.js +++ b/packages/components/src/box-control/stories/index.js @@ -1,10 +1,27 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; /** * Internal dependencies */ import BoxControl from '../'; +import BoxControlIcon from '../icon'; export default { title: 'Components/BoxControl', component: BoxControl }; export const _default = () => { - return ; + return ( + + + + ); }; + +export const icon = () => { + return ; +}; + +const Wrapper = styled.div` + padding: 40px; +`; diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js index 7e152a4263cc8f..974acb607d1e16 100644 --- a/packages/components/src/utils/colors-values.js +++ b/packages/components/src/utils/colors-values.js @@ -138,6 +138,7 @@ export const UI = { background: BASE.white, border: BASE.black, borderFocus: BLUE.medium.focus, + borderLight: LIGHT_GRAY[ 600 ], }; export const COLORS = { From 7904c7594d4883acb31502883ef230f3d93c41de Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 3 Apr 2020 12:55:45 -0400 Subject: [PATCH 003/103] Add Flex component and integrate into BoxControl component --- packages/components/src/box-control/index.js | 149 ++++++----- packages/components/src/flex/block.js | 21 ++ packages/components/src/flex/flex.js | 40 +++ packages/components/src/flex/index.js | 10 + packages/components/src/flex/item.js | 22 ++ packages/components/src/flex/stories/index.js | 241 ++++++++++++++++++ .../components/src/flex/stories/style.css | 7 + .../components/src/flex/styles/flex-styles.js | 62 +++++ packages/components/src/index.js | 3 + packages/components/src/unit-control/index.js | 44 ++-- 10 files changed, 518 insertions(+), 81 deletions(-) create mode 100644 packages/components/src/flex/block.js create mode 100644 packages/components/src/flex/flex.js create mode 100644 packages/components/src/flex/index.js create mode 100644 packages/components/src/flex/item.js create mode 100644 packages/components/src/flex/stories/index.js create mode 100644 packages/components/src/flex/stories/style.css create mode 100644 packages/components/src/flex/styles/flex-styles.js diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index 0059cfe881edf8..769ab659975cff 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -16,6 +16,7 @@ import ButtonGroup from '../button-group'; import Button from '../button'; import BoxControlIcon from './icon'; import BaseUnitControl from '../unit-control'; +import { Flex, FlexBlock, FlexItem } from '../flex'; const types = [ 'all', 'pairs', 'custom' ]; const defaultInputProps = { @@ -66,17 +67,17 @@ export default function BoxControl( { const mixedLabel = __( 'Mixed' ); return ( -
-
- { label } -
+ +
+ { label } + -
-
+ + -
+ ); } @@ -94,31 +95,35 @@ function BoxAllControl( { placeholder, onChange = noop, values, ...props } ) { const isMixed = ! allValues.every( ( v ) => v === value ); return ( - - - { - onChange( { - top: [ next, unit ], - right: [ next, unit ], - bottom: [ next, unit ], - left: [ next, unit ], - } ); - } } - onUnitChange={ ( next ) => { - onChange( { - top: [ value, next ], - right: [ value, next ], - bottom: [ value, next ], - left: [ value, next ], - } ); - } } - /> - + + + + + + { + onChange( { + top: [ next, unit ], + right: [ next, unit ], + bottom: [ next, unit ], + left: [ next, unit ], + } ); + } } + onUnitChange={ ( next ) => { + onChange( { + top: [ value, next ], + right: [ value, next ], + bottom: [ value, next ], + left: [ value, next ], + } ); + } } + /> + + ); } @@ -142,7 +147,7 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { const iconSide = iconSides[ selected ]; return ( - + - + ); } @@ -194,33 +199,45 @@ function BoxCustomControl( { onChange = noop, values, ...props } ) { } ); return ( - - - setSelected( 'top' ) } - placeholder="" - /> - setSelected( 'right' ) } - placeholder="" - /> - setSelected( 'bottom' ) } - placeholder="" - /> - setSelected( 'left' ) } - placeholder="" - /> - + + + + + + setSelected( 'top' ) } + placeholder="" + /> + setSelected( 'right' ) } + placeholder="" + /> + setSelected( 'bottom' ) } + placeholder="" + /> + setSelected( 'left' ) } + placeholder="" + /> + + + ); +} + +function ControlContainer( { children } ) { + return ( + + { children } + ); } @@ -286,15 +303,17 @@ function UnitControl( props ) { ); } -const UnitControlContainer = styled.div` - box-sizing: border-box; - display: flex; +const Root = styled.div` + max-width: 400px; +`; + +const Header = styled( Flex )` + margin-bottom: 8px; `; const UnitControlWrapper = styled.div` box-sizing: border-box; max-width: 80px; - padding-right: 4px; `; function parseValues( values = {} ) { diff --git a/packages/components/src/flex/block.js b/packages/components/src/flex/block.js new file mode 100644 index 00000000000000..adcded360dbb30 --- /dev/null +++ b/packages/components/src/flex/block.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +/** + * Internal dependencies + */ +import { Block } from './styles/flex-styles'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +function FlexBlock( { className, ...props }, ref ) { + const classes = classnames( 'components-flex__block', className ); + + return ; +} + +export default forwardRef( FlexBlock ); diff --git a/packages/components/src/flex/flex.js b/packages/components/src/flex/flex.js new file mode 100644 index 00000000000000..3b230d29cd5058 --- /dev/null +++ b/packages/components/src/flex/flex.js @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { Flex as BaseFlex } from './styles/flex-styles'; + +function Flex( + { + align = 'center', + className, + gap = 2, + justify = 'space-between', + ...props + }, + ref +) { + const classes = classnames( 'components-flex', className ); + + return ( + + ); +} + +export default forwardRef( Flex ); diff --git a/packages/components/src/flex/index.js b/packages/components/src/flex/index.js new file mode 100644 index 00000000000000..174da20e5be467 --- /dev/null +++ b/packages/components/src/flex/index.js @@ -0,0 +1,10 @@ +export { default as Flex } from './flex'; +export { default as FlexBlock } from './block'; +export { default as FlexItem } from './item'; + +/** + * Internal dependencies + */ +import Flex from './flex'; + +export default Flex; diff --git a/packages/components/src/flex/item.js b/packages/components/src/flex/item.js new file mode 100644 index 00000000000000..9112d010c1f0ca --- /dev/null +++ b/packages/components/src/flex/item.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { Item } from './styles/flex-styles'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +function FlexItem( { className, ...props }, ref ) { + const classes = classnames( 'components-flex__item', className ); + + return ; +} + +export default forwardRef( FlexItem ); diff --git a/packages/components/src/flex/stories/index.js b/packages/components/src/flex/stories/index.js new file mode 100644 index 00000000000000..a67883dd49482b --- /dev/null +++ b/packages/components/src/flex/stories/index.js @@ -0,0 +1,241 @@ +/** + * External dependencies + */ +import { boolean, number, select } from '@storybook/addon-knobs'; +import { random } from 'lodash'; +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; +import { arrowLeft } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import Button from '../../button'; +import Flex from '../flex'; +import FlexBlock from '../block'; +import FlexItem from '../item'; + +import './style.css'; + +export default { title: 'Components/Flex', component: Flex }; + +const getStoryProps = () => { + const showOutline = boolean( 'Example: Show Outline', true ); + + const align = select( + 'align', + { + top: 'top', + center: 'center', + bottom: 'bottom', + }, + 'center' + ); + + const gap = number( 'gap', 1 ); + + const justify = select( + 'justify', + { + 'space-between': 'space-between', + left: 'left', + center: 'center', + right: 'right', + }, + 'space-between' + ); + + return { + showOutline, + align, + gap, + justify, + }; +}; + +const Box = ( props ) => { + const { height, width, style: styleProps = {} } = props; + + const style = { + background: '#ddd', + fontSize: 12, + ...styleProps, + height: height || 40, + width: width || '100%', + }; + + return
; +}; + +const EnhancedFlex = ( props ) => { + const { showOutline, ...restProps } = props; + const exampleClassName = showOutline ? 'example-only-show-outline' : ''; + + return ; +}; + +export const _default = () => { + const showBlock = boolean( 'Example: Show Block', true ); + const differSize = boolean( 'Example: Differ Sizes', true ); + const props = getStoryProps(); + + const baseSize = 40; + + return ( + + + + Item + + + { showBlock && ( + + Block + + ) } + + + Item + + + ); +}; + +const ItemExample = () => { + const props = getStoryProps(); + + const [ items, setItems ] = useState( [ + { + width: 40, + height: 40, + }, + ] ); + + const addItem = () => { + setItems( [ + ...items, + { width: random( 12, 150 ), height: random( 12, 150 ) }, + ] ); + }; + + const removeItem = () => { + const nextItems = items.filter( ( item, index ) => { + return index !== items.length - 1; + } ); + setItems( nextItems ); + }; + + return ( +
+ + + + + + + + +
+ + { items.map( ( item, index ) => ( + + + + ) ) } + +
+ ); +}; + +export const flexItem = () => { + return ; +}; + +const BlockExample = () => { + const props = getStoryProps(); + + const [ items, setItems ] = useState( [ + { + height: 40, + }, + ] ); + + const addItem = () => { + setItems( [ ...items, { height: random( 20, 150 ) } ] ); + }; + + const removeItem = () => { + const nextItems = items.filter( ( item, index ) => { + return index !== items.length - 1; + } ); + setItems( nextItems ); + }; + + return ( +
+ + + + + + + + +
+ + + + + { items.map( ( item, index ) => ( + + + + ) ) } + + + + +
+ ); +}; + +export const flexBlock = () => { + return ; +}; + +export const exampleActions = () => { + const props = getStoryProps(); + + return ( + + + + + + + +
+ + + ); +}; diff --git a/packages/components/src/flex/stories/style.css b/packages/components/src/flex/stories/style.css new file mode 100644 index 00000000000000..06a05379c37354 --- /dev/null +++ b/packages/components/src/flex/stories/style.css @@ -0,0 +1,7 @@ +.components-flex.example-only-show-outline { + box-shadow: 0 0 0 1px pink; +} + +.components-flex.example-only-show-outline > * { + box-shadow: 0 0 0 1px pink; +} diff --git a/packages/components/src/flex/styles/flex-styles.js b/packages/components/src/flex/styles/flex-styles.js new file mode 100644 index 00000000000000..3c192d8b94d796 --- /dev/null +++ b/packages/components/src/flex/styles/flex-styles.js @@ -0,0 +1,62 @@ +/** + * External dependencies + */ +import { css } from '@emotion/core'; +import styled from '@emotion/styled'; + +const alignStyle = ( { align } ) => { + const aligns = { + top: 'flex-start', + bottom: 'flex-end', + }; + const value = aligns[ align ] || align; + + return css( { + alignItems: value, + } ); +}; + +const justifyStyle = ( { justify } ) => { + const justifies = { + left: 'flex-start', + right: 'flex-end', + }; + const value = justifies[ justify ] || justify; + + return css( { + justifyContent: value, + } ); +}; + +const gapStyle = ( { gap } ) => { + const base = 4; + const value = typeof gap === 'number' ? base * gap : base; + + return css` + > * { + margin-right: ${value}px; + + &:last-child { + margin-right: 0; + } + } + `; +}; + +export const Flex = styled.div` + box-sizing: border-box; + display: flex; + + ${alignStyle}; + ${justifyStyle}; + ${gapStyle}; +`; + +export const Item = styled.div` + min-width: 0; + max-width: 100%; +`; + +export const Block = styled( Item )` + flex: 1; +`; diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 293eff0c10bf11..b6188e94b0113a 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -43,6 +43,9 @@ export { default as DropZoneProvider } from './drop-zone/provider'; export { default as Dropdown } from './dropdown'; export { default as DropdownMenu } from './dropdown-menu'; export { default as ExternalLink } from './external-link'; +export { default as __experimentalFlex } from './flex'; +export { default as __experimentalFlexBlock } from './flex/block'; +export { default as __experimentalFlexItem } from './flex/item'; export { default as FocalPointPicker } from './focal-point-picker'; export { default as FocusableIframe } from './focusable-iframe'; export { default as FontSizePicker } from './font-size-picker'; diff --git a/packages/components/src/unit-control/index.js b/packages/components/src/unit-control/index.js index c88ea378c28c3c..05a9f176e6f473 100644 --- a/packages/components/src/unit-control/index.js +++ b/packages/components/src/unit-control/index.js @@ -3,6 +3,12 @@ */ import { noop } from 'lodash'; import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + /** * Internal dependencies */ @@ -10,20 +16,23 @@ import { Root, ValueInput } from './styles/unit-control-styles'; import UnitSelectControl from './unit-select-control'; import { CSS_UNITS } from './utils'; -export default function UnitControl( { - className, - isUnitSelectTabbable = true, - isResetValueOnUnitChange = true, - label, - onChange = noop, - onUnitChange = noop, - size = 'default', - style, - unit = 'px', - units = CSS_UNITS, - value, - ...props -} ) { +function UnitControl( + { + className, + isUnitSelectTabbable = true, + isResetValueOnUnitChange = true, + label, + onChange = noop, + onUnitChange = noop, + size = 'default', + style, + unit = 'px', + units = CSS_UNITS, + value, + ...props + }, + ref +) { const handleOnUnitChange = ( unitValue, changeProps ) => { const { data } = changeProps; onUnitChange( unitValue, changeProps ); @@ -36,7 +45,7 @@ export default function UnitControl( { const classes = classnames( 'component-unit-control', className ); return ( - + Date: Fri, 3 Apr 2020 13:53:15 -0400 Subject: [PATCH 004/103] Add labels for UnitControls --- packages/components/src/box-control/index.js | 87 +++++++++++++------ .../components/src/flex/styles/flex-styles.js | 4 +- 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index 769ab659975cff..9dc614b8daf559 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -15,6 +15,7 @@ import { __ } from '@wordpress/i18n'; import ButtonGroup from '../button-group'; import Button from '../button'; import BoxControlIcon from './icon'; +import Tooltip from '../tooltip'; import BaseUnitControl from '../unit-control'; import { Flex, FlexBlock, FlexItem } from '../flex'; @@ -22,10 +23,20 @@ const types = [ 'all', 'pairs', 'custom' ]; const defaultInputProps = { min: 0, }; -const typeIconSides = { - all: [ 'all' ], - pairs: [ 'top', 'bottom' ], - custom: [ 'top' ], + +const typeProps = { + all: { + sides: [ 'all' ], + label: __( 'All sides' ), + }, + pairs: { + sides: [ 'top', 'bottom' ], + label: __( 'Pair of sides' ), + }, + custom: { + sides: [ 'top' ], + label: __( 'Individual sides' ), + }, }; const defaultValueProps = { @@ -48,7 +59,7 @@ const parseType = ( type ) => { export default function BoxControl( { inputProps = defaultInputProps, onChange = noop, - label = 'Box Control', + label = __( 'Box Control' ), type: typeProp = 'pairs', values: valuesProp, // Disable units for now @@ -96,12 +107,13 @@ function BoxAllControl( { placeholder, onChange = noop, values, ...props } ) { return ( - + - + - + + + setSelected( 'vertical' ) } onChange={ ( next ) => { @@ -170,6 +185,7 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { /> setSelected( 'horizontal' ) } onChange={ ( next ) => { @@ -200,31 +216,35 @@ function BoxCustomControl( { onChange = noop, values, ...props } ) { return ( - + - + setSelected( 'top' ) } placeholder="" /> setSelected( 'right' ) } placeholder="" /> setSelected( 'bottom' ) } placeholder="" /> setSelected( 'left' ) } placeholder="" /> @@ -280,26 +300,37 @@ function BoxTypeControl( { return ( - { types.map( ( value ) => ( - - - - ) ) } + { types.map( ( value ) => { + const valueProps = typeProps[ value ]; + const isSelected = radio.state === value; + + return ( + + + + + + ); + } ) } ); } -function UnitControl( props ) { +function UnitControl( { label, ...props } ) { return ( - - - + + + + + ); } @@ -311,9 +342,13 @@ const Header = styled( Flex )` margin-bottom: 8px; `; +const IconWrapper = styled( FlexItem )` + padding-right: 8px; +`; + const UnitControlWrapper = styled.div` box-sizing: border-box; - max-width: 80px; + max-width: 70px; `; function parseValues( values = {} ) { diff --git a/packages/components/src/flex/styles/flex-styles.js b/packages/components/src/flex/styles/flex-styles.js index 3c192d8b94d796..f923cb505bf69e 100644 --- a/packages/components/src/flex/styles/flex-styles.js +++ b/packages/components/src/flex/styles/flex-styles.js @@ -34,10 +34,10 @@ const gapStyle = ( { gap } ) => { return css` > * { - margin-right: ${value}px; + padding-right: ${value}px; &:last-child { - margin-right: 0; + padding-right: 0; } } `; From fb77888b77380984be56b69e7986ebdb5e6b7a23 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 3 Apr 2020 17:11:40 -0400 Subject: [PATCH 005/103] Add react-use-gesture --- package-lock.json | 6 ++++++ packages/components/package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index 73ce417bfa67fb..a07351cd266899 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9954,6 +9954,7 @@ "re-resizable": "^6.0.0", "react-dates": "^17.1.1", "react-spring": "^8.0.20", + "react-use-gesture": "^7.0.9", "reakit": "^1.0.0-rc.2", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", @@ -36857,6 +36858,11 @@ "react-lifecycles-compat": "^3.0.4" } }, + "react-use-gesture": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-7.0.9.tgz", + "integrity": "sha512-z2gNEZhMBiyVRF7g+n54oA/1abEd+v6k99KqlzR+emG+I90rFXBG8U0ftk0h0B3MUxXcBWHZJijlrNQhS6iLAw==" + }, "react-with-direction": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-with-direction/-/react-with-direction-1.3.0.tgz", diff --git a/packages/components/package.json b/packages/components/package.json index 641933d747b6c5..e0f5ce12190718 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -53,6 +53,7 @@ "re-resizable": "^6.0.0", "react-dates": "^17.1.1", "react-spring": "^8.0.20", + "react-use-gesture": "^7.0.9", "reakit": "^1.0.0-rc.2", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", From 69e4a1ceb024586a695465ad88c4bbd2ffc57a71 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 3 Apr 2020 17:12:56 -0400 Subject: [PATCH 006/103] Add drag support to NumberControl --- .../components/src/number-control/index.js | 80 ++++++++++++++++--- .../components/src/number-control/utils.js | 25 ++++++ .../src/unit-control/stories/index.js | 8 +- 3 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 packages/components/src/number-control/utils.js diff --git a/packages/components/src/number-control/index.js b/packages/components/src/number-control/index.js index 33914140975b06..257169b923f43b 100644 --- a/packages/components/src/number-control/index.js +++ b/packages/components/src/number-control/index.js @@ -3,25 +3,70 @@ */ import { clamp, noop } from 'lodash'; import classNames from 'classnames'; +import { useDrag } from 'react-use-gesture'; /** * WordPress dependencies */ +import { forwardRef, useState } from '@wordpress/element'; import { UP, DOWN } from '@wordpress/keycodes'; -export default function NumberControl( { - className, - isShiftStepEnabled = true, - max = Infinity, - min = -Infinity, - onChange = noop, - onKeyDown = noop, - shiftStep = 10, - step = 1, - ...props -} ) { +/** + * Internal dependencies + */ +import { DRAG_CURSOR, useDragCursor, add } from './utils'; + +export function NumberControl( + { + className, + dragAxis = 'y', + dragThreshold = 10, + isDragEnabled = true, + isShiftStepEnabled = true, + max = Infinity, + min = -Infinity, + onChange = noop, + onKeyDown = noop, + shiftStep = 10, + step = 1, + style: styleProp, + value: valueProp, + ...props + }, + ref +) { + const [ isDragging, setIsDragging ] = useState( false ); const baseValue = clamp( 0, min, max ); + useDragCursor( isDragging ); + + const dragGestureProps = useDrag( + ( { dragging, delta, event } ) => { + if ( ! isDragEnabled ) return; + if ( ! dragging ) { + setIsDragging( false ); + return; + } + + event.stopPropagation(); + + const [ , y ] = delta; + const distance = y * -1; + const nextValue = clamp( add( valueProp, distance ), min, max ); + + onChange( nextValue.toString(), { event } ); + + if ( ! isDragging ) { + setIsDragging( true ); + } + }, + { + axis: dragAxis, + threshold: dragThreshold, + enabled: isDragEnabled, + } + ); + const handleOnKeyDown = ( event ) => { onKeyDown( event ); const { value } = event.target; @@ -65,15 +110,26 @@ export default function NumberControl( { }; const classes = classNames( 'component-number-control', className ); + const style = { + ...styleProp, + cursor: isDragging ? DRAG_CURSOR : styleProp?.cursor, + userSelect: isDragging ? 'none' : styleProp?.userSelect, + }; return ( ); } + +export default forwardRef( NumberControl ); diff --git a/packages/components/src/number-control/utils.js b/packages/components/src/number-control/utils.js new file mode 100644 index 00000000000000..e8b30b2a0f235c --- /dev/null +++ b/packages/components/src/number-control/utils.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; + +export const DRAG_CURSOR = 'ns-resize'; + +export function useDragCursor( isDragging ) { + useEffect( () => { + if ( isDragging ) { + document.documentElement.style.cursor = DRAG_CURSOR; + } else { + document.documentElement.style.cursor = null; + } + }, [ isDragging ] ); +} + +function getValue( value ) { + const number = Number( value ); + return isNaN( number ) ? 0 : number; +} + +export function add( a, b ) { + return getValue( a ) + getValue( b ); +} diff --git a/packages/components/src/unit-control/stories/index.js b/packages/components/src/unit-control/stories/index.js index bf5680c6ae630f..2aab99180fd39c 100644 --- a/packages/components/src/unit-control/stories/index.js +++ b/packages/components/src/unit-control/stories/index.js @@ -20,13 +20,15 @@ export default { }; function Example() { - const [ value, setValue ] = useState( '' ); + const [ value, setValue ] = useState( '10' ); const [ unit, setUnit ] = useState( 'px' ); const props = { isShiftStepEnabled: boolean( 'isShiftStepEnabled', true ), isUnitSelectTabbable: boolean( 'isUnitSelectTabbable', true ), shiftStep: number( 'shiftStep', 10 ), + max: number( 'max', 100 ), + min: number( 'min', 0 ), size: select( 'size', { @@ -43,9 +45,9 @@ function Example() { setValue( v ) } unit={ unit } - onUnitChange={ setUnit } + onUnitChange={ ( v ) => setUnit( v ) } /> ); From b0877d1b4f85b9d2bc94ec205ec0f7d130df0034 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 3 Apr 2020 17:13:07 -0400 Subject: [PATCH 007/103] Improve layout for BoxControl --- packages/components/src/box-control/icon.js | 9 +- packages/components/src/box-control/index.js | 28 +++-- .../src/box-control/stories/index.js | 101 ++++++++++++++++-- 3 files changed, 121 insertions(+), 17 deletions(-) diff --git a/packages/components/src/box-control/icon.js b/packages/components/src/box-control/icon.js index 78aa39182242c1..3bfac3ead3d178 100644 --- a/packages/components/src/box-control/icon.js +++ b/packages/components/src/box-control/icon.js @@ -8,14 +8,19 @@ import styled from '@emotion/styled'; */ import { color } from '../utils/style-mixins'; -export default function BoxControlIcon( { sides = [ 'all' ] } ) { +const BASE_ICON_SIZE = 24; + +export default function BoxControlIcon( { size = 24, sides = [ 'all' ] } ) { const top = getSide( sides, 'top' ); const right = getSide( sides, 'right' ); const bottom = getSide( sides, 'bottom' ); const left = getSide( sides, 'left' ); + // Simulates SVG Icon scaling + const scale = size / BASE_ICON_SIZE; + return ( - + diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index 9dc614b8daf559..ca4acd5a3381ee 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -60,7 +60,7 @@ export default function BoxControl( { inputProps = defaultInputProps, onChange = noop, label = __( 'Box Control' ), - type: typeProp = 'pairs', + type: typeProp = 'all', values: valuesProp, // Disable units for now units = false, @@ -105,6 +105,8 @@ function BoxAllControl( { placeholder, onChange = noop, values, ...props } ) { const allValues = getValues( values, 'top', 'right', 'bottom', 'left' ); const isMixed = ! allValues.every( ( v ) => v === value ); + const placeholderLabel = typeof value === 'number' ? placeholder : null; + return ( @@ -115,7 +117,7 @@ function BoxAllControl( { placeholder, onChange = noop, values, ...props } ) { { ...props } label={ __( 'All Sides' ) } value={ isMixed ? '' : value } - placeholder={ placeholder } + placeholder={ placeholderLabel } unit={ unit } onChange={ ( next ) => { onChange( { @@ -151,6 +153,11 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { ( v ) => v === horizontal ); + const verticalPlaceholder = + typeof vertical === 'number' ? placeholder : null; + const horizontalPlaceholder = + typeof horizontal === 'number' ? placeholder : null; + const iconSides = { vertical: [ 'top', 'bottom' ], horizontal: [ 'left', 'right' ], @@ -165,8 +172,8 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { setSelected( 'vertical' ) } onChange={ ( next ) => { onChange( { @@ -185,8 +192,8 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { /> setSelected( 'horizontal' ) } onChange={ ( next ) => { onChange( { @@ -321,12 +328,17 @@ function BoxTypeControl( { ); } -function UnitControl( { label, ...props } ) { +function UnitControl( { onChange, label, ...props } ) { + const handleOnChange = ( nextValue ) => { + const value = parseFloat( nextValue ); + onChange( isNaN( value ) ? nextValue : value ); + }; return ( @@ -348,7 +360,7 @@ const IconWrapper = styled( FlexItem )` const UnitControlWrapper = styled.div` box-sizing: border-box; - max-width: 70px; + max-width: 75px; `; function parseValues( values = {} ) { diff --git a/packages/components/src/box-control/stories/index.js b/packages/components/src/box-control/stories/index.js index ef9a00021aeb75..06b711efc90c50 100644 --- a/packages/components/src/box-control/stories/index.js +++ b/packages/components/src/box-control/stories/index.js @@ -2,26 +2,113 @@ * External dependencies */ import styled from '@emotion/styled'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; /** * Internal dependencies */ import BoxControl from '../'; import BoxControlIcon from '../icon'; +import { Flex, FlexBlock } from '../../flex'; export default { title: 'Components/BoxControl', component: BoxControl }; export const _default = () => { - return ( - - - - ); + return ; }; export const icon = () => { return ; }; -const Wrapper = styled.div` - padding: 40px; +function DemoExample() { + const [ values, setValues ] = useState( { + top: [ 10 ], + right: [ 10 ], + bottom: [ 10 ], + left: [ 10 ], + } ); + const top = values?.top?.[ 0 ]; + const right = values?.right?.[ 0 ]; + const bottom = values?.bottom?.[ 0 ]; + const left = values?.left?.[ 0 ]; + + return ( + + + + + + + + + + + + + + + + + + ); +} + +export const demo = () => { + return ; +}; + +const Container = styled( Flex )` + max-width: 780px; +`; + +const Box = styled.div` + width: 300px; + height: 300px; + position: relative; +`; + +const Padding = styled.div` + position: absolute; + background: blue; + opacity: 0.2; +`; + +const Content = styled.div` + padding: 20px; `; From 4f05a2473e8b6fd86c0c1da85780c0a8fbcc93e1 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 3 Apr 2020 17:19:15 -0400 Subject: [PATCH 008/103] Update snapshot tests --- packages/components/src/flex/stories/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/src/flex/stories/index.js b/packages/components/src/flex/stories/index.js index a67883dd49482b..1408270a11c855 100644 --- a/packages/components/src/flex/stories/index.js +++ b/packages/components/src/flex/stories/index.js @@ -134,12 +134,12 @@ const ItemExample = () => {
- - @@ -184,12 +184,12 @@ const BlockExample = () => {
- - @@ -227,12 +227,12 @@ export const exampleActions = () => { - - + From 65736fc5dab0bcd676f76d2a9622689895334c45 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Fri, 3 Apr 2020 17:38:37 -0400 Subject: [PATCH 009/103] Add shiftStep in drag feature in NumberControl --- packages/components/src/number-control/index.js | 11 +++++++---- .../components/src/number-control/stories/index.js | 8 +++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/components/src/number-control/index.js b/packages/components/src/number-control/index.js index 257169b923f43b..6cc57777fb7daa 100644 --- a/packages/components/src/number-control/index.js +++ b/packages/components/src/number-control/index.js @@ -41,7 +41,7 @@ export function NumberControl( useDragCursor( isDragging ); const dragGestureProps = useDrag( - ( { dragging, delta, event } ) => { + ( { dragging, delta, event, shiftKey } ) => { if ( ! isDragEnabled ) return; if ( ! dragging ) { setIsDragging( false ); @@ -51,10 +51,13 @@ export function NumberControl( event.stopPropagation(); const [ , y ] = delta; - const distance = y * -1; - const nextValue = clamp( add( valueProp, distance ), min, max ); + const modifier = shiftKey ? shiftStep : 1; + const distance = y * modifier * -1; - onChange( nextValue.toString(), { event } ); + if ( distance !== 0 ) { + const nextValue = clamp( add( valueProp, distance ), min, max ); + onChange( nextValue.toString(), { event } ); + } if ( ! isDragging ) { setIsDragging( true ); diff --git a/packages/components/src/number-control/stories/index.js b/packages/components/src/number-control/stories/index.js index b35af6a74eb11f..859afe27fc867c 100644 --- a/packages/components/src/number-control/stories/index.js +++ b/packages/components/src/number-control/stories/index.js @@ -27,7 +27,13 @@ function Example() { step: number( 'step', 1 ), }; - return ; + return ( + setValue( v ) } + /> + ); } export const _default = () => { From 779ae5bf149fc71676ff70f50bcf8fa37834df95 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Tue, 7 Apr 2020 13:12:50 -0400 Subject: [PATCH 010/103] Adjust Box control UI --- packages/components/src/box-control/icon.js | 6 +- packages/components/src/box-control/index.js | 280 ++++++++++++------ .../components/src/flex/styles/flex-styles.js | 1 + .../styles/unit-control-styles.js | 1 - 4 files changed, 184 insertions(+), 104 deletions(-) diff --git a/packages/components/src/box-control/icon.js b/packages/components/src/box-control/icon.js index 3bfac3ead3d178..b3baed2810e039 100644 --- a/packages/components/src/box-control/icon.js +++ b/packages/components/src/box-control/icon.js @@ -3,10 +3,6 @@ */ import { css } from '@emotion/core'; import styled from '@emotion/styled'; -/** - * Internal dependencies - */ -import { color } from '../utils/style-mixins'; const BASE_ICON_SIZE = 24; @@ -59,7 +55,7 @@ const Viewbox = styled.span` const strokeFocus = ( { isFocused } ) => { return css( { - backgroundColor: color( 'ui.border' ), + backgroundColor: 'currentColor', opacity: isFocused ? 1 : 0.3, } ); }; diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index ca4acd5a3381ee..d59410dad03cf5 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -2,18 +2,19 @@ * External dependencies */ import { noop } from 'lodash'; -import { useRadioState, Radio, RadioGroup } from 'reakit/Radio'; +// import { useRadioState, Radio, RadioGroup } from 'reakit/Radio'; import styled from '@emotion/styled'; /** * WordPress dependencies */ -import { useEffect, useState } from '@wordpress/element'; +import { useState } from '@wordpress/element'; +import { Icon, chevronDown } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import ButtonGroup from '../button-group'; -import Button from '../button'; +// import BaseButtonGroup from '../button-group'; +import DropdownMenu from '../dropdown-menu'; import BoxControlIcon from './icon'; import Tooltip from '../tooltip'; import BaseUnitControl from '../unit-control'; @@ -67,6 +68,7 @@ export default function BoxControl( { } ) { const [ type, setType ] = useState( parseType( typeProp ) ); const [ values, setValues ] = useState( parseValues( valuesProp ) ); + const [ icon, setIcon ] = useState( parseType( typeProp ) ); const ControlComponent = BoxControlComponent[ type ]; const updateValues = ( nextValues ) => { @@ -81,21 +83,28 @@ export default function BoxControl( {
{ label } +
+ - - - + + + +
); } @@ -109,9 +118,6 @@ function BoxAllControl( { placeholder, onChange = noop, values, ...props } ) { return ( - - - { + onSelect( next ); + }; return ( - - - setSelected( 'vertical' ) } + onFocus={ () => handleOnSelect( 'vertical' ) } onChange={ ( next ) => { onChange( { top: [ next, verticalUnit ], @@ -192,9 +202,9 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { /> setSelected( 'horizontal' ) } + onFocus={ () => handleOnSelect( 'horizontal' ) } onChange={ ( next ) => { onChange( { left: [ next, horizontalUnit ], @@ -214,55 +224,65 @@ function BoxPairsControl( { placeholder, onChange = noop, values, ...props } ) { ); } -function BoxCustomControl( { onChange = noop, values, ...props } ) { - const [ selected, setSelected ] = useState( 'top' ); +function BoxCustomControl( { + onSelect = noop, + onChange = noop, + values, + ...props +} ) { const unitControlProps = useCustomUnitControlProps( { values, onChange, } ); + const labels = { + top: __( 'Top' ), + right: __( 'Right' ), + bottom: __( 'Bottom' ), + left: __( 'Left' ), + }; + + const handleOnSelect = ( next ) => { + onSelect( next ); + }; + return ( - - - - - setSelected( 'top' ) } - placeholder="" - /> - setSelected( 'right' ) } - placeholder="" - /> - setSelected( 'bottom' ) } - placeholder="" - /> - setSelected( 'left' ) } - placeholder="" - /> - + handleOnSelect( 'top' ) } + placeholder="" + /> + handleOnSelect( 'right' ) } + placeholder="" + /> + handleOnSelect( 'bottom' ) } + placeholder="" + /> + handleOnSelect( 'left' ) } + placeholder="" + /> ); } function ControlContainer( { children } ) { return ( - + { children } ); @@ -294,40 +314,99 @@ function useCustomUnitControlProps( { values, onChange = noop } ) { return props; } -function BoxTypeControl( { - label = 'Box Control', - onChange = noop, - type = 'all', -} ) { - const radio = useRadioState( { state: type } ); +function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon } ) { + const icons = { + all: , + pairs: , + custom: , + vertical: , + horizontal: , + top: , + right: , + bottom: , + left: , + }; + + const handleOnChange = ( next ) => { + onChange( next ); + onSelect( next ); + }; - useEffect( () => { - onChange( radio.state ); - }, [ radio.state ] ); + const options = [ + { + title: typeProps.all.label, + value: 'all', + icon: icons.all, + onClick: () => handleOnChange( 'all' ), + }, + { + title: typeProps.pairs.label, + value: 'pairs', + icon: icons.pairs, + onClick: () => handleOnChange( 'pairs' ), + }, + { + title: typeProps.custom.label, + value: 'custom', + icon: icons.custom, + onClick: () => handleOnChange( 'custom' ), + }, + ]; + + const dropdownIcon = ( + + { icons[ icon ] } + + + ); + + const toggleProps = { + children: dropdownIcon, + }; return ( - - { types.map( ( value ) => { - const valueProps = typeProps[ value ]; - const isSelected = radio.state === value; - - return ( - - - - - - ); - } ) } - + ); } +// function BoxTypeControl( { +// label = 'Box Control', +// onChange = noop, +// type = 'all', +// } ) { +// const radio = useRadioState( { state: type } ); + +// useEffect( () => { +// onChange( radio.state ); +// }, [ radio.state ] ); + +// return ( +// +// { types.map( ( value ) => { +// const valueProps = typeProps[ value ]; +// const isSelected = radio.state === value; + +// return ( +// +// +// +// +// +// ); +// } ) } +// +// ); +// } + function UnitControl( { onChange, label, ...props } ) { const handleOnChange = ( nextValue ) => { const value = parseFloat( nextValue ); @@ -347,22 +426,27 @@ function UnitControl( { onChange, label, ...props } ) { } const Root = styled.div` - max-width: 400px; + max-width: 280px; `; const Header = styled( Flex )` margin-bottom: 8px; `; -const IconWrapper = styled( FlexItem )` - padding-right: 8px; -`; - const UnitControlWrapper = styled.div` box-sizing: border-box; max-width: 75px; `; +// const ButtonGroup = styled( BaseButtonGroup )` +// display: flex; +// margin: 0; +// `; + +const DropdownButton = styled( Flex )` + margin: 0 -8px; +`; + function parseValues( values = {} ) { const nextValueProps = {}; diff --git a/packages/components/src/flex/styles/flex-styles.js b/packages/components/src/flex/styles/flex-styles.js index f923cb505bf69e..3e53a5e06f0daf 100644 --- a/packages/components/src/flex/styles/flex-styles.js +++ b/packages/components/src/flex/styles/flex-styles.js @@ -53,6 +53,7 @@ export const Flex = styled.div` `; export const Item = styled.div` + box-sizing: border-box; min-width: 0; max-width: 100%; `; diff --git a/packages/components/src/unit-control/styles/unit-control-styles.js b/packages/components/src/unit-control/styles/unit-control-styles.js index eb17bdf1d87fb8..9d3f5c29191bf5 100644 --- a/packages/components/src/unit-control/styles/unit-control-styles.js +++ b/packages/components/src/unit-control/styles/unit-control-styles.js @@ -111,7 +111,6 @@ const baseUnitLabelStyles = ( props ) => { text-align-last: center; text-transform: uppercase; width: 22px; - z-index: 1; ${rtl( { right: 4 } )()} ${unitSizeStyles( props )} From d37a6f5072a8260c0048e431d1274bed327cb2f6 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Tue, 7 Apr 2020 14:15:23 -0400 Subject: [PATCH 011/103] Initial integration with Cover --- packages/block-library/src/cover/block.json | 3 + packages/block-library/src/cover/edit.js | 2 + packages/components/src/box-control/index.js | 113 +++++++++++------- packages/components/src/flex/flex.js | 2 + .../components/src/flex/styles/flex-styles.js | 25 +++- packages/components/src/index.js | 1 + .../components/src/number-control/utils.js | 2 + 7 files changed, 103 insertions(+), 45 deletions(-) diff --git a/packages/block-library/src/cover/block.json b/packages/block-library/src/cover/block.json index 630bb1aeed8430..46d8e0fefddd56 100644 --- a/packages/block-library/src/cover/block.json +++ b/packages/block-library/src/cover/block.json @@ -43,6 +43,9 @@ }, "contentPosition": { "type": "string" + }, + "padding": { + "type": "object" } } } diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 70c0d1b350dccc..1fcf366fbbf48e 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, + __experimentalBoxControl as BoxControl, } from '@wordpress/components'; import { compose, withInstanceId, useInstanceId } from '@wordpress/compose'; import { @@ -376,6 +377,7 @@ function CoverEdit( { } ); } } /> + { label } - + v === value ); @@ -126,6 +132,7 @@ function BoxAllControl( { placeholder, onChange = noop, values, ...props } ) { placeholder={ placeholderLabel } unit={ unit } onChange={ ( next ) => { + onSelect( 'all' ); onChange( { top: [ next, unit ], right: [ next, unit ], @@ -247,42 +254,54 @@ function BoxCustomControl( { }; return ( - - handleOnSelect( 'top' ) } - placeholder="" - /> - handleOnSelect( 'right' ) } - placeholder="" - /> - handleOnSelect( 'bottom' ) } - placeholder="" - /> - handleOnSelect( 'left' ) } - placeholder="" - /> - +
+ + + handleOnSelect( 'top' ) } + placeholder="" + /> + + + handleOnSelect( 'right' ) } + placeholder="" + /> + + + + + handleOnSelect( 'bottom' ) } + placeholder="" + /> + + + handleOnSelect( 'left' ) } + placeholder="" + /> + + +
); } -function ControlContainer( { children } ) { +function ControlContainer( { children, ...props } ) { return ( - + { children } ); @@ -314,7 +333,7 @@ function useCustomUnitControlProps( { values, onChange = noop } ) { return props; } -function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon } ) { +function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon, type } ) { const icons = { all: , pairs: , @@ -336,20 +355,23 @@ function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon } ) { { title: typeProps.all.label, value: 'all', - icon: icons.all, + icon: { icons.all }, onClick: () => handleOnChange( 'all' ), + isActive: type === 'all', }, { title: typeProps.pairs.label, value: 'pairs', - icon: icons.pairs, + icon: { icons.pairs }, onClick: () => handleOnChange( 'pairs' ), + isActive: type === 'pairs', }, { title: typeProps.custom.label, value: 'custom', - icon: icons.custom, + icon: { icons.custom }, onClick: () => handleOnChange( 'custom' ), + isActive: type === 'custom', }, ]; @@ -362,13 +384,19 @@ function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon } ) { const toggleProps = { children: dropdownIcon, + isSmall: true, + style: { + height: 30, + lineHeight: 28, + }, }; return ( ); } @@ -412,6 +440,7 @@ function UnitControl( { onChange, label, ...props } ) { const value = parseFloat( nextValue ); onChange( isNaN( value ) ? nextValue : value ); }; + return ( @@ -444,7 +473,11 @@ const UnitControlWrapper = styled.div` // `; const DropdownButton = styled( Flex )` - margin: 0 -8px; + margin: 0 -4px; +`; + +const DropdownIconWrapper = styled.div` + margin-right: 8px; `; function parseValues( values = {} ) { diff --git a/packages/components/src/flex/flex.js b/packages/components/src/flex/flex.js index 3b230d29cd5058..a601b207be81c0 100644 --- a/packages/components/src/flex/flex.js +++ b/packages/components/src/flex/flex.js @@ -19,6 +19,7 @@ function Flex( className, gap = 2, justify = 'space-between', + isReversed = false, ...props }, ref @@ -33,6 +34,7 @@ function Flex( ref={ ref } gap={ gap } justify={ justify } + isReversed={ isReversed } /> ); } diff --git a/packages/components/src/flex/styles/flex-styles.js b/packages/components/src/flex/styles/flex-styles.js index 3e53a5e06f0daf..50bfbfd087b58d 100644 --- a/packages/components/src/flex/styles/flex-styles.js +++ b/packages/components/src/flex/styles/flex-styles.js @@ -16,33 +16,47 @@ const alignStyle = ( { align } ) => { } ); }; -const justifyStyle = ( { justify } ) => { +const justifyStyle = ( { justify, isReversed } ) => { const justifies = { left: 'flex-start', right: 'flex-end', }; - const value = justifies[ justify ] || justify; + let value = justifies[ justify ] || justify; + + if ( isReversed && justifies[ justify ] ) { + value = justify === 'left' ? justifies.right : justifies.left; + } return css( { justifyContent: value, } ); }; -const gapStyle = ( { gap } ) => { +const gapStyle = ( { gap, isReversed } ) => { const base = 4; const value = typeof gap === 'number' ? base * gap : base; + const dir = isReversed ? 'left' : 'right'; + const padding = `padding-${ dir }`; return css` > * { - padding-right: ${value}px; + ${padding}: ${value}px; &:last-child { - padding-right: 0; + ${padding}: 0; } } `; }; +const reversedStyles = ( { isReversed } ) => { + if ( ! isReversed ) return ''; + + return css` + flex-direction: row-reverse; + `; +}; + export const Flex = styled.div` box-sizing: border-box; display: flex; @@ -50,6 +64,7 @@ export const Flex = styled.div` ${alignStyle}; ${justifyStyle}; ${gapStyle}; + ${reversedStyles}; `; export const Item = styled.div` diff --git a/packages/components/src/index.js b/packages/components/src/index.js index b6188e94b0113a..4410c5e4b2256f 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -16,6 +16,7 @@ export { default as Animate } from './animate'; export { default as AnglePickerControl } from './angle-picker-control'; export { default as Autocomplete } from './autocomplete'; export { default as BaseControl } from './base-control'; +export { default as __experimentalBoxControl } from './box-control'; export { default as Button } from './button'; export { default as ButtonGroup } from './button-group'; export { default as Card } from './card'; diff --git a/packages/components/src/number-control/utils.js b/packages/components/src/number-control/utils.js index e8b30b2a0f235c..0159091791f23d 100644 --- a/packages/components/src/number-control/utils.js +++ b/packages/components/src/number-control/utils.js @@ -9,8 +9,10 @@ export function useDragCursor( isDragging ) { useEffect( () => { if ( isDragging ) { document.documentElement.style.cursor = DRAG_CURSOR; + document.documentElement.style.pointerEvents = 'none'; } else { document.documentElement.style.cursor = null; + document.documentElement.style.pointerEvents = null; } }, [ isDragging ] ); } From 8ef3f80f13ccc12a928974aabc97b98dd855e5ac Mon Sep 17 00:00:00 2001 From: Jon Q Date: Tue, 7 Apr 2020 16:45:08 -0400 Subject: [PATCH 012/103] Integrate PaddingControl with Cover using hooks --- packages/block-editor/src/hooks/padding.js | 61 ++++++++ packages/block-editor/src/hooks/style.js | 14 ++ packages/block-library/src/cover/edit.js | 114 +++++++------- packages/block-library/src/cover/index.js | 1 + packages/block-library/src/cover/style.scss | 10 ++ packages/components/src/box-control/index.js | 118 +++++++------- .../src/box-control/stories/index.js | 54 ++----- packages/components/src/box-control/utils.js | 83 ++++++++++ .../components/src/box-control/visualizer.js | 148 ++++++++++++++++++ .../components/src/utils/colors-values.js | 1 + 10 files changed, 440 insertions(+), 164 deletions(-) create mode 100644 packages/block-editor/src/hooks/padding.js create mode 100644 packages/components/src/box-control/utils.js create mode 100644 packages/components/src/box-control/visualizer.js diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js new file mode 100644 index 00000000000000..a59f55644dd49d --- /dev/null +++ b/packages/block-editor/src/hooks/padding.js @@ -0,0 +1,61 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Platform } from '@wordpress/element'; +import { hasBlockSupport } from '@wordpress/blocks'; +import { __experimentalBoxControl as BoxControl } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { cleanEmptyObject } from './utils'; + +export const PADDING_SUPPORT_KEY = '__experimentalPadding'; + +/** + * Inspector control panel containing the line height related configuration + * + * @param {Object} props + * + * @return {WPElement} Line height edit element. + */ +export function PaddingEdit( props ) { + const { + name: blockName, + attributes: { padding, style }, + setAttributes, + } = props; + + if ( ! hasBlockSupport( blockName, PADDING_SUPPORT_KEY ) ) { + return null; + } + + const onChange = ( next ) => { + const { top, right, bottom, left } = next; + + const newStyle = { + ...style, + paddingTop: `${ top[ 0 ] }${ top[ 1 ] }`, + paddingRight: `${ right[ 0 ] }${ right[ 1 ] }`, + paddingBottom: `${ bottom[ 0 ] }${ bottom[ 1 ] }`, + paddingLeft: `${ left[ 0 ] }${ left[ 1 ] }`, + }; + + setAttributes( { + padding: next, + style: cleanEmptyObject( newStyle ), + } ); + }; + + return Platform.select( { + web: ( + + ), + native: null, + } ); +} diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index f23a5c2612e2bc..a86180cb195d4e 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -20,11 +20,13 @@ import InspectorControls from '../components/inspector-controls'; import { COLOR_SUPPORT_KEY, ColorEdit } from './color'; import { LINE_HEIGHT_SUPPORT_KEY, LineHeightEdit } from './line-height'; import { FONT_SIZE_SUPPORT_KEY, FontSizeEdit } from './font-size'; +import { PADDING_SUPPORT_KEY, PaddingEdit } from './padding'; const styleSupportKeys = [ COLOR_SUPPORT_KEY, LINE_HEIGHT_SUPPORT_KEY, FONT_SIZE_SUPPORT_KEY, + PADDING_SUPPORT_KEY, ]; const typographySupportKeys = [ @@ -144,6 +146,11 @@ export const withBlockControls = createHigherOrderComponent( hasBlockSupport( blockName, key ) ); + const hasPaddingSupport = hasBlockSupport( + blockName, + PADDING_SUPPORT_KEY + ); + return [ Platform.OS === 'web' && hasTypographySupport && ( @@ -155,6 +162,13 @@ export const withBlockControls = createHigherOrderComponent( ), , , + hasPaddingSupport && ( + + + + + + ), ]; }, 'withToolbarControls' diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 1fcf366fbbf48e..9366e39625e14c 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -70,6 +70,8 @@ const INNER_BLOCKS_TEMPLATE = [ ], ]; +const { __Visualizer: BoxControlVisualizer } = BoxControl; + function retrieveFastAverageColor() { if ( ! retrieveFastAverageColor.fastAverageColor ) { retrieveFastAverageColor.fastAverageColor = new FastAverageColor(); @@ -245,6 +247,7 @@ function CoverEdit( { hasParallax, minHeight, minHeightUnit, + padding, url, } = attributes; const { @@ -377,7 +380,6 @@ function CoverEdit( { } ); } } /> - { controls } - { - setAttributes( { minHeightUnit: 'px' } ); - toggleSelection( false ); - } } - onResize={ setTemporaryMinHeight } - onResizeStop={ ( newMinHeight ) => { - toggleSelection( true ); - setAttributes( { minHeight: newMinHeight } ); - setTemporaryMinHeight( null ); - } } - > -
- { IMAGE_BACKGROUND_TYPE === backgroundType && ( - // Used only to programmatically check if the image is dark or not - + + + + ); } diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index cba3d554e5f01a..caf1e41746cf6a 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -26,6 +26,7 @@ export const settings = { supports: { align: true, html: false, + __experimentalPadding: true, }, example: { attributes: { diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss index 4dc295d28fd849..0469ed6ee3663c 100644 --- a/packages/block-library/src/cover/style.scss +++ b/packages/block-library/src/cover/style.scss @@ -1,3 +1,13 @@ +:root { + .wp-block-cover-image, + .wp-block-cover { + padding-top: var(--wp--padding-top); + padding-right: var(--wp--padding-right); + padding-bottom: var(--wp--padding-bottom); + padding-left: var(--wp--padding-left); + } +} + .wp-block-cover-image, .wp-block-cover { position: relative; diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index 8873639f7dbcd3..c6e24ee7b8d966 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -16,59 +16,40 @@ import { __ } from '@wordpress/i18n'; // import BaseButtonGroup from '../button-group'; import DropdownMenu from '../dropdown-menu'; import BoxControlIcon from './icon'; +import Visualizer from './visualizer'; import Tooltip from '../tooltip'; import BaseUnitControl from '../unit-control'; import { Flex, FlexBlock, FlexItem } from '../flex'; +import { + DEFAULT_VALUES, + TYPE_PROPS, + parseValues, + parseType, + getValues, +} from './utils'; -const types = [ 'all', 'pairs', 'custom' ]; const defaultInputProps = { min: 0, }; -const typeProps = { - all: { - sides: [ 'all' ], - label: __( 'All sides' ), - }, - pairs: { - sides: [ 'top', 'bottom' ], - label: __( 'Pair of sides' ), - }, - custom: { - sides: [ 'top' ], - label: __( 'Individual sides' ), - }, -}; - -const defaultValueProps = { - top: [ 0, 'px' ], - right: [ 0, 'px' ], - bottom: [ 0, 'px' ], - left: [ 0, 'px' ], -}; - const BoxControlComponent = { all: BoxAllControl, pairs: BoxPairsControl, custom: BoxCustomControl, }; -const parseType = ( type ) => { - return types.includes( type ) ? type : 'all'; -}; - export default function BoxControl( { inputProps = defaultInputProps, onChange = noop, + onSelect = noop, label = __( 'Box Control' ), - type: typeProp = 'all', - values: valuesProp, + values: valuesProp = DEFAULT_VALUES, // Disable units for now units = false, } ) { - const [ type, setType ] = useState( parseType( typeProp ) ); + const [ type, setType ] = useState( parseType( valuesProp ) ); const [ values, setValues ] = useState( parseValues( valuesProp ) ); - const [ icon, setIcon ] = useState( parseType( typeProp ) ); + const [ icon, setIcon ] = useState( parseType( valuesProp ) ); const ControlComponent = BoxControlComponent[ type ]; const updateValues = ( nextValues ) => { @@ -77,6 +58,30 @@ export default function BoxControl( { onChange( mergedValues ); }; + const handleOnSelect = ( next ) => { + let nextSelect = [ next ]; + + switch ( next ) { + case 'all': + nextSelect = [ 'top', 'right', 'bottom', 'left' ]; + break; + case 'vertical': + nextSelect = [ 'top', 'bottom' ]; + break; + case 'horizontal': + nextSelect = [ 'right', 'left' ]; + break; + case 'pairs': + nextSelect = [ 'top', 'bottom' ]; + break; + case 'custom': + nextSelect = [ 'top' ]; + break; + } + + onSelect( nextSelect ); + }; + const mixedLabel = __( 'Mixed' ); return ( @@ -89,7 +94,10 @@ export default function BoxControl( { { + setType( next ); + handleOnSelect( next ); + } } onSelect={ setIcon } type={ type } /> @@ -99,7 +107,10 @@ export default function BoxControl( { { ...inputProps } placeholder={ mixedLabel } values={ values } - onSelect={ setIcon } + onSelect={ ( next ) => { + handleOnSelect( next ); + setIcon( next ); + } } onChange={ updateValues } units={ units } /> @@ -131,8 +142,8 @@ function BoxAllControl( { value={ isMixed ? '' : value } placeholder={ placeholderLabel } unit={ unit } + onFocus={ () => onSelect( 'all' ) } onChange={ ( next ) => { - onSelect( 'all' ); onChange( { top: [ next, unit ], right: [ next, unit ], @@ -335,9 +346,9 @@ function useCustomUnitControlProps( { values, onChange = noop } ) { function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon, type } ) { const icons = { - all: , - pairs: , - custom: , + all: , + pairs: , + custom: , vertical: , horizontal: , top: , @@ -353,21 +364,21 @@ function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon, type } ) { const options = [ { - title: typeProps.all.label, + title: TYPE_PROPS.all.label, value: 'all', icon: { icons.all }, onClick: () => handleOnChange( 'all' ), isActive: type === 'all', }, { - title: typeProps.pairs.label, + title: TYPE_PROPS.pairs.label, value: 'pairs', icon: { icons.pairs }, onClick: () => handleOnChange( 'pairs' ), isActive: type === 'pairs', }, { - title: typeProps.custom.label, + title: TYPE_PROPS.custom.label, value: 'custom', icon: { icons.custom }, onClick: () => handleOnChange( 'custom' ), @@ -415,7 +426,7 @@ function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon, type } ) { // return ( // // { types.map( ( value ) => { -// const valueProps = typeProps[ value ]; +// const valueProps = TYPE_PROPS[ value ]; // const isSelected = radio.state === value; // return ( @@ -480,27 +491,4 @@ const DropdownIconWrapper = styled.div` margin-right: 8px; `; -function parseValues( values = {} ) { - const nextValueProps = {}; - - Object.keys( defaultValueProps ).forEach( ( key ) => { - const defaultValue = defaultValueProps[ key ]; - const prop = values[ key ] || []; - - nextValueProps[ key ] = [ - prop?.[ 0 ] || defaultValue[ 0 ], - prop?.[ 1 ] || defaultValue[ 1 ], - ]; - } ); - - return nextValueProps; -} - -function getValues( values, ...args ) { - const nextValues = []; - args.forEach( ( key ) => { - nextValues.push( values[ key ][ 0 ] ); - } ); - - return nextValues; -} +BoxControl.__Visualizer = Visualizer; diff --git a/packages/components/src/box-control/stories/index.js b/packages/components/src/box-control/stories/index.js index 06b711efc90c50..bda9725e01eea3 100644 --- a/packages/components/src/box-control/stories/index.js +++ b/packages/components/src/box-control/stories/index.js @@ -12,6 +12,7 @@ import { useState } from '@wordpress/element'; */ import BoxControl from '../'; import BoxControlIcon from '../icon'; +import BoxControlVisualizer from '../visualizer'; import { Flex, FlexBlock } from '../../flex'; export default { title: 'Components/BoxControl', component: BoxControl }; @@ -31,10 +32,6 @@ function DemoExample() { bottom: [ 10 ], left: [ 10 ], } ); - const top = values?.top?.[ 0 ]; - const right = values?.right?.[ 0 ]; - const bottom = values?.bottom?.[ 0 ]; - const left = values?.left?.[ 0 ]; return ( @@ -49,40 +46,11 @@ function DemoExample() { - - - - - - + + + + + @@ -97,16 +65,14 @@ const Container = styled( Flex )` max-width: 780px; `; -const Box = styled.div` +const BoxContainer = styled.div` width: 300px; height: 300px; - position: relative; `; -const Padding = styled.div` - position: absolute; - background: blue; - opacity: 0.2; +const Box = styled.div` + background: #ddd; + height: 300px; `; const Content = styled.div` diff --git a/packages/components/src/box-control/utils.js b/packages/components/src/box-control/utils.js new file mode 100644 index 00000000000000..5c09be501f12f2 --- /dev/null +++ b/packages/components/src/box-control/utils.js @@ -0,0 +1,83 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +export const TYPES = [ 'all', 'pairs', 'custom' ]; + +export const TYPE_PROPS = { + all: { + sides: [ 'all' ], + label: __( 'All sides' ), + }, + pairs: { + sides: [ 'top', 'bottom' ], + label: __( 'Pair of sides' ), + }, + custom: { + sides: [ 'top' ], + label: __( 'Individual sides' ), + }, +}; + +export const DEFAULT_VALUES = { + top: [ 0, 'px' ], + right: [ 0, 'px' ], + bottom: [ 0, 'px' ], + left: [ 0, 'px' ], +}; + +export const parseType = ( values = {} ) => { + const { + top: [ top ], + bottom: [ bottom ], + left: [ left ], + right: [ right ], + } = parseValues( values ); + + const isAll = [ top, bottom, left, right ].every( + ( value ) => value === top + ); + + if ( isAll ) { + return 'all'; + } + + const isVerticalMatch = [ top, bottom ].every( ( value ) => value === top ); + const isHorizontalMatch = [ left, right ].every( + ( value ) => value === left + ); + + const isPairs = isVerticalMatch && isHorizontalMatch; + + if ( isPairs ) { + return 'pairs'; + } + + return 'custom'; +}; + +export function parseValues( values = {} ) { + const nextValueProps = {}; + + Object.keys( DEFAULT_VALUES ).forEach( ( key ) => { + const defaultValue = DEFAULT_VALUES[ key ]; + const prop = values[ key ] || []; + + nextValueProps[ key ] = [ + prop?.[ 0 ] || defaultValue[ 0 ], + prop?.[ 1 ] || defaultValue[ 1 ], + ]; + } ); + + return nextValueProps; +} + +export function getValues( values, ...args ) { + const nextValues = []; + args.forEach( ( key ) => { + nextValues.push( values[ key ][ 0 ] ); + } ); + + return nextValues; +} diff --git a/packages/components/src/box-control/visualizer.js b/packages/components/src/box-control/visualizer.js new file mode 100644 index 00000000000000..9cc3e911a97a8c --- /dev/null +++ b/packages/components/src/box-control/visualizer.js @@ -0,0 +1,148 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; + +/** + * WordPress dependencies + */ +import { useRef, useEffect, useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { color } from '../utils/style-mixins'; +import { DEFAULT_VALUES, parseValues } from './utils'; + +export default function BoxControlVisualizer( { + children, + values: valuesProp = DEFAULT_VALUES, + ...props +} ) { + const values = parseValues( valuesProp ); + + return ( + + + { children } + + ); +} + +function Sides( { values } ) { + const { top, right, bottom, left } = values; + + return ( + <> + + + + + + ); +} + +function Top( { value } ) { + const [ height ] = value; + const animationProps = useSideAnimation( height ); + + return ; +} + +function Right( { value } ) { + const [ width ] = value; + const animationProps = useSideAnimation( width ); + + return ; +} + +function Bottom( { value } ) { + const [ height ] = value; + const animationProps = useSideAnimation( height ); + + return ; +} + +function Left( { value } ) { + const [ width ] = value; + const animationProps = useSideAnimation( width ); + + return ; +} + +function useSideAnimation( value ) { + const [ isActive, setIsActive ] = useState( false ); + const valueRef = useRef( value ); + const timeoutRef = useRef(); + + const clearTimer = () => { + if ( timeoutRef.current ) { + window.clearTimeout( timeoutRef.current ); + } + }; + + useEffect( () => { + if ( value !== valueRef.current ) { + setIsActive( true ); + valueRef.current = value; + + clearTimer(); + + timeoutRef.current = setTimeout( () => { + setIsActive( false ); + }, 400 ); + } + + return () => clearTimer(); + }, [ value ] ); + + return { + isActive, + }; +} + +const Container = styled.div` + box-sizing: border-box; + position: relative; +`; + +const Side = styled.div` + box-sizing: border-box; + background: ${color( 'ui.brand' )}; + filter: brightness( 0.7 ); + opacity: 0; + position: absolute; + pointer-events: none; + transition: opacity 120ms linear; + z-index: 1; + + ${( { isActive } ) => + isActive && + ` + opacity: 0.3; + `} +`; + +const TopView = styled( Side )` + top: 0; + left: 0; + right: 0; +`; + +const RightView = styled( Side )` + top: 0; + bottom: 0; + right: 0; +`; + +const BottomView = styled( Side )` + bottom: 0; + left: 0; + right: 0; +`; + +const LeftView = styled( Side )` + top: 0; + bottom: 0; + left: 0; +`; diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js index 974acb607d1e16..38d6a6f5030164 100644 --- a/packages/components/src/utils/colors-values.js +++ b/packages/components/src/utils/colors-values.js @@ -136,6 +136,7 @@ export const ALERT = { // Namespaced values for raw colors hex codes export const UI = { background: BASE.white, + brand: BLUE.wordpress[ 700 ], border: BASE.black, borderFocus: BLUE.medium.focus, borderLight: LIGHT_GRAY[ 600 ], From 74e327c5279e7c2573ac1a643b34fa705625d7ed Mon Sep 17 00:00:00 2001 From: Jon Q Date: Tue, 7 Apr 2020 16:47:31 -0400 Subject: [PATCH 013/103] Improve visualization of padding --- packages/components/src/box-control/visualizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/box-control/visualizer.js b/packages/components/src/box-control/visualizer.js index 9cc3e911a97a8c..4d1096e3aac5fc 100644 --- a/packages/components/src/box-control/visualizer.js +++ b/packages/components/src/box-control/visualizer.js @@ -109,7 +109,7 @@ const Container = styled.div` const Side = styled.div` box-sizing: border-box; background: ${color( 'ui.brand' )}; - filter: brightness( 0.7 ); + filter: brightness( 1 ); opacity: 0; position: absolute; pointer-events: none; From aa790eac5b1011258e6f54bdc47c8b3bc8bf9f5d Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 8 Apr 2020 10:51:59 -0400 Subject: [PATCH 014/103] Experiment with new Box Controls layout --- packages/components/src/box-control/index.js | 238 ++++++++++++++++-- .../components/src/number-control/index.js | 52 +++- .../components/src/number-control/utils.js | 21 ++ packages/components/src/unit-control/index.js | 20 +- .../styles/unit-control-styles.js | 32 ++- 5 files changed, 313 insertions(+), 50 deletions(-) diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index c6e24ee7b8d966..7796cf50d5a5b6 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -89,37 +89,189 @@ export default function BoxControl( {
{ label }
- - - { - setType( next ); - handleOnSelect( next ); - } } - onSelect={ setIcon } - type={ type } - /> - - - { - handleOnSelect( next ); - setIcon( next ); - } } - onChange={ updateValues } - units={ units } - /> - - + { false && ( + + + { + setType( next ); + handleOnSelect( next ); + } } + onSelect={ setIcon } + type={ type } + /> + + + { + handleOnSelect( next ); + setIcon( next ); + } } + onChange={ updateValues } + units={ units } + /> + + + ) } + { + handleOnSelect( next ); + setIcon( next ); + } } + onChange={ updateValues } + units={ units } + /> ); } +function BoxUIControl( { onChange = noop, values, ...props } ) { + const { + top: [ top, unit ], + right: [ right ], + bottom: [ bottom ], + left: [ left ], + } = values; + + const allValues = getValues( values, 'top', 'right', 'bottom', 'left' ); + const isMixed = ! allValues.every( ( v ) => v === top ); + + const createHandleOnChange = ( side ) => ( next ) => { + onChange( { ...values, [ side ]: [ next, unit ] } ); + }; + + const baseStyles = { + position: 'absolute', + zIndex: 1, + maxWidth: 50, + }; + + return ( + + + + + + + + + + + { + onChange( { + top: [ next, unit ], + right: [ next, unit ], + bottom: [ next, unit ], + left: [ next, unit ], + } ); + } } + placeholder="All" + label="All" + size="small" + style={ { + ...baseStyles, + left: '50%', + top: '50%', + transform: 'translate(-50%, -50%)', + } } + /> + + ); +} + function BoxAllControl( { placeholder, onSelect = noop, @@ -491,4 +643,36 @@ const DropdownIconWrapper = styled.div` margin-right: 8px; `; +const GridUI = styled.div` + position: relative; + height: 120px; + width: 200px; +`; + +const GridIndicator = styled.div` + height: 14px; + border-left: 1px dashed dodgerblue; + width: 0; + position: absolute; + + &::before { + content: ''; + position: absolute; + width: 7px; + height: 0; + border-top: 1px dashed dodgerblue; + top: 0; + left: -4px; + } + &::after { + content: ''; + position: absolute; + width: 7px; + height: 0; + border-top: 1px dashed dodgerblue; + bottom: 0; + left: -4px; + } +`; + BoxControl.__Visualizer = Visualizer; diff --git a/packages/components/src/number-control/index.js b/packages/components/src/number-control/index.js index 6cc57777fb7daa..0fa4adfbaeeac1 100644 --- a/packages/components/src/number-control/index.js +++ b/packages/components/src/number-control/index.js @@ -4,6 +4,8 @@ import { clamp, noop } from 'lodash'; import classNames from 'classnames'; import { useDrag } from 'react-use-gesture'; +import { css } from '@emotion/core'; +import styled from '@emotion/styled'; /** * WordPress dependencies @@ -14,7 +16,7 @@ import { UP, DOWN } from '@wordpress/keycodes'; /** * Internal dependencies */ -import { DRAG_CURSOR, useDragCursor, add } from './utils'; +import { DRAG_CURSOR, useDragCursor, add, roundClampString } from './utils'; export function NumberControl( { @@ -55,8 +57,13 @@ export function NumberControl( const distance = y * modifier * -1; if ( distance !== 0 ) { - const nextValue = clamp( add( valueProp, distance ), min, max ); - onChange( nextValue.toString(), { event } ); + const nextValue = roundClampString( + add( valueProp, distance ), + min, + max, + modifier + ); + onChange( nextValue, { event } ); } if ( ! isDragging ) { @@ -90,9 +97,14 @@ export function NumberControl( event.preventDefault(); nextValue = nextValue + incrementalValue; - nextValue = clamp( nextValue, min, max ); + nextValue = roundClampString( + nextValue, + min, + max, + incrementalValue + ); - onChange( nextValue.toString(), { event } ); + onChange( nextValue, { event } ); break; @@ -100,9 +112,14 @@ export function NumberControl( event.preventDefault(); nextValue = nextValue - incrementalValue; - nextValue = clamp( nextValue, min, max ); + nextValue = roundClampString( + nextValue, + min, + max, + incrementalValue + ); - onChange( nextValue.toString(), { event } ); + onChange( nextValue, { event } ); break; } @@ -120,11 +137,12 @@ export function NumberControl( }; return ( - { + if ( ! isDragging ) return ''; + + return css` + user-select: none; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none !important; + margin: 0 !important; + } + `; +}; + +const Input = styled.input` + ${dragStyles}; +`; + export default forwardRef( NumberControl ); diff --git a/packages/components/src/number-control/utils.js b/packages/components/src/number-control/utils.js index 0159091791f23d..ccffb649edfb0b 100644 --- a/packages/components/src/number-control/utils.js +++ b/packages/components/src/number-control/utils.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { clamp } from 'lodash'; + /** * WordPress dependencies */ @@ -25,3 +30,19 @@ function getValue( value ) { export function add( a, b ) { return getValue( a ) + getValue( b ); } + +export function roundClamp( + value = 0, + min = Infinity, + max = Infinity, + step = 1 +) { + const clampedValue = clamp( value, min, max ); + const rounded = Math.ceil( clampedValue / step ) * step; + + return rounded; +} + +export function roundClampString( ...args ) { + return roundClamp( ...args ).toString(); +} diff --git a/packages/components/src/unit-control/index.js b/packages/components/src/unit-control/index.js index 05a9f176e6f473..b82c55745c059c 100644 --- a/packages/components/src/unit-control/index.js +++ b/packages/components/src/unit-control/index.js @@ -19,6 +19,7 @@ import { CSS_UNITS } from './utils'; function UnitControl( { className, + disableUnits = false, isUnitSelectTabbable = true, isResetValueOnUnitChange = true, label, @@ -49,20 +50,23 @@ function UnitControl( - + { ! disableUnits && ( + + ) } ); } diff --git a/packages/components/src/unit-control/styles/unit-control-styles.js b/packages/components/src/unit-control/styles/unit-control-styles.js index 9d3f5c29191bf5..9d369fe1e80605 100644 --- a/packages/components/src/unit-control/styles/unit-control-styles.js +++ b/packages/components/src/unit-control/styles/unit-control-styles.js @@ -50,6 +50,26 @@ const sizeStyles = ( { size } ) => { return css( style ); }; +const paddingStyles = ( { disableUnits } ) => { + const value = disableUnits ? 3 : 20; + + return css` + ${rtl( { paddingRight: value } )()}; + `; +}; + +const arrowStyles = ( { disableUnits } ) => { + if ( disableUnits ) return ''; + + return css` + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + `; +}; + // TODO: Resolve need to use &&& to increase specificity // https://github.com/WordPress/gutenberg/issues/18483 @@ -64,15 +84,11 @@ export const ValueInput = styled( NumberControl )` display: block; width: 100%; - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - - ${rtl( { paddingRight: 20 } )} ${fontSizeStyles}; ${sizeStyles}; + + ${arrowStyles}; + ${paddingStyles}; } `; @@ -128,6 +144,8 @@ const unitLabelPaddingStyles = ( { size } ) => { export const UnitLabel = styled.div` &&& { + pointer-events: none; + ${baseUnitLabelStyles}; ${unitLabelPaddingStyles}; } From aa5b18bad32add61e4789fcb91cb19c73b8993fd Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 8 Apr 2020 12:44:16 -0400 Subject: [PATCH 015/103] Improve NumberControl drag interaction --- packages/components/src/box-control/index.js | 30 ++++++++++++------- .../components/src/number-control/index.js | 12 ++++++-- .../components/src/number-control/utils.js | 10 +++++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index 7796cf50d5a5b6..a2a04612077a08 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -118,17 +118,19 @@ export default function BoxControl( { ) } - { - handleOnSelect( next ); - setIcon( next ); - } } - onChange={ updateValues } - units={ units } - /> + + { + handleOnSelect( next ); + setIcon( next ); + } } + onChange={ updateValues } + units={ units } + /> + ); } @@ -643,6 +645,12 @@ const DropdownIconWrapper = styled.div` margin-right: 8px; `; +const LayoutContainer = styled( Flex )` + height: 120px; + justify-content: center; + padding-bottom: 16px; +`; + const GridUI = styled.div` position: relative; height: 120px; diff --git a/packages/components/src/number-control/index.js b/packages/components/src/number-control/index.js index 0fa4adfbaeeac1..38c6c6eca3dfd7 100644 --- a/packages/components/src/number-control/index.js +++ b/packages/components/src/number-control/index.js @@ -16,7 +16,13 @@ import { UP, DOWN } from '@wordpress/keycodes'; /** * Internal dependencies */ -import { DRAG_CURSOR, useDragCursor, add, roundClampString } from './utils'; +import { + DRAG_CURSOR, + useDragCursor, + add, + subtract, + roundClampString, +} from './utils'; export function NumberControl( { @@ -96,7 +102,7 @@ export function NumberControl( case UP: event.preventDefault(); - nextValue = nextValue + incrementalValue; + nextValue = add( nextValue, incrementalValue ); nextValue = roundClampString( nextValue, min, @@ -111,7 +117,7 @@ export function NumberControl( case DOWN: event.preventDefault(); - nextValue = nextValue - incrementalValue; + nextValue = subtract( nextValue, incrementalValue ); nextValue = roundClampString( nextValue, min, diff --git a/packages/components/src/number-control/utils.js b/packages/components/src/number-control/utils.js index ccffb649edfb0b..fdcbc079dcba71 100644 --- a/packages/components/src/number-control/utils.js +++ b/packages/components/src/number-control/utils.js @@ -24,6 +24,7 @@ export function useDragCursor( isDragging ) { function getValue( value ) { const number = Number( value ); + return isNaN( number ) ? 0 : number; } @@ -31,14 +32,19 @@ export function add( a, b ) { return getValue( a ) + getValue( b ); } +export function subtract( a, b ) { + return getValue( a ) - getValue( b ); +} + export function roundClamp( value = 0, min = Infinity, max = Infinity, step = 1 ) { - const clampedValue = clamp( value, min, max ); - const rounded = Math.ceil( clampedValue / step ) * step; + const clampedValue = clamp( getValue( value ), min, max ); + const stepValue = getValue( step ); + const rounded = Math.round( clampedValue / stepValue ) * stepValue; return rounded; } From 7687799869c3a38930df2d5e37284f621022bf8a Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 8 Apr 2020 16:12:05 -0400 Subject: [PATCH 016/103] Create InputControl primitive. Add floating labels. Refactor BoxControl --- packages/components/src/box-control/index.js | 640 +----------------- .../src/box-control/input-controls.js | 138 ++++ .../box-control/styles/box-control-styles.js | 54 ++ .../src/box-control/unit-control.js | 23 + packages/components/src/box-control/utils.js | 50 -- .../components/src/input-control/index.js | 139 ++++ .../src/input-control/stories/index.js | 49 ++ .../styles/input-control-styles.js | 240 +++++++ .../components/src/number-control/index.js | 9 +- .../src/number-control/stories/index.js | 3 +- packages/components/src/unit-control/index.js | 24 +- .../src/unit-control/stories/index.js | 3 +- .../styles/unit-control-styles.js | 39 -- packages/components/src/utils/style-mixins.js | 2 +- 14 files changed, 672 insertions(+), 741 deletions(-) create mode 100644 packages/components/src/box-control/input-controls.js create mode 100644 packages/components/src/box-control/styles/box-control-styles.js create mode 100644 packages/components/src/box-control/unit-control.js create mode 100644 packages/components/src/input-control/index.js create mode 100644 packages/components/src/input-control/stories/index.js create mode 100644 packages/components/src/input-control/styles/input-control-styles.js diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index a2a04612077a08..e0d485459902bd 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -2,55 +2,33 @@ * External dependencies */ import { noop } from 'lodash'; -// import { useRadioState, Radio, RadioGroup } from 'reakit/Radio'; -import styled from '@emotion/styled'; /** * WordPress dependencies */ import { useState } from '@wordpress/element'; -import { Icon, chevronDown } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -// import BaseButtonGroup from '../button-group'; -import DropdownMenu from '../dropdown-menu'; -import BoxControlIcon from './icon'; import Visualizer from './visualizer'; -import Tooltip from '../tooltip'; -import BaseUnitControl from '../unit-control'; -import { Flex, FlexBlock, FlexItem } from '../flex'; -import { - DEFAULT_VALUES, - TYPE_PROPS, - parseValues, - parseType, - getValues, -} from './utils'; +import { FlexItem } from '../flex'; +import InputControls from './input-controls'; +import { DEFAULT_VALUES, parseValues } from './utils'; +import { Root, Header, LayoutContainer } from './styles/box-control-styles'; const defaultInputProps = { min: 0, }; -const BoxControlComponent = { - all: BoxAllControl, - pairs: BoxPairsControl, - custom: BoxCustomControl, -}; - export default function BoxControl( { inputProps = defaultInputProps, onChange = noop, - onSelect = noop, label = __( 'Box Control' ), values: valuesProp = DEFAULT_VALUES, // Disable units for now units = false, } ) { - const [ type, setType ] = useState( parseType( valuesProp ) ); const [ values, setValues ] = useState( parseValues( valuesProp ) ); - const [ icon, setIcon ] = useState( parseType( valuesProp ) ); - const ControlComponent = BoxControlComponent[ type ]; const updateValues = ( nextValues ) => { const mergedValues = { ...values, ...nextValues }; @@ -58,75 +36,15 @@ export default function BoxControl( { onChange( mergedValues ); }; - const handleOnSelect = ( next ) => { - let nextSelect = [ next ]; - - switch ( next ) { - case 'all': - nextSelect = [ 'top', 'right', 'bottom', 'left' ]; - break; - case 'vertical': - nextSelect = [ 'top', 'bottom' ]; - break; - case 'horizontal': - nextSelect = [ 'right', 'left' ]; - break; - case 'pairs': - nextSelect = [ 'top', 'bottom' ]; - break; - case 'custom': - nextSelect = [ 'top' ]; - break; - } - - onSelect( nextSelect ); - }; - - const mixedLabel = __( 'Mixed' ); - return (
{ label }
- { false && ( - - - { - setType( next ); - handleOnSelect( next ); - } } - onSelect={ setIcon } - type={ type } - /> - - - { - handleOnSelect( next ); - setIcon( next ); - } } - onChange={ updateValues } - units={ units } - /> - - - ) } - { - handleOnSelect( next ); - setIcon( next ); - } } onChange={ updateValues } units={ units } /> @@ -135,552 +53,4 @@ export default function BoxControl( { ); } -function BoxUIControl( { onChange = noop, values, ...props } ) { - const { - top: [ top, unit ], - right: [ right ], - bottom: [ bottom ], - left: [ left ], - } = values; - - const allValues = getValues( values, 'top', 'right', 'bottom', 'left' ); - const isMixed = ! allValues.every( ( v ) => v === top ); - - const createHandleOnChange = ( side ) => ( next ) => { - onChange( { ...values, [ side ]: [ next, unit ] } ); - }; - - const baseStyles = { - position: 'absolute', - zIndex: 1, - maxWidth: 50, - }; - - return ( - - - - - - - - - - - { - onChange( { - top: [ next, unit ], - right: [ next, unit ], - bottom: [ next, unit ], - left: [ next, unit ], - } ); - } } - placeholder="All" - label="All" - size="small" - style={ { - ...baseStyles, - left: '50%', - top: '50%', - transform: 'translate(-50%, -50%)', - } } - /> - - ); -} - -function BoxAllControl( { - placeholder, - onSelect = noop, - onChange = noop, - values, - ...props -} ) { - const [ value, unit ] = values.top; - const allValues = getValues( values, 'top', 'right', 'bottom', 'left' ); - const isMixed = ! allValues.every( ( v ) => v === value ); - - const placeholderLabel = typeof value === 'number' ? placeholder : null; - - return ( - - - onSelect( 'all' ) } - onChange={ ( next ) => { - onChange( { - top: [ next, unit ], - right: [ next, unit ], - bottom: [ next, unit ], - left: [ next, unit ], - } ); - } } - onUnitChange={ ( next ) => { - onChange( { - top: [ value, next ], - right: [ value, next ], - bottom: [ value, next ], - left: [ value, next ], - } ); - } } - /> - - - ); -} - -function BoxPairsControl( { - placeholder, - onChange = noop, - onSelect = noop, - values, - ...props -} ) { - const [ vertical, verticalUnit ] = values.top; - const [ horizontal, horizontalUnit ] = values.left; - - const isVerticalMixed = ! getValues( values, 'top', 'bottom' ).every( - ( v ) => v === vertical - ); - const isHorizontalMixed = ! getValues( values, 'left', 'right' ).every( - ( v ) => v === horizontal - ); - - const verticalPlaceholder = - typeof vertical === 'number' ? placeholder : null; - const horizontalPlaceholder = - typeof horizontal === 'number' ? placeholder : null; - - const labels = { - vertical: __( 'Top/Bottom' ), - horizontal: __( 'Left/Right' ), - }; - - const handleOnSelect = ( next ) => { - onSelect( next ); - }; - - return ( - - handleOnSelect( 'vertical' ) } - onChange={ ( next ) => { - onChange( { - top: [ next, verticalUnit ], - bottom: [ next, verticalUnit ], - } ); - } } - onUnitChange={ ( next ) => { - onChange( { - top: [ vertical, next ], - bottom: [ vertical, next ], - } ); - } } - value={ isVerticalMixed ? '' : vertical } - unit={ verticalUnit } - /> - handleOnSelect( 'horizontal' ) } - onChange={ ( next ) => { - onChange( { - left: [ next, horizontalUnit ], - right: [ next, horizontalUnit ], - } ); - } } - onUnitChange={ ( next ) => { - onChange( { - left: [ horizontal, next ], - right: [ horizontal, next ], - } ); - } } - value={ isHorizontalMixed ? '' : horizontal } - unit={ horizontalUnit } - /> - - ); -} - -function BoxCustomControl( { - onSelect = noop, - onChange = noop, - values, - ...props -} ) { - const unitControlProps = useCustomUnitControlProps( { - values, - onChange, - } ); - - const labels = { - top: __( 'Top' ), - right: __( 'Right' ), - bottom: __( 'Bottom' ), - left: __( 'Left' ), - }; - - const handleOnSelect = ( next ) => { - onSelect( next ); - }; - - return ( -
- - - handleOnSelect( 'top' ) } - placeholder="" - /> - - - handleOnSelect( 'right' ) } - placeholder="" - /> - - - - - handleOnSelect( 'bottom' ) } - placeholder="" - /> - - - handleOnSelect( 'left' ) } - placeholder="" - /> - - -
- ); -} - -function ControlContainer( { children, ...props } ) { - return ( - - { children } - - ); -} - -function useCustomUnitControlProps( { values, onChange = noop } ) { - const valueKeys = Object.keys( values ); - const props = {}; - - valueKeys.forEach( ( key ) => { - const [ value, unit ] = values[ key ]; - - const handleOnChange = ( next ) => { - onChange( { [ key ]: [ next, unit ] } ); - }; - - const handleOnUnitChange = ( next ) => { - onChange( { [ key ]: [ value, next ] } ); - }; - - props[ key ] = { - value, - unit, - onChange: handleOnChange, - onUnitChange: handleOnUnitChange, - }; - } ); - - return props; -} - -function BoxTypeDropdown( { onChange = noop, onSelect = noop, icon, type } ) { - const icons = { - all: , - pairs: , - custom: , - vertical: , - horizontal: , - top: , - right: , - bottom: , - left: , - }; - - const handleOnChange = ( next ) => { - onChange( next ); - onSelect( next ); - }; - - const options = [ - { - title: TYPE_PROPS.all.label, - value: 'all', - icon: { icons.all }, - onClick: () => handleOnChange( 'all' ), - isActive: type === 'all', - }, - { - title: TYPE_PROPS.pairs.label, - value: 'pairs', - icon: { icons.pairs }, - onClick: () => handleOnChange( 'pairs' ), - isActive: type === 'pairs', - }, - { - title: TYPE_PROPS.custom.label, - value: 'custom', - icon: { icons.custom }, - onClick: () => handleOnChange( 'custom' ), - isActive: type === 'custom', - }, - ]; - - const dropdownIcon = ( - - { icons[ icon ] } - - - ); - - const toggleProps = { - children: dropdownIcon, - isSmall: true, - style: { - height: 30, - lineHeight: 28, - }, - }; - - return ( - - ); -} - -// function BoxTypeControl( { -// label = 'Box Control', -// onChange = noop, -// type = 'all', -// } ) { -// const radio = useRadioState( { state: type } ); - -// useEffect( () => { -// onChange( radio.state ); -// }, [ radio.state ] ); - -// return ( -// -// { types.map( ( value ) => { -// const valueProps = TYPE_PROPS[ value ]; -// const isSelected = radio.state === value; - -// return ( -// -// -// -// -// -// ); -// } ) } -// -// ); -// } - -function UnitControl( { onChange, label, ...props } ) { - const handleOnChange = ( nextValue ) => { - const value = parseFloat( nextValue ); - onChange( isNaN( value ) ? nextValue : value ); - }; - - return ( - - - - - - ); -} - -const Root = styled.div` - max-width: 280px; -`; - -const Header = styled( Flex )` - margin-bottom: 8px; -`; - -const UnitControlWrapper = styled.div` - box-sizing: border-box; - max-width: 75px; -`; - -// const ButtonGroup = styled( BaseButtonGroup )` -// display: flex; -// margin: 0; -// `; - -const DropdownButton = styled( Flex )` - margin: 0 -4px; -`; - -const DropdownIconWrapper = styled.div` - margin-right: 8px; -`; - -const LayoutContainer = styled( Flex )` - height: 120px; - justify-content: center; - padding-bottom: 16px; -`; - -const GridUI = styled.div` - position: relative; - height: 120px; - width: 200px; -`; - -const GridIndicator = styled.div` - height: 14px; - border-left: 1px dashed dodgerblue; - width: 0; - position: absolute; - - &::before { - content: ''; - position: absolute; - width: 7px; - height: 0; - border-top: 1px dashed dodgerblue; - top: 0; - left: -4px; - } - &::after { - content: ''; - position: absolute; - width: 7px; - height: 0; - border-top: 1px dashed dodgerblue; - bottom: 0; - left: -4px; - } -`; - BoxControl.__Visualizer = Visualizer; diff --git a/packages/components/src/box-control/input-controls.js b/packages/components/src/box-control/input-controls.js new file mode 100644 index 00000000000000..d7bcd6cd49cf8c --- /dev/null +++ b/packages/components/src/box-control/input-controls.js @@ -0,0 +1,138 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * Internal dependencies + */ +import UnitControl from './unit-control'; +import { getValues } from './utils'; +import { Layout, LayoutBox, SideIndicator } from './styles/box-control-styles'; +import { useRtl } from '../utils/style-mixins'; + +export default function BoxInputControls( { + onChange = noop, + values, + ...props +} ) { + const { + top: [ top, unit ], + right: [ right ], + bottom: [ bottom ], + left: [ left ], + } = values; + + const allValues = getValues( values, 'top', 'right', 'bottom', 'left' ); + const isMixed = ! allValues.every( ( v ) => v === top ); + const isRtl = useRtl(); + + const createHandleOnChange = ( side ) => ( next ) => { + onChange( { ...values, [ side ]: [ next, unit ] } ); + }; + + const baseStyles = { + position: 'absolute', + zIndex: 1, + maxWidth: 60, + }; + + return ( + + + + + + + + + { + onChange( { + top: [ next, unit ], + right: [ next, unit ], + bottom: [ next, unit ], + left: [ next, unit ], + } ); + } } + label="All" + size="small" + style={ { + ...baseStyles, + left: '50%', + top: '50%', + transform: 'translate(-50%, calc(-50% - 3px))', + } } + /> + + ); +} diff --git a/packages/components/src/box-control/styles/box-control-styles.js b/packages/components/src/box-control/styles/box-control-styles.js new file mode 100644 index 00000000000000..0737c61f2034e5 --- /dev/null +++ b/packages/components/src/box-control/styles/box-control-styles.js @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; +/** + * Internal dependencies + */ +import { Flex } from '../../flex'; +import { color } from '../../utils/style-mixins'; + +export const Root = styled.div` + max-width: 280px; +`; + +export const Header = styled( Flex )` + margin-bottom: 8px; +`; + +export const UnitControlWrapper = styled.div` + box-sizing: border-box; + max-width: 75px; +`; + +export const LayoutContainer = styled( Flex )` + height: 120px; + justify-content: center; + padding-bottom: 16px; +`; + +export const Layout = styled.div` + box-sizing: border-box; + position: relative; + height: 120px; + width: 200px; +`; + +export const LayoutBox = styled.div` + position: absolute; + box-sizing: border-box; + top: 12px; + bottom: 12px; + left: 30px; + right: 30px; + pointer-events: none; + border: 1px solid ${color( 'ui.borderLight' )}; +`; + +export const SideIndicator = styled.div` + height: 50%; + border-left: 1px dashed ${color( 'ui.borderLight' )}; + width: 0; + position: absolute; + pointer-events: none; +`; diff --git a/packages/components/src/box-control/unit-control.js b/packages/components/src/box-control/unit-control.js new file mode 100644 index 00000000000000..c15371c01d24dd --- /dev/null +++ b/packages/components/src/box-control/unit-control.js @@ -0,0 +1,23 @@ +/** + * Internal dependencies + */ +import BaseUnitControl from '../unit-control'; +import { UnitControlWrapper } from './styles/box-control-styles'; + +export default function BoxUnitControl( { onChange, label, ...props } ) { + const handleOnChange = ( nextValue ) => { + const value = parseFloat( nextValue ); + onChange( isNaN( value ) ? nextValue : value ); + }; + + return ( + + + + ); +} diff --git a/packages/components/src/box-control/utils.js b/packages/components/src/box-control/utils.js index 5c09be501f12f2..b1b57574b6a870 100644 --- a/packages/components/src/box-control/utils.js +++ b/packages/components/src/box-control/utils.js @@ -1,25 +1,5 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; - export const TYPES = [ 'all', 'pairs', 'custom' ]; -export const TYPE_PROPS = { - all: { - sides: [ 'all' ], - label: __( 'All sides' ), - }, - pairs: { - sides: [ 'top', 'bottom' ], - label: __( 'Pair of sides' ), - }, - custom: { - sides: [ 'top' ], - label: __( 'Individual sides' ), - }, -}; - export const DEFAULT_VALUES = { top: [ 0, 'px' ], right: [ 0, 'px' ], @@ -27,36 +7,6 @@ export const DEFAULT_VALUES = { left: [ 0, 'px' ], }; -export const parseType = ( values = {} ) => { - const { - top: [ top ], - bottom: [ bottom ], - left: [ left ], - right: [ right ], - } = parseValues( values ); - - const isAll = [ top, bottom, left, right ].every( - ( value ) => value === top - ); - - if ( isAll ) { - return 'all'; - } - - const isVerticalMatch = [ top, bottom ].every( ( value ) => value === top ); - const isHorizontalMatch = [ left, right ].every( - ( value ) => value === left - ); - - const isPairs = isVerticalMatch && isHorizontalMatch; - - if ( isPairs ) { - return 'pairs'; - } - - return 'custom'; -}; - export function parseValues( values = {} ) { const nextValueProps = {}; diff --git a/packages/components/src/input-control/index.js b/packages/components/src/input-control/index.js new file mode 100644 index 00000000000000..f0c6a8b7105424 --- /dev/null +++ b/packages/components/src/input-control/index.js @@ -0,0 +1,139 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; +import classNames from 'classnames'; + +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; +import { useEffect, useRef, useState, forwardRef } from '@wordpress/element'; +/** + * Internal dependencies + */ +import { + Container, + Fieldset, + Input, + Label, + Legend, + LegendText, + Root, +} from './styles/input-control-styles'; + +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 useUniqueId( idProp ) { + const instanceId = useInstanceId( InputControl ); + const id = `inspector-input-control-${ instanceId }`; + + return idProp || id; +} + +function isEmpty( value ) { + const isNullish = typeof value === 'undefined' || value === null; + const isEmptyString = value === ''; + + return isNullish || isEmptyString; +} + +export function InputControl( + { + children, + className, + onBlur = noop, + onChange = noop, + onFocus = noop, + id: idProp, + isFloatingLabel = true, + label, + size = 'default', + value: valueProp, + ...props + }, + ref +) { + const [ isFocused, setIsFocused ] = useState( false ); + const [ value, setValue ] = useControlledState( valueProp ); + const id = useUniqueId( idProp ); + const classes = classNames( 'component-input-control', className ); + + const handleOnBlur = ( event ) => { + onBlur( event ); + setIsFocused( false ); + }; + + const handleOnFocus = ( event ) => { + onFocus( event ); + setIsFocused( true ); + }; + + const handleOnChange = ( event ) => { + const nextValue = event.target.value; + + onChange( nextValue, { event } ); + setValue( nextValue ); + }; + + const isFilled = ! isEmpty( value ); + const isFloating = isFloatingLabel ? isFilled || isFocused : false; + const isFloatingLabelSet = isFloatingLabel && label; + + return ( + + { label && ( + + ) } + + +
+ { isFloatingLabelSet && ( + + { label } + + ) } +
+ { children } +
+
+ ); +} + +export default forwardRef( InputControl ); diff --git a/packages/components/src/input-control/stories/index.js b/packages/components/src/input-control/stories/index.js new file mode 100644 index 00000000000000..18959aab149c74 --- /dev/null +++ b/packages/components/src/input-control/stories/index.js @@ -0,0 +1,49 @@ +/** + * External dependencies + */ +import { boolean, select, text } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import InputControl from '../'; + +export default { + title: 'Components/InputControl', + component: InputControl, +}; + +function Example() { + const [ value, setValue ] = useState( '' ); + + const props = { + isFloatingLabel: boolean( 'isFloatingLabel', false ), + label: text( 'label', 'Value' ), + placeholder: text( 'placeholder', 'Placeholder' ), + size: select( + 'size', + { + default: 'default', + small: 'small', + }, + 'default' + ), + }; + + return ( + setValue( v ) } + /> + ); +} + +export const _default = () => { + return ; +}; diff --git a/packages/components/src/input-control/styles/input-control-styles.js b/packages/components/src/input-control/styles/input-control-styles.js new file mode 100644 index 00000000000000..7954b6fd694448 --- /dev/null +++ b/packages/components/src/input-control/styles/input-control-styles.js @@ -0,0 +1,240 @@ +/** + * External dependencies + */ +import { css } from '@emotion/core'; +import styled from '@emotion/styled'; + +/** + * Internal dependencies + */ +import { color, rtl, reduceMotion } from '../../utils/style-mixins'; + +const rootFloatLabelStyles = ( { isFloatingLabel } ) => { + const paddingTop = isFloatingLabel ? 5 : 0; + return css( { paddingTop } ); +}; + +export const Root = styled.div` + box-sizing: border-box; + position: relative; + border-radius: 2px; + ${rootFloatLabelStyles}; +`; + +const containerBorder = ( { isFocused } ) => { + const borderRadius = isFocused ? 3 : 2; + + return css( { borderRadius } ); +}; + +export const Container = styled.div` + box-sizing: border-box; + position: relative; + ${containerBorder}; +`; + +const fontSizeStyles = ( { size } ) => { + const sizes = { + default: '13px', + small: '11px', + }; + + const fontSize = sizes[ size ]; + const fontSizeMobile = '16px'; + + if ( ! fontSize ) return ''; + + return css` + font-size: ${fontSizeMobile}; + + @media ( min-width: 600px ) { + font-size: ${fontSize}; + } + `; +}; + +const sizeStyles = ( { size } ) => { + const sizes = { + default: { + height: 30, + lineHeight: 30, + minHeight: 30, + }, + small: { + height: 24, + lineHeight: 24, + minHeight: 24, + }, + }; + + const style = sizes[ size ] || sizes.default; + + return css( style ); +}; + +const placeholderStyles = ( { isFilled, isFloating, isFloatingLabel } ) => { + let opacity = 1; + + if ( isFloatingLabel ) { + if ( ! isFilled && ! isFloating ) { + opacity = 0; + } + } + + return css` + &::placeholder { + opacity: ${opacity}; + } + `; +}; + +// TODO: Resolve need to use &&& to increase specificity +// https://github.com/WordPress/gutenberg/issues/18483 + +export const Input = styled.input` + &&& { + box-sizing: border-box; + border: none !important; + box-shadow: none !important; + display: block; + outline: none; + padding-left: 8px !important; + padding-right: 8px !important; + width: 100%; + + ${fontSizeStyles}; + ${sizeStyles}; + + ${placeholderStyles}; + } +`; + +const laberColor = ( { isFloatingLabel, isFilled, isFloating } ) => { + const isPlaceholder = isFloatingLabel && ! isFilled; + const textColor = + isPlaceholder || isFloating ? color( 'lightGray.900' ) : 'currentColor'; + + return css( { color: textColor } ); +}; + +const labelFontSize = ( { size } ) => { + const sizes = { + default: '13px', + small: '11px', + }; + const fontSize = sizes[ size ]; + + return css( { fontSize } ); +}; + +const labelPosition = ( { isFloatingLabel, isFloating, size } ) => { + const paddingBottom = isFloatingLabel ? 0 : 4; + const position = isFloatingLabel ? 'absolute' : null; + const pointerEvents = isFloating ? null : 'none'; + + const marginTop = isFloating ? 0 : 2; + const offset = size === 'small' ? '-2px' : '-5px'; + + let transform = isFloating + ? `translate( 0, calc(-100% + ${ offset }) ) scale( 0.75 )` + : 'translate( 0, -50%) scale(1)'; + + if ( ! isFloatingLabel ) { + transform = null; + } + + const transition = isFloatingLabel ? 'transform 50ms linear' : null; + + return css( + { + marginTop, + paddingBottom, + position, + pointerEvents, + transition, + transform, + }, + rtl( { marginLeft: 8 } )(), + rtl( + { transformOrigin: 'top left' }, + { transformOrigin: 'top right' } + )() + ); +}; + +export const Label = styled.label` + box-sizing: border-box; + display: block; + top: 50%; + transition: transform 50ms linear; + z-index: 1; + + ${laberColor}; + ${labelFontSize}; + ${labelPosition}; + ${reduceMotion( 'transition' )}; + + ${rtl( { left: 0 } )} +`; + +const fieldsetTopStyles = ( { isFloatingLabel } ) => { + const top = isFloatingLabel ? -5 : 0; + return css( { top } ); +}; + +const fieldsetFocusedStyles = ( { isFocused } ) => { + const borderColor = isFocused + ? color( 'ui.borderFocus' ) + : color( 'ui.border' ); + + const borderWidth = isFocused ? 2 : 1; + + return css( { borderColor, borderWidth } ); +}; + +export const Fieldset = styled.fieldset` + box-sizing: border-box; + border-color: black; + border-radius: inherit; + border-style: solid; + border-width: 1px; + bottom: 0; + left: 0; + margin: 0; + padding: 0; + pointer-events: none; + position: absolute; + right: 0; + + ${fieldsetFocusedStyles}; + ${fieldsetTopStyles}; + ${rtl( { paddingLeft: 2 } )} +`; + +const legendWidth = ( { isFloating } ) => { + const maxWidth = isFloating ? 1000 : 0.01; + return css( { + maxWidth, + } ); +}; + +export const Legend = styled.legend` + box-sizing: border-box; + width: auto; + height: 11px; + display: block; + padding: 0; + font-size: 0.75em; + transition: max-width 50ms linear; + visibility: hidden; + + ${legendWidth}; + ${reduceMotion( 'transition' )}; +`; + +export const LegendText = styled.span` + box-sizing: border-box; + display: inline-block; + padding-left: 4px; + padding-right: 4px; +`; diff --git a/packages/components/src/number-control/index.js b/packages/components/src/number-control/index.js index 38c6c6eca3dfd7..f2433e22ec4f0d 100644 --- a/packages/components/src/number-control/index.js +++ b/packages/components/src/number-control/index.js @@ -16,6 +16,7 @@ import { UP, DOWN } from '@wordpress/keycodes'; /** * Internal dependencies */ +import InputControl from '../input-control'; import { DRAG_CURSOR, useDragCursor, @@ -27,6 +28,7 @@ import { export function NumberControl( { className, + label, dragAxis = 'y', dragThreshold = 10, isDragEnabled = true, @@ -131,8 +133,8 @@ export function NumberControl( } }; - const handleOnChange = ( event ) => { - onChange( event.target.value, { event } ); + const handleOnChange = ( value, { event } ) => { + onChange( value, { event } ); }; const classes = classNames( 'component-number-control', className ); @@ -148,6 +150,7 @@ export function NumberControl( { ...props } { ...dragGestureProps() } className={ classes } + label={ label } isDragging={ isDragging } ref={ ref } onChange={ handleOnChange } @@ -173,7 +176,7 @@ const dragStyles = ( { isDragging } ) => { `; }; -const Input = styled.input` +const Input = styled( InputControl )` ${dragStyles}; `; diff --git a/packages/components/src/number-control/stories/index.js b/packages/components/src/number-control/stories/index.js index 859afe27fc867c..c7b0589568b7fa 100644 --- a/packages/components/src/number-control/stories/index.js +++ b/packages/components/src/number-control/stories/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { boolean, number } from '@storybook/addon-knobs'; +import { boolean, number, text } from '@storybook/addon-knobs'; /** * WordPress dependencies @@ -22,6 +22,7 @@ function Example() { const [ value, setValue ] = useState( '' ); const props = { + label: text( 'label', 'Number' ), isShiftStepEnabled: boolean( 'isShiftStepEnabled', true ), shiftStep: number( 'shiftStep', 10 ), step: number( 'step', 1 ), diff --git a/packages/components/src/unit-control/index.js b/packages/components/src/unit-control/index.js index b82c55745c059c..dbf15476dfc5d3 100644 --- a/packages/components/src/unit-control/index.js +++ b/packages/components/src/unit-control/index.js @@ -52,21 +52,23 @@ function UnitControl( { ...props } disableUnits={ disableUnits } className="component-unit-control__input" + label={ label } value={ value } onChange={ onChange } size={ size } type="number" - /> - { ! disableUnits && ( - - ) } + > + { ! disableUnits && ( + + ) } +
); } diff --git a/packages/components/src/unit-control/stories/index.js b/packages/components/src/unit-control/stories/index.js index 2aab99180fd39c..f95f7641ab0389 100644 --- a/packages/components/src/unit-control/stories/index.js +++ b/packages/components/src/unit-control/stories/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { boolean, number, select } from '@storybook/addon-knobs'; +import { boolean, number, select, text } from '@storybook/addon-knobs'; import styled from '@emotion/styled'; /** @@ -26,6 +26,7 @@ function Example() { const props = { isShiftStepEnabled: boolean( 'isShiftStepEnabled', true ), isUnitSelectTabbable: boolean( 'isUnitSelectTabbable', true ), + label: text( 'label', 'Value' ), shiftStep: number( 'shiftStep', 10 ), max: number( 'max', 100 ), min: number( 'min', 0 ), diff --git a/packages/components/src/unit-control/styles/unit-control-styles.js b/packages/components/src/unit-control/styles/unit-control-styles.js index 9d369fe1e80605..71870b0961416a 100644 --- a/packages/components/src/unit-control/styles/unit-control-styles.js +++ b/packages/components/src/unit-control/styles/unit-control-styles.js @@ -14,42 +14,6 @@ export const Root = styled.div` position: relative; `; -const fontSizeStyles = ( { size } ) => { - const sizes = { - default: null, - small: '11px', - }; - - const fontSize = sizes[ size ]; - - if ( ! fontSize ) return ''; - - return css` - @media ( min-width: 600px ) { - font-size: ${fontSize}; - } - `; -}; - -const sizeStyles = ( { size } ) => { - const sizes = { - default: { - height: 30, - lineHeight: 30, - minHeight: 30, - }, - small: { - height: 24, - lineHeight: 24, - minHeight: 24, - }, - }; - - const style = sizes[ size ] || sizes.default; - - return css( style ); -}; - const paddingStyles = ( { disableUnits } ) => { const value = disableUnits ? 3 : 20; @@ -84,9 +48,6 @@ export const ValueInput = styled( NumberControl )` display: block; width: 100%; - ${fontSizeStyles}; - ${sizeStyles}; - ${arrowStyles}; ${paddingStyles}; } diff --git a/packages/components/src/utils/style-mixins.js b/packages/components/src/utils/style-mixins.js index 26344df68a9d95..0ce36cca9384a4 100644 --- a/packages/components/src/utils/style-mixins.js +++ b/packages/components/src/utils/style-mixins.js @@ -1,3 +1,3 @@ export { color, rgba } from './colors'; export { reduceMotion } from './reduce-motion'; -export { rtl } from './rtl'; +export { rtl, useRtl } from './rtl'; From 21efbac1e642931bcf9a88dd560296803ee585e1 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 8 Apr 2020 16:20:25 -0400 Subject: [PATCH 017/103] Adjust UI for BoxControl + InputControl labels --- .../components/src/box-control/styles/box-control-styles.js | 2 +- .../src/input-control/styles/input-control-styles.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/components/src/box-control/styles/box-control-styles.js b/packages/components/src/box-control/styles/box-control-styles.js index 0737c61f2034e5..81af7db0f76bf7 100644 --- a/packages/components/src/box-control/styles/box-control-styles.js +++ b/packages/components/src/box-control/styles/box-control-styles.js @@ -9,7 +9,7 @@ import { Flex } from '../../flex'; import { color } from '../../utils/style-mixins'; export const Root = styled.div` - max-width: 280px; + max-width: 300px; `; export const Header = styled( Flex )` diff --git a/packages/components/src/input-control/styles/input-control-styles.js b/packages/components/src/input-control/styles/input-control-styles.js index 7954b6fd694448..fb89a259e154c0 100644 --- a/packages/components/src/input-control/styles/input-control-styles.js +++ b/packages/components/src/input-control/styles/input-control-styles.js @@ -117,14 +117,15 @@ const laberColor = ( { isFloatingLabel, isFilled, isFloating } ) => { return css( { color: textColor } ); }; -const labelFontSize = ( { size } ) => { +const labelFontSize = ( { isFloatingLabel, size } ) => { const sizes = { default: '13px', small: '11px', }; const fontSize = sizes[ size ]; + const lineHeight = isFloatingLabel ? 1 : null; - return css( { fontSize } ); + return css( { fontSize, lineHeight } ); }; const labelPosition = ( { isFloatingLabel, isFloating, size } ) => { From cd8c007504b1f695d7c0c341e87abd3f678fe158 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 8 Apr 2020 16:59:03 -0400 Subject: [PATCH 018/103] Fix tests --- packages/components/src/index.js | 1 + .../components/src/input-control/index.js | 24 +----- .../components/src/input-control/utils.js | 25 +++++++ .../src/number-control/test/index.js | 74 ++++++++++++++----- .../components/src/number-control/utils.js | 7 +- packages/components/src/unit-control/index.js | 2 +- 6 files changed, 87 insertions(+), 46 deletions(-) create mode 100644 packages/components/src/input-control/utils.js diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 4410c5e4b2256f..a99e2f7d1e0f7a 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -59,6 +59,7 @@ export { default as Guide } from './guide'; export { default as GuidePage } from './guide/page'; export { default as Icon } from './icon'; export { default as IconButton } from './button/deprecated'; +export { default as __experimentalInputControl } from './input-control'; export { default as KeyboardShortcuts } from './keyboard-shortcuts'; export { default as MenuGroup } from './menu-group'; export { default as MenuItem } from './menu-item'; diff --git a/packages/components/src/input-control/index.js b/packages/components/src/input-control/index.js index f0c6a8b7105424..cd1c65b3202f44 100644 --- a/packages/components/src/input-control/index.js +++ b/packages/components/src/input-control/index.js @@ -8,7 +8,7 @@ import classNames from 'classnames'; * WordPress dependencies */ import { useInstanceId } from '@wordpress/compose'; -import { useEffect, useRef, useState, forwardRef } from '@wordpress/element'; +import { useState, forwardRef } from '@wordpress/element'; /** * Internal dependencies */ @@ -21,20 +21,7 @@ import { LegendText, Root, } from './styles/input-control-styles'; - -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 ]; -} +import { useControlledState, isEmpty } from './utils'; function useUniqueId( idProp ) { const instanceId = useInstanceId( InputControl ); @@ -43,13 +30,6 @@ function useUniqueId( idProp ) { return idProp || id; } -function isEmpty( value ) { - const isNullish = typeof value === 'undefined' || value === null; - const isEmptyString = value === ''; - - return isNullish || isEmptyString; -} - export function InputControl( { children, diff --git a/packages/components/src/input-control/utils.js b/packages/components/src/input-control/utils.js new file mode 100644 index 00000000000000..db2f75f5f7f3db --- /dev/null +++ b/packages/components/src/input-control/utils.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { useEffect, useRef, useState } from '@wordpress/element'; + +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 ]; +} + +export function isEmpty( value ) { + const isNullish = typeof value === 'undefined' || value === null; + const isEmptyString = value === ''; + + return isNullish || isEmptyString; +} diff --git a/packages/components/src/number-control/test/index.js b/packages/components/src/number-control/test/index.js index c946ba12aeb048..182ae932507fcd 100644 --- a/packages/components/src/number-control/test/index.js +++ b/packages/components/src/number-control/test/index.js @@ -81,7 +81,10 @@ describe( 'NumberControl', () => { const input = getInput(); input.value = 10; - Simulate.change( input ); + + act( () => { + Simulate.change( input ); + } ); expect( spy.mock.calls[ 0 ][ 0 ] ).toBe( '10' ); } ); @@ -99,7 +102,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: UP } ); + act( () => { + Simulate.keyDown( input, { keyCode: UP } ); + } ); expect( spy ).toHaveBeenCalled(); } ); @@ -111,7 +116,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: UP } ); + act( () => { + Simulate.keyDown( input, { keyCode: UP } ); + } ); expect( input.value ).toBe( '6' ); } ); @@ -123,21 +130,28 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: UP } ); + act( () => { + Simulate.keyDown( input, { keyCode: UP } ); + } ); expect( input.value ).toBe( '-4' ); } ); it( 'should increment by shiftStep on key UP + shift press', () => { act( () => { - render( , container ); + render( + , + container + ); } ); const input = getInput(); - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); + act( () => { + Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); + } ); - expect( input.value ).toBe( '15' ); + expect( input.value ).toBe( '20' ); } ); it( 'should increment by custom shiftStep on key UP + shift press', () => { @@ -150,9 +164,11 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); + act( () => { + Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); + } ); - expect( input.value ).toBe( '105' ); + expect( input.value ).toBe( '100' ); } ); it( 'should increment but be limited by max on shiftStep', () => { @@ -169,7 +185,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); + act( () => { + Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); + } ); expect( input.value ).toBe( '99' ); } ); @@ -188,7 +206,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); + act( () => { + Simulate.keyDown( input, { keyCode: UP, shiftKey: true } ); + } ); expect( input.value ).toBe( '6' ); } ); @@ -206,7 +226,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: DOWN } ); + act( () => { + Simulate.keyDown( input, { keyCode: DOWN } ); + } ); expect( spy ).toHaveBeenCalled(); } ); @@ -218,7 +240,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: DOWN } ); + act( () => { + Simulate.keyDown( input, { keyCode: DOWN } ); + } ); expect( input.value ).toBe( '4' ); } ); @@ -230,7 +254,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: DOWN } ); + act( () => { + Simulate.keyDown( input, { keyCode: DOWN } ); + } ); expect( input.value ).toBe( '-6' ); } ); @@ -242,9 +268,11 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); + act( () => { + Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); + } ); - expect( input.value ).toBe( '-5' ); + expect( input.value ).toBe( '0' ); } ); it( 'should decrement by custom shiftStep on key DOWN + shift press', () => { @@ -257,9 +285,11 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); + act( () => { + Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); + } ); - expect( input.value ).toBe( '-95' ); + expect( input.value ).toBe( '-100' ); } ); it( 'should decrement but be limited by min on shiftStep', () => { @@ -276,7 +306,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); + act( () => { + Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); + } ); expect( input.value ).toBe( '4' ); } ); @@ -295,7 +327,9 @@ describe( 'NumberControl', () => { const input = getInput(); - Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); + act( () => { + Simulate.keyDown( input, { keyCode: DOWN, shiftKey: true } ); + } ); expect( input.value ).toBe( '4' ); } ); diff --git a/packages/components/src/number-control/utils.js b/packages/components/src/number-control/utils.js index fdcbc079dcba71..be839f1ec3f2d6 100644 --- a/packages/components/src/number-control/utils.js +++ b/packages/components/src/number-control/utils.js @@ -42,11 +42,12 @@ export function roundClamp( max = Infinity, step = 1 ) { - const clampedValue = clamp( getValue( value ), min, max ); + const baseValue = getValue( value ); const stepValue = getValue( step ); - const rounded = Math.round( clampedValue / stepValue ) * stepValue; + const rounded = Math.round( baseValue / stepValue ) * stepValue; + const clampedValue = clamp( rounded, min, max ); - return rounded; + return clampedValue; } export function roundClampString( ...args ) { diff --git a/packages/components/src/unit-control/index.js b/packages/components/src/unit-control/index.js index dbf15476dfc5d3..a614919a888aff 100644 --- a/packages/components/src/unit-control/index.js +++ b/packages/components/src/unit-control/index.js @@ -38,7 +38,7 @@ function UnitControl( const { data } = changeProps; onUnitChange( unitValue, changeProps ); - if ( isResetValueOnUnitChange && ! data.default === undefined ) { + if ( isResetValueOnUnitChange && data.default !== undefined ) { onChange( data.default, changeProps ); } }; From 0acb9eec3aec2412a5d7b0220fa323addb9155c0 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 8 Apr 2020 19:33:09 -0400 Subject: [PATCH 019/103] Increase CSS scope. Minor label UI adjustments. --- .../components/src/input-control/index.js | 2 +- .../styles/input-control-styles.js | 105 ++++++++++-------- 2 files changed, 62 insertions(+), 45 deletions(-) diff --git a/packages/components/src/input-control/index.js b/packages/components/src/input-control/index.js index cd1c65b3202f44..cd53d6f0d941ef 100644 --- a/packages/components/src/input-control/index.js +++ b/packages/components/src/input-control/index.js @@ -105,7 +105,7 @@ export function InputControl( isFocused={ isFocused } > { isFloatingLabelSet && ( - + { label } ) } diff --git a/packages/components/src/input-control/styles/input-control-styles.js b/packages/components/src/input-control/styles/input-control-styles.js index fb89a259e154c0..18835ffe9dada9 100644 --- a/packages/components/src/input-control/styles/input-control-styles.js +++ b/packages/components/src/input-control/styles/input-control-styles.js @@ -9,6 +9,8 @@ import styled from '@emotion/styled'; */ import { color, rtl, reduceMotion } from '../../utils/style-mixins'; +const FLOATING_LABEL_TRANSITION_SPEED = '60ms'; + const rootFloatLabelStyles = ( { isFloatingLabel } ) => { const paddingTop = isFloatingLabel ? 5 : 0; return css( { paddingTop } ); @@ -133,8 +135,8 @@ const labelPosition = ( { isFloatingLabel, isFloating, size } ) => { const position = isFloatingLabel ? 'absolute' : null; const pointerEvents = isFloating ? null : 'none'; - const marginTop = isFloating ? 0 : 2; - const offset = size === 'small' ? '-2px' : '-5px'; + const marginTop = isFloating ? 0 : 1; + const offset = size === 'small' ? '-3px' : '-5px'; let transform = isFloating ? `translate( 0, calc(-100% + ${ offset }) ) scale( 0.75 )` @@ -144,7 +146,9 @@ const labelPosition = ( { isFloatingLabel, isFloating, size } ) => { transform = null; } - const transition = isFloatingLabel ? 'transform 50ms linear' : null; + const transition = isFloatingLabel + ? `transform ${ FLOATING_LABEL_TRANSITION_SPEED } linear` + : null; return css( { @@ -164,18 +168,21 @@ const labelPosition = ( { isFloatingLabel, isFloating, size } ) => { }; export const Label = styled.label` - box-sizing: border-box; - display: block; - top: 50%; - transition: transform 50ms linear; - z-index: 1; + &&& { + box-sizing: border-box; + display: block; + margin: 0; + top: 50%; + transition: transform ${FLOATING_LABEL_TRANSITION_SPEED} linear; + z-index: 1; - ${laberColor}; - ${labelFontSize}; - ${labelPosition}; - ${reduceMotion( 'transition' )}; + ${laberColor}; + ${labelFontSize}; + ${labelPosition}; + ${reduceMotion( 'transition' )}; - ${rtl( { left: 0 } )} + ${rtl( { left: 0 } )} + } `; const fieldsetTopStyles = ( { isFloatingLabel } ) => { @@ -189,53 +196,63 @@ const fieldsetFocusedStyles = ( { isFocused } ) => { : color( 'ui.border' ); const borderWidth = isFocused ? 2 : 1; + const borderStyle = 'solid'; - return css( { borderColor, borderWidth } ); + return css( { borderColor, borderStyle, borderWidth } ); }; export const Fieldset = styled.fieldset` - box-sizing: border-box; - border-color: black; - border-radius: inherit; - border-style: solid; - border-width: 1px; - bottom: 0; - left: 0; - margin: 0; - padding: 0; - pointer-events: none; - position: absolute; - right: 0; - - ${fieldsetFocusedStyles}; - ${fieldsetTopStyles}; - ${rtl( { paddingLeft: 2 } )} + &&& { + box-sizing: border-box; + border-radius: inherit; + bottom: 0; + left: 0; + margin: 0; + padding: 0; + pointer-events: none; + position: absolute; + right: 0; + + ${fieldsetFocusedStyles}; + ${fieldsetTopStyles}; + ${rtl( { paddingLeft: 2 } )} + } `; -const legendWidth = ( { isFloating } ) => { +const legendSize = ( { isFloating, size } ) => { const maxWidth = isFloating ? 1000 : 0.01; + const sizes = { + default: 9.75, + small: 8.25, + }; + + const fontSize = sizes[ size ]; + return css( { + fontSize, maxWidth, } ); }; export const Legend = styled.legend` - box-sizing: border-box; - width: auto; - height: 11px; - display: block; - padding: 0; - font-size: 0.75em; - transition: max-width 50ms linear; - visibility: hidden; - - ${legendWidth}; - ${reduceMotion( 'transition' )}; + &&& { + box-sizing: border-box; + display: block; + height: 11px; + line-height: 11px; + margin: 0; + padding: 0; + transition: max-width ${FLOATING_LABEL_TRANSITION_SPEED} linear; + visibility: hidden; + width: auto; + + ${legendSize}; + ${reduceMotion( 'transition' )}; + } `; export const LegendText = styled.span` box-sizing: border-box; display: inline-block; - padding-left: 4px; - padding-right: 4px; + ${rtl( { paddingLeft: 4, paddingRight: 5 } )} `; From ebbf0312d5467df94f3969a7c4aeebc014ff4b15 Mon Sep 17 00:00:00 2001 From: Jon Q Date: Wed, 8 Apr 2020 22:35:44 -0400 Subject: [PATCH 020/103] Refine UI for BoxControl --- packages/components/src/box-control/index.js | 16 ++++++++--- .../src/box-control/input-controls.js | 26 +++++++----------- .../box-control/styles/box-control-styles.js | 27 ++++++++++++++----- .../components/src/box-control/visualizer.js | 2 +- .../components/src/input-control/index.js | 7 ++++- .../styles/input-control-styles.js | 13 +++++++-- .../components/src/utils/colors-values.js | 1 + 7 files changed, 62 insertions(+), 30 deletions(-) diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index e0d485459902bd..4709dbfd1294bf 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -5,14 +5,15 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ +import { useInstanceId } from '@wordpress/compose'; import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import Visualizer from './visualizer'; -import { FlexItem } from '../flex'; import InputControls from './input-controls'; +import Text from '../text'; import { DEFAULT_VALUES, parseValues } from './utils'; import { Root, Header, LayoutContainer } from './styles/box-control-styles'; @@ -20,7 +21,14 @@ const defaultInputProps = { min: 0, }; +function useUniqueId( idProp ) { + const instanceId = useInstanceId( BoxControl ); + const id = `inspector-box-control-${ instanceId }`; + + return idProp || id; +} export default function BoxControl( { + idProp, inputProps = defaultInputProps, onChange = noop, label = __( 'Box Control' ), @@ -29,6 +37,8 @@ export default function BoxControl( { units = false, } ) { const [ values, setValues ] = useState( parseValues( valuesProp ) ); + const id = useUniqueId( idProp ); + const headingId = `${ id }-heading`; const updateValues = ( nextValues ) => { const mergedValues = { ...values, ...nextValues }; @@ -37,9 +47,9 @@ export default function BoxControl( { }; return ( - +
- { label } + { label }
- - - +