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" > - + + red + + + #f00 + +
+
); + 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" > - + + red + + + #f00 + +
+
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(); + } ); } );