diff --git a/packages/block-editor/src/components/color-palette/with-color-context.js b/packages/block-editor/src/components/color-palette/with-color-context.js index 98c61ec3f71861..e941f19ee56770 100644 --- a/packages/block-editor/src/components/color-palette/with-color-context.js +++ b/packages/block-editor/src/components/color-palette/with-color-context.js @@ -7,6 +7,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; * Internal dependencies */ import { useSettings } from '../use-settings'; +import useColorSchemePresets from '../colors-gradients/use-color-scheme-presets'; export default createHigherOrderComponent( ( WrappedComponent ) => { return function WithColorContext( props ) { @@ -24,14 +25,18 @@ export default createHigherOrderComponent( ( WrappedComponent ) => { 'color.custom', 'color.defaultPalette' ); + const { presets: currentThemeColors } = useColorSchemePresets( + 'palette', + themeColors + ); const _colors = enableDefaultColors ? [ - ...( themeColors || [] ), + ...( currentThemeColors || [] ), ...( defaultColors || [] ), ...( customColors || [] ), ] - : [ ...( themeColors || [] ), ...( customColors || [] ) ]; + : [ ...( currentThemeColors || [] ), ...( customColors || [] ) ]; const { colors = _colors, disableCustomColors = ! enableCustomColors } = props; diff --git a/packages/block-editor/src/components/colors-gradients/test/use-color-scheme-presets.js b/packages/block-editor/src/components/colors-gradients/test/use-color-scheme-presets.js new file mode 100644 index 00000000000000..0caf52309164f8 --- /dev/null +++ b/packages/block-editor/src/components/colors-gradients/test/use-color-scheme-presets.js @@ -0,0 +1,78 @@ +/** + * WordPress dependencies + */ +import { useMediaQuery } from '@wordpress/compose'; +import { renderHook } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import { useSettings } from '../../use-settings'; +import useColorSchemePresets from '../use-color-scheme-presets'; + +jest.mock( '@wordpress/compose', () => ( { + ...jest.requireActual( '@wordpress/compose' ), + useMediaQuery: jest.fn(), +} ) ); + +jest.mock( '../../use-settings', () => ( { + useSettings: jest.fn(), +} ) ); + +const baseColors = [ + { slug: 'base', name: 'Base', color: '#fff' }, + { slug: 'accent', name: 'Accent', color: '#f00' }, +]; + +describe( 'useColorSchemePresets', () => { + beforeEach( () => { + useSettings.mockReset(); + useMediaQuery.mockReset(); + } ); + + it( 'returns a complete dark palette for a partial alternative', () => { + useSettings.mockReturnValue( [ + undefined, + [ + { slug: 'base', color: '#111' }, + { slug: 'unknown', color: '#f0f' }, + ], + ] ); + useMediaQuery.mockImplementation( ( query ) => + query.includes( 'dark' ) + ); + + const { result } = renderHook( () => + useColorSchemePresets( 'palette', baseColors ) + ); + + expect( result.current ).toEqual( { + colorScheme: 'dark', + hasColorSchemes: true, + presets: [ + { slug: 'base', name: 'Base', color: '#111' }, + { slug: 'accent', name: 'Accent', color: '#f00' }, + ], + } ); + } ); + + it( 'uses the base palette when the available scheme does not match', () => { + useSettings.mockReturnValue( [ + undefined, + [ { slug: 'base', color: '#111' } ], + ] ); + useMediaQuery.mockImplementation( ( query ) => + query.includes( 'light' ) + ); + + const { result } = renderHook( () => + useColorSchemePresets( 'palette', baseColors ) + ); + + expect( result.current ).toEqual( { + colorScheme: undefined, + hasColorSchemes: true, + presets: baseColors, + } ); + } ); +} ); diff --git a/packages/block-editor/src/components/colors-gradients/use-color-scheme-presets.js b/packages/block-editor/src/components/colors-gradients/use-color-scheme-presets.js new file mode 100644 index 00000000000000..b84f7a4ed2657b --- /dev/null +++ b/packages/block-editor/src/components/colors-gradients/use-color-scheme-presets.js @@ -0,0 +1,59 @@ +/** + * WordPress dependencies + */ +import { useMediaQuery } from '@wordpress/compose'; +import { normalizeColorSchemePresets } from '@wordpress/global-styles-engine'; +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSettings } from '../use-settings'; + +/** + * Returns the effective preset list for the current color scheme. + * + * Alternative presets are normalized against the base list by slug. Missing + * alternative values use the base value and unmatched alternative slugs are + * ignored. + * + * @param {'palette'|'gradients'|'duotone'} presetType Color preset type. + * @param {Array} basePresets Complete base presets. + * @return {{ presets: Array, hasColorSchemes: boolean, colorScheme: string|undefined }} Effective presets and scheme metadata. + */ +export default function useColorSchemePresets( presetType, basePresets = [] ) { + const [ lightPresets, darkPresets ] = useSettings( + `color.light.${ presetType }`, + `color.dark.${ presetType }` + ); + const prefersLight = useMediaQuery( '(prefers-color-scheme: light)' ); + const prefersDark = useMediaQuery( '(prefers-color-scheme: dark)' ); + + let colorScheme; + let alternativePresets; + if ( prefersDark && darkPresets !== undefined ) { + colorScheme = 'dark'; + alternativePresets = darkPresets; + } else if ( prefersLight && lightPresets !== undefined ) { + colorScheme = 'light'; + alternativePresets = lightPresets; + } + + const presets = useMemo( + () => + alternativePresets === undefined + ? basePresets + : normalizeColorSchemePresets( + basePresets, + alternativePresets + ), + [ alternativePresets, basePresets ] + ); + + return { + presets, + hasColorSchemes: + lightPresets !== undefined || darkPresets !== undefined, + colorScheme, + }; +} diff --git a/packages/block-editor/src/components/colors-gradients/use-multiple-origin-colors-and-gradients.js b/packages/block-editor/src/components/colors-gradients/use-multiple-origin-colors-and-gradients.js index cfa5930ba7034c..102624f406e927 100644 --- a/packages/block-editor/src/components/colors-gradients/use-multiple-origin-colors-and-gradients.js +++ b/packages/block-editor/src/components/colors-gradients/use-multiple-origin-colors-and-gradients.js @@ -8,6 +8,7 @@ import { _x } from '@wordpress/i18n'; * Internal dependencies */ import { useSettings } from '../use-settings'; +import useColorSchemePresets from './use-color-scheme-presets'; /** * Retrieves color and gradient related settings. @@ -46,17 +47,24 @@ export default function useMultipleOriginColorsAndGradients() { disableCustomColors: ! enableCustomColors, disableCustomGradients: ! enableCustomGradients, }; + const { presets: currentThemeColors, hasColorSchemes } = + useColorSchemePresets( 'palette', themeColors ); + const { presets: currentThemeGradients } = useColorSchemePresets( + 'gradients', + themeGradients + ); + colorGradientSettings.hasColorSchemes = hasColorSchemes; colorGradientSettings.colors = useMemo( () => { const result = []; - if ( themeColors && themeColors.length ) { + if ( currentThemeColors && currentThemeColors.length ) { result.push( { name: _x( 'Theme', 'Indicates this palette comes from the theme.' ), slug: 'theme', - colors: themeColors, + colors: currentThemeColors, } ); } if ( @@ -86,21 +94,21 @@ export default function useMultipleOriginColorsAndGradients() { return result; }, [ customColors, - themeColors, + currentThemeColors, defaultColors, shouldDisplayDefaultColors, ] ); colorGradientSettings.gradients = useMemo( () => { const result = []; - if ( themeGradients && themeGradients.length ) { + if ( currentThemeGradients && currentThemeGradients.length ) { result.push( { name: _x( 'Theme', 'Indicates this palette comes from the theme.' ), slug: 'theme', - gradients: themeGradients, + gradients: currentThemeGradients, } ); } if ( @@ -130,7 +138,7 @@ export default function useMultipleOriginColorsAndGradients() { return result; }, [ customGradients, - themeGradients, + currentThemeGradients, defaultGradients, shouldDisplayDefaultGradients, ] ); diff --git a/packages/block-editor/src/components/colors/with-colors.js b/packages/block-editor/src/components/colors/with-colors.js index e2298e08b33d3a..781bb3ac258276 100644 --- a/packages/block-editor/src/components/colors/with-colors.js +++ b/packages/block-editor/src/components/colors/with-colors.js @@ -16,6 +16,7 @@ import { } from './utils'; import { useSettings } from '../use-settings'; import { unlock } from '../../lock-unlock'; +import useColorSchemePresets from '../colors-gradients/use-color-scheme-presets'; const { kebabCase } = unlock( componentsPrivateApis ); @@ -62,13 +63,17 @@ const withEditorColorPalette = () => 'color.palette.theme', 'color.palette.default' ); + const { presets: currentThemePalette } = useColorSchemePresets( + 'palette', + themePalette + ); const allColors = useMemo( () => [ ...( userPalette || [] ), - ...( themePalette || [] ), + ...( currentThemePalette || [] ), ...( defaultPalette || [] ), ], - [ userPalette, themePalette, defaultPalette ] + [ userPalette, currentThemePalette, defaultPalette ] ); return ; }, diff --git a/packages/block-editor/src/components/global-styles/border-panel.js b/packages/block-editor/src/components/global-styles/border-panel.js index fff3c070303c79..b4c83fedb7711e 100644 --- a/packages/block-editor/src/components/global-styles/border-panel.js +++ b/packages/block-editor/src/components/global-styles/border-panel.js @@ -20,6 +20,7 @@ import { useColorsPerOrigin } from './hooks'; import { useToolsPanelDropdownMenuProps } from './utils'; import { setImmutably } from '../../utils/object'; import { useBorderPanelLabel } from '../../hooks/border'; +import { extractPresetSlug } from '../../utils/color-values'; import { ShadowPopover, useShadowPresets } from './shadow-panel-components'; import { getInheritanceProps, @@ -111,10 +112,24 @@ export default function BorderPanel( { } ) { const colors = useColorsPerOrigin( settings ); const areCustomSolidsEnabled = settings?.color?.custom; + const allColors = useMemo( + () => colors.flatMap( ( { colors: originColors } ) => originColors ), + [ colors ] + ); const decodeValue = useCallback( ( rawValue ) => getValueFromVariable( { settings }, '', rawValue ), [ settings ] ); + const decodeColorValue = useCallback( + ( rawValue ) => { + const slug = extractPresetSlug( rawValue, 'color' ); + return ( + allColors.find( ( color ) => color.slug === slug )?.color ?? + decodeValue( rawValue ) + ); + }, + [ allColors, decodeValue ] + ); // Always keep the layout className (e.g. `single-column`); only the // inheritance treatment is gated on `showInheritanceLabelIndicators`. const inheritanceProps = ( isInherited, hasLocalOverride, className ) => @@ -124,9 +139,6 @@ export default function BorderPanel( { className ); const encodeColorValue = ( colorValue ) => { - const allColors = colors.flatMap( - ( { colors: originColors } ) => originColors - ); const colorObject = allColors.find( ( { color } ) => color === colorValue ); @@ -145,17 +157,19 @@ export default function BorderPanel( { [ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => { out[ side ] = { ...out[ side ], - color: decodeValue( out[ side ]?.color ), + color: decodeColorValue( out[ side ]?.color ), }; } ); return out; } return { ...source, - color: source.color ? decodeValue( source.color ) : undefined, + color: source.color + ? decodeColorValue( source.color ) + : undefined, }; }, - [ decodeValue ] + [ decodeColorValue ] ); // Local-then-inherited: prefer the user's locally-set border (whether // flat or split) when defined, otherwise fall back to the inherited diff --git a/packages/block-editor/src/components/global-styles/filters-panel.js b/packages/block-editor/src/components/global-styles/filters-panel.js index ef3d97ed6dbdc8..5ad1556c499feb 100644 --- a/packages/block-editor/src/components/global-styles/filters-panel.js +++ b/packages/block-editor/src/components/global-styles/filters-panel.js @@ -22,8 +22,12 @@ import { } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; import { useCallback, useMemo, useRef } from '@wordpress/element'; +import { useMediaQuery } from '@wordpress/compose'; import { reset as resetIcon } from '@wordpress/icons'; -import { getValueFromVariable } from '@wordpress/global-styles-engine'; +import { + getValueFromVariable, + normalizeColorSchemePresets, +} from '@wordpress/global-styles-engine'; /** * Internal dependencies @@ -49,13 +53,26 @@ function useMultiOriginColorPresets( settings?.color?.[ presetSetting ]?.theme || EMPTY_ARRAY; const defaultPresets = settings?.color?.[ presetSetting ]?.default || EMPTY_ARRAY; + const lightPresets = settings?.color?.light?.[ presetSetting ]; + const darkPresets = settings?.color?.dark?.[ presetSetting ]; + const prefersLight = useMediaQuery( '(prefers-color-scheme: light)' ); + const prefersDark = useMediaQuery( '(prefers-color-scheme: dark)' ); + const currentThemePresets = useMemo( () => { + if ( prefersDark && darkPresets !== undefined ) { + return normalizeColorSchemePresets( themePresets, darkPresets ); + } + if ( prefersLight && lightPresets !== undefined ) { + return normalizeColorSchemePresets( themePresets, lightPresets ); + } + return themePresets; + }, [ darkPresets, lightPresets, prefersDark, prefersLight, themePresets ] ); return useMemo( () => [ ...userPresets, - ...themePresets, + ...currentThemePresets, ...( disableDefault ? EMPTY_ARRAY : defaultPresets ), ], - [ disableDefault, userPresets, themePresets, defaultPresets ] + [ currentThemePresets, defaultPresets, disableDefault, userPresets ] ); } @@ -189,8 +206,19 @@ export default function FiltersPanel( { defaultControls = DEFAULT_CONTROLS, showInheritanceLabelIndicators = ENABLE_GLOBAL_STYLES_INHERITANCE, } ) { - const decodeValue = ( rawValue ) => - getValueFromVariable( { settings }, '', rawValue ); + const decodeValue = ( rawValue ) => { + const duotonePrefix = 'var:preset|duotone|'; + if ( rawValue?.startsWith?.( duotonePrefix ) ) { + const slug = rawValue.slice( duotonePrefix.length ); + const preset = duotonePalette.find( + ( duotone ) => duotone.slug === slug + ); + if ( preset ) { + return preset.colors; + } + } + return getValueFromVariable( { settings }, '', rawValue ); + }; // Always keep the layout className (e.g. `single-column`); only the // inheritance treatment is gated on `showInheritanceLabelIndicators`. const inheritanceProps = ( isInherited, hasLocalOverride, className ) => diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index baba0cb53d4fc0..9ab1c7e23c54b2 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -2,15 +2,20 @@ * WordPress dependencies */ import { useMemo } from '@wordpress/element'; +import { useMediaQuery } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; import { store as blocksStore } from '@wordpress/blocks'; import { _x } from '@wordpress/i18n'; -import { getValueFromVariable } from '@wordpress/global-styles-engine'; +import { + getValueFromVariable, + normalizeColorSchemePresets, +} from '@wordpress/global-styles-engine'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; +import { extractPresetSlug } from '../../utils/color-values'; /** * React hook that overrides a global settings object with block and element specific settings. @@ -217,16 +222,29 @@ export function useColorsPerOrigin( settings ) { const themeColors = settings?.color?.palette?.theme; const defaultColors = settings?.color?.palette?.default; const shouldDisplayDefaultColors = settings?.color?.defaultPalette; + const prefersLight = useMediaQuery( '(prefers-color-scheme: light)' ); + const prefersDark = useMediaQuery( '(prefers-color-scheme: dark)' ); + const lightColors = settings?.color?.light?.palette; + const darkColors = settings?.color?.dark?.palette; + const currentThemeColors = useMemo( () => { + if ( prefersDark && darkColors !== undefined ) { + return normalizeColorSchemePresets( themeColors, darkColors ); + } + if ( prefersLight && lightColors !== undefined ) { + return normalizeColorSchemePresets( themeColors, lightColors ); + } + return themeColors; + }, [ darkColors, lightColors, prefersDark, prefersLight, themeColors ] ); return useMemo( () => { const result = []; - if ( themeColors && themeColors.length ) { + if ( currentThemeColors && currentThemeColors.length ) { result.push( { name: _x( 'Theme', 'Indicates this palette comes from the theme.' ), - colors: themeColors, + colors: currentThemeColors, } ); } if ( @@ -254,7 +272,7 @@ export function useColorsPerOrigin( settings ) { return result; }, [ customColors, - themeColors, + currentThemeColors, defaultColors, shouldDisplayDefaultColors, ] ); @@ -265,16 +283,38 @@ export function useGradientsPerOrigin( settings ) { const themeGradients = settings?.color?.gradients?.theme; const defaultGradients = settings?.color?.gradients?.default; const shouldDisplayDefaultGradients = settings?.color?.defaultGradients; + const prefersLight = useMediaQuery( '(prefers-color-scheme: light)' ); + const prefersDark = useMediaQuery( '(prefers-color-scheme: dark)' ); + const lightGradients = settings?.color?.light?.gradients; + const darkGradients = settings?.color?.dark?.gradients; + const currentThemeGradients = useMemo( () => { + if ( prefersDark && darkGradients !== undefined ) { + return normalizeColorSchemePresets( themeGradients, darkGradients ); + } + if ( prefersLight && lightGradients !== undefined ) { + return normalizeColorSchemePresets( + themeGradients, + lightGradients + ); + } + return themeGradients; + }, [ + darkGradients, + lightGradients, + prefersDark, + prefersLight, + themeGradients, + ] ); return useMemo( () => { const result = []; - if ( themeGradients && themeGradients.length ) { + if ( currentThemeGradients && currentThemeGradients.length ) { result.push( { name: _x( 'Theme', 'Indicates this palette comes from the theme.' ), - gradients: themeGradients, + gradients: currentThemeGradients, } ); } if ( @@ -302,7 +342,7 @@ export function useGradientsPerOrigin( settings ) { return result; }, [ customGradients, - themeGradients, + currentThemeGradients, defaultGradients, shouldDisplayDefaultGradients, ] ); @@ -326,8 +366,34 @@ export function useColorGradientSettings( settings ) { () => colors.flatMap( ( { colors: originColors } ) => originColors ), [ colors ] ); - const decodeValue = ( rawValue ) => - getValueFromVariable( { settings }, '', rawValue ); + const allGradients = useMemo( + () => + gradients.flatMap( + ( { gradients: originGradients } ) => originGradients + ), + [ gradients ] + ); + const decodeValue = ( rawValue ) => { + const colorSlug = extractPresetSlug( rawValue, 'color' ); + if ( colorSlug ) { + const colorObject = allColors.find( + ( { slug } ) => slug === colorSlug + ); + if ( colorObject ) { + return colorObject.color; + } + } + const gradientSlug = extractPresetSlug( rawValue, 'gradient' ); + if ( gradientSlug ) { + const gradientObject = allGradients.find( + ( { slug } ) => slug === gradientSlug + ); + if ( gradientObject ) { + return gradientObject.gradient; + } + } + return getValueFromVariable( { settings }, '', rawValue ); + }; // When a slug is provided it is used directly: two presets can share the // same gradient string, and matching by string alone would collapse them // onto whichever entry appears first. Without a slug, fall back to @@ -336,9 +402,6 @@ export function useColorGradientSettings( settings ) { if ( slug ) { return 'var:preset|gradient|' + slug; } - const allGradients = gradients.flatMap( - ( { gradients: originGradients } ) => originGradients - ); const gradientObject = allGradients.find( ( { gradient } ) => gradient === gradientValue ); diff --git a/packages/block-editor/src/components/gradients/use-gradient.js b/packages/block-editor/src/components/gradients/use-gradient.js index 18a63a96e492ec..48af9a1376f29c 100644 --- a/packages/block-editor/src/components/gradients/use-gradient.js +++ b/packages/block-editor/src/components/gradients/use-gradient.js @@ -10,6 +10,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { useBlockEditContext } from '../block-edit'; import { useSettings } from '../use-settings'; import { store as blockEditorStore } from '../../store'; +import useColorSchemePresets from '../colors-gradients/use-color-scheme-presets'; export function __experimentalGetGradientClass( gradientSlug ) { if ( ! gradientSlug ) { @@ -69,13 +70,21 @@ function useGradient( { 'color.gradients.theme', 'color.gradients.default' ); + const { presets: currentThemeGradientPalette } = useColorSchemePresets( + 'gradients', + themeGradientPalette + ); const allGradients = useMemo( () => [ ...( userGradientPalette || [] ), - ...( themeGradientPalette || [] ), + ...( currentThemeGradientPalette || [] ), ...( defaultGradientPalette || [] ), ], - [ userGradientPalette, themeGradientPalette, defaultGradientPalette ] + [ + userGradientPalette, + currentThemeGradientPalette, + defaultGradientPalette, + ] ); const { gradient, customGradient } = useSelect( ( select ) => { @@ -108,7 +117,13 @@ function useGradient( { [ customGradientAttribute ]: newGradientValue, } ); }, - [ allGradients, clientId, updateBlockAttributes ] + [ + allGradients, + clientId, + customGradientAttribute, + gradientAttribute, + updateBlockAttributes, + ] ); const gradientClass = __experimentalGetGradientClass( gradient ); diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index f38f48ac93fa0e..6f603790976688 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -21,6 +21,8 @@ import { __experimentalGetGradientClass } from '../components/gradients'; import { transformStyles, shouldSkipSerialization } from './utils'; import { getBackgroundImageClasses } from './background'; import { useSettings } from '../components/use-settings'; +import useColorSchemePresets from '../components/colors-gradients/use-color-scheme-presets'; +import { getNamedPresetStyleValue } from '../utils/color-values'; export const COLOR_SUPPORT_KEY = 'color'; @@ -185,14 +187,16 @@ function useBlockProps( { 'color.palette.theme', 'color.palette.default' ); + const { presets: currentThemePalette, hasColorSchemes } = + useColorSchemePresets( 'palette', themePalette ); const colors = useMemo( () => [ ...( userPalette || [] ), - ...( themePalette || [] ), + ...( currentThemePalette || [] ), ...( defaultPalette || [] ), ], - [ userPalette, themePalette, defaultPalette ] + [ userPalette, currentThemePalette, defaultPalette ] ); if ( ! hasColorSupport( name ) || @@ -206,19 +210,23 @@ function useBlockProps( { textColor && ! shouldSkipSerialization( name, COLOR_SUPPORT_KEY, 'text' ) ) { - extraStyles.color = getColorObjectByAttributeValues( - colors, - textColor - )?.color; + extraStyles.color = getNamedPresetStyleValue( + 'color', + textColor, + getColorObjectByAttributeValues( colors, textColor )?.color, + hasColorSchemes + ); } if ( backgroundColor && ! shouldSkipSerialization( name, COLOR_SUPPORT_KEY, 'background' ) ) { - extraStyles.backgroundColor = getColorObjectByAttributeValues( - colors, - backgroundColor - )?.color; + extraStyles.backgroundColor = getNamedPresetStyleValue( + 'color', + backgroundColor, + getColorObjectByAttributeValues( colors, backgroundColor )?.color, + hasColorSchemes + ); } const saveProps = addSaveProps( { style: extraStyles }, name, { diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index 4b15e117cf7443..2205f07220055b 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -42,6 +42,7 @@ import { useResolvedStyle } from '../components/global-styles/inherited-value-co import { useBlockEditingMode } from '../components/block-editing-mode'; import { useBlockElement } from '../components/block-list/use-block-props/use-block-refs'; import { store as blockEditorStore } from '../store'; +import useColorSchemePresets from '../components/colors-gradients/use-color-scheme-presets'; const { getDuotoneFilter, getDuotoneStylesheet, getDuotoneUnsetStylesheet } = unlock( globalStylesEnginePrivateApis ); @@ -68,13 +69,18 @@ function useMultiOriginPresets( { presetSetting, defaultSetting } ) { `${ presetSetting }.theme`, `${ presetSetting }.default` ); + const presetType = presetSetting.slice( 'color.'.length ); + const { presets: currentThemePresets } = useColorSchemePresets( + presetType, + themePresets + ); return useMemo( () => [ ...( userPresets || EMPTY_ARRAY ), - ...( themePresets || EMPTY_ARRAY ), + ...( currentThemePresets || EMPTY_ARRAY ), ...( ( enableDefault && defaultPresets ) || EMPTY_ARRAY ), ], - [ enableDefault, userPresets, themePresets, defaultPresets ] + [ enableDefault, userPresets, currentThemePresets, defaultPresets ] ); } diff --git a/packages/block-editor/src/hooks/use-border-props.js b/packages/block-editor/src/hooks/use-border-props.js index 0afe1bb70cb5a8..9ca87226809484 100644 --- a/packages/block-editor/src/hooks/use-border-props.js +++ b/packages/block-editor/src/hooks/use-border-props.js @@ -4,6 +4,7 @@ import { getInlineStyles } from './style'; import { getBorderClasses, getMultiOriginColor } from './border'; import useMultipleOriginColorsAndGradients from '../components/colors-gradients/use-multiple-origin-colors-and-gradients'; +import { getNamedPresetStyleValue } from '../utils/color-values'; // This utility is intended to assist where the serialization of the border // block support is being skipped for a block but the border related CSS classes @@ -38,7 +39,7 @@ export function getBorderClassesAndStyles( attributes ) { * @return {Object} ClassName & style props from border block support. */ export function useBorderProps( attributes ) { - const { colors } = useMultipleOriginColorsAndGradients(); + const { colors, hasColorSchemes } = useMultipleOriginColorsAndGradients(); const borderProps = getBorderClassesAndStyles( attributes ); const { borderColor } = attributes; @@ -50,7 +51,12 @@ export function useBorderProps( attributes ) { namedColor: borderColor, } ); - borderProps.style.borderColor = borderColorObject.color; + borderProps.style.borderColor = getNamedPresetStyleValue( + 'color', + borderColor, + borderColorObject.color, + hasColorSchemes + ); } return borderProps; diff --git a/packages/block-editor/src/hooks/use-color-props.js b/packages/block-editor/src/hooks/use-color-props.js index 5e52b3eb6841fa..dba23c9c640400 100644 --- a/packages/block-editor/src/hooks/use-color-props.js +++ b/packages/block-editor/src/hooks/use-color-props.js @@ -21,6 +21,8 @@ import { getGradientValueBySlug, } from '../components/gradients'; import { useSettings } from '../components/use-settings'; +import useColorSchemePresets from '../components/colors-gradients/use-color-scheme-presets'; +import { getNamedPresetStyleValue } from '../utils/color-values'; // The code in this file has largely been lifted from the color block support // hook. @@ -102,22 +104,28 @@ export function useColorProps( attributes ) { 'color.gradients.theme', 'color.gradients.default' ); + const { presets: currentThemePalette, hasColorSchemes } = + useColorSchemePresets( 'palette', themePalette ); + const { + presets: currentThemeGradients, + hasColorSchemes: hasGradientColorSchemes, + } = useColorSchemePresets( 'gradients', themeGradients ); const colors = useMemo( () => [ ...( userPalette || [] ), - ...( themePalette || [] ), + ...( currentThemePalette || [] ), ...( defaultPalette || [] ), ], - [ userPalette, themePalette, defaultPalette ] + [ userPalette, currentThemePalette, defaultPalette ] ); const gradients = useMemo( () => [ ...( userGradients || [] ), - ...( themeGradients || [] ), + ...( currentThemeGradients || [] ), ...( defaultGradients || [] ), ], - [ userGradients, themeGradients, defaultGradients ] + [ userGradients, currentThemeGradients, defaultGradients ] ); const colorProps = getColorClassesAndStyles( attributes ); @@ -130,13 +138,20 @@ export function useColorProps( attributes ) { backgroundColor ); - colorProps.style.backgroundColor = backgroundColorObject.color; + colorProps.style.backgroundColor = getNamedPresetStyleValue( + 'color', + backgroundColor, + backgroundColorObject.color, + hasColorSchemes + ); } if ( gradient ) { - colorProps.style.background = getGradientValueBySlug( - gradients, - gradient + colorProps.style.background = getNamedPresetStyleValue( + 'gradient', + gradient, + getGradientValueBySlug( gradients, gradient ), + hasGradientColorSchemes ); } @@ -146,7 +161,12 @@ export function useColorProps( attributes ) { textColor ); - colorProps.style.color = textColorObject.color; + colorProps.style.color = getNamedPresetStyleValue( + 'color', + textColor, + textColorObject.color, + hasColorSchemes + ); } return colorProps; diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 434a35ba6d2bdb..2c0f64b5b8b303 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -293,6 +293,8 @@ export function useBlockSettings( name, parentLayout ) { defaultGradientPalette, defaultGradients, areCustomGradientsEnabled, + lightColorScheme, + darkColorScheme, isBackgroundEnabled, isLinkEnabled, isTextEnabled, @@ -357,6 +359,8 @@ export function useBlockSettings( name, parentLayout ) { 'color.gradients.default', 'color.defaultGradients', 'color.customGradient', + 'color.light', + 'color.dark', 'color.background', 'color.link', 'color.text', @@ -394,6 +398,8 @@ export function useBlockSettings( name, parentLayout ) { custom: customColorsEnabled, customGradient: areCustomGradientsEnabled, customDuotone, + light: lightColorScheme, + dark: darkColorScheme, background: isBackgroundEnabled, link: isLinkEnabled, heading: isHeadingEnabled, @@ -515,6 +521,8 @@ export function useBlockSettings( name, parentLayout ) { defaultGradientPalette, defaultGradients, areCustomGradientsEnabled, + lightColorScheme, + darkColorScheme, isBackgroundEnabled, isLinkEnabled, isTextEnabled, diff --git a/packages/block-editor/src/utils/color-values.ts b/packages/block-editor/src/utils/color-values.ts index 4b9706bc6b2ad7..572da57a9f5759 100644 --- a/packages/block-editor/src/utils/color-values.ts +++ b/packages/block-editor/src/utils/color-values.ts @@ -61,3 +61,36 @@ export function encodeColorValueWithPalette( const colorObject = allColors.find( ( { color } ) => color === colorValue ); return colorObject ? 'var:preset|color|' + colorObject.slug : colorValue; } + +/** + * Returns the CSS custom property reference for a named preset. + * + * @param type Preset type. + * @param slug Preset slug. + * @return CSS custom property reference. + */ +export function getPresetCSSVar( type: 'color' | 'gradient', slug: string ) { + return `var(--wp--preset--${ type }--${ slug })`; +} + +/** + * Returns the value used to render a named preset in the editor. + * + * Color scheme presets must retain their CSS custom property reference so the + * browser can apply the current scheme. Existing themes keep the resolved + * value used by the editor today. + * + * @param type Preset type. + * @param slug Preset slug. + * @param resolvedValue Resolved base or active preset value. + * @param hasColorSchemes Whether the preset type has color scheme alternatives. + * @return Editor style value. + */ +export function getNamedPresetStyleValue( + type: 'color' | 'gradient', + slug: string, + resolvedValue: string | undefined, + hasColorSchemes: boolean +) { + return hasColorSchemes ? getPresetCSSVar( type, slug ) : resolvedValue; +} diff --git a/packages/block-editor/src/utils/test/color-values.js b/packages/block-editor/src/utils/test/color-values.js index 0e15730db485cc..87df856e4c6246 100644 --- a/packages/block-editor/src/utils/test/color-values.js +++ b/packages/block-editor/src/utils/test/color-values.js @@ -4,6 +4,8 @@ import { extractPresetSlug, encodeColorValueWithPalette, + getNamedPresetStyleValue, + getPresetCSSVar, } from '../color-values'; describe( 'extractPresetSlug', () => { @@ -119,3 +121,31 @@ describe( 'encodeColorValueWithPalette', () => { ); } ); } ); + +describe( 'getPresetCSSVar', () => { + it( 'returns a color preset CSS custom property reference', () => { + expect( getPresetCSSVar( 'color', 'accent' ) ).toBe( + 'var(--wp--preset--color--accent)' + ); + } ); + + it( 'returns a gradient preset CSS custom property reference', () => { + expect( getPresetCSSVar( 'gradient', 'signal' ) ).toBe( + 'var(--wp--preset--gradient--signal)' + ); + } ); +} ); + +describe( 'getNamedPresetStyleValue', () => { + it( 'preserves the CSS custom property for color scheme presets', () => { + expect( + getNamedPresetStyleValue( 'color', 'accent', '#f00', true ) + ).toBe( 'var(--wp--preset--color--accent)' ); + } ); + + it( 'keeps the resolved value when color schemes are absent', () => { + expect( + getNamedPresetStyleValue( 'color', 'accent', '#f00', false ) + ).toBe( '#f00' ); + } ); +} ); diff --git a/packages/components/src/palette-edit/index.tsx b/packages/components/src/palette-edit/index.tsx index 3999b6bb9f7b24..878d13d0fce68e 100644 --- a/packages/components/src/palette-edit/index.tsx +++ b/packages/components/src/palette-edit/index.tsx @@ -48,13 +48,12 @@ import CustomGradientPicker from '../custom-gradient-picker'; import { kebabCase } from '../utils/strings'; import type { Color, + Gradient, ColorPickerPopoverProps, NameInputProps, OptionProps, PaletteEditListViewProps, PaletteEditProps, - PaletteEditColorVariation, - PaletteEditGradientVariation, PaletteElement, } from './types'; @@ -356,10 +355,7 @@ function PaletteEditListView< T extends PaletteElement >( { } const EMPTY_ARRAY: Color[] = []; -const EMPTY_VARIATIONS: ( - | PaletteEditColorVariation - | PaletteEditGradientVariation -)[] = []; +const EMPTY_VARIATIONS: never[] = []; /** * Allows editing a palette of colors or gradients. @@ -397,10 +393,15 @@ 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 variations = paletteVariations.map( ( variation ) => { + const variationElements = isGradient + ? variation.gradients + : variation.colors; + return { + ...variation, + elements: variationElements ?? EMPTY_ARRAY, + }; + } ); const [ isEditing, setIsEditing ] = useState( false ); const [ editingElement, setEditingElement ] = useState< number | null | undefined @@ -663,9 +664,7 @@ export function PaletteEdit( { { isEditing && ( - + canOnlyChangeValues={ canOnlyChangeValues } diff --git a/packages/components/src/palette-edit/stories/index.story.tsx b/packages/components/src/palette-edit/stories/index.story.tsx index dab12164ddf166..20ea62ecf52a78 100644 --- a/packages/components/src/palette-edit/stories/index.story.tsx +++ b/packages/components/src/palette-edit/stories/index.story.tsx @@ -13,7 +13,12 @@ import { useState } from '@wordpress/element'; * Internal dependencies */ import PaletteEdit from '..'; -import type { Color, Gradient } from '../types'; +import type { + Color, + Gradient, + PaletteEditColorVariation, + PaletteEditGradientVariation, +} from '../types'; const meta: Meta< typeof PaletteEdit > = { title: 'Components/PaletteEdit', @@ -33,7 +38,7 @@ const meta: Meta< typeof PaletteEdit > = { export default meta; const Template: StoryFn< typeof PaletteEdit > = ( args ) => { - const { colors, gradients, onChange, ...props } = args; + const { colors, gradients, onChange, paletteVariations, ...props } = args; const [ value, setValue ] = useState( gradients || colors ); return ( @@ -41,6 +46,8 @@ const Template: StoryFn< typeof PaletteEdit > = ( args ) => { { ...( gradients ? { gradients: value as Gradient[], + paletteVariations: + paletteVariations as PaletteEditGradientVariation[], onChange: ( newValue?: Gradient[] ) => { setValue( newValue ); onChange( newValue ); @@ -48,6 +55,8 @@ const Template: StoryFn< typeof PaletteEdit > = ( args ) => { } : { colors: value as Color[], + paletteVariations: + paletteVariations as PaletteEditColorVariation[], onChange: ( newValue?: Color[] ) => { setValue( newValue ); onChange( newValue ); diff --git a/packages/global-styles-engine/src/index.ts b/packages/global-styles-engine/src/index.ts index 9ddf6b7cfc4866..181836ad55df1d 100644 --- a/packages/global-styles-engine/src/index.ts +++ b/packages/global-styles-engine/src/index.ts @@ -29,6 +29,11 @@ export { getResolvedValue, splitSelectorList, } from './utils/common'; +export { + flattenColorSchemePresets, + normalizeColorSchemePresets, +} from './utils/color-schemes'; +export type { ColorSchemePresetCollection } from './utils/color-schemes'; export { privateApis } from './private-apis'; // Types diff --git a/packages/global-styles-engine/src/utils/color-schemes.ts b/packages/global-styles-engine/src/utils/color-schemes.ts new file mode 100644 index 00000000000000..43466882a6edae --- /dev/null +++ b/packages/global-styles-engine/src/utils/color-schemes.ts @@ -0,0 +1,74 @@ +/** + * Internal dependencies + */ +import type { BasePreset, ColorSchemePreset } from '../types'; + +type PresetCollection< T extends { slug: string } > = + | T[] + | { + theme?: T[]; + custom?: T[]; + default?: T[]; + }; + +export type ColorSchemePresetCollection< T extends BasePreset > = + PresetCollection< ColorSchemePreset< T > >; + +/** + * Flattens a color scheme preset collection after settings from different + * origins have been merged. + * + * @param presets Color scheme presets in authored or origin-keyed form. + * @return A flat list of color scheme presets. + */ +export function flattenColorSchemePresets< T extends { slug: string } >( + presets?: PresetCollection< T > +): T[] { + if ( ! presets ) { + return []; + } + if ( Array.isArray( presets ) ) { + return presets; + } + return [ + ...( presets.theme ?? [] ), + ...( presets.custom ?? [] ), + ...( presets.default ?? [] ), + ]; +} + +/** + * Creates the effective 1:1 preset list for a color scheme. + * + * The base preset list owns identity, names, and ordering. Alternative values + * replace matching base presets by slug. Missing alternatives use the base + * value, while alternatives without a matching base slug are ignored. + * + * @param basePresets The complete base preset list. + * @param alternativePresets The alternative scheme preset overrides. + * @return A complete alternative preset list matching the base presets 1:1. + */ +export function normalizeColorSchemePresets< T extends BasePreset >( + basePresets: T[] | undefined, + alternativePresets?: ColorSchemePresetCollection< T > +): T[] { + const alternativesBySlug = new Map( + flattenColorSchemePresets( alternativePresets ).map( ( preset ) => [ + preset.slug, + preset, + ] ) + ); + + return ( basePresets ?? [] ).map( ( basePreset ) => { + const alternative = alternativesBySlug.get( basePreset.slug ); + if ( ! alternative ) { + return { ...basePreset }; + } + return { + ...basePreset, + ...alternative, + name: basePreset.name, + slug: basePreset.slug, + } as T; + } ); +} diff --git a/packages/global-styles-ui/src/color-palette-panel.tsx b/packages/global-styles-ui/src/color-palette-panel.tsx index 2d85734bf2b564..b8e470d71a40f5 100644 --- a/packages/global-styles-ui/src/color-palette-panel.tsx +++ b/packages/global-styles-ui/src/color-palette-panel.tsx @@ -1,7 +1,10 @@ /** * WordPress dependencies */ -import type { Color, ColorSchemePreset } from '@wordpress/global-styles-engine'; +import type { + Color, + ColorSchemeSettings, +} from '@wordpress/global-styles-engine'; import { useViewportMatch } from '@wordpress/compose'; import { __experimentalPaletteEdit as PaletteEdit, @@ -17,8 +20,7 @@ import { shuffle } from '@wordpress/icons'; import { useSetting, useColorRandomizer } from './hooks'; import ColorVariations from './variations/variations-color'; import { - addBasePresetNames, - flattenSchemePresets, + normalizeColorSchemePresets, SchemePaletteIcon, type SchemePresetCollection, } from './color-scheme-palette'; @@ -52,29 +54,43 @@ export default function ColorPalettePanel( { name }: ColorPalettePanelProps ) { 'color.palette.custom', name ); + const [ lightScheme ] = useSetting< ColorSchemeSettings >( + 'color.light', + name + ); const [ lightColors, setLightColors ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Color > > + SchemePresetCollection< Color > >( 'color.light.palette', name ); - const [ userLightColors ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Color > > - >( 'color.light.palette', name, 'user' ); + const [ userLightColors ] = useSetting< SchemePresetCollection< Color > >( + 'color.light.palette', + name, + 'user' + ); + const [ darkScheme ] = useSetting< ColorSchemeSettings >( + 'color.dark', + name + ); const [ darkColors, setDarkColors ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Color > > + SchemePresetCollection< Color > >( 'color.dark.palette', name ); - const [ userDarkColors ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Color > > - >( 'color.dark.palette', name, 'user' ); + const [ userDarkColors ] = useSetting< SchemePresetCollection< Color > >( + 'color.dark.palette', + name, + 'user' + ); - const namedLightColors = addBasePresetNames( - flattenSchemePresets( lightColors ), - themeColors + const normalizedLightColors = normalizeColorSchemePresets( + themeColors, + lightColors ); - const namedDarkColors = addBasePresetNames( - flattenSchemePresets( darkColors ), - themeColors + const normalizedDarkColors = normalizeColorSchemePresets( + themeColors, + darkColors ); - const hasLightColors = namedLightColors.length > 0; - const hasDarkColors = namedDarkColors.length > 0; + const hasLightColors = + lightScheme !== undefined && normalizedLightColors.length > 0; + const hasDarkColors = + darkScheme !== undefined && normalizedDarkColors.length > 0; const [ defaultPaletteEnabled ] = useSetting< boolean >( 'color.defaultPalette', @@ -103,7 +119,7 @@ export default function ColorPalettePanel( { name }: ColorPalettePanelProps ) { { canReset: userLightColors !== undefined, - colors: namedLightColors, + colors: normalizedLightColors, onChange: setLightColors, paletteIcon: ( @@ -117,7 +133,7 @@ export default function ColorPalettePanel( { name }: ColorPalettePanelProps ) { { canReset: userDarkColors !== undefined, - colors: namedDarkColors, + colors: normalizedDarkColors, onChange: setDarkColors, paletteIcon: ( diff --git a/packages/global-styles-ui/src/color-scheme-palette.tsx b/packages/global-styles-ui/src/color-scheme-palette.tsx index bd3054c9c22e47..22f71ba44db97d 100644 --- a/packages/global-styles-ui/src/color-scheme-palette.tsx +++ b/packages/global-styles-ui/src/color-scheme-palette.tsx @@ -2,52 +2,14 @@ * WordPress dependencies */ import { Icon, moon, sun } from '@wordpress/icons'; +export { + flattenColorSchemePresets as flattenSchemePresets, + normalizeColorSchemePresets, +} from '@wordpress/global-styles-engine'; +export type { ColorSchemePresetCollection as SchemePresetCollection } from '@wordpress/global-styles-engine'; export type ColorScheme = 'light' | 'dark'; -type PresetWithOptionalName = { - name?: string; - slug: string; -}; - -export type SchemePresetCollection< T > = - | T[] - | { - theme?: T[]; - custom?: T[]; - default?: T[]; - }; - 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 index 3f289f09f061e5..ceab58c0be3c60 100644 --- a/packages/global-styles-ui/src/duotone-palette-panel.tsx +++ b/packages/global-styles-ui/src/duotone-palette-panel.tsx @@ -2,7 +2,7 @@ * WordPress dependencies */ import type { - ColorSchemePreset, + ColorSchemeSettings, Duotone, } from '@wordpress/global-styles-engine'; import { DuotonePicker } from '@wordpress/components'; @@ -15,8 +15,7 @@ import { Stack } from '@wordpress/ui'; import { useSetting } from './hooks'; import { Subtitle } from './subtitle'; import { - addBasePresetNames, - flattenSchemePresets, + normalizeColorSchemePresets, SchemePaletteIcon, type ColorScheme, type SchemePresetCollection, @@ -86,23 +85,35 @@ export default function DuotonePalettePanel( { ...( defaultDuotones && defaultDuotoneEnabled ? defaultDuotones : [] ), ]; - const [ lightDuotones ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Duotone > > - >( 'color.light.duotone', name ); - const [ darkDuotones ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Duotone > > - >( 'color.dark.duotone', name ); + const [ lightScheme ] = useSetting< ColorSchemeSettings >( + 'color.light', + name + ); + const [ lightDuotones ] = useSetting< SchemePresetCollection< Duotone > >( + 'color.light.duotone', + name + ); + const [ darkScheme ] = useSetting< ColorSchemeSettings >( + 'color.dark', + name + ); + const [ darkDuotones ] = useSetting< SchemePresetCollection< Duotone > >( + 'color.dark.duotone', + name + ); - const namedLightDuotones = addBasePresetNames( - flattenSchemePresets( lightDuotones ), - themeDuotones + const normalizedLightDuotones = normalizeColorSchemePresets( + themeDuotones, + lightDuotones ); - const namedDarkDuotones = addBasePresetNames( - flattenSchemePresets( darkDuotones ), - themeDuotones + const normalizedDarkDuotones = normalizeColorSchemePresets( + themeDuotones, + darkDuotones ); - const hasLightDuotones = namedLightDuotones.length > 0; - const hasDarkDuotones = namedDarkDuotones.length > 0; + const hasLightDuotones = + lightScheme !== undefined && normalizedLightDuotones.length > 0; + const hasDarkDuotones = + darkScheme !== undefined && normalizedDarkDuotones.length > 0; return ( ) } { hasDarkDuotones && ( diff --git a/packages/global-styles-ui/src/gradients-palette-panel.tsx b/packages/global-styles-ui/src/gradients-palette-panel.tsx index 981a970f23a705..a3970cc0048ce5 100644 --- a/packages/global-styles-ui/src/gradients-palette-panel.tsx +++ b/packages/global-styles-ui/src/gradients-palette-panel.tsx @@ -8,7 +8,7 @@ import { } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import type { - ColorSchemePreset, + ColorSchemeSettings, Gradient, } from '@wordpress/global-styles-engine'; @@ -17,8 +17,7 @@ import type { */ import { useSetting } from './hooks'; import { - addBasePresetNames, - flattenSchemePresets, + normalizeColorSchemePresets, SchemePaletteIcon, type SchemePresetCollection, } from './color-scheme-palette'; @@ -54,29 +53,39 @@ export default function GradientPalettePanel( { 'color.gradients.custom', name ); + const [ lightScheme ] = useSetting< ColorSchemeSettings >( + 'color.light', + name + ); const [ lightGradients, setLightGradients ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Gradient > > + SchemePresetCollection< Gradient > >( 'color.light.gradients', name ); const [ userLightGradients ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Gradient > > + SchemePresetCollection< Gradient > >( 'color.light.gradients', name, 'user' ); + const [ darkScheme ] = useSetting< ColorSchemeSettings >( + 'color.dark', + name + ); const [ darkGradients, setDarkGradients ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Gradient > > + SchemePresetCollection< Gradient > >( 'color.dark.gradients', name ); const [ userDarkGradients ] = useSetting< - SchemePresetCollection< ColorSchemePreset< Gradient > > + SchemePresetCollection< Gradient > >( 'color.dark.gradients', name, 'user' ); - const namedLightGradients = addBasePresetNames( - flattenSchemePresets( lightGradients ), - themeGradients + const normalizedLightGradients = normalizeColorSchemePresets( + themeGradients, + lightGradients ); - const namedDarkGradients = addBasePresetNames( - flattenSchemePresets( darkGradients ), - themeGradients + const normalizedDarkGradients = normalizeColorSchemePresets( + themeGradients, + darkGradients ); - const hasLightGradients = namedLightGradients.length > 0; - const hasDarkGradients = namedDarkGradients.length > 0; + const hasLightGradients = + lightScheme !== undefined && normalizedLightGradients.length > 0; + const hasDarkGradients = + darkScheme !== undefined && normalizedDarkGradients.length > 0; const [ defaultPaletteEnabled ] = useSetting< boolean >( 'color.defaultGradients', @@ -105,7 +114,7 @@ export default function GradientPalettePanel( { { canReset: userLightGradients !== undefined, - gradients: namedLightGradients, + gradients: normalizedLightGradients, onChange: setLightGradients, paletteIcon: ( @@ -119,7 +128,7 @@ export default function GradientPalettePanel( { { canReset: userDarkGradients !== undefined, - gradients: namedDarkGradients, + gradients: normalizedDarkGradients, onChange: setDarkGradients, paletteIcon: ( diff --git a/packages/global-styles-ui/src/scheme-preview-indicator.tsx b/packages/global-styles-ui/src/scheme-preview-indicator.tsx index 794a5fcdfc477f..4b0421401b5110 100644 --- a/packages/global-styles-ui/src/scheme-preview-indicator.tsx +++ b/packages/global-styles-ui/src/scheme-preview-indicator.tsx @@ -3,6 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { Icon, moon, sun } from '@wordpress/icons'; +import type { BasePreset } from '@wordpress/global-styles-engine'; /** * Internal dependencies @@ -16,7 +17,7 @@ import { type SchemeSettings = Partial< Record< 'palette' | 'gradients' | 'duotone', - SchemePresetCollection< unknown > + SchemePresetCollection< BasePreset > > >; diff --git a/packages/global-styles-ui/src/test/color-palette-panel.spec.tsx b/packages/global-styles-ui/src/test/color-palette-panel.spec.tsx new file mode 100644 index 00000000000000..8eb473e8a1836f --- /dev/null +++ b/packages/global-styles-ui/src/test/color-palette-panel.spec.tsx @@ -0,0 +1,71 @@ +/** + * External dependencies + */ +import { fireEvent, render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import ColorPalettePanel from '../color-palette-panel'; +import { GlobalStylesProvider } from '../provider'; + +describe( 'ColorPalettePanel color schemes', () => { + it( 'shows a complete alternative palette and omits unmatched slugs', async () => { + render( + {} } + > + + + ); + + fireEvent.click( + screen.getByRole( 'button', { + name: 'Color options', + } ) + ); + fireEvent.click( + screen.getByRole( 'button', { + name: 'Show details', + } ) + ); + + expect( + screen.getAllByRole( 'button', { name: 'Edit: Base' } ) + ).toHaveLength( 2 ); + expect( + screen.getAllByRole( 'button', { name: 'Edit: Accent' } ) + ).toHaveLength( 2 ); + expect( + screen.queryByRole( 'button', { name: 'Edit: unknown' } ) + ).not.toBeInTheDocument(); + } ); +} ); 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 index d34bbb266fdb93..ab9ac3470de525 100644 --- a/packages/global-styles-ui/src/test/color-scheme-palette.spec.ts +++ b/packages/global-styles-ui/src/test/color-scheme-palette.spec.ts @@ -2,8 +2,8 @@ * Internal dependencies */ import { - addBasePresetNames, flattenSchemePresets, + normalizeColorSchemePresets, } from '../color-scheme-palette'; describe( 'flattenSchemePresets', () => { @@ -26,22 +26,31 @@ describe( 'flattenSchemePresets', () => { } ); } ); -describe( 'addBasePresetNames', () => { - it( 'uses the matching base preset name when a scheme omits it', () => { +describe( 'normalizeColorSchemePresets', () => { + it( 'creates a complete alternative palette in base order', () => { expect( - addBasePresetNames( - [ { slug: 'base', color: '#111' } ], - [ { slug: 'base', name: 'Base', color: '#fff' } ] + normalizeColorSchemePresets( + [ + { slug: 'base', name: 'Base', color: '#fff' }, + { slug: 'accent', name: 'Accent', color: '#f00' }, + ], + [ { slug: 'base', color: '#111' } ] ) - ).toEqual( [ { slug: 'base', name: 'Base', color: '#111' } ] ); + ).toEqual( [ + { slug: 'base', name: 'Base', color: '#111' }, + { slug: 'accent', name: 'Accent', color: '#f00' }, + ] ); } ); - it( 'preserves a scheme-specific name', () => { + it( 'uses base identity and ignores unmatched alternative presets', () => { expect( - addBasePresetNames( - [ { slug: 'base', name: 'Night base', color: '#111' } ], - [ { slug: 'base', name: 'Base', color: '#fff' } ] + normalizeColorSchemePresets( + [ { slug: 'base', name: 'Base', color: '#fff' } ], + [ + { slug: 'base', name: 'Night base', color: '#111' }, + { slug: 'unknown', color: '#f0f' }, + ] ) - ).toEqual( [ { slug: 'base', name: 'Night base', color: '#111' } ] ); + ).toEqual( [ { slug: 'base', name: 'Base', color: '#111' } ] ); } ); } ); diff --git a/packages/global-styles-ui/tsconfig.json b/packages/global-styles-ui/tsconfig.json index 71ce10c14a3711..1db116b339f062 100644 --- a/packages/global-styles-ui/tsconfig.json +++ b/packages/global-styles-ui/tsconfig.json @@ -24,5 +24,5 @@ { "path": "../ui" } ], "include": [ "src/**/*" ], - "exclude": [ "src/font-library/lib/**/*" ] + "exclude": [ "src/font-library/lib/**/*", "src/**/test/**/*" ] }