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 } } > - +
+
Aa
- Aa +
{ ' ' } +
- - - - - - +
+ ); }; 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() { + + + +