diff --git a/packages/block-editor/src/components/block-settings-menu-controls/index.js b/packages/block-editor/src/components/block-settings-menu-controls/index.js
index 53b3835fad1a1..cb4be30d3b286 100644
--- a/packages/block-editor/src/components/block-settings-menu-controls/index.js
+++ b/packages/block-editor/src/components/block-settings-menu-controls/index.js
@@ -72,8 +72,8 @@ const BlockSettingsMenuControlsSlot = ( {
}
return (
-
- { showConvertToGroupButton && (
+ <>
+ { /* { showConvertToGroupButton && (
- ) }
+ ) } */ }
{ fills }
- { fillProps?.canMove && ! fillProps?.onlyBlock && (
+ { /* { fillProps?.canMove && ! fillProps?.onlyBlock && (
- ) }
-
+ ) } */ }
+ >
);
} }
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..bdc7d50efa8e4 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
@@ -6,7 +6,11 @@ import {
serialize,
store as blocksStore,
} from '@wordpress/blocks';
-import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
+import {
+ // DropdownMenu, MenuGroup, MenuItem
+ Icon,
+ privateApis as componentsPrivateApis,
+} from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { moreVertical } from '@wordpress/icons';
import {
@@ -34,6 +38,10 @@ import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';
import { useShowHoveredOrFocusedGestures } from '../block-toolbar/utils';
+const { DropdownMenuV2Ariakit, DropdownMenuItemV2Ariakit } = unlock(
+ componentsPrivateApis
+);
+
const POPOVER_PROPS = {
className: 'block-editor-block-settings-menu__popover',
placement: 'bottom-start',
@@ -44,7 +52,11 @@ function CopyMenuItem( { blocks, onCopy, label } ) {
const copyMenuItemBlocksLabel =
blocks.length > 1 ? __( 'Copy blocks' ) : __( 'Copy' );
const copyMenuItemLabel = label ? label : copyMenuItemBlocksLabel;
- return ;
+ return (
+
+ { copyMenuItemLabel }
+
+ );
}
export function BlockSettingsDropdown( {
@@ -234,153 +246,28 @@ export function BlockSettingsDropdown( {
onMoveTo,
blocks,
} ) => (
- }
>
- { ( { onClose } ) => (
+
+ Test item
+
+ { canCopyStyles && (
<>
-
- <__unstableBlockSettingsMenuFirstItem.Slot
- fillProps={ { onClose } }
- />
- { ! parentBlockIsSelected &&
- !! firstParentClientId && (
-
- }
- onClick={ () =>
- selectBlock(
- firstParentClientId
- )
- }
- >
- { sprintf(
- /* translators: %s: Name of the block's parent. */
- __(
- 'Select parent block (%s)'
- ),
- parentBlockType.title
- ) }
-
- ) }
- { count === 1 && (
-
- ) }
-
- { canDuplicate && (
-
- ) }
- { canInsertDefaultBlock && (
- <>
-
-
- >
- ) }
-
- { canCopyStyles && (
-
-
-
-
- ) }
+
+
+ { __( 'Paste styles' ) }
+
+
- { typeof children === 'function'
- ? children( { onClose } )
- : Children.map( ( child ) =>
- cloneElement( child, { onClose } )
- ) }
- { canRemove && (
-
-
-
- ) }
+
+
+ }
+ hideOnClick={ false }
+ >
+ { __( 'I am a link' ) }
+
>
) }
-
+
+ //
+ // { ( { onClose } ) => (
+ // <>
+ //
+ // <__unstableBlockSettingsMenuFirstItem.Slot
+ // fillProps={ { onClose } }
+ // />
+ // { ! parentBlockIsSelected &&
+ // !! firstParentClientId && (
+ //
+ // }
+ // onClick={ () =>
+ // selectBlock(
+ // firstParentClientId
+ // )
+ // }
+ // >
+ // { sprintf(
+ // /* translators: %s: Name of the block's parent. */
+ // __(
+ // 'Select parent block (%s)'
+ // ),
+ // parentBlockType.title
+ // ) }
+ //
+ // ) }
+ // { count === 1 && (
+ //
+ // ) }
+ //
+ // { canDuplicate && (
+ //
+ // ) }
+ // { canInsertDefaultBlock && (
+ // <>
+ //
+ //
+ // >
+ // ) }
+ //
+ // { canCopyStyles && (
+ //
+ //
+ //
+ //
+ // ) }
+ //
+ // { typeof children === 'function'
+ // ? children( { onClose } )
+ // : Children.map( ( child ) =>
+ // cloneElement( child, { onClose } )
+ // ) }
+ // { canRemove && (
+ //
+ //
+ //
+ // ) }
+ // >
+ // ) }
+ //
) }
);
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx
new file mode 100644
index 0000000000000..f5efa980fc3c5
--- /dev/null
+++ b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx
@@ -0,0 +1,105 @@
+/**
+ * External dependencies
+ */
+// eslint-disable-next-line no-restricted-imports
+import * as Ariakit from '@ariakit/react';
+
+/**
+ * WordPress dependencies
+ */
+import {
+ forwardRef,
+ createContext,
+ useContext,
+ useMemo,
+} from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import {
+ StyledAriakitMenu,
+ StyledAriakitMenuItem,
+ toggleButton,
+} from './styles';
+import { useCx } from '../utils';
+
+export const DropdownMenuContext = createContext<
+ { store: Ariakit.MenuStore } | undefined
+>( undefined );
+
+export interface DropdownMenuItemProps
+ extends Omit< Ariakit.MenuItemProps, 'store' > {}
+
+export const DropdownMenuItem = forwardRef<
+ HTMLDivElement,
+ DropdownMenuItemProps
+>( function DropdownMenuItem( props, ref ) {
+ const dropdownMenuContext = useContext( DropdownMenuContext );
+ return (
+
+ );
+} );
+
+export interface DropdownMenuProps extends Ariakit.MenuButtonProps {
+ trigger: React.ReactNode;
+ children?: React.ReactNode;
+}
+
+export const DropdownMenu = forwardRef< HTMLDivElement, DropdownMenuProps >(
+ function DropdownMenu( { trigger, children, className, ...props }, ref ) {
+ const parentContext = useContext( DropdownMenuContext );
+
+ const dropdownMenuStore = Ariakit.useMenuStore( {
+ parent: parentContext?.store,
+ } );
+
+ const cx = useCx();
+ const menuButtonClassName = useMemo(
+ () => cx( ! dropdownMenuStore.parent && toggleButton, className ),
+ [ cx, dropdownMenuStore.parent, className ]
+ );
+
+ const contextValue = useMemo(
+ () => ( { store: dropdownMenuStore } ),
+ [ dropdownMenuStore ]
+ );
+
+ return (
+ <>
+ { /* Menu trigger */ }
+
+ ) : undefined
+ }
+ >
+ { trigger }
+
+
+
+ { /* Menu popover */ }
+
+
+ { children }
+
+
+ >
+ );
+ }
+);
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx b/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx
new file mode 100644
index 0000000000000..c3e9d479384ee
--- /dev/null
+++ b/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx
@@ -0,0 +1,202 @@
+/**
+ * External dependencies
+ */
+import type { Meta, StoryFn } from '@storybook/react';
+
+/**
+ * WordPress dependencies
+ */
+import { menu } from '@wordpress/icons';
+import { useState, useMemo, useContext } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { DropdownMenu, DropdownMenuItem, DropdownMenuContext } from '..';
+import Icon from '../../icon';
+import Modal from '../../modal';
+import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill';
+
+const meta: Meta< typeof DropdownMenu > = {
+ title: 'Components (Experimental)/DropdownMenu v2 ariakit',
+ component: DropdownMenu,
+ subcomponents: {
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ DropdownMenuItem,
+ },
+ argTypes: {
+ children: { control: { type: null } },
+ },
+ parameters: {
+ actions: { argTypesRegex: '^on.*' },
+ controls: { expanded: true },
+ docs: {
+ canvas: { sourceState: 'shown' },
+ source: { excludeDecorators: true },
+ },
+ },
+ decorators: [
+ // Layout wrapper
+ ( Story ) => (
+
+
+
+ ),
+ ],
+};
+export default meta;
+
+const Template: StoryFn< typeof DropdownMenu > = ( props ) => (
+
+);
+export const Default = Template.bind( {} );
+Default.args = {
+ trigger: ,
+ children: (
+ <>
+ Undo
+ Redo
+
+ Search the Web...
+ Find...
+ Find Next
+ Find Previous
+
+
+ Start Speaking
+ Stop Speaking
+
+ >
+ ),
+};
+
+export const WithModalAsSiblingOfMenu: StoryFn< typeof DropdownMenu > = (
+ props
+) => {
+ const [ isModalOpen, setModalOpen ] = useState( false );
+ return (
+ <>
+
+ setModalOpen( true ) }>
+ Open modal
+
+
+ { isModalOpen && (
+ setModalOpen( false ) }>
+ Modal's contents
+
+
+ ) }
+ >
+ );
+};
+WithModalAsSiblingOfMenu.args = {
+ trigger: ,
+};
+
+export const WithModalAsSiblingOfMenuItem: StoryFn< typeof DropdownMenu > = (
+ props
+) => {
+ const [ isModalOpen, setModalOpen ] = useState( false );
+ return (
+
+ setModalOpen( true ) }
+ >
+ Open modal
+
+ { isModalOpen && (
+ setModalOpen( false ) }>
+ Yo!
+
+
+ ) }
+
+ );
+};
+WithModalAsSiblingOfMenuItem.args = {
+ trigger: ,
+};
+
+const ExampleSlotFill = createSlotFill( 'Example' );
+
+const Slot = () => {
+ const dropdownMenuContext = useContext( DropdownMenuContext );
+
+ // Forwarding the content of the slot so that it can be used by the fill
+ const fillProps = useMemo(
+ () => ( {
+ forwardedContext: [
+ [
+ DropdownMenuContext.Provider,
+ { value: dropdownMenuContext },
+ ],
+ ],
+ } ),
+ [ dropdownMenuContext ]
+ );
+
+ return ;
+};
+
+type ForwardedContextTuple< P = {} > = [
+ React.ComponentType< React.PropsWithChildren< P > >,
+ P,
+];
+
+const Fill = ( { children }: { children: React.ReactNode } ) => {
+ const innerMarkup = <>{ children }>;
+
+ return (
+
+ { ( fillProps: { forwardedContext?: ForwardedContextTuple[] } ) => {
+ const { forwardedContext = [] } = fillProps;
+
+ return forwardedContext.reduce(
+ ( inner: JSX.Element, [ Provider, props ] ) => (
+ { inner }
+ ),
+ innerMarkup
+ );
+ } }
+
+ );
+};
+
+export const AddItemsViaSlotFill: StoryFn< typeof DropdownMenu > = (
+ props
+) => {
+ return (
+
+
+ Item
+
+
+
+
+
+
+
+ Item from fill
+
+
+
+ Test
+
+
+
+
+ );
+};
+AddItemsViaSlotFill.args = {
+ trigger: ,
+};
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/styles.ts b/packages/components/src/dropdown-menu-v2-ariakit/styles.ts
new file mode 100644
index 0000000000000..71dc6dc0fc563
--- /dev/null
+++ b/packages/components/src/dropdown-menu-v2-ariakit/styles.ts
@@ -0,0 +1,151 @@
+/**
+ * External dependencies
+ */
+// eslint-disable-next-line no-restricted-imports
+import * as Ariakit from '@ariakit/react';
+import styled from '@emotion/styled';
+import { css } from '@emotion/react';
+
+export const toggleButton = css`
+ display: flex;
+ height: 2.5rem;
+ touch-action: none;
+ user-select: none;
+ align-items: center;
+ justify-content: center;
+ gap: 0.25rem;
+ white-space: nowrap;
+ border-radius: 0.5rem;
+ border-style: none;
+ background-color: hsl( 204 20% 100% );
+ padding-left: 1rem;
+ padding-right: 1rem;
+ font-size: 1rem;
+ line-height: 1.5rem;
+ color: hsl( 204 10% 10% );
+ text-decoration-line: none;
+ outline-width: 2px;
+ outline-offset: 2px;
+ outline-color: hsl( 204 100% 40% );
+ box-shadow:
+ inset 0 0 0 1px rgba( 0, 0, 0, 0.1 ),
+ inset 0 -1px 0 rgba( 0, 0, 0, 0.1 ),
+ 0 1px 1px rgba( 0, 0, 0, 0.1 );
+ font-weight: 500;
+
+ &:hover {
+ background-color: hsl( 204 20% 96% );
+ }
+
+ &[aria-disabled='true'] {
+ opacity: 0.5;
+ }
+
+ &[aria-expanded='true'] {
+ background-color: hsl( 204 20% 96% );
+ }
+
+ &[data-focus-visible] {
+ outline-style: solid;
+ }
+
+ &:active,
+ &[data-active] {
+ transform: scale( 0.98 );
+ }
+
+ &:active[aria-expanded='true'],
+ &[data-active][aria-expanded='true'] {
+ transform: scale( 1 );
+ }
+
+ @media ( min-width: 640px ) {
+ gap: 0.5rem;
+ }
+`;
+
+// :is(.dark .button) {
+// background-color: hsl(204 20% 100% / 0.05);
+// color: hsl(204 20% 100%);
+// box-shadow:
+// inset 0 0 0 1px rgba(255, 255, 255, 0.1),
+// inset 0 -1px 0 1px rgba(0, 0, 0, 0.2),
+// inset 0 1px 0 rgba(255, 255, 255, 0.05);
+// }
+
+// :is(.dark .button:hover) {
+// background-color: hsl(204 20% 100% / 0.1);
+// }
+
+// :is(.dark .button)[aria-expanded="true"] {
+// background-color: hsl(204 20% 100% / 0.1);
+// }
+
+export const StyledAriakitMenu = styled( Ariakit.Menu )`
+ position: relative;
+ z-index: 50;
+ display: flex;
+ max-height: var( --popover-available-height );
+ min-width: 180px;
+ flex-direction: column;
+ overscroll-behavior: contain;
+ border-radius: 0.5rem;
+ border-width: 1px;
+ border-style: solid;
+ border-color: hsl( 204 20% 88% );
+ background-color: hsl( 204 20% 100% );
+ padding: 0.5rem;
+ color: hsl( 204 10% 10% );
+ box-shadow:
+ 0 10px 15px -3px rgb( 0 0 0 / 0.1 ),
+ 0 4px 6px -4px rgb( 0 0 0 / 0.1 );
+ outline: none !important;
+ overflow: visible;
+`;
+
+// :is(.dark .menu) {
+// border-color: hsl(204 3% 26%);
+// background-color: hsl(204 3% 18%);
+// color: hsl(204 20% 100%);
+// box-shadow:
+// 0 10px 15px -3px rgb(0 0 0 / 0.25),
+// 0 4px 6px -4px rgb(0 0 0 / 0.1);
+// }
+
+export const StyledAriakitMenuItem = styled( Ariakit.MenuItem )`
+ display: flex;
+ cursor: default;
+ scroll-margin: 0.5rem;
+ align-items: center;
+ gap: 0.5rem;
+ border-radius: 0.25rem;
+ padding: 0.5rem;
+ outline: none !important;
+
+ &[aria-disabled='true'] {
+ opacity: 0.25;
+ }
+
+ &[data-active-item] {
+ background-color: hsl( 204 100% 40% );
+ color: hsl( 204 20% 100% );
+ }
+
+ &:active,
+ &[data-active] {
+ background-color: hsl( 204 100% 32% );
+ }
+
+ ${ StyledAriakitMenu }:not(:focus) &:not(:focus)[aria-expanded="true"] {
+ background-color: hsl( 204 10% 10% / 0.1 );
+ color: currentColor;
+ }
+`;
+
+// :is(.dark .menu:not(:focus) .menu-item:not(:focus)[aria-expanded="true"]) {
+// background-color: hsl(204 20% 100% / 0.1);
+// }
+
+export const StyledMenuButtonLabel = styled.span`
+ flex: 1 1 0%;
+`;
diff --git a/packages/components/src/dropdown-menu/index.tsx b/packages/components/src/dropdown-menu/index.tsx
index 9105555927f47..b5ccd92d68b90 100644
--- a/packages/components/src/dropdown-menu/index.tsx
+++ b/packages/components/src/dropdown-menu/index.tsx
@@ -22,7 +22,7 @@ import type {
} from './types';
function mergeProps<
- T extends { className?: string; [ key: string ]: unknown },
+ T extends { className?: string; [ key: string ]: unknown }
>( defaultProps: Partial< T > = {}, props: T = {} as T ) {
const mergedProps: T = {
...defaultProps,
diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts
index 6e17abde0c627..966be9887a30a 100644
--- a/packages/components/src/private-apis.ts
+++ b/packages/components/src/private-apis.ts
@@ -22,6 +22,10 @@ import {
DropdownSubMenu as DropdownSubMenuV2,
DropdownSubMenuTrigger as DropdownSubMenuTriggerV2,
} from './dropdown-menu-v2';
+import {
+ DropdownMenu as DropdownMenuV2Ariakit,
+ DropdownMenuItem as DropdownMenuItemV2Ariakit,
+} from './dropdown-menu-v2-ariakit';
import { ComponentsContext } from './ui/context/context-system-provider';
import Theme from './theme';
@@ -49,4 +53,6 @@ lock( privateApis, {
DropdownSubMenuTriggerV2,
ProgressBar,
Theme,
+ DropdownMenuV2Ariakit,
+ DropdownMenuItemV2Ariakit,
} );
diff --git a/packages/patterns/src/components/pattern-convert-button.js b/packages/patterns/src/components/pattern-convert-button.js
index 002dbbd8c0181..9356eddbfc7b6 100644
--- a/packages/patterns/src/components/pattern-convert-button.js
+++ b/packages/patterns/src/components/pattern-convert-button.js
@@ -9,8 +9,8 @@ import {
} from '@wordpress/blocks';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { useState, useCallback } from '@wordpress/element';
-import { MenuItem } from '@wordpress/components';
-import { symbol } from '@wordpress/icons';
+import { privateApis as componentsPrivateApis } from '@wordpress/components';
+// import { symbol } from '@wordpress/icons';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { __, sprintf } from '@wordpress/i18n';
@@ -23,6 +23,10 @@ import CreatePatternModal from './create-pattern-modal';
import { unlock } from '../lock-unlock';
import { PATTERN_SYNC_TYPES } from '../constants';
+const { DropdownMenuV2Ariakit, DropdownMenuItemV2Ariakit } = unlock(
+ componentsPrivateApis
+);
+
/**
* Menu control to convert block(s) to a pattern block.
*
@@ -126,15 +130,16 @@ export default function PatternConvertButton( { clientIds, rootClientId } ) {
setIsModalOpen( false );
};
return (
- <>
-
+
{ isModalOpen && (
) }
- >
+
);
}