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: Convert to TypeScript #47622

Merged
merged 29 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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
38 changes: 14 additions & 24 deletions packages/components/src/box-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,82 +30,72 @@ 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`
mirka marked this conversation as resolved.
Show resolved Hide resolved

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

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

Collection of sides to allow control of. If omitted or empty, all sides will be available.

- 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;
Copy link
Member Author

Choose a reason for hiding this comment

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

The placeholder prop on inputs do not accept null.


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 ) );
Copy link
Member Author

Choose a reason for hiding this comment

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

Just making the undefined case explicit. Should not change runtime behavior.

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"
Copy link
Member Author

Choose a reason for hiding this comment

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

Updated a deprecated prop.

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