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 ee27960529ede..cfa5930ba7034 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 6cd465e237100..9ca88f40f1f00 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 0000000000000..97bdeecee32d3 --- /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 fc06d8f1409f0..1ba1bf5e3dffa 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 0000000000000..7ee90e61f1c6a --- /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 80807b10374c6..0000000000000 --- 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 0000000000000..9f4badd99a658 --- /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 42b6e3f4fc99b..0db99cfe56638 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 5629689e260f8..128eaf282a9c5 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 4729b38b1b2bb..e7be17b17dd4d 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' >; +};