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 ee27960529ede3..cfa5930ba7034c 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
@@ -55,6 +55,7 @@ export default function useMultipleOriginColorsAndGradients() {
'Theme',
'Indicates this palette comes from the theme.'
),
+ slug: 'theme',
colors: themeColors,
} );
}
@@ -68,6 +69,7 @@ export default function useMultipleOriginColorsAndGradients() {
'Default',
'Indicates this palette comes from WordPress.'
),
+ slug: 'default',
colors: defaultColors,
} );
}
@@ -75,8 +77,9 @@ export default function useMultipleOriginColorsAndGradients() {
result.push( {
name: _x(
'Custom',
- 'Indicates this palette comes from the theme.'
+ 'Indicates this palette is created by the user.'
),
+ slug: 'custom',
colors: customColors,
} );
}
@@ -96,6 +99,7 @@ export default function useMultipleOriginColorsAndGradients() {
'Theme',
'Indicates this palette comes from the theme.'
),
+ slug: 'theme',
gradients: themeGradients,
} );
}
@@ -109,6 +113,7 @@ export default function useMultipleOriginColorsAndGradients() {
'Default',
'Indicates this palette comes from WordPress.'
),
+ slug: 'default',
gradients: defaultGradients,
} );
}
@@ -118,6 +123,7 @@ export default function useMultipleOriginColorsAndGradients() {
'Custom',
'Indicates this palette is created by the user.'
),
+ slug: 'custom',
gradients: customGradients,
} );
}
diff --git a/packages/edit-site/src/components/global-styles/ui.js b/packages/edit-site/src/components/global-styles/ui.js
index 6cd465e237100a..9ca88f40f1f001 100644
--- a/packages/edit-site/src/components/global-styles/ui.js
+++ b/packages/edit-site/src/components/global-styles/ui.js
@@ -45,6 +45,7 @@ import ScreenCSS from './screen-css';
import ScreenRevisions from './screen-revisions';
import { unlock } from '../../lock-unlock';
import { store as editSiteStore } from '../../store';
+import { STYLE_BOOK_COLOR_GROUPS } from '../style-book/constants';
const SLOT_FILL_NAME = 'GlobalStylesMenu';
const { useGlobalStylesReset } = unlock( blockEditorPrivateApis );
@@ -191,6 +192,16 @@ function GlobalStylesStyleBook() {
)
}
onSelect={ ( blockName ) => {
+ if (
+ STYLE_BOOK_COLOR_GROUPS.find(
+ ( group ) => group.slug === blockName
+ )
+ ) {
+ // Go to color palettes Global Styles.
+ navigator.goTo( '/colors/palette' );
+ return;
+ }
+
// Now go to the selected block.
navigator.goTo( '/blocks/' + encodeURIComponent( blockName ) );
} }
diff --git a/packages/edit-site/src/components/style-book/color-examples.tsx b/packages/edit-site/src/components/style-book/color-examples.tsx
new file mode 100644
index 00000000000000..97bdeecee32d3d
--- /dev/null
+++ b/packages/edit-site/src/components/style-book/color-examples.tsx
@@ -0,0 +1,44 @@
+/**
+ * External dependencies
+ */
+import clsx from 'clsx';
+
+/**
+ * WordPress dependencies
+ */
+import { __experimentalGrid as Grid } from '@wordpress/components';
+import { View } from '@wordpress/primitives';
+import {
+ getColorClassName,
+ __experimentalGetGradientClass,
+} from '@wordpress/block-editor';
+
+/**
+ * Internal dependencies
+ */
+import type { Color, Gradient } from './types';
+
+const ColorExamples = ( { colors, type } ): JSX.Element | null => {
+ if ( ! colors ) {
+ return null;
+ }
+
+ return (
+
+ { colors.map( ( color: Color | Gradient ) => {
+ const className =
+ type === 'gradients'
+ ? __experimentalGetGradientClass( color.slug )
+ : getColorClassName( 'background-color', color.slug );
+ const classes = clsx(
+ 'edit-site-style-book__color-example',
+ className
+ );
+
+ return ;
+ } ) }
+
+ );
+};
+
+export default ColorExamples;
diff --git a/packages/edit-site/src/components/style-book/constants.ts b/packages/edit-site/src/components/style-book/constants.ts
index fc06d8f1409f0d..1ba1bf5e3dffa9 100644
--- a/packages/edit-site/src/components/style-book/constants.ts
+++ b/packages/edit-site/src/components/style-book/constants.ts
@@ -6,7 +6,52 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
-import type { StyleBookCategory } from './types';
+import type { StyleBookCategory, StyleBookColorGroup } from './types';
+
+export const STYLE_BOOK_COLOR_GROUPS: StyleBookColorGroup[] = [
+ {
+ slug: 'theme-colors',
+ title: __( 'Theme Colors' ),
+ origin: 'theme',
+ type: 'colors',
+ },
+ {
+ slug: 'theme-gradients',
+ title: __( 'Theme Gradients' ),
+ origin: 'theme',
+ type: 'gradients',
+ },
+ {
+ slug: 'custom-colors',
+ title: __( 'Custom Colors' ),
+ origin: 'custom',
+ type: 'colors',
+ },
+ {
+ slug: 'custom-gradients',
+ title: __( 'Custom Gradients' ),
+ origin: 'custom', // User.
+ type: 'gradients',
+ },
+ {
+ slug: 'duotones',
+ title: __( 'Duotones' ),
+ origin: 'theme',
+ type: 'duotones',
+ },
+ {
+ slug: 'default-colors',
+ title: __( 'Default Colors' ),
+ origin: 'default',
+ type: 'colors',
+ },
+ {
+ slug: 'default-gradients',
+ title: __( 'Default Gradients' ),
+ origin: 'default',
+ type: 'gradients',
+ },
+];
export const STYLE_BOOK_THEME_SUBCATEGORIES: Omit<
StyleBookCategory,
@@ -74,7 +119,7 @@ export const STYLE_BOOK_CATEGORIES: StyleBookCategory[] = [
{
slug: 'colors',
title: __( 'Colors' ),
- blocks: [ 'custom/colors' ],
+ blocks: [],
},
{
slug: 'theme',
@@ -111,7 +156,7 @@ export const STYLE_BOOK_IFRAME_STYLES = `
.is-root-container {
display: flow-root;
}
-
+
body {
position: relative;
padding: 32px !important;
@@ -141,15 +186,40 @@ export const STYLE_BOOK_IFRAME_STYLES = `
box-shadow: 0 0 0 1px var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
}
+ .edit-site-style-book__example.is-disabled-example {
+ pointer-events: none;
+ }
+
.edit-site-style-book__example:focus:not(:disabled) {
box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
outline: 3px solid transparent;
}
+ .edit-site-style-book__duotone-example > div:first-child {
+ display: flex;
+ aspect-ratio: 16 / 9;
+ grid-row: span 1;
+ grid-column: span 2;
+ }
+ .edit-site-style-book__duotone-example img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ .edit-site-style-book__duotone-example > div:not(:first-child) {
+ height: 20px;
+ border: 1px solid #ddd;
+ }
+
+ .edit-site-style-book__color-example {
+ height: 52px;
+ border: 1px solid #ddd;
+ }
+
.edit-site-style-book__examples.is-wide .edit-site-style-book__example {
flex-direction: row;
}
-
+
.edit-site-style-book__subcategory-title,
.edit-site-style-book__example-title {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
@@ -160,7 +230,7 @@ export const STYLE_BOOK_IFRAME_STYLES = `
text-align: left;
text-transform: uppercase;
}
-
+
.edit-site-style-book__subcategory-title {
font-size: 16px;
margin-bottom: 40px;
diff --git a/packages/edit-site/src/components/style-book/duotone-examples.tsx b/packages/edit-site/src/components/style-book/duotone-examples.tsx
new file mode 100644
index 00000000000000..7ee90e61f1c6aa
--- /dev/null
+++ b/packages/edit-site/src/components/style-book/duotone-examples.tsx
@@ -0,0 +1,53 @@
+/**
+ * WordPress dependencies
+ */
+import { __experimentalGrid as Grid } from '@wordpress/components';
+import { View } from '@wordpress/primitives';
+
+/**
+ * Internal dependencies
+ */
+import type { Duotone } from './types';
+
+const DuotoneExamples = ( { duotones } ): JSX.Element | null => {
+ if ( ! duotones ) {
+ return null;
+ }
+
+ return (
+
+ { duotones.map( ( duotone: Duotone ) => {
+ return (
+
+
+
+
+ { duotone.colors.map( ( color ) => {
+ return (
+
+ );
+ } ) }
+
+ );
+ } ) }
+
+ );
+};
+
+export default DuotoneExamples;
diff --git a/packages/edit-site/src/components/style-book/examples.ts b/packages/edit-site/src/components/style-book/examples.ts
deleted file mode 100644
index 80807b10374c68..00000000000000
--- a/packages/edit-site/src/components/style-book/examples.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __, sprintf } from '@wordpress/i18n';
-import {
- getBlockType,
- getBlockTypes,
- getBlockFromExample,
- createBlock,
-} from '@wordpress/blocks';
-
-/**
- * Internal dependencies
- */
-import type { BlockExample } from './types';
-
-/**
- * Returns a list of examples for registered block types.
- *
- * @return {BlockExample[]} An array of block examples.
- */
-export function getExamples(): BlockExample[] {
- const nonHeadingBlockExamples = getBlockTypes()
- .filter( ( blockType ) => {
- const { name, example, supports } = blockType;
- return (
- name !== 'core/heading' &&
- !! example &&
- supports.inserter !== false
- );
- } )
- .map( ( blockType ) => ( {
- name: blockType.name,
- title: blockType.title,
- category: blockType.category,
- blocks: getBlockFromExample( blockType.name, blockType.example ),
- } ) );
- const isHeadingBlockRegistered = !! getBlockType( 'core/heading' );
-
- if ( ! isHeadingBlockRegistered ) {
- return nonHeadingBlockExamples;
- }
-
- // Use our own example for the Heading block so that we can show multiple
- // heading levels.
- const headingsExample = {
- name: 'core/heading',
- title: __( 'Headings' ),
- category: 'text',
- blocks: [ 1, 2, 3, 4, 5, 6 ].map( ( level ) => {
- return createBlock( 'core/heading', {
- content: sprintf(
- // translators: %d: heading level e.g: "1", "2", "3"
- __( 'Heading %d' ),
- level
- ),
- level,
- } );
- } ),
- };
-
- return [ headingsExample, ...nonHeadingBlockExamples ];
-}
diff --git a/packages/edit-site/src/components/style-book/examples.tsx b/packages/edit-site/src/components/style-book/examples.tsx
new file mode 100644
index 00000000000000..9f4badd99a6582
--- /dev/null
+++ b/packages/edit-site/src/components/style-book/examples.tsx
@@ -0,0 +1,113 @@
+/**
+ * WordPress dependencies
+ */
+import { __, sprintf } from '@wordpress/i18n';
+import {
+ getBlockType,
+ getBlockTypes,
+ getBlockFromExample,
+ createBlock,
+} from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import type { BlockExample, ColorOrigin, MultiOriginPalettes } from './types';
+import ColorExamples from './color-examples';
+import DuotoneExamples from './duotone-examples';
+import { STYLE_BOOK_COLOR_GROUPS } from './constants';
+
+/**
+ * Returns examples color examples for each origin
+ * e.g. Core (Default), Theme, and User.
+ *
+ * @param {MultiOriginPalettes} colors Global Styles color palettes per origin.
+ * @return {BlockExample[]} An array of color block examples.
+ */
+function getColorExamples( colors: MultiOriginPalettes ): BlockExample[] {
+ if ( ! colors ) {
+ return [];
+ }
+
+ const examples: BlockExample[] = [];
+
+ STYLE_BOOK_COLOR_GROUPS.forEach( ( group ) => {
+ const palette = colors[ group.type ].find(
+ ( origin: ColorOrigin ) => origin.slug === group.origin
+ );
+
+ if ( palette?.[ group.type ] ) {
+ const example: BlockExample = {
+ name: group.slug,
+ title: group.title,
+ category: 'colors',
+ };
+ if ( group.type === 'duotones' ) {
+ example.content = (
+
+ );
+ examples.push( example );
+ } else {
+ example.content = (
+
+ );
+ examples.push( example );
+ }
+ }
+ } );
+
+ return examples;
+}
+
+/**
+ * Returns a list of examples for registered block types.
+ *
+ * @param {MultiOriginPalettes} colors Global styles colors grouped by origin e.g. Core, Theme, and User.
+ * @return {BlockExample[]} An array of block examples.
+ */
+export function getExamples( colors: MultiOriginPalettes ): BlockExample[] {
+ const nonHeadingBlockExamples = getBlockTypes()
+ .filter( ( blockType ) => {
+ const { name, example, supports } = blockType;
+ return (
+ name !== 'core/heading' &&
+ !! example &&
+ supports?.inserter !== false
+ );
+ } )
+ .map( ( blockType ) => ( {
+ name: blockType.name,
+ title: blockType.title,
+ category: blockType.category,
+ blocks: getBlockFromExample( blockType.name, blockType.example ),
+ } ) );
+ const isHeadingBlockRegistered = !! getBlockType( 'core/heading' );
+
+ if ( ! isHeadingBlockRegistered ) {
+ return nonHeadingBlockExamples;
+ }
+
+ // Use our own example for the Heading block so that we can show multiple
+ // heading levels.
+ const headingsExample = {
+ name: 'core/heading',
+ title: __( 'Headings' ),
+ category: 'text',
+ blocks: [ 1, 2, 3, 4, 5, 6 ].map( ( level ) => {
+ return createBlock( 'core/heading', {
+ content: sprintf(
+ // translators: %d: heading level e.g: "1", "2", "3"
+ __( 'Heading %d' ),
+ level
+ ),
+ level,
+ } );
+ } ),
+ };
+ const colorExamples = getColorExamples( colors );
+
+ return [ headingsExample, ...colorExamples, ...nonHeadingBlockExamples ];
+}
diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js
index 42b6e3f4fc99b6..0db99cfe56638a 100644
--- a/packages/edit-site/src/components/style-book/index.js
+++ b/packages/edit-site/src/components/style-book/index.js
@@ -11,18 +11,26 @@ import {
Composite,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
-import { __, sprintf } from '@wordpress/i18n';
+import { __, _x, sprintf } from '@wordpress/i18n';
import {
BlockList,
privateApis as blockEditorPrivateApis,
store as blockEditorStore,
+ useSettings,
__unstableEditorStyles as EditorStyles,
__unstableIframe as Iframe,
+ __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients,
} from '@wordpress/block-editor';
import { privateApis as editorPrivateApis } from '@wordpress/editor';
import { useSelect } from '@wordpress/data';
import { useResizeObserver } from '@wordpress/compose';
-import { useMemo, useState, memo, useContext } from '@wordpress/element';
+import {
+ useMemo,
+ useState,
+ memo,
+ useContext,
+ useEffect,
+} from '@wordpress/element';
import { ENTER, SPACE } from '@wordpress/keycodes';
/**
@@ -51,6 +59,81 @@ function isObjectEmpty( object ) {
return ! object || Object.keys( object ).length === 0;
}
+/**
+ * Retrieves colors, gradients, and duotone filters from Global Styles.
+ * The inclusion of default (Core) palettes is controlled by the relevant
+ * theme.json property e.g. defaultPalette, defaultGradients, defaultDuotone.
+ *
+ * @return {Object} Object containing properties for each type of palette.
+ */
+function useMultiOriginPalettes() {
+ const { colors, gradients } = useMultipleOriginColorsAndGradients();
+
+ // Add duotone filters to the palettes data.
+ const [
+ shouldDisplayDefaultDuotones,
+ customDuotones,
+ themeDuotones,
+ defaultDuotones,
+ ] = useSettings(
+ 'color.defaultDuotone',
+ 'color.duotone.custom',
+ 'color.duotone.theme',
+ 'color.duotone.default'
+ );
+
+ const palettes = useMemo( () => {
+ const result = { colors, gradients, duotones: [] };
+
+ if ( themeDuotones && themeDuotones.length ) {
+ result.duotones.push( {
+ name: _x(
+ 'Theme',
+ 'Indicates these duotone filters come from the theme.'
+ ),
+ slug: 'theme',
+ duotones: themeDuotones,
+ } );
+ }
+
+ if (
+ shouldDisplayDefaultDuotones &&
+ defaultDuotones &&
+ defaultDuotones.length
+ ) {
+ result.duotones.push( {
+ name: _x(
+ 'Default',
+ 'Indicates these duotone filters come from WordPress.'
+ ),
+ slug: 'default',
+ duotones: defaultDuotones,
+ } );
+ }
+ if ( customDuotones && customDuotones.length ) {
+ result.duotones.push( {
+ name: _x(
+ 'Custom',
+ 'Indicates these doutone filters are created by the user.'
+ ),
+ slug: 'custom',
+ duotones: customDuotones,
+ } );
+ }
+
+ return result;
+ }, [
+ colors,
+ gradients,
+ customDuotones,
+ themeDuotones,
+ defaultDuotones,
+ shouldDisplayDefaultDuotones,
+ ] );
+
+ return palettes;
+}
+
function StyleBook( {
enableResizing = true,
isSelected,
@@ -64,7 +147,8 @@ function StyleBook( {
const [ resizeObserver, sizes ] = useResizeObserver();
const [ textColor ] = useGlobalStyle( 'color.text' );
const [ backgroundColor ] = useGlobalStyle( 'color.background' );
- const [ examples ] = useState( getExamples );
+ const colors = useMultiOriginPalettes();
+ const [ examples, setExamples ] = useState( () => getExamples( colors ) );
const tabs = useMemo(
() =>
getTopLevelStyleBookCategories().filter( ( category ) =>
@@ -74,6 +158,12 @@ function StyleBook( {
),
[ examples ]
);
+
+ // Ensure color examples are kept in sync with Global Styles palette changes.
+ useEffect( () => {
+ setExamples( getExamples( colors ) );
+ }, [ colors ] );
+
const { base: baseConfig } = useContext( GlobalStylesContext );
const mergedConfig = useMemo( () => {
@@ -271,6 +361,7 @@ const Examples = memo(
key={ example.name }
id={ `example-${ example.name }` }
title={ example.title }
+ content={ example.content }
blocks={ example.blocks }
isSelected={ isSelected( example.name ) }
onClick={ () => {
@@ -309,6 +400,7 @@ const Subcategory = ( { examples, isSelected, onSelect } ) => {
key={ example.name }
id={ `example-${ example.name }` }
title={ example.title }
+ content={ example.content }
blocks={ example.blocks }
isSelected={ isSelected( example.name ) }
onClick={ () => {
@@ -319,7 +411,9 @@ const Subcategory = ( { examples, isSelected, onSelect } ) => {
);
};
-const Example = ( { id, title, blocks, isSelected, onClick } ) => {
+const disabledExamples = [ 'example-duotones' ];
+
+const Example = ( { id, title, blocks, isSelected, onClick, content } ) => {
const originalSettings = useSelect(
( select ) => select( blockEditorStore ).getSettings(),
[]
@@ -339,12 +433,20 @@ const Example = ( { id, title, blocks, isSelected, onClick } ) => {
[ blocks ]
);
+ const disabledProps = disabledExamples.includes( id )
+ ? {
+ disabled: true,
+ accessibleWhenDisabled: true,
+ }
+ : {};
+
return (
{
render={ }
role="button"
onClick={ onClick }
+ { ...disabledProps }
>
{ title }
@@ -364,12 +467,17 @@ const Example = ( { id, title, blocks, isSelected, onClick } ) => {
aria-hidden
>
-
-
-
+ { content ? (
+ content
+ ) : (
+
+
+
+
+ ) }
diff --git a/packages/edit-site/src/components/style-book/test/categories.js b/packages/edit-site/src/components/style-book/test/categories.js
index 5629689e260f89..128eaf282a9c5d 100644
--- a/packages/edit-site/src/components/style-book/test/categories.js
+++ b/packages/edit-site/src/components/style-book/test/categories.js
@@ -48,11 +48,6 @@ const exampleThemeBlocks = [
title: 'Home Link',
category: 'design',
},
- {
- name: 'custom/colors',
- title: 'Colors',
- category: 'colors',
- },
{
name: 'core/site-logo',
title: 'Site Logo',
diff --git a/packages/edit-site/src/components/style-book/types.ts b/packages/edit-site/src/components/style-book/types.ts
index 4729b38b1b2bb1..e7be17b17dd4df 100644
--- a/packages/edit-site/src/components/style-book/types.ts
+++ b/packages/edit-site/src/components/style-book/types.ts
@@ -9,6 +9,7 @@ export type StyleBookCategory = {
slug: string;
blocks?: string[];
exclude?: string[];
+ include?: string[];
subcategories?: StyleBookCategory[];
};
@@ -16,7 +17,8 @@ export type BlockExample = {
name: string;
title: string;
category: string;
- blocks: Block | Block[];
+ content?: JSX.Element;
+ blocks?: Block | Block[];
};
export type CategoryExamples = {
@@ -25,3 +27,34 @@ export type CategoryExamples = {
examples?: BlockExample[];
subcategories?: CategoryExamples[];
};
+
+export type StyleBookColorGroup = {
+ origin: string;
+ slug: string;
+ title: string;
+ type: string;
+};
+
+export type Color = { slug: string };
+export type Gradient = { slug: string };
+export type Duotone = {
+ colors: string[];
+ slug: string;
+};
+
+export type ColorOrigin = {
+ name: string;
+ slug: string;
+ colors?: Color[];
+ gradients?: Gradient[];
+ duotones?: Duotone[];
+};
+
+export type MultiOriginPalettes = {
+ disableCustomColors: boolean;
+ disableCustomGradients: boolean;
+ hasColorsOrGradients: boolean;
+ colors: Omit< ColorOrigin, 'gradients' | 'duotones' >;
+ duotones: Omit< ColorOrigin, 'colors' | 'gradients' >;
+ gradients: Omit< ColorOrigin, 'colors' | 'duotones' >;
+};