diff --git a/core-blocks/paragraph/index.js b/core-blocks/paragraph/index.js index 03647f4e55c6ef..f1ebe001709305 100644 --- a/core-blocks/paragraph/index.js +++ b/core-blocks/paragraph/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { isFinite, find, omit } from 'lodash'; +import { isFinite, omit } from 'lodash'; /** * WordPress dependencies @@ -14,20 +14,22 @@ import { RawHTML, } from '@wordpress/element'; import { - FontSizePicker, PanelBody, ToggleControl, withFallbackStyles, } from '@wordpress/components'; import { getColorClass, + getFontSizeClass, withColors, AlignmentToolbar, BlockControls, + ContrastChecker, + FontSizePicker, InspectorControls, PanelColorSettings, RichText, - ContrastChecker, + withFontSizes, } from '@wordpress/editor'; import { createBlock, @@ -55,37 +57,12 @@ const FallbackStyles = withFallbackStyles( ( node, ownProps ) => { }; } ); -const FONT_SIZES = [ - { - name: 'small', - shortName: 'S', - size: 14, - }, - { - name: 'regular', - shortName: 'M', - size: 16, - }, - { - name: 'large', - shortName: 'L', - size: 36, - }, - { - name: 'larger', - shortName: 'XL', - size: 48, - }, -]; - class ParagraphBlock extends Component { constructor() { super( ...arguments ); this.onReplace = this.onReplace.bind( this ); this.toggleDropCap = this.toggleDropCap.bind( this ); - this.getFontSize = this.getFontSize.bind( this ); - this.setFontSize = this.setFontSize.bind( this ); this.splitBlock = this.splitBlock.bind( this ); } @@ -112,36 +89,6 @@ class ParagraphBlock extends Component { return checked ? __( 'Showing large initial letter.' ) : __( 'Toggle to show a large initial letter.' ); } - getFontSize() { - const { customFontSize, fontSize } = this.props.attributes; - if ( fontSize ) { - const fontSizeObj = find( FONT_SIZES, { name: fontSize } ); - if ( fontSizeObj ) { - return fontSizeObj.size; - } - } - - if ( customFontSize ) { - return customFontSize; - } - } - - setFontSize( fontSizeValue ) { - const { setAttributes } = this.props; - const thresholdFontSize = find( FONT_SIZES, { size: fontSizeValue } ); - if ( thresholdFontSize ) { - setAttributes( { - fontSize: thresholdFontSize.name, - customFontSize: undefined, - } ); - return; - } - setAttributes( { - fontSize: undefined, - customFontSize: fontSizeValue, - } ); - } - /** * Split handler for RichText value, namely when content is pasted or the * user presses the Enter key. @@ -199,6 +146,8 @@ class ParagraphBlock extends Component { fallbackBackgroundColor, fallbackTextColor, fallbackFontSize, + fontSize, + setFontSize, } = this.props; const { @@ -208,8 +157,6 @@ class ParagraphBlock extends Component { placeholder, } = attributes; - const fontSize = this.getFontSize(); - return ( @@ -223,10 +170,9 @@ class ParagraphBlock extends Component { @@ -269,11 +215,12 @@ class ParagraphBlock extends Component { 'has-drop-cap': dropCap, [ backgroundColor.class ]: backgroundColor.class, [ textColor.class ]: textColor.class, + [ fontSize.class ]: fontSize.class, } ) } style={ { backgroundColor: backgroundColor.value, color: textColor.value, - fontSize: fontSize ? fontSize + 'px' : undefined, + fontSize: fontSize.size ? fontSize.size + 'px' : undefined, textAlign: align, } } value={ content } @@ -494,6 +441,7 @@ export const settings = { edit: compose( [ withColors( 'backgroundColor', { textColor: 'color' } ), + withFontSizes( 'fontSize' ), FallbackStyles, ] )( ParagraphBlock ), @@ -512,7 +460,7 @@ export const settings = { const textClass = getColorClass( 'color', textColor ); const backgroundClass = getColorClass( 'background-color', backgroundColor ); - const fontSizeClass = fontSize && `is-${ fontSize }-text`; + const fontSizeClass = getFontSizeClass( fontSize ); const className = classnames( { 'has-background': backgroundColor || customBackgroundColor, diff --git a/core-blocks/style.scss b/core-blocks/style.scss index fa6cef63fb6cb5..a13e6364e038a4 100644 --- a/core-blocks/style.scss +++ b/core-blocks/style.scss @@ -85,3 +85,19 @@ .has-very-dark-gray-color { color: #313131; } + +.has-small-font-size { + font-size: 14px; +} + +.has-regular-font-size { + font-size: 16px; +} + +.has-large-font-size { + font-size: 36px; +} + +.has-larger-font-size { + font-size: 48px; +} diff --git a/docs/extensibility/theme-support.md b/docs/extensibility/theme-support.md index eec4829b3a99b0..46f5d8f1d3dc38 100644 --- a/docs/extensibility/theme-support.md +++ b/docs/extensibility/theme-support.md @@ -90,6 +90,53 @@ Themes are responsible for creating the classes that apply the colors in differe The class name is built appending 'has-', followed by the class name *using* kebab case and ending with the context name. +### Block Font Sizes: + +Blocks may allow the user to configure the font sizes they use, e.g., the paragraph block. Gutenberg provides a default set of font sizes, but a theme can overwrite it and provide its own: + + +```php +add_theme_support( 'editor-font-sizes', array( + array( + 'name' => __( 'small', 'themeLangDomain' ), + 'shortName' => __( 'S', 'themeLangDomain' ), + 'size' => 12, + 'slug' => 'small' + ), + array( + 'name' => __( 'regular', 'themeLangDomain' ), + 'shortName' => __( 'M', 'themeLangDomain' ), + 'size' => 16, + 'slug' => 'regular' + ), + array( + 'name' => __( 'large', 'themeLangDomain' ), + 'shortName' => __( 'L', 'themeLangDomain' ), + 'size' => 36, + 'slug' => 'large' + ), + array( + 'name' => __( 'larger', 'themeLangDomain' ), + 'shortName' => __( 'XL', 'themeLangDomain' ), + 'size' => 50, + 'slug' => 'larger' + ) +) ); +``` + +The font sizes are rendered on the font size picker in the order themes provide them. + +Themes are responsible for creating the classes that apply the correct font size styles. +The class name is built appending 'has-', followed by the font size name *using* kebab case and ending with `-font-size`. + +As an example for the regular font size, a theme may provide the following class. + +```css +.has-regular-font-size { + font-size: 16px; +} +``` + ### Disabling custom colors in block Color Palettes By default, the color palette offered to blocks, allows the user to select a custom color different from the editor or theme default colors. diff --git a/edit-post/index.js b/edit-post/index.js index 0dcf10823ba137..f9f62beaf28024 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -60,6 +60,13 @@ export function initializeEditor( id, postType, postId, settings, overridePost ) const reboot = reinitializeEditor.bind( null, postType, postId, target, settings, overridePost ); // Global deprecations which cannot otherwise be injected into known usage. + deprecated( 'paragraphs block class set is-small-text, ..., is-large-text', { + version: '3.6', + alternative: 'has-small-font-size, ..., has-large-font-size class set', + plugin: 'Gutenberg', + hint: 'If paragraphs using this classes are opened in the editor new classes are automatically applied the post just needs to be saved. This is a global warning, shown regardless of whether the classes are used in the current post.', + } ); + deprecated( 'block `id` prop in `edit` function', { version: '3.4', alternative: 'block `clientId` prop', diff --git a/lib/client-assets.php b/lib/client-assets.php index 2e36553bd3a245..34437b7a79b5fe 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1207,6 +1207,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $gutenberg_theme_support = get_theme_support( 'gutenberg' ); $align_wide = get_theme_support( 'align-wide' ); $color_palette = (array) get_theme_support( 'editor-color-palette' ); + $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); // Backcompat for Color Palette set as multiple parameters. if ( isset( $color_palette[0] ) && ( is_string( $color_palette[0] ) || isset( $color_palette[0]['color'] ) ) ) { @@ -1282,6 +1283,10 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $editor_settings['colors'] = editor_color_palette_slugs( $color_palette ); } + if ( ! empty( $font_sizes ) ) { + $editor_settings['fontSizes'] = $font_sizes; + } + if ( ! empty( $post_type_object->template ) ) { $editor_settings['template'] = $post_type_object->template; $editor_settings['templateLock'] = ! empty( $post_type_object->template_lock ) ? $post_type_object->template_lock : false; diff --git a/packages/editor/src/components/font-sizes/font-size-picker.js b/packages/editor/src/components/font-sizes/font-size-picker.js new file mode 100644 index 00000000000000..a106b1ca786884 --- /dev/null +++ b/packages/editor/src/components/font-sizes/font-size-picker.js @@ -0,0 +1,14 @@ +/** + * WordPress dependencies + */ +import { FontSizePicker } from '@wordpress/components'; +import { withSelect } from '@wordpress/data'; + +export default withSelect( + ( select ) => { + const { fontSizes } = select( 'core/editor' ).getEditorSettings(); + return { + fontSizes, + }; + } +)( FontSizePicker ); diff --git a/packages/editor/src/components/font-sizes/index.js b/packages/editor/src/components/font-sizes/index.js new file mode 100644 index 00000000000000..931f9c2a89474d --- /dev/null +++ b/packages/editor/src/components/font-sizes/index.js @@ -0,0 +1,3 @@ +export { getFontSize, getFontSizeClass } from './utils'; +export { default as FontSizePicker } from './font-size-picker'; +export { default as withFontSizes } from './with-font-sizes'; diff --git a/packages/editor/src/components/font-sizes/utils.js b/packages/editor/src/components/font-sizes/utils.js new file mode 100644 index 00000000000000..09de76dcf7f6aa --- /dev/null +++ b/packages/editor/src/components/font-sizes/utils.js @@ -0,0 +1,43 @@ +/** + * External dependencies + */ +import { find, kebabCase } from 'lodash'; + +/** + * Returns the font size object based on an array of named font sizes and the namedFontSize and customFontSize values. + * If namedFontSize is undefined or not found in fontSizes an object with just the size value based on customFontSize is returned. + * + * @param {Array} fontSizes Array of font size objects containing at least the "name" and "size" values as properties. + * @param {?string} fontSizeAttribute Content of the font size attribute (slug). + * @param {?number} customFontSizeAttribute Contents of the custom font size attribute (value). + * + * @return {?string} If fontSizeAttribute is set and an equal slug is found in fontSizes it returns the font size object for that slug. + * Otherwise, an object with just the size value based on customFontSize is returned. + */ +export const getFontSize = ( fontSizes, fontSizeAttribute, customFontSizeAttribute ) => { + if ( fontSizeAttribute ) { + const fontSizeObject = find( fontSizes, { slug: fontSizeAttribute } ); + if ( fontSizeObject ) { + return fontSizeObject; + } + } + return { + size: customFontSizeAttribute, + }; +}; + +/** + * Returns a class based on fontSizeName. + * + * @param {string} fontSizeSlug Slug of the fontSize. + * + * @return {string} String with the class corresponding to the fontSize passed. + * The class is generated by appending 'has-' followed by fontSizeSlug in kebabCase and ending with '-font-size'. + */ +export function getFontSizeClass( fontSizeSlug ) { + if ( ! fontSizeSlug ) { + return; + } + + return `has-${ kebabCase( fontSizeSlug ) }-font-size`; +} diff --git a/packages/editor/src/components/font-sizes/with-font-sizes.js b/packages/editor/src/components/font-sizes/with-font-sizes.js new file mode 100644 index 00000000000000..3c9be5c1a4440f --- /dev/null +++ b/packages/editor/src/components/font-sizes/with-font-sizes.js @@ -0,0 +1,134 @@ +/** + * External dependencies + */ +import { find, pickBy, reduce, some, upperFirst } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createHigherOrderComponent, compose } from '@wordpress/compose'; +import { Component } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { getFontSize, getFontSizeClass } from './utils'; + +/** + * Higher-order component, which handles font size logic for class generation, + * font size value retrieval, and font size change handling. + * + * @param {...(object|string)} args The arguments should all be strings + * Each string contains the font size attribute name e.g: 'fontSize'. + * + * @return {Function} Higher-order component. + */ +export default ( ...fontSizeNames ) => { + /* + * Computes an object whose key is the font size attribute name as passed in the array, + * and the value is the custom font size attribute name. + * Custom font size is automatically compted by appending custom followed by the font size attribute name in with the first letter capitalized. + */ + const fontSizeAttributeNames = reduce( fontSizeNames, ( fontSizeAttributeNamesAccumulator, fontSizeAttributeName ) => { + fontSizeAttributeNamesAccumulator[ fontSizeAttributeName ] = `custom${ upperFirst( fontSizeAttributeName ) }`; + return fontSizeAttributeNamesAccumulator; + }, {} ); + + return createHigherOrderComponent( + compose( [ + withSelect( ( select ) => { + const { fontSizes } = select( 'core/editor' ).getEditorSettings(); + return { + fontSizes, + }; + } ), + ( WrappedComponent ) => { + return class extends Component { + constructor( props ) { + super( props ); + + this.setters = this.createSetters(); + + this.state = {}; + } + + createSetters() { + return reduce( fontSizeAttributeNames, ( settersAccumulator, customFontSizeAttributeName, fontSizeAttributeName ) => { + const upperFirstFontSizeAttributeName = upperFirst( fontSizeAttributeName ); + settersAccumulator[ `set${ upperFirstFontSizeAttributeName }` ] = + this.createSetFontSize( fontSizeAttributeName, customFontSizeAttributeName ); + return settersAccumulator; + }, {} ); + } + + createSetFontSize( fontSizeAttributeName, customFontSizeAttributeName ) { + return ( fontSizeValue ) => { + const fontSizeObject = find( this.props.fontSizes, { size: fontSizeValue } ); + this.props.setAttributes( { + [ fontSizeAttributeName ]: fontSizeObject && fontSizeObject.slug ? fontSizeObject.slug : undefined, + [ customFontSizeAttributeName ]: fontSizeObject && fontSizeObject.slug ? undefined : fontSizeValue, + } ); + }; + } + + static getDerivedStateFromProps( { attributes, fontSizes }, previousState ) { + const didAttributesChange = ( customFontSizeAttributeName, fontSizeAttributeName ) => { + if ( previousState[ fontSizeAttributeName ] ) { + // if new font size is name compare with the previous slug + if ( attributes[ fontSizeAttributeName ] ) { + return attributes[ fontSizeAttributeName ] !== previousState[ fontSizeAttributeName ].slug; + } + // if font size is not named, update when the font size value changes. + return previousState[ fontSizeAttributeName ].size !== attributes[ customFontSizeAttributeName ]; + } + // in this case we need to build the font size object + return true; + }; + + if ( ! some( fontSizeAttributeNames, didAttributesChange ) ) { + return null; + } + + const newState = reduce( + pickBy( fontSizeAttributeNames, didAttributesChange ), + ( newStateAccumulator, customFontSizeAttributeName, fontSizeAttributeName ) => { + const fontSizeAttributeValue = attributes[ fontSizeAttributeName ]; + const fontSizeObject = getFontSize( + fontSizes, + fontSizeAttributeValue, + attributes[ customFontSizeAttributeName ] + ); + newStateAccumulator[ fontSizeAttributeName ] = { + ...fontSizeObject, + class: getFontSizeClass( fontSizeAttributeValue ), + }; + return newStateAccumulator; + }, + {} + ); + + return { + ...previousState, + ...newState, + }; + } + + render() { + return ( + + ); + } + }; + }, + ] ), + 'withFontSizes' + ); +}; diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index d7c7eeb5c35495..c7571f52a584ad 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -11,6 +11,7 @@ export { default as ColorPalette } from './color-palette'; export { default as withColorContext } from './color-palette/with-color-context'; export * from './colors'; export { default as ContrastChecker } from './contrast-checker'; +export * from './font-sizes'; export { default as InnerBlocks } from './inner-blocks'; export { default as InspectorAdvancedControls } from './inspector-advanced-controls'; export { default as InspectorControls } from './inspector-controls'; diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index b0b597a9d96d67..6c0a64377a7ea1 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -12,6 +12,7 @@ export const PREFERENCES_DEFAULTS = { * * alignWide boolean Enable/Disable Wide/Full Alignments * colors Array Palette colors + * fontSizes Array Available font sizes * maxWidth number Max width to constraint resizing * blockTypes boolean|Array Allowed block types * hasFixedToolbar boolean Whether or not the editor toolbar is fixed @@ -75,6 +76,33 @@ export const EDITOR_SETTINGS_DEFAULTS = { }, ], + fontSizes: [ + { + name: __( 'small' ), + shortName: __( 'S' ), + size: 14, + slug: 'small', + }, + { + name: __( 'regular' ), + shortName: __( 'M' ), + size: 16, + slug: 'regular', + }, + { + name: __( 'large' ), + shortName: __( 'L' ), + size: 36, + slug: 'large', + }, + { + name: __( 'larger' ), + shortName: __( 'XL' ), + size: 48, + slug: 'larger', + }, + ], + // This is current max width of the block inner area // It's used to constraint image resizing and this value could be overridden later by themes maxWidth: 580,