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 presets #67688

Merged
merged 7 commits into from
Dec 12, 2024
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
6 changes: 5 additions & 1 deletion packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Enhancements

- `BoxControl`: Add presets support ([#67688](https://github.com/WordPress/gutenberg/pull/67688)).

### Deprecations

- `SelectControl`: Deprecate 36px default size ([#66898](https://github.com/WordPress/gutenberg/pull/66898)).
Expand All @@ -24,7 +28,7 @@
- `Menu`: Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)).
- `Navigation` (deprecated): Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)).
- `ToggleGroupControl`: Replace hardcoded white color with theme-ready variable ([#67649](https://github.com/WordPress/gutenberg/pull/67649)).
- `RangeControl`: Update the design of the range control marks ([#67611](https://github.com/WordPress/gutenberg/pull/67611))
- `RangeControl`: Update the design of the range control marks ([#67611](https://github.com/WordPress/gutenberg/pull/67611)).
- `BorderBoxControl`: Reduce gap value when unlinked ([#67049](https://github.com/WordPress/gutenberg/pull/67049)).
- `DropdownMenu`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)).
- `MenuItem`: Increase height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)).
Expand Down
16 changes: 16 additions & 0 deletions packages/components/src/box-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ A callback function when an input value changes.
- Required: No
- Default: `() => {}`

### `presets`

Available 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential improvements for Developer Experience:

  • a runtime check, warning when only one of the two presets and presetKey props are defined
  • conditional types, specifying that presetKey is not optional if the presets prop is specified

The format of preset selected values is going to be `var:preset|${ presetKey }|${ presetSlug }`

- Type: `string`
- Required: No

### `resetValues`

The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset.
Expand Down
4 changes: 4 additions & 0 deletions packages/components/src/box-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ function BoxControl( {
splitOnAxis = false,
allowReset = true,
resetValues = DEFAULT_VALUES,
presets,
presetKey,
onMouseOver,
onMouseOut,
}: BoxControlProps ) {
Expand Down Expand Up @@ -153,6 +155,8 @@ function BoxControl( {
sides,
values: inputValues,
__next40pxDefaultSize,
presets,
presetKey,
};

maybeWarnDeprecated36pxSize( {
Expand Down
182 changes: 141 additions & 41 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 @@ -11,10 +13,13 @@ import Tooltip from '../tooltip';
import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils';
import {
CUSTOM_VALUE_SETTINGS,
getAllowedSides,
getMergedValue,
isValueMixed,
getAllowedSides,
getPresetIndexFromValue,
getPresetValueFromIndex,
isValuePreset,
isValuesDefined,
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 @@ -79,6 +85,8 @@ export default function BoxInputControl( {
sides,
side,
min = 0,
presets,
presetKey,
...props
}: BoxControlInputControlProps ) {
const defaultValuesToModify = getSidesToModify( side, sides );
Expand All @@ -91,6 +99,15 @@ export default function BoxInputControl( {
onChange( nextValues );
};

const handleRawOnValueChange = ( 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 @@ -148,52 +165,135 @@ export default function BoxInputControl( {
const usedValue =
mergedValue === undefined && computedUnit ? computedUnit : mergedValue;
const mixedPlaceholder = isMixed || isMixedUnit ? __( 'Mixed' ) : undefined;
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 )
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
);
const presetIndex = hasPresetValue
? getPresetIndexFromValue( mergedValue, presetKey, presets )
: undefined;
const marks = hasPresets
? [ { value: 0, label: '', tooltip: __( 'None' ) } ].concat(
presets.map( ( preset, index ) => ( {
value: index + 1,
label: '',
tooltip: preset.name ?? preset.slug,
} ) )
)
: [];

return (
<InputWrapper key={ `box-control-${ side }` } expanded>
<FlexedBoxControlIcon side={ side } sides={ sides } />
<Tooltip placement="top-end" text={ LABELS[ side ] }>
<StyledUnitControl
{ ...props }
min={ min }
__shouldNotWarnDeprecated36pxSize
__next40pxDefaultSize={ __next40pxDefaultSize }
className="component-box-control__unit-control"
id={ inputId }
isPressEnterToChange
disableUnits={ isMixed || isMixedUnit }
value={ usedValue }
onChange={ handleOnValueChange }
onUnitChange={ handleOnUnitChange }
onFocus={ handleOnFocus }
{ showCustomValueControl && (
<>
<Tooltip placement="top-end" text={ LABELS[ side ] }>
<StyledUnitControl
{ ...props }
min={ min }
__shouldNotWarnDeprecated36pxSize
__next40pxDefaultSize={ __next40pxDefaultSize }
className="component-box-control__unit-control"
id={ inputId }
isPressEnterToChange
disableUnits={ isMixed || isMixedUnit }
value={ usedValue }
onChange={ handleOnValueChange }
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={ isFinite( min ) ? 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 && (
<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
);
handleRawOnValueChange( newValue );
} }
withInputField={ false }
aria-valuenow={
presetIndex !== undefined ? presetIndex + 1 : 0
}
aria-valuetext={
marks[ presetIndex !== undefined ? presetIndex + 1 : 0 ]
.label
}
renderTooltipContent={ ( index ) =>
marks[ ! index ? 0 : index ].tooltip
}
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"
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={ isFinite( min ) ? 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 @@ -120,6 +136,8 @@ export type BoxControlInputControlProps = UnitControlPassthroughProps & {
* It can be a concrete side like: left, right, top, bottom or a combined one like: horizontal, vertical.
*/
side: keyof typeof LABELS;
presets?: Preset[];
presetKey?: string;
};

export type BoxControlIconProps = {
Expand Down
Loading
Loading