@wordpress/block-library
Version:
Block library for the WordPress editor.
273 lines (252 loc) • 6.69 kB
JavaScript
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useCallback, useEffect, useState, useRef } from '@wordpress/element';
import {
Button,
ButtonGroup,
PanelBody,
TextControl,
ToolbarButton,
Popover,
} from '@wordpress/components';
import {
BlockControls,
InspectorControls,
RichText,
useBlockProps,
__experimentalUseBorderProps as useBorderProps,
__experimentalUseColorProps as useColorProps,
__experimentalGetSpacingClassesAndStyles as useSpacingProps,
__experimentalLinkControl as LinkControl,
__experimentalGetElementClassName,
} from '@wordpress/block-editor';
import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes';
import { link, linkOff } from '@wordpress/icons';
import { createBlock } from '@wordpress/blocks';
import { useMergeRefs } from '@wordpress/compose';
const NEW_TAB_REL = 'noreferrer noopener';
function WidthPanel( { selectedWidth, setAttributes } ) {
function handleChange( newWidth ) {
// Check if we are toggling the width off
const width = selectedWidth === newWidth ? undefined : newWidth;
// Update attributes.
setAttributes( { width } );
}
return (
<PanelBody title={ __( 'Width settings' ) }>
<ButtonGroup aria-label={ __( 'Button width' ) }>
{ [ 25, 50, 75, 100 ].map( ( widthValue ) => {
return (
<Button
key={ widthValue }
isSmall
variant={
widthValue === selectedWidth
? 'primary'
: undefined
}
onClick={ () => handleChange( widthValue ) }
>
{ widthValue }%
</Button>
);
} ) }
</ButtonGroup>
</PanelBody>
);
}
function ButtonEdit( props ) {
const {
attributes,
setAttributes,
className,
isSelected,
onReplace,
mergeBlocks,
} = props;
const { linkTarget, placeholder, rel, style, text, url, width } =
attributes;
const onSetLinkRel = useCallback(
( value ) => {
setAttributes( { rel: value } );
},
[ setAttributes ]
);
function onToggleOpenInNewTab( value ) {
const newLinkTarget = value ? '_blank' : undefined;
let updatedRel = rel;
if ( newLinkTarget && ! rel ) {
updatedRel = NEW_TAB_REL;
} else if ( ! newLinkTarget && rel === NEW_TAB_REL ) {
updatedRel = undefined;
}
setAttributes( {
linkTarget: newLinkTarget,
rel: updatedRel,
} );
}
function setButtonText( newText ) {
// Remove anchor tags from button text content.
setAttributes( { text: newText.replace( /<\/?a[^>]*>/g, '' ) } );
}
function onKeyDown( event ) {
if ( isKeyboardEvent.primary( event, 'k' ) ) {
startEditing( event );
} else if ( isKeyboardEvent.primaryShift( event, 'k' ) ) {
unlink();
richTextRef.current?.focus();
}
}
// Use internal state instead of a ref to make sure that the component
// re-renders when the popover's anchor updates.
const [ popoverAnchor, setPopoverAnchor ] = useState( null );
const borderProps = useBorderProps( attributes );
const colorProps = useColorProps( attributes );
const spacingProps = useSpacingProps( attributes );
const ref = useRef();
const richTextRef = useRef();
const blockProps = useBlockProps( {
ref: useMergeRefs( [ setPopoverAnchor, ref ] ),
onKeyDown,
} );
const [ isEditingURL, setIsEditingURL ] = useState( false );
const isURLSet = !! url;
const opensInNewTab = linkTarget === '_blank';
function startEditing( event ) {
event.preventDefault();
setIsEditingURL( true );
}
function unlink() {
setAttributes( {
url: undefined,
linkTarget: undefined,
rel: undefined,
} );
setIsEditingURL( false );
}
useEffect( () => {
if ( ! isSelected ) {
setIsEditingURL( false );
}
}, [ isSelected ] );
return (
<>
<div
{ ...blockProps }
className={ classnames( blockProps.className, {
[ `has-custom-width wp-block-button__width-${ width }` ]:
width,
[ `has-custom-font-size` ]: blockProps.style.fontSize,
} ) }
>
<RichText
ref={ richTextRef }
aria-label={ __( 'Button text' ) }
placeholder={ placeholder || __( 'Add text…' ) }
value={ text }
onChange={ ( value ) => setButtonText( value ) }
withoutInteractiveFormatting
className={ classnames(
className,
'wp-block-button__link',
colorProps.className,
borderProps.className,
{
// For backwards compatibility add style that isn't
// provided via block support.
'no-border-radius': style?.border?.radius === 0,
},
__experimentalGetElementClassName( 'button' )
) }
style={ {
...borderProps.style,
...colorProps.style,
...spacingProps.style,
} }
onSplit={ ( value ) =>
createBlock( 'core/button', {
...attributes,
text: value,
} )
}
onReplace={ onReplace }
onMerge={ mergeBlocks }
identifier="text"
/>
</div>
<BlockControls group="block">
{ ! isURLSet && (
<ToolbarButton
name="link"
icon={ link }
title={ __( 'Link' ) }
shortcut={ displayShortcut.primary( 'k' ) }
onClick={ startEditing }
/>
) }
{ isURLSet && (
<ToolbarButton
name="link"
icon={ linkOff }
title={ __( 'Unlink' ) }
shortcut={ displayShortcut.primaryShift( 'k' ) }
onClick={ unlink }
isActive={ true }
/>
) }
</BlockControls>
{ isSelected && ( isEditingURL || isURLSet ) && (
<Popover
position="bottom center"
onClose={ () => {
setIsEditingURL( false );
richTextRef.current?.focus();
} }
anchor={ popoverAnchor }
focusOnMount={ isEditingURL ? 'firstElement' : false }
__unstableSlotName={ '__unstable-block-tools-after' }
shift
>
<LinkControl
className="wp-block-navigation-link__inline-link-input"
value={ { url, opensInNewTab } }
onChange={ ( {
url: newURL = '',
opensInNewTab: newOpensInNewTab,
} ) => {
setAttributes( { url: newURL } );
if ( opensInNewTab !== newOpensInNewTab ) {
onToggleOpenInNewTab( newOpensInNewTab );
}
} }
onRemove={ () => {
unlink();
richTextRef.current?.focus();
} }
forceIsEditingLink={ isEditingURL }
/>
</Popover>
) }
<InspectorControls>
<WidthPanel
selectedWidth={ width }
setAttributes={ setAttributes }
/>
</InspectorControls>
<InspectorControls __experimentalGroup="advanced">
<TextControl
label={ __( 'Link rel' ) }
value={ rel || '' }
onChange={ onSetLinkRel }
/>
</InspectorControls>
</>
);
}
export default ButtonEdit;