From aa7d06fc3b4777a6a632e6589c99e9dc53baff58 Mon Sep 17 00:00:00 2001 From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com> Date: Tue, 30 Jan 2024 15:34:49 +0100 Subject: [PATCH 01/16] Block Bindings: Change `core/pattern-attributes` source for `core/pattern-overrides` (#58434) * Change `pattern-attributes` for `pattern-overrides` * Use `_x()` function for translations * Add check in post meta source * Update pattern description * Update `_x( )` context to be more concise --- .../wordpress-6.5/block-bindings/sources/pattern.php | 10 +++++----- .../wordpress-6.5/block-bindings/sources/post-meta.php | 9 ++++++++- packages/block-library/src/block/edit.js | 4 ++-- .../src/components/partial-syncing-controls.js | 8 ++++---- .../e2e/specs/editor/various/pattern-overrides.spec.js | 6 +++--- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php index b43484660f1f8e..503723596bf9cc 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -28,14 +28,14 @@ function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs, $bl } function gutenberg_register_block_bindings_pattern_overrides_source() { - // Override the "core/pattern-attributes" source from core. - if ( array_key_exists( 'core/pattern-attributes', get_all_registered_block_bindings_sources() ) ) { - unregister_block_bindings_source( 'core/pattern-attributes' ); + // Override the "core/pattern-overrides" source from core. + if ( array_key_exists( 'core/pattern-overrides', get_all_registered_block_bindings_sources() ) ) { + unregister_block_bindings_source( 'core/pattern-overrides' ); } register_block_bindings_source( - 'core/pattern-attributes', + 'core/pattern-overrides', array( - 'label' => __( 'Pattern Attributes' ), + 'label' => _x( 'Pattern Overrides', 'block bindings source' ), 'get_value_callback' => 'gutenberg_block_bindings_pattern_overrides_callback', ) ); diff --git a/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php b/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php index 4166ed3243ef0d..e9b84108e52a34 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php @@ -17,6 +17,13 @@ function gutenberg_block_bindings_post_meta_callback( $source_attrs ) { $post_id = get_the_ID(); } + // If a post isn't public, we need to prevent + // unauthorized users from accessing the post meta. + $post = get_post( $post_id ); + if ( ( $post && 'publish' !== $post->post_status && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { + return null; + } + return get_post_meta( $post_id, $source_attrs['key'], true ); } @@ -28,7 +35,7 @@ function gutenberg_register_block_bindings_post_meta_source() { register_block_bindings_source( 'core/post-meta', array( - 'label' => __( 'Post Meta' ), + 'label' => _x( 'Post Meta', 'block bindings source' ), 'get_value_callback' => 'gutenberg_block_bindings_post_meta_callback', ) ); diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index b76b655ee0d943..c940f36e1a8b86 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -45,14 +45,14 @@ function isPartiallySynced( block ) { ) && !! block.attributes.metadata?.bindings && Object.values( block.attributes.metadata.bindings ).some( - ( binding ) => binding.source === 'core/pattern-attributes' + ( binding ) => binding.source === 'core/pattern-overrides' ) ); } function getPartiallySyncedAttributes( block ) { return Object.entries( block.attributes.metadata.bindings ) .filter( - ( [ , binding ] ) => binding.source === 'core/pattern-attributes' + ( [ , binding ] ) => binding.source === 'core/pattern-overrides' ) .map( ( [ attributeKey ] ) => attributeKey ); } diff --git a/packages/patterns/src/components/partial-syncing-controls.js b/packages/patterns/src/components/partial-syncing-controls.js index 0f6cde5ce7d689..f55ee7bdc26d36 100644 --- a/packages/patterns/src/components/partial-syncing-controls.js +++ b/packages/patterns/src/components/partial-syncing-controls.js @@ -22,7 +22,7 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { attributes.metadata?.bindings?.[ attributeName ]?.source ); const isConnectedToOtherSources = attributeSources.every( - ( source ) => source && source !== 'core/pattern-attributes' + ( source ) => source && source !== 'core/pattern-overrides' ); // Render nothing if all supported attributes are connected to other sources. @@ -39,7 +39,7 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { for ( const attributeName of Object.keys( syncedAttributes ) ) { if ( updatedBindings[ attributeName ]?.source === - 'core/pattern-attributes' + 'core/pattern-overrides' ) { delete updatedBindings[ attributeName ]; } @@ -59,7 +59,7 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { for ( const attributeName of Object.keys( syncedAttributes ) ) { if ( ! updatedBindings[ attributeName ] ) { updatedBindings[ attributeName ] = { - source: 'core/pattern-attributes', + source: 'core/pattern-overrides', }; } } @@ -94,7 +94,7 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { __nextHasNoMarginBottom label={ __( 'Allow instance overrides' ) } checked={ attributeSources.some( - ( source ) => source === 'core/pattern-attributes' + ( source ) => source === 'core/pattern-overrides' ) } onChange={ ( isChecked ) => { updateBindings( isChecked ); diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index 541f2f1600ad29..417827a9071581 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -92,7 +92,7 @@ test.describe( 'Pattern Overrides', () => { id: expect.any( String ), bindings: { content: { - source: 'core/pattern-attributes', + source: 'core/pattern-overrides', }, }, }, @@ -222,7 +222,7 @@ test.describe( 'Pattern Overrides', () => { const paragraphId = 'paragraph-id'; const { id } = await requestUtils.createBlock( { title: 'Pattern', - content: ` + content: `

Editable

`, status: 'publish', @@ -270,7 +270,7 @@ test.describe( 'Pattern Overrides', () => { const { id } = await requestUtils.createBlock( { title: 'Pattern with overrides', content: ` -
+
`, From 4853ce5833802d14e5c70de4230d465a0f46dda5 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Tue, 30 Jan 2024 11:46:54 -0300 Subject: [PATCH 02/16] remove unused component and css (#58449) --- .../font-library-modal/font-variant.js | 53 ------------------- .../font-library-modal/style.scss | 22 -------- 2 files changed, 75 deletions(-) delete mode 100644 packages/edit-site/src/components/global-styles/font-library-modal/font-variant.js diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-variant.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-variant.js deleted file mode 100644 index cd6dcbdd5a3d4c..00000000000000 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-variant.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * WordPress dependencies - */ -import { - __experimentalText as Text, - __experimentalVStack as VStack, - __experimentalHStack as HStack, - CheckboxControl, -} from '@wordpress/components'; - -/** - * Internal dependencies - */ -import FontFaceDemo from './font-demo'; - -function FontVariant( { - fontFace, - variantName, - checked, - onClick, - text, - actionHandler, -} ) { - const { fontStyle, fontWeight } = fontFace; - const displayVariantName = variantName || `${ fontWeight } ${ fontStyle }`; - - return ( -
- - - { !! actionHandler ? ( - actionHandler - ) : ( - - ) } - { typeof displayVariantName === 'string' ? ( - { displayVariantName } - ) : ( - displayVariantName - ) } - -
- -
-
-
- ); -} - -export default FontVariant; diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss index d8020b3b672e04..cd55fe99bcd369 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss +++ b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss @@ -65,11 +65,6 @@ margin-top: -1px; /* To collapse the margin with the previous element */ } -.font-library-modal__font-variant { - border-bottom: 1px solid $gray-200; - padding-bottom: $grid-unit-20; -} - .font-library-modal__tabs { [role="tablist"] { position: sticky; @@ -103,23 +98,6 @@ button.font-library-modal__upload-area { } } -.font-library-modal__font-variant_demo-wrapper { - white-space: nowrap; - overflow: hidden; - width: 100%; - position: relative; - - &::after { - content: ""; - position: absolute; - bottom: 0; - right: 0; - width: 30vw; - height: 100%; - background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); - } -} - .font-library__google-fonts-confirm { display: flex; justify-content: center; From df8142a17dae77a0ab0e2e4ae0109ef227b02bd1 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 30 Jan 2024 19:10:52 +0400 Subject: [PATCH 03/16] Editor: Use hooks instead of HoCs in the post-taxonomies components (#58446) * Editor: Use hooks instead of HoCs in the post taxonomies components * Combine filters --- .../src/components/post-taxonomies/check.js | 26 +++--- .../src/components/post-taxonomies/index.js | 37 ++++---- .../components/post-taxonomies/test/index.js | 88 ++++++++----------- 3 files changed, 63 insertions(+), 88 deletions(-) diff --git a/packages/editor/src/components/post-taxonomies/check.js b/packages/editor/src/components/post-taxonomies/check.js index a9d0c7e0cf97c3..c5621d724cd83b 100644 --- a/packages/editor/src/components/post-taxonomies/check.js +++ b/packages/editor/src/components/post-taxonomies/check.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ -import { compose } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; /** @@ -10,22 +9,19 @@ import { store as coreStore } from '@wordpress/core-data'; */ import { store as editorStore } from '../../store'; -export function PostTaxonomiesCheck( { postType, taxonomies, children } ) { - const hasTaxonomies = taxonomies?.some( ( taxonomy ) => - taxonomy.types.includes( postType ) - ); +export default function PostTaxonomiesCheck( { children } ) { + const hasTaxonomies = useSelect( ( select ) => { + const postType = select( editorStore ).getCurrentPostType(); + const taxonomies = select( coreStore ).getTaxonomies( { + per_page: -1, + } ); + return taxonomies?.some( ( taxonomy ) => + taxonomy.types.includes( postType ) + ); + }, [] ); if ( ! hasTaxonomies ) { return null; } return children; } - -export default compose( [ - withSelect( ( select ) => { - return { - postType: select( editorStore ).getCurrentPostType(), - taxonomies: select( coreStore ).getTaxonomies( { per_page: -1 } ), - }; - } ), -] )( PostTaxonomiesCheck ); diff --git a/packages/editor/src/components/post-taxonomies/index.js b/packages/editor/src/components/post-taxonomies/index.js index ee565bd4fe6eff..30468e04091837 100644 --- a/packages/editor/src/components/post-taxonomies/index.js +++ b/packages/editor/src/components/post-taxonomies/index.js @@ -2,8 +2,7 @@ * WordPress dependencies */ import { Fragment } from '@wordpress/element'; -import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; /** @@ -15,19 +14,20 @@ import { store as editorStore } from '../../store'; const identity = ( x ) => x; -export function PostTaxonomies( { - postType, - taxonomies, - taxonomyWrapper = identity, -} ) { - const availableTaxonomies = ( taxonomies ?? [] ).filter( ( taxonomy ) => - taxonomy.types.includes( postType ) - ); - const visibleTaxonomies = availableTaxonomies.filter( - // In some circumstances .visibility can end up as undefined so optional chaining operator required. - // https://github.com/WordPress/gutenberg/issues/40326 - ( taxonomy ) => taxonomy.visibility?.show_ui +export function PostTaxonomies( { taxonomyWrapper = identity } ) { + const { postType, taxonomies } = useSelect( ( select ) => { + return { + postType: select( editorStore ).getCurrentPostType(), + taxonomies: select( coreStore ).getTaxonomies( { per_page: -1 } ), + }; + }, [] ); + const visibleTaxonomies = ( taxonomies ?? [] ).filter( + ( taxonomy ) => + // In some circumstances .visibility can end up as undefined so optional chaining operator required. + // https://github.com/WordPress/gutenberg/issues/40326 + taxonomy.types.includes( postType ) && taxonomy.visibility?.show_ui ); + return visibleTaxonomies.map( ( taxonomy ) => { const TaxonomyComponent = taxonomy.hierarchical ? HierarchicalTermSelector @@ -43,11 +43,4 @@ export function PostTaxonomies( { } ); } -export default compose( [ - withSelect( ( select ) => { - return { - postType: select( editorStore ).getCurrentPostType(), - taxonomies: select( coreStore ).getTaxonomies( { per_page: -1 } ), - }; - } ), -] )( PostTaxonomies ); +export default PostTaxonomies; diff --git a/packages/editor/src/components/post-taxonomies/test/index.js b/packages/editor/src/components/post-taxonomies/test/index.js index 0a388143061750..1f386f4c2fab15 100644 --- a/packages/editor/src/components/post-taxonomies/test/index.js +++ b/packages/editor/src/components/post-taxonomies/test/index.js @@ -13,7 +13,7 @@ import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ -import { PostTaxonomies } from '../'; +import PostTaxonomies from '../'; describe( 'PostTaxonomies', () => { const genresTaxonomy = { @@ -87,20 +87,30 @@ describe( 'PostTaxonomies', () => { it( 'should render no children if taxonomy data not available', () => { const taxonomies = null; - const { container } = render( - + jest.spyOn( + select( editorStore ), + 'getCurrentPostType' + ).mockReturnValue( 'page' ); + jest.spyOn( select( coreStore ), 'getTaxonomies' ).mockReturnValue( + taxonomies ); + const { container } = render( ); + expect( container ).toBeEmptyDOMElement(); } ); it( 'should render taxonomy components for taxonomies assigned to post type', () => { - const { rerender } = render( - - ); + jest.spyOn( + select( editorStore ), + 'getCurrentPostType' + ).mockReturnValue( 'book' ); + jest.spyOn( select( coreStore ), 'getTaxonomies' ).mockReturnValue( [ + genresTaxonomy, + categoriesTaxonomy, + ] ); + + render( ); expect( screen.getByRole( 'group', { name: 'Genres' } ) ).toBeVisible(); expect( @@ -112,59 +122,35 @@ describe( 'PostTaxonomies', () => { expect( screen.queryByRole( 'button', { name: 'Add new category' } ) ).not.toBeInTheDocument(); - - rerender( - - ); - - expect( screen.getByRole( 'group', { name: 'Genres' } ) ).toBeVisible(); - expect( - screen.getByRole( 'group', { name: 'Categories' } ) - ).toBeVisible(); - expect( - screen.getByRole( 'button', { name: 'Add new genre' } ) - ).toBeVisible(); - expect( - screen.getByRole( 'button', { name: 'Add new category' } ) - ).toBeVisible(); } ); it( 'should not render taxonomy components that hide their ui', () => { - const { rerender } = render( - - ); + jest.spyOn( + select( editorStore ), + 'getCurrentPostType' + ).mockReturnValue( 'book' ); + jest.spyOn( select( coreStore ), 'getTaxonomies' ).mockReturnValue( [ + genresTaxonomy, + { + ...categoriesTaxonomy, + types: [ 'post', 'page', 'book' ], + visibility: { + show_ui: false, + }, + }, + ] ); + + render( ); expect( screen.getByRole( 'group', { name: 'Genres' } ) ).toBeVisible(); expect( screen.getByRole( 'button', { name: 'Add new genre' } ) ).toBeVisible(); - - rerender( - - ); - expect( - screen.queryByRole( 'group', { name: 'Genres' } ) + screen.queryByRole( 'group', { name: 'Categories' } ) ).not.toBeInTheDocument(); expect( - screen.queryByRole( 'button', { name: 'Add new genre' } ) + screen.queryByRole( 'button', { name: 'Add new category' } ) ).not.toBeInTheDocument(); } ); } ); From f145f085dd12364beaa5971105382f6b68717347 Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Tue, 30 Jan 2024 15:19:45 +0000 Subject: [PATCH 04/16] Home Link: Render Home text if there is no attribute label present (#58387) Co-authored-by: Bernie Reiter <96308+ockham@users.noreply.github.com> --- packages/block-library/src/home-link/index.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/home-link/index.php b/packages/block-library/src/home-link/index.php index 8fb5ed109019d3..9ec0c185872c0f 100644 --- a/packages/block-library/src/home-link/index.php +++ b/packages/block-library/src/home-link/index.php @@ -129,7 +129,10 @@ function block_core_home_link_build_li_wrapper_attributes( $context ) { */ function render_block_core_home_link( $attributes, $content, $block ) { if ( empty( $attributes['label'] ) ) { - return ''; + // Using a fallback for the label attribute allows rendering the block even if no attributes have been set, + // e.g. when using the block as a hooked block. + // Note that the fallback value needs to be kept in sync with the one set in `edit.js` (upon first loading the block in the editor). + $attributes['label'] = __( 'Home' ); } $aria_current = ''; From 0ea66b030ada051edf572508fd1397ef01667260 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Tue, 30 Jan 2024 15:20:03 +0000 Subject: [PATCH 05/16] Add missing reference to PHP documentation in lib dir (#58453) --- docs/contributors/code/back-merging-to-wp-core.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/contributors/code/back-merging-to-wp-core.md b/docs/contributors/code/back-merging-to-wp-core.md index 2b1ec77df1e550..c01fd74382fbca 100644 --- a/docs/contributors/code/back-merging-to-wp-core.md +++ b/docs/contributors/code/back-merging-to-wp-core.md @@ -2,14 +2,16 @@ For major releases of the WordPress software, Gutenberg features need to be merged into WordPress Core. Typically this involves taking changes made in `.php` files within the Gutenberg repository and making the equivalent updates in the WP Core codebase. -## Files/Directories +## Criteria + +### Files/Directories Changes to files within the following files/directories will typically require back-merging to WP Core: - `lib/` - `phpunit/` -## Ignored directories/files +### Ignored directories/files The following directories/files do _not_ require back-merging to WP Core: @@ -21,11 +23,16 @@ The following directories/files do _not_ require back-merging to WP Core: Please note this list is not exhaustive. -## Pull Request Criteria +### Pull Request Criteria In general, all PHP code committed to the Gutenberg repository since the date of the final Gutenberg release that was included in [the _last_ stable WP Core release](https://developer.wordpress.org/block-editor/contributors/versions-in-wordpress/) should be considered for back merging to WP Core. There are however certain exceptions to that rule. PRs with the following criteria do _not_ require back-merging to WP Core: - Does not contain changes to PHP code. -- Has label `Backport from WordPress Core` - this code is already in WP Core. +- Has label `Backport from WordPress Core` - this code is already in WP Core and is being synchronized back to Gutenberg. +- Has label `Backport to WordPress Core` - this code has already been syncrhonized to WP Core. + +## Further Reading + +Please see also additional documentation regarding [Gutenberg PHP code](/lib/README.md). From 6fab9e37bdaeff26ecfe5501f239ac09adbaba64 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Tue, 30 Jan 2024 15:55:49 +0000 Subject: [PATCH 06/16] Update Changelog for 17.5.3 --- changelog.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/changelog.txt b/changelog.txt index 62aadb76520c79..34d3a5018aea6b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,25 @@ == Changelog == += 17.5.3 = + + +## Changelog + +### Bug Fixes + +#### Block Library +- Navigation: Update the navigaiton renderer class name so that it doesn't clash with the one backported to core. ([58429](https://github.com/WordPress/gutenberg/pull/58429)) + + + + +## Contributors + +The following contributors merged PRs in this release: + +@scruffian + + = 17.6.0-rc.2 = ## Changelog From 6e8d5155bce6d051c247e35a6ce3c01d43e7f1b5 Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+c4rl0sbr4v0@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:31:27 +0100 Subject: [PATCH 07/16] Revert "Add defaultFontSizes option to theme.json (#56661)" (#58456) This reverts commit 940f0fe52cfd1e3599e507a7d3f83146914e978f. --- .../theme-json-reference/theme-json-living.md | 1 - lib/class-wp-theme-json-gutenberg.php | 29 +++++----- lib/theme.json | 1 - .../src/components/global-styles/hooks.js | 2 - .../global-styles/typography-panel.js | 58 +++++-------------- packages/block-editor/src/hooks/utils.js | 20 ++----- packages/block-editor/src/utils/object.js | 16 ----- schemas/json/theme.json | 5 -- 8 files changed, 31 insertions(+), 101 deletions(-) diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index d9389597b89da2..c58b8b3239f33e 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -177,7 +177,6 @@ Settings related to typography. | Property | Type | Default | Props | | --- | --- | --- |--- | -| defaultFontSizes | boolean | true | | | customFontSize | boolean | true | | | fontStyle | boolean | true | | | fontWeight | boolean | true | | diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 2ebebeaaaf3075..e23b0c88dd7070 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -155,7 +155,7 @@ class WP_Theme_JSON_Gutenberg { ), array( 'path' => array( 'typography', 'fontSizes' ), - 'prevent_override' => array( 'typography', 'defaultFontSizes' ), + 'prevent_override' => false, 'use_default_names' => true, 'value_func' => 'gutenberg_get_typography_font_size_value', 'css_vars' => '--wp--preset--font-size--$slug', @@ -413,20 +413,19 @@ class WP_Theme_JSON_Gutenberg { 'defaultPresets' => null, ), 'typography' => array( - 'fluid' => null, - 'customFontSize' => null, - 'defaultFontSizes' => null, - 'dropCap' => null, - 'fontFamilies' => null, - 'fontSizes' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textColumns' => null, - 'textDecoration' => null, - 'textTransform' => null, - 'writingMode' => null, + 'fluid' => null, + 'customFontSize' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textColumns' => null, + 'textDecoration' => null, + 'textTransform' => null, + 'writingMode' => null, ), ); diff --git a/lib/theme.json b/lib/theme.json index b7bc3cb89e60f2..c2ed7fdca39ed5 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -236,7 +236,6 @@ }, "typography": { "customFontSize": true, - "defaultFontSizes": true, "dropCap": true, "fontSizes": [ { diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index 7c5cf4cbf3e0fa..6be5481a633daa 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -67,7 +67,6 @@ const VALID_SETTINGS = [ 'spacing.units', 'typography.fluid', 'typography.customFontSize', - 'typography.defaultFontSizes', 'typography.dropCap', 'typography.fontFamilies', 'typography.fontSizes', @@ -240,7 +239,6 @@ export function useSettingsForBlockElement( ...updatedSettings.typography, fontSizes: {}, customFontSize: false, - defaultFontSizes: false, }; } diff --git a/packages/block-editor/src/components/global-styles/typography-panel.js b/packages/block-editor/src/components/global-styles/typography-panel.js index cc8b0589644bd5..5347ddab922651 100644 --- a/packages/block-editor/src/components/global-styles/typography-panel.js +++ b/packages/block-editor/src/components/global-styles/typography-panel.js @@ -22,7 +22,7 @@ import TextTransformControl from '../text-transform-control'; import TextDecorationControl from '../text-decoration-control'; import WritingModeControl from '../writing-mode-control'; import { getValueFromVariable, TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; -import { setImmutably, uniqByProperty } from '../../utils/object'; +import { setImmutably } from '../../utils/object'; const MIN_TEXT_COLUMNS = 1; const MAX_TEXT_COLUMNS = 6; @@ -53,10 +53,7 @@ export function useHasTypographyPanel( settings ) { function useHasFontSizeControl( settings ) { return ( - ( settings?.typography?.defaultFontSizes !== false && - settings?.typography?.fontSizes?.default?.length ) || - settings?.typography?.fontSizes?.theme?.length || - settings?.typography?.fontSizes?.custom?.length || + hasMergedOrigins( settings?.typography?.fontSizes ) || settings?.typography?.customFontSize ); } @@ -103,45 +100,16 @@ function useHasTextColumnsControl( settings ) { return settings?.typography?.textColumns; } -/** - * TODO: The reversing and filtering of default font sizes is a hack so the - * dropdown UI matches what is generated in the global styles CSS stylesheet. - * - * This is a temporary solution until #57733 is resolved. At which point, - * the mergedFontSizes would just need to be the concatenated array of all - * presets or a custom dropdown with sections for each. - * - * @see {@link https://github.com/WordPress/gutenberg/issues/57733} - * - * @param {Object} settings The global styles settings. - * - * @return {Array} The merged font sizes. - */ -function getMergedFontSizes( settings ) { - // The font size presets are merged in reverse order so that the duplicates - // that may defined later in the array have higher priority to match the CSS. - const mergedFontSizesAll = uniqByProperty( - [ - settings?.typography?.fontSizes?.custom, - settings?.typography?.fontSizes?.theme, - settings?.typography?.fontSizes?.default, - ].flatMap( ( presets ) => presets?.toReversed() ?? [] ), - 'slug' - ).reverse(); - - // Default presets exist in the global styles CSS no matter the setting, so - // filtering them out in the UI has to be done after merging. - const mergedFontSizes = - settings?.typography?.defaultFontSizes === false - ? mergedFontSizesAll.filter( - ( { slug } ) => - ! [ 'small', 'medium', 'large', 'x-large' ].includes( - slug - ) - ) - : mergedFontSizesAll; - - return mergedFontSizes; +function getUniqueFontSizesBySlug( settings ) { + const fontSizes = settings?.typography?.fontSizes; + const mergedFontSizes = fontSizes ? mergeOrigins( fontSizes ) : []; + const uniqueSizes = []; + for ( const currentSize of mergedFontSizes ) { + if ( ! uniqueSizes.some( ( { slug } ) => slug === currentSize.slug ) ) { + uniqueSizes.push( currentSize ); + } + } + return uniqueSizes; } function TypographyToolsPanel( { @@ -217,7 +185,7 @@ export default function TypographyPanel( { // Font Size const hasFontSizeEnabled = useHasFontSizeControl( settings ); const disableCustomFontSizes = ! settings?.typography?.customFontSize; - const mergedFontSizes = getMergedFontSizes( settings ); + const mergedFontSizes = getUniqueFontSizesBySlug( settings ); const fontSize = decodeValue( inheritedValue?.typography?.fontSize ); const setFontSize = ( newValue, metadata ) => { diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index f13963933e5225..2f7a8f3a81f19d 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -177,10 +177,7 @@ export function useBlockSettings( name, parentLayout ) { backgroundImage, backgroundSize, fontFamilies, - userFontSizes, - themeFontSizes, - defaultFontSizes, - defaultFontSizesEnabled, + fontSizes, customFontSize, fontStyle, fontWeight, @@ -227,10 +224,7 @@ export function useBlockSettings( name, parentLayout ) { 'background.backgroundImage', 'background.backgroundSize', 'typography.fontFamilies', - 'typography.fontSizes.custom', - 'typography.fontSizes.theme', - 'typography.fontSizes.default', - 'typography.defaultFontSizes', + 'typography.fontSizes', 'typography.customFontSize', 'typography.fontStyle', 'typography.fontWeight', @@ -314,12 +308,9 @@ export function useBlockSettings( name, parentLayout ) { custom: fontFamilies, }, fontSizes: { - custom: userFontSizes, - theme: themeFontSizes, - default: defaultFontSizes, + custom: fontSizes, }, customFontSize, - defaultFontSizes: defaultFontSizesEnabled, fontStyle, fontWeight, lineHeight, @@ -356,10 +347,7 @@ export function useBlockSettings( name, parentLayout ) { backgroundImage, backgroundSize, fontFamilies, - userFontSizes, - themeFontSizes, - defaultFontSizes, - defaultFontSizesEnabled, + fontSizes, customFontSize, fontStyle, fontWeight, diff --git a/packages/block-editor/src/utils/object.js b/packages/block-editor/src/utils/object.js index c78fe0e656dfef..8f6c82a9c3991e 100644 --- a/packages/block-editor/src/utils/object.js +++ b/packages/block-editor/src/utils/object.js @@ -49,19 +49,3 @@ export const getValueFromObjectPath = ( object, path, defaultValue ) => { } ); return value ?? defaultValue; }; - -/** - * Helper util to filter out objects with duplicate values for a given property. - * - * @param {Object[]} array Array of objects to filter. - * @param {string} property Property to filter unique values by. - * - * @return {Object[]} Array of objects with unique values for the specified property. - */ -export function uniqByProperty( array, property ) { - const seen = new Set(); - return array.filter( ( item ) => { - const value = item[ property ]; - return seen.has( value ) ? false : seen.add( value ); - } ); -} diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 64e32b4da9df88..8ec1de58478b8d 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -489,11 +489,6 @@ "description": "Settings related to typography.", "type": "object", "properties": { - "defaultFontSizes": { - "description": "Allow users to choose font sizes from the default font size presets.", - "type": "boolean", - "default": true - }, "customFontSize": { "description": "Allow users to set custom font sizes.", "type": "boolean", From f4f0b0c1359cb5986e9a99752f67310a16f4cfd0 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 30 Jan 2024 18:52:29 +0200 Subject: [PATCH 08/16] Disables saving react root view from parent (#58266) --- .../org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java index 6098f15a6927b1..17fa8fa174e6e1 100644 --- a/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java +++ b/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/WPAndroidGlueCode.java @@ -666,6 +666,10 @@ public void onCreateView(Context initContext, mReactRootView = new ReactRootView(new MutableContextWrapper(initContext)); mReactRootView.setBackgroundColor(colorBackground); + // Workaround to prevent saving large RN view hierarchies that lead to a `TransactionTooLargeException` + // Ref: https://github.com/wordpress-mobile/WordPress-Android/issues/9685#issuecomment-1908452392 + mReactRootView.setSaveFromParentEnabled(false); + ReactInstanceManagerBuilder builder = ReactInstanceManager.builder() .setApplication(application) From 775eed2cb8e480e5978fd888ba8df7a98e7e53f4 Mon Sep 17 00:00:00 2001 From: Andrew Hayward Date: Tue, 30 Jan 2024 17:04:28 +0000 Subject: [PATCH 09/16] Adding `constrainTabbing` prop to `useDialog` hook (#57962) Adding `constrainTabbing` prop to `useDialog` hook Tabbing constraint is currently tied to the `focusOnMount` prop in `useDialog'; if `focusOnMount` is not `false`, tabbing will be constrained. Sometimes we want the `focusOnMount` behaviour, without constrained tabbing. This patch adds a separate `constrainTabbing` prop, which implicitly maintains the current behaviour, taking its default value from `focusOnMount`. Otherwise, if set explicitly, tabbing will be constrained based on the value passed. --- packages/components/CHANGELOG.md | 1 + packages/components/src/popover/index.tsx | 2 + .../components/src/popover/test/index.tsx | 250 ++++++++++++++++++ packages/components/src/popover/types.ts | 9 + .../hooks/use-constrained-tabbing/index.js | 5 +- .../compose/src/hooks/use-dialog/index.ts | 22 +- 6 files changed, 285 insertions(+), 4 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 65ed149e34c216..6080498d5e92ba 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -31,6 +31,7 @@ - `Tooltip` and `Button`: tidy up unit tests ([#57975](https://github.com/WordPress/gutenberg/pull/57975)). - `BorderControl`, `BorderBoxControl`: Replace style picker with ToggleGroupControl ([#57562](https://github.com/WordPress/gutenberg/pull/57562)). - `SlotFill`: fix typo in use-slot-fills return docs ([#57654](https://github.com/WordPress/gutenberg/pull/57654)) +- `Popover`: Adding `constrainTabbing` prop to `useDialog` hook ([#57962](https://github.com/WordPress/gutenberg/pull/57962)) ### Bug Fix diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx index 709d4b9884b5ec..1634079c8cae76 100644 --- a/packages/components/src/popover/index.tsx +++ b/packages/components/src/popover/index.tsx @@ -126,6 +126,7 @@ const UnconnectedPopover = ( const { animate = true, headerTitle, + constrainTabbing, onClose, children, className, @@ -264,6 +265,7 @@ const UnconnectedPopover = ( } const [ dialogRef, dialogProps ] = useDialog( { + constrainTabbing, focusOnMount, __unstableOnClose: onDialogClose, // @ts-expect-error The __unstableOnClose property needs to be deprecated first (see https://github.com/WordPress/gutenberg/pull/27675) diff --git a/packages/components/src/popover/test/index.tsx b/packages/components/src/popover/test/index.tsx index 33a2d8758c09db..22c576683a0108 100644 --- a/packages/components/src/popover/test/index.tsx +++ b/packages/components/src/popover/test/index.tsx @@ -2,6 +2,7 @@ * External dependencies */ import { render, screen, waitFor, getByText } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import type { CSSProperties } from 'react'; /** @@ -36,6 +37,28 @@ type PlacementToInitialTranslationTuple = [ CSSProperties[ 'translate' ], ]; +beforeAll( () => { + // This mock is necessary because deep in the weeds, `useConstrained` relies + // on `focusable` to return a list of DOM elements that can be focused. Part + // of this process involves checking that an element has an intrinsic size, + // which will always fail in JSDom. + // + // https://github.com/WordPress/gutenberg/blob/trunk/packages/dom/src/focusable.js#L55-L61 + jest.spyOn( + HTMLElement.prototype, + 'offsetHeight', + 'get' + ).mockImplementation( function getOffsetHeight( this: HTMLElement ) { + // The `1` returned here is somewhat arbitrary – it just needs to be a + // non-zero integer. + return 1; + } ); +} ); + +afterAll( () => { + jest.restoreAllMocks(); +} ); + // There's no matching `placement` for 'middle center' positions, // fallback to 'bottom' (same as `floating-ui`'s default.) const FALLBACK_FOR_MIDDLE_CENTER_POSITIONS = 'bottom'; @@ -188,6 +211,233 @@ describe( 'Popover', () => { expect( document.body ).toHaveFocus(); } ); } ); + + describe( 'tab constraint behavior', () => { + // `constrainTabbing` is implicitly controlled by `focusOnMount`. + // By default, when `focusOnMount` is false, `constrainTabbing` will + // also be false; otherwise, `constrainTabbing` will be true. + + const setup = async ( + props?: Partial< React.ComponentProps< typeof Popover > > + ) => { + const user = await userEvent.setup(); + const view = render( + + + + + + ); + + const popover = screen.getByTestId( 'popover-element' ); + await waitFor( () => expect( popover ).toBeVisible() ); + + const [ firstButton, secondButton, thirdButton ] = + screen.getAllByRole( 'button' ); + + return { + ...view, + popover, + firstButton, + secondButton, + thirdButton, + user, + }; + }; + + // Note: due to an issue in testing-library/user-event [1], the + // tests for constrained tabbing fail. + // [1]: https://github.com/testing-library/user-event/issues/1188 + // + // eslint-disable-next-line jest/no-disabled-tests + describe.skip( 'constrains tabbing', () => { + test( 'by default', async () => { + // The default value for `focusOnMount` is 'firstElement', + // which means the default value for `constrainTabbing` is + // 'true'. + + const { user, firstButton, secondButton, thirdButton } = + await setup(); + + await waitFor( () => expect( firstButton ).toHaveFocus() ); + await user.tab(); + expect( secondButton ).toHaveFocus(); + await user.tab(); + expect( thirdButton ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab( { shift: true } ); + expect( thirdButton ).toHaveFocus(); + } ); + + test( 'when `focusOnMount` is true', async () => { + const { + user, + popover, + firstButton, + secondButton, + thirdButton, + } = await setup( { focusOnMount: true } ); + + expect( popover ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab(); + expect( secondButton ).toHaveFocus(); + await user.tab(); + expect( thirdButton ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab( { shift: true } ); + expect( thirdButton ).toHaveFocus(); + } ); + + test( 'when `focusOnMount` is "firstElement"', async () => { + const { user, firstButton, secondButton, thirdButton } = + await setup( { focusOnMount: 'firstElement' } ); + + await waitFor( () => expect( firstButton ).toHaveFocus() ); + await user.tab(); + expect( secondButton ).toHaveFocus(); + await user.tab(); + expect( thirdButton ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab( { shift: true } ); + expect( thirdButton ).toHaveFocus(); + } ); + + test( 'when `focusOnMount` is false if `constrainTabbing` is true', async () => { + const { + user, + baseElement, + firstButton, + secondButton, + thirdButton, + } = await setup( { + focusOnMount: false, + constrainTabbing: true, + } ); + + expect( baseElement ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab(); + expect( secondButton ).toHaveFocus(); + await user.tab(); + expect( thirdButton ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab( { shift: true } ); + expect( thirdButton ).toHaveFocus(); + } ); + } ); + + describe( 'does not constrain tabbing', () => { + test( 'when `constrainTabbing` is false', async () => { + // The default value for `focusOnMount` is 'firstElement', + // which means the default value for `constrainTabbing` is + // 'true', but the provided value should override this. + + const { + user, + baseElement, + firstButton, + secondButton, + thirdButton, + } = await setup( { constrainTabbing: false } ); + + await waitFor( () => expect( firstButton ).toHaveFocus() ); + await user.tab(); + expect( secondButton ).toHaveFocus(); + await user.tab(); + expect( thirdButton ).toHaveFocus(); + await user.tab(); + expect( baseElement ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab( { shift: true } ); + expect( baseElement ).toHaveFocus(); + } ); + + test( 'when `focusOnMount` is false', async () => { + const { + user, + baseElement, + firstButton, + secondButton, + thirdButton, + } = await setup( { focusOnMount: false } ); + + expect( baseElement ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab(); + expect( secondButton ).toHaveFocus(); + await user.tab(); + expect( thirdButton ).toHaveFocus(); + await user.tab(); + expect( baseElement ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab( { shift: true } ); + expect( baseElement ).toHaveFocus(); + } ); + + test( 'when `focusOnMount` is true if `constrainTabbing` is false', async () => { + const { + user, + baseElement, + popover, + firstButton, + secondButton, + thirdButton, + } = await setup( { + focusOnMount: true, + constrainTabbing: false, + } ); + + expect( popover ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab(); + expect( secondButton ).toHaveFocus(); + await user.tab(); + expect( thirdButton ).toHaveFocus(); + await user.tab(); + expect( baseElement ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab( { shift: true } ); + expect( baseElement ).toHaveFocus(); + } ); + + test( 'when `focusOnMount` is "firstElement" if `constrainTabbing` is false', async () => { + const { + user, + baseElement, + firstButton, + secondButton, + thirdButton, + } = await setup( { + focusOnMount: 'firstElement', + constrainTabbing: false, + } ); + + await waitFor( () => expect( firstButton ).toHaveFocus() ); + await user.tab(); + expect( secondButton ).toHaveFocus(); + await user.tab(); + expect( thirdButton ).toHaveFocus(); + await user.tab(); + expect( baseElement ).toHaveFocus(); + await user.tab(); + expect( firstButton ).toHaveFocus(); + await user.tab( { shift: true } ); + expect( baseElement ).toHaveFocus(); + } ); + } ); + } ); } ); describe( 'Slot outside iframe', () => { diff --git a/packages/components/src/popover/types.ts b/packages/components/src/popover/types.ts index c4250b22ba8349..427f4afb81bfb9 100644 --- a/packages/components/src/popover/types.ts +++ b/packages/components/src/popover/types.ts @@ -65,6 +65,15 @@ export type PopoverProps = { * @default true */ flip?: boolean; + /** + * Determines whether tabbing is constrained to within the popover, + * preventing keyboard focus from leaving the popover content without + * explicit focus elswhere, or whether the popover remains part of the wider + * tab order. If no value is passed, it will be derived from `focusOnMount`. + * + * @default `focusOnMount` !== false + */ + constrainTabbing?: boolean; /** * By default, the _first tabbable element_ in the popover will receive focus * when it mounts. This is the same as setting this prop to `"firstElement"`. diff --git a/packages/compose/src/hooks/use-constrained-tabbing/index.js b/packages/compose/src/hooks/use-constrained-tabbing/index.js index 97b8a2a0a5eb52..94e0080b211cd9 100644 --- a/packages/compose/src/hooks/use-constrained-tabbing/index.js +++ b/packages/compose/src/hooks/use-constrained-tabbing/index.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { TAB } from '@wordpress/keycodes'; import { focus } from '@wordpress/dom'; /** @@ -33,9 +32,9 @@ import useRefEffect from '../use-ref-effect'; function useConstrainedTabbing() { return useRefEffect( ( /** @type {HTMLElement} */ node ) => { function onKeyDown( /** @type {KeyboardEvent} */ event ) { - const { keyCode, shiftKey, target } = event; + const { key, shiftKey, target } = event; - if ( keyCode !== TAB ) { + if ( key !== 'Tab' ) { return; } diff --git a/packages/compose/src/hooks/use-dialog/index.ts b/packages/compose/src/hooks/use-dialog/index.ts index 66974b60e07035..1b517478fa0bd2 100644 --- a/packages/compose/src/hooks/use-dialog/index.ts +++ b/packages/compose/src/hooks/use-dialog/index.ts @@ -19,7 +19,26 @@ import useFocusOutside from '../use-focus-outside'; import useMergeRefs from '../use-merge-refs'; type DialogOptions = { + /** + * Determines whether focus should be automatically moved to the popover + * when it mounts. `false` causes no focus shift, `true` causes the popover + * itself to gain focus, and `firstElement` focuses the first focusable + * element within the popover. + * + * @default 'firstElement' + */ focusOnMount?: Parameters< typeof useFocusOnMount >[ 0 ]; + /** + * Determines whether tabbing is constrained to within the popover, + * preventing keyboard focus from leaving the popover content without + * explicit focus elsewhere, or whether the popover remains part of the + * wider tab order. + * If no value is passed, it will be derived from `focusOnMount`. + * + * @see focusOnMount + * @default `focusOnMount` !== false + */ + constrainTabbing?: boolean; onClose?: () => void; /** * Use the `onClose` prop instead. @@ -48,6 +67,7 @@ type useDialogReturn = [ */ function useDialog( options: DialogOptions ): useDialogReturn { const currentOptions = useRef< DialogOptions | undefined >(); + const { constrainTabbing = options.focusOnMount !== false } = options; useEffect( () => { currentOptions.current = options; }, Object.values( options ) ); @@ -83,7 +103,7 @@ function useDialog( options: DialogOptions ): useDialogReturn { return [ useMergeRefs( [ - options.focusOnMount !== false ? constrainedTabbingRef : null, + constrainTabbing ? constrainedTabbingRef : null, options.focusOnMount !== false ? focusReturnRef : null, options.focusOnMount !== false ? focusOnMountRef : null, closeOnEscapeRef, From 8c80c0e2be01b4868aa55727b6cfcfca0ef438ba Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Wed, 31 Jan 2024 02:31:22 +0900 Subject: [PATCH 10/16] CheckboxControl: Add custom label example to Storybook (#58438) --- .../checkbox-control/stories/index.story.tsx | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/components/src/checkbox-control/stories/index.story.tsx b/packages/components/src/checkbox-control/stories/index.story.tsx index ce55cfb655a17c..6752ce07667cbd 100644 --- a/packages/components/src/checkbox-control/stories/index.story.tsx +++ b/packages/components/src/checkbox-control/stories/index.story.tsx @@ -13,6 +13,7 @@ import { useState } from '@wordpress/element'; */ import CheckboxControl from '..'; import { VStack } from '../../v-stack'; +import { HStack } from '../../h-stack'; const meta: Meta< typeof CheckboxControl > = { component: CheckboxControl, @@ -115,3 +116,46 @@ Indeterminate.args = { label: 'Select all', __nextHasNoMarginBottom: true, }; + +/** + * For more complex designs, a custom `