Skip to content

Commit

Permalink
BoxControl: Add support for presets
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Dec 6, 2024
1 parent 0a9cf26 commit 9f6e4f9
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 109 deletions.
15 changes: 15 additions & 0 deletions packages/components/src/box-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,18 @@ The current values of the control, expressed as an object of `top`, `right`, `bo

- Type: `BoxControlValue`
- Required: No

### `presets`

The list of presets to pick from.

- Type: `Preset`
- Required: No

### `presetKey`

The key of the preset to apply. If you provide a list of presets, you must provide a preset key to use. The format of preset selected values is going to be `var:preset|${ presetKey }|${ presetSlug }`

- Type: `string`
- Required: No

8 changes: 6 additions & 2 deletions packages/components/src/box-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils';
import {
DEFAULT_VALUES,
getInitialSide,
isValuesMixed,
isValueMixed,
isValuesDefined,
ALL_SIDES,
} from './utils';
Expand Down Expand Up @@ -83,6 +83,8 @@ function BoxControl( {
splitOnAxis = false,
allowReset = true,
resetValues = DEFAULT_VALUES,
presets,
presetKey,
onMouseOver,
onMouseOut,
}: BoxControlProps ) {
Expand All @@ -95,7 +97,7 @@ function BoxControl( {

const [ isDirty, setIsDirty ] = useState( hasInitialValue );
const [ isLinked, setIsLinked ] = useState(
! hasInitialValue || ! isValuesMixed( inputValues ) || hasOneSide
! hasInitialValue || ! isValueMixed( inputValues ) || hasOneSide
);

const [ side, setSide ] = useState< BoxControlIconProps[ 'side' ] >(
Expand Down Expand Up @@ -153,6 +155,8 @@ function BoxControl( {
sides,
values: inputValues,
__next40pxDefaultSize,
presets,
presetKey,
};

maybeWarnDeprecated36pxSize( {
Expand Down
191 changes: 143 additions & 48 deletions packages/components/src/box-control/input-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/
import { useInstanceId } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { settings } from '@wordpress/icons';

/**
* Internal dependencies
Expand All @@ -12,9 +14,12 @@ import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils';
import {
ALL_SIDES,
CUSTOM_VALUE_SETTINGS,
getAllValue,
getMergedValue,
getPresetIndexFromValue,
getPresetValueFromIndex,
isValuePreset,
isValuesDefined,
isValuesMixed,
isValueMixed,
LABELS,
} from './utils';
import {
Expand All @@ -24,6 +29,7 @@ import {
StyledUnitControl,
} from './styles/box-control-styles';
import type { BoxControlInputControlProps, BoxControlValue } from './types';
import Button from '../button';

const noop = () => {};

Expand Down Expand Up @@ -96,6 +102,8 @@ export default function BoxInputControl( {
setSelectedUnits,
sides,
side,
presets,
presetKey,
...props
}: BoxControlInputControlProps ) {
const defaultValuesToModify = getSidesToModify( side, sides );
Expand All @@ -108,6 +116,15 @@ export default function BoxInputControl( {
onChange( nextValues );
};

const handleRewOnValueChange = ( next?: string ) => {
const nextValues = { ...values };
defaultValuesToModify.forEach( ( modifiedSide ) => {
nextValues[ modifiedSide ] = next;
} );

handleOnChange( nextValues );
};

const handleOnValueChange = (
next?: string,
extra?: { event: React.SyntheticEvent< Element, Event > }
Expand Down Expand Up @@ -143,15 +160,9 @@ export default function BoxInputControl( {
setSelectedUnits( newUnits );
};

const mergedValue = getAllValue(
values,
selectedUnits,
defaultValuesToModify
);
const mergedValue = getMergedValue( values, defaultValuesToModify );
const hasValues = isValuesDefined( values );
const isMixed =
hasValues &&
isValuesMixed( values, selectedUnits, defaultValuesToModify );
const isMixed = hasValues && isValueMixed( values, defaultValuesToModify );
const mixedPlaceholder = isMixed ? __( 'Mixed' ) : undefined;
const [ parsedQuantity, parsedUnit ] =
parseQuantityAndUnitFromRawValue( mergedValue );
Expand All @@ -160,52 +171,136 @@ export default function BoxInputControl( {
: selectedUnits[ defaultValuesToModify[ 0 ] ];
const generatedId = useInstanceId( BoxInputControl, 'box-control-input' );
const inputId = [ generatedId, side ].join( '-' );
const hasPresets = presets && presets.length > 0 && presetKey;
const hasPresetValue =
hasPresets &&
mergedValue !== undefined &&
! isMixed &&
isValuePreset( mergedValue, presetKey );
const [ showCustomValueControl, setShowCustomValueControl ] = useState(
! hasPresets ||
( ! hasPresetValue && ! isMixed && mergedValue !== undefined )
);
const showRangeControl = true;
const presetIndex = hasPresetValue
? getPresetIndexFromValue( mergedValue, presetKey, presets )
: undefined;
const marks = hasPresets
? [ { value: 0, label: __( 'None' ) } ].concat(
presets.map( ( preset, index ) => ( {
value: index + 1,
label: preset.name,
} ) )
)
: [];

return (
<InputWrapper key={ `box-control-${ side }` } expanded>
<FlexedBoxControlIcon side={ side } sides={ sides } />
<Tooltip placement="top-end" text={ LABELS[ side ] }>
<StyledUnitControl
{ ...props }
__shouldNotWarnDeprecated36pxSize
__next40pxDefaultSize={ __next40pxDefaultSize }
className="component-box-control__unit-control"
id={ inputId }
isPressEnterToChange
value={ mergedValue }
onChange={ ( nextValue, extra ) =>
handleOnValueChange( nextValue, extra )
{ showCustomValueControl && (
<>
<Tooltip placement="top-end" text={ LABELS[ side ] }>
<StyledUnitControl
{ ...props }
__shouldNotWarnDeprecated36pxSize
__next40pxDefaultSize={ __next40pxDefaultSize }
className="component-box-control__unit-control"
id={ inputId }
isPressEnterToChange
value={ mergedValue }
onChange={ ( nextValue, extra ) =>
handleOnValueChange( nextValue, extra )
}
onUnitChange={ handleOnUnitChange }
onFocus={ handleOnFocus }
label={ LABELS[ side ] }
placeholder={ mixedPlaceholder }
hideLabelFromVision
/>
</Tooltip>

<FlexedRangeControl
__nextHasNoMarginBottom
__next40pxDefaultSize={ __next40pxDefaultSize }
__shouldNotWarnDeprecated36pxSize
aria-controls={ inputId }
label={ LABELS[ side ] }
hideLabelFromVision
onChange={ ( newValue ) => {
handleOnValueChange(
newValue !== undefined
? [ newValue, computedUnit ].join( '' )
: undefined
);
} }
min={ 0 }
max={
CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ]
?.max ?? 10
}
step={
CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ]
?.step ?? 0.1
}
value={ parsedQuantity ?? 0 }
withInputField={ false }
/>
</>
) }

{ hasPresets && ! showCustomValueControl && showRangeControl && (
<FlexedRangeControl
__next40pxDefaultSize
className="spacing-sizes-control__range-control"
value={ presetIndex !== undefined ? presetIndex + 1 : 0 }
onChange={ ( newIndex ) => {
const newValue =
newIndex === 0 || newIndex === undefined
? undefined
: getPresetValueFromIndex(
newIndex - 1,
presetKey,
presets
);
handleRewOnValueChange( newValue );
} }
withInputField={ false }
aria-valuenow={
presetIndex !== undefined ? presetIndex + 1 : 0
}
aria-valuetext={
marks[ presetIndex !== undefined ? presetIndex + 1 : 0 ]
.label
}
onUnitChange={ handleOnUnitChange }
onFocus={ handleOnFocus }
renderTooltipContent={ ( index ) =>
marks[ ! index ? 0 : index ].label
}
min={ 0 }
max={ marks.length - 1 }
marks={ marks }
label={ LABELS[ side ] }
placeholder={ mixedPlaceholder }
hideLabelFromVision
__nextHasNoMarginBottom
/>
) }

{ hasPresets && (
<Button
label={
showCustomValueControl
? __( 'Use size preset' )
: __( 'Set custom size' )
}
icon={ settings }
onClick={ () => {
setShowCustomValueControl( ! showCustomValueControl );
} }
isPressed={ showCustomValueControl }
size="small"
className="spacing-sizes-control__custom-toggle"
iconSize={ 24 }
/>
</Tooltip>

<FlexedRangeControl
__nextHasNoMarginBottom
__next40pxDefaultSize={ __next40pxDefaultSize }
__shouldNotWarnDeprecated36pxSize
aria-controls={ inputId }
label={ LABELS[ side ] }
hideLabelFromVision
onChange={ ( newValue ) => {
handleOnValueChange(
newValue !== undefined
? [ newValue, computedUnit ].join( '' )
: undefined
);
} }
min={ 0 }
max={ CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ]?.max ?? 10 }
step={
CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ]?.step ?? 0.1
}
value={ parsedQuantity ?? 0 }
withInputField={ false }
/>
) }
</InputWrapper>
);
}
12 changes: 12 additions & 0 deletions packages/components/src/box-control/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,15 @@ AxialControlsWithSingleSide.args = {
sides: [ 'horizontal' ],
splitOnAxis: true,
};

export const ControlWithPresets = TemplateControlled.bind( {} );
ControlWithPresets.args = {
...Default.args,
presets: [
{ name: 'Small', slug: 'small', value: '4px' },
{ name: 'Medium', slug: 'medium', value: '8px' },
{ name: 'Large', slug: 'large', value: '12px' },
{ name: 'Extra Large', slug: 'extra-large', value: '16px' },
],
presetKey: 'padding',
};
18 changes: 18 additions & 0 deletions packages/components/src/box-control/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export type CustomValueUnits = {
[ key: string ]: { max: number; step: number };
};

export interface Preset {
name: string;
slug: string;
value: string;
}

type UnitControlPassthroughProps = Omit<
UnitControlProps,
'label' | 'onChange' | 'onFocus' | 'units'
Expand Down Expand Up @@ -94,6 +100,16 @@ export type BoxControlProps = Pick< UnitControlProps, 'units' > &
* @default false
*/
__next40pxDefaultSize?: boolean;
/**
* Available presets to pick from.
*/
presets?: Preset[];
/**
* The key of the preset to apply.
* If you provide a list of presets, you must provide a preset key to use.
* The format of preset selected values is going to be `var:preset|${ presetKey }|${ presetSlug }`
*/
presetKey?: string;
};

export type BoxControlInputControlProps = UnitControlPassthroughProps & {
Expand All @@ -113,6 +129,8 @@ export type BoxControlInputControlProps = UnitControlPassthroughProps & {
sides: BoxControlProps[ 'sides' ];
values: BoxControlValue;
side: keyof typeof LABELS;
presets?: Preset[];
presetKey?: string;
};

export type BoxControlIconProps = {
Expand Down
Loading

0 comments on commit 9f6e4f9

Please sign in to comment.