Skip to content

Commit 7bb013f

Browse files
MaggieCabreraclaudemikachan
authored
Global Styles: List elements alongside blocks (#81645)
* Global Styles: Add Forms section * Global Styles: Add Elements section * Global Styles: Give elements their own home and the full control set Blocks and elements are listed together under one item in the main Styles menu, so there is a single place to style anything on the site. Inputs, selects and buttons are grouped under Form controls. Elements now offer the background, spacing, border, shadow and custom CSS controls that theme.json already accepts and the styles engine already emits, instead of typography and colour alone. Sizing, block gap, text alignment and duotone stay out: the first three belong to the block that contains an element, and the engine emits nothing for the last two. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Global Styles: Preview elements with the site's own styles An element was previewed as a letter specimen built from a handful of properties copied across by hand, so it could not react to the border, shadow, spacing or background controls elements now have. Each element is previewed instead as a sample of the markup its selector applies to, rendered in the editor iframe with the generated stylesheet. The sample picks up the cascade, the theme's fonts and every panel without a list of properties to keep in sync. Text color and background color are left to the Typography and Background panels, where they were relocated in #77279, rather than shown a second time in a Colors panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Global Styles: List elements in one flat list with blocks Elements sit in the same list as blocks under Elements, Headings and Form controls subheadings, with no drilldowns between them, so everything that can be styled is one screen away and easy to find alongside blocks. Each heading level is its own row, and the button element is labelled to tell it apart from the Button block further down the list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Global Styles: Resolve an element's styles once per screen The typography panel had its own wrapper that read the element's styles and settings a second time, separately from the panels beside it. One component now resolves them and renders every panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Global Styles: Share border handling and the CSS capability check The block and element screens each carried their own copy of the border reconciliation and of the check for whether custom CSS may be edited. Both now use one shared version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Global Styles: Restore the Text and All headings entries and fix CI Bring back the root Text screen under Typography, which the style variations tests depend on, and list "All headings" at the top of the Headings group so the shared `elements.heading` node themes style can still be edited. Update the unit expectations for the widened element supports, the e2e labels for the renamed "Blocks & Elements" screen, and add the PR links the changelog check requires. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Global Styles: Reformat for the wp-prettier upgrade on trunk Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Icons: Move the formInput changelog entry to Unreleased The rebase left the entry under the published 17.0.0 section, which fails the changelog checks. Also restore the formatting of an older entry the branch had touched without reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Mention elements in the Blocks & Elements description Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Blocks: Keep text alignment for elements in Global Styles Text alignment was added to elements in #73854, and the element screens should keep offering it alongside the new panels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Offer states on the link and button element screens Moving the element colors out of the Colors screen dropped the only control for link hover color. The link and button element screens now offer the states the package already defines for them, and follow the setting that turns off state editing for blocks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Show the default background size on element screens An element's background image with no size renders with cover, as a block's does, so the element screens now show the same defaults as the block screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Use one route for the element screens The block list's elements now open through a single path with the element name as a parameter, instead of one route per element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Share element titles between the list and their screens The Blocks & Elements list now reads each element's label from the element screen's table, so the two cannot drift apart. The Button element's screen is titled "Button (element)" to match its row and tell it apart from the Button block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Restore trunk's formatting of a media query Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Drop the deprecated spacer from the element screen The preview's own class now sets its side margin, and no longer shrinks inside the screen's scrolling flex column. This removes the element screen's use-recommended-components suppression. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Keep trunk's Text row under Typography Restore trunk's Typography element list with only the Text row, instead of rebuilding it inline. The row keeps its sample in the site's font, and the other elements stay in the Blocks & Elements list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Move this PR's changelog entries back under Unreleased Trunk published new releases, so the rebase left the entries under published versions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Drop the custom styles dot from element rows Trunk removed the dot from block rows in #84657. Element rows follow, and the customized filter keeps working for both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Keep the border helper in the block screen Trunk's fallback helpers stay where they were, and the border handling the element screen shares becomes an exported function next to them, instead of a new file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Inline the customized check for elements The element list calls the existing value check directly instead of a separate exported wrapper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Check that searching lists matching elements Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Style Book: Show the Text category on element screens Element screens moved under the Blocks & Elements path, so the Style Book showed every block for them instead of the Text category it showed when they lived under Typography. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: Match element names and group titles in search Searching "h1" now finds Heading 1, and "form" lists the form controls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the formatting of the heading styles end-to-end test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Update the ESLint suppressions after rebasing on trunk Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Blocks: Leave the layout widths out of element styles Elements started from the root supports, so they kept contentSize and wideSize. The element screen never renders the width controls, but the Dimensions panel still counted them and showed up empty on themes that set layout widths without padding or margin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Global Styles: List elements without icons Whether elements need icons is still open in #84074, so start without them and drop the formInput icon this PR added to @wordpress/icons. Also restore the icons changelog line this PR reformatted by accident. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Update packages/editor/src/components/global-styles/block-link.js Co-authored-by: Sarah Norris <1645628+mikachan@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Sarah Norris <1645628+mikachan@users.noreply.github.com>
1 parent 2a96cb2 commit 7bb013f

26 files changed

Lines changed: 790 additions & 503 deletions

‎packages/blocks/src/store/private-selectors.ts‎

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { createSelector, createRegistrySelector } from '@wordpress/data';
22
import deprecated from '@wordpress/deprecated';
33
import { getBlockType } from './selectors';
44
import { getValueFromObjectPath } from './utils';
5-
import { __EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY } from '../api/constants';
5+
import {
6+
__EXPERIMENTAL_ELEMENTS as ELEMENTS,
7+
__EXPERIMENTAL_STYLE_PROPERTY as STYLE_PROPERTY,
8+
} from '../api/constants';
69
import type { BlockStoreState } from './types';
710
import type {
811
BlockAttribute,
@@ -56,6 +59,30 @@ const ROOT_BLOCK_SUPPORTS: string[] = [
5659
'letterSpacing',
5760
];
5861

62+
/*
63+
* theme.json accepts the complete style set on every element, and the styles
64+
* engine emits all of it, so Global Styles offers elements the same controls it
65+
* offers blocks. These are the supports missing from the root list above.
66+
*
67+
* `filter` is deliberately absent: theme.json validates a duotone on an element
68+
* but the engine emits no CSS for it, so the control would do nothing. Sizing
69+
* is left out entirely (aspect ratio, height, width and their minimums)
70+
* because an element is sized by the block that contains it.
71+
*/
72+
const ELEMENT_BLOCK_SUPPORTS: string[] = [
73+
...ROOT_BLOCK_SUPPORTS,
74+
'backgroundGradient',
75+
'backgroundImage',
76+
'backgroundSize',
77+
'borderColor',
78+
'borderRadius',
79+
'borderStyle',
80+
'borderWidth',
81+
'margin',
82+
'shadow',
83+
'writingMode',
84+
];
85+
5986
/**
6087
* Filters the list of supported styles for a given element.
6188
*
@@ -122,6 +149,18 @@ function filterElementBlockSupports(
122149
return false;
123150
}
124151

152+
const isElement = ! name && !! element && element in ELEMENTS;
153+
154+
// Block gap and the layout widths are emitted for blocks and for the
155+
// root, but not for an element's own selector, so their controls would
156+
// have no effect there.
157+
if (
158+
[ 'blockGap', 'contentSize', 'wideSize' ].includes( support ) &&
159+
isElement
160+
) {
161+
return false;
162+
}
163+
125164
return true;
126165
} );
127166
}
@@ -136,8 +175,11 @@ export const getSupportedStyles = createSelector(
136175
element: string | undefined
137176
): string[] => {
138177
if ( ! name ) {
178+
// `element` also carries UI-only names such as 'text', which mean
179+
// the root rather than an element, and keep the root supports.
180+
const isElement = !! element && element in ELEMENTS;
139181
return filterElementBlockSupports(
140-
ROOT_BLOCK_SUPPORTS,
182+
isElement ? ELEMENT_BLOCK_SUPPORTS : ROOT_BLOCK_SUPPORTS,
141183
name,
142184
element
143185
);

‎packages/blocks/src/store/test/private-selectors.js‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,19 @@ describe( 'private selectors', () => {
6161
'fontWeight',
6262
'lineHeight',
6363
'padding',
64-
'contentSize',
65-
'wideSize',
66-
'blockGap',
6764
'textAlign',
6865
'textDecoration',
6966
'textShadow',
67+
'backgroundGradient',
68+
'backgroundImage',
69+
'backgroundSize',
70+
'borderColor',
71+
'borderRadius',
72+
'borderStyle',
73+
'borderWidth',
74+
'margin',
75+
'shadow',
76+
'writingMode',
7077
] );
7178
} );
7279

@@ -90,13 +97,20 @@ describe( 'private selectors', () => {
9097
'fontWeight',
9198
'lineHeight',
9299
'padding',
93-
'contentSize',
94-
'wideSize',
95-
'blockGap',
96100
'textAlign',
97101
'textTransform',
98102
'textShadow',
99103
'letterSpacing',
104+
'backgroundGradient',
105+
'backgroundImage',
106+
'backgroundSize',
107+
'borderColor',
108+
'borderRadius',
109+
'borderStyle',
110+
'borderWidth',
111+
'margin',
112+
'shadow',
113+
'writingMode',
100114
] );
101115
} );
102116

‎packages/editor/src/components/global-styles/block-link.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export function GlobalStylesBlockLink( { path, onPathChange } ) {
3939
}
4040
if (
4141
! path ||
42-
( path !== '/blocks' && ! path.startsWith( '/blocks/' ) )
42+
( path !== '/blocks' && ! path.startsWith( '/blocks/' ) ) ||
43+
path.startsWith( '/blocks/elements/' )
4344
) {
4445
return;
4546
}

‎packages/editor/src/components/style-book/index.jsx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,12 @@ export const StyleBookPreview = ( {
443443
let blockVariation = null;
444444
if ( section.includes( '/colors' ) ) {
445445
previewCategory = 'colors';
446-
} else if ( section.includes( '/typography' ) ) {
446+
} else if (
447+
section.includes( '/typography' ) ||
448+
// Element screens show the Text category, as they did when they
449+
// lived under Typography.
450+
section.startsWith( '/blocks/elements/' )
451+
) {
447452
previewCategory = 'text';
448453
} else if ( section.includes( '/blocks' ) ) {
449454
previewCategory = 'blocks';

‎packages/global-styles-ui/CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
### Enhancements
1010

11+
- List elements alongside blocks in one Blocks & Elements screen, grouped under Elements, Headings and Form controls, instead of splitting them between Typography and Colors ([#81645](https://github.com/WordPress/gutenberg/pull/81645)).
12+
- Offer every element the background, dimensions, border, shadow and custom CSS controls that `theme.json` already accepts, instead of typography and color alone ([#81645](https://github.com/WordPress/gutenberg/pull/81645)).
13+
- Preview an element as a sample of the markup it applies to, rendered with the site's own styles, so the preview reflects every panel rather than typography and color alone ([#81645](https://github.com/WordPress/gutenberg/pull/81645)).
1114
- Font Library: Use `Select` from `@wordpress/ui` for the pagination page selector ([#83035](https://github.com/WordPress/gutenberg/pull/83035)).
1215
- Remove the dot that marks blocks with user styles in the block list. The filter to show only those blocks stays ([#84657](https://github.com/WordPress/gutenberg/pull/84657)).
1316
- Font Library: Mark installed font variants in font collections ([#84023](https://github.com/WordPress/gutenberg/pull/84023)).
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { __ } from '@wordpress/i18n';
2+
import { useMemo } from '@wordpress/element';
3+
import { useSelect } from '@wordpress/data';
4+
import { getBlockType } from '@wordpress/blocks';
5+
import {
6+
__unstableIframe as Iframe,
7+
__unstableEditorStyles as EditorStyles,
8+
store as blockEditorStore,
9+
// @ts-expect-error: Not typed yet.
10+
} from '@wordpress/block-editor';
11+
12+
interface ElementPreviewProps {
13+
element: string;
14+
}
15+
16+
/*
17+
* Centres the sample and strips the canvas chrome. Everything the sample
18+
* actually looks like comes from the site's own stylesheet.
19+
*/
20+
const PREVIEW_CSS = `
21+
body {
22+
margin: 0;
23+
padding: 16px;
24+
min-height: 100px;
25+
display: flex;
26+
align-items: center;
27+
justify-content: center;
28+
text-align: center;
29+
overflow: hidden;
30+
}
31+
figure.wp-block-image img.global-styles-ui-element-preview__media {
32+
display: block;
33+
width: 100%;
34+
height: 44px;
35+
max-height: 44px;
36+
border-radius: 2px;
37+
object-fit: cover;
38+
aspect-ratio: auto;
39+
}
40+
figure {
41+
margin: 0;
42+
max-width: 160px;
43+
}
44+
blockquote {
45+
margin: 0;
46+
max-width: 220px;
47+
}
48+
blockquote p {
49+
margin: 0 0 4px;
50+
}
51+
`;
52+
53+
/**
54+
* Renders a sample of an element using the site's own styles.
55+
*
56+
* The sample is real markup carrying the classes the element's selector
57+
* targets, rendered in the editor's iframe with the generated stylesheet, so it
58+
* picks up every panel, the theme's fonts and the cascade, rather than a list of
59+
* properties copied across by hand.
60+
*
61+
* @param props
62+
* @param props.element The element being previewed.
63+
*/
64+
export default function ElementPreview( { element }: ElementPreviewProps ) {
65+
const styles = useSelect(
66+
( select ) => select( blockEditorStore ).getSettings().styles,
67+
[]
68+
);
69+
70+
const editorStyles = useMemo(
71+
() => [ ...( styles ?? [] ), { css: PREVIEW_CSS } ],
72+
[ styles ]
73+
);
74+
75+
// Show a caption on the image the Image block uses for its own example,
76+
// rather than keeping a second copy of that URL here.
77+
const imageUrl = getBlockType( 'core/image' )?.example?.attributes?.url as
78+
string | undefined;
79+
80+
let sample;
81+
switch ( element ) {
82+
case 'button':
83+
// The class is half of the element's selector, so a button only
84+
// picks up the element's styles when it carries it.
85+
sample = (
86+
<button className="wp-element-button" type="button">
87+
{ __( 'Call to action' ) }
88+
</button>
89+
);
90+
break;
91+
case 'textInput':
92+
sample = (
93+
<input type="text" defaultValue={ __( 'Text' ) } readOnly />
94+
);
95+
break;
96+
case 'select':
97+
sample = (
98+
<select>
99+
<option>{ __( 'Select an option' ) }</option>
100+
</select>
101+
);
102+
break;
103+
case 'caption':
104+
sample = (
105+
<figure className="wp-block-image">
106+
{ imageUrl && (
107+
<img
108+
className="global-styles-ui-element-preview__media"
109+
src={ imageUrl }
110+
alt=""
111+
/>
112+
) }
113+
<figcaption className="wp-element-caption">
114+
{ __( 'A caption for the image above' ) }
115+
</figcaption>
116+
</figure>
117+
);
118+
break;
119+
case 'cite':
120+
sample = (
121+
<blockquote>
122+
<p>{ __( 'In quoting others, we cite ourselves.' ) }</p>
123+
<cite>{ __( 'Julio Cortázar' ) }</cite>
124+
</blockquote>
125+
);
126+
break;
127+
case 'link':
128+
sample = <a href="#anchor">{ __( 'A link' ) }</a>;
129+
break;
130+
case 'heading':
131+
// The shared heading entry has no tag of its own; any level shows
132+
// what it sets, and h2 is the most common in content.
133+
sample = <h2>{ __( 'Aa' ) }</h2>;
134+
break;
135+
default: {
136+
// The heading level elements are named after their tag.
137+
const Tag = ( /^h[1-6]$/.test( element ) ? element : 'p' ) as
138+
'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p';
139+
sample = <Tag>{ __( 'Aa' ) }</Tag>;
140+
}
141+
}
142+
143+
return (
144+
<div className="global-styles-ui-element-preview">
145+
<Iframe
146+
tabIndex={ -1 }
147+
readonly
148+
title={ __( 'Element preview' ) }
149+
aria-hidden="true"
150+
>
151+
<EditorStyles styles={ editorStyles } />
152+
{ sample }
153+
</Iframe>
154+
</div>
155+
);
156+
}

‎packages/global-styles-ui/src/global-styles-ui.tsx‎

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import ScreenRoot from './screen-root';
1515
import ScreenBlockList from './screen-block-list';
1616
import ScreenBlock from './screen-block';
1717
import ScreenTypography from './screen-typography';
18-
import ScreenTypographyElement from './screen-typography-element';
18+
import ScreenElement from './screen-element';
1919
import ScreenColors from './screen-colors';
2020
import ScreenColorPalette from './screen-color-palette';
2121
import ScreenBackground from './screen-background';
@@ -206,6 +206,9 @@ export function GlobalStylesUI( {
206206
<GlobalStylesNavigationScreen path="/typography">
207207
<ScreenTypography />
208208
</GlobalStylesNavigationScreen>
209+
<GlobalStylesNavigationScreen path="/typography/text">
210+
<ScreenElement element="text" />
211+
</GlobalStylesNavigationScreen>
209212
<GlobalStylesNavigationScreen path="/typography/font-sizes">
210213
<FontSizes />
211214
</GlobalStylesNavigationScreen>
@@ -242,33 +245,14 @@ export function GlobalStylesUI( {
242245
<GlobalStylesNavigationScreen path="/background">
243246
<ScreenBackground />
244247
</GlobalStylesNavigationScreen>
245-
<GlobalStylesNavigationScreen path="/typography/text">
246-
<ScreenTypographyElement element="text" />
247-
</GlobalStylesNavigationScreen>
248-
<GlobalStylesNavigationScreen path="/typography/link">
249-
<ScreenTypographyElement element="link" />
250-
</GlobalStylesNavigationScreen>
251-
<GlobalStylesNavigationScreen path="/typography/heading">
252-
<ScreenTypographyElement element="heading" />
253-
</GlobalStylesNavigationScreen>
254-
<GlobalStylesNavigationScreen path="/typography/caption">
255-
<ScreenTypographyElement element="caption" />
256-
</GlobalStylesNavigationScreen>
257-
<GlobalStylesNavigationScreen path="/typography/cite">
258-
<ScreenTypographyElement element="cite" />
259-
</GlobalStylesNavigationScreen>
260-
<GlobalStylesNavigationScreen path="/typography/button">
261-
<ScreenTypographyElement element="button" />
262-
</GlobalStylesNavigationScreen>
263-
<GlobalStylesNavigationScreen path="/typography/textInput">
264-
<ScreenTypographyElement element="textInput" />
265-
</GlobalStylesNavigationScreen>
266-
<GlobalStylesNavigationScreen path="/typography/select">
267-
<ScreenTypographyElement element="select" />
268-
</GlobalStylesNavigationScreen>
269248
<GlobalStylesNavigationScreen path="/blocks">
270249
<ScreenBlockList />
271250
</GlobalStylesNavigationScreen>
251+
<GlobalStylesNavigationScreen path="/blocks/elements/:element">
252+
<ScreenElement
253+
showStateControls={ showBlockStateControls }
254+
/>
255+
</GlobalStylesNavigationScreen>
272256
{ blocks.map( ( block ) => (
273257
<Fragment key={ block.name }>
274258
<GlobalStylesNavigationScreen

‎packages/global-styles-ui/src/hooks.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,25 @@ export function useStyleWithResolvedBackground(
269269
return { ...style, background };
270270
}, [ style, merged ] );
271271
}
272+
273+
/**
274+
* Whether the current user may edit custom CSS in Global Styles.
275+
*
276+
* The capability is reported as a link on the global styles record rather
277+
* than as a user capability.
278+
*/
279+
export function useCanEditCSS(): boolean {
280+
return useSelect( ( select ) => {
281+
const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } =
282+
select( coreStore );
283+
284+
const globalStylesId = __experimentalGetCurrentGlobalStylesId();
285+
const globalStyles = globalStylesId
286+
? getEntityRecord( 'root', 'globalStyles', globalStylesId )
287+
: undefined;
288+
289+
return !! ( globalStyles as GlobalStylesConfig )?._links?.[
290+
'wp:action-edit-css'
291+
];
292+
}, [] );
293+
}

0 commit comments

Comments
 (0)