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 93fed5780e454d..af91abe6f356b6 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -15,17 +15,23 @@ import { __experimentalUnitControl as UnitControl, __experimentalVStack as VStack, DropZone, + Flex, FlexItem, FocalPointPicker, MenuItem, VisuallyHidden, __experimentalItemGroup as ItemGroup, + __experimentalItem as Item, __experimentalHStack as HStack, + __experimentalZStack as ZStack, __experimentalTruncate as Truncate, Dropdown, Placeholder, Spinner, + Button, + ColorIndicator, __experimentalDropdownContentWrapper as DropdownContentWrapper, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { __, _x, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; @@ -35,7 +41,6 @@ import { Platform, useRef, useState, - useEffect, useMemo, } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -45,10 +50,17 @@ import { isBlobURL } from '@wordpress/blob'; /** * Internal dependencies */ -import { useToolsPanelDropdownMenuProps, getResolvedValue } from './utils'; +import { + getValueFromVariable, + useToolsPanelDropdownMenuProps, + getResolvedValue, +} from './utils'; +import { useColorsPerOrigin, useGradientsPerOrigin } from './hooks'; import { setImmutably } from '../../utils/object'; import MediaReplaceFlow from '../media-replace-flow'; +import ColorGradientControl from '../colors-gradients/control'; import { store as blockEditorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; import { globalStylesDataKey, @@ -58,6 +70,7 @@ import { const IMAGE_BACKGROUND_TYPE = 'image'; const DEFAULT_CONTROLS = { backgroundImage: true, + background: true, }; const BACKGROUND_POPOVER_PROPS = { placement: 'left-start', @@ -169,13 +182,7 @@ function InspectorImagePreviewItem( { filename, label, className, - onToggleCallback = noop, } ) { - useEffect( () => { - if ( typeof toggleProps?.isOpen !== 'undefined' ) { - onToggleCallback( toggleProps?.isOpen ); - } - }, [ toggleProps?.isOpen, onToggleCallback ] ); return ( - { imgUrl && ( - - - - ) } + { const toggleProps = { onClick: onToggle, - className: + className: clsx( 'block-editor-global-styles-background-panel__dropdown-toggle', + { + 'is-open': isOpen, + } + ), 'aria-expanded': isOpen, 'aria-label': __( 'Background size, position and repeat options.' @@ -254,7 +261,6 @@ function BackgroundControlsPanel( { label={ imgLabel } toggleProps={ toggleProps } as="button" - onToggleCallback={ onToggleCallback } /> ); } } @@ -405,7 +411,7 @@ function BackgroundImageControls( { title || getFilename( url ) || __( 'Add background image' ); return ( -
@@ -453,7 +459,7 @@ function BackgroundImageControls( { onFilesDrop={ onFilesDrop } label={ __( 'Drop to upload' ) } /> -
+ ); } @@ -697,6 +703,146 @@ function BackgroundToolsPanel( { ); } +const { Tabs } = unlock( componentsPrivateApis ); + +function ColorPanelTab( { + isGradient, + inheritedValue, + userValue, + setValue, + colorGradientControlSettings, +} ) { + return ( + + ); +} + +const LabeledColorIndicators = ( { indicators, label } ) => ( + + + + { indicators.map( ( indicator, index ) => ( + + + + ) ) } + + + { label } + + + +); + +function ColorPanelDropdown( { + label, + indicators, + tabs, + colorGradientControlSettings, +} ) { + const currentTab = tabs.find( ( tab ) => tab.userValue !== undefined ); + const { key: firstTabKey, ...firstTab } = tabs[ 0 ] ?? {}; + return ( + + { + const toggleProps = { + onClick: onToggle, + className: clsx( + 'block-editor-panel-color-gradient-settings__dropdown', + { 'is-open': isOpen } + ), + 'aria-expanded': isOpen, + 'aria-label': sprintf( + /* translators: %s is the type of color property, e.g., "background" */ + __( 'Color %s styles' ), + label + ), + }; + + return ( + // no-restricted-syntax + + ); + } } + renderContent={ () => ( + +
+ { tabs.length === 1 && ( + + ) } + { tabs.length > 1 && ( + + + { tabs.map( ( tab ) => ( + + { tab.label } + + ) ) } + + + { tabs.map( ( tab ) => { + const { key: tabKey, ...restTabProps } = + tab; + return ( + + + + ); + } ) } + + ) } +
+
+ ) } + /> +
+ ); +} + export default function BackgroundPanel( { as: Wrapper = BackgroundToolsPanel, value, @@ -706,7 +852,8 @@ export default function BackgroundPanel( { panelId, defaultControls = DEFAULT_CONTROLS, defaultValues = {}, - headerLabel = __( 'Background image' ), + headerLabel = __( 'Tools' ), + showColorControl = false, } ) { /* * Resolve any inherited "ref" pointers. @@ -722,6 +869,75 @@ export default function BackgroundPanel( { _links: _settings[ globalStylesLinksDataKey ], }; }, [] ); + + const colors = useColorsPerOrigin( settings ); + const gradients = useGradientsPerOrigin( settings ); + const areCustomSolidsEnabled = settings?.color?.custom; + const areCustomGradientsEnabled = settings?.color?.customGradient; + const hasSolidColors = colors.length > 0 || areCustomSolidsEnabled; + const hasGradientColors = gradients.length > 0 || areCustomGradientsEnabled; + const encodeColorValue = ( colorValue ) => { + const allColors = colors.flatMap( + ( { colors: originColors } ) => originColors + ); + const colorObject = allColors.find( + ( { color } ) => color === colorValue + ); + return colorObject + ? 'var:preset|color|' + colorObject.slug + : colorValue; + }; + const encodeGradientValue = ( gradientValue ) => { + const allGradients = gradients.flatMap( + ( { gradients: originGradients } ) => originGradients + ); + const gradientObject = allGradients.find( + ( { gradient } ) => gradient === gradientValue + ); + return gradientObject + ? 'var:preset|gradient|' + gradientObject.slug + : gradientValue; + }; + + const decodeValue = ( rawValue ) => + getValueFromVariable( { settings }, '', rawValue ); + const shouldShowBackgroundColorControls = useHasBackgroundPanel( settings ); + const backgroundColor = decodeValue( inheritedValue?.color?.background ); + const userBackgroundColor = decodeValue( value?.color?.background ); + const gradient = decodeValue( inheritedValue?.color?.gradient ); + const userGradient = decodeValue( value?.color?.gradient ); + const hasBackground = () => !! userBackgroundColor || !! userGradient; + + const setBackgroundColor = ( newColor ) => { + const newValue = setImmutably( + value, + [ 'color', 'background' ], + encodeColorValue( newColor ) + ); + newValue.color.gradient = undefined; + onChange( newValue ); + }; + + const setGradient = ( newGradient ) => { + const newValue = setImmutably( + value, + [ 'color', 'gradient' ], + encodeGradientValue( newGradient ) + ); + newValue.color.background = undefined; + onChange( newValue ); + }; + + const resetBackgroundColor = () => { + const newValue = setImmutably( + value, + [ 'color', 'background' ], + undefined + ); + newValue.color.gradient = undefined; + onChange( newValue ); + }; + const resolvedInheritedValue = useMemo( () => { const resolvedValues = { background: {}, @@ -749,6 +965,7 @@ export default function BackgroundPanel( { return { ...previousValue, background: {}, + color: undefined, }; }, [] ); @@ -773,8 +990,6 @@ export default function BackgroundPanel( { settings?.background?.backgroundPosition || settings?.background?.backgroundRepeat ); - const [ isDropDownOpen, setIsDropDownOpen ] = useState( false ); - return ( -
!! value?.background } @@ -803,7 +1015,6 @@ export default function BackgroundPanel( { label={ title } filename={ title } url={ url } - onToggle={ setIsDropDownOpen } hasImageValue={ hasImageValue } > @@ -813,12 +1024,8 @@ export default function BackgroundPanel( { inheritedValue={ resolvedInheritedValue } displayInPanel onResetImage={ () => { - setIsDropDownOpen( false ); resetBackground(); } } - onRemoveImage={ () => - setIsDropDownOpen( false ) - } defaultValues={ defaultValues } /> { - setIsDropDownOpen( false ); resetBackground(); } } - onRemoveImage={ () => setIsDropDownOpen( false ) } /> ) } -
+ { shouldShowBackgroundColorControls && showColorControl && ( + + + + ) } +
); } diff --git a/packages/block-editor/src/components/global-styles/style.scss b/packages/block-editor/src/components/global-styles/style.scss index 6fb07eb090a55f..c6fe6a6a208f4b 100644 --- a/packages/block-editor/src/components/global-styles/style.scss +++ b/packages/block-editor/src/components/global-styles/style.scss @@ -39,7 +39,7 @@ .block-editor-global-styles__shadow-indicator { color: $gray-800; border: $gray-200 $border-width solid; - border-radius: $radius-block-ui; + border-radius: $radius-small; cursor: pointer; padding: 0; @@ -83,6 +83,7 @@ } .block-editor-global-styles-background-panel__image-tools-panel-item { + padding: 0; flex-grow: 1; border: 0; .components-dropdown { @@ -90,6 +91,10 @@ } } + .block-editor-global-styles-background-panel__color-tools-panel-item { + padding: 0; + } + .block-editor-global-styles-background-panel__inspector-preview-inner { height: 100%; } @@ -130,7 +135,7 @@ } &:focus { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); } } @@ -146,6 +151,12 @@ } } +.block-editor-global-styles-background-panel__color-tools-panel-item { + button { + padding: 0; + } +} + .block-editor-global-styles-background-panel__image-preview-content, .block-editor-global-styles-background-panel__dropdown-toggle { height: 100%; @@ -157,6 +168,15 @@ cursor: pointer; background: transparent; border: none; + + &.is-open { + background-color: $gray-100; + } + + &:focus { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + outline: none; + } } .block-editor-global-styles-background-panel__inspector-media-replace-title { @@ -166,7 +186,6 @@ // 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-preview-inner { @@ -179,25 +198,11 @@ .block-editor-global-styles-background-panel__inspector-image-indicator { background-size: cover; - border-radius: $radius-round; - width: 20px; - height: 20px; - display: block; - position: relative; -} + border-radius: $radius-small; -.block-editor-global-styles-background-panel__inspector-image-indicator::after { - content: ""; - position: absolute; - top: -1px; - left: -1px; - bottom: -1px; - right: -1px; - border-radius: $radius-round; - box-shadow: inset 0 0 0 $border-width rgba(0, 0, 0, 0.2); - // Show a thin outline in Windows high contrast mode, otherwise the button is invisible. - border: 1px solid transparent; - box-sizing: inherit; + &.has-image { + background-image: var(--image-url); + } } .block-editor-global-styles-background-panel__dropdown-content-wrapper { @@ -207,7 +212,7 @@ .components-focal-point-picker-wrapper { background-color: $gray-100; width: 100%; - border-radius: $radius-block-ui; + border-radius: $radius-small; border: $border-width solid $gray-300; } @@ -226,6 +231,12 @@ z-index: z-index(".block-editor-global-styles-background-panel__popover"); } +.block-editor-global-styles-background-panel__popover { + .block-editor-global-styles-background-panel__image-tools-panel-item { + padding: 0; + } +} + .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. diff --git a/packages/edit-site/src/components/global-styles/background-panel.js b/packages/edit-site/src/components/global-styles/background-panel.js index e185079d8cee04..553e2e67c0598a 100644 --- a/packages/edit-site/src/components/global-styles/background-panel.js +++ b/packages/edit-site/src/components/global-styles/background-panel.js @@ -50,6 +50,7 @@ export default function BackgroundPanel() { onChange={ setStyle } settings={ settings } defaultValues={ BACKGROUND_DEFAULT_VALUES } + showColorControl /> ); } diff --git a/packages/edit-site/src/components/global-styles/root-menu.js b/packages/edit-site/src/components/global-styles/root-menu.js index 6db4621299f1e2..cbf052cf792f84 100644 --- a/packages/edit-site/src/components/global-styles/root-menu.js +++ b/packages/edit-site/src/components/global-styles/root-menu.js @@ -4,6 +4,7 @@ import { __experimentalItemGroup as ItemGroup } from '@wordpress/components'; import { typography, + background, color, layout, shadow as shadowIcon, @@ -23,6 +24,7 @@ const { useHasColorPanel, useGlobalSetting, useSettingsForBlockElement, + useHasBackgroundPanel, } = unlock( blockEditorPrivateApis ); function RootMenu() { @@ -33,10 +35,20 @@ function RootMenu() { const hasShadowPanel = true; // useHasShadowPanel( settings ); const hasDimensionsPanel = useHasDimensionsPanel( settings ); const hasLayoutPanel = hasDimensionsPanel; + const hasBackgroundPanel = useHasBackgroundPanel( settings ); return ( <> + { hasBackgroundPanel && ( + + { __( 'Background' ) } + + ) } { hasTypographyPanel && ( + + { hasBackgroundPanel && } + + ); +} + +export default ScreenBackground; diff --git a/packages/edit-site/src/components/global-styles/screen-layout.js b/packages/edit-site/src/components/global-styles/screen-layout.js index 1e68309fe01866..e7794985b7984f 100644 --- a/packages/edit-site/src/components/global-styles/screen-layout.js +++ b/packages/edit-site/src/components/global-styles/screen-layout.js @@ -8,31 +8,20 @@ import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; * Internal dependencies */ import DimensionsPanel from './dimensions-panel'; -import BackgroundPanel from './background-panel'; import ScreenHeader from './header'; import { unlock } from '../../lock-unlock'; -const { - useHasBackgroundPanel, - useHasDimensionsPanel, - useGlobalSetting, - useSettingsForBlockElement, -} = unlock( blockEditorPrivateApis ); +const { useHasDimensionsPanel, useGlobalSetting, useSettingsForBlockElement } = + unlock( blockEditorPrivateApis ); function ScreenLayout() { const [ rawSettings ] = useGlobalSetting( '' ); const settings = useSettingsForBlockElement( rawSettings ); const hasDimensionsPanel = useHasDimensionsPanel( settings ); - /* - * Use the raw settings to determine if the background panel should be displayed, - * as the background panel is not dependent on the block element settings. - */ - const hasBackgroundPanel = useHasBackgroundPanel( rawSettings ); return ( <> { hasDimensionsPanel && } - { hasBackgroundPanel && } ); } diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js index 54bd4f97390a8f..14420036337e33 100644 --- a/packages/edit-site/src/components/global-styles/ui.js +++ b/packages/edit-site/src/components/global-styles/ui.js @@ -44,6 +44,7 @@ import ScreenStyleVariations from './screen-style-variations'; import StyleBook from '../style-book'; import ScreenCSS from './screen-css'; import ScreenRevisions from './screen-revisions'; +import ScreenBackground from './screen-background'; import { unlock } from '../../lock-unlock'; import { store as editSiteStore } from '../../store'; @@ -371,6 +372,9 @@ function GlobalStylesUI() { + + + { blocks.map( ( block ) => ( + + +); + +export default background;