From 0a3d9c64c870e33670a1a87d39ef1bd1362a4214 Mon Sep 17 00:00:00 2001 From: tfrommen Date: Mon, 14 Nov 2022 10:31:13 +0100 Subject: [PATCH 1/2] Add HeadingSelectControl --- README.md | 1 + .../images/heading-select-control--custom.png | Bin 0 -> 2905 bytes .../heading-select-control--default.png | Bin 0 -> 3417 bytes src/components/HeadingSelectControl/README.md | 112 ++++++++++++++++++ src/components/HeadingSelectControl/index.js | 53 +++++++++ 5 files changed, 166 insertions(+) create mode 100644 assets/images/heading-select-control--custom.png create mode 100644 assets/images/heading-select-control--default.png create mode 100644 src/components/HeadingSelectControl/README.md create mode 100644 src/components/HeadingSelectControl/index.js diff --git a/README.md b/README.md index 71781a9..35d6153 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ One way to ensure all dependencies are loaded is to use the [`@wordpress/depende - [`ConditionalComponent`](src/components/ConditionalComponent) - [`FetchAllTermSelectControl`](src/components/FetchAllTermSelectControl) - [`FileControls`](src/components/FileControls) +- [`HeadingSelectControl`](src/components/HeadingSelectControl) - [`ImageControl`](src/components/ImageControl) - [`LinkToolbar`](src/components/LinkToolbar) - [`PlainTextWithLimit`](src/components/PlainTextWithLimit) diff --git a/assets/images/heading-select-control--custom.png b/assets/images/heading-select-control--custom.png new file mode 100644 index 0000000000000000000000000000000000000000..53395aa4433b780f22a58340b65d49a053381360 GIT binary patch literal 2905 zcmaJ@cU)7+8ohvG2oXbs7@D*wf&u{qq=XVe0;n_*hzb~`2nZ2FK#IajLXpsAMG#Ox zEHsfKQrDnFo)o1;S&AB7B3+E3i!ZXfZ{OSfz4ynRd*_>P=FaasXU>@g9KB+uWhlGE|)xK^Udi} zECuGI$7_o-PRiC!&hDa}8AVyiiGha*$RfMsGR-s4lkzZ^&<0E6YgUb!qJ~^ww7F6#ae2^Tx|*R|+F| z@ClK4C`LFA1yX)yiJ+#xSAt+{EjE?VGe^P4mj={=%1QZ5rf5pwY9W5{HW>1SaijhR;Cj1C61cDdiNYj6@tP2|1~#% zjl&{pN<&U}9YG&X-I3FA{-6a&JzZI>J@~W

QeBa**#`Pn4*oO6eH*j4?IEBD^dS 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 literal 0 HcmV?d00001 diff --git a/src/components/HeadingSelectControl/README.md b/src/components/HeadingSelectControl/README.md new file mode 100644 index 0000000..a050396 --- /dev/null +++ b/src/components/HeadingSelectControl/README.md @@ -0,0 +1,112 @@ +# HeadingSelectControl + +The `HeadingSelectControl` component allows for choosing one of a pre-defined list of heading levels. +It is intended to be used for blocks or plugin sidebars where some text element needs to be rendered as a user-defined heading. +The component wraps a regular [`SelectControl`](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/select-control/index.tsx) component. + +| ![heading-select-control--default.png](../../../assets/images/heading-select-control--default.png) | +|----------------------------------------------------------------------------------------------------| +| _`HeadingSelectControl` component._ | + +| ![heading-select-control--custom.png](../../../assets/images/heading-select-control--custom.png) | +|--------------------------------------------------------------------------------------------------| +| _`HeadingSelectControl` component with custom `min` and `max` value specified._ | + +## Usage + +For a minimum working setup, all you need to do is pass a heading level as `value` to `HeadingSelectControl`, as well as an `onChange` callback that accepts a heading level. + +```js +import { HeadingSelectControl } from '@humanmade/block-editor-components'; +import { InspectorControls } from '@wordpress/block-editor'; + +function BlockEdit( props ) { + const { attributes, setAttributes } = props; + const { deckLevel } = attributes; + + return ( + + setAttributes( { deckLevel } ) } + /> + + ); +} +``` + +Additionally, you can also specify a minimum and/or maximum heading level by passing `min` and `max`, respectively. +For accessibility reasons, the default minimum heading level is set to `2`, so if you want to allow for selecting a Heading 1, you have to explicitly pass `min={ 1 }`. + +```js +import { HeadingSelectControl } from '@humanmade/block-editor-components'; +import { InspectorControls } from '@wordpress/block-editor'; + +function BlockEdit( props ) { + const { attributes, setAttributes } = props; + const { deckLevel } = attributes; + + return ( + + setAttributes( { deckLevel } ) } + /> + + ); +} +``` + +Since the component only allows for selecting a heading level, but does not actually render any heading element, you can also allow for something like a Heading 7 or Heading 8, if you really need to. +The HTML specification includes dedicated tags for 6 headings only, but sometimes editorial teams use special fake or pseudo headings, which will then end up in them having more than just 6 heading levels. +By passing a number greater than 6 to `max`, you can allow for that, and then handle rendering a Heading 7 or so, for example, as a paragraph with a custom class. + +```js +import { HeadingSelectControl } from '@humanmade/block-editor-components'; +import { InspectorControls } from '@wordpress/block-editor'; + +function BlockEdit( props ) { + const { attributes, setAttributes } = props; + const { level } = attributes; + + return ( + + setAttributes( { level } ) } + /> + + ); +} +``` + +## Props + +The `HeadingSelectControl` component does not have any custom props other than the optional `max` and `min`, but you can pass anything that is supported by the nested [`SelectControl`](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/select-control/index.tsx) component. + +### `max` + +An optional maximum heading level. + +| Type | Required | Default | +|--------------------------------------|--------------------------------------|--------------------------------------| +| `number` | no | `6` | + +### `min` + +An optional minimum heading level. +This value is also being used as default value. + +| Type | Required | Default | +|--------------------------------------|--------------------------------------|--------------------------------------| +| `number` | no | `2` | + +## Dependencies + +The `HeadingSelectControl` component requires the following dependencies, which are expected to be available: + +- `@wordpress/components` +- `@wordpress/i18n` diff --git a/src/components/HeadingSelectControl/index.js b/src/components/HeadingSelectControl/index.js new file mode 100644 index 0000000..2152270 --- /dev/null +++ b/src/components/HeadingSelectControl/index.js @@ -0,0 +1,53 @@ +import React, { ReactNode, useMemo } from 'react'; + +import { SelectControl } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * A dropdown control that allows for selecting a heading level. + * + * @param {object} props - Component props. + * @returns {ReactNode} Component. + */ +function HeadingSelectControl( props ) { + const { + max = 6, + min = 2, + onChange, + value = min, + ...selectProps + } = props; + + const options = useMemo( + () => { + if ( min > max ) { + return undefined; + } + + return Array( max - min + 1 ).fill().map( ( _, index ) => { + const level = min + index; + + return { + label: sprintf( + // translators: %s: heading level (e.g.: 1, 2, 3). + __( 'Heading %d', 'block-editor-components' ), + level + ), + value: level, + }; + } ); + }, + [ max, min ] + ); + + return ( + onChange( Number( value ) ) } + /> + ); +} + +export default HeadingSelectControl; From 8937021aa283e2698ad55cf34bda63601570e3c1 Mon Sep 17 00:00:00 2001 From: tfrommen Date: Mon, 19 Dec 2022 18:17:47 +0100 Subject: [PATCH 2/2] Allow for passing custom createLabel function --- src/components/HeadingSelectControl/README.md | 41 +++++++++++++++++++ src/components/HeadingSelectControl/index.js | 23 ++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/components/HeadingSelectControl/README.md b/src/components/HeadingSelectControl/README.md index a050396..19eae9f 100644 --- a/src/components/HeadingSelectControl/README.md +++ b/src/components/HeadingSelectControl/README.md @@ -83,10 +83,51 @@ function BlockEdit( props ) { } ``` +Also, you can pass a custom `createLabel` function that takes a numeric heading level and returns the label to use for the heading. + +```js +import { HeadingSelectControl } from '@humanmade/block-editor-components'; +import { InspectorControls } from '@wordpress/block-editor'; + +function createLabel( level ) { + if ( level === 8 ) { + return 'Subheading'; + } + + return `Heading ${ level }`; +} + +function BlockEdit( props ) { + const { attributes, setAttributes } = props; + const { level } = attributes; + + return ( + + setAttributes( { level } ) } + /> + + ); +} +``` + ## Props The `HeadingSelectControl` component does not have any custom props other than the optional `max` and `min`, but you can pass anything that is supported by the nested [`SelectControl`](https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/select-control/index.tsx) component. +### `createLabel` + +An optional function to create the label for the heading with the given level. +The first and only argument passed to the function is the numeric heading level. + +| Type | Required | Default | +|------------------------------|------------------------------|------------------------------------------------------| +| `Function` | no | `( level ) => sprintf( __( 'Heading %d' ), level )` | + ### `max` An optional maximum heading level. diff --git a/src/components/HeadingSelectControl/index.js b/src/components/HeadingSelectControl/index.js index 2152270..7271e4f 100644 --- a/src/components/HeadingSelectControl/index.js +++ b/src/components/HeadingSelectControl/index.js @@ -3,6 +3,20 @@ import React, { ReactNode, useMemo } from 'react'; import { SelectControl } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; +/** + * Get the label for the heading with the given level. + * + * @param {number} level - Heading level. + * @returns {string} Label. + */ +function _createLabel( level ) { + return sprintf( + // translators: %s: heading level (e.g.: 1, 2, 3). + __( 'Heading %d', 'block-editor-components' ), + level + ); +} + /** * A dropdown control that allows for selecting a heading level. * @@ -11,6 +25,7 @@ import { __, sprintf } from '@wordpress/i18n'; */ function HeadingSelectControl( props ) { const { + createLabel = _createLabel, max = 6, min = 2, onChange, @@ -28,16 +43,12 @@ function HeadingSelectControl( props ) { const level = min + index; return { - label: sprintf( - // translators: %s: heading level (e.g.: 1, 2, 3). - __( 'Heading %d', 'block-editor-components' ), - level - ), + label: createLabel( level ), value: level, }; } ); }, - [ max, min ] + [ createLabel, max, min ] ); return (