Skip to content

Commit

Permalink
Allow adding custom CSS variables (#25446)
Browse files Browse the repository at this point in the history
  • Loading branch information
nosolosw authored Sep 24, 2020
1 parent d349f0b commit d4d0830
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 29 deletions.
62 changes: 49 additions & 13 deletions docs/designers-developers/developers/themes/theme-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
This is documentation for the current direction and work in progress about how themes can hook into the various sub-systems that the Block Editor provides.

- Rationale
- Presets become CSS Custom Properties
- Some block styles are managed
- Settings can be controlled per block
- CSS Custom Properties: presets & custom
- Some block styles are managed
- Specification
- Settings
- Styles
Expand All @@ -20,20 +20,18 @@ The Block Editor surface API has evolved at different velocities, and it's now a

This describes the current efforts to consolidate the various APIs into a single point – a `experimental-theme.json` file that should be located inside the root of the theme directory.

### Presets become CSS Custom Properties
### Settings can be controlled per block

The Block Editor already allows the control of specific settings such as alignment, drop cap, whether it's present in the inserter, etc at the block level. The goal is to surface these for themes to control at a block level.

Presets such as [color palettes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes), [font sizes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-font-sizes), and [gradients](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets) will be enqueued as CSS Custom Properties for themes to use.
### CSS Custom Properties

These will be enqueued to the front-end and editor.
Presets such as [color palettes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-color-palettes), [font sizes](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-font-sizes), and [gradients](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#block-gradient-presets) become CSS Custom Properties, and will be enqueued by the system for themes to use in both the front-end and the editors. There's also a mechanism to create your own CSS Custom Properties.

### Some block styles are managed

By providing the block style properties in a structured way, the Block Editor can "manage" the CSS that comes from different origins (user, theme, and core CSS), reducing the amount of CSS loaded in the page and preventing specificity wars due to the competing needs of the components involved (themes, blocks, plugins).

### Settings can be controlled per block

The Block Editor already allows the control of specific settings such as alignment, drop cap, whether it's present in the inserter, etc at the block level. The goal is to surface these for themes to control.

## Specification

The `experimental-theme.json` file is divided into sections known as "contexts", that represent a different CSS selector. For example, the `core/paragraph` context maps to `p` while `core/group` maps to `.wp-block-group`. In general, one block will map to a single context as in the cases mentioned. There are cases where one block can generate multiple contexts (different CSS selectors). For example, the heading block generates six different contexts (`core/heading/h1`, `core/heading/h2`, etc), one for each different selector (h1, h2, etc).
Expand All @@ -60,7 +58,8 @@ Every context has the same structure, divided in two sections: `settings` and `s
"settings": {
"color": [ ... ],
"typography": [ ... ],
"spacing": [ ... ]
"spacing": [ ... ],
"custom": [ ... ]
},
"styles": {
"color": { ... },
Expand Down Expand Up @@ -96,13 +95,14 @@ The settings section has the following structure and default values:
"customLineHeight": false, /* true to opt-in, as in add_theme_support( 'custom-line-height' ) */
"dropCap": true, /* false to opt-out */
"fontSizes": [ ... ], /* font size presets, as in add_theme_support('editor-font-sizes', ... ) */
}
},
"custom": { ... }
}
}
}
```

To retain backward compatibility, `add_theme_support` declarations are considered as well. If a theme uses `add_theme_support('disable-custom-colors')`, it'll be the same as set `global.settings.color.custom` to `false`. If the `experimental-theme.json` contains any settings, these will take precedence over the values declared via `add_theme_support`.
To retain backward compatibility, `add_theme_support` declarations are retrofit in the proper categories. If a theme uses `add_theme_support('disable-custom-colors')`, it'll be the same as set `global.settings.color.custom` to `false`. If the `experimental-theme.json` contains any settings, these will take precedence over the values declared via `add_theme_support`.

Settings can also be controlled by context, providing a more fine-grained control over what exists via `add_theme_support`. As an example, let's say that a theme author wants to enable custom colors for the paragraph block exclusively. This is how it'd be done:

Expand All @@ -126,7 +126,7 @@ Settings can also be controlled by context, providing a more fine-grained contro

Note, however, that not all settings are relevant for all contexts and the blocks they represent. The settings section provides an opt-in/opt-out mechanism for themes, but it's the block's responsibility to add support for the features that are relevant to it. For example, if a block doesn't implement the `dropCap` feature, a theme can't enable it for such a block through `experimental-theme.json`.

### Presets
#### Presets

Presets are part of the settings section. At the moment, they only work within the `global` context. Each preset value will generate a CSS Custom Property that will be added to the new stylesheet, which follow this naming schema: `--wp--preset--{preset-category}--{preset-slug}`.

Expand Down Expand Up @@ -190,6 +190,42 @@ The output to be enqueued will be:

The goal is that presets can be defined using this format, although, right now, the name property (used in the editor) can't be translated from this file. For that reason, and to maintain backward compatibility, the presets declared via `add_theme_support` will also generate the CSS Custom Properties. If the `experimental-theme.json` contains any presets, these will take precedence over the ones declared via `add_theme_support`.

#### Free-form CSS Custom Properties

In addition to create CSS Custom Properties for the presets, the theme.json also allows for themes to create their own, so they don't have to be enqueued separately. Any values declared within the `settings.custom` section will be transformed to CSS Custom Properties following this naming schema: `--wp--custom--<variable-name>`.

For example, for this input:

```json
{
"global": {
"settings": {
"custom": {
"base-font": 16,
"line-height": {
"small": 1.2,
"medium": 1.4,
"large": 1.8
}
}
}
}
}
```

The output will be:

```css
:root {
--wp--custom--base-font: 16;
--wp--custom--line-height--small: 1.2;
--wp--custom--line-height--medium: 1.4;
--wp--custom--line-height--large: 1.8;
}
```

Note that, the name of the variable is created by adding `--` in between each nesting level.

### Styles

Each block declares which style properties it exposes. This has been coined as "implicit style attributes" of the block. These properties are then used to automatically generate the UI controls for the block in the editor, as well as being available through the `experimental-theme.json` file for themes to target.
Expand Down
46 changes: 30 additions & 16 deletions lib/global-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ function gutenberg_experimental_global_styles_has_theme_json_support() {
* by merging the keys and binding the leaf values
* to the new keys.
*
* It also transforms camelCase names into kebab-case
* and substitutes '/' by '-'.
*
* This is thought to be useful to generate
* CSS Custom Properties from a tree,
* although there's nothing in the implementation
Expand All @@ -28,16 +31,16 @@ function gutenberg_experimental_global_styles_has_theme_json_support() {
* and the token is '--', for this input tree:
*
* {
* 'property': 'value',
* 'nested-property': {
* 'some/property': 'value',
* 'nestedProperty': {
* 'sub-property': 'value'
* }
* }
*
* it'll return this output:
*
* {
* '--wp--property': 'value',
* '--wp--some-property': 'value',
* '--wp--nested-property--sub-property': 'value'
* }
*
Expand All @@ -50,7 +53,11 @@ function gutenberg_experimental_global_styles_has_theme_json_support() {
function gutenberg_experimental_global_styles_get_css_vars( $tree, $prefix = '', $token = '--' ) {
$result = array();
foreach ( $tree as $property => $value ) {
$new_key = $prefix . str_replace( '/', '-', $property );
$new_key = $prefix . str_replace(
'/',
'-',
strtolower( preg_replace( '/(?<!^)[A-Z]/', '-$0', $property ) ) // CamelCase to kebab-case.
);

if ( is_array( $value ) ) {
$new_prefix = $new_key . $token;
Expand Down Expand Up @@ -592,32 +599,38 @@ function gutenberg_experimental_global_styles_get_stylesheet( $tree ) {
continue;
}

// Create the CSS Custom Properties for the presets.
$computed_presets = array();
$presets_structure = gutenberg_experimental_global_styles_get_presets_structure();

$computed_presets = array();

// Extract the relevant preset info before converting them to CSS Custom Properties.
foreach ( $presets_structure as $token => $preset_meta ) {
$block_preset = gutenberg_experimental_get( $tree[ $block_name ]['settings'], $preset_meta['path'] );
if ( ! empty( $block_preset ) ) {
$css_var_token = gutenberg_experimental_global_styles_get_css_property( $token );
$computed_presets[ $css_var_token ] = array();
$computed_presets[ $token ] = array();
foreach ( $block_preset as $preset_value ) {
$computed_presets[ $css_var_token ][ $preset_value['slug'] ] = $preset_value[ $preset_meta['key'] ];
$computed_presets[ $token ][ $preset_value['slug'] ] = $preset_value[ $preset_meta['key'] ];
}
}
}

$token = '--';
$prefix = '--wp--preset' . $token;
$css_variables = gutenberg_experimental_global_styles_get_css_vars( $computed_presets, $prefix, $token );
$token = '--';
$preset_prefix = '--wp--preset' . $token;
$preset_variables = gutenberg_experimental_global_styles_get_css_vars( $computed_presets, $preset_prefix, $token );

// Create the CSS Custom Properties that are specific to the theme.
$computed_theme_props = gutenberg_experimental_get( $tree[ $block_name ]['settings'], array( 'custom' ) );
$theme_props_prefix = '--wp--custom' . $token;
$theme_variables = gutenberg_experimental_global_styles_get_css_vars(
$computed_theme_props,
$theme_props_prefix,
$token
);

$stylesheet .= gutenberg_experimental_global_styles_resolver_styles(
$block_data[ $block_name ]['selector'],
$block_data[ $block_name ]['supports'],
array_merge(
gutenberg_experimental_global_styles_flatten_styles_tree( $tree[ $block_name ]['styles'] ),
$css_variables
$preset_variables,
$theme_variables
)
);
}
Expand Down Expand Up @@ -733,6 +746,7 @@ function gutenberg_experimental_global_styles_normalize_schema( $tree ) {
),
'settings' => array(
'color' => array(),
'custom' => array(),
'typography' => array(),
'spacing' => array(),
),
Expand Down
28 changes: 28 additions & 0 deletions packages/edit-site/src/components/editor/global-styles-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,33 @@ export default ( blockData, baseTree, userTree ) => {
);
};

const flattenTree = ( input, prefix, token ) => {
let result = [];
Object.keys( input ).forEach( ( key ) => {
const newKey = prefix + key.replace( '/', '-' );
const newLeaf = input[ key ];

if ( newLeaf instanceof Object ) {
const newPrefix = newKey + token;
result = [
...result,
...flattenTree( newLeaf, newPrefix, token ),
];
} else {
result.push( `${ newKey }: ${ newLeaf }` );
}
} );
return result;
};

const getCustomDeclarations = ( blockCustom ) => {
if ( Object.keys( blockCustom ).length === 0 ) {
return [];
}

return flattenTree( blockCustom, '--wp--custom--', '--' );
};

const getBlockSelector = ( selector ) => {
// Can we hook into the styles generation mechanism
// so we can avoid having to increase the class specificity here
Expand All @@ -118,6 +145,7 @@ export default ( blockData, baseTree, userTree ) => {
tree[ context ].styles
),
...getBlockPresetsDeclarations( tree[ context ].settings ),
...getCustomDeclarations( tree[ context ].settings.custom ),
];
if ( blockDeclarations.length > 0 ) {
styles.push(
Expand Down

0 comments on commit d4d0830

Please sign in to comment.