diff --git a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
index 631f8e45a6ec1d..93ff7de8807175 100644
--- a/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
+++ b/packages/block-editor/src/components/color-palette/test/__snapshots__/control.js.snap
@@ -78,45 +78,52 @@ exports[`ColorPaletteControl matches the snapshot 1`] = `
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-align-items: flex-start;
- -webkit-box-align: flex-start;
- -ms-flex-align: flex-start;
- align-items: flex-start;
- -webkit-flex-direction: row;
- -ms-flex-direction: row;
- flex-direction: row;
- gap: calc(4px * 2);
- -webkit-box-pack: justify;
- -webkit-justify-content: space-between;
- justify-content: space-between;
- width: 100%;
+ -webkit-align-items: stretch;
+ -webkit-box-align: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ gap: 0;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
}
.emotion-10>* {
- min-width: 0;
+ min-height: 0;
}
-.emotion-13 {
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- display: block;
- max-height: 100%;
- max-width: 100%;
+.emotion-12 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-align-items: stretch;
+ -webkit-box-align: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ gap: calc(4px * 0.5);
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+}
+
+.emotion-12>* {
min-height: 0;
- min-width: 0;
- -webkit-flex: 1;
- -ms-flex: 1;
- flex: 1;
}
-.emotion-15 {
+.emotion-14 {
display: block;
- max-height: 100%;
- max-width: 100%;
- min-height: 0;
- min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
@@ -157,30 +164,39 @@ exports[`ColorPaletteControl matches the snapshot 1`] = `
class="components-dropdown"
tabindex="-1"
>
-
);
+ const isHex = value?.startsWith( '#' );
- const colordColor = colord( normalizedColorValue ?? '' );
-
- const valueWithoutLeadingHash = value?.startsWith( '#' )
- ? value.substring( 1 )
- : value ?? '';
-
- const customColorAccessibleLabel = !! valueWithoutLeadingHash
+ // Leave hex values as-is. Remove the `var()` wrapper from CSS vars.
+ const displayValue = value?.replace( /^var\((.+)\)$/, '$1' );
+ const customColorAccessibleLabel = !! displayValue
? sprintf(
- // translators: %1$s: The name of the color e.g: "vivid red". %2$s: The color's hex code e.g: "#f00".
+ // translators: %1$s: The name of the color e.g: "vivid red". %2$s: The color's hex code, with added hyphens e.g: "#-f-0-0".
__(
'Custom color picker. The currently selected color is called "%1$s" and has a value of "%2$s".'
),
buttonLabelName,
- valueWithoutLeadingHash
+ isHex ? displayValue.split( '' ).join( '-' ) : displayValue
)
: __( 'Custom color picker.' );
@@ -257,43 +253,48 @@ function UnforwardedColorPalette(
isRenderedInSidebar={ __experimentalIsRenderedInSidebar }
renderContent={ renderCustomColorPicker }
renderToggle={ ( { isOpen, onToggle } ) => (
-
- colordColor.contrast( '#000' )
- ? '#fff'
- : '#000',
- }
- }
+
-
- { buttonLabelName }
-
-
+
- { valueWithoutLeadingHash }
-
-
+
+ { value
+ ? buttonLabelName
+ : 'No color selected' }
+
+ { /*
+ This `Truncate` is always rendered, even if
+ there is no `displayValue`, to ensure the layout
+ does not shift
+ */ }
+
+ { displayValue }
+
+
+
) }
/>
) }
diff --git a/packages/components/src/color-palette/style.scss b/packages/components/src/color-palette/style.scss
index 82f1858d64d3c4..2d6bc4ddc1db3d 100644
--- a/packages/components/src/color-palette/style.scss
+++ b/packages/components/src/color-palette/style.scss
@@ -1,41 +1,79 @@
-.components-color-palette__custom-color {
+$border-as-box-shadow: inset 0 0 0 $border-width rgba(0, 0, 0, 0.2);
+
+.components-color-palette__custom-color-wrapper {
+ position: relative;
+ z-index: 0;
+}
+.components-color-palette__custom-color-button {
position: relative;
border: none;
background: none;
- border-radius: $radius-block-ui;
height: $grid-unit-80;
- padding: $grid-unit-15;
- font-family: inherit;
width: 100%;
- // The background image creates a checkerboard pattern. Ignore rtlcss to
- // make it work both in LTR and RTL.
- // See https://github.com/WordPress/gutenberg/pull/42510
- /*rtl:begin:ignore*/
- background-image:
- repeating-linear-gradient(45deg, $gray-200 25%, transparent 25%, transparent 75%, $gray-200 75%, $gray-200),
- repeating-linear-gradient(45deg, $gray-200 25%, transparent 25%, transparent 75%, $gray-200 75%, $gray-200);
- background-position: 0 0, 24px 24px;
- /*rtl:end:ignore*/
- background-size: calc(2 * 24px) calc(2 * 24px);
box-sizing: border-box;
- color: $white;
cursor: pointer;
- box-shadow: inset 0 0 0 $border-width rgba(0, 0, 0, 0.2);
// Show a thin outline in Windows high contrast mode.
outline: 1px solid transparent;
+ border-radius: $radius-block-ui $radius-block-ui 0 0;
+ box-shadow: $border-as-box-shadow;
&:focus {
box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) $components-color-accent;
// Show a outline in Windows high contrast mode.
outline-width: 2px;
}
+
+ &::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: -1;
+ // The background image creates a checkerboard pattern. Ignore rtlcss to
+ // make it work both in LTR and RTL.
+ // See https://github.com/WordPress/gutenberg/pull/42510
+ /*rtl:begin:ignore*/
+ background-image:
+ repeating-linear-gradient(45deg, $gray-200 25%, transparent 25%, transparent 75%, $gray-200 75%, $gray-200),
+ repeating-linear-gradient(45deg, $gray-200 25%, transparent 25%, transparent 75%, $gray-200 75%, $gray-200);
+ background-position: 0 0, 24px 24px;
+ /*rtl:end:ignore*/
+ background-size: calc(2 * 24px) calc(2 * 24px);
+ }
+}
+
+.components-color-palette__custom-color-text-wrapper {
+ padding: $grid-unit-15 $grid-unit-20;
+ border-radius: 0 0 $radius-block-ui $radius-block-ui;
+ position: relative;
+ font-size: $default-font-size;
+
+ // Add a border with the same technique as the button above,
+ // but only for left, bottom, and right sides.
+ box-shadow:
+ inset 0 -1 * $border-width 0 0 rgba(0, 0, 0, 0.2),
+ inset $border-width 0 0 0 rgba(0, 0, 0, 0.2),
+ inset -1 * $border-width 0 0 0 rgba(0, 0, 0, 0.2);
}
.components-color-palette__custom-color-name {
- text-align: left;
+ color: $components-color-foreground;
+ margin: 0 $border-width;
}
.components-color-palette__custom-color-value {
- margin-left: $grid-unit-20;
- text-transform: uppercase;
+ color: $gray-700;
+
+ &--is-hex {
+ text-transform: uppercase;
+ }
+
+ // Add a zero-width space when this element is empty to preserve
+ // a minimum height of 1 line of text, and avoid layout jumps.
+ &:empty::after {
+ content: "\200B";
+ visibility: hidden;
+ }
}
diff --git a/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap b/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap
index efbcf7a4aa4a40..8c7557366e37de 100644
--- a/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap
+++ b/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap
@@ -127,45 +127,52 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = `
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
- -webkit-align-items: flex-start;
- -webkit-box-align: flex-start;
- -ms-flex-align: flex-start;
- align-items: flex-start;
- -webkit-flex-direction: row;
- -ms-flex-direction: row;
- flex-direction: row;
- gap: calc(4px * 2);
- -webkit-box-pack: justify;
- -webkit-justify-content: space-between;
- justify-content: space-between;
- width: 100%;
+ -webkit-align-items: stretch;
+ -webkit-box-align: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ gap: 0;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
}
.emotion-2>* {
- min-width: 0;
+ min-height: 0;
}
-.emotion-5 {
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- display: block;
- max-height: 100%;
- max-width: 100%;
+.emotion-4 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-align-items: stretch;
+ -webkit-box-align: stretch;
+ -ms-flex-align: stretch;
+ align-items: stretch;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ gap: calc(4px * 0.5);
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+}
+
+.emotion-4>* {
min-height: 0;
- min-width: 0;
- -webkit-flex: 1;
- -ms-flex: 1;
- flex: 1;
}
-.emotion-7 {
+.emotion-6 {
display: block;
- max-height: 100%;
- max-width: 100%;
- min-height: 0;
- min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
@@ -178,30 +185,39 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = `
class="components-dropdown"
tabindex="-1"
>
-
void;
+} ) => {
+ const [ color, setColor ] = useState< string | undefined >( undefined );
+
+ return (
+ {
+ setColor( newColor );
+ onChange?.( newColor );
+ } }
+ />
+ );
+};
+
describe( 'ColorPalette', () => {
it( 'should render a dynamic toolbar of colors', () => {
const onChange = jest.fn();
@@ -142,6 +164,12 @@ describe( 'ColorPalette', () => {
/>
);
+ // Check that custom color popover is not visible by default.
+ expect(
+ screen.queryByLabelText( 'Hex color' )
+ ).not.toBeInTheDocument();
+
+ // Click the dropdown button while the dropdown is not expanded.
await user.click(
screen.getByRole( 'button', {
name: /^Custom color picker/,
@@ -149,23 +177,13 @@ describe( 'ColorPalette', () => {
} )
);
+ // Confirm the dropdown is now expanded, and the button is still visible.
const dropdownButton = screen.getByRole( 'button', {
name: /^Custom color picker/,
expanded: true,
} );
-
expect( dropdownButton ).toBeVisible();
- expect(
- within( dropdownButton ).getByText( EXAMPLE_COLORS[ 0 ].name )
- ).toBeVisible();
-
- expect(
- within( dropdownButton ).getByText(
- EXAMPLE_COLORS[ 0 ].color.replace( '#', '' )
- )
- ).toBeVisible();
-
// Check that the popover with custom color input has appeared.
const dropdownColorInput = screen.getByLabelText( 'Hex color' );
@@ -201,4 +219,49 @@ describe( 'ColorPalette', () => {
screen.getByRole( 'button', { name: 'Clear' } )
).toBeInTheDocument();
} );
+
+ it( 'should display the selected color name and value', async () => {
+ const user = userEvent.setup();
+
+ render( );
+
+ expect( screen.getByText( 'No color selected' ) ).toBeVisible();
+
+ // Click the first unpressed button
+ await user.click(
+ screen.getAllByRole( 'button', {
+ name: /^Color:/,
+ pressed: false,
+ } )[ 0 ]
+ );
+
+ // Confirm the correct color name, color value, and button label are used
+ expect( screen.getByText( EXAMPLE_COLORS[ 0 ].name ) ).toBeVisible();
+ expect( screen.getByText( EXAMPLE_COLORS[ 0 ].color ) ).toBeVisible();
+ expect(
+ screen.getByRole( 'button', {
+ name: `Custom color picker. The currently selected color is called "${
+ EXAMPLE_COLORS[ 0 ].name
+ }" and has a value of "${ EXAMPLE_COLORS[ 0 ].color
+ .split( '' )
+ .join( '-' ) }".`,
+ expanded: false,
+ } )
+ ).toBeInTheDocument();
+
+ // Clear the color, confirm that the relative values are cleared/updated.
+ await user.click( screen.getByRole( 'button', { name: 'Clear' } ) );
+ expect( screen.getByText( 'No color selected' ) ).toBeVisible();
+ expect(
+ screen.queryByText( EXAMPLE_COLORS[ 0 ].name )
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByText( EXAMPLE_COLORS[ 0 ].color )
+ ).not.toBeInTheDocument();
+ expect(
+ screen.getByRole( 'button', {
+ name: /^Custom color picker.$/,
+ } )
+ ).toBeInTheDocument();
+ } );
} );