Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BoxControl: Add support for grouped directions (vertical and horizontal controls) #32610

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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