Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Support: Add font styles support using CSS variables #26050

58 changes: 50 additions & 8 deletions lib/block-supports/typography.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ function gutenberg_register_typography_support( $block_type ) {
$has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false );
}

$has_font_style_support = false;
if ( property_exists( $block_type, 'supports' ) ) {
$has_font_style_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontStyle' ), false );
}

$has_line_height_support = false;
if ( property_exists( $block_type, 'supports' ) ) {
$has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false );
Expand All @@ -25,7 +30,7 @@ function gutenberg_register_typography_support( $block_type ) {
$block_type->attributes = array();
}

if ( ( $has_font_size_support || $has_line_height_support ) && ! array_key_exists( 'style', $block_type->attributes ) ) {
if ( ( $has_font_size_support || $has_line_height_support || $has_font_style_support ) && ! array_key_exists( 'style', $block_type->attributes ) ) {
$block_type->attributes['style'] = array(
'type' => 'object',
);
Expand All @@ -49,15 +54,13 @@ function gutenberg_register_typography_support( $block_type ) {
* @return array Font size CSS classes and inline styles.
*/
function gutenberg_apply_typography_support( $attributes, $block_attributes, $block_type ) {
$has_font_size_support = false;
if ( property_exists( $block_type, 'supports' ) ) {
$has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false );
if ( ! property_exists( $block_type, 'supports' ) ) {
return $attributes;
}

$has_line_height_support = false;
if ( property_exists( $block_type, 'supports' ) ) {
$has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false );
}
$has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false );
$has_font_style_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontStyle' ), false );
$has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false );

// Font Size.
if ( $has_font_size_support ) {
Expand All @@ -72,6 +75,14 @@ function gutenberg_apply_typography_support( $attributes, $block_attributes, $bl
}
}

// Font Style.
if ( $has_font_style_support ) {
$font_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontStyle', 'font-style' );
if ( $font_style ) {
$attributes['inline_styles'][] = $font_style;
}
}

// Line Height.
if ( $has_line_height_support ) {
$has_line_height = isset( $block_attributes['style']['typography']['lineHeight'] );
Expand All @@ -83,3 +94,34 @@ function gutenberg_apply_typography_support( $attributes, $block_attributes, $bl

return $attributes;
}

/**
* Generates an inline style for a typography feature e.g. text decoration,
* text transform, and font style.
*
* @param array $attributes Block's attributes.
* @param string $feature Key for the feature within the typography styles.
* @param string $css_property Slug for the CSS property the inline style sets.
*
* @return string CSS inline style.
*/
function gutenberg_typography_get_css_variable_inline_style( $attributes, $feature, $css_property ) {
// Retrieve current attribute value or skip if not found.
$style_value = gutenberg_experimental_get( $attributes, array( 'style', 'typography', $feature ), false );
if ( ! $style_value ) {
return;
}

// If we don't have a preset CSS variable, we'll assume it's a regular CSS value.
if ( strpos( $style_value, "var:preset|{$css_property}|" ) === false ) {
return sprintf( '%s:%s;', $css_property, $style_value );
}

// We have a preset CSS variable as the style.
// Get the style value from the string and return CSS style.
$index_to_splice = strrpos( $style_value, '|' ) + 1;
$slug = substr( $style_value, $index_to_splice );

// Return the actual CSS inline style e.g. `text-decoration:var(--wp--preset--text-decoration--underline);`.
return sprintf( '%s:var(--wp--preset--%s--%s);', $css_property, $css_property, $slug );
}
10 changes: 10 additions & 0 deletions lib/experimental-default-theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@
"slug": "huge",
"size": 42
}
],
"fontStyles": [
{
"name": "Normal",
"slug": "normal"
},
{
"name": "Italic",
"slug": "italic"
}
]
},
"spacing": {
Expand Down
26 changes: 20 additions & 6 deletions lib/global-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,8 @@ function gutenberg_experimental_global_styles_get_css_property( $style_property
return 'background-color';
case 'fontSize':
return 'font-size';
case 'fontStyle':
return 'font-style';
case 'lineHeight':
return 'line-height';
default:
Expand All @@ -406,6 +408,7 @@ function gutenberg_experimental_global_styles_get_style_property() {
'backgroundColor' => array( 'color', 'background' ),
'color' => array( 'color', 'text' ),
'fontSize' => array( 'typography', 'fontSize' ),
'fontStyle' => array( 'typography', 'fontStyle' ),
'lineHeight' => array( 'typography', 'lineHeight' ),
);
}
Expand All @@ -422,6 +425,7 @@ function gutenberg_experimental_global_styles_get_support_keys() {
'backgroundColor' => array( 'color' ),
'color' => array( 'color' ),
'fontSize' => array( 'fontSize' ),
'fontStyle' => array( '__experimentalFontStyle' ),
'lineHeight' => array( 'lineHeight' ),
);
}
Expand All @@ -433,18 +437,22 @@ function gutenberg_experimental_global_styles_get_support_keys() {
*/
function gutenberg_experimental_global_styles_get_presets_structure() {
return array(
'color' => array(
'color' => array(
'path' => array( 'color', 'palette' ),
'key' => 'color',
),
'gradient' => array(
'gradient' => array(
'path' => array( 'color', 'gradients' ),
'key' => 'gradient',
),
'fontSize' => array(
'fontSize' => array(
'path' => array( 'typography', 'fontSizes' ),
'key' => 'size',
),
'fontStyle' => array(
'path' => array( 'typography', 'fontStyles' ),
'key' => 'slug',
),
);
}

Expand Down Expand Up @@ -483,9 +491,10 @@ function gutenberg_experimental_global_styles_get_block_data() {
'global',
array(
'supports' => array(
'__experimentalSelector' => ':root',
'fontSize' => true,
'color' => array(
'__experimentalSelector' => ':root',
'fontSize' => true,
'__experimentalFontStyle' => true,
'color' => array(
'linkColor' => true,
'gradients' => true,
),
Expand Down Expand Up @@ -611,6 +620,11 @@ function gutenberg_experimental_global_styles_get_preset_classes( $selector, $se
'key' => 'size',
'property' => 'font-size',
),
'font-style' => array(
'path' => array( 'typography', 'fontStyles' ),
'key' => 'slug',
'property' => 'font-style',
),
);

foreach ( $classes_structure as $class_suffix => $preset_structure ) {
Expand Down
67 changes: 67 additions & 0 deletions packages/block-editor/src/components/font-style-control/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* WordPress dependencies
*/
import { SelectControl } from '@wordpress/components';
import { useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Control to facilitate font style selections.
*
* @param {Object} props Component props.
* @param {string} props.value Currently selected font style.
* @param {Array} props.fontStyles Font styles available for selection.
* @param {Function} props.onChange Handles change in font style selection.
* @return {WPElement} Font style control.
*/
export default function FontStyleControl( {
value: fontStyle,
fontStyles,
onChange,
} ) {
/**
* Determines the what the new font style is as a result of a user
* interaction with the control then passes this on to the supplied onChange
* handler.
*
* @param {string} style Slug for selected style.
*/
const handleOnChange = ( style ) => {
// Ensure only defined font styles are allowed.
const presetStyle = fontStyles.find( ( { slug } ) => slug === style );

// Create string that will be turned into CSS custom property
const newFontStyle = presetStyle
? `var:preset|font-style|${ presetStyle.slug }`
: undefined;

onChange( newFontStyle );
};

// Map styles to select options and inject a default for inheriting font style.
const options = useMemo(
() => [
{ label: __( 'Default' ), value: '' },
...fontStyles.map( ( { name, slug } ) => ( {
label: name,
value: slug,
} ) ),
],
[ fontStyles ]
);

return (
<fieldset className="components-font-style-control">
<div className="components-font-style-control__select">
{ fontStyles.length > 0 && (
<SelectControl
options={ options }
value={ fontStyle }
label={ __( 'Font style' ) }
onChange={ handleOnChange }
/>
) }
</div>
</fieldset>
);
}
12 changes: 12 additions & 0 deletions packages/block-editor/src/components/font-style-control/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.components-font-style-control {
.components-font-style-control__select {
label {
padding-bottom: 0;
margin-bottom: 8px;
}

.components-base-control {
margin-bottom: 24px;
}
}
}
94 changes: 94 additions & 0 deletions packages/block-editor/src/hooks/font-style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* WordPress dependencies
*/
import { hasBlockSupport } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import FontStyleControl from '../components/font-style-control';
import useEditorFeature from '../components/use-editor-feature';
import { cleanEmptyObject } from './utils';

/**
* Key within block settings' supports array indicating support for font styles.
* e.g. settings found in `block.json`.
*/
export const FONT_STYLE_SUPPORT_KEY = '__experimentalFontStyle';

/**
* Inspector control panel containing the font style options.
*
* @param {Object} props Block properties.
* @return {WPElement} Font style edit element.
*/
export function FontStyleEdit( props ) {
const {
attributes: { style },
setAttributes,
} = props;
const fontStyles = useEditorFeature( 'typography.fontStyles' );
const isDisabled = useIsFontStyleDisabled( props );

if ( isDisabled ) {
return null;
}

const fontStyle = getFontStyleFromAttributeValue(
fontStyles,
style?.typography?.fontStyle
);

function onChange( newStyle ) {
setAttributes( {
style: cleanEmptyObject( {
...style,
typography: {
...style?.typography,
fontStyle: newStyle,
},
} ),
} );
}

return (
<FontStyleControl
value={ fontStyle }
fontStyles={ fontStyles }
onChange={ onChange }
/>
);
}

/**
* Checks if font-style settings have been disabled.
*
* @param {string} name Name of the block.
* @return {boolean} Whether or not the setting is disabled.
*/
export function useIsFontStyleDisabled( { name: blockName } = {} ) {
const notSupported = ! hasBlockSupport( blockName, FONT_STYLE_SUPPORT_KEY );
const fontStyles = useEditorFeature( 'typography.fontStyles' );
const hasFontStyles = !! fontStyles?.length;

return notSupported || ! hasFontStyles;
}

/**
* Extracts the current font style selection, if available, from the CSS
* variable set as the `styles.typography.fontStyle` attribute.
*
* @param {Array} fontStyles Available font styles as defined in theme.json.
* @param {string} value Attribute value in `styles.typography.fontStyle`
* @return {string} Actual font style value
*/
const getFontStyleFromAttributeValue = ( fontStyles, value ) => {
const attributeParsed = /var:preset\|font-style\|(.+)/.exec( value );

if ( attributeParsed && attributeParsed[ 1 ] ) {
return fontStyles.find( ( { slug } ) => slug === attributeParsed[ 1 ] )
?.slug;
}

return value;
};
8 changes: 8 additions & 0 deletions packages/block-editor/src/hooks/typography.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ import {
FontSizeEdit,
useIsFontSizeDisabled,
} from './font-size';
import {
FONT_STYLE_SUPPORT_KEY,
FontStyleEdit,
useIsFontStyleDisabled,
} from './font-style';

export const TYPOGRAPHY_SUPPORT_KEYS = [
LINE_HEIGHT_SUPPORT_KEY,
FONT_SIZE_SUPPORT_KEY,
FONT_STYLE_SUPPORT_KEY,
];

export function TypographyPanel( props ) {
Expand All @@ -37,6 +43,7 @@ export function TypographyPanel( props ) {
<InspectorControls>
<PanelBody title={ __( 'Typography' ) }>
<FontSizeEdit { ...props } />
<FontStyleEdit { ...props } />
<LineHeightEdit { ...props } />
</PanelBody>
</InspectorControls>
Expand All @@ -55,6 +62,7 @@ const hasTypographySupport = ( blockName ) => {
function useIsTypographyDisabled( props = {} ) {
const configs = [
useIsFontSizeDisabled( props ),
useIsFontStyleDisabled( props ),
useIsLineHeightDisabled( props ),
];

Expand Down
Loading