From 1d9c07a5b9a81dde5ef2a2f988600e5a3af115d6 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 6 Nov 2020 10:22:26 +1000 Subject: [PATCH] Block Support: Add font style and weight options with combined UI (#26444) * Add combined font style and weight block support Adds both font style and font weight block support options. The UI for both are combined into a single dropdown. The inline styles generated via this feature leverage CSS variables. * Update styling of font appearance list items --- lib/block-supports/typography.php | 61 ++++++++------ lib/experimental-default-theme.json | 48 +++++++++++ lib/global-styles.php | 61 ++++++++++++++ .../font-appearance-control/index.js | 77 +++++++++++++++++ .../font-appearance-control/style.scss | 10 +++ .../block-editor/src/hooks/font-appearance.js | 82 +++++++++++++++++++ packages/block-editor/src/hooks/typography.js | 8 ++ packages/block-editor/src/style.scss | 1 + .../block-library/src/navigation/block.json | 1 + .../block-library/src/navigation/index.php | 4 +- packages/blocks/src/api/constants.js | 2 + .../edit-site/src/components/editor/utils.js | 2 + 12 files changed, 329 insertions(+), 28 deletions(-) create mode 100644 packages/block-editor/src/components/font-appearance-control/index.js create mode 100644 packages/block-editor/src/components/font-appearance-control/style.scss create mode 100644 packages/block-editor/src/hooks/font-appearance.js diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 925071767182dc..63f7f292a88bc9 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -11,31 +11,22 @@ * @param WP_Block_Type $block_type Block Type. */ function gutenberg_register_typography_support( $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 ); - } - - $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_text_transform_support = false; - if ( property_exists( $block_type, 'supports' ) ) { - $has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); + if ( ! property_exists( $block_type, 'supports' ) ) { + return; } - $has_text_decoration_support = false; - if ( property_exists( $block_type, 'supports' ) ) { - $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); - } + $has_font_appearance_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontAppearance' ), false ); + $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); + $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); + $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); + $has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false ); + $has_typography_support = $has_font_appearance_support || $has_font_size_support || $has_line_height_support || $has_text_transform_support || $has_text_decoration_support; if ( ! $block_type->attributes ) { $block_type->attributes = array(); } - if ( ( $has_font_size_support || $has_line_height_support || $has_text_transform_support || $has_text_decoration_support ) && ! array_key_exists( 'style', $block_type->attributes ) ) { + if ( $has_typography_support && ! array_key_exists( 'style', $block_type->attributes ) ) { $block_type->attributes['style'] = array( 'type' => 'object', ); @@ -65,6 +56,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { $classes = array(); $styles = array(); + $has_font_appearance_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalFontAppearance' ), false ); $has_font_size_support = gutenberg_experimental_get( $block_type->supports, array( 'fontSize' ), false ); $has_line_height_support = gutenberg_experimental_get( $block_type->supports, array( 'lineHeight' ), false ); $has_text_decoration_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextDecoration' ), false ); @@ -105,6 +97,21 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { } } + // Font appearance - style and weight. + if ( $has_font_appearance_support ) { + // Apply font style. + $font_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontStyle', 'font-style' ); + if ( $font_style ) { + $styles[] = $font_style; + } + + // Apply font weight. + $font_weight = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'fontWeight', 'font-weight' ); + if ( $font_weight ) { + $styles[] = $font_weight; + } + } + // Line Height. if ( $has_line_height_support ) { $has_line_height = isset( $block_attributes['style']['typography']['lineHeight'] ); @@ -141,15 +148,6 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { return $attributes; } -// Register the block support. -WP_Block_Supports::get_instance()->register( - 'typography', - array( - 'register_attribute' => 'gutenberg_register_typography_support', - 'apply' => 'gutenberg_apply_typography_support', - ) -); - /** * Generates an inline style for a typography feature e.g. text decoration, * text transform, and font style. @@ -180,3 +178,12 @@ function gutenberg_typography_get_css_variable_inline_style( $attributes, $featu // 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 ); } + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'typography', + array( + 'register_attribute' => 'gutenberg_register_typography_support', + 'apply' => 'gutenberg_apply_typography_support', + ) +); diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json index 8448b0c05f24b4..d2cb060b67d9c4 100644 --- a/lib/experimental-default-theme.json +++ b/lib/experimental-default-theme.json @@ -161,6 +161,54 @@ "size": 42 } ], + "fontStyles": [ + { + "name": "Regular", + "slug": "normal" + }, + { + "name": "Italic", + "slug": "italic" + } + ], + "fontWeights": [ + { + "name": "Ultralight", + "slug": "100" + }, + { + "name": "Thin", + "slug": "200" + }, + { + "name": "Light", + "slug": "300" + }, + { + "name": "Regular", + "slug": "400" + }, + { + "name": "Medium", + "slug": "500" + }, + { + "name": "Semibold", + "slug": "600" + }, + { + "name": "Bold", + "slug": "700" + }, + { + "name": "Heavy", + "slug": "800" + }, + { + "name": "Black", + "slug": "900" + } + ], "textTransforms": [ { "name": "AB", diff --git a/lib/global-styles.php b/lib/global-styles.php index d8c3c3e72cc4ea..04babf8b3c73af 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -250,6 +250,40 @@ function gutenberg_experimental_global_styles_get_core() { $font_size['name'] = $default_font_sizes_i18n[ $font_size['slug'] ]; } } + + $default_font_styles_i18n = array( + 'normal' => __( 'Regular', 'gutenberg' ), + 'italic' => __( 'Italic', 'gutenberg' ), + 'initial' => __( 'Initial', 'gutenberg' ), + 'inherit' => __( 'Inherit', 'gutenberg' ), + ); + + if ( ! empty( $config['global']['settings']['typography']['fontStyles'] ) ) { + foreach ( $config['global']['settings']['typography']['fontStyles'] as &$font_style ) { + $font_style['name'] = $default_font_styles_i18n[ $font_style['slug'] ]; + } + } + + $default_font_weights_i18n = array( + '100' => __( 'Ultralight', 'gutenberg' ), + '200' => __( 'Thin', 'gutenberg' ), + '300' => __( 'Light', 'gutenberg' ), + '400' => __( 'Regular', 'gutenberg' ), + '500' => __( 'Medium', 'gutenberg' ), + '600' => __( 'Semibold', 'gutenberg' ), + '700' => __( 'Bold', 'gutenberg' ), + '800' => __( 'Heavy', 'gutenberg' ), + '900' => __( 'Black', 'gutenberg' ), + 'initial' => __( 'Initial', 'gutenberg' ), + 'inherit' => __( 'Inherit', 'gutenberg' ), + ); + + if ( ! empty( $config['global']['settings']['typography']['fontWeights'] ) ) { + foreach ( $config['global']['settings']['typography']['fontWeights'] as &$font_weight ) { + $font_weight['name'] = $default_font_weights_i18n[ $font_weight['slug'] ]; + } + } + // End i18n logic to remove when JSON i18 strings are extracted. return $config; } @@ -393,6 +427,10 @@ function gutenberg_experimental_global_styles_get_css_property( $style_property return 'background-color'; case 'fontSize': return 'font-size'; + case 'fontStyle': + return 'font-style'; + case 'fontWeight': + return 'font-weight'; case 'lineHeight': return 'line-height'; case 'fontFamily': @@ -419,6 +457,8 @@ function gutenberg_experimental_global_styles_get_style_property() { 'color' => array( 'color', 'text' ), 'fontSize' => array( 'typography', 'fontSize' ), 'fontFamily' => array( 'typography', 'fontFamily' ), + 'fontStyle' => array( 'typography', 'fontStyle' ), + 'fontWeight' => array( 'typography', 'fontWeight' ), 'lineHeight' => array( 'typography', 'lineHeight' ), 'textDecoration' => array( 'typography', 'textDecoration' ), 'textTransform' => array( 'typography', 'textTransform' ), @@ -437,6 +477,8 @@ function gutenberg_experimental_global_styles_get_support_keys() { 'backgroundColor' => array( 'color' ), 'color' => array( 'color' ), 'fontSize' => array( 'fontSize' ), + 'fontStyle' => array( '__experimentalFontAppearance' ), + 'fontWeight' => array( '__experimentalFontAppearance' ), 'lineHeight' => array( 'lineHeight' ), 'fontFamily' => array( '__experimentalFontFamily' ), 'textDecoration' => array( '__experimentalTextDecoration' ), @@ -467,6 +509,14 @@ function gutenberg_experimental_global_styles_get_presets_structure() { 'path' => array( 'typography', 'fontFamilies' ), 'key' => 'fontFamily', ), + 'fontStyle' => array( + 'path' => array( 'typography', 'fontStyles' ), + 'key' => 'slug', + ), + 'fontWeight' => array( + 'path' => array( 'typography', 'fontWeights' ), + 'key' => 'slug', + ), 'textDecoration' => array( 'path' => array( 'typography', 'textDecorations' ), 'key' => 'value', @@ -514,6 +564,7 @@ function gutenberg_experimental_global_styles_get_block_data() { array( 'supports' => array( '__experimentalSelector' => ':root', + '__experimentalFontAppearance' => false, '__experimentalFontFamily' => true, 'fontSize' => true, '__experimentalTextDecoration' => true, @@ -653,6 +704,16 @@ 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', + ), + 'font-weight' => array( + 'path' => array( 'typography', 'fontWeights' ), + 'key' => 'slug', + 'property' => 'font-weight', + ), 'text-decoration' => array( 'path' => array( 'typography', 'textDecorations' ), 'key' => 'value', diff --git a/packages/block-editor/src/components/font-appearance-control/index.js b/packages/block-editor/src/components/font-appearance-control/index.js new file mode 100644 index 00000000000000..29a6036d4a385a --- /dev/null +++ b/packages/block-editor/src/components/font-appearance-control/index.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { CustomSelectControl } from '@wordpress/components'; +import { useMemo } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Control to display unified font style and weight options. + * + * @param {Object} props Component props. + * @param {Object} props.value Currently selected combination of font style and weight. + * @param {Object} props.options Object containing weight and style options. + * @param {Function} props.onChange Handles selection change. + * @return {WPElement} Font appearance control. + */ +export default function FontAppearanceControl( { value, options, onChange } ) { + const { fontStyle, fontWeight } = value; + const { fontStyles = [], fontWeights = [] } = options; + const hasStylesOrWeights = fontStyles.length > 0 || fontWeights.length > 0; + + // Map font styles and weights to select options. + const selectOptions = useMemo( () => { + const defaultCombo = { fontStyle: undefined, fontWeight: undefined }; + const combinedOptions = [ + { + key: 'default', + name: __( 'Default' ), + style: defaultCombo, + presetStyle: defaultCombo, + }, + ]; + + fontStyles.forEach( ( { name: styleName, slug: styleSlug } ) => { + fontWeights.forEach( ( { name: weightName, slug: weightSlug } ) => { + combinedOptions.push( { + key: `${ weightSlug }-${ styleSlug }`, + name: + styleSlug === 'normal' + ? weightName + : `${ weightName } ${ styleName }`, + // style applies font appearance to the individual select option. + style: { fontStyle: styleSlug, fontWeight: weightSlug }, + // presetStyle are the actual typography styles that should be given to onChange. + presetStyle: { + fontStyle: `var:preset|font-style|${ styleSlug }`, + fontWeight: `var:preset|font-weight|${ weightSlug }`, + }, + } ); + } ); + } ); + + return combinedOptions; + }, [ options ] ); + + const currentSelection = selectOptions.find( + ( option ) => + option.presetStyle.fontStyle === fontStyle && + option.presetStyle.fontWeight === fontWeight + ); + + return ( +
+ ); +} diff --git a/packages/block-editor/src/components/font-appearance-control/style.scss b/packages/block-editor/src/components/font-appearance-control/style.scss new file mode 100644 index 00000000000000..6669d2c452d1a4 --- /dev/null +++ b/packages/block-editor/src/components/font-appearance-control/style.scss @@ -0,0 +1,10 @@ +.components-font-appearance-control__select { + margin-bottom: 24px; + + ul { + li { + color: $gray-900; + text-transform: capitalize; + } + } +} diff --git a/packages/block-editor/src/hooks/font-appearance.js b/packages/block-editor/src/hooks/font-appearance.js new file mode 100644 index 00000000000000..4e51e86edfef8d --- /dev/null +++ b/packages/block-editor/src/hooks/font-appearance.js @@ -0,0 +1,82 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import FontAppearanceControl from '../components/font-appearance-control'; +import useEditorFeature from '../components/use-editor-feature'; +import { cleanEmptyObject } from './utils'; + +/** + * Key within block settings' support array indicating support for font + * appearance options e.g. font weight and style. + */ +export const FONT_APPEARANCE_SUPPORT_KEY = '__experimentalFontAppearance'; + +/** + * Inspector control panel containing the font appearance options. + * + * @param {Object} props Block properties. + * @return {WPElement} Font appearance edit element. + */ +export function FontAppearanceEdit( props ) { + const { + attributes: { style }, + setAttributes, + } = props; + + const fontStyles = useEditorFeature( 'typography.fontStyles' ); + const fontWeights = useEditorFeature( 'typography.fontWeights' ); + const isDisabled = useIsFontAppearanceDisabled( props ); + + if ( isDisabled ) { + return null; + } + + const onChange = ( newStyles ) => { + setAttributes( { + style: cleanEmptyObject( { + ...style, + typography: { + ...style?.typography, + ...newStyles, + }, + } ), + } ); + }; + + const currentSelection = { + fontStyle: style?.typography?.fontStyle, + fontWeight: style?.typography?.fontWeight, + }; + + return ( +