diff --git a/packages/block-editor/src/components/global-styles/color-panel.native.js b/packages/block-editor/src/components/global-styles/color-panel.native.js
new file mode 100644
index 00000000000000..f329ad0369b468
--- /dev/null
+++ b/packages/block-editor/src/components/global-styles/color-panel.native.js
@@ -0,0 +1,207 @@
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+import { useEffect, useState, useMemo, useCallback } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { useGlobalStyles } from '@wordpress/components';
+import { store as blockEditorStore } from '@wordpress/block-editor';
+
+/**
+ * Internal dependencies
+ */
+import PanelColorGradientSettings from '../colors-gradients/panel-color-gradient-settings';
+import { useColorsPerOrigin, useGradientsPerOrigin } from './hooks';
+import { getValueFromVariable } from './utils';
+import { immutableSet } from '../../utils/object';
+import ContrastChecker from '../contrast-checker';
+import InspectorControls from '../inspector-controls';
+import {
+ useHasColorPanel,
+ useHasTextPanel,
+ useHasBackgroundPanel,
+} from './color-panel.js';
+
+const ColorPanel = ( {
+ value,
+ inheritedValue = value,
+ onChange,
+ settings,
+} ) => {
+ const colors = useColorsPerOrigin( settings );
+ const gradients = useGradientsPerOrigin( settings );
+ const globalStyles = useGlobalStyles();
+
+ const [ detectedBackgroundColor, setDetectedBackgroundColor ] = useState();
+ const [ detectedTextColor, setDetectedTextColor ] = useState();
+
+ const { baseGlobalStyles } = useSelect( ( select ) => {
+ const { getSettings } = select( blockEditorStore );
+ return {
+ baseGlobalStyles:
+ getSettings()?.__experimentalGlobalStylesBaseStyles?.color,
+ };
+ } );
+
+ const decodeValue = ( rawValue ) =>
+ getValueFromVariable( { settings }, '', rawValue );
+ const encodeColorValue = useCallback(
+ ( colorValue ) => {
+ const allColors = colors.flatMap(
+ ( { colors: originColors } ) => originColors
+ );
+ const colorObject = allColors.find(
+ ( { color } ) => color === colorValue
+ );
+ return colorObject
+ ? 'var:preset|color|' + colorObject.slug
+ : colorValue;
+ },
+ [ colors ]
+ );
+ const encodeGradientValue = useCallback(
+ ( gradientValue ) => {
+ const allGradients = gradients.flatMap(
+ ( { gradients: originGradients } ) => originGradients
+ );
+ const gradientObject = allGradients.find(
+ ( { gradient } ) => gradient === gradientValue
+ );
+ return gradientObject
+ ? 'var:preset|gradient|' + gradientObject.slug
+ : gradientValue;
+ },
+ [ gradients ]
+ );
+
+ // Text Color
+ const showTextPanel = useHasTextPanel( settings );
+ const textColor = decodeValue( inheritedValue?.color?.text );
+ const setTextColor = useCallback(
+ ( newColor ) => {
+ onChange(
+ immutableSet(
+ value,
+ [ 'color', 'text' ],
+ encodeColorValue( newColor )
+ )
+ );
+ },
+ [ encodeColorValue, onChange, value ]
+ );
+ const resetTextColor = useCallback(
+ () => setTextColor( undefined ),
+ [ setTextColor ]
+ );
+
+ // BackgroundColor
+ const showBackgroundPanel = useHasBackgroundPanel( settings );
+ const backgroundColor = decodeValue( inheritedValue?.color?.background );
+ const gradient = decodeValue( inheritedValue?.color?.gradient );
+ const setBackgroundColor = useCallback(
+ ( newColor ) => {
+ const newValue = immutableSet(
+ value,
+ [ 'color', 'background' ],
+ encodeColorValue( newColor )
+ );
+ newValue.color.gradient = undefined;
+ onChange( newValue );
+ },
+ [ encodeColorValue, onChange, value ]
+ );
+ const setGradient = useCallback(
+ ( newGradient ) => {
+ const newValue = immutableSet(
+ value,
+ [ 'color', 'gradient' ],
+ encodeGradientValue( newGradient )
+ );
+ newValue.color.background = undefined;
+ onChange( newValue );
+ },
+ [ encodeGradientValue, onChange, value ]
+ );
+ const resetBackground = useCallback( () => {
+ const newValue = immutableSet(
+ value,
+ [ 'color', 'background' ],
+ undefined
+ );
+ newValue.color.gradient = undefined;
+ onChange( newValue );
+ }, [ onChange, value ] );
+ const currentGradients = settings?.color?.gradients;
+ const withoutGradientsSupport =
+ Array.isArray( currentGradients ) && currentGradients.length === 0;
+
+ const items = useMemo(
+ () =>
+ [
+ showTextPanel && {
+ label: __( 'Text' ),
+ colorValue: textColor,
+ onColorChange: setTextColor,
+ onColorCleared: resetTextColor,
+ },
+ showBackgroundPanel && {
+ label: __( 'Background' ),
+ colorValue: backgroundColor,
+ onColorChange: setBackgroundColor,
+ onColorCleared: resetBackground,
+ onGradientChange: ! withoutGradientsSupport
+ ? setGradient
+ : undefined,
+ gradientValue: gradient,
+ },
+ ].filter( Boolean ),
+ [
+ backgroundColor,
+ gradient,
+ resetBackground,
+ resetTextColor,
+ setBackgroundColor,
+ setGradient,
+ setTextColor,
+ showBackgroundPanel,
+ showTextPanel,
+ textColor,
+ withoutGradientsSupport,
+ ]
+ );
+
+ useEffect( () => {
+ // The following logic is used to determine current text/background colors:
+ // 1. The globalStyles object is queried to determine whether a color has been
+ // set via a block's settings.
+ // 2. If a block-based theme is in use and no globalStyles exist, the theme's
+ // default/base colors are used.
+ // 3. If no globalStyles exist and a theme isn't block-based, there is no way
+ // to determine the default text/background color and the checker won't run.
+ const currentDetectedTextColor =
+ globalStyles?.color || baseGlobalStyles?.text;
+ const currentDetectedBackgroundColor =
+ globalStyles?.backgroundColor || baseGlobalStyles?.background;
+
+ setDetectedTextColor( currentDetectedTextColor );
+ setDetectedBackgroundColor( currentDetectedBackgroundColor );
+ }, [ globalStyles, baseGlobalStyles ] );
+
+ return (
+
+
+
+
+
+ );
+};
+
+export { useHasColorPanel };
+export default ColorPanel;
diff --git a/packages/block-editor/src/hooks/color-panel.native.js b/packages/block-editor/src/hooks/color-panel.native.js
deleted file mode 100644
index ff994899bbf06b..00000000000000
--- a/packages/block-editor/src/hooks/color-panel.native.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { useSelect } from '@wordpress/data';
-import { useEffect, useState } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-import { useGlobalStyles } from '@wordpress/components';
-import { store as blockEditorStore } from '@wordpress/block-editor';
-
-/**
- * Internal dependencies
- */
-import PanelColorGradientSettings from '../components/colors-gradients/panel-color-gradient-settings';
-import ContrastChecker from '../components/contrast-checker';
-import InspectorControls from '../components/inspector-controls';
-
-const ColorPanel = ( { settings } ) => {
- const globalStyles = useGlobalStyles();
-
- const [ detectedBackgroundColor, setDetectedBackgroundColor ] = useState();
- const [ detectedTextColor, setDetectedTextColor ] = useState();
-
- const { baseGlobalStyles } = useSelect( ( select ) => {
- const { getSettings } = select( blockEditorStore );
- return {
- baseGlobalStyles:
- getSettings()?.__experimentalGlobalStylesBaseStyles?.color,
- };
- } );
-
- useEffect( () => {
- // The following logic is used to determine current text/background colors:
- // 1. The globalStyles object is queried to determine whether a color has been
- // set via a block's settings.
- // 2. If a block-based theme is in use and no globalStyles exist, the theme's
- // default/base colors are used.
- // 3. If no globalStyles exist and a theme isn't block-based, there is no way
- // to determine the default text/background color and the checker won't run.
- const textColor = globalStyles?.color || baseGlobalStyles?.text;
- const backgroundColor =
- globalStyles?.backgroundColor || baseGlobalStyles?.background;
-
- setDetectedTextColor( textColor );
- setDetectedBackgroundColor( backgroundColor );
- }, [ globalStyles, baseGlobalStyles ] );
-
- return (
-
-
-
-
-
- );
-};
-
-export default ColorPanel;
diff --git a/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap
index 617edecbe4b0cb..1a55c807225d9d 100644
--- a/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap
+++ b/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap
@@ -1,5 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Buttons block color customization sets a background color 1`] = `
+"
+
+"
+`;
+
+exports[`Buttons block color customization sets a gradient background color 1`] = `
+"
+
+"
+`;
+
+exports[`Buttons block color customization sets a text color 1`] = `
+"
+
+"
+`;
+
exports[`Buttons block justify content sets Justify items center option 1`] = `
"
diff --git a/packages/block-library/src/buttons/test/edit.native.js b/packages/block-library/src/buttons/test/edit.native.js
index 788c1b1e40e4bb..ff79be61b92909 100644
--- a/packages/block-library/src/buttons/test/edit.native.js
+++ b/packages/block-library/src/buttons/test/edit.native.js
@@ -2,12 +2,15 @@
* External dependencies
*/
import {
+ addBlock,
fireEvent,
getEditorHtml,
within,
getBlock,
initializeEditor,
+ triggerBlockListLayout,
typeInRichText,
+ waitFor,
} from 'test/helpers';
/**
@@ -271,4 +274,122 @@ describe( 'Buttons block', () => {
} )
);
} );
+
+ describe( 'color customization', () => {
+ it( 'sets a text color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Buttons' );
+
+ // Act
+ const buttonsBlock = getBlock( screen, 'Buttons' );
+ fireEvent.press( buttonsBlock );
+
+ // Trigger onLayout for the list
+ await triggerBlockListLayout( buttonsBlock );
+
+ const buttonBlock = await getBlock( screen, 'Button' );
+ fireEvent.press( buttonBlock );
+
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId(
+ 'block-settings-modal'
+ );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Text, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'Pale pink' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchSnapshot();
+ } );
+
+ it( 'sets a background color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Buttons' );
+
+ // Act
+ const buttonsBlock = getBlock( screen, 'Buttons' );
+ fireEvent.press( buttonsBlock );
+
+ // Trigger onLayout for the list
+ await triggerBlockListLayout( buttonsBlock );
+
+ const buttonBlock = await getBlock( screen, 'Button' );
+ fireEvent.press( buttonBlock );
+
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId(
+ 'block-settings-modal'
+ );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Background, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'Luminous vivid amber' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchSnapshot();
+ } );
+
+ it( 'sets a gradient background color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Buttons' );
+
+ // Act
+ const buttonsBlock = getBlock( screen, 'Buttons' );
+ fireEvent.press( buttonsBlock );
+
+ // Trigger onLayout for the list
+ await triggerBlockListLayout( buttonsBlock );
+
+ const buttonBlock = await getBlock( screen, 'Button' );
+ fireEvent.press( buttonBlock );
+
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId(
+ 'block-settings-modal'
+ );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Background, Default' ) );
+
+ // Tap on the gradient segment
+ fireEvent.press( screen.getByLabelText( 'Gradient' ) );
+
+ // Tap one gradient color
+ fireEvent.press(
+ screen.getByLabelText( 'Light green cyan to vivid green cyan' )
+ );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchSnapshot();
+ } );
+ } );
} );
diff --git a/packages/block-library/src/heading/test/__snapshots__/index.native.js.snap b/packages/block-library/src/heading/test/__snapshots__/index.native.js.snap
index 0e0febb84d549f..308aa8ac729bff 100644
--- a/packages/block-library/src/heading/test/__snapshots__/index.native.js.snap
+++ b/packages/block-library/src/heading/test/__snapshots__/index.native.js.snap
@@ -5,3 +5,15 @@ exports[`Heading block inserts block 1`] = `
"
`;
+
+exports[`Heading block should set a background color 1`] = `
+"
+A quick brown fox jumps over the lazy dog.
+"
+`;
+
+exports[`Heading block should set a text color 1`] = `
+"
+A quick brown fox jumps over the lazy dog.
+"
+`;
diff --git a/packages/block-library/src/heading/test/index.native.js b/packages/block-library/src/heading/test/index.native.js
index fce294cf9c9920..34fdc00c032ed8 100644
--- a/packages/block-library/src/heading/test/index.native.js
+++ b/packages/block-library/src/heading/test/index.native.js
@@ -7,6 +7,9 @@ import {
initializeEditor,
addBlock,
getBlock,
+ typeInRichText,
+ waitFor,
+ within,
} from 'test/helpers';
/**
@@ -41,4 +44,72 @@ describe( 'Heading block', () => {
expect( getEditorHtml() ).toMatchSnapshot();
} );
+
+ it( 'should set a text color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Heading' );
+
+ // Act
+ const headingBlock = getBlock( screen, 'Heading' );
+ fireEvent.press( headingBlock );
+ const headingTextInput =
+ within( headingBlock ).getByPlaceholderText( 'Heading' );
+ typeInRichText(
+ headingTextInput,
+ 'A quick brown fox jumps over the lazy dog.'
+ );
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Text, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'Pale pink' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchSnapshot();
+ } );
+
+ it( 'should set a background color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Heading' );
+
+ // Act
+ const headingBlock = getBlock( screen, 'Heading' );
+ fireEvent.press( headingBlock );
+ const headingTextInput =
+ within( headingBlock ).getByPlaceholderText( 'Heading' );
+ typeInRichText(
+ headingTextInput,
+ 'A quick brown fox jumps over the lazy dog.'
+ );
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Background color settings
+ fireEvent.press( screen.getByLabelText( 'Background, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'Luminous vivid orange' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchSnapshot();
+ } );
} );
diff --git a/packages/block-library/src/paragraph/test/edit.native.js b/packages/block-library/src/paragraph/test/edit.native.js
index a4dcfdac92041b..d3ff59c0e42c29 100644
--- a/packages/block-library/src/paragraph/test/edit.native.js
+++ b/packages/block-library/src/paragraph/test/edit.native.js
@@ -11,6 +11,7 @@ import {
initializeEditor,
render,
setupCoreBlocks,
+ waitFor,
within,
} from 'test/helpers';
import Clipboard from '@react-native-clipboard/clipboard';
@@ -374,4 +375,268 @@ describe( 'Paragraph block', () => {
"
` );
} );
+
+ it( 'should set a text color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Paragraph' );
+
+ // Act
+ const paragraphBlock = getBlock( screen, 'Paragraph' );
+ fireEvent.press( paragraphBlock );
+ const paragraphTextInput =
+ within( paragraphBlock ).getByPlaceholderText( 'Start writing…' );
+ typeInRichText(
+ paragraphTextInput,
+ 'A quick brown fox jumps over the lazy dog.'
+ );
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Text, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'Pale pink' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchInlineSnapshot( `
+ "
+ A quick brown fox jumps over the lazy dog.
+ "
+ ` );
+ } );
+
+ it( 'should set a background color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Paragraph' );
+
+ // Act
+ const paragraphBlock = getBlock( screen, 'Paragraph' );
+ fireEvent.press( paragraphBlock );
+ const paragraphTextInput =
+ within( paragraphBlock ).getByPlaceholderText( 'Start writing…' );
+ typeInRichText(
+ paragraphTextInput,
+ 'A quick brown fox jumps over the lazy dog.'
+ );
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Background color settings
+ fireEvent.press( screen.getByLabelText( 'Background, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'Luminous vivid orange' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchInlineSnapshot( `
+ "
+ A quick brown fox jumps over the lazy dog.
+ "
+ ` );
+ } );
+
+ it( 'should set a text and background color', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Paragraph' );
+
+ // Act
+ const paragraphBlock = getBlock( screen, 'Paragraph' );
+ fireEvent.press( paragraphBlock );
+ const paragraphTextInput =
+ within( paragraphBlock ).getByPlaceholderText( 'Start writing…' );
+ typeInRichText(
+ paragraphTextInput,
+ 'A quick brown fox jumps over the lazy dog.'
+ );
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Text, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'White' ) );
+
+ // Go back to the settings menu
+ fireEvent.press( screen.getByLabelText( 'Go back' ) );
+
+ // Open Background color settings
+ fireEvent.press( screen.getByLabelText( 'Background, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'Luminous vivid orange' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchInlineSnapshot( `
+ "
+ A quick brown fox jumps over the lazy dog.
+ "
+ ` );
+ } );
+
+ it( 'should remove text and background colors', async () => {
+ // Arrange
+ const screen = await initializeEditor( {
+ initialHtml: `
+ A quick brown fox jumps over the lazy dog.
+ `,
+ } );
+
+ // Act
+ const paragraphBlock = getBlock( screen, 'Paragraph' );
+ fireEvent.press( paragraphBlock );
+
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Text. Empty' ) );
+
+ // Reset color
+ fireEvent.press( await screen.findByText( 'Reset' ) );
+
+ // Go back to the settings menu
+ fireEvent.press( screen.getByLabelText( 'Go back' ) );
+
+ // Open Background color settings
+ fireEvent.press( screen.getByLabelText( 'Background. Empty' ) );
+
+ // Reset color
+ fireEvent.press( await screen.findByText( 'Reset' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchInlineSnapshot( `
+ "
+ A quick brown fox jumps over the lazy dog.
+ "
+ ` );
+ } );
+
+ it( 'should not have a gradient background color option', async () => {
+ // Arrange
+ const screen = await initializeEditor();
+ await addBlock( screen, 'Paragraph' );
+
+ // Act
+ const paragraphBlock = getBlock( screen, 'Paragraph' );
+ fireEvent.press( paragraphBlock );
+ const paragraphTextInput =
+ within( paragraphBlock ).getByPlaceholderText( 'Start writing…' );
+ typeInRichText(
+ paragraphTextInput,
+ 'A quick brown fox jumps over the lazy dog.'
+ );
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Background color settings
+ fireEvent.press( screen.getByLabelText( 'Background, Default' ) );
+
+ // Assert
+ const colorButton = screen.getByLabelText( 'Luminous vivid orange' );
+ expect( colorButton ).toBeDefined();
+
+ const gradientButton = screen.queryByLabelText( 'Gradient' );
+ expect( gradientButton ).toBeNull();
+ } );
+
+ it( 'should set a theme text color', async () => {
+ // Arrange
+ const screen = await initializeEditor( { withGlobalStyles: true } );
+ await addBlock( screen, 'Paragraph' );
+
+ // Act
+ const paragraphBlock = getBlock( screen, 'Paragraph' );
+ fireEvent.press( paragraphBlock );
+ const paragraphTextInput =
+ within( paragraphBlock ).getByPlaceholderText( 'Start writing…' );
+ typeInRichText(
+ paragraphTextInput,
+ 'A quick brown fox jumps over the lazy dog.'
+ );
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Open Text color settings
+ fireEvent.press( screen.getByLabelText( 'Text, Default' ) );
+
+ // Tap one color
+ fireEvent.press( screen.getByLabelText( 'Tertiary' ) );
+
+ // Dismiss the Block Settings modal.
+ fireEvent( blockSettingsModal, 'backdropPress' );
+
+ // Assert
+ expect( getEditorHtml() ).toMatchInlineSnapshot( `
+ "
+ A quick brown fox jumps over the lazy dog.
+ "
+ ` );
+ } );
+
+ it( 'should show the contrast check warning', async () => {
+ // Arrange
+ const screen = await initializeEditor( {
+ initialHtml: `
+ A quick brown fox jumps over the lazy dog.
+ `,
+ } );
+
+ // Act
+ const paragraphBlock = getBlock( screen, 'Paragraph' );
+ fireEvent.press( paragraphBlock );
+
+ // Open Block Settings.
+ fireEvent.press( screen.getByLabelText( 'Open Settings' ) );
+
+ // Wait for Block Settings to be visible.
+ const blockSettingsModal = screen.getByTestId( 'block-settings-modal' );
+ await waitFor( () => blockSettingsModal.props.isVisible );
+
+ // Assert
+ const contrastCheckElement = screen.getByText(
+ /This color combination/
+ );
+ expect( contrastCheckElement ).toBeDefined();
+ } );
} );
diff --git a/packages/components/src/color-palette/index.native.js b/packages/components/src/color-palette/index.native.js
index ffb063f7f23f49..51a61785df9afe 100644
--- a/packages/components/src/color-palette/index.native.js
+++ b/packages/components/src/color-palette/index.native.js
@@ -15,7 +15,7 @@ import {
/**
* WordPress dependencies
*/
-import { __ } from '@wordpress/i18n';
+import { __, sprintf } from '@wordpress/i18n';
import { useRef, useEffect } from '@wordpress/element';
import { usePreferredColorSchemeStyle } from '@wordpress/compose';
@@ -175,6 +175,22 @@ function ColorPalette( {
}
}
+ function getColorGradientName( value ) {
+ const fallbackName = sprintf(
+ /* translators: %s: the hex color value */
+ __( 'Unlabeled color. %s' ),
+ value
+ );
+ const foundColorName = isGradientSegment
+ ? defaultSettings.gradients?.find(
+ ( gradient ) => gradient.gradient === value
+ )
+ : defaultSettings.allColors?.find(
+ ( color ) => color.color === value
+ );
+ return foundColorName ? foundColorName?.name : fallbackName;
+ }
+
function onColorPress( color ) {
deselectCustomGradient();
performAnimation( color );
@@ -251,6 +267,8 @@ function ColorPalette( {
const scaleValue = isSelected( color )
? scaleInterpolation
: 1;
+ const colorName = getColorGradientName( color );
+
return (
{
setCurrentValue( color );
if ( isSolidSegment && onColorChange && onGradientChange ) {
onColorChange( color );
- onGradientChange( '' );
} else if ( isSolidSegment && onColorChange ) {
onColorChange( color );
} else if ( ! isSolidSegment && onGradientChange ) {
onGradientChange( color );
- onColorChange( '' );
}
};
function onClear() {
setCurrentValue( undefined );
- if ( isSolidSegment ) {
- onColorChange( '' );
- } else {
- onGradientChange( '' );
- }
if ( onColorCleared ) {
onColorCleared();
diff --git a/packages/components/src/mobile/segmented-control/index.native.js b/packages/components/src/mobile/segmented-control/index.native.js
index b3270e37120e3b..1a218fcece100f 100644
--- a/packages/components/src/mobile/segmented-control/index.native.js
+++ b/packages/components/src/mobile/segmented-control/index.native.js
@@ -140,8 +140,8 @@ const SegmentedControls = ( {
styles.selectedDark
);
- const width = segmentsDimensions[ activeSegmentIndex ].width;
- const height = segmentsDimensions[ activeSegmentIndex ].height;
+ const width = segmentsDimensions[ activeSegmentIndex ]?.width;
+ const height = segmentsDimensions[ activeSegmentIndex ]?.height;
const outlineStyle = [ styles.outline, isIOS && styles.outlineIOS ];
diff --git a/test/native/integration-test-helpers/get-global-styles.js b/test/native/integration-test-helpers/get-global-styles.js
new file mode 100644
index 00000000000000..1156018358c3ca
--- /dev/null
+++ b/test/native/integration-test-helpers/get-global-styles.js
@@ -0,0 +1,55 @@
+const GLOBAL_STYLES_RAW_FEATURES = {
+ color: {
+ text: true,
+ background: true,
+ defaultPalette: true,
+ defaultGradients: false,
+ palette: {
+ default: [
+ {
+ color: '#f78da7',
+ name: 'Pale pink',
+ slug: 'pale-pink',
+ },
+ {
+ color: '#cf2e2e',
+ name: 'Vivid red',
+ slug: 'vivid-red',
+ },
+ {
+ color: '#ff6900',
+ name: 'Luminous vivid orange',
+ slug: 'luminous-vivid-orange',
+ },
+ ],
+ theme: [
+ {
+ color: '#e2d8ff',
+ name: 'Foreground',
+ slug: 'foreground',
+ },
+ {
+ color: '#2f1ab2',
+ name: 'Background',
+ slug: 'background',
+ },
+ {
+ color: '#2411a4',
+ name: 'Tertiary',
+ slug: 'tertiary',
+ },
+ ],
+ },
+ },
+};
+
+/**
+ * Returns some global styles data to test with the editor.
+ *
+ * @return {Object} Editor features.
+ */
+export function getGlobalStyles() {
+ return {
+ rawFeatures: JSON.stringify( GLOBAL_STYLES_RAW_FEATURES ),
+ };
+}
diff --git a/test/native/integration-test-helpers/initialize-editor.js b/test/native/integration-test-helpers/initialize-editor.js
index 440d69550ae0e6..54ece9d347a07c 100644
--- a/test/native/integration-test-helpers/initialize-editor.js
+++ b/test/native/integration-test-helpers/initialize-editor.js
@@ -15,14 +15,16 @@ import { initializeEditor as internalInitializeEditor } from '@wordpress/edit-po
* Internal dependencies
*/
import { waitForStoreResolvers } from './wait-for-store-resolvers';
+import { getGlobalStyles } from './get-global-styles';
/**
* Initialize an editor for test assertions.
*
- * @param {Object} props Properties passed to the editor component.
- * @param {string} props.initialHtml String of block editor HTML to parse and render.
- * @param {Object} [options] Configuration options for the editor.
- * @param {import('react').ReactNode} [options.component] A specific editor component to render.
+ * @param {Object} props Properties passed to the editor component.
+ * @param {string} props.initialHtml String of block editor HTML to parse and render.
+ * @param {string} props.withGlobalStyles Boolean to pass global styles data to the editor.
+ * @param {Object} [options] Configuration options for the editor.
+ * @param {import('react').ReactNode} [options.component] A specific editor component to render.
* @return {import('@testing-library/react-native').RenderAPI} A Testing Library screen.
*/
export async function initializeEditor( props, { component } = {} ) {
@@ -31,7 +33,11 @@ export async function initializeEditor( props, { component } = {} ) {
const postType = 'post';
return waitForStoreResolvers( () => {
- const { screenWidth = 320, ...rest } = props || {};
+ const {
+ screenWidth = 320,
+ withGlobalStyles = false,
+ ...rest
+ } = props || {};
const editorElement = component
? createElement( component, { postType, postId } )
: internalInitializeEditor( uniqueId, postType, postId );
@@ -39,6 +45,7 @@ export async function initializeEditor( props, { component } = {} ) {
const screen = render(
cloneElement( editorElement, {
initialTitle: 'test',
+ ...( withGlobalStyles ? getGlobalStyles() : {} ),
...rest,
} )
);