diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 9bd6ba212f0d8..777549e334aa9 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -17,14 +17,11 @@ concurrency: jobs: e2e-puppeteer: - name: Puppeteer - ${{ matrix.part }} + name: Puppeteer runs-on: ubuntu-latest if: ${{ github.repository == 'WordPress/gutenberg' || github.event_name == 'pull_request' }} strategy: fail-fast: false - matrix: - part: [1, 2, 3] - totalParts: [3] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -43,8 +40,7 @@ jobs: - name: Running the tests run: | - npx wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npx wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % ${{ matrix.totalParts }} == ${{ matrix.part }} - 1' < ~/.jest-e2e-tests ) + npx wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" - name: Archive debug artifacts (screenshots, HTML snapshots) uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 @@ -69,8 +65,8 @@ jobs: strategy: fail-fast: false matrix: - part: [1, 2, 3, 4] - totalParts: [4] + part: [1, 2, 3, 4, 5, 6, 7, 8] + totalParts: [8] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 485668a755b8c..517febe9774a9 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -68,13 +68,13 @@ jobs: - name: Compare performance with base branch if: github.event_name == 'push' # The base hash used here need to be a commit that is compatible with the current WP version - # The current one is bd2a881101727b03b0be09382b34841af5a3c03e and it needs to be updated every WP major release. + # The current one is b61dde2e5ec29d98801e623de968bfb286c5be3f and it needs to be updated every WP major release. # It is used as a base comparison point to avoid fluctuation in the performance metrics. run: | WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt) IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION" WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" - ./bin/plugin/cli.js perf $GITHUB_SHA bd2a881101727b03b0be09382b34841af5a3c03e --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" + ./bin/plugin/cli.js perf $GITHUB_SHA b61dde2e5ec29d98801e623de968bfb286c5be3f --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" - name: Compare performance with custom branches if: github.event_name == 'workflow_dispatch' @@ -97,7 +97,7 @@ jobs: CODEHEALTH_PROJECT_TOKEN: ${{ secrets.CODEHEALTH_PROJECT_TOKEN }} run: | COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI") - ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA bd2a881101727b03b0be09382b34841af5a3c03e $COMMITTED_AT + ./bin/log-performance-results.js $CODEHEALTH_PROJECT_TOKEN trunk $GITHUB_SHA b61dde2e5ec29d98801e623de968bfb286c5be3f $COMMITTED_AT - name: Archive debug artifacts (screenshots, HTML snapshots) uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 diff --git a/changelog.txt b/changelog.txt index 0f86c2d8f593b..1814106e5e509 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,45 @@ == Changelog == += 17.1.2 = + +## Changelog + +### Bug Fixes + +#### Block Editor + +- PostCSS style transformation: fail gracefully instead of throwing an error (https://github.com/WordPress/gutenberg/pull/56093) + +## Contributors + +The following contributors merged PRs in this release: + +@zaguiini + + += 17.1.1 = + +# Changelog + +## Bug Fixes + +### Block Library + +Fix fatal error when calling undefined block library function. #56459 + + += 17.0.3 = + +## Changelog + +### Bug Fixes + +#### Block Editor + +- PostCSS style transformation: fail gracefully instead of throwing an error (https://github.com/WordPress/gutenberg/pull/56093) + + + = 17.1.0 = diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 8c8ed3ff3c334..d19be240f4870 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -25,6 +25,7 @@ Similar requirements apply to releasing WordPress's [npm packages](https://devel - [Automated cherry-picking](#automated-cherry-picking) - [Manual cherry-picking](#manual-cherry-picking) - [Publishing the release](#publishing-the-release) + - [Troubleshooting the release](#troubleshooting-the-release) - [Documenting the release](#documenting-the-release) - [Selecting the release highlights](#selecting-the-release-highlights) - [Requesting release assets](#requesting-release-assets) @@ -253,6 +254,41 @@ Once approved, the new Gutenberg version will be available to WordPress users al The final step is to write a release post on [make.wordpress.org/core](https://make.wordpress.org/core/). You can find some tips on that below. +#### Troubleshooting the release + +> The plugin was published to the WordPress.org plugin directory but the workflow failed. + +This has happened ocassionally, see [this one](https://github.com/WordPress/gutenberg/actions/runs/6955409957/job/18924124118) for example. + +It's important to check that: + +- the plugin from the directory works as expected +- the ZIP contents (see [Downloads](https://plugins.trac.wordpress.org/browser/gutenberg/)) looks correct (doesn't have anything obvious missing) +- the [Gutenberg SVN repo](https://plugins.trac.wordpress.org/browser/gutenberg/) has two new commits (see [the log](https://plugins.trac.wordpress.org/browser/gutenberg/)): + - the `trunk` folder should have "Commiting version X.Y.Z" + - there is a new `tags/X.Y.Z` folder with the same contents as `trunk` whose latest commit is "Tagging version X.Y.Z" + +Most likely, the tag folder couldn't be created. This is a [known issue](https://plugins.trac.wordpress.org/browser/gutenberg/) that [can be fixed manually](https://github.com/WordPress/gutenberg/issues/55295#issuecomment-1759292978). + +Either substitute SVN_USERNAME, SVN_PASSWORD, and VERSION for the proper values or set them as global environment variables first: + +```sh +# CHECKOUT THE REPOSITORY +svn checkout https://plugins.svn.wordpress.org/gutenberg/trunk --username "$SVN_USERNAME" --password "$SVN_PASSWORD" gutenberg-svn + +# MOVE TO THE LOCAL FOLDER +cd gutenberg-svn + +# IF YOU HAPPEN TO HAVE ALREADY THE REPO LOCALLY +# AND DIDN'T CHECKOUT, MAKE SURE IT IS UPDATED +# svn up . + +# COPY CURRENT TRUNK INTO THE NEW TAGS FOLDER +svn copy https://plugins.svn.wordpress.org/gutenberg/trunk https://plugins.svn.wordpress.org/gutenberg/tags/$VERSION -m 'Tagging version $VERSION' --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" +``` + +Ask around if you need help with any of this. + ### Documenting the release Documenting the release is led by the release manager with the help of [Gutenberg development team](https://developer.wordpress.org/block-editor/block-editor/contributors/repository-management/#teams) members. This process is comprised of a series of sequential steps that, because of the number of people involved, and the coordination required, need to adhere to a timeline between the RC and stable releases. Stable Gutenberg releases happen on Wednesdays, one week after the initial RC. diff --git a/docs/manifest.json b/docs/manifest.json index ba345e7716ee3..5906743512062 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -785,6 +785,12 @@ "markdown_source": "../packages/components/src/confirm-dialog/README.md", "parent": "components" }, + { + "title": "CustomSelectControlV2", + "slug": "custom-select-control-v2", + "markdown_source": "../packages/components/src/custom-select-control-v2/README.md", + "parent": "components" + }, { "title": "CustomSelectControl", "slug": "custom-select-control", 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 4890386ca8333..492cdaf089cc4 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -95,6 +95,8 @@ Settings related to colors. | link | boolean | false | | | palette | array | | color, name, slug | | text | boolean | true | | +| heading | boolean | true | | +| button | boolean | true | | --- diff --git a/gutenberg.php b/gutenberg.php index 018c269f4f565..ca61f52f1afe4 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.2 * Requires PHP: 7.0 - * Version: 17.1.0 + * Version: 17.1.2 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php index 0e9166a7c7d54..e2eb4e10414fe 100644 --- a/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php +++ b/lib/compat/wordpress-6.5/class-wp-navigation-block-renderer.php @@ -170,7 +170,7 @@ private static function get_inner_blocks_html( $attributes, $inner_blocks ) { // Add directives to the submenu if needed. if ( $has_submenus && $should_load_view_script ) { $tags = new WP_HTML_Tag_Processor( $inner_blocks_html ); - $inner_blocks_html = block_core_navigation_add_directives_to_submenu( $tags, $attributes ); + $inner_blocks_html = gutenberg_block_core_navigation_add_directives_to_submenu( $tags, $attributes ); } return $inner_blocks_html; @@ -195,7 +195,7 @@ private static function get_inner_blocks_from_navigation_post( $attributes ) { // 'parse_blocks' includes a null block with '\n\n' as the content when // it encounters whitespace. This code strips it. - $compacted_blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); + $compacted_blocks = gutenberg_block_core_navigation_filter_out_empty_blocks( $parsed_blocks ); // TODO - this uses the full navigation block attributes for the // context which could be refined. @@ -210,7 +210,7 @@ private static function get_inner_blocks_from_navigation_post( $attributes ) { * @return WP_Block_List Returns the inner blocks for the navigation block. */ private static function get_inner_blocks_from_fallback( $attributes ) { - $fallback_blocks = block_core_navigation_get_fallback_blocks(); + $fallback_blocks = gutenberg_block_core_navigation_get_fallback_blocks(); // Fallback my have been filtered so do basic test for validity. if ( empty( $fallback_blocks ) || ! is_array( $fallback_blocks ) ) { @@ -245,9 +245,9 @@ private static function get_inner_blocks( $attributes, $block ) { defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN && array_key_exists( '__unstableLocation', $attributes ) && ! array_key_exists( 'ref', $attributes ) && - ! empty( block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) ) + ! empty( gutenberg_block_core_navigation_get_menu_items_at_location( $attributes['__unstableLocation'] ) ) ) { - $inner_blocks = block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ); + $inner_blocks = gutenberg_block_core_navigation_get_inner_blocks_from_unstable_location( $attributes ); } // Load inner blocks from the navigation post. @@ -270,7 +270,7 @@ private static function get_inner_blocks( $attributes, $block ) { */ $inner_blocks = apply_filters( 'block_core_navigation_render_inner_blocks', $inner_blocks ); - $post_ids = block_core_navigation_get_post_ids( $inner_blocks ); + $post_ids = gutenberg_block_core_navigation_get_post_ids( $inner_blocks ); if ( $post_ids ) { _prime_post_caches( $post_ids, false, false ); } @@ -353,8 +353,8 @@ private static function get_layout_class( $attributes ) { private static function get_classes( $attributes ) { // Restore legacy classnames for submenu positioning. $layout_class = static::get_layout_class( $attributes ); - $colors = block_core_navigation_build_css_colors( $attributes ); - $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); + $colors = gutenberg_block_core_navigation_build_css_colors( $attributes ); + $font_sizes = gutenberg_block_core_navigation_build_css_font_sizes( $attributes ); $is_responsive_menu = static::is_responsive( $attributes ); // Manually add block support text decoration as CSS class. @@ -378,8 +378,8 @@ private static function get_classes( $attributes ) { * @return string Returns the styles for the navigation block. */ private static function get_styles( $attributes ) { - $colors = block_core_navigation_build_css_colors( $attributes ); - $font_sizes = block_core_navigation_build_css_font_sizes( $attributes ); + $colors = gutenberg_block_core_navigation_build_css_colors( $attributes ); + $font_sizes = gutenberg_block_core_navigation_build_css_font_sizes( $attributes ); $block_styles = isset( $attributes['styles'] ) ? $attributes['styles'] : ''; return $block_styles . $colors['inline_styles'] . $font_sizes['inline_styles']; } @@ -394,7 +394,7 @@ private static function get_styles( $attributes ) { */ private static function get_responsive_container_markup( $attributes, $inner_blocks, $inner_blocks_html ) { $should_load_view_script = static::should_load_view_script( $attributes, $inner_blocks ); - $colors = block_core_navigation_build_css_colors( $attributes ); + $colors = gutenberg_block_core_navigation_build_css_colors( $attributes ); $modal_unique_id = wp_unique_id( 'modal-' ); $is_hidden_by_default = isset( $attributes['overlayMenu'] ) && 'always' === $attributes['overlayMenu']; @@ -627,7 +627,7 @@ public static function render( $attributes, $content, $block ) { $inner_blocks = static::get_inner_blocks( $attributes, $block ); // Prevent navigation blocks referencing themselves from rendering. - if ( block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) { + if ( gutenberg_block_core_navigation_block_contains_core_navigation( $inner_blocks ) ) { return ''; } diff --git a/package-lock.json b/package-lock.json index 601aafc18b268..b2deb50ef559f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.1.0", + "version": "17.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.1.0", + "version": "17.1.2", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 43ca7f0858f60..a7df84ea029a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.1.0", + "version": "17.1.2", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/packages/block-editor/src/components/block-parent-selector/index.js b/packages/block-editor/src/components/block-parent-selector/index.js index 31e0b1b8fd8cf..80b314eeb42e5 100644 --- a/packages/block-editor/src/components/block-parent-selector/index.js +++ b/packages/block-editor/src/components/block-parent-selector/index.js @@ -74,7 +74,7 @@ export default function BlockParentSelector() { onClick={ () => selectBlock( firstParentClientId ) } label={ sprintf( /* translators: %s: Name of the block's parent. */ - __( 'Select %s' ), + __( 'Select parent block: %s' ), blockInformation?.title ) } showTooltip diff --git a/packages/block-editor/src/components/block-patterns-list/README.md b/packages/block-editor/src/components/block-patterns-list/README.md index 7b30dcecc7bbd..8b798f93b7190 100644 --- a/packages/block-editor/src/components/block-patterns-list/README.md +++ b/packages/block-editor/src/components/block-patterns-list/README.md @@ -1,6 +1,6 @@ # Block Patterns List -The `BlockPatternList` component makes a list of the different registered block patterns. It uses the `BlockPreview` component to display a preview for each block pattern. +The `BlockPatternsList` component makes a list of the different registered block patterns. It uses the `BlockPreview` component to display a preview for each block pattern. For more infos about blocks patterns, read [this](https://make.wordpress.org/core/2020/07/16/block-patterns-in-wordpress-5-5/). @@ -18,10 +18,10 @@ For more infos about blocks patterns, read [this](https://make.wordpress.org/cor Renders a block patterns list. ```jsx -import { BlockPatternList } from '@wordpress/block-editor'; +import { BlockPatternsList } from '@wordpress/block-editor'; -const MyBlockPatternList = () => ( - ( + { if ( showTooltip ) { return { children }; @@ -35,11 +40,11 @@ const WithToolTip = ( { showTooltip, title, children } ) => { }; function BlockPattern( { + id, isDraggable, pattern, onClick, onHover, - composite, showTooltip, } ) { const [ isDragging, setIsDragging ] = useState( false ); @@ -78,17 +83,27 @@ function BlockPattern( { title={ pattern.title } > + } + id={ id } onClick={ () => { onClick( pattern, blocks ); onHover?.( null ); @@ -100,10 +115,6 @@ function BlockPattern( { onHover?.( pattern ); } } onMouseLeave={ () => onHover?.( null ) } - aria-label={ pattern.title } - aria-describedby={ - pattern.description ? descriptionId : undefined - } > { + // We reset the active composite item whenever the + // available patterns change, to make sure that + // focus is put back to the start. + setActiveId( undefined ); + }, [ setActiveId, shownPatterns, blockPatterns ] ); + return ( ) : ( @@ -191,4 +211,4 @@ function BlockPatternList( ); } -export default forwardRef( BlockPatternList ); +export default forwardRef( BlockPatternsList ); diff --git a/packages/block-editor/src/components/block-patterns-list/style.scss b/packages/block-editor/src/components/block-patterns-list/style.scss index e3b38deff5ef7..8009dfbcce1f2 100644 --- a/packages/block-editor/src/components/block-patterns-list/style.scss +++ b/packages/block-editor/src/components/block-patterns-list/style.scss @@ -18,6 +18,13 @@ .block-editor-block-patterns-list__item { height: 100%; + // This is derived from the top padding set on + // `.block-editor-block-patterns-explorer__list` + scroll-margin-top: $grid-unit-30; + // This is derived from the bottom padding set on + // `.block-editor-block-patterns-explorer__list` and + // the bottom margin set on `...__list-item` above + scroll-margin-bottom: ($grid-unit-40 + $grid-unit-30); .block-editor-block-preview__container { display: flex; diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js index bb8e00d0c11db..4d43865a7f83a 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js @@ -20,7 +20,7 @@ import { store as keyboardShortcutsStore, __unstableUseShortcutEventMatch, } from '@wordpress/keyboard-shortcuts'; -import { pipe, useCopyToClipboard } from '@wordpress/compose'; +import { pipe, useCopyToClipboard, useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -47,6 +47,38 @@ function CopyMenuItem( { blocks, onCopy, label } ) { return { copyMenuItemLabel }; } +function ParentSelectorMenuItem( { parentClientId, parentBlockType } ) { + const isSmallViewport = useViewportMatch( 'medium', '<' ); + const { selectBlock } = useDispatch( blockEditorStore ); + + // Allows highlighting the parent block outline when focusing or hovering + // the parent block selector within the child. + const menuItemRef = useRef(); + const gesturesProps = useShowHoveredOrFocusedGestures( { + ref: menuItemRef, + highlightParent: true, + } ); + + if ( ! isSmallViewport ) { + return null; + } + + return ( + } + onClick={ () => selectBlock( parentClientId ) } + > + { sprintf( + /* translators: %s: Name of the block's parent. */ + __( 'Select parent block (%s)' ), + parentBlockType.title + ) } + + ); +} + export function BlockSettingsDropdown( { block, clientIds, @@ -132,8 +164,6 @@ export function BlockSettingsDropdown( { }; }, [] ); const isMatch = __unstableUseShortcutEventMatch(); - - const { selectBlock } = useDispatch( blockEditorStore ); const hasSelectedBlocks = selectedBlockClientIds.length > 0; const updateSelectionAfterDuplicate = useCallback( @@ -175,14 +205,6 @@ export function BlockSettingsDropdown( { const removeBlockLabel = count === 1 ? __( 'Delete' ) : __( 'Delete blocks' ); - // Allows highlighting the parent block outline when focusing or hovering - // the parent block selector within the child. - const selectParentButtonRef = useRef(); - const showParentOutlineGestures = useShowHoveredOrFocusedGestures( { - ref: selectParentButtonRef, - highlightParent: true, - } ); - // This can occur when the selected block (the parent) // displays child blocks within a List View. const parentBlockIsSelected = @@ -297,30 +319,12 @@ export function BlockSettingsDropdown( { /> { ! parentBlockIsSelected && !! firstParentClientId && ( - - } - onClick={ () => - selectBlock( - firstParentClientId - ) + - { sprintf( - /* translators: %s: Name of the block's parent. */ - __( - 'Select parent block (%s)' - ), - parentBlockType.title - ) } - + parentBlockType={ parentBlockType } + /> ) } { count === 1 && ( { const [ showDetails, setShowDetails ] = useState( false ); - const { isSelected, innerBlocks } = useSelect( - ( select ) => { - const { getBlock, isBlockSelected } = select( blockEditorStore ); - return { - innerBlocks: getBlock( clientId )?.innerBlocks || [], - isSelected: isBlockSelected( clientId ), - }; - }, + const isSelected = useSelect( + ( select ) => select( blockEditorStore ).isBlockSelected( clientId ), [ clientId ] ); - const { replaceBlocks } = useDispatch( blockEditorStore ); + + // We rely on the logic related to the Group/Ungroup buttons used in the block options to + // determine whether to use the Ungroup action. + const convertToGroupButtonProps = useConvertToGroupButtonProps( [ + clientId, + ] ); + const { isUngroupable } = convertToGroupButtonProps; + const convertToGroupButtons = useConvertToGroupButtons( { + ...convertToGroupButtonProps, + } ); + const onUngroup = convertToGroupButtons.ungroup.onSelect; const { isUnsupportedBlockEditorSupported, canEnableUnsupportedBlockEditor, } = useUnsupportedBlockEditor( clientId ); - const onUngroup = () => { - if ( ! innerBlocks.length ) { - return; - } - - replaceBlocks( clientId, innerBlocks ); - }; - - let description; - // When UBE can't be used, the description mentions using the web browser to edit the block. + /* translators: Warning related to having blocks deeply nested. %d: The deepest nesting level. */ + const descriptionFormat = __( + 'Blocks nested deeper than %d levels may not render properly in the mobile editor.' + ); + let description = sprintf( descriptionFormat, MAX_NESTING_DEPTH ); if ( ! isUnsupportedBlockEditorSupported && ! canEnableUnsupportedBlockEditor ) { - /* translators: Warning related to having blocks deeply nested. %d: The deepest nesting level. */ - const descriptionFormat = __( - 'Blocks nested deeper than %d levels may not render properly in the mobile editor. For this reason, we recommend flattening the content by ungrouping the block or editing the block using your web browser.' - ); - description = sprintf( descriptionFormat, MAX_NESTING_DEPTH ); + // When UBE can't be used, the description mentions using the web browser to edit the block. + description += + ' ' + + /* translators: Recommendation included in a warning related to having blocks deeply nested. */ + __( + 'For this reason, we recommend editing the block using your web browser.' + ); } // Otherwise, the description mentions using the web editor (i.e. UBE). else { - /* translators: Warning related to having blocks deeply nested. %d: The deepest nesting level. */ - const descriptionFormat = __( - 'Blocks nested deeper than %d levels may not render properly in the mobile editor. For this reason, we recommend flattening the content by ungrouping the block or editing the block using the web editor.' - ); - description = sprintf( descriptionFormat, MAX_NESTING_DEPTH ); + description += + ' ' + + /* translators: Recommendation included in a warning related to having blocks deeply nested. */ + __( + 'For this reason, we recommend editing the block using the web editor.' + ); + } + // If the block can be flattened, we also suggest to ungroup the block. + if ( isUngroupable ) { + description += + ' ' + + /* translators: Alternative option included in a warning related to having blocks deeply nested. */ + __( + 'Alternatively, you can flatten the content by ungrouping the block.' + ); } return ( @@ -88,9 +105,16 @@ const WarningMaxDepthExceeded = ( { clientId } ) => { onCloseSheet={ () => setShowDetails( false ) } title={ __( 'Deeply nested block' ) } description={ description } - customActions={ [ - { label: __( 'Ungroup block' ), onPress: onUngroup }, - ] } + customActions={ + isUngroupable + ? [ + { + label: __( 'Ungroup block' ), + onPress: onUngroup, + }, + ] + : EMPTY_ARRAY + } /> diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js index 76864a6a00ccc..9e5b6373e54d8 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js @@ -22,7 +22,7 @@ import { * Internal dependencies */ import usePatternsState from '../hooks/use-patterns-state'; -import BlockPatternList from '../../block-patterns-list'; +import BlockPatternsList from '../../block-patterns-list'; import usePatternsPaging from '../hooks/use-patterns-paging'; import { PatternsFilter } from './patterns-filter'; import { usePatternCategories } from './use-pattern-categories'; @@ -159,7 +159,7 @@ export function PatternCategoryPreviews( { { currentCategoryPatterns.length > 0 && ( - + + { + if ( nextValue === '' ) { + props.setAttributes( { + connections: undefined, + [ attributeName ]: undefined, + placeholder: undefined, + } ); + } else { + props.setAttributes( { + connections: { + attributes: { + // The attributeName will be either `content` or `url`. + [ attributeName ]: { + // Source will be variable, could be post_meta, user_meta, term_meta, etc. + // Could even be a custom source like a social media attribute. + source: 'meta_fields', + value: nextValue, + }, + }, + }, + [ attributeName ]: undefined, + placeholder: sprintf( + 'This content will be replaced on the frontend by the value of "%s" custom field.', + nextValue + ), + } ); + } + } } + /> + + + ); +} + /** * Override the default edit UI to include a new block inspector control for * assigning a connection to blocks that has support for connections. @@ -46,7 +105,6 @@ function addAttribute( settings ) { */ const withCustomFieldsControls = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { - const blockEditingMode = useBlockEditingMode(); const hasCustomFieldsSupport = hasBlockSupport( props.name, '__experimentalConnections', @@ -56,72 +114,17 @@ const withCustomFieldsControls = createHigherOrderComponent( ( BlockEdit ) => { // Check if the current block is a paragraph or image block. // Currently, only these two blocks are supported. if ( ! [ 'core/paragraph', 'core/image' ].includes( props.name ) ) { - return ; + return ; } - // If the block is a paragraph or image block, we need to know which - // attribute to use for the connection. Only the `content` attribute - // of the paragraph block and the `url` attribute of the image block are supported. - let attributeName; - if ( props.name === 'core/paragraph' ) attributeName = 'content'; - if ( props.name === 'core/image' ) attributeName = 'url'; - - if ( hasCustomFieldsSupport && props.isSelected ) { - return ( - <> - - { blockEditingMode === 'default' && ( - - - { - if ( nextValue === '' ) { - props.setAttributes( { - connections: undefined, - [ attributeName ]: undefined, - placeholder: undefined, - } ); - } else { - props.setAttributes( { - connections: { - attributes: { - // The attributeName will be either `content` or `url`. - [ attributeName ]: { - // Source will be variable, could be post_meta, user_meta, term_meta, etc. - // Could even be a custom source like a social media attribute. - source: 'meta_fields', - value: nextValue, - }, - }, - }, - [ attributeName ]: undefined, - placeholder: sprintf( - 'This content will be replaced on the frontend by the value of "%s" custom field.', - nextValue - ), - } ); - } - } } - /> - - - ) } - - ); - } - - return ; + return ( + <> + + { hasCustomFieldsSupport && props.isSelected && ( + + ) } + + ); }; }, 'withCustomFieldsControls' ); diff --git a/packages/block-editor/src/utils/test/transform-styles.js b/packages/block-editor/src/utils/test/transform-styles.js index f162a0b2f6048..38eaa1947d2a8 100644 --- a/packages/block-editor/src/utils/test/transform-styles.js +++ b/packages/block-editor/src/utils/test/transform-styles.js @@ -4,6 +4,55 @@ import transformStyles from '../transform-styles'; describe( 'transformStyles', () => { + describe( 'error handling', () => { + beforeEach( () => { + // Intentionally suppress the expected console errors and warnings to reduce + // noise in the test output. + jest.spyOn( console, 'warn' ).mockImplementation( jest.fn() ); + } ); + + it( 'should not throw error in case of invalid css', () => { + const run = () => + transformStyles( + [ + { + css: 'h1 { color: red;', // invalid CSS + }, + ], + '.my-namespace' + ); + + expect( run ).not.toThrow(); + expect( console ).toHaveWarned(); + } ); + + it( 'should warn invalid css in the console', () => { + const run = () => + transformStyles( + [ + { + css: 'h1 { color: red; }', // valid CSS + }, + { + css: 'h1 { color: red;', // invalid CSS + }, + ], + '.my-namespace' + ); + + const [ validCSS, invalidCSS ] = run(); + + expect( validCSS ).toBe( '.my-namespace h1 { color: red; }' ); + expect( invalidCSS ).toBe( null ); + + expect( console ).toHaveWarnedWith( + 'wp.blockEditor.transformStyles Failed to transform CSS.', + ':1:1: Unclosed block\n> 1 | h1 { color: red;\n | ^' + // ^^^^ In PostCSS, a tab is equal four spaces + ); + } ); + } ); + describe( 'selector wrap', () => { it( 'should wrap regular selectors', () => { const input = `h1 { color: red; }`; diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js index 8f5e1702307a4..4da6f6ce23795 100644 --- a/packages/block-editor/src/utils/transform-styles/index.js +++ b/packages/block-editor/src/utils/transform-styles/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import postcss from 'postcss'; +import postcss, { CssSyntaxError } from 'postcss'; import wrap from 'postcss-prefixwrap'; import rebaseUrl from 'postcss-urlrebase'; @@ -19,18 +19,36 @@ import rebaseUrl from 'postcss-urlrebase'; */ const transformStyles = ( styles, wrapperSelector = '' ) => { return styles.map( ( { css, ignoredSelectors = [], baseURL } ) => { - return postcss( - [ - wrapperSelector && - wrap( wrapperSelector, { - ignoredSelectors: [ - ...ignoredSelectors, - wrapperSelector, - ], - } ), - baseURL && rebaseUrl( { rootUrl: baseURL } ), - ].filter( Boolean ) - ).process( css, {} ).css; // use sync PostCSS API + try { + return postcss( + [ + wrapperSelector && + wrap( wrapperSelector, { + ignoredSelectors: [ + ...ignoredSelectors, + wrapperSelector, + ], + } ), + baseURL && rebaseUrl( { rootUrl: baseURL } ), + ].filter( Boolean ) + ).process( css, {} ).css; // use sync PostCSS API + } catch ( error ) { + if ( error instanceof CssSyntaxError ) { + // eslint-disable-next-line no-console + console.warn( + 'wp.blockEditor.transformStyles Failed to transform CSS.', + error.message + '\n' + error.showSourceCode( false ) + ); + } else { + // eslint-disable-next-line no-console + console.warn( + 'wp.blockEditor.transformStyles Failed to transform CSS.', + error + ); + } + + return null; + } } ); }; diff --git a/packages/block-library/src/cover/edit/index.js b/packages/block-library/src/cover/edit/index.js index ab49d58b766b0..8c5488584094c 100644 --- a/packages/block-library/src/cover/edit/index.js +++ b/packages/block-library/src/cover/edit/index.js @@ -335,6 +335,7 @@ function CoverEdit( { templateInsertUpdatesSelection: true, allowedBlocks, templateLock, + dropZoneElement: ref.current, } ); diff --git a/packages/block-library/src/cover/test/edit.js b/packages/block-library/src/cover/test/edit.js index d1febe3c6fa11..879b77636255a 100644 --- a/packages/block-library/src/cover/test/edit.js +++ b/packages/block-library/src/cover/test/edit.js @@ -53,7 +53,7 @@ async function createAndSelectBlock() { ); await userEvent.click( screen.getByRole( 'button', { - name: 'Select Cover', + name: 'Select parent block: Cover', } ) ); } diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index 025a8bf4f6c93..2e668674e8530 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -123,7 +123,7 @@ export default function PostTemplateEdit( { slug: templateSlug.replace( 'category-', '' ), } ); const query = { - offset: perPage ? perPage + offset : 0, + offset: offset || 0, order, orderby: orderBy, }; diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index eb6e595e304ec..f92bd43f80661 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,9 +2,22 @@ ## Unreleased +### Enhancements + +- `Button`: Add focus rings to focusable disabled buttons ([#56383](https://github.com/WordPress/gutenberg/pull/56383)). + ### Experimental - `Tabs`: Memoize and expose the component context ([#56224](https://github.com/WordPress/gutenberg/pull/56224)). +- `DropdownMenuV2`: Design tweaks ([#56041](https://github.com/WordPress/gutenberg/pull/56041)) + +### Internal + +- `Slot`: add `style` prop to `bubblesVirtually` version ([#56428](https://github.com/WordPress/gutenberg/pull/56428)) + +### Internal + +- Introduce experimental new version of `CustomSelectControl` based on `ariakit` ([#55790](https://github.com/WordPress/gutenberg/pull/55790)) ## 25.12.0 (2023-11-16) diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 03273056cfa17..4b11d9169a090 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -129,8 +129,11 @@ background: transparent; transform: none; opacity: 1; - box-shadow: none; - outline: none; + + &:not(:focus) { + box-shadow: none; + outline: none; + } } } diff --git a/packages/components/src/custom-select-control-v2/README.md b/packages/components/src/custom-select-control-v2/README.md new file mode 100644 index 0000000000000..3cd9c3f8534e7 --- /dev/null +++ b/packages/components/src/custom-select-control-v2/README.md @@ -0,0 +1,73 @@ +
+This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. +
+ +### `CustomSelect` + +Used to render a customizable select control component. + +#### Props + +The component accepts the following props: + +##### `children`: `React.ReactNode` + +The child elements. This should be composed of CustomSelect.Item components. + +- Required: yes + +##### `defaultValue`: `string` + +An optional default value for the control. If left `undefined`, the first non-disabled item will be used. + +- Required: no + +##### `label`: `string` + +Label for the control. + +- Required: yes + +##### `onChange`: `( newValue: string ) => void` + +A function that receives the new value of the input. + +- Required: no + +##### `renderSelectedValue`: `( selectValue: string ) => React.ReactNode` + +Can be used to render select UI with custom styled values. + +- Required: no + +##### `size`: `'default' | 'large'` + +The size of the control. + +- Required: no + +##### `value`: `string` + +Can be used to externally control the value of the control. + +- Required: no + +### `CustomSelectItem` + +Used to render a select item. + +#### Props + +The component accepts the following props: + +##### `value`: `string` + +The value of the select item. This will be used as the children if children are left `undefined`. + +- Required: yes + +##### `children`: `React.ReactNode` + +The children to display for each select item. The `value` will be used if left `undefined`. + +- Required: no diff --git a/packages/components/src/custom-select-control-v2/index.tsx b/packages/components/src/custom-select-control-v2/index.tsx new file mode 100644 index 0000000000000..88231078fa8d5 --- /dev/null +++ b/packages/components/src/custom-select-control-v2/index.tsx @@ -0,0 +1,99 @@ +/** + * External dependencies + */ +// eslint-disable-next-line no-restricted-imports +import * as Ariakit from '@ariakit/react'; +/** + * WordPress dependencies + */ +import { createContext, useContext } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import * as Styled from './styles'; +import type { + CustomSelectProps, + CustomSelectItemProps, + CustomSelectContext as CustomSelectContextType, +} from './types'; + +export const CustomSelectContext = + createContext< CustomSelectContextType >( undefined ); + +function defaultRenderSelectedValue( value: CustomSelectProps[ 'value' ] ) { + const isValueEmpty = Array.isArray( value ) + ? value.length === 0 + : value === undefined || value === null; + + if ( isValueEmpty ) { + return __( 'Select an item' ); + } + + if ( Array.isArray( value ) ) { + return value.length === 1 + ? value[ 0 ] + : // translators: %s: number of items selected (it will always be 2 or more items) + sprintf( __( '%s items selected' ), value.length ); + } + + return value; +} + +export function CustomSelect( props: CustomSelectProps ) { + const { + children, + defaultValue, + label, + onChange, + size = 'default', + value, + renderSelectedValue = defaultRenderSelectedValue, + } = props; + + const store = Ariakit.useSelectStore( { + setValue: ( nextValue ) => onChange?.( nextValue ), + defaultValue, + value, + } ); + + const { value: currentValue } = store.useState(); + + return ( + <> + + { label } + + + { renderSelectedValue( currentValue ) } + + + + + { children } + + + + ); +} + +export function CustomSelectItem( { + children, + ...props +}: CustomSelectItemProps ) { + const customSelectContext = useContext( CustomSelectContext ); + return ( + + { children ?? props.value } + + + ); +} diff --git a/packages/components/src/custom-select-control-v2/stories/index.story.tsx b/packages/components/src/custom-select-control-v2/stories/index.story.tsx new file mode 100644 index 0000000000000..2c7ae3507046b --- /dev/null +++ b/packages/components/src/custom-select-control-v2/stories/index.story.tsx @@ -0,0 +1,149 @@ +/** + * External dependencies + */ +import type { Meta, StoryFn } from '@storybook/react'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { CustomSelect, CustomSelectItem } from '..'; + +const meta: Meta< typeof CustomSelect > = { + title: 'Components (Experimental)/CustomSelectControl v2', + component: CustomSelect, + subcomponents: { + // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 + CustomSelectItem, + }, + argTypes: { + children: { control: { type: null } }, + renderSelectedValue: { control: { type: null } }, + value: { control: { type: null } }, + }, + parameters: { + actions: { argTypesRegex: '^on.*' }, + controls: { expanded: true }, + docs: { + canvas: { sourceState: 'shown' }, + source: { excludeDecorators: true }, + }, + }, + decorators: [ + ( Story ) => ( +
+ +
+ ), + ], +}; +export default meta; + +const Template: StoryFn< typeof CustomSelect > = ( props ) => { + return ; +}; + +const ControlledTemplate: StoryFn< typeof CustomSelect > = ( props ) => { + const [ value, setValue ] = useState< string | string[] >(); + return ( + { + setValue( nextValue ); + props.onChange?.( nextValue ); + } } + value={ value } + /> + ); +}; + +export const Default = Template.bind( {} ); +Default.args = { + label: 'Label', + children: ( + <> + + Small + + + Something bigger + + + ), +}; + +/** + * Multiple selection can be enabled by using an array for the `value` and + * `defaultValue` props. The argument of the `onChange` function will also + * change accordingly. + */ +export const MultiSelect = Template.bind( {} ); +MultiSelect.args = { + defaultValue: [ 'lavender', 'tangerine' ], + label: 'Select Colors', + renderSelectedValue: ( currentValue: string | string[] ) => { + if ( ! Array.isArray( currentValue ) ) { + return currentValue; + } + if ( currentValue.length === 0 ) return 'No colors selected'; + if ( currentValue.length === 1 ) return currentValue[ 0 ]; + return `${ currentValue.length } colors selected`; + }, + children: ( + <> + { [ + 'amber', + 'aquamarine', + 'flamingo pink', + 'lavender', + 'maroon', + 'tangerine', + ].map( ( item ) => ( + + { item } + + ) ) } + + ), +}; + +const renderControlledValue = ( gravatar: string | string[] ) => { + const avatar = `https://gravatar.com/avatar?d=${ gravatar }`; + return ( +
+ + { gravatar } +
+ ); +}; + +export const Controlled = ControlledTemplate.bind( {} ); +Controlled.args = { + label: 'Default Gravatars', + renderSelectedValue: renderControlledValue, + children: ( + <> + { [ 'mystery-person', 'identicon', 'wavatar', 'retro' ].map( + ( option ) => ( + + { renderControlledValue( option ) } + + ) + ) } + + ), +}; diff --git a/packages/components/src/custom-select-control-v2/styles.ts b/packages/components/src/custom-select-control-v2/styles.ts new file mode 100644 index 0000000000000..c04f6ac32e5ff --- /dev/null +++ b/packages/components/src/custom-select-control-v2/styles.ts @@ -0,0 +1,76 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; +// eslint-disable-next-line no-restricted-imports +import * as Ariakit from '@ariakit/react'; + +/** + * Internal dependencies + */ +import { COLORS } from '../utils'; +import { space } from '../utils/space'; +import type { CustomSelectProps } from './types'; + +export const CustomSelectLabel = styled( Ariakit.SelectLabel )` + font-size: 11px; + font-weight: 500; + line-height: 1.4; + text-transform: uppercase; + margin-bottom: ${ space( 2 ) }; +`; + +const inputHeights = { + default: 40, + small: 24, +}; + +export const CustomSelectButton = styled( Ariakit.Select, { + // Do not forward `hasCustomRenderProp` to the underlying Ariakit.Select component + shouldForwardProp: ( prop ) => prop !== 'hasCustomRenderProp', +} )( ( { + size, + hasCustomRenderProp, +}: { + size: NonNullable< CustomSelectProps[ 'size' ] >; + hasCustomRenderProp: boolean; +} ) => { + const isSmallSize = size === 'small' && ! hasCustomRenderProp; + const heightProperty = hasCustomRenderProp ? 'minHeight' : 'height'; + + return { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: COLORS.white, + border: `1px solid ${ COLORS.gray[ 600 ] }`, + borderRadius: space( 0.5 ), + cursor: 'pointer', + width: '100%', + [ heightProperty ]: `${ inputHeights[ size ] }px`, + padding: isSmallSize ? space( 2 ) : space( 4 ), + fontSize: isSmallSize ? '11px' : '13px', + '&[data-focus-visible]': { + outlineStyle: 'solid', + }, + '&[aria-expanded="true"]': { + outlineStyle: `1.5px solid ${ COLORS.theme.accent }`, + }, + }; +} ); + +export const CustomSelectPopover = styled( Ariakit.SelectPopover )` + border-radius: ${ space( 0.5 ) }; + background: ${ COLORS.white }; + border: 1px solid ${ COLORS.gray[ 900 ] }; +`; + +export const CustomSelectItem = styled( Ariakit.SelectItem )` + display: flex; + align-items: center; + justify-content: space-between; + padding: ${ space( 2 ) }; + &[data-active-item] { + background-color: ${ COLORS.gray[ 300 ] }; + } +`; diff --git a/packages/components/src/custom-select-control-v2/types.ts b/packages/components/src/custom-select-control-v2/types.ts new file mode 100644 index 0000000000000..2aecc1d4746f5 --- /dev/null +++ b/packages/components/src/custom-select-control-v2/types.ts @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +// eslint-disable-next-line no-restricted-imports +import type * as Ariakit from '@ariakit/react'; + +export type CustomSelectContext = + | { + /** + * The store object returned by Ariakit's `useSelectStore` hook. + */ + store: Ariakit.SelectStore; + } + | undefined; + +export type CustomSelectProps = { + /** + * The child elements. This should be composed of CustomSelectItem components. + */ + children: React.ReactNode; + /** + * An optional default value for the control. If left `undefined`, the first + * non-disabled item will be used. + */ + defaultValue?: string | string[]; + /** + * Label for the control. + */ + label: string; + /** + * A function that receives the new value of the input. + */ + onChange?: ( newValue: string | string[] ) => void; + /** + * Can be used to render select UI with custom styled values. + */ + renderSelectedValue?: ( + selectedValue: string | string[] + ) => React.ReactNode; + /** + * The size of the control. + * + * @default 'default' + */ + size?: 'default' | 'small'; + /** + * Can be used to externally control the value of the control. + */ + value?: string | string[]; +}; + +export type CustomSelectItemProps = { + /** + * The value of the select item. This will be used as the children if + * children are left `undefined`. + */ + value: string; + /** + * The children to display for each select item. The `value` will be + * used if left `undefined`. + */ + children?: React.ReactNode; +}; diff --git a/packages/components/src/dropdown-menu-v2-ariakit/README.md b/packages/components/src/dropdown-menu-v2-ariakit/README.md index f74098efff410..2902b54116976 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/README.md +++ b/packages/components/src/dropdown-menu-v2-ariakit/README.md @@ -284,9 +284,9 @@ Event handler called when the checked radio menu item changes. - Required: no -### `DropdownMenuGroup` +### `DropdownMenuItemLabel` -Used to group menu items. +Used to render the menu item's label. #### Props @@ -294,13 +294,27 @@ The component accepts the following props: ##### `children`: `React.ReactNode` -The contents of the group. +The label contents. + +- Required: yes + +### `DropdownMenuItemHelpText` + +Used to render the menu item's help text. + +#### Props + +The component accepts the following props: + +##### `children`: `React.ReactNode` + +The help text contents. - Required: yes -### `DropdownMenuGroupLabel` +### `DropdownMenuGroup` -Used to render a group label. +Used to group menu items. #### Props diff --git a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx index 10b93d8c552c1..37d4a1f9cfcc5 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx +++ b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx @@ -30,7 +30,6 @@ import type { DropdownMenuContext as DropdownMenuContextType, DropdownMenuProps, DropdownMenuGroupProps, - DropdownMenuGroupLabelProps, DropdownMenuItemProps, DropdownMenuCheckboxItemProps, DropdownMenuRadioItemProps, @@ -55,16 +54,23 @@ export const DropdownMenuItem = forwardRef< - { prefix && ( - { prefix } - ) } - { children } - { suffix && ( - { suffix } - ) } + { prefix } + + + + { children } + + + { suffix && ( + + { suffix } + + ) } + ); } ); @@ -82,20 +88,30 @@ export const DropdownMenuCheckboxItem = forwardRef< } + // Override some ariakit inline styles + style={ { width: 'auto', height: 'auto' } } > - { children } - { suffix && ( - { suffix } - ) } + + + { children } + + + { suffix && ( + + { suffix } + + ) } + ); } ); @@ -119,17 +135,30 @@ export const DropdownMenuRadioItem = forwardRef< } + // Override some ariakit inline styles + style={ { width: 'auto', height: 'auto' } } > - { children } - { suffix } + + + + { children } + + + { suffix && ( + + { suffix } + + ) } + ); } ); @@ -148,20 +177,6 @@ export const DropdownMenuGroup = forwardRef< ); } ); -export const DropdownMenuGroupLabel = forwardRef< - HTMLDivElement, - WordPressComponentProps< DropdownMenuGroupLabelProps, 'div', false > ->( function DropdownMenuGroupLabel( props, ref ) { - const dropdownMenuContext = useContext( DropdownMenuContext ); - return ( - - ); -} ); - const UnconnectedDropdownMenu = ( props: WordPressComponentProps< DropdownMenuProps, 'div', false >, ref: React.ForwardedRef< HTMLDivElement > @@ -280,12 +295,16 @@ const UnconnectedDropdownMenu = ( dropdownMenuStore.parent ? cloneElement( trigger, { // Add submenu arrow, unless a `suffix` is explicitly specified - suffix: trigger.props.suffix ?? ( -