diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index c8c35acfce486..a110c0052b56e 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -65,8 +65,10 @@ $z-layers: ( // Needs to be below media library that has a z-index of 160000. "{core/video track editor popover}": 160000 - 10, - // Needs to be below media library that has a z-index of 160000. + // Needs to be below media library (.media-model) that has a z-index of 160000. ".block-editor-format-toolbar__image-popover": 160000 - 10, + // Below the media library backdrop (.media-modal-backdrop), which has a z-index of 159900. + ".block-editor-global-styles-background-panel__popover": 159900 - 10, // Small screen inner blocks overlay must be displayed above drop zone, // settings menu, and movers. diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index d4b2468bf2a25..6baef04f39b6b 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -22,11 +22,19 @@ import { __experimentalItemGroup as ItemGroup, __experimentalHStack as HStack, __experimentalTruncate as Truncate, + Dropdown, + __experimentalDropdownContentWrapper as DropdownContentWrapper, } from '@wordpress/components'; import { __, _x, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { getFilename } from '@wordpress/url'; -import { useCallback, Platform, useRef } from '@wordpress/element'; +import { + useCallback, + Platform, + useRef, + useState, + useEffect, +} from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { focus } from '@wordpress/dom'; import { isBlobURL } from '@wordpress/blob'; @@ -43,8 +51,14 @@ import { getResolvedThemeFilePath } from './theme-file-uri-utils'; const IMAGE_BACKGROUND_TYPE = 'image'; const DEFAULT_CONTROLS = { backgroundImage: true, - backgroundSize: false, }; +const BACKGROUND_POPOVER_PROPS = { + placement: 'left-start', + offset: 36, + shift: true, + className: 'block-editor-global-styles-background-panel__popover', +}; +const noop = () => {}; /** * Checks site settings to see if the background panel may be used. @@ -141,21 +155,30 @@ export const backgroundPositionToCoords = ( value ) => { return { x, y }; }; -function InspectorImagePreview( { label, filename, url: imgUrl } ) { - const imgLabel = - label || getFilename( imgUrl ) || __( 'Add background image' ); - +function InspectorImagePreviewItem( { + as = 'span', + imgUrl, + toggleProps = {}, + filename, + label, + className, + onToggleCallback = noop, +} ) { + useEffect( () => { + if ( typeof toggleProps?.isOpen !== 'undefined' ) { + onToggleCallback( toggleProps?.isOpen ); + } + }, [ toggleProps?.isOpen, onToggleCallback ] ); return ( - - + + { imgUrl && ( ) } - + - { imgLabel } + { label } { imgUrl ? sprintf( /* translators: %s: file name */ __( 'Background image: %s' ), - filename || imgLabel + filename || label ) : __( 'No background image selected' ) } @@ -188,13 +211,64 @@ function InspectorImagePreview( { label, filename, url: imgUrl } ) { ); } -function BackgroundImageToolsPanelItem( { - panelId, - isShownByDefault, +function BackgroundControlsPanel( { + label, + filename, + url: imgUrl, + children, + onToggle: onToggleCallback = noop, + hasImageValue, +} ) { + if ( ! hasImageValue ) { + return; + } + + const imgLabel = + label || getFilename( imgUrl ) || __( 'Add background image' ); + + return ( + { + const toggleProps = { + onClick: onToggle, + className: + 'block-editor-global-styles-background-panel__dropdown-toggle', + 'aria-expanded': isOpen, + 'aria-label': __( + 'Background size, position and repeat options.' + ), + isOpen, + }; + return ( + + ); + } } + renderContent={ () => ( + + { children } + + ) } + /> + ); +} + +function BackgroundImageControls( { onChange, style, inheritedValue, - themeFileURIs, + onRemoveImage = noop, + displayInPanel, } ) { const mediaUpload = useSelect( ( select ) => select( blockEditorStore ).getSettings().mediaUpload, @@ -204,9 +278,7 @@ function BackgroundImageToolsPanelItem( { const { id, title, url } = style?.background?.backgroundImage || { ...inheritedValue?.background?.backgroundImage, }; - const replaceContainerRef = useRef(); - const { createErrorNotice } = useDispatch( noticesStore ); const onUploadError = ( message ) => { createErrorNotice( message, { type: 'snackbar' } ); @@ -279,16 +351,6 @@ function BackgroundImageToolsPanelItem( { } ); }; - const resetAllFilter = useCallback( ( previousValue ) => { - return { - ...previousValue, - style: { - ...previousValue.style, - background: undefined, - }, - }; - }, [] ); - const hasValue = hasBackgroundImageValue( style ); const closeAndFocus = () => { @@ -307,72 +369,66 @@ function BackgroundImageToolsPanelItem( { setImmutably( style, [ 'background', 'backgroundImage' ], 'none' ) ); const canRemove = ! hasValue && hasBackgroundImageValue( inheritedValue ); + const imgLabel = + title || getFilename( url ) || __( 'Add background image' ); return ( - hasValue } - label={ __( 'Background image' ) } - onDeselect={ resetBackgroundImage } - isShownByDefault={ isShownByDefault } - resetAllFilter={ resetAllFilter } - panelId={ panelId } +
-
+ } + variant="secondary" > - - } - variant="secondary" - > - { canRemove && ( - { - closeAndFocus(); - onRemove(); - } } - > - { __( 'Remove' ) } - - ) } - { hasValue && ( - { - closeAndFocus(); - resetBackgroundImage(); - } } - > - { __( 'Reset ' ) } - - ) } - - -
- + { canRemove && ( + { + closeAndFocus(); + onRemove(); + } } + > + { __( 'Remove' ) } + + ) } + { hasValue && ( + { + closeAndFocus(); + onRemoveImage(); + } } + > + { __( 'Reset ' ) } + + ) } + + +
); } -function BackgroundSizeToolsPanelItem( { - panelId, - isShownByDefault, +function BackgroundSizeControls( { onChange, style, inheritedValue, @@ -417,22 +473,6 @@ function BackgroundSizeToolsPanelItem( { ( currentValueForToggle === 'cover' && repeatValue === undefined ) ); - const hasValue = hasBackgroundSizeValue( style ); - - const resetAllFilter = useCallback( ( previousValue ) => { - return { - ...previousValue, - style: { - ...previousValue.style, - background: { - ...previousValue.style?.background, - backgroundRepeat: undefined, - backgroundSize: undefined, - }, - }, - }; - }, [] ); - const updateBackgroundSize = ( next ) => { // When switching to 'contain' toggle the repeat off. let nextRepeat = repeatValue; @@ -502,30 +542,11 @@ function BackgroundSizeToolsPanelItem( { ) ); - const resetBackgroundSize = () => - onChange( - setImmutably( style, [ 'background' ], { - ...style?.background, - backgroundPosition: undefined, - backgroundRepeat: undefined, - backgroundSize: undefined, - } ) - ); - return ( - hasValue } - label={ __( 'Size' ) } - onDeselect={ resetBackgroundSize } - isShownByDefault={ isShownByDefault } - resetAllFilter={ resetAllFilter } - panelId={ panelId } - > + - { currentValueForToggle !== undefined && - currentValueForToggle !== 'cover' && - currentValueForToggle !== 'contain' ? ( - - ) : null } - { currentValueForToggle !== 'cover' && ( - - ) } + + ); @@ -638,8 +658,24 @@ export default function BackgroundPanel( { background: {}, }; }, [] ); - const shouldShowBackgroundSizeControls = - settings?.background?.backgroundSize; + + const resetBackground = () => + onChange( setImmutably( value, [ 'background' ], {} ) ); + + const { title, url } = value?.background?.backgroundImage || { + ...inheritedValue?.background?.backgroundImage, + }; + const hasImageValue = + hasBackgroundImageValue( value ) || + hasBackgroundImageValue( inheritedValue ); + + const shouldShowBackgroundImageControls = + hasImageValue && + ( settings?.background?.backgroundSize || + settings?.background?.backgroundPosition || + settings?.background?.backgroundRepeat ); + + const [ isDropDownOpen, setIsDropDownOpen ] = useState( false ); return ( - + { shouldShowBackgroundImageControls ? ( + + + { + setIsDropDownOpen( false ); + resetBackground(); + } } + /> + + + + ) : ( + + ) } + + + { /* Dummy ToolsPanel items, so we can control what's in the dropdown popover */ } + hasImageValue } + label={ __( 'Image' ) } + onDeselect={ resetBackground } isShownByDefault={ defaultControls.backgroundImage } - style={ value } - inheritedValue={ inheritedValue } - themeFileURIs={ themeFileURIs } + panelId={ panelId } + className="block-editor-global-styles-background-panel__hidden-tools-panel-item" /> - { shouldShowBackgroundSizeControls && ( - - ) } ); } diff --git a/packages/block-editor/src/components/global-styles/style.scss b/packages/block-editor/src/components/global-styles/style.scss index 1630ac2abde57..f55e2da643704 100644 --- a/packages/block-editor/src/components/global-styles/style.scss +++ b/packages/block-editor/src/components/global-styles/style.scss @@ -71,20 +71,55 @@ direction: ltr; } + .block-editor-global-styles-background-panel__inspector-media-replace-container { - position: relative; + border: 1px solid $gray-300; + border-radius: 2px; + // Full width. ToolsPanel lays out children in a grid. + grid-column: 1 / -1; + + &.is-open { + background-color: $gray-100; + } + + .block-editor-global-styles-background-panel__image-tools-panel-item { + flex-grow: 1; + border: 0; + .components-dropdown { + display: block; + } + } + + .block-editor-global-styles-background-panel__inspector-preview-inner { + height: 100%; + } + + .components-dropdown { + display: block; + height: 36px; + } +} + +.block-editor-global-styles-background-panel__image-tools-panel-item { + border: 1px solid $gray-300; + border-radius: 2px; + // Full width. ToolsPanel lays out children in a grid. + grid-column: 1 / -1; // Since there is no option to skip rendering the drag'n'drop icon in drop // zone, we hide it for now. .components-drop-zone__content-icon { display: none; } + .components-dropdown { + display: block; + height: 36px; + } + button.components-button { color: $gray-900; - box-shadow: inset 0 0 0 $border-width $gray-400; width: 100%; display: block; - height: $grid-unit-50; &:hover { color: var(--wp-admin-theme-color); @@ -94,28 +129,37 @@ box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); } } +} - .block-editor-global-styles-background-panel__inspector-media-replace-title { - word-break: break-all; - // The Button component is white-space: nowrap, and that won't work with line-clamp. - white-space: normal; +.block-editor-global-styles-background-panel__image-preview-content, +.block-editor-global-styles-background-panel__dropdown-toggle { + height: 100%; + width: 100%; + padding-left: $grid-unit-15; +} - // Without this, the ellipsis can sometimes be partially hidden by the Button padding. - text-align: start; - text-align-last: center; - } +.block-editor-global-styles-background-panel__dropdown-toggle { + cursor: pointer; + background: transparent; + border: none; +} - .components-dropdown { - display: block; - } +.block-editor-global-styles-background-panel__inspector-media-replace-title { + word-break: break-all; + // The Button component is white-space: nowrap, and that won't work with line-clamp. + white-space: normal; + + // Without this, the ellipsis can sometimes be partially hidden by the Button padding. + text-align: start; + text-align-last: center; } -.block-editor-global-styles-background-panel__inspector-image-indicator-wrapper { - display: block; - width: 20px; - height: 20px; - flex: none; - background: #fff; +.block-editor-global-styles-background-panel__inspector-preview-inner { + .block-editor-global-styles-background-panel__inspector-image-indicator-wrapper { + width: 20px; + height: 20px; + min-width: auto; + } } .block-editor-global-styles-background-panel__inspector-image-indicator { @@ -141,3 +185,38 @@ box-sizing: inherit; } +.block-editor-global-styles-background-panel__dropdown-content-wrapper { + min-width: 260px; + overflow-x: hidden; + .components-base-control__help, + .components-toggle-control { + margin-bottom: 0; + } + .components-focal-point-picker__media--image { + max-height: clamp(120px, 9vh, 280px); + } +} + +.block-editor-global-styles-background-panel__hidden-tools-panel-item { + height: 0; + width: 0; + position: absolute; +} + +// Push control panel into the background when the media modal is open. +.modal-open .block-editor-global-styles-background-panel__popover { + z-index: z-index(".block-editor-global-styles-background-panel__popover"); +} + +.block-editor-global-styles-background-panel__media-replace-popover { + .components-popover__content { + // width of block-editor-global-styles-background-panel__dropdown-content-wrapper minus padding. + width: 226px; + } + .components-button { + padding: 0 $grid-unit-10; + } + .components-button .components-menu-items__item-icon.has-icon-right { + margin-left: $grid-unit-30 - $grid-unit-10; + } +} diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js index 3e23efcbf5fc9..c22e5cf20ff16 100644 --- a/packages/block-editor/src/hooks/background.js +++ b/packages/block-editor/src/hooks/background.js @@ -147,11 +147,6 @@ export function BackgroundImagePanel( { return null; } - const defaultControls = getBlockSupport( name, [ - BACKGROUND_SUPPORT_KEY, - '__experimentalDefaultControls', - ] ); - const onChange = ( newStyle ) => { setAttributes( { style: cleanEmptyObject( newStyle ), @@ -172,7 +167,6 @@ export function BackgroundImagePanel( { );