diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 3308b98d281696..c0e5c1b5f8bb85 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -15,3 +15,4 @@ import './layout'; export { useCustomSides } from './spacing'; export { getBorderClassesAndStyles, useBorderProps } from './use-border-props'; export { getColorClassesAndStyles, useColorProps } from './use-color-props'; +export { getSpacingClassesAndStyles } from './use-spacing-props'; diff --git a/packages/block-editor/src/hooks/index.native.js b/packages/block-editor/src/hooks/index.native.js index 11dbe0a30e4c9a..b0aac50354da6b 100644 --- a/packages/block-editor/src/hooks/index.native.js +++ b/packages/block-editor/src/hooks/index.native.js @@ -11,3 +11,4 @@ import './font-size'; export { getBorderClassesAndStyles, useBorderProps } from './use-border-props'; export { getColorClassesAndStyles, useColorProps } from './use-color-props'; +export { getSpacingClassesAndStyles } from './use-spacing-props'; diff --git a/packages/block-editor/src/hooks/use-spacing-props.js b/packages/block-editor/src/hooks/use-spacing-props.js new file mode 100644 index 00000000000000..35fe87c1e5ec40 --- /dev/null +++ b/packages/block-editor/src/hooks/use-spacing-props.js @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import { getInlineStyles } from './style'; + +// This utility is intended to assist where the serialization of the spacing +// block support is being skipped for a block but the spacing related CSS +// styles still need to be generated so they can be applied to inner elements. + +/** + * Provides the CSS class names and inline styles for a block's spacing support + * attributes. + * + * @param {Object} attributes Block attributes. + * + * @return {Object} Spacing block support derived CSS classes & styles. + */ +export function getSpacingClassesAndStyles( attributes ) { + const { style } = attributes; + + // Collect inline styles for spacing. + const spacingStyles = style?.spacing || {}; + const styleProp = getInlineStyles( { spacing: spacingStyles } ); + + return { + style: styleProp, + }; +} diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index 27af4c4be666b8..aba0be6926a21c 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -13,6 +13,7 @@ export { getColorClassesAndStyles as __experimentalGetColorClassesAndStyles, useColorProps as __experimentalUseColorProps, useCustomSides as __experimentalUseCustomSides, + getSpacingClassesAndStyles as __experimentalGetSpacingClassesAndStyles, } from './hooks'; export * from './components'; export * from './utils'; diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index 3cb59a7fd653eb..72fd2dbccef724 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -63,6 +63,10 @@ }, "fontSize": true, "reusable": false, + "spacing": { + "__experimentalSkipSerialization": true, + "padding": true + }, "__experimentalBorder": { "radius": true, "__experimentalSkipSerialization": true diff --git a/packages/block-library/src/button/controls.native.js b/packages/block-library/src/button/controls.native.js new file mode 100644 index 00000000000000..3a11da889d8663 --- /dev/null +++ b/packages/block-library/src/button/controls.native.js @@ -0,0 +1,274 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + InspectorControls, + BlockControls, + useSetting, +} from '@wordpress/block-editor'; +import { + PanelBody, + RangeControl, + ToolbarGroup, + ToolbarButton, + BottomSheetSelectControl, + UnitControl, + getValueAndUnit, + __experimentalUseCustomUnits as useCustomUnits, +} from '@wordpress/components'; + +import { link } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import ColorEdit from './color-edit'; +import styles from './editor.scss'; +import { cleanEmptyObject } from '../../../block-editor/src/hooks/utils'; + +const MIN_BORDER_RADIUS_VALUE = 0; +const MAX_BORDER_RADIUS_VALUE = 50; + +function WidthPanel( { selectedWidth, setAttributes } ) { + function handleChange( newWidth ) { + // Check if we are toggling the width off + let width = selectedWidth === newWidth ? undefined : newWidth; + if ( newWidth === 'auto' ) { + width = undefined; + } + // Update attributes + setAttributes( { width } ); + } + + const options = [ + { value: 'auto', label: __( 'Auto' ) }, + { value: 25, label: '25%' }, + { value: 50, label: '50%' }, + { value: 75, label: '75%' }, + { value: 100, label: '100%' }, + ]; + + if ( ! selectedWidth ) { + selectedWidth = 'auto'; + } + + return ( + + + + ); +} + +const PaddingInputs = ( props ) => { + const { + values, + units, + sides, + onChange, + onChangeUnits, + unitOptions, + } = props; + + const LABELS = { + top: __( 'Top' ), + bottom: __( 'Bottom' ), + left: __( 'Left' ), + right: __( 'Right' ), + }; + + const handleOnChange = ( nextValues ) => { + onChange( nextValues ); + }; + + const handleOnChangeUnits = ( nextUnits ) => { + onChangeUnits( nextUnits ); + }; + + const createHandleOnChange = ( side ) => ( next ) => { + const nextValues = { ...values }; + nextValues[ side ] = next; + handleOnChange( nextValues ); + }; + + const createHandleOnUnitChange = ( side ) => ( next ) => { + const nextUnits = { ...units }; + nextUnits[ side ] = next; + handleOnChangeUnits( nextUnits ); + }; + + return ( + <> + { sides.map( ( side ) => ( + + ) ) } + + ); +}; + +const PaddingPanel = ( props ) => { + const { + attributes: { style }, + setAttributes, + defaultPadding, + } = props; + + const customPaddingSelections = style?.spacing?.padding || {}; + + const sides = [ 'top', 'bottom', 'left', 'right' ]; + const values = {}; + const units = {}; + + sides.forEach( ( side ) => { + // Get custom padding selection from the style attribute, falling back to default. + const paddingForSide = customPaddingSelections[ side ] + ? customPaddingSelections[ side ] + : defaultPadding[ side ]; + + // Break out into value and unit, as UnitControl for native requires setting unit and value separately. + const { valueToConvert, valueUnit } = + getValueAndUnit( paddingForSide ) || {}; + values[ side ] = valueToConvert; + units[ side ] = valueUnit; + } ); + + const unitOptions = useCustomUnits( { + availableUnits: useSetting( 'spacing.units' ) || [ + 'px', + 'em', + 'rem', + 'vw', + 'vh', + ], + defaultValues: { px: '430', em: '20', rem: '20', vw: '20', vh: '50' }, + } ); + + // Update the style object with new padding selections. + const updatePadding = ( nextValues, nextUnits ) => { + const updatedPaddingValues = getValuesWithUnits( + nextValues, + nextUnits + ); + const newStyle = { + ...style, + spacing: { + ...style?.spacing, + padding: { + ...updatedPaddingValues, + }, + }, + }; + setAttributes( { + style: cleanEmptyObject( newStyle ), + } ); + }; + + // Get the values with units, for updating the style. + const getValuesWithUnits = ( nextValues, nextUnits ) => { + const valuesWithUnits = {}; + sides.forEach( ( side ) => { + valuesWithUnits[ + side + ] = `${ nextValues[ side ] }${ nextUnits[ side ] }`; + } ); + + return valuesWithUnits; + }; + + // Update when a value was changed. + const handleChange = ( nextValues ) => { + updatePadding( nextValues, units ); + }; + + // Update when a unit was changed. + const handleUnitsChange = ( nextUnits ) => { + updatePadding( values, nextUnits ); + }; + + return ( + + + + ); +}; + +export default function Controls( { + attributes, + setAttributes, + clientId, + borderRadiusValue, + getLinkSettings, + onShowLinkSettings, + onChangeBorderRadius, + defaultPadding, +} ) { + const { url, width } = attributes; + + return ( + <> + + + + + + { getLinkSettings( false ) } + + + + + + + + + { getLinkSettings( true ) } + + + + ); +} diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 70ce701138999b..c9d795890d65b4 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -24,6 +24,7 @@ import { RichText, useBlockProps, __experimentalUseColorProps as useColorProps, + __experimentalGetSpacingClassesAndStyles as useSpacingProps, __experimentalLinkControl as LinkControl, } from '@wordpress/block-editor'; import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; @@ -198,6 +199,7 @@ function ButtonEdit( props ) { const borderRadius = style?.border?.radius; const colorProps = useColorProps( attributes ); + const spacingProps = useSpacingProps( attributes ); const ref = useRef(); const blockProps = useBlockProps( { ref } ); @@ -229,6 +231,7 @@ function ButtonEdit( props ) { ? borderRadius + 'px' : undefined, ...colorProps.style, + ...spacingProps.style, } } onSplit={ ( value ) => createBlock( 'core/button', { diff --git a/packages/block-library/src/button/edit.native.js b/packages/block-library/src/button/edit.native.js index 7818d3e37d197a..db18e60af68a2f 100644 --- a/packages/block-library/src/button/edit.native.js +++ b/packages/block-library/src/button/edit.native.js @@ -2,32 +2,28 @@ * External dependencies */ import { View, AccessibilityInfo, Platform, Text } from 'react-native'; +import { get } from 'lodash'; + /** * WordPress dependencies */ -import { withInstanceId, compose } from '@wordpress/compose'; +import { withInstanceId, compose, usePrevious } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { RichText, - InspectorControls, - BlockControls, withGradient, store as blockEditorStore, getColorObjectByAttributeValues, getGradientValueBySlug, __experimentalGetColorClassesAndStyles as getColorClassesAndStyles, + __experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles, } from '@wordpress/block-editor'; import { - PanelBody, - RangeControl, - ToolbarGroup, - ToolbarButton, LinkSettingsNavigation, - BottomSheetSelectControl, + useConvertUnitToMobile, } from '@wordpress/components'; -import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; -import { link } from '@wordpress/icons'; +import { useEffect, useState, useRef } from '@wordpress/element'; /** * Internal dependencies @@ -35,10 +31,8 @@ import { link } from '@wordpress/icons'; import richTextStyle from './rich-text.scss'; import styles from './editor.scss'; import ColorBackground from './color-background'; -import ColorEdit from './color-edit'; +import Controls from './controls'; -const MIN_BORDER_RADIUS_VALUE = 0; -const MAX_BORDER_RADIUS_VALUE = 50; const INITIAL_MAX_WIDTH = 108; const MIN_WIDTH = 40; // Map of the percentage width to pixel subtraction that make the buttons fit nicely into columns. @@ -49,145 +43,138 @@ const MIN_WIDTH_MARGINS = { 25: styles.button25?.marginLeft, }; -function WidthPanel( { selectedWidth, setAttributes } ) { - function handleChange( newWidth ) { - // Check if we are toggling the width off - let width = selectedWidth === newWidth ? undefined : newWidth; - if ( newWidth === 'auto' ) { - width = undefined; - } - // Update attributes - setAttributes( { width } ); - } +const DEFAULT_PADDING = { + top: '0px', + bottom: '0px', + left: richTextStyle.richText.paddingLeft + 'px', + right: richTextStyle.richText.paddingRight + 'px', +}; - const options = [ - { value: 'auto', label: __( 'Auto' ) }, - { value: 25, label: '25%' }, - { value: 50, label: '50%' }, - { value: 75, label: '75%' }, - { value: 100, label: '100%' }, +const ButtonEdit = ( { + attributes, + colors, + gradients, + isSelected, + clientId, + onReplace, + mergeBlocks, + parentWidth, + setAttributes, + editorSidebarOpened, + numOfButtons, + onDeleteBlock, + closeSettingsBottomSheet, +} ) => { + const { + placeholder, + text, + borderRadius, + align = 'center', + width, + gradient, + } = attributes; + const [ maxWidth, setMaxWidth ] = useState( INITIAL_MAX_WIDTH ); + const [ isLinkSheetVisible, setIsLinkSheetVisible ] = useState( false ); + const [ isButtonFocused, setIsButtonFocused ] = useState( true ); + const [ placeholderTextWidth, setPlaceholderTextWidth ] = useState( 0 ); + const richTextRef = useRef( null ); + + const prevParentWidth = usePrevious( parentWidth ); + const wasSelected = usePrevious( isSelected ); + const wasEditorSidebarOpened = usePrevious( editorSidebarOpened ); + const wasLinkSheetVisible = usePrevious( isLinkSheetVisible ); + + const linkSettingsActions = [ + { + label: __( 'Remove link' ), + onPress: onClearSettings, + }, ]; - if ( ! selectedWidth ) { - selectedWidth = 'auto'; - } - - return ( - - - - ); -} - -class ButtonEdit extends Component { - constructor( props ) { - super( props ); - this.onChangeText = this.onChangeText.bind( this ); - this.onChangeBorderRadius = this.onChangeBorderRadius.bind( this ); - this.onClearSettings = this.onClearSettings.bind( this ); - this.onLayout = this.onLayout.bind( this ); - this.onSetMaxWidth = this.onSetMaxWidth.bind( this ); - this.dismissSheet = this.dismissSheet.bind( this ); - this.onShowLinkSettings = this.onShowLinkSettings.bind( this ); - this.onHideLinkSettings = this.onHideLinkSettings.bind( this ); - this.onToggleButtonFocus = this.onToggleButtonFocus.bind( this ); - this.onPlaceholderTextWidth = this.onPlaceholderTextWidth.bind( this ); - this.setRef = this.setRef.bind( this ); - this.onRemove = this.onRemove.bind( this ); - this.getPlaceholderWidth = this.getPlaceholderWidth.bind( this ); - - this.state = { - maxWidth: INITIAL_MAX_WIDTH, - isLinkSheetVisible: false, - isButtonFocused: true, - placeholderTextWidth: 0, - }; - - this.linkSettingsActions = [ - { - label: __( 'Remove link' ), - onPress: this.onClearSettings, - }, - ]; - - this.linkSettingsOptions = { - url: { - label: __( 'Button Link URL' ), - placeholder: __( 'Add URL' ), - autoFocus: true, - autoFill: true, - }, - openInNewTab: { - label: __( 'Open in new tab' ), - }, - linkRel: { - label: __( 'Link Rel' ), - placeholder: __( 'None' ), - }, - }; - - this.noFocusLinkSettingOptions = { - ...this.linkSettingsOptions, - url: { - ...this.linkSettingsOptions.url, - autoFocus: false, - }, - }; - } - - componentDidMount() { - this.onSetMaxWidth(); - } - - componentDidUpdate( prevProps, prevState ) { - const { isSelected, editorSidebarOpened, parentWidth } = this.props; - const { isLinkSheetVisible, isButtonFocused } = this.state; - - if ( isSelected && ! prevProps.isSelected ) { - this.onToggleButtonFocus( true ); + const linkSettingsOptions = { + url: { + label: __( 'Button Link URL' ), + placeholder: __( 'Add URL' ), + autoFocus: true, + autoFill: true, + }, + openInNewTab: { + label: __( 'Open in new tab' ), + }, + linkRel: { + label: __( 'Link Rel' ), + placeholder: __( 'None' ), + }, + }; + + const noFocusLinkSettingOptions = { + ...linkSettingsOptions, + url: { + ...linkSettingsOptions.url, + autoFocus: false, + }, + }; + + useEffect( () => { + onSetMaxWidth(); + }, [] ); + + useEffect( () => { + if ( isSelected && ! wasSelected ) { + onToggleButtonFocus( true ); } - if ( prevProps.parentWidth !== parentWidth ) { - this.onSetMaxWidth( null, true ); + if ( prevParentWidth !== parentWidth ) { + onSetMaxWidth( null, true ); } // Blur `RichText` on Android when link settings sheet or button settings sheet is opened, // to avoid flashing caret after closing one of them if ( - ( ! prevProps.editorSidebarOpened && editorSidebarOpened ) || - ( ! prevState.isLinkSheetVisible && isLinkSheetVisible ) + ( ! wasEditorSidebarOpened && editorSidebarOpened ) || + ( ! wasLinkSheetVisible && isLinkSheetVisible ) ) { - if ( Platform.OS === 'android' && this.richTextRef ) { - this.richTextRef.blur(); - this.onToggleButtonFocus( false ); + if ( + Platform.OS === 'android' && + richTextRef && + richTextRef.current + ) { + richTextRef.current.blur(); + onToggleButtonFocus( false ); } } - if ( this.richTextRef ) { + if ( richTextRef.current ) { if ( ! isSelected && isButtonFocused ) { - this.onToggleButtonFocus( false ); + onToggleButtonFocus( false ); } if ( isSelected && ! isButtonFocused ) { AccessibilityInfo.isScreenReaderEnabled().then( ( enabled ) => { if ( enabled ) { - this.onToggleButtonFocus( true ); - this.richTextRef.focus(); + onToggleButtonFocus( true ); + richTextRef.current.focus(); } } ); } } - } - - getBackgroundColor() { - const { attributes, colors, gradients } = this.props; - const { backgroundColor, gradient } = attributes; - + }, [ + isSelected, + editorSidebarOpened, + parentWidth, + isLinkSheetVisible, + isButtonFocused, + wasSelected, + wasEditorSidebarOpened, + wasLinkSheetVisible, + richTextRef.current, + ] ); + + const setRef = ( nodeRef ) => { + richTextRef.current = nodeRef; + }; + + const getBackgroundColor = () => { // Return named gradient value if available. const gradientValue = getGradientValueBySlug( gradients, gradient ); @@ -201,7 +188,7 @@ class ButtonEdit extends Component { // do not load their color stylesheets in the editor. const colorObject = getColorObjectByAttributeValues( colors, - backgroundColor + attributes.backgroundColor ); return ( @@ -210,10 +197,9 @@ class ButtonEdit extends Component { colorProps.style?.background || styles.defaultButton.backgroundColor ); - } + }; - getTextColor() { - const { attributes, colors } = this.props; + const getTextColor = () => { const colorProps = getColorClassesAndStyles( attributes ); // Retrieve named color object to force inline styles for themes that @@ -228,294 +214,240 @@ class ButtonEdit extends Component { colorProps.style?.color || styles.defaultButton.color ); - } + }; - onChangeText( value ) { - const { setAttributes } = this.props; + const onChangeText = ( value ) => { setAttributes( { text: value } ); - } + }; - onChangeBorderRadius( value ) { - const { setAttributes } = this.props; + const onChangeBorderRadius = ( value ) => { setAttributes( { borderRadius: value, } ); - } + }; - onShowLinkSettings() { - this.setState( { isLinkSheetVisible: true } ); - } + const onShowLinkSettings = () => { + setIsLinkSheetVisible( true ); + }; - onHideLinkSettings() { - this.setState( { isLinkSheetVisible: false } ); - } - - onToggleButtonFocus( value ) { - if ( value !== this.state.isButtonFocused ) { - this.setState( { isButtonFocused: value } ); - } - } - - onClearSettings() { - const { setAttributes } = this.props; + const onHideLinkSettings = () => { + setIsLinkSheetVisible( false ); + }; + const onClearSettings = () => { setAttributes( { url: '', rel: '', linkTarget: '', } ); - this.onHideLinkSettings(); - } + onHideLinkSettings(); + }; - onLayout( { nativeEvent } ) { - const { width } = nativeEvent.layout; - this.onSetMaxWidth( width ); - } + const onToggleButtonFocus = ( value ) => { + if ( value !== isButtonFocused ) { + setIsButtonFocused( value ); + } + }; + + const onLayout = ( { nativeEvent } ) => { + const { layoutWidth } = nativeEvent.layout; + onSetMaxWidth( layoutWidth ); + }; - onSetMaxWidth( width, isParentWidthDidChange = false ) { - const { maxWidth } = this.state; - const { parentWidth } = this.props; + const onSetMaxWidth = ( newWidth, isParentWidthDidChange = false ) => { const { marginRight: spacing } = styles.defaultButton; const isParentWidthChanged = isParentWidthDidChange ? isParentWidthDidChange : maxWidth !== parentWidth; - const isWidthChanged = maxWidth !== width; - - if ( parentWidth && ! width && isParentWidthChanged ) { - this.setState( { - maxWidth: parentWidth - spacing, - } ); - } else if ( ! parentWidth && width && isWidthChanged ) { - this.setState( { maxWidth: width - spacing } ); - } - } + const isWidthChanged = maxWidth !== newWidth; - onRemove() { - const { numOfButtons, onDeleteBlock, onReplace } = this.props; + if ( parentWidth && ! newWidth && isParentWidthChanged ) { + setMaxWidth( parentWidth - spacing ); + } else if ( ! parentWidth && newWidth && isWidthChanged ) { + setMaxWidth( newWidth - spacing ); + } + }; + const onRemove = () => { if ( numOfButtons === 1 ) { onDeleteBlock(); } else { onReplace( [] ); } - } + }; - dismissSheet() { - this.onHideLinkSettings(); - this.props.closeSettingsBottomSheet(); - } + const dismissSheet = () => { + onHideLinkSettings(); + closeSettingsBottomSheet(); + }; - getLinkSettings( isCompatibleWithSettings ) { - const { isLinkSheetVisible } = this.state; - const { attributes, setAttributes } = this.props; + const getLinkSettings = ( isCompatibleWithSettings ) => { return ( ); - } - - setRef( richText ) { - this.richTextRef = richText; - } + }; // Render `Text` with `placeholderText` styled as a placeholder // to calculate its width which then is set as a `minWidth` - getPlaceholderWidth( placeholderText ) { + const getPlaceholderWidth = ( placeholderText ) => { return ( { placeholderText } ); - } + }; - onPlaceholderTextWidth( { nativeEvent } ) { - const { maxWidth, placeholderTextWidth } = this.state; + const onPlaceholderTextWidth = ( { nativeEvent } ) => { const textWidth = nativeEvent.lines[ 0 ] && nativeEvent.lines[ 0 ].width; if ( textWidth && textWidth !== placeholderTextWidth ) { - this.setState( { - placeholderTextWidth: Math.min( textWidth, maxWidth ), - } ); + setPlaceholderTextWidth( Math.min( textWidth, maxWidth ) ); } + }; + + const paddingStyles = getSpacingClassesAndStyles( attributes ).style; + + // Convert units for mobile. + const convertedPadding = { + paddingTop: useConvertUnitToMobile( + get( paddingStyles, 'paddingTop', DEFAULT_PADDING.top ) + ), + paddingBottom: useConvertUnitToMobile( + get( paddingStyles, 'paddingBottom', DEFAULT_PADDING.bottom ) + ), + paddingLeft: useConvertUnitToMobile( + get( paddingStyles, 'paddingLeft', DEFAULT_PADDING.left ) + ), + paddingRight: useConvertUnitToMobile( + get( paddingStyles, 'paddingRight', DEFAULT_PADDING.right ) + ), + }; + + if ( parentWidth === 0 ) { + return null; } - render() { - const { - attributes, - isSelected, - clientId, - onReplace, - mergeBlocks, - parentWidth, - setAttributes, - } = this.props; - const { - placeholder, - text, - borderRadius, - url, - align = 'center', - width, - } = attributes; - const { maxWidth, isButtonFocused, placeholderTextWidth } = this.state; - const { paddingTop: spacing, borderWidth } = styles.defaultButton; - - if ( parentWidth === 0 ) { - return null; - } - - const borderRadiusValue = Number.isInteger( borderRadius ) - ? borderRadius - : styles.defaultButton.borderRadius; - const outlineBorderRadius = - borderRadiusValue > 0 - ? borderRadiusValue + spacing + borderWidth - : 0; - - // To achieve proper expanding and shrinking `RichText` on iOS, there is a need to set a `minWidth` - // value at least on 1 when `RichText` is focused or when is not focused, but `RichText` value is - // different than empty string. - let minWidth = - isButtonFocused || ( ! isButtonFocused && text && text !== '' ) - ? MIN_WIDTH - : placeholderTextWidth; - if ( width ) { - // Set the width of the button. - minWidth = Math.floor( - maxWidth * ( width / 100 ) - MIN_WIDTH_MARGINS[ width ] - ); - } - // To achieve proper expanding and shrinking `RichText` on Android, there is a need to set - // a `placeholder` as an empty string when `RichText` is focused, - // because `AztecView` is calculating a `minWidth` based on placeholder text. - const placeholderText = - isButtonFocused || ( ! isButtonFocused && text && text !== '' ) - ? '' - : placeholder || __( 'Add text…' ); - - const backgroundColor = this.getBackgroundColor(); - const textColor = this.getTextColor(); - const isFixedWidth = !! width; - - return ( - - { this.getPlaceholderWidth( placeholderText ) } - - { isSelected && ( - - ) } - - this.onToggleButtonFocus( true ) - } - __unstableMobileNoFocusOnMount={ ! isSelected } - selectionColor={ textColor } - onBlur={ () => { - this.onSetMaxWidth(); - } } - onReplace={ onReplace } - onRemove={ this.onRemove } - onMerge={ mergeBlocks } - /> - + const { paddingTop: spacing, borderWidth } = styles.defaultButton; + + const borderRadiusValue = Number.isInteger( borderRadius ) + ? borderRadius + : styles.defaultButton.borderRadius; + const outlineBorderRadius = + borderRadiusValue > 0 ? borderRadiusValue + spacing + borderWidth : 0; + + // To achieve proper expanding and shrinking `RichText` on iOS, there is a need to set a `minWidth` + // value at least on 1 when `RichText` is focused or when is not focused, but `RichText` value is + // different than empty string. + let minWidth = + isButtonFocused || ( ! isButtonFocused && text && text !== '' ) + ? MIN_WIDTH + : placeholderTextWidth; + if ( width ) { + // Set the width of the button. + minWidth = Math.floor( + maxWidth * ( width / 100 ) - MIN_WIDTH_MARGINS[ width ] + ); + } + // To achieve proper expanding and shrinking `RichText` on Android, there is a need to set + // a `placeholder` as an empty string when `RichText` is focused, + // because `AztecView` is calculating a `minWidth` based on placeholder text. + const placeholderText = + isButtonFocused || ( ! isButtonFocused && text && text !== '' ) + ? '' + : placeholder || __( 'Add text…' ); + + const backgroundColor = getBackgroundColor(); + const textColor = getTextColor(); + const isFixedWidth = !! width; + return ( + + { getPlaceholderWidth( placeholderText ) } + { isSelected && ( - <> - - - - - - { this.getLinkSettings( false ) } - - - - - - - - { this.getLinkSettings( true ) } - - - + ) } - - ); - } -} + onToggleButtonFocus( true ) } + __unstableMobileNoFocusOnMount={ ! isSelected } + selectionColor={ textColor } + onBlur={ () => { + onSetMaxWidth(); + } } + onReplace={ onReplace } + onRemove={ onRemove } + onMerge={ mergeBlocks } + /> + + + { isSelected && ( + + ) } + + ); +}; export default compose( [ withInstanceId, diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index 695e27e07b5b40..a09d35a6447c15 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -10,6 +10,7 @@ import { RichText, useBlockProps, __experimentalGetColorClassesAndStyles as getColorClassesAndStyles, + __experimentalGetSpacingClassesAndStyles as getSpacingClassesAndStyles, } from '@wordpress/block-editor'; export default function save( { attributes, className } ) { @@ -30,6 +31,7 @@ export default function save( { attributes, className } ) { const borderRadius = style?.border?.radius; const colorProps = getColorClassesAndStyles( attributes ); + const spacingProps = getSpacingClassesAndStyles( attributes ); const buttonClasses = classnames( 'wp-block-button__link', colorProps.className, @@ -40,6 +42,7 @@ export default function save( { attributes, className } ) { const buttonStyle = { borderRadius: borderRadius ? borderRadius + 'px' : undefined, ...colorProps.style, + ...spacingProps.style, }; // The use of a `title` attribute here is soft-deprecated, but still applied