From 0b6c4a003f90b18509ee6d3d120435444dc15e43 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 23 Nov 2023 13:42:17 +0100 Subject: [PATCH] DropdownMenu V2: add support for rendering in legacy popover slot (#56342) * DropdownMenuV2: support rendering in slotfill * Add temporary stories * CHANGELOG --- packages/components/CHANGELOG.md | 1 + .../src/dropdown-menu-v2-ariakit/index.tsx | 14 ++++ .../stories/index.story.tsx | 66 +++++++++++++++++++ .../src/dropdown-menu-v2-ariakit/types.ts | 7 ++ .../src/dropdown-menu/stories/index.story.tsx | 52 +++++++++++++++ packages/components/src/popover/index.tsx | 2 +- 6 files changed, 141 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 9a3beb0342396..cbb12e0146b55 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,7 @@ ### Internal +- `DropdownMenuV2`: add support for rendering in legacy popover slot ([#56342](https://github.com/WordPress/gutenberg/pull/56342)). - `Slot`: add `style` prop to `bubblesVirtually` version ([#56428](https://github.com/WordPress/gutenberg/pull/56428)) ## 25.12.0 (2023-11-16) diff --git a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx index 10b93d8c552c1..b992c7b4c7b5a 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx +++ b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx @@ -26,6 +26,11 @@ import { SVG, Circle } from '@wordpress/primitives'; import { useContextSystem, contextConnect } from '../context'; import type { WordPressComponentProps } from '../context'; import Icon from '../icon'; +import { useSlot } from '../slot-fill'; +import { + SLOT_NAME as POPOVER_DEFAULT_SLOT_NAME, + slotNameContext, +} from '../popover'; import type { DropdownMenuContext as DropdownMenuContextType, DropdownMenuProps, @@ -182,6 +187,9 @@ const UnconnectedDropdownMenu = ( shift, modal = true, + // Other props + slotName: slotNameProp = POPOVER_DEFAULT_SLOT_NAME, + // From internal components context variant, @@ -270,6 +278,11 @@ const UnconnectedDropdownMenu = ( [ computedDirection ] ); + // Render the portal in the default slot used by the legacy Popover component. + const slotName = useContext( slotNameContext ) || slotNameProp; + const slot = useSlot( slotName ); + const portalContainer = slot.ref?.current; + return ( <> { /* Menu trigger */ } @@ -305,6 +318,7 @@ const UnconnectedDropdownMenu = ( wrapperProps={ wrapperProps } hideOnEscape={ hideOnEscape } unmountOnHide + portalElement={ portalContainer } > { 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 index a6319c6cfdc93..147de3861b160 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx +++ b/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx @@ -28,6 +28,7 @@ import { import Icon from '../../icon'; import Button from '../../button'; import Modal from '../../modal'; +import Popover from '../../popover'; import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; import { ContextSystemProvider } from '../../context'; @@ -501,3 +502,68 @@ InsideModal.parameters = { source: { type: 'code' }, }, }; + +export const WithDefaultSlotFill: StoryFn< typeof DropdownMenu > = ( + props +) => ( + + { /* @ts-expect-error Slot is not currently typed on Popover */ } + + + Top level item + Open submenu } + > + Nested item + + + +); +WithDefaultSlotFill.args = { + ...Default.args, +}; + +export const WithNamedSlotFill: StoryFn< typeof DropdownMenu > = ( props ) => ( + + { /* @ts-expect-error Slot is not currently typed on Popover */ } + + + Top level item + Open submenu } + > + Nested item + + + +); +WithNamedSlotFill.args = { + ...Default.args, + slotName: 'dropdown-menu-slot-with-name', +}; + +// @ts-expect-error __unstableSlotNameProvider is not currently typed on Popover +const SlotNameProvider = Popover.__unstableSlotNameProvider; +export const WithSlotFillViaContext: StoryFn< typeof DropdownMenu > = ( + props +) => ( + + + { /* @ts-expect-error Slot is not currently typed on Popover */ } + + + Top level item + Open submenu + } + > + Nested item + + + + +); +WithSlotFillViaContext.args = { + ...Default.args, +}; diff --git a/packages/components/src/dropdown-menu-v2-ariakit/types.ts b/packages/components/src/dropdown-menu-v2-ariakit/types.ts index 27d3b1e8c4339..64c7134d62bff 100644 --- a/packages/components/src/dropdown-menu-v2-ariakit/types.ts +++ b/packages/components/src/dropdown-menu-v2-ariakit/types.ts @@ -79,6 +79,13 @@ export interface DropdownMenuProps { | ( ( event: KeyboardEvent | React.KeyboardEvent< Element > ) => boolean ); + /** + * The name of the Slot in which the popover should be rendered. It should + * be also passed to the corresponding `PopoverSlot` component. + * + * @default 'Popover' + */ + slotName?: string; } export interface DropdownMenuGroupProps { diff --git a/packages/components/src/dropdown-menu/stories/index.story.tsx b/packages/components/src/dropdown-menu/stories/index.story.tsx index d4b856380db92..e94b5199b2537 100644 --- a/packages/components/src/dropdown-menu/stories/index.story.tsx +++ b/packages/components/src/dropdown-menu/stories/index.story.tsx @@ -9,6 +9,8 @@ import type { Meta, StoryFn } from '@storybook/react'; import { DropdownMenu } from '..'; import MenuItem from '../../menu-item'; import MenuGroup from '../../menu-group'; +import Popover from '../../popover'; +import { Provider as SlotFillProvider } from '../../slot-fill'; /** * WordPress dependencies @@ -116,3 +118,53 @@ WithChildren.args = { ), }; + +export const WithDefaultSlotFill: StoryFn< typeof DropdownMenu > = ( + props +) => ( + +
+ { /* @ts-expect-error Slot is not currently typed on Popover */ } + + +
+
+); +WithDefaultSlotFill.args = { + ...Default.args, +}; + +export const WithNamedSlotFill: StoryFn< typeof DropdownMenu > = ( props ) => ( + +
+ { /* @ts-expect-error Slot is not currently typed on Popover */ } + + +
+
+); +WithNamedSlotFill.args = { + ...Default.args, + popoverProps: { + __unstableSlotName: 'dropdown-menu-slot-with-name', + }, +}; + +// @ts-expect-error __unstableSlotNameProvider is not currently typed on Popover +const SlotNameProvider = Popover.__unstableSlotNameProvider; +export const WithSlotFillViaContext: StoryFn< typeof DropdownMenu > = ( + props +) => ( + + + { /* @ts-expect-error Slot is not currently typed on Popover */ } + +
+ +
+
+
+); +WithSlotFillViaContext.args = { + ...Default.args, +}; diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx index 709d4b9884b5e..ff67b65cf5b0d 100644 --- a/packages/components/src/popover/index.tsx +++ b/packages/components/src/popover/index.tsx @@ -97,7 +97,7 @@ const ArrowTriangle = () => ( ); -const slotNameContext = createContext< string | undefined >( undefined ); +export const slotNameContext = createContext< string | undefined >( undefined ); const fallbackContainerClassname = 'components-popover__fallback-container'; const getPopoverFallbackContainer = () => {