@wordpress/block-editor
Version:
223 lines (206 loc) • 5.21 kB
JavaScript
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
__experimentalVStack as VStack,
__experimentalHeading as Heading,
__experimentalHStack as HStack,
__experimentalDropdownContentWrapper as DropdownContentWrapper,
Button,
FlexItem,
Dropdown,
Composite,
Tooltip,
} from '@wordpress/components';
import { useMemo, useRef } from '@wordpress/element';
import { shadow as shadowIcon, Icon, check, reset } from '@wordpress/icons';
/**
* External dependencies
*/
import clsx from 'clsx';
/**
* Shared reference to an empty array for cases where it is important to avoid
* returning a new array reference on every invocation.
*
* @type {Array}
*/
const EMPTY_ARRAY = [];
export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) {
const shadows = useShadowPresets( settings );
return (
<div className="block-editor-global-styles__shadow-popover-container">
<VStack spacing={ 4 }>
<Heading level={ 5 }>{ __( 'Drop shadow' ) }</Heading>
<ShadowPresets
presets={ shadows }
activeShadow={ shadow }
onSelect={ onShadowChange }
/>
<div className="block-editor-global-styles__clear-shadow">
<Button
__next40pxDefaultSize
variant="tertiary"
onClick={ () => onShadowChange( undefined ) }
disabled={ ! shadow }
accessibleWhenDisabled
>
{ __( 'Clear' ) }
</Button>
</div>
</VStack>
</div>
);
}
export function ShadowPresets( { presets, activeShadow, onSelect } ) {
return ! presets ? null : (
<Composite
role="listbox"
className="block-editor-global-styles__shadow__list"
aria-label={ __( 'Drop shadows' ) }
>
{ presets.map( ( { name, slug, shadow } ) => (
<ShadowIndicator
key={ slug }
label={ name }
isActive={ shadow === activeShadow }
type={ slug === 'unset' ? 'unset' : 'preset' }
onSelect={ () =>
onSelect( shadow === activeShadow ? undefined : shadow )
}
shadow={ shadow }
/>
) ) }
</Composite>
);
}
export function ShadowIndicator( { type, label, isActive, onSelect, shadow } ) {
return (
<Tooltip text={ label }>
<Composite.Item
role="option"
aria-label={ label }
aria-selected={ isActive }
className={ clsx( 'block-editor-global-styles__shadow__item', {
'is-active': isActive,
} ) }
render={
<button
className={ clsx(
'block-editor-global-styles__shadow-indicator',
{
unset: type === 'unset',
}
) }
onClick={ onSelect }
style={ { boxShadow: shadow } }
aria-label={ label }
>
{ isActive && <Icon icon={ check } /> }
</button>
}
/>
</Tooltip>
);
}
export function ShadowPopover( { shadow, onShadowChange, settings } ) {
const popoverProps = {
placement: 'left-start',
offset: 36,
shift: true,
};
return (
<Dropdown
popoverProps={ popoverProps }
className="block-editor-global-styles__shadow-dropdown"
renderToggle={ renderShadowToggle( shadow, onShadowChange ) }
renderContent={ () => (
<DropdownContentWrapper paddingSize="medium">
<ShadowPopoverContainer
shadow={ shadow }
onShadowChange={ onShadowChange }
settings={ settings }
/>
</DropdownContentWrapper>
) }
/>
);
}
function renderShadowToggle( shadow, onShadowChange ) {
return ( { onToggle, isOpen } ) => {
const shadowButtonRef = useRef( undefined );
const toggleProps = {
onClick: onToggle,
className: clsx(
'block-editor-global-styles__shadow-dropdown-toggle',
{ 'is-open': isOpen }
),
'aria-expanded': isOpen,
ref: shadowButtonRef,
};
const removeButtonProps = {
onClick: () => {
if ( isOpen ) {
onToggle();
}
onShadowChange( undefined );
// Return focus to parent button.
shadowButtonRef.current?.focus();
},
className: clsx(
'block-editor-global-styles__shadow-editor__remove-button',
{ 'is-open': isOpen }
),
label: __( 'Remove' ),
};
return (
<>
<Button __next40pxDefaultSize { ...toggleProps }>
<HStack justify="flex-start">
<Icon
className="block-editor-global-styles__toggle-icon"
icon={ shadowIcon }
size={ 24 }
/>
<FlexItem>{ __( 'Drop shadow' ) }</FlexItem>
</HStack>
</Button>
{ !! shadow && (
<Button
__next40pxDefaultSize
size="small"
icon={ reset }
{ ...removeButtonProps }
/>
) }
</>
);
};
}
export function useShadowPresets( settings ) {
return useMemo( () => {
if ( ! settings?.shadow ) {
return EMPTY_ARRAY;
}
const defaultPresetsEnabled = settings?.shadow?.defaultPresets;
const {
default: defaultShadows,
theme: themeShadows,
custom: customShadows,
} = settings?.shadow?.presets ?? {};
const unsetShadow = {
name: __( 'Unset' ),
slug: 'unset',
shadow: 'none',
};
const shadowPresets = [
...( ( defaultPresetsEnabled && defaultShadows ) || EMPTY_ARRAY ),
...( themeShadows || EMPTY_ARRAY ),
...( customShadows || EMPTY_ARRAY ),
];
if ( shadowPresets.length ) {
shadowPresets.unshift( unsetShadow );
}
return shadowPresets;
}, [ settings ] );
}