diff --git a/packages/block-editor/src/components/provider/block-refs-provider.js b/packages/block-editor/src/components/provider/block-refs-provider.js
index e98e0f4d25c9d4..3f2d19b658a630 100644
--- a/packages/block-editor/src/components/provider/block-refs-provider.js
+++ b/packages/block-editor/src/components/provider/block-refs-provider.js
@@ -3,7 +3,10 @@
*/
import { createContext, useMemo } from '@wordpress/element';
-export const BlockRefs = createContext();
+export const BlockRefs = createContext( {
+ refs: new Map(),
+ callbacks: new Map(),
+} );
export function BlockRefsProvider( { children } ) {
const value = useMemo(
diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js
index 42f087a0b6223c..28e6513e4cd642 100644
--- a/packages/core-data/src/actions.js
+++ b/packages/core-data/src/actions.js
@@ -153,6 +153,25 @@ export function __experimentalReceiveThemeBaseGlobalStyles(
};
}
+/**
+ * Returns an action object used in signalling that the theme global styles variations have been received.
+ *
+ * @param {string} stylesheet The theme's identifier
+ * @param {Array} variations The global styles variations.
+ *
+ * @return {Object} Action object.
+ */
+export function __experimentalReceiveThemeGlobalStyleVariations(
+ stylesheet,
+ variations
+) {
+ return {
+ type: 'RECEIVE_THEME_GLOBAL_STYLE_VARIATIONS',
+ stylesheet,
+ variations,
+ };
+}
+
/**
* Returns an action object used in signalling that the index has been received.
*
diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js
index 693c4e58ef46e1..3f88f189b03bed 100644
--- a/packages/core-data/src/reducer.js
+++ b/packages/core-data/src/reducer.js
@@ -156,6 +156,26 @@ export function themeBaseGlobalStyles( state = {}, action ) {
return state;
}
+/**
+ * Reducer managing the theme global styles variations.
+ *
+ * @param {string} state Current state.
+ * @param {Object} action Dispatched action.
+ *
+ * @return {string} Updated state.
+ */
+export function themeGlobalStyleVariations( state = {}, action ) {
+ switch ( action.type ) {
+ case 'RECEIVE_THEME_GLOBAL_STYLE_VARIATIONS':
+ return {
+ ...state,
+ [ action.stylesheet ]: action.variations,
+ };
+ }
+
+ return state;
+}
+
/**
* Higher Order Reducer for a given entity config. It supports:
*
@@ -578,6 +598,7 @@ export default combineReducers( {
currentTheme,
currentGlobalStylesId,
currentUser,
+ themeGlobalStyleVariations,
themeBaseGlobalStyles,
taxonomies,
entities,
diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js
index 5c6f4faaac156d..cc606e45735aa6 100644
--- a/packages/core-data/src/resolvers.js
+++ b/packages/core-data/src/resolvers.js
@@ -450,8 +450,22 @@ export const __experimentalGetCurrentThemeBaseGlobalStyles = () => async ( {
const themeGlobalStyles = await apiFetch( {
path: `/wp/v2/global-styles/themes/${ currentTheme.stylesheet }`,
} );
- await dispatch.__experimentalReceiveThemeBaseGlobalStyles(
+ dispatch.__experimentalReceiveThemeBaseGlobalStyles(
currentTheme.stylesheet,
themeGlobalStyles
);
};
+
+export const __experimentalGetCurrentThemeGlobalStylesVariations = () => async ( {
+ resolveSelect,
+ dispatch,
+} ) => {
+ const currentTheme = await resolveSelect.getCurrentTheme();
+ const variations = await apiFetch( {
+ path: `/wp/v2/global-styles/themes/${ currentTheme.stylesheet }/variations`,
+ } );
+ dispatch.__experimentalReceiveThemeGlobalStyleVariations(
+ currentTheme.stylesheet,
+ variations
+ );
+};
diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js
index 0d5a86c0200038..2370b15b6e72ce 100644
--- a/packages/core-data/src/selectors.js
+++ b/packages/core-data/src/selectors.js
@@ -908,3 +908,18 @@ export function __experimentalGetCurrentThemeBaseGlobalStyles( state ) {
}
return state.themeBaseGlobalStyles[ currentTheme.stylesheet ];
}
+
+/**
+ * Return the ID of the current global styles object.
+ *
+ * @param {Object} state Data state.
+ *
+ * @return {string} The current global styles ID.
+ */
+export function __experimentalGetCurrentThemeGlobalStylesVariations( state ) {
+ const currentTheme = getCurrentTheme( state );
+ if ( ! currentTheme ) {
+ return null;
+ }
+ return state.themeGlobalStyleVariations[ currentTheme.stylesheet ];
+}
diff --git a/packages/edit-site/src/components/global-styles/global-styles-provider.js b/packages/edit-site/src/components/global-styles/global-styles-provider.js
index 25dd5d04796780..e4f481b05b6f85 100644
--- a/packages/edit-site/src/components/global-styles/global-styles-provider.js
+++ b/packages/edit-site/src/components/global-styles/global-styles-provider.js
@@ -31,7 +31,7 @@ function mergeTreesCustomizer( _, srcValue ) {
}
}
-function mergeBaseAndUserConfigs( base, user ) {
+export function mergeBaseAndUserConfigs( base, user ) {
return mergeWith( {}, base, user, mergeTreesCustomizer );
}
@@ -67,7 +67,6 @@ function useGlobalStylesUserConfig() {
const { getEditedEntityRecord } = useSelect( coreStore );
const { editEntityRecord } = useDispatch( coreStore );
-
const config = useMemo( () => {
return {
settings: settings ?? {},
diff --git a/packages/edit-site/src/components/global-styles/preview.js b/packages/edit-site/src/components/global-styles/preview.js
index 0993d2d859298b..735fdeb78d3a9a 100644
--- a/packages/edit-site/src/components/global-styles/preview.js
+++ b/packages/edit-site/src/components/global-styles/preview.js
@@ -2,41 +2,69 @@
* WordPress dependencies
*/
import {
- __experimentalHStack as HStack,
- __experimentalVStack as VStack,
- Card,
- ColorIndicator,
-} from '@wordpress/components';
+ __unstableIframe as Iframe,
+ __unstableEditorStyles as EditorStyles,
+} from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import { useStyle } from './hooks';
+import { useGlobalStylesOutput } from './use-global-styles-output';
-const StylesPreview = () => {
+const StylesPreview = ( { height = 150 } ) => {
const [ fontFamily = 'serif' ] = useStyle( 'typography.fontFamily' );
const [ textColor = 'black' ] = useStyle( 'color.text' );
const [ linkColor = 'blue' ] = useStyle( 'elements.link.color.text' );
const [ backgroundColor = 'white' ] = useStyle( 'color.background' );
const [ gradientValue ] = useStyle( 'color.gradient' );
+ const [ styles ] = useGlobalStylesOutput();
return (
- }
+ style={ { height } }
>
-
+
+
);
};
diff --git a/packages/edit-site/src/components/global-styles/screen-root.js b/packages/edit-site/src/components/global-styles/screen-root.js
index 7761407f0363d4..815c84f0d5cbd3 100644
--- a/packages/edit-site/src/components/global-styles/screen-root.js
+++ b/packages/edit-site/src/components/global-styles/screen-root.js
@@ -5,6 +5,7 @@ import {
__experimentalItemGroup as ItemGroup,
__experimentalItem as Item,
__experimentalHStack as HStack,
+ __experimentalVStack as VStack,
FlexItem,
CardBody,
Card,
@@ -12,19 +13,47 @@ import {
} from '@wordpress/components';
import { isRTL, __ } from '@wordpress/i18n';
import { chevronLeft, chevronRight, Icon } from '@wordpress/icons';
+import { useSelect } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
-import StylesPreview from './preview';
import { NavigationButton } from './navigation-button';
import ContextMenu from './context-menu';
+import StylesPreview from './preview';
function ScreenRoot() {
+ const { variations } = useSelect( ( select ) => {
+ return {
+ variations: select(
+ coreStore
+ ).__experimentalGetCurrentThemeGlobalStylesVariations(),
+ };
+ }, [] );
+
return (
-
+
+
+
+
+ { !! variations?.length && (
+
+
+ { __( 'Other styles' ) }
+
+
+
+
+
+ ) }
+
diff --git a/packages/edit-site/src/components/global-styles/screen-style-variations.js b/packages/edit-site/src/components/global-styles/screen-style-variations.js
new file mode 100644
index 00000000000000..f6b93d25982c29
--- /dev/null
+++ b/packages/edit-site/src/components/global-styles/screen-style-variations.js
@@ -0,0 +1,130 @@
+/**
+ * External dependencies
+ */
+import { isEqual } from 'lodash';
+import classnames from 'classnames';
+
+/**
+ * WordPress dependencies
+ */
+import { store as coreStore } from '@wordpress/core-data';
+import { useSelect } from '@wordpress/data';
+import { useMemo, useContext } from '@wordpress/element';
+import { ENTER } from '@wordpress/keycodes';
+import {
+ __experimentalGrid as Grid,
+ Card,
+ CardBody,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { mergeBaseAndUserConfigs } from './global-styles-provider';
+import { GlobalStylesContext } from './context';
+import StylesPreview from './preview';
+import ScreenHeader from './header';
+
+function compareVariations( a, b ) {
+ return isEqual( a.styles, b.styles ) && isEqual( a.settings, b.settings );
+}
+
+function Variation( { variation } ) {
+ const { base, user, setUserConfig } = useContext( GlobalStylesContext );
+ const context = useMemo( () => {
+ return {
+ user: {
+ settings: variation.settings ?? {},
+ styles: variation.styles ?? {},
+ },
+ base,
+ merged: mergeBaseAndUserConfigs( base, variation ),
+ setUserConfig: () => {},
+ };
+ }, [ variation, base ] );
+
+ const selectVariation = () => {
+ setUserConfig( () => {
+ return {
+ settings: variation.settings,
+ styles: variation.styles,
+ };
+ } );
+ };
+
+ const selectOnEnter = ( event ) => {
+ if ( event.keyCode === ENTER ) {
+ event.preventDefault();
+ selectVariation();
+ }
+ };
+
+ const isActive = useMemo( () => {
+ return compareVariations( user, variation );
+ }, [ user, variation ] );
+
+ return (
+
+
+
+
+
+ );
+}
+
+function ScreenStyleVariations() {
+ const { variations } = useSelect( ( select ) => {
+ return {
+ variations: select(
+ coreStore
+ ).__experimentalGetCurrentThemeGlobalStylesVariations(),
+ };
+ }, [] );
+
+ const withEmptyVariation = useMemo( () => {
+ return [
+ {
+ name: __( 'Default' ),
+ settings: {},
+ styles: {},
+ },
+ ...variations,
+ ];
+ }, [ variations ] );
+
+ return (
+ <>
+
+
+
+
+
+ { withEmptyVariation?.map( ( variation, index ) => (
+
+ ) ) }
+
+
+
+ >
+ );
+}
+
+export default ScreenStyleVariations;
diff --git a/packages/edit-site/src/components/global-styles/style.scss b/packages/edit-site/src/components/global-styles/style.scss
index ab06b2d4a65e67..6dd79615f673af 100644
--- a/packages/edit-site/src/components/global-styles/style.scss
+++ b/packages/edit-site/src/components/global-styles/style.scss
@@ -2,8 +2,13 @@
display: flex;
align-items: center;
justify-content: center;
- min-height: 152px;
line-height: 1;
+ cursor: pointer;
+}
+
+.edit-site-global-styles-preview__iframe {
+ max-width: 100%;
+ display: block;
}
.edit-site-typography-panel__preview {
@@ -65,3 +70,22 @@
.edit-site-screen-background-color__control {
padding: $grid-unit-20;
}
+
+.edit-site-global-styles-variations_item {
+ box-sizing: border-box;
+ padding: $border-width * 2;
+ border-radius: $radius-block-ui;
+ border: $gray-200 $border-width solid;
+
+ &.is-active {
+ border: $gray-900 $border-width solid;
+ }
+
+ &:hover {
+ border: var(--wp-admin-theme-color) $border-width solid;
+ }
+
+ &:focus {
+ border: var(--wp-admin-theme-color) $border-width solid;
+ }
+}
diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js
index 9d0b545d199002..793b0939d9014c 100644
--- a/packages/edit-site/src/components/global-styles/ui.js
+++ b/packages/edit-site/src/components/global-styles/ui.js
@@ -21,6 +21,7 @@ import ScreenBackgroundColor from './screen-background-color';
import ScreenTextColor from './screen-text-color';
import ScreenLinkColor from './screen-link-color';
import ScreenLayout from './screen-layout';
+import ScreenStyleVariations from './screen-style-variations';
function GlobalStylesNavigationScreen( { className, ...props } ) {
return (
@@ -100,6 +101,10 @@ function GlobalStylesUI() {
+
+
+
+