Skip to content

Commit

Permalink
BoxControl: Add support for grouped directions (vertical and horizont…
Browse files Browse the repository at this point in the history
…al controls) (#32610)

* BoxControl: Add option to group vertical and horizontal controls when unlinked
  • Loading branch information
andrewserong authored Jun 16, 2021
1 parent a4e3977 commit f1f0cd7
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 15 deletions.
8 changes: 8 additions & 0 deletions packages/components/src/box-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ If this property is true, a button to reset the box control is rendered.
- Required: No
- Default: `true`

### splitOnAxis

If this property is true, when the box control is unlinked, vertical and horizontal controls can be used instead of updating individual sides.

- Type: `Boolean`
- Required: No
- Default: `false`

### inputProps

Props for the internal [InputControl](../input-control) components.
Expand Down
21 changes: 17 additions & 4 deletions packages/components/src/box-control/all-input-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,24 @@ export default function AllInputControl( {

const handleOnChange = ( next ) => {
const nextValues = { ...values };
const selectedSides = sides?.length
? sides
: [ 'top', 'right', 'bottom', 'left' ];

selectedSides.forEach( ( side ) => ( nextValues[ side ] = next ) );
if ( sides?.length ) {
sides.forEach( ( side ) => {
if ( side === 'vertical' ) {
nextValues.top = next;
nextValues.bottom = next;
} else if ( side === 'horizontal' ) {
nextValues.left = next;
nextValues.right = next;
} else {
nextValues[ side ] = next;
}
} );
} else {
[ 'top', 'right', 'bottom', 'left' ].forEach(
( side ) => ( nextValues[ side ] = next )
);
}

onChange( nextValues );
};
Expand Down
10 changes: 5 additions & 5 deletions packages/components/src/box-control/icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ export default function BoxControlIcon( {
const isSideDisabled = ( value ) =>
sides?.length && ! sides.includes( value );

const getSide = ( value ) => {
const hasSide = ( value ) => {
if ( isSideDisabled( value ) ) {
return false;
}

return side === 'all' || side === value;
};

const top = getSide( 'top' );
const right = getSide( 'right' );
const bottom = getSide( 'bottom' );
const left = getSide( 'left' );
const top = hasSide( 'top' ) || hasSide( 'vertical' );
const right = hasSide( 'right' ) || hasSide( 'horizontal' );
const bottom = hasSide( 'bottom' ) || hasSide( 'vertical' );
const left = hasSide( 'left' ) || hasSide( 'horizontal' );

// Simulates SVG Icon scaling
const scale = size / BASE_ICON_SIZE;
Expand Down
20 changes: 17 additions & 3 deletions packages/components/src/box-control/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Button from '../button';
import { FlexItem, FlexBlock } from '../flex';
import AllInputControl from './all-input-control';
import InputControls from './input-controls';
import VerticalHorizontalInputControls from './vertical-horizontal-input-controls';
import BoxControlIcon from './icon';
import { Text } from '../text';
import LinkedButton from './linked-button';
Expand All @@ -29,6 +30,7 @@ import {
import {
DEFAULT_VALUES,
DEFAULT_VISUALIZER_VALUES,
getInitialSide,
isValuesMixed,
isValuesDefined,
} from './utils';
Expand All @@ -52,6 +54,7 @@ export default function BoxControl( {
values: valuesProp,
units,
sides,
splitOnAxis = false,
allowReset = true,
resetValues = DEFAULT_VALUES,
} ) {
Expand All @@ -67,14 +70,16 @@ export default function BoxControl( {
! hasInitialValue || ! isValuesMixed( inputValues ) || hasOneSide
);

const [ side, setSide ] = useState( isLinked ? 'all' : 'top' );
const [ side, setSide ] = useState(
getInitialSide( isLinked, splitOnAxis )
);

const id = useUniqueId( idProp );
const headingId = `${ id }-heading`;

const toggleLinked = () => {
setIsLinked( ! isLinked );
setSide( ! isLinked ? 'all' : 'top' );
setSide( getInitialSide( ! isLinked, splitOnAxis ) );
};

const handleOnFocus = ( event, { side: nextSide } ) => {
Expand Down Expand Up @@ -150,6 +155,13 @@ export default function BoxControl( {
/>
</FlexBlock>
) }
{ ! isLinked && splitOnAxis && (
<FlexBlock>
<VerticalHorizontalInputControls
{ ...inputControlProps }
/>
</FlexBlock>
) }
{ ! hasOneSide && (
<FlexItem>
<LinkedButton
Expand All @@ -159,7 +171,9 @@ export default function BoxControl( {
</FlexItem>
) }
</HeaderControlWrapper>
{ ! isLinked && <InputControls { ...inputControlProps } /> }
{ ! isLinked && ! splitOnAxis && (
<InputControls { ...inputControlProps } />
) }
</Root>
);
}
Expand Down
21 changes: 20 additions & 1 deletion packages/components/src/box-control/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ const defaultSideValues = {
left: '10px',
};

function DemoExample( { sides, defaultValues = defaultSideValues } ) {
function DemoExample( {
sides,
defaultValues = defaultSideValues,
splitOnAxis = false,
} ) {
const [ values, setValues ] = useState( defaultValues );
const [ showVisualizer, setShowVisualizer ] = useState( {} );

Expand All @@ -41,6 +45,7 @@ function DemoExample( { sides, defaultValues = defaultSideValues } ) {
sides={ sides }
onChange={ setValues }
onChangeShowVisualizer={ setShowVisualizer }
splitOnAxis={ splitOnAxis }
/>
</Content>
</FlexBlock>
Expand Down Expand Up @@ -82,6 +87,20 @@ export const singleSide = () => {
);
};

export const verticalHorizontalControls = () => {
return <DemoExample splitOnAxis={ true } />;
};

export const verticalHorizontalControlsWithSingleSide = () => {
return (
<DemoExample
sides={ [ 'horizontal' ] }
defaultValues={ { left: '10px', right: '10px' } }
splitOnAxis={ true }
/>
);
};

const Container = styled( Flex )`
max-width: 780px;
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const Header = styled( Flex )`

export const HeaderControlWrapper = styled( Flex )`
min-height: 30px;
gap: 0;
`;

export const UnitControlWrapper = styled.div`
Expand Down Expand Up @@ -59,8 +60,8 @@ const unitControlBorderRadiusStyles = ( { isFirst, isLast, isOnly } ) => {
} );
};

const unitControlMarginStyles = ( { isFirst } ) => {
const marginLeft = isFirst ? 0 : -1;
const unitControlMarginStyles = ( { isFirst, isOnly } ) => {
const marginLeft = isFirst || isOnly ? 0 : -1;

return rtl( { marginLeft } )();
};
Expand Down
67 changes: 67 additions & 0 deletions packages/components/src/box-control/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,71 @@ describe( 'BoxControl', () => {
expect( unitSelect.value ).toBe( 'px' );
} );
} );

describe( 'Unlinked Sides', () => {
it( 'should update a single side value when unlinked', () => {
let state = {};
const setState = ( newState ) => ( state = newState );

const { container, getByLabelText } = render(
<BoxControl
values={ state }
onChange={ ( next ) => setState( next ) }
/>
);

const unlink = getByLabelText( /Unlink Sides/ );
fireEvent.click( unlink );

const input = container.querySelector( 'input' );
const unitSelect = container.querySelector( 'select' );

input.focus();
fireEvent.change( input, { target: { value: '100px' } } );
fireEvent.keyDown( input, { keyCode: ENTER } );

expect( input.value ).toBe( '100' );
expect( unitSelect.value ).toBe( 'px' );

expect( state ).toEqual( {
top: '100px',
right: undefined,
bottom: undefined,
left: undefined,
} );
} );

it( 'should update a whole axis when value is changed when unlinked', () => {
let state = {};
const setState = ( newState ) => ( state = newState );

const { container, getByLabelText } = render(
<BoxControl
values={ state }
onChange={ ( next ) => setState( next ) }
splitOnAxis={ true }
/>
);

const unlink = getByLabelText( /Unlink Sides/ );
fireEvent.click( unlink );

const input = container.querySelector( 'input' );
const unitSelect = container.querySelector( 'select' );

input.focus();
fireEvent.change( input, { target: { value: '100px' } } );
fireEvent.keyDown( input, { keyCode: ENTER } );

expect( input.value ).toBe( '100' );
expect( unitSelect.value ).toBe( 'px' );

expect( state ).toEqual( {
top: '100px',
right: undefined,
bottom: '100px',
left: undefined,
} );
} );
} );
} );
20 changes: 20 additions & 0 deletions packages/components/src/box-control/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const LABELS = {
left: __( 'Left' ),
right: __( 'Right' ),
mixed: __( 'Mixed' ),
vertical: __( 'Vertical' ),
horizontal: __( 'Horizontal' ),
};

export const DEFAULT_VALUES = {
Expand Down Expand Up @@ -119,3 +121,21 @@ export function isValuesDefined( values ) {
)
);
}

/**
* Get initial selected side, factoring in whether the sides are linked,
* and whether the vertical / horizontal directions are grouped via splitOnAxis.
*
* @param {boolean} isLinked
* @param {boolean} splitOnAxis
* @return {string} The initial side.
*/
export function getInitialSide( isLinked, splitOnAxis ) {
let initialSide = 'all';

if ( ! isLinked ) {
initialSide = splitOnAxis ? 'vertical' : 'top';
}

return initialSide;
}
Loading

0 comments on commit f1f0cd7

Please sign in to comment.