Skip to content

Commit

Permalink
BoxControl: Convert to TypeScript (#47622)
Browse files Browse the repository at this point in the history
* Rename index.tsx

* BoxControl: Add types

* Rename all-input-control.tsx

* Rename unit-control.tsx

* Add types

* Rename axial-input-controls.tsx

* Add types to axial input controls

* Rename input-controls.tsx

* Add types for input-controls

* Rename icon.tsx

* Add types to icon

* Add types to LinkedButton

* Add types for styles

* Improve types in utils

* Remove from tsconfig

* Convert tests

* Add main JSDoc

* Add changelog

* Add description for `id` prop

* Fix lint errors

* Remove copypasta in types

* Update formatting in readme

* Add todo comment

* Fixup optional

* Add default value

* Make onChange required

* Add default reset value to readme

* Add allowed `side` values to readme

* Fix default value for `label` in readme
  • Loading branch information
mirka authored Feb 2, 2023
1 parent 53a4d51 commit 0636e97
Show file tree
Hide file tree
Showing 17 changed files with 347 additions and 142 deletions.
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- `ColorPalette`, `BorderControl`, `GradientPicker`: refine types and logic around single vs multiple palettes
([#47384](https://github.com/WordPress/gutenberg/pull/47384)).
- `Button`: Convert to TypeScript ([#46997](https://github.com/WordPress/gutenberg/pull/46997)).
- `BoxControl`: Convert to TypeScript ([#47622](https://github.com/WordPress/gutenberg/pull/47622)).
- `QueryControls`: Convert to TypeScript ([#46721](https://github.com/WordPress/gutenberg/pull/46721)).
- `Notice`: refactor to TypeScript ([47118](https://github.com/WordPress/gutenberg/pull/47118)).

Expand Down
43 changes: 17 additions & 26 deletions packages/components/src/box-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,82 +30,73 @@ const Example = () => {
```

## Props
### allowReset
### `allowReset`: `boolean`

If this property is true, a button to reset the box control is rendered.

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

### splitOnAxis
### `splitOnAxis`: `boolean`

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
### `inputProps`: `object`

Props for the internal [InputControl](../input-control) components.
Props for the internal [UnitControl](../unit-control) components.

- Type: `Object`
- Required: No
- Default: `{ min: 0 }`

### label
### `label`: `string`

Heading label for BoxControl.
Heading label for the control.

- Type: `String`
- Required: No
- Default: `Box Control`
- Default: `__( 'Box Control' )`

### onChange
### `onChange`: `(next: BoxControlValue) => void`

A callback function when an input value changes.

- Type: `Function`
- Required: Yes

### resetValues
### `resetValues`: `object`

The `top`, `right`, `bottom`, and `left` box dimension values to use when the control is reset.

- Type: `Object`
- Required: No
- Default: `{ top: undefined, right: undefined, bottom: undefined, left: undefined }`

### sides
### `sides`: `string[]`

Collection of sides to allow control of. If omitted or empty, all sides will be available.
Collection of sides to allow control of. If omitted or empty, all sides will be available. Allowed values are "top", "right", "bottom", "left", "vertical", and "horizontal".

- Type: `Array<Object>`
- Required: No

### units
### `units`: `WPUnitControlUnit[]`

Collection of available units which are compatible with [UnitControl](../unit-control).

- Type: `Array<Object>`
- Required: No

### values
### `values`: `object`

The `top`, `right`, `bottom`, and `left` box dimension values.

- Type: `Object`
- Required: No

### onMouseOver
### `onMouseOver`: `function`

A handler for onMouseOver events.

- Type: `Function`
- Required: No

### onMouseOut
### `onMouseOut`: `function`

A handler for onMouseOut events.

- Type: `Function`
- Required: No
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* Internal dependencies
*/
import type { UnitControlProps } from '../unit-control/types';
import type { BoxControlInputControlProps } from './types';
import UnitControl from './unit-control';
import {
LABELS,
Expand All @@ -22,18 +24,20 @@ export default function AllInputControl( {
selectedUnits,
setSelectedUnits,
...props
} ) {
}: BoxControlInputControlProps ) {
const allValue = getAllValue( values, selectedUnits, sides );
const hasValues = isValuesDefined( values );
const isMixed = hasValues && isValuesMixed( values, selectedUnits, sides );
const allPlaceholder = isMixed ? LABELS.mixed : null;
const allPlaceholder = isMixed ? LABELS.mixed : undefined;

const handleOnFocus = ( event ) => {
const handleOnFocus: React.FocusEventHandler< HTMLInputElement > = (
event
) => {
onFocus( event, { side: 'all' } );
};

const handleOnChange = ( next ) => {
const isNumeric = ! isNaN( parseFloat( next ) );
const handleOnChange: UnitControlProps[ 'onChange' ] = ( next ) => {
const isNumeric = next !== undefined && ! isNaN( parseFloat( next ) );
const nextValue = isNumeric ? next : undefined;
const nextValues = applyValueToSides( values, nextValue, sides );

Expand All @@ -42,7 +46,7 @@ export default function AllInputControl( {

// Set selected unit so it can be used as fallback by unlinked controls
// when individual sides do not have a value containing a unit.
const handleOnUnitChange = ( unit ) => {
const handleOnUnitChange: UnitControlProps[ 'onUnitChange' ] = ( unit ) => {
const newUnits = applyValueToSides( selectedUnits, unit, sides );
setSelectedUnits( newUnits );
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils';
import UnitControl from './unit-control';
import { LABELS } from './utils';
import { Layout } from './styles/box-control-styles';
import type { BoxControlInputControlProps } from './types';

const groupedSides = [ 'vertical', 'horizontal' ];
const groupedSides = [ 'vertical', 'horizontal' ] as const;
type GroupedSide = typeof groupedSides[ number ];

export default function AxialInputControls( {
onChange,
Expand All @@ -18,15 +20,17 @@ export default function AxialInputControls( {
setSelectedUnits,
sides,
...props
} ) {
const createHandleOnFocus = ( side ) => ( event ) => {
if ( ! onFocus ) {
return;
}
onFocus( event, { side } );
};
}: BoxControlInputControlProps ) {
const createHandleOnFocus =
( side: GroupedSide ) =>
( event: React.FocusEvent< HTMLInputElement > ) => {
if ( ! onFocus ) {
return;
}
onFocus( event, { side } );
};

const createHandleOnHoverOn = ( side ) => () => {
const createHandleOnHoverOn = ( side: GroupedSide ) => () => {
if ( ! onHoverOn ) {
return;
}
Expand All @@ -44,7 +48,7 @@ export default function AxialInputControls( {
}
};

const createHandleOnHoverOff = ( side ) => () => {
const createHandleOnHoverOff = ( side: GroupedSide ) => () => {
if ( ! onHoverOff ) {
return;
}
Expand All @@ -62,12 +66,12 @@ export default function AxialInputControls( {
}
};

const createHandleOnChange = ( side ) => ( next ) => {
const createHandleOnChange = ( side: GroupedSide ) => ( next?: string ) => {
if ( ! onChange ) {
return;
}
const nextValues = { ...values };
const isNumeric = ! isNaN( parseFloat( next ) );
const isNumeric = next !== undefined && ! isNaN( parseFloat( next ) );
const nextValue = isNumeric ? next : undefined;

if ( side === 'vertical' ) {
Expand All @@ -83,21 +87,22 @@ export default function AxialInputControls( {
onChange( nextValues );
};

const createHandleOnUnitChange = ( side ) => ( next ) => {
const newUnits = { ...selectedUnits };
const createHandleOnUnitChange =
( side: GroupedSide ) => ( next?: string ) => {
const newUnits = { ...selectedUnits };

if ( side === 'vertical' ) {
newUnits.top = next;
newUnits.bottom = next;
}
if ( side === 'vertical' ) {
newUnits.top = next;
newUnits.bottom = next;
}

if ( side === 'horizontal' ) {
newUnits.left = next;
newUnits.right = next;
}
if ( side === 'horizontal' ) {
newUnits.left = next;
newUnits.right = next;
}

setSelectedUnits( newUnits );
};
setSelectedUnits( newUnits );
};

// Filter sides if custom configuration provided, maintaining default order.
const filteredSides = sides?.length
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Internal dependencies
*/
import type { WordPressComponentProps } from '../ui/context';
import {
Root,
Viewbox,
Expand All @@ -9,6 +10,7 @@ import {
BottomStroke,
LeftStroke,
} from './styles/box-control-icon-styles';
import type { BoxControlIconProps, BoxControlProps } from './types';

const BASE_ICON_SIZE = 24;

Expand All @@ -17,11 +19,14 @@ export default function BoxControlIcon( {
side = 'all',
sides,
...props
} ) {
const isSideDisabled = ( value ) =>
sides?.length && ! sides.includes( value );
}: WordPressComponentProps< BoxControlIconProps, 'span' > ) {
const isSideDisabled = (
value: NonNullable< BoxControlProps[ 'sides' ] >[ number ]
) => sides?.length && ! sides.includes( value );

const hasSide = ( value ) => {
const hasSide = (
value: NonNullable< BoxControlProps[ 'sides' ] >[ number ]
) => {
if ( isSideDisabled( value ) ) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,50 @@ import {
isValuesDefined,
} from './utils';
import { useControlledState } from '../utils/hooks';
import type {
BoxControlIconProps,
BoxControlProps,
BoxControlValue,
} from './types';

const defaultInputProps = {
min: 0,
};

const noop = () => {};

function useUniqueId( idProp ) {
function useUniqueId( idProp?: string ) {
const instanceId = useInstanceId( BoxControl, 'inspector-box-control' );

return idProp || instanceId;
}
export default function BoxControl( {

/**
* BoxControl components let users set values for Top, Right, Bottom, and Left.
* This can be used as an input control for values like `padding` or `margin`.
*
* ```jsx
* import { __experimentalBoxControl as BoxControl } from '@wordpress/components';
* import { useState } from '@wordpress/element';
*
* const Example = () => {
* const [ values, setValues ] = useState( {
* top: '50px',
* left: '10%',
* right: '10%',
* bottom: '50px',
* } );
*
* return (
* <BoxControl
* values={ values }
* onChange={ ( nextValues ) => setValues( nextValues ) }
* />
* );
* };
* ```
*/
function BoxControl( {
id: idProp,
inputProps = defaultInputProps,
onChange = noop,
Expand All @@ -54,7 +85,7 @@ export default function BoxControl( {
resetValues = DEFAULT_VALUES,
onMouseOver,
onMouseOut,
} ) {
}: BoxControlProps ) {
const [ values, setValues ] = useControlledState( valuesProp, {
fallback: DEFAULT_VALUES,
} );
Expand All @@ -67,14 +98,14 @@ export default function BoxControl( {
! hasInitialValue || ! isValuesMixed( inputValues ) || hasOneSide
);

const [ side, setSide ] = useState(
const [ side, setSide ] = useState< BoxControlIconProps[ 'side' ] >(
getInitialSide( isLinked, splitOnAxis )
);

// Tracking selected units via internal state allows filtering of CSS unit
// only values from being saved while maintaining preexisting unit selection
// behaviour. Filtering CSS only values prevents invalid style values.
const [ selectedUnits, setSelectedUnits ] = useState( {
const [ selectedUnits, setSelectedUnits ] = useState< BoxControlValue >( {
top: parseQuantityAndUnitFromRawValue( valuesProp?.top )[ 1 ],
right: parseQuantityAndUnitFromRawValue( valuesProp?.right )[ 1 ],
bottom: parseQuantityAndUnitFromRawValue( valuesProp?.bottom )[ 1 ],
Expand All @@ -89,11 +120,14 @@ export default function BoxControl( {
setSide( getInitialSide( ! isLinked, splitOnAxis ) );
};

const handleOnFocus = ( event, { side: nextSide } ) => {
const handleOnFocus = (
_event: React.FocusEvent< HTMLInputElement >,
{ side: nextSide }: { side: typeof side }
) => {
setSide( nextSide );
};

const handleOnChange = ( nextValues ) => {
const handleOnChange = ( nextValues: BoxControlValue ) => {
onChange( nextValues );
setValues( nextValues );
setIsDirty( true );
Expand Down Expand Up @@ -132,7 +166,7 @@ export default function BoxControl( {
<FlexItem>
<Button
className="component-box-control__reset-button"
isSecondary
variant="secondary"
isSmall
onClick={ handleOnReset }
disabled={ ! isDirty }
Expand Down Expand Up @@ -176,3 +210,4 @@ export default function BoxControl( {
}

export { applyValueToSides } from './utils';
export default BoxControl;
Loading

1 comment on commit 0636e97

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 0636e97.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4077358930
📝 Reported issues:

Please sign in to comment.