From 1e56e43bc4a9aced1d435ad462fab8eb43b087ef Mon Sep 17 00:00:00 2001 From: Maggie Cabrera Date: Tue, 28 Jul 2026 11:18:00 -0400 Subject: [PATCH 1/6] Global Styles: Add color scheme palette controls --- .../components/src/palette-edit/index.tsx | 2 + .../src/palette-edit/test/index.tsx | 18 ++ packages/components/src/palette-edit/types.ts | 6 +- .../src/settings/get-setting.ts | 2 + packages/global-styles-engine/src/types.ts | 12 + .../src/color-palette-panel.tsx | 55 +++- .../src/color-scheme-palette.tsx | 83 ++++++ .../src/duotone-palette-panel.tsx | 266 ++++++++++++++++++ .../src/gradients-palette-panel.tsx | 88 ++++-- .../src/screen-color-palette.tsx | 5 + packages/global-styles-ui/src/style.scss | 8 +- .../src/test/color-scheme-palette.spec.ts | 47 ++++ .../src/test/duotone-palette-panel.spec.tsx | 63 +++++ 13 files changed, 621 insertions(+), 34 deletions(-) create mode 100644 packages/global-styles-ui/src/color-scheme-palette.tsx create mode 100644 packages/global-styles-ui/src/duotone-palette-panel.tsx create mode 100644 packages/global-styles-ui/src/test/color-scheme-palette.spec.ts create mode 100644 packages/global-styles-ui/src/test/duotone-palette-panel.spec.tsx diff --git a/packages/components/src/palette-edit/index.tsx b/packages/components/src/palette-edit/index.tsx index 0732f8015db562..ad35a2e9792c26 100644 --- a/packages/components/src/palette-edit/index.tsx +++ b/packages/components/src/palette-edit/index.tsx @@ -379,6 +379,7 @@ export function PaletteEdit( { colors = EMPTY_ARRAY, onChange, paletteLabel, + paletteIcon, paletteLabelHeadingLevel = 2, emptyMessage, canOnlyChangeValues, @@ -426,6 +427,7 @@ export function PaletteEdit( { + { paletteIcon } { paletteLabel } diff --git a/packages/components/src/palette-edit/test/index.tsx b/packages/components/src/palette-edit/test/index.tsx index 7f3a0335eb6814..9e1ff6d3e13889 100644 --- a/packages/components/src/palette-edit/test/index.tsx +++ b/packages/components/src/palette-edit/test/index.tsx @@ -183,6 +183,24 @@ describe( 'PaletteEdit', () => { expect( paletteLabel ).toBeVisible(); } ); + it( 'shows an icon before the heading label', () => { + render( + } + /> + ); + + expect( + screen.getByRole( 'heading', { + level: 2, + name: 'Test label', + } ) + ).toBeVisible(); + expect( screen.getByTestId( 'palette-icon' ) ).toBeVisible(); + } ); + it( 'shows heading label with custom heading level', () => { render( = Omit< T, 'name' > & { + name?: string; +}; + +export interface ColorSchemeSettings { + palette?: ColorSchemePreset< Color >[]; + gradients?: ColorSchemePreset< Gradient >[]; + duotone?: ColorSchemePreset< Duotone >[]; +} + /** * Palette collection for a specific origin (theme, custom, default) */ @@ -225,6 +235,8 @@ export interface GlobalStylesSettings { defaultPalette?: boolean; defaultGradients?: boolean; defaultDuotone?: boolean; + light?: ColorSchemeSettings; + dark?: ColorSchemeSettings; }; custom?: Record< string, diff --git a/packages/global-styles-ui/src/color-palette-panel.tsx b/packages/global-styles-ui/src/color-palette-panel.tsx index f72c29da219d86..572bd4935b048c 100644 --- a/packages/global-styles-ui/src/color-palette-panel.tsx +++ b/packages/global-styles-ui/src/color-palette-panel.tsx @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import type { Color } from '@wordpress/global-styles-engine'; +import type { Color, ColorSchemePreset } from '@wordpress/global-styles-engine'; import { useViewportMatch } from '@wordpress/compose'; import { __experimentalPaletteEdit as PaletteEdit, @@ -16,6 +16,12 @@ import { shuffle } from '@wordpress/icons'; */ import { useSetting, useColorRandomizer } from './hooks'; import ColorVariations from './variations/variations-color'; +import { + addBasePresetNames, + flattenSchemePresets, + SchemePaletteIcon, + type SchemePresetCollection, +} from './color-scheme-palette'; const mobilePopoverProps = { placement: 'bottom-start' as const, offset: 8 }; @@ -46,6 +52,29 @@ export default function ColorPalettePanel( { name }: ColorPalettePanelProps ) { 'color.palette.custom', name ); + const [ lightColors, setLightColors ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Color > > + >( 'color.light.palette', name ); + const [ userLightColors ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Color > > + >( 'color.light.palette', name, 'user' ); + const [ darkColors, setDarkColors ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Color > > + >( 'color.dark.palette', name ); + const [ userDarkColors ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Color > > + >( 'color.dark.palette', name, 'user' ); + + const namedLightColors = addBasePresetNames( + flattenSchemePresets( lightColors ), + themeColors + ); + const namedDarkColors = addBasePresetNames( + flattenSchemePresets( darkColors ), + themeColors + ); + const hasLightColors = namedLightColors.length > 0; + const hasDarkColors = namedDarkColors.length > 0; const [ defaultPaletteEnabled ] = useSetting< boolean >( 'color.defaultPalette', @@ -71,6 +100,30 @@ export default function ColorPalettePanel( { name }: ColorPalettePanelProps ) { popoverProps={ popoverProps } /> ) } + { hasLightColors && ( + } + paletteLabelHeadingLevel={ 3 } + popoverProps={ popoverProps } + /> + ) } + { hasDarkColors && ( + } + paletteLabelHeadingLevel={ 3 } + popoverProps={ popoverProps } + /> + ) } { ( window as any ).__experimentalEnableColorRandomizer && themeColors?.length > 0 && randomizeThemeColors && ( diff --git a/packages/global-styles-ui/src/color-scheme-palette.tsx b/packages/global-styles-ui/src/color-scheme-palette.tsx new file mode 100644 index 00000000000000..8fd0c73ce516bf --- /dev/null +++ b/packages/global-styles-ui/src/color-scheme-palette.tsx @@ -0,0 +1,83 @@ +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/icons'; + +export type ColorScheme = 'light' | 'dark'; + +type PresetWithOptionalName = { + name?: string; + slug: string; +}; + +export type SchemePresetCollection< T > = + | T[] + | { + theme?: T[]; + custom?: T[]; + default?: T[]; + }; + +const lightIcon = ( + +); + +const darkIcon = ( + +); + +export function SchemePaletteIcon( { scheme }: { scheme: ColorScheme } ) { + return ( + + ); +} + +export function flattenSchemePresets< T >( + presets?: SchemePresetCollection< T > +): T[] { + if ( ! presets ) { + return []; + } + if ( Array.isArray( presets ) ) { + return presets; + } + return [ + ...( presets.theme ?? [] ), + ...( presets.custom ?? [] ), + ...( presets.default ?? [] ), + ]; +} + +export function addBasePresetNames< + T extends PresetWithOptionalName, + U extends { name: string; slug: string }, +>( presets: T[], basePresets?: U[] ): ( T & { name: string } )[] { + const baseNames = new Map( + basePresets?.map( ( { name, slug } ) => [ slug, name ] ) + ); + + return presets.map( ( preset ) => ( { + ...preset, + name: preset.name ?? baseNames.get( preset.slug ) ?? preset.slug, + } ) ); +} diff --git a/packages/global-styles-ui/src/duotone-palette-panel.tsx b/packages/global-styles-ui/src/duotone-palette-panel.tsx new file mode 100644 index 00000000000000..50531838ce2fc2 --- /dev/null +++ b/packages/global-styles-ui/src/duotone-palette-panel.tsx @@ -0,0 +1,266 @@ +/** + * WordPress dependencies + */ +import type { + Color, + ColorSchemePreset, + Duotone, +} from '@wordpress/global-styles-engine'; +import { + Button, + Dropdown, + DuotonePicker, + DuotoneSwatch, + FlexBlock, + __experimentalItem as Item, + __experimentalItemGroup as ItemGroup, +} from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { reset } from '@wordpress/icons'; +import { Stack } from '@wordpress/ui'; + +/** + * Internal dependencies + */ +import { useSetting } from './hooks'; +import { Subtitle } from './subtitle'; +import { + addBasePresetNames, + flattenSchemePresets, + SchemePaletteIcon, + type ColorScheme, + type SchemePresetCollection, +} from './color-scheme-palette'; + +interface DuotonePalettePanelProps { + name?: string; +} + +interface DuotoneSchemePaletteProps { + canReset: boolean; + colorPalette: Color[]; + duotones: Duotone[]; + label: string; + onChange: ( duotones?: Duotone[] ) => void; + onReset: () => void; + scheme: ColorScheme; +} + +function DuotoneSchemePalette( { + canReset, + colorPalette, + duotones, + label, + onChange, + onReset, + scheme, +}: DuotoneSchemePaletteProps ) { + return ( + + + + + { label } + + { canReset && ( + + ) } + renderContent={ () => ( + { + if ( ! Array.isArray( colors ) ) { + return; + } + onChange( + duotones.map( + ( + currentDuotone, + currentIndex + ) => + currentIndex === index + ? { + ...currentDuotone, + colors, + } + : currentDuotone + ) + ); + } } + /> + ) } + /> + { duotone.name } + + + ) ) } + + + ); +} + +export default function DuotonePalettePanel( { + name, +}: DuotonePalettePanelProps ) { + const [ customDuotones ] = useSetting< Duotone[] >( + 'color.duotone.custom', + name + ); + const [ defaultDuotones ] = useSetting< Duotone[] >( + 'color.duotone.default', + name + ); + const [ themeDuotones ] = useSetting< Duotone[] >( + 'color.duotone.theme', + name + ); + const [ defaultDuotoneEnabled ] = useSetting< boolean >( + 'color.defaultDuotone', + name + ); + const duotonePalette = [ + ...( customDuotones ?? [] ), + ...( themeDuotones ?? [] ), + ...( defaultDuotones && defaultDuotoneEnabled ? defaultDuotones : [] ), + ]; + + const [ themeColors ] = useSetting< Color[] >( + 'color.palette.theme', + name + ); + const [ defaultColors ] = useSetting< Color[] >( + 'color.palette.default', + name + ); + const [ customColors ] = useSetting< Color[] >( + 'color.palette.custom', + name + ); + const colorPalette = [ + ...( customColors ?? [] ), + ...( themeColors ?? [] ), + ...( defaultColors ?? [] ), + ]; + + const [ lightDuotones, setLightDuotones ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Duotone > > + >( 'color.light.duotone', name ); + const [ userLightDuotones ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Duotone > > + >( 'color.light.duotone', name, 'user' ); + const [ darkDuotones, setDarkDuotones ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Duotone > > + >( 'color.dark.duotone', name ); + const [ userDarkDuotones ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Duotone > > + >( 'color.dark.duotone', name, 'user' ); + + const namedLightDuotones = addBasePresetNames( + flattenSchemePresets( lightDuotones ), + themeDuotones + ); + const namedDarkDuotones = addBasePresetNames( + flattenSchemePresets( darkDuotones ), + themeDuotones + ); + const hasLightDuotones = namedLightDuotones.length > 0; + const hasDarkDuotones = namedDarkDuotones.length > 0; + + return ( + + { duotonePalette.length > 0 && ( + + { __( 'Theme' ) } + {} } + colorPalette={ [] } + /> + + ) } + { hasLightDuotones && ( + setLightDuotones( undefined ) } + scheme="light" + /> + ) } + { hasDarkDuotones && ( + setDarkDuotones( undefined ) } + scheme="dark" + /> + ) } + + ); +} diff --git a/packages/global-styles-ui/src/gradients-palette-panel.tsx b/packages/global-styles-ui/src/gradients-palette-panel.tsx index 6950317f7397d9..191c98dfab5452 100644 --- a/packages/global-styles-ui/src/gradients-palette-panel.tsx +++ b/packages/global-styles-ui/src/gradients-palette-panel.tsx @@ -5,22 +5,26 @@ import { useViewportMatch } from '@wordpress/compose'; import { __experimentalVStack as VStack, __experimentalPaletteEdit as PaletteEdit, - __experimentalSpacer as Spacer, - DuotonePicker, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import type { Gradient } from '@wordpress/global-styles-engine'; +import type { + ColorSchemePreset, + Gradient, +} from '@wordpress/global-styles-engine'; /** * Internal dependencies */ -import { Subtitle } from './subtitle'; import { useSetting } from './hooks'; +import { + addBasePresetNames, + flattenSchemePresets, + SchemePaletteIcon, + type SchemePresetCollection, +} from './color-scheme-palette'; const mobilePopoverProps = { placement: 'bottom-start' as const, offset: 8 }; -const noop = () => {}; - interface GradientPalettePanelProps { name?: string; } @@ -50,23 +54,35 @@ export default function GradientPalettePanel( { 'color.gradients.custom', name ); + const [ lightGradients, setLightGradients ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Gradient > > + >( 'color.light.gradients', name ); + const [ userLightGradients ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Gradient > > + >( 'color.light.gradients', name, 'user' ); + const [ darkGradients, setDarkGradients ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Gradient > > + >( 'color.dark.gradients', name ); + const [ userDarkGradients ] = useSetting< + SchemePresetCollection< ColorSchemePreset< Gradient > > + >( 'color.dark.gradients', name, 'user' ); + + const namedLightGradients = addBasePresetNames( + flattenSchemePresets( lightGradients ), + themeGradients + ); + const namedDarkGradients = addBasePresetNames( + flattenSchemePresets( darkGradients ), + themeGradients + ); + const hasLightGradients = namedLightGradients.length > 0; + const hasDarkGradients = namedDarkGradients.length > 0; const [ defaultPaletteEnabled ] = useSetting< boolean >( 'color.defaultGradients', name ); - const [ customDuotone ] = useSetting( 'color.duotone.custom' ) || []; - const [ defaultDuotone ] = useSetting( 'color.duotone.default' ) || []; - const [ themeDuotone ] = useSetting( 'color.duotone.theme' ) || []; - const [ defaultDuotoneEnabled ] = useSetting( 'color.defaultDuotone' ); - - const duotonePalette = [ - ...( customDuotone || [] ), - ...( themeDuotone || [] ), - ...( defaultDuotone && defaultDuotoneEnabled ? defaultDuotone : [] ), - ]; - const isMobileViewport = useViewportMatch( 'small', '<' ); const popoverProps = isMobileViewport ? mobilePopoverProps : undefined; @@ -86,6 +102,30 @@ export default function GradientPalettePanel( { popoverProps={ popoverProps } /> ) } + { hasLightGradients && ( + } + paletteLabelHeadingLevel={ 3 } + popoverProps={ popoverProps } + /> + ) } + { hasDarkGradients && ( + } + paletteLabelHeadingLevel={ 3 } + popoverProps={ popoverProps } + /> + ) } { !! defaultGradients && !! defaultGradients.length && !! defaultPaletteEnabled && ( @@ -107,20 +147,6 @@ export default function GradientPalettePanel( { slugPrefix="custom-" popoverProps={ popoverProps } /> - { !! duotonePalette && !! duotonePalette.length && ( -
- { __( 'Duotone' ) } - - -
- ) } ); } diff --git a/packages/global-styles-ui/src/screen-color-palette.tsx b/packages/global-styles-ui/src/screen-color-palette.tsx index 1a67126f3aff95..a29d0be3d23f6e 100644 --- a/packages/global-styles-ui/src/screen-color-palette.tsx +++ b/packages/global-styles-ui/src/screen-color-palette.tsx @@ -10,6 +10,7 @@ import { Tabs } from '@wordpress/ui'; import { ScreenHeader } from './screen-header'; import ColorPalettePanel from './color-palette-panel'; import GradientPalettePanel from './gradients-palette-panel'; +import DuotonePalettePanel from './duotone-palette-panel'; function ScreenColorPalette( { name }: { name?: string } ) { return ( @@ -27,6 +28,7 @@ function ScreenColorPalette( { name }: { name?: string } ) { { __( 'Gradient' ) } + { __( 'Duotone' ) } @@ -35,6 +37,9 @@ function ScreenColorPalette( { name }: { name?: string } ) { + + + ); diff --git a/packages/global-styles-ui/src/style.scss b/packages/global-styles-ui/src/style.scss index 69a3eb9a8d27e9..e2e973a5204517 100644 --- a/packages/global-styles-ui/src/style.scss +++ b/packages/global-styles-ui/src/style.scss @@ -269,6 +269,12 @@ } .global-styles-ui-color-palette-panel, -.global-styles-ui-gradient-palette-panel { +.global-styles-ui-gradient-palette-panel, +.global-styles-ui-duotone-palette-panel { padding: variables.$grid-unit-20; } + +.global-styles-ui-scheme-palette-icon { + margin-inline-end: variables.$grid-unit; + vertical-align: middle; +} diff --git a/packages/global-styles-ui/src/test/color-scheme-palette.spec.ts b/packages/global-styles-ui/src/test/color-scheme-palette.spec.ts new file mode 100644 index 00000000000000..d34bbb266fdb93 --- /dev/null +++ b/packages/global-styles-ui/src/test/color-scheme-palette.spec.ts @@ -0,0 +1,47 @@ +/** + * Internal dependencies + */ +import { + addBasePresetNames, + flattenSchemePresets, +} from '../color-scheme-palette'; + +describe( 'flattenSchemePresets', () => { + it( 'returns an inline scheme palette unchanged', () => { + const presets = [ { slug: 'base', color: '#fff' } ]; + + expect( flattenSchemePresets( presets ) ).toEqual( presets ); + } ); + + it( 'flattens an origin-keyed scheme palette', () => { + expect( + flattenSchemePresets( { + theme: [ { slug: 'base', color: '#fff' } ], + custom: [ { slug: 'accent', color: '#f00' } ], + } ) + ).toEqual( [ + { slug: 'base', color: '#fff' }, + { slug: 'accent', color: '#f00' }, + ] ); + } ); +} ); + +describe( 'addBasePresetNames', () => { + it( 'uses the matching base preset name when a scheme omits it', () => { + expect( + addBasePresetNames( + [ { slug: 'base', color: '#111' } ], + [ { slug: 'base', name: 'Base', color: '#fff' } ] + ) + ).toEqual( [ { slug: 'base', name: 'Base', color: '#111' } ] ); + } ); + + it( 'preserves a scheme-specific name', () => { + expect( + addBasePresetNames( + [ { slug: 'base', name: 'Night base', color: '#111' } ], + [ { slug: 'base', name: 'Base', color: '#fff' } ] + ) + ).toEqual( [ { slug: 'base', name: 'Night base', color: '#111' } ] ); + } ); +} ); diff --git a/packages/global-styles-ui/src/test/duotone-palette-panel.spec.tsx b/packages/global-styles-ui/src/test/duotone-palette-panel.spec.tsx new file mode 100644 index 00000000000000..be23962142575d --- /dev/null +++ b/packages/global-styles-ui/src/test/duotone-palette-panel.spec.tsx @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import DuotonePalettePanel from '../duotone-palette-panel'; +import { GlobalStylesProvider } from '../provider'; + +describe( 'DuotonePalettePanel', () => { + it( 'shows only theme-provided scheme palettes as editable controls', () => { + render( + {} } + > + + + ); + + expect( screen.getByText( 'Dark duotone' ) ).toBeVisible(); + expect( screen.queryByText( 'Light duotone' ) ).not.toBeInTheDocument(); + expect( + screen.getByRole( 'button', { name: 'Edit Portrait' } ) + ).toBeVisible(); + } ); +} ); From f59547e1be223086d0186482643d5089423dc9a6 Mon Sep 17 00:00:00 2001 From: Maggie Cabrera Date: Tue, 28 Jul 2026 11:41:13 -0400 Subject: [PATCH 2/6] Global Styles: Unify color scheme palette editing --- .../components/src/palette-edit/index.tsx | 86 +++++++++- .../components/src/palette-edit/styles.ts | 10 ++ .../src/palette-edit/test/index.tsx | 88 ++++++++++ packages/components/src/palette-edit/types.ts | 56 +++++++ .../global-styles-engine/src/core/render.tsx | 154 +++++++++++++++++- .../src/test/render.test.ts | 59 +++++++ .../src/color-palette-panel.tsx | 54 +++--- .../src/color-scheme-palette.tsx | 34 +--- .../src/gradients-palette-panel.tsx | 54 +++--- packages/global-styles-ui/src/style.scss | 5 - packages/icons/src/library/moon.svg | 3 + packages/icons/src/library/sun.svg | 3 + packages/icons/src/manifest.json | 12 ++ packages/icons/src/manifest.php | 8 + 14 files changed, 530 insertions(+), 96 deletions(-) create mode 100644 packages/icons/src/library/moon.svg create mode 100644 packages/icons/src/library/sun.svg diff --git a/packages/components/src/palette-edit/index.tsx b/packages/components/src/palette-edit/index.tsx index ad35a2e9792c26..3999b6bb9f7b24 100644 --- a/packages/components/src/palette-edit/index.tsx +++ b/packages/components/src/palette-edit/index.tsx @@ -40,6 +40,7 @@ import { DoneButton, RemoveButton, PaletteEditContents, + PaletteVariation, } from './styles'; import { NavigableMenu } from '../navigable-container'; import { DEFAULT_GRADIENT } from '../custom-gradient-picker/constants'; @@ -52,6 +53,8 @@ import type { OptionProps, PaletteEditListViewProps, PaletteEditProps, + PaletteEditColorVariation, + PaletteEditGradientVariation, PaletteElement, } from './types'; @@ -353,6 +356,10 @@ function PaletteEditListView< T extends PaletteElement >( { } const EMPTY_ARRAY: Color[] = []; +const EMPTY_VARIATIONS: ( + | PaletteEditColorVariation + | PaletteEditGradientVariation +)[] = []; /** * Allows editing a palette of colors or gradients. @@ -380,6 +387,7 @@ export function PaletteEdit( { onChange, paletteLabel, paletteIcon, + paletteVariations = EMPTY_VARIATIONS, paletteLabelHeadingLevel = 2, emptyMessage, canOnlyChangeValues, @@ -389,6 +397,10 @@ export function PaletteEdit( { }: PaletteEditProps ) { const isGradient = !! gradients; const elements = isGradient ? gradients : colors; + const variations = paletteVariations.map( ( variation ) => ( { + ...variation, + elements: isGradient ? variation.gradients : variation.colors, + } ) ); const [ isEditing, setIsEditing ] = useState( false ); const [ editingElement, setEditingElement ] = useState< number | null | undefined @@ -400,6 +412,12 @@ export function PaletteEdit( { ! elements[ editingElement ].slug; const elementsLength = elements.length; const hasElements = elementsLength > 0; + const hasVariations = variations.some( + ( variation ) => variation.elements.length > 0 + ); + const hasAnyElements = hasElements || hasVariations; + const canResetAny = + canReset || variations.some( ( variation ) => variation.canReset ); const debounceOnChange = useDebounce( onChange, 100 ); const onSelectPaletteItem = useCallback( ( @@ -431,7 +449,7 @@ export function PaletteEdit( { { paletteLabel } - { hasElements && isEditing && ( + { hasAnyElements && isEditing && ( { @@ -485,10 +503,10 @@ export function PaletteEdit( { /> ) } - { hasElements && + { hasAnyElements && ( ! isEditing || ! canOnlyChangeValues || - canReset ) && ( + canResetAny ) && ( ) } - { canReset && ( + { canResetAny && ( - ) } - renderContent={ () => ( - { - if ( ! Array.isArray( colors ) ) { - return; - } - onChange( - duotones.map( - ( - currentDuotone, - currentIndex - ) => - currentIndex === index - ? { - ...currentDuotone, - colors, - } - : currentDuotone - ) - ); - } } - /> - ) } - /> - { duotone.name } - - - ) ) } - + + { scheme && } + { label } + + {} } + colorPalette={ [] } + /> ); } @@ -176,36 +86,12 @@ export default function DuotonePalettePanel( { ...( defaultDuotones && defaultDuotoneEnabled ? defaultDuotones : [] ), ]; - const [ themeColors ] = useSetting< Color[] >( - 'color.palette.theme', - name - ); - const [ defaultColors ] = useSetting< Color[] >( - 'color.palette.default', - name - ); - const [ customColors ] = useSetting< Color[] >( - 'color.palette.custom', - name - ); - const colorPalette = [ - ...( customColors ?? [] ), - ...( themeColors ?? [] ), - ...( defaultColors ?? [] ), - ]; - - const [ lightDuotones, setLightDuotones ] = useSetting< + const [ lightDuotones ] = useSetting< SchemePresetCollection< ColorSchemePreset< Duotone > > >( 'color.light.duotone', name ); - const [ userLightDuotones ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Duotone > > - >( 'color.light.duotone', name, 'user' ); - const [ darkDuotones, setDarkDuotones ] = useSetting< + const [ darkDuotones ] = useSetting< SchemePresetCollection< ColorSchemePreset< Duotone > > >( 'color.dark.duotone', name ); - const [ userDarkDuotones ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Duotone > > - >( 'color.dark.duotone', name, 'user' ); const namedLightDuotones = addBasePresetNames( flattenSchemePresets( lightDuotones ), @@ -225,39 +111,22 @@ export default function DuotonePalettePanel( { className="global-styles-ui-duotone-palette-panel" > { duotonePalette.length > 0 && ( - - { __( 'Theme' ) } - {} } - colorPalette={ [] } - /> - + ) } { hasLightDuotones && ( - setLightDuotones( undefined ) } scheme="light" /> ) } { hasDarkDuotones && ( - setDarkDuotones( undefined ) } scheme="dark" /> ) } diff --git a/packages/global-styles-ui/src/test/duotone-palette-panel.spec.tsx b/packages/global-styles-ui/src/test/duotone-palette-panel.spec.tsx index be23962142575d..803202e442e734 100644 --- a/packages/global-styles-ui/src/test/duotone-palette-panel.spec.tsx +++ b/packages/global-styles-ui/src/test/duotone-palette-panel.spec.tsx @@ -10,10 +10,25 @@ import DuotonePalettePanel from '../duotone-palette-panel'; import { GlobalStylesProvider } from '../provider'; describe( 'DuotonePalettePanel', () => { - it( 'shows only theme-provided scheme palettes as editable controls', () => { + it( 'shows theme and scheme duotones with the same read-only controls', () => { render( { ); - expect( screen.getByText( 'Dark duotone' ) ).toBeVisible(); - expect( screen.queryByText( 'Light duotone' ) ).not.toBeInTheDocument(); + expect( screen.getByText( 'Theme' ) ).toBeVisible(); expect( - screen.getByRole( 'button', { name: 'Edit Portrait' } ) + screen.getByRole( 'listbox', { + name: 'Theme duotone palette', + } ) ).toBeVisible(); + expect( + screen.getByRole( 'listbox', { + name: 'Dark duotone palette', + } ) + ).toBeVisible(); + expect( screen.queryByText( 'Light duotone' ) ).not.toBeInTheDocument(); + expect( + screen.queryByRole( 'button', { name: 'Edit Portrait' } ) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole( 'button', { name: 'Reset Dark duotone' } ) + ).not.toBeInTheDocument(); } ); } ); From 14cab5613c0e7d6eaaa29ed801a96b22caae0744 Mon Sep 17 00:00:00 2001 From: Maggie Cabrera Date: Tue, 28 Jul 2026 12:12:04 -0400 Subject: [PATCH 5/6] Use Lucide icons for color schemes --- packages/icons/src/library/moon.svg | 4 ++-- packages/icons/src/library/sun.svg | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/icons/src/library/moon.svg b/packages/icons/src/library/moon.svg index e87b4fa5c142a3..e6bde071a86a79 100644 --- a/packages/icons/src/library/moon.svg +++ b/packages/icons/src/library/moon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/packages/icons/src/library/sun.svg b/packages/icons/src/library/sun.svg index d3e0f355bb7b2b..05d65e869457c2 100644 --- a/packages/icons/src/library/sun.svg +++ b/packages/icons/src/library/sun.svg @@ -1,3 +1,11 @@ - - + + + + + + + + + + From 9a0edcaa49fbfd3c97e9b5e9af644d6ca0a71803 Mon Sep 17 00:00:00 2001 From: Maggie Cabrera Date: Tue, 28 Jul 2026 12:23:29 -0400 Subject: [PATCH 6/6] Align duotone palette spacing --- .../global-styles-ui/src/duotone-palette-panel.tsx | 2 +- packages/global-styles-ui/src/style.scss | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/global-styles-ui/src/duotone-palette-panel.tsx b/packages/global-styles-ui/src/duotone-palette-panel.tsx index 34c537815e99d8..3f289f09f061e5 100644 --- a/packages/global-styles-ui/src/duotone-palette-panel.tsx +++ b/packages/global-styles-ui/src/duotone-palette-panel.tsx @@ -107,7 +107,7 @@ export default function DuotonePalettePanel( { return ( { duotonePalette.length > 0 && ( diff --git a/packages/global-styles-ui/src/style.scss b/packages/global-styles-ui/src/style.scss index 358067b0fae205..cfc7e2afe13039 100644 --- a/packages/global-styles-ui/src/style.scss +++ b/packages/global-styles-ui/src/style.scss @@ -290,3 +290,14 @@ .global-styles-ui-duotone-palette-panel { padding: variables.$grid-unit-20; } + +.global-styles-ui-duotone-palette-panel .global-styles-ui-subtitle { + align-items: center; + display: flex; + gap: variables.$grid-unit-05; + line-height: variables.$grid-unit-30; +} + +.global-styles-ui-duotone-palette-panel .components-circular-option-picker > .components-spacer { + display: none; +}