diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js
index c2a1b313fcd765..e3061f0004af45 100644
--- a/packages/block-editor/src/components/block-inspector/index.js
+++ b/packages/block-editor/src/components/block-inspector/index.js
@@ -67,6 +67,10 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => {
+
) }
+
+ (
+
+ ) }
+ renderContent={ () => (
+
+ ) }
+ />
+
+ );
+}
diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js
index df21d82dda5e25..87ef64deccc6f5 100644
--- a/packages/block-editor/src/components/index.js
+++ b/packages/block-editor/src/components/index.js
@@ -51,6 +51,7 @@ export { default as __experimentalTextTransformControl } from './text-transform-
export { default as __experimentalColorGradientControl } from './colors-gradients/control';
export { default as __experimentalColorGradientSettingsDropdown } from './colors-gradients/dropdown';
export { default as __experimentalPanelColorGradientSettings } from './colors-gradients/panel-color-gradient-settings';
+export { default as __experimentalToolsPanelColorDropdown } from './colors-gradients/tools-panel-color-dropdown';
export {
default as __experimentalImageEditor,
ImageEditingProvider as __experimentalImageEditingProvider,
diff --git a/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js b/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js
index 354ee0c14e4965..d582548b5d64ef 100644
--- a/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js
+++ b/packages/block-editor/src/components/inspector-controls/block-support-tools-panel.js
@@ -70,6 +70,8 @@ export default function BlockSupportToolsPanel( { children, group, label } ) {
panelId={ panelId }
hasInnerWrapper={ true }
shouldRenderPlaceholderItems={ true } // Required to maintain fills ordering.
+ __experimentalFirstVisibleItemClass="first"
+ __experimentalLastVisibleItemClass="last"
>
{ children }
diff --git a/packages/block-editor/src/components/inspector-controls/groups.js b/packages/block-editor/src/components/inspector-controls/groups.js
index 61de91ef1e9e4e..0b7d1d2f4479f9 100644
--- a/packages/block-editor/src/components/inspector-controls/groups.js
+++ b/packages/block-editor/src/components/inspector-controls/groups.js
@@ -6,6 +6,7 @@ import { createSlotFill } from '@wordpress/components';
const InspectorControlsDefault = createSlotFill( 'InspectorControls' );
const InspectorControlsAdvanced = createSlotFill( 'InspectorAdvancedControls' );
const InspectorControlsBorder = createSlotFill( 'InspectorControlsBorder' );
+const InspectorControlsColor = createSlotFill( 'InspectorControlsColor' );
const InspectorControlsDimensions = createSlotFill(
'InspectorControlsDimensions'
);
@@ -17,6 +18,7 @@ const groups = {
default: InspectorControlsDefault,
advanced: InspectorControlsAdvanced,
border: InspectorControlsBorder,
+ color: InspectorControlsColor,
dimensions: InspectorControlsDimensions,
typography: InspectorControlsTypography,
};
diff --git a/packages/block-editor/src/hooks/border-color.js b/packages/block-editor/src/hooks/border-color.js
index 07b34fa6ba795a..e26af52e364f5e 100644
--- a/packages/block-editor/src/hooks/border-color.js
+++ b/packages/block-editor/src/hooks/border-color.js
@@ -68,7 +68,7 @@ export function BorderColorEdit( props ) {
// Detect changes in the color attributes and update the colorValue to keep the
// UI in sync. This is necessary for situations when border controls interact with
- // eachother: eg, setting the border width to zero causes the color and style
+ // each other: eg, setting the border width to zero causes the color and style
// selections to be cleared.
useEffect( () => {
setColorValue(
diff --git a/packages/block-editor/src/hooks/color-panel.js b/packages/block-editor/src/hooks/color-panel.js
index c360ed4e87c6cb..e4482693699ba2 100644
--- a/packages/block-editor/src/hooks/color-panel.js
+++ b/packages/block-editor/src/hooks/color-panel.js
@@ -2,13 +2,12 @@
* WordPress dependencies
*/
import { useState, useEffect } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
-import PanelColorGradientSettings from '../components/colors-gradients/panel-color-gradient-settings';
import ContrastChecker from '../components/contrast-checker';
+import ToolsPanelColorDropdown from '../components/colors-gradients/tools-panel-color-dropdown';
import InspectorControls from '../components/inspector-controls';
import { __unstableUseBlockRef as useBlockRef } from '../components/block-list/use-block-props/use-block-refs';
@@ -21,7 +20,6 @@ export default function ColorPanel( {
settings,
clientId,
enableContrastChecking = true,
- showTitle = true,
} ) {
const [ detectedBackgroundColor, setDetectedBackgroundColor ] = useState();
const [ detectedColor, setDetectedColor ] = useState();
@@ -61,25 +59,23 @@ export default function ColorPanel( {
} );
return (
-
-
- { enableContrastChecking && (
-
- ) }
-
+
+ { settings.map( ( setting, index ) => (
+
+ ) ) }
+ { enableContrastChecking && (
+
+ ) }
);
}
diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js
index c911bc43670b68..fcbd79e92596b5 100644
--- a/packages/block-editor/src/hooks/color.js
+++ b/packages/block-editor/src/hooks/color.js
@@ -77,6 +77,125 @@ const hasTextColorSupport = ( blockType ) => {
return colorSupport && colorSupport.text !== false;
};
+/**
+ * Checks whether a color has been set either with a named preset color in
+ * a top level block attribute or as a custom value within the style attribute
+ * object.
+ *
+ * @param {string} name Name of the color to check.
+ * @return {boolean} Whether or not a color has a value.
+ */
+const hasColor = ( name ) => ( props ) => {
+ if ( name === 'background' ) {
+ return (
+ !! props.attributes.backgroundColor ||
+ !! props.attributes.style?.color?.background ||
+ !! props.attributes.gradient ||
+ !! props.attributes.style?.color?.gradient
+ );
+ }
+
+ if ( name === 'link' ) {
+ return !! props.attributes.style?.elements?.link?.color?.text;
+ }
+
+ return (
+ !! props.attributes[ `${ name }Color` ] ||
+ !! props.attributes.style?.color?.[ name ]
+ );
+};
+
+/**
+ * Clears a single color property from a style object.
+ *
+ * @param {Array} path Path to color property to clear within styles object.
+ * @param {Object} style Block attributes style object.
+ * @return {Object} Styles with the color property omitted.
+ */
+const clearColorFromStyles = ( path, style ) =>
+ cleanEmptyObject( immutableSet( style, path, undefined ) );
+
+/**
+ * Resets the block attributes for text color.
+ *
+ * @param {Object} props Current block props.
+ * @param {Object} props.attributes Block attributes.
+ * @param {Function} props.setAttributes Block's setAttributes prop used to apply reset.
+ */
+const resetTextColor = ( { attributes, setAttributes } ) => {
+ setAttributes( {
+ textColor: undefined,
+ style: clearColorFromStyles( [ 'color', 'text' ], attributes.style ),
+ } );
+};
+
+/**
+ * Clears text color related properties from supplied attributes.
+ *
+ * @param {Object} attributes Block attributes.
+ * @return {Object} Update block attributes with text color properties omitted.
+ */
+const resetAllTextFilter = ( attributes ) => ( {
+ textColor: undefined,
+ style: clearColorFromStyles( [ 'color', 'text' ], attributes.style ),
+} );
+
+/**
+ * Resets the block attributes for link color.
+ *
+ * @param {Object} props Current block props.
+ * @param {Object} props.attributes Block attributes.
+ * @param {Function} props.setAttributes Block's setAttributes prop used to apply reset.
+ */
+const resetLinkColor = ( { attributes, setAttributes } ) => {
+ const path = [ 'elements', 'link', 'color', 'text' ];
+ setAttributes( { style: clearColorFromStyles( path, attributes.style ) } );
+};
+
+/**
+ * Clears link color related properties from supplied attributes.
+ *
+ * @param {Object} attributes Block attributes.
+ * @return {Object} Update block attributes with link color properties omitted.
+ */
+const resetAllLinkFilter = ( attributes ) => ( {
+ style: clearColorFromStyles(
+ [ 'elements', 'link', 'color', 'text' ],
+ attributes.style
+ ),
+} );
+
+/**
+ * Clears all background color related properties including gradients from
+ * supplied block attributes.
+ *
+ * @param {Object} attributes Block attributes.
+ * @return {Object} Block attributes with background and gradient omitted.
+ */
+const clearBackgroundAndGradient = ( attributes ) => ( {
+ backgroundColor: undefined,
+ gradient: undefined,
+ style: {
+ ...attributes.style,
+ color: {
+ ...attributes.style?.color,
+ background: undefined,
+ gradient: undefined,
+ },
+ },
+} );
+
+/**
+ * Resets the block attributes for both background color and gradient.
+ *
+ * @param {Object} props Current block props.
+ * @param {Object} props.attributes Block attributes.
+ * @param {Function} props.setAttributes Block's setAttributes prop used to apply reset.
+ */
+const resetBackgroundAndGradient = ( { attributes, setAttributes } ) => {
+ setAttributes( clearBackgroundAndGradient( attributes ) );
+};
+
/**
* Filters registered block settings, extending attributes to include
* `backgroundColor` and `textColor` attribute.
@@ -136,7 +255,7 @@ export function addSaveProps( props, blockType, attributes ) {
const hasGradient = hasGradientSupport( blockType );
- // I'd have prefered to avoid the "style" attribute usage here
+ // I'd have preferred to avoid the "style" attribute usage here
const { backgroundColor, textColor, gradient, style } = attributes;
const backgroundClass = getColorClassName(
@@ -168,7 +287,7 @@ export function addSaveProps( props, blockType, attributes ) {
}
/**
- * Filters registered block settings to extand the block edit wrapper
+ * Filters registered block settings to extend the block edit wrapper
* to apply the desired styles and classnames properly.
*
* @param {Object} settings Original block settings.
@@ -370,6 +489,11 @@ export function ColorEdit( props ) {
const enableContrastChecking =
Platform.OS === 'web' && ! gradient && ! style?.color?.gradient;
+ const defaultColorControls = getBlockSupport( props.name, [
+ COLOR_SUPPORT_KEY,
+ '__experimentalDefaultControls',
+ ] );
+
return (
hasColor( 'text' )( props ),
+ onDeselect: () => resetTextColor( props ),
+ resetAllFilter: resetAllTextFilter,
},
]
: [] ),
@@ -405,6 +533,13 @@ export function ColorEdit( props ) {
onGradientChange: hasGradientColor
? onChangeGradient
: undefined,
+ isShownByDefault:
+ defaultColorControls?.background,
+ hasValue: () =>
+ hasColor( 'background' )( props ),
+ onDeselect: () =>
+ resetBackgroundAndGradient( props ),
+ resetAllFilter: clearBackgroundAndGradient,
},
]
: [] ),
@@ -419,6 +554,10 @@ export function ColorEdit( props ) {
),
clearable: !! style?.elements?.link?.color
?.text,
+ isShownByDefault: defaultColorControls?.link,
+ hasValue: () => hasColor( 'link' )( props ),
+ onDeselect: () => resetLinkColor( props ),
+ resetAllFilter: resetAllLinkFilter,
},
]
: [] ),
diff --git a/packages/block-editor/src/hooks/color.scss b/packages/block-editor/src/hooks/color.scss
new file mode 100644
index 00000000000000..42e6ff928d3dc4
--- /dev/null
+++ b/packages/block-editor/src/hooks/color.scss
@@ -0,0 +1,85 @@
+.color-block-support-panel {
+ .block-editor-contrast-checker {
+ /**
+ * Contrast checkers are forced to the bottom of the panel so all
+ * injected color controls can appear as a single item group without
+ * the contrast checkers suddenly appearing between items.
+ */
+ order: 9999;
+ grid-column: span 2;
+ margin-top: $grid-unit-20;
+
+ .components-notice__content {
+ margin-right: 0;
+ }
+ }
+
+ /* Increased specificity required to remove the slot wrapper's row gap */
+ {&} {
+ .color-block-support-panel__inner-wrapper {
+ row-gap: 0;
+ }
+ }
+
+ /**
+ * The following styles replicate the separated border of the
+ * `ItemGroup` component but allows for hidden items. This is because
+ * to maintain the order of `ToolsPanel` controls, each `ToolsPanelItem`
+ * must at least render a placeholder which would otherwise interfere
+ * with the `:last-child` styles.
+ */
+ .block-editor-tools-panel-color-gradient-settings__item {
+ padding: 0;
+
+ // Border styles.
+ border-left: 1px solid rgba(0, 0, 0, 0.1);
+ border-right: 1px solid rgba(0, 0, 0, 0.1);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+
+ &.first {
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+ }
+
+ &.last {
+ border-bottom-left-radius: 2px;
+ border-bottom-right-radius: 2px;
+ }
+
+ > div,
+ > div > button {
+ border-radius: inherit;
+ }
+ }
+
+ .block-editor-panel-color-gradient-settings__color-indicator {
+ // Show a diagonal line (crossed out) for empty swatches.
+ background: linear-gradient(-45deg, transparent 48%, $gray-300 48%, $gray-300 52%, transparent 52%);
+ }
+
+ /**
+ * The following few styles fix the layout and spacing for the due to the
+ * introduced wrapper element by the `Item` component.
+ */
+ .block-editor-tools-panel-color-dropdown {
+ display: block;
+ padding: 0;
+
+ > button {
+ height: 46px;
+
+ &.is-open {
+ background: $gray-100;
+ color: var(--wp-admin-theme-color);
+ }
+ }
+ }
+
+ .color-block-support-panel__item-group {
+ > div {
+ grid-column: span 2;
+ border-radius: inherit;
+ }
+ }
+}
diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss
index 6f20a3b1b29012..fae1c7053c8e65 100644
--- a/packages/block-editor/src/style.scss
+++ b/packages/block-editor/src/style.scss
@@ -57,6 +57,7 @@
@import "./hooks/border.scss";
@import "./hooks/dimensions.scss";
@import "./hooks/typography.scss";
+@import "./hooks/color.scss";
@import "./components/block-toolbar/style.scss";
@import "./components/inserter/style.scss";
diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md
index 66ab7b10da8d18..180dbbdf2defb8 100644
--- a/packages/e2e-test-utils/README.md
+++ b/packages/e2e-test-utils/README.md
@@ -581,6 +581,10 @@ Navigates the site editor back
Goes back until it gets to the root
+### openColorToolsPanelMenu
+
+Opens the Color tools panel menu provided via block supports.
+
### openDocumentSettingsSidebar
Clicks on the button in the header which opens Document Settings sidebar when it is closed.
diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js
index c10e761a5d58b9..493bac35955075 100644
--- a/packages/e2e-test-utils/src/index.js
+++ b/packages/e2e-test-utils/src/index.js
@@ -64,6 +64,7 @@ export {
} from './observe-focus-loss';
export { openDocumentSettingsSidebar } from './open-document-settings-sidebar';
export { openPublishPanel } from './open-publish-panel';
+export { openColorToolsPanelMenu } from './open-color-tools-panel-menu';
export { openTypographyToolsPanelMenu } from './open-typography-tools-panel-menu';
export { trashAllPosts } from './posts';
export { pressKeyTimes } from './press-key-times';
diff --git a/packages/e2e-test-utils/src/open-color-tools-panel-menu.js b/packages/e2e-test-utils/src/open-color-tools-panel-menu.js
new file mode 100644
index 00000000000000..0b358c7560b0eb
--- /dev/null
+++ b/packages/e2e-test-utils/src/open-color-tools-panel-menu.js
@@ -0,0 +1,9 @@
+/**
+ * Opens the Color tools panel menu provided via block supports.
+ */
+export async function openColorToolsPanelMenu() {
+ const toggleSelector =
+ "//div[contains(@class, 'color-block-support-panel')]//button[contains(@class, 'components-dropdown-menu__toggle')]";
+ const toggle = await page.waitForXPath( toggleSelector );
+ return toggle.click();
+}
diff --git a/packages/e2e-tests/specs/editor/blocks/heading.test.js b/packages/e2e-tests/specs/editor/blocks/heading.test.js
index 14a06dfb8c5056..d344d5cf4223b8 100644
--- a/packages/e2e-tests/specs/editor/blocks/heading.test.js
+++ b/packages/e2e-tests/specs/editor/blocks/heading.test.js
@@ -5,19 +5,18 @@ import {
clickBlockAppender,
createNewPost,
getEditedPostContent,
+ openColorToolsPanelMenu,
pressKeyWithModifier,
} from '@wordpress/e2e-test-utils';
describe( 'Heading', () => {
const COLOR_ITEM_SELECTOR =
- '.block-editor-panel-color-gradient-settings__item';
+ '.block-editor-panel-color-gradient-settings__dropdown';
const CUSTOM_COLOR_BUTTON_X_SELECTOR = `.components-color-palette__custom-color`;
const CUSTOM_COLOR_DETAILS_BUTTON_SELECTOR =
'.components-color-picker button[aria-label="Show detailed inputs"]';
const COLOR_INPUT_FIELD_SELECTOR =
'.components-color-picker .components-input-control__input';
- const COLOR_PANEL_TOGGLE_X_SELECTOR =
- "//button[./span[contains(text(),'Color')]]";
beforeEach( async () => {
await createNewPost();
@@ -73,10 +72,8 @@ describe( 'Heading', () => {
it( 'should correctly apply custom colors', async () => {
await clickBlockAppender();
await page.keyboard.type( '### Heading' );
- const colorPanelToggle = await page.waitForXPath(
- COLOR_PANEL_TOGGLE_X_SELECTOR
- );
- await colorPanelToggle.click();
+ await openColorToolsPanelMenu();
+ await page.click( 'button[aria-label="Show Text"]' );
const textColorButton = await page.waitForSelector(
COLOR_ITEM_SELECTOR
@@ -101,10 +98,8 @@ describe( 'Heading', () => {
it( 'should correctly apply named colors', async () => {
await clickBlockAppender();
await page.keyboard.type( '## Heading' );
- const [ colorPanelToggle ] = await page.$x(
- COLOR_PANEL_TOGGLE_X_SELECTOR
- );
- await colorPanelToggle.click();
+ await openColorToolsPanelMenu();
+ await page.click( 'button[aria-label="Show Text"]' );
const textColorButton = await page.waitForSelector(
COLOR_ITEM_SELECTOR
diff --git a/packages/e2e-tests/specs/editor/various/keep-styles-on-block-transforms.test.js b/packages/e2e-tests/specs/editor/various/keep-styles-on-block-transforms.test.js
index b79c71167318b4..ae2e8f696ccec9 100644
--- a/packages/e2e-tests/specs/editor/various/keep-styles-on-block-transforms.test.js
+++ b/packages/e2e-tests/specs/editor/various/keep-styles-on-block-transforms.test.js
@@ -4,9 +4,10 @@
import {
clickBlockAppender,
createNewPost,
+ getEditedPostContent,
+ openColorToolsPanelMenu,
pressKeyWithModifier,
transformBlockTo,
- getEditedPostContent,
} from '@wordpress/e2e-test-utils';
describe( 'Keep styles on block transforms', () => {
@@ -17,13 +18,11 @@ describe( 'Keep styles on block transforms', () => {
it( 'Should keep colors during a transform', async () => {
await clickBlockAppender();
await page.keyboard.type( '## Heading' );
- const [ colorPanelToggle ] = await page.$x(
- "//button[./span[contains(text(),'Color')]]"
- );
- await colorPanelToggle.click();
+ await openColorToolsPanelMenu();
+ await page.click( 'button[aria-label="Show Text"]' );
const textColorButton = await page.waitForSelector(
- '.block-editor-panel-color-gradient-settings__item'
+ '.block-editor-panel-color-gradient-settings__dropdown'
);
await textColorButton.click();