Skip to content

Commit

Permalink
Block Support: Add text decoration block support using CSS variables (#…
Browse files Browse the repository at this point in the history
…26059)

* Add text decoration block support

* Add config for block preset classes

* Update text decoration controls

* Allow for text decoration controls alongside transform

The direction the design is headed it to display text decoration and text transform controls side-by-side. This commit provides a new component to handle grouping these together and arranging them within a flex container.

It also makes minor tweaks like labelling the controls "Decoration" instead of "Text Decoration".

* Add new underline icon

* Add back inline style generation for dynamic blocks

* This updates the typography block support to check for text decoration support and generate an appropriate inline-style adding it to the attributes.
* Removes opt-in for text decoration support from heading block
* Opts-in for text decoration for navigation block and updates its CSS to leverage it

* Tweak ordering of text decoration and transform
  • Loading branch information
aaronrobertshaw authored Nov 5, 2020
1 parent a2c8480 commit 24dc3a9
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 27 deletions.
22 changes: 18 additions & 4 deletions lib/block-supports/typography.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ function gutenberg_register_typography_support( $block_type ) {
$has_text_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), false );
}

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

if ( ! $block_type->attributes ) {
$block_type->attributes = array();
}

if ( ( $has_font_size_support || $has_line_height_support || $has_text_transform_support ) && ! array_key_exists( 'style', $block_type->attributes ) ) {
if ( ( $has_font_size_support || $has_line_height_support || $has_text_transform_support || $has_text_decoration_support ) && ! array_key_exists( 'style', $block_type->attributes ) ) {
$block_type->attributes['style'] = array(
'type' => 'object',
);
Expand Down Expand Up @@ -60,9 +65,10 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) {
$classes = array();
$styles = array();

$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_transform_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalTextTransform' ), 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_font_family_support = false;
if ( property_exists( $block_type, 'supports' ) ) {
Expand Down Expand Up @@ -108,6 +114,14 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) {
}
}

// Text Decoration.
if ( $has_text_decoration_support ) {
$text_decoration_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'textDecoration', 'text-decoration' );
if ( $text_decoration_style ) {
$styles[] = $text_decoration_style;
}
}

// Text Transform.
if ( $has_text_transform_support ) {
$text_transform_style = gutenberg_typography_get_css_variable_inline_style( $block_attributes, 'textTransform', 'text-transform' );
Expand Down
12 changes: 12 additions & 0 deletions lib/experimental-default-theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@
"name": "Ab",
"slug": "capitalize"
}
],
"textDecorations": [
{
"name": "Underline",
"slug": "underline",
"value": "underline"
},
{
"name": "Strikethrough",
"slug": "strikethrough",
"value": "line-through"
}
]
},
"spacing": {
Expand Down
34 changes: 24 additions & 10 deletions lib/global-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ function gutenberg_experimental_global_styles_get_css_property( $style_property
return 'line-height';
case 'fontFamily':
return 'font-family';
case 'textDecoration':
return 'text-decoration';
case 'textTransform':
return 'text-transform';
default:
Expand All @@ -418,6 +420,7 @@ function gutenberg_experimental_global_styles_get_style_property() {
'fontSize' => array( 'typography', 'fontSize' ),
'fontFamily' => array( 'typography', 'fontFamily' ),
'lineHeight' => array( 'typography', 'lineHeight' ),
'textDecoration' => array( 'typography', 'textDecoration' ),
'textTransform' => array( 'typography', 'textTransform' ),
);
}
Expand All @@ -436,6 +439,7 @@ function gutenberg_experimental_global_styles_get_support_keys() {
'fontSize' => array( 'fontSize' ),
'lineHeight' => array( 'lineHeight' ),
'fontFamily' => array( '__experimentalFontFamily' ),
'textDecoration' => array( '__experimentalTextDecoration' ),
'textTransform' => array( '__experimentalTextTransform' ),
);
}
Expand All @@ -447,23 +451,27 @@ 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',
),
'fontFamily' => array(
'fontFamily' => array(
'path' => array( 'typography', 'fontFamilies' ),
'key' => 'fontFamily',
),
'textTransform' => array(
'textDecoration' => array(
'path' => array( 'typography', 'textDecorations' ),
'key' => 'value',
),
'textTransform' => array(
'path' => array( 'typography', 'textTransforms' ),
'key' => 'slug',
),
Expand Down Expand Up @@ -505,11 +513,12 @@ function gutenberg_experimental_global_styles_get_block_data() {
'global',
array(
'supports' => array(
'__experimentalSelector' => ':root',
'__experimentalFontFamily' => true,
'fontSize' => true,
'__experimentalTextTransform' => true,
'color' => array(
'__experimentalSelector' => ':root',
'__experimentalFontFamily' => true,
'fontSize' => true,
'__experimentalTextDecoration' => true,
'__experimentalTextTransform' => true,
'color' => array(
'linkColor' => true,
'gradients' => true,
),
Expand Down Expand Up @@ -644,6 +653,11 @@ function gutenberg_experimental_global_styles_get_preset_classes( $selector, $se
'key' => 'size',
'property' => 'font-size',
),
'text-decoration' => array(
'path' => array( 'typography', 'textDecorations' ),
'key' => 'value',
'property' => 'text-decoration',
),
'text-transform' => array(
'path' => array( 'typography', 'textTransforms' ),
'key' => 'slug',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/**
* Internal dependencies
*/
import {
TextDecorationEdit,
useIsTextDecorationDisabled,
} from '../../hooks/text-decoration';
import {
TextTransformEdit,
useIsTextTransformDisabled,
Expand All @@ -15,21 +19,17 @@ import {
* @return {WPElement} Component containing text decoration or transform controls.
*/
export default function TextDecorationAndTransformEdit( props ) {
// Once text decorations block support is added additional checks will
// need to be added below and it's edit component included.
const decorationAvailable = ! useIsTextDecorationDisabled( props );
const transformAvailable = ! useIsTextTransformDisabled( props );

if ( ! transformAvailable ) {
if ( ! decorationAvailable && ! transformAvailable ) {
return null;
}

return (
<>
{ transformAvailable && (
<div className="block-editor-text-decoration-and-transform">
{ transformAvailable && <TextTransformEdit { ...props } /> }
</div>
) }
</>
<div className="block-editor-text-decoration-and-transform">
{ decorationAvailable && <TextDecorationEdit { ...props } /> }
{ transformAvailable && <TextTransformEdit { ...props } /> }
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* WordPress dependencies
*/
import { Button } from '@wordpress/components';
import { formatStrikethrough, formatUnderline } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';

/**
* Control to facilitate text decoration selections.
*
* @param {Object} props Component props.
* @param {string} props.value Currently selected text decoration.
* @param {Array} props.textDecorations Text decorations available for selection.
* @param {Function} props.onChange Handles change in text decoration selection.
* @return {WPElement} Text decoration control.
*/
export default function TextDecorationControl( {
value: textDecoration,
textDecorations,
onChange,
} ) {
/**
* Determines the what the new text decoration is as a result of a user
* interaction with the control. Then passes this on to the supplied
* onChange handler.
*
* @param {string} newDecoration Slug for selected decoration.
*/
const handleOnChange = ( newDecoration ) => {
// Check if we are toggling a decoration off.
const decoration =
textDecoration === newDecoration ? undefined : newDecoration;

// Ensure only defined text decorations are allowed.
const presetDecoration = textDecorations.find(
( { slug } ) => slug === decoration
);

// Create string that will be turned into CSS custom property
const newTextDecoration = presetDecoration
? `var:preset|text-decoration|${ presetDecoration.slug }`
: undefined;

onChange( newTextDecoration );
};

// Text Decoration icons to use.
const icons = {
strikethrough: formatStrikethrough,
underline: formatUnderline,
};

return (
<fieldset className="block-editor-text-decoration-control">
<legend>{ __( 'Decoration' ) }</legend>
<div className="block-editor-text-decoration-control__buttons">
{ textDecorations.map( ( presetDecoration ) => {
return (
<Button
key={ presetDecoration.slug }
icon={ icons[ presetDecoration.slug ] }
isSmall
isPressed={
textDecoration === presetDecoration.slug
}
onClick={ () =>
handleOnChange( presetDecoration.slug )
}
>
{ ! icons[ presetDecoration.slug ] &&
presetDecoration.name }
</Button>
);
} ) }
</div>
</fieldset>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.block-editor-text-decoration-control {
flex: 0 0 50%;

legend {
margin-bottom: 8px;
}

.block-editor-text-decoration-control__buttons {
display: inline-flex;
margin-bottom: 24px;

.components-button.has-icon {
min-width: 24px;
padding: 0;
margin-right: 4px;
}
}
}
98 changes: 98 additions & 0 deletions packages/block-editor/src/hooks/text-decoration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* WordPress dependencies
*/
import { hasBlockSupport } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import TextDecorationControl from '../components/text-decoration-control';
import useEditorFeature from '../components/use-editor-feature';
import { cleanEmptyObject } from './utils';

/**
* Key within block settings' supports array indicating support for text
* decorations e.g. settings found in `block.json`.
*/
export const TEXT_DECORATION_SUPPORT_KEY = '__experimentalTextDecoration';

/**
* Inspector control panel containing the text decoration options.
*
* @param {Object} props Block properties.
* @return {WPElement} Text decoration edit element.
*/
export function TextDecorationEdit( props ) {
const {
attributes: { style },
setAttributes,
} = props;
const textDecorations = useEditorFeature( 'typography.textDecorations' );
const isDisabled = useIsTextDecorationDisabled( props );

if ( isDisabled ) {
return null;
}

const textDecoration = getTextDecorationFromAttributeValue(
textDecorations,
style?.typography?.textDecoration
);

function onChange( newDecoration ) {
setAttributes( {
style: cleanEmptyObject( {
...style,
typography: {
...style?.typography,
textDecoration: newDecoration,
},
} ),
} );
}

return (
<TextDecorationControl
value={ textDecoration }
textDecorations={ textDecorations }
onChange={ onChange }
/>
);
}

/**
* Checks if text-decoration settings have been disabled.
*
* @param {string} name Name of the block.
* @return {boolean} Whether or not the setting is disabled.
*/
export function useIsTextDecorationDisabled( { name: blockName } = {} ) {
const notSupported = ! hasBlockSupport(
blockName,
TEXT_DECORATION_SUPPORT_KEY
);
const textDecorations = useEditorFeature( 'typography.textDecorations' );
const hasTextDecorations = !! textDecorations?.length;

return notSupported || ! hasTextDecorations;
}

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

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

return value;
};
Loading

0 comments on commit 24dc3a9

Please sign in to comment.