From 2c4ef3040c9d8beecc883375337667366e27ac08 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:25:39 +0900 Subject: [PATCH] Block Editor: Add new `TextAlignmentControl` component (#60841) * Block Editor: Add new `TextAlignControl` component * Rename `TextAlignControl` to `TextAlignmentControl` * Replate `align` with `alignment` * Use components to reduce CSS writing * Refactor using new SegmentedTextControl component * Make a component private * SegmentedTextControl: Update JSDoc parameters * TextAlignmentControl: Update JSDoc parameter * TextAlignmentControl: Cache valid controls filtering * WritingModeControl: fix control property * TextAlignmentControl: Allow `justify` as an allowed value * TextAlignmentControl: Update docs * SegmentedTextControl: Update description Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * TextAlignmentControl: Update readme Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * TextAlignmentControl: Fix JSDoc Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * TextAlignmentControl: Update readme Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * TextAlignmentControl: Update readme Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * SegmentedTextControl: Update JSDoc * TextAlignmentcontrol: remove unecessary `unlock()` * TextAlignmentControl: Remove unnecessary styles * Restor default component classname * Rename `control(s)` to `option(s)` * TextAlignmentControl: Remove README * TextAlignmentControl: Fix control name --------- Co-authored-by: t-hamano Co-authored-by: aaronrobertshaw Co-authored-by: noisysocks Co-authored-by: jasmussen --- .../segmented-text-control/index.js | 63 +++++++++++++ .../style.scss | 7 +- .../text-alignment-control/index.js | 91 +++++++++++++++++++ .../stories/index.story.js | 39 ++++++++ .../text-decoration-control/index.js | 53 ++++------- .../text-decoration-control/style.scss | 18 ---- .../text-transform-control/index.js | 47 ++++------ .../text-transform-control/style.scss | 18 ---- .../components/writing-mode-control/index.js | 43 +++------ packages/block-editor/src/private-apis.js | 2 + packages/block-editor/src/style.scss | 3 +- 11 files changed, 250 insertions(+), 134 deletions(-) create mode 100644 packages/block-editor/src/components/segmented-text-control/index.js rename packages/block-editor/src/components/{writing-mode-control => segmented-text-control}/style.scss (58%) create mode 100644 packages/block-editor/src/components/text-alignment-control/index.js create mode 100644 packages/block-editor/src/components/text-alignment-control/stories/index.story.js delete mode 100644 packages/block-editor/src/components/text-decoration-control/style.scss delete mode 100644 packages/block-editor/src/components/text-transform-control/style.scss diff --git a/packages/block-editor/src/components/segmented-text-control/index.js b/packages/block-editor/src/components/segmented-text-control/index.js new file mode 100644 index 0000000000000..a6117bcc22151 --- /dev/null +++ b/packages/block-editor/src/components/segmented-text-control/index.js @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { BaseControl, Button } from '@wordpress/components'; + +/** + * @typedef {Object} Option + * @property {string} label The label of the option. + * @property {string} value The value of the option. + * @property {string} icon The icon of the option. + */ + +/** + * Control to facilitate selecting a text style from a set of options. + * + * @param {Object} props Component props. + * @param {string} props.label A label for the option. + * @param {string} props.value Currently selected value. + * @param {Function} props.onChange Callback to handle onChange. + * @param {Option[]} props.options Array of options to display. + * @param {string} props.className Additional class name to apply. + * + * @return {Element} Element to render. + */ +export default function SegmentedTextControl( { + label, + value, + options, + onChange, + className, +} ) { + return ( +
+ + { label } + +
+ { options.map( ( option ) => { + return ( +
+
+ ); +} diff --git a/packages/block-editor/src/components/writing-mode-control/style.scss b/packages/block-editor/src/components/segmented-text-control/style.scss similarity index 58% rename from packages/block-editor/src/components/writing-mode-control/style.scss rename to packages/block-editor/src/components/segmented-text-control/style.scss index 4b865dc0282c0..7a4a3bbea7cb3 100644 --- a/packages/block-editor/src/components/writing-mode-control/style.scss +++ b/packages/block-editor/src/components/segmented-text-control/style.scss @@ -1,18 +1,15 @@ -.block-editor-writing-mode-control { +.block-editor-segmented-text-control { border: 0; margin: 0; padding: 0; - .block-editor-writing-mode-control__buttons { + .block-editor-segmented-text-control__buttons { // 4px of padding makes the row 40px high, same as an input. padding: $grid-unit-05 0; display: flex; } .components-button.has-icon { - height: $grid-unit-40; margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; } } diff --git a/packages/block-editor/src/components/text-alignment-control/index.js b/packages/block-editor/src/components/text-alignment-control/index.js new file mode 100644 index 0000000000000..a8e9ee22df7fd --- /dev/null +++ b/packages/block-editor/src/components/text-alignment-control/index.js @@ -0,0 +1,91 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + alignLeft, + alignCenter, + alignRight, + alignJustify, +} from '@wordpress/icons'; +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + +const TEXT_ALIGNMENT_OPTIONS = [ + { + label: __( 'Align text left' ), + value: 'left', + icon: alignLeft, + }, + { + label: __( 'Align text center' ), + value: 'center', + icon: alignCenter, + }, + { + label: __( 'Align text right' ), + value: 'right', + icon: alignRight, + }, + { + label: __( 'Justify text' ), + value: 'justify', + icon: alignJustify, + }, +]; + +const DEFAULT_OPTIONS = [ 'left', 'center', 'right' ]; + +/** + * Control to facilitate text alignment selections. + * + * @param {Object} props Component props. + * @param {string} props.className Class name to add to the control. + * @param {string} props.value Currently selected text alignment. + * @param {Function} props.onChange Handles change in text alignment selection. + * @param {string[]} props.options Array of text alignment options to display. + * + * @return {Element} Text alignment control. + */ +export default function TextAlignmentControl( { + className, + value, + onChange, + options = DEFAULT_OPTIONS, +} ) { + const validOptions = useMemo( + () => + TEXT_ALIGNMENT_OPTIONS.filter( ( option ) => + options.includes( option.value ) + ), + [ options ] + ); + + if ( ! validOptions.length ) { + return null; + } + + return ( + { + onChange( newValue === value ? undefined : newValue ); + } } + /> + ); +} diff --git a/packages/block-editor/src/components/text-alignment-control/stories/index.story.js b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js new file mode 100644 index 0000000000000..b2c171497acb0 --- /dev/null +++ b/packages/block-editor/src/components/text-alignment-control/stories/index.story.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import TextAlignmentControl from '../'; + +export default { + title: 'BlockEditor/TextAlignmentControl', + component: TextAlignmentControl, + argTypes: { + onChange: { action: 'onChange' }, + className: { control: 'text' }, + options: { + control: 'check', + options: [ 'left', 'center', 'right', 'justify' ], + }, + value: { control: { type: null } }, + }, +}; + +const Template = ( { onChange, ...args } ) => { + const [ value, setValue ] = useState(); + return ( + { + onChange( ...changeArgs ); + setValue( ...changeArgs ); + } } + value={ value } + /> + ); +}; + +export const Default = Template.bind( {} ); diff --git a/packages/block-editor/src/components/text-decoration-control/index.js b/packages/block-editor/src/components/text-decoration-control/index.js index 973fb71a7448f..6a2085e980bd4 100644 --- a/packages/block-editor/src/components/text-decoration-control/index.js +++ b/packages/block-editor/src/components/text-decoration-control/index.js @@ -6,23 +6,27 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { reset, formatStrikethrough, formatUnderline } from '@wordpress/icons'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const TEXT_DECORATIONS = [ { - name: __( 'None' ), + label: __( 'None' ), value: 'none', icon: reset, }, { - name: __( 'Underline' ), + label: __( 'Underline' ), value: 'underline', icon: formatUnderline, }, { - name: __( 'Strikethrough' ), + label: __( 'Strikethrough' ), value: 'line-through', icon: formatStrikethrough, }, @@ -31,10 +35,10 @@ const TEXT_DECORATIONS = [ /** * Control to facilitate text decoration selections. * - * @param {Object} props Component props. - * @param {string} props.value Currently selected text decoration. - * @param {Function} props.onChange Handles change in text decoration selection. - * @param {string} [props.className] Additional class name to apply. + * @param {Object} props Component props. + * @param {string} props.value Currently selected text decoration. + * @param {Function} props.onChange Handles change in text decoration selection. + * @param {string} props.className Additional class name to apply. * * @return {Element} Text decoration control. */ @@ -44,34 +48,17 @@ export default function TextDecorationControl( { className, } ) { return ( -
- - { __( 'Decoration' ) } - -
- { TEXT_DECORATIONS.map( ( textDecoration ) => { - return ( -
-
+ value={ value } + onChange={ ( newValue ) => { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/components/text-decoration-control/style.scss b/packages/block-editor/src/components/text-decoration-control/style.scss deleted file mode 100644 index 689707a66b7ca..0000000000000 --- a/packages/block-editor/src/components/text-decoration-control/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.block-editor-text-decoration-control { - border: 0; - margin: 0; - padding: 0; - - .block-editor-text-decoration-control__buttons { - // 4px of padding makes the row 40px high, same as an input. - padding: $grid-unit-05 0; - display: flex; - } - - .components-button.has-icon { - height: $grid-unit-40; - margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; - } -} diff --git a/packages/block-editor/src/components/text-transform-control/index.js b/packages/block-editor/src/components/text-transform-control/index.js index c5d0ce37e9acd..8fd1fb9cfdf51 100644 --- a/packages/block-editor/src/components/text-transform-control/index.js +++ b/packages/block-editor/src/components/text-transform-control/index.js @@ -6,7 +6,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { reset, @@ -15,24 +14,29 @@ import { formatUppercase, } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const TEXT_TRANSFORMS = [ { - name: __( 'None' ), + label: __( 'None' ), value: 'none', icon: reset, }, { - name: __( 'Uppercase' ), + label: __( 'Uppercase' ), value: 'uppercase', icon: formatUppercase, }, { - name: __( 'Lowercase' ), + label: __( 'Lowercase' ), value: 'lowercase', icon: formatLowercase, }, { - name: __( 'Capitalize' ), + label: __( 'Capitalize' ), value: 'capitalize', icon: formatCapitalize, }, @@ -50,34 +54,17 @@ const TEXT_TRANSFORMS = [ */ export default function TextTransformControl( { className, value, onChange } ) { return ( -
- - { __( 'Letter case' ) } - -
- { TEXT_TRANSFORMS.map( ( textTransform ) => { - return ( -
-
+ value={ value } + onChange={ ( newValue ) => { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/components/text-transform-control/style.scss b/packages/block-editor/src/components/text-transform-control/style.scss deleted file mode 100644 index 0e097405e332b..0000000000000 --- a/packages/block-editor/src/components/text-transform-control/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.block-editor-text-transform-control { - border: 0; - margin: 0; - padding: 0; - - .block-editor-text-transform-control__buttons { - // 4px of padding makes the row 40px high, same as an input. - padding: $grid-unit-05 0; - display: flex; - } - - .components-button.has-icon { - height: $grid-unit-40; - margin-right: $grid-unit-05; - min-width: $grid-unit-40; - padding: 0; - } -} diff --git a/packages/block-editor/src/components/writing-mode-control/index.js b/packages/block-editor/src/components/writing-mode-control/index.js index 2adf8be14ad39..420c3d75ba31a 100644 --- a/packages/block-editor/src/components/writing-mode-control/index.js +++ b/packages/block-editor/src/components/writing-mode-control/index.js @@ -6,18 +6,22 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { BaseControl, Button } from '@wordpress/components'; import { __, isRTL } from '@wordpress/i18n'; import { textHorizontal, textVertical } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import SegmentedTextControl from '../segmented-text-control'; + const WRITING_MODES = [ { - name: __( 'Horizontal' ), + label: __( 'Horizontal' ), value: 'horizontal-tb', icon: textHorizontal, }, { - name: __( 'Vertical' ), + label: __( 'Vertical' ), value: isRTL() ? 'vertical-lr' : 'vertical-rl', icon: textVertical, }, @@ -35,34 +39,17 @@ const WRITING_MODES = [ */ export default function WritingModeControl( { className, value, onChange } ) { return ( -
- - { __( 'Orientation' ) } - -
- { WRITING_MODES.map( ( writingMode ) => { - return ( -
-
+ value={ value } + onChange={ ( newValue ) => { + onChange( newValue === value ? undefined : newValue ); + } } + /> ); } diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index e81a5eed39d5b..f10fcc4df2c72 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -23,6 +23,7 @@ import { BlockRemovalWarningModal } from './components/block-removal-warning-mod import { useLayoutClasses, useLayoutStyles } from './hooks'; import DimensionsTool from './components/dimensions-tool'; import ResolutionTool from './components/resolution-tool'; +import TextAlignmentControl from './components/text-alignment-control'; import { default as ReusableBlocksRenameHint, useReusableBlocksRenameHint, @@ -66,6 +67,7 @@ lock( privateApis, { useLayoutStyles, DimensionsTool, ResolutionTool, + TextAlignmentControl, ReusableBlocksRenameHint, useReusableBlocksRenameHint, usesContextKey, diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 535d1005d274d..5080aa05718bb 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -41,9 +41,8 @@ @import "./components/multi-selection-inspector/style.scss"; @import "./components/responsive-block-control/style.scss"; @import "./components/rich-text/style.scss"; +@import "./components/segmented-text-control/style.scss"; @import "./components/skip-to-selected-block/style.scss"; -@import "./components/text-decoration-control/style.scss"; -@import "./components/text-transform-control/style.scss"; @import "./components/tool-selector/style.scss"; @import "./components/url-input/style.scss"; @import "./components/url-popover/style.scss";