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 text decoration block support using CSS variables #26059

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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