-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Block Support: Update border support UI (#31585)
Refine the controls provided via the border block support feature.
- Loading branch information
1 parent
ede5e74
commit 803dffa
Showing
21 changed files
with
557 additions
and
162 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
packages/block-editor/src/components/border-radius-control/all-input-control.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __experimentalUnitControl as UnitControl } from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { getAllValue, hasMixedValues, hasDefinedValues } from './utils'; | ||
|
||
export default function AllInputControl( { onChange, values, ...props } ) { | ||
const allValue = getAllValue( values ); | ||
const hasValues = hasDefinedValues( values ); | ||
const isMixed = hasValues && hasMixedValues( values ); | ||
const allPlaceholder = isMixed ? __( 'Mixed' ) : null; | ||
|
||
return ( | ||
<UnitControl | ||
{ ...props } | ||
aria-label={ __( 'Border radius' ) } | ||
disableUnits={ isMixed } | ||
isOnly | ||
value={ allValue } | ||
onChange={ onChange } | ||
placeholder={ allPlaceholder } | ||
/> | ||
); | ||
} |
102 changes: 102 additions & 0 deletions
102
packages/block-editor/src/components/border-radius-control/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
RangeControl, | ||
__experimentalParseUnit as parseUnit, | ||
__experimentalUseCustomUnits as useCustomUnits, | ||
} from '@wordpress/components'; | ||
import { useState } from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import AllInputControl from './all-input-control'; | ||
import InputControls from './input-controls'; | ||
import LinkedButton from './linked-button'; | ||
import { | ||
getAllValue, | ||
getAllUnit, | ||
hasDefinedValues, | ||
hasMixedValues, | ||
} from './utils'; | ||
|
||
const DEFAULT_VALUES = { | ||
topLeft: null, | ||
topRight: null, | ||
bottomLeft: null, | ||
bottomRight: null, | ||
}; | ||
const MIN_BORDER_RADIUS_VALUE = 0; | ||
const MAX_BORDER_RADIUS_VALUES = { | ||
px: 100, | ||
em: 20, | ||
rem: 20, | ||
}; | ||
|
||
/** | ||
* Control to display border radius options. | ||
* | ||
* @param {Object} props Component props. | ||
* @param {Function} props.onChange Callback to handle onChange. | ||
* @param {Object} props.values Border radius values. | ||
* | ||
* @return {WPElement} Custom border radius control. | ||
*/ | ||
export default function BorderRadiusControl( { onChange, values } ) { | ||
const [ isLinked, setIsLinked ] = useState( | ||
! hasDefinedValues( values ) || ! hasMixedValues( values ) | ||
); | ||
|
||
const units = useCustomUnits( { availableUnits: [ 'px', 'em', 'rem' ] } ); | ||
const unit = getAllUnit( values ); | ||
const unitConfig = units.find( ( item ) => item.value === unit ); | ||
const step = unitConfig?.step || 1; | ||
|
||
const [ allValue ] = parseUnit( getAllValue( values ) ); | ||
|
||
const toggleLinked = () => setIsLinked( ! isLinked ); | ||
|
||
const handleSliderChange = ( next ) => { | ||
onChange( next !== undefined ? `${ next }${ unit }` : undefined ); | ||
}; | ||
|
||
return ( | ||
<fieldset className="components-border-radius-control"> | ||
<legend>{ __( 'Radius' ) }</legend> | ||
<div className="components-border-radius-control__wrapper"> | ||
{ isLinked ? ( | ||
<> | ||
<AllInputControl | ||
className="components-border-radius-control__unit-control" | ||
values={ values } | ||
min={ MIN_BORDER_RADIUS_VALUE } | ||
onChange={ onChange } | ||
unit={ unit } | ||
units={ units } | ||
/> | ||
<RangeControl | ||
className="components-border-radius-control__range-control" | ||
value={ allValue } | ||
min={ MIN_BORDER_RADIUS_VALUE } | ||
max={ MAX_BORDER_RADIUS_VALUES[ unit ] } | ||
initialPosition={ 0 } | ||
withInputField={ false } | ||
onChange={ handleSliderChange } | ||
step={ step } | ||
/> | ||
</> | ||
) : ( | ||
<InputControls | ||
min={ MIN_BORDER_RADIUS_VALUE } | ||
onChange={ onChange } | ||
values={ values || DEFAULT_VALUES } | ||
units={ units } | ||
/> | ||
) } | ||
<LinkedButton onClick={ toggleLinked } isLinked={ isLinked } /> | ||
</div> | ||
</fieldset> | ||
); | ||
} |
55 changes: 55 additions & 0 deletions
55
packages/block-editor/src/components/border-radius-control/input-controls.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __experimentalUnitControl as UnitControl } from '@wordpress/components'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
const CORNERS = { | ||
topLeft: __( 'Top left' ), | ||
topRight: __( 'Top right' ), | ||
bottomLeft: __( 'Bottom left' ), | ||
bottomRight: __( 'Bottom right' ), | ||
}; | ||
|
||
export default function BoxInputControls( { | ||
onChange, | ||
values: valuesProp, | ||
...props | ||
} ) { | ||
const createHandleOnChange = ( corner ) => ( next ) => { | ||
if ( ! onChange ) { | ||
return; | ||
} | ||
|
||
onChange( { | ||
...values, | ||
[ corner ]: next ? next : undefined, | ||
} ); | ||
}; | ||
|
||
// For shorthand style & backwards compatibility, handle flat string value. | ||
const values = | ||
typeof valuesProp !== 'string' | ||
? valuesProp | ||
: { | ||
topLeft: valuesProp, | ||
topRight: valuesProp, | ||
bottomLeft: valuesProp, | ||
bottomRight: valuesProp, | ||
}; | ||
|
||
// Controls are wrapped in tooltips as visible labels aren't desired here. | ||
return ( | ||
<div className="components-border-radius-control__input-controls-wrapper"> | ||
{ Object.entries( CORNERS ).map( ( [ key, label ] ) => ( | ||
<UnitControl | ||
{ ...props } | ||
key={ key } | ||
aria-label={ label } | ||
value={ values[ key ] } | ||
onChange={ createHandleOnChange( key ) } | ||
/> | ||
) ) } | ||
</div> | ||
); | ||
} |
25 changes: 25 additions & 0 deletions
25
packages/block-editor/src/components/border-radius-control/linked-button.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Button, Tooltip } from '@wordpress/components'; | ||
import { link, linkOff } from '@wordpress/icons'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
export default function LinkedButton( { isLinked, ...props } ) { | ||
const label = isLinked ? __( 'Unlink Radii' ) : __( 'Link Radii' ); | ||
|
||
return ( | ||
<Tooltip text={ label }> | ||
<Button | ||
{ ...props } | ||
className="component-border-radius-control__linked-button" | ||
isPrimary={ isLinked } | ||
isSecondary={ ! isLinked } | ||
isSmall | ||
icon={ isLinked ? link : linkOff } | ||
iconSize={ 16 } | ||
aria-label={ label } | ||
/> | ||
</Tooltip> | ||
); | ||
} |
57 changes: 57 additions & 0 deletions
57
packages/block-editor/src/components/border-radius-control/style.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
.components-border-radius-control { | ||
margin-bottom: $grid-unit-15; | ||
|
||
legend { | ||
padding-bottom: $grid-unit-05; | ||
} | ||
|
||
.components-border-radius-control__wrapper { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: flex-start; | ||
|
||
> .components-unit-control-wrapper { | ||
width: calc(50% - 26px); | ||
margin-bottom: 0; | ||
} | ||
|
||
.components-range-control { | ||
width: calc(50% - 26px); | ||
margin-bottom: 0; | ||
|
||
.components-base-control__field { | ||
margin-bottom: 0; | ||
height: 30px; | ||
} | ||
|
||
.components-range-control__wrapper { | ||
margin-right: 10px; | ||
} | ||
} | ||
|
||
> span { | ||
flex: 0 0 auto; | ||
} | ||
} | ||
|
||
.components-border-radius-control__input-controls-wrapper { | ||
display: flex; | ||
width: 70%; | ||
flex-wrap: wrap; | ||
|
||
.components-unit-control-wrapper { | ||
width: calc(50% - #{ $grid-unit-10 }); | ||
margin-bottom: $grid-unit-10; | ||
margin-right: $grid-unit-10; | ||
} | ||
} | ||
|
||
.component-border-radius-control__linked-button.has-icon { | ||
display: flex; | ||
justify-content: center; | ||
|
||
svg { | ||
margin-right: 0; | ||
} | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
packages/block-editor/src/components/border-radius-control/utils.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __experimentalParseUnit as parseUnit } from '@wordpress/components'; | ||
|
||
/** | ||
* Gets the item with the highest occurrence within an array | ||
* https://stackoverflow.com/a/20762713 | ||
* | ||
* @param {Array<any>} arr Array of items to check. | ||
* @return {any} The item with the most occurrences. | ||
*/ | ||
function mode( arr ) { | ||
return arr | ||
.sort( | ||
( a, b ) => | ||
arr.filter( ( v ) => v === a ).length - | ||
arr.filter( ( v ) => v === b ).length | ||
) | ||
.pop(); | ||
} | ||
|
||
/** | ||
* Returns the most common CSS unit in the radius values. | ||
* | ||
* @param {Object|string} values Radius values. | ||
* @return {string} Most common CSS unit in values. | ||
*/ | ||
export function getAllUnit( values = {} ) { | ||
if ( typeof values === 'string' ) { | ||
const [ , unit ] = parseUnit( values ); | ||
return unit || 'px'; | ||
} | ||
|
||
const allUnits = Object.values( values ).map( ( value ) => { | ||
const [ , unit ] = parseUnit( value ); | ||
return unit; | ||
} ); | ||
|
||
return mode( allUnits ); | ||
} | ||
|
||
/** | ||
* Gets the 'all' input value and unit from values data. | ||
* | ||
* @param {Object|string} values Radius values. | ||
* @return {string} A value + unit for the 'all' input. | ||
*/ | ||
export function getAllValue( values = {} ) { | ||
/** | ||
* Border radius support was originally a single pixel value. | ||
* | ||
* To maintain backwards compatibility treat this case as the all value. | ||
*/ | ||
if ( typeof values === 'string' ) { | ||
return values; | ||
} | ||
|
||
const parsedValues = Object.values( values ).map( ( value ) => | ||
parseUnit( value ) | ||
); | ||
|
||
const allValues = parsedValues.map( ( value ) => value[ 0 ] ); | ||
const allUnits = parsedValues.map( ( value ) => value[ 1 ] ); | ||
|
||
const value = allValues.every( ( v ) => v === allValues[ 0 ] ) | ||
? allValues[ 0 ] | ||
: ''; | ||
const unit = mode( allUnits ); | ||
|
||
const allValue = value === 0 || value ? `${ value }${ unit }` : null; | ||
|
||
return allValue; | ||
} | ||
|
||
/** | ||
* Checks to determine if values are mixed. | ||
* | ||
* @param {Object} values Radius values. | ||
* @return {boolean} Whether values are mixed. | ||
*/ | ||
export function hasMixedValues( values = {} ) { | ||
const allValue = getAllValue( values ); | ||
const isMixed = isNaN( parseFloat( allValue ) ); | ||
|
||
return isMixed; | ||
} | ||
|
||
/** | ||
* Checks to determine if values are defined. | ||
* | ||
* @param {Object} values Radius values. | ||
* @return {boolean} Whether values are mixed. | ||
*/ | ||
export function hasDefinedValues( values ) { | ||
if ( ! values ) { | ||
return false; | ||
} | ||
|
||
// A string value represents a shorthand value. | ||
if ( typeof values === 'string' ) { | ||
return true; | ||
} | ||
|
||
// An object represents longhand border radius values, if any are set | ||
// flag values as being defined. | ||
const filteredValues = Object.values( values ).filter( ( value ) => { | ||
return !! value || value === 0; | ||
} ); | ||
|
||
return !! filteredValues.length; | ||
} |
Oops, something went wrong.