From 2fc6530c7b46be23110390bc607f105d02559349 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 7 Sep 2023 17:45:08 +0200 Subject: [PATCH 01/10] Allow dropdown open state to be controlled --- packages/components/src/dropdown/index.tsx | 50 +++++++--------------- packages/components/src/dropdown/types.ts | 4 ++ 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/packages/components/src/dropdown/index.tsx b/packages/components/src/dropdown/index.tsx index 2060254fa73c11..0a870049a5b697 100644 --- a/packages/components/src/dropdown/index.tsx +++ b/packages/components/src/dropdown/index.tsx @@ -7,7 +7,7 @@ import type { ForwardedRef } from 'react'; /** * WordPress dependencies */ -import { useEffect, useRef, useState } from '@wordpress/element'; +import { useRef, useState } from '@wordpress/element'; import { useMergeRefs } from '@wordpress/compose'; import deprecated from '@wordpress/deprecated'; @@ -15,25 +15,10 @@ import deprecated from '@wordpress/deprecated'; * Internal dependencies */ import { contextConnect, useContextSystem } from '../ui/context'; +import { useControlledValue } from '../utils/hooks'; import Popover from '../popover'; import type { DropdownProps, DropdownInternalContext } from './types'; -function useObservableState( - initialState: boolean, - onStateChange?: ( newState: boolean ) => void -) { - const [ state, setState ] = useState( initialState ); - return [ - state, - ( value: boolean ) => { - setState( value ); - if ( onStateChange ) { - onStateChange( value ); - } - }, - ] as const; -} - const UnconnectedDropdown = ( props: DropdownProps, forwardedRef: ForwardedRef< any > @@ -51,6 +36,9 @@ const UnconnectedDropdown = ( onToggle, style, + open, + defaultOpen, + // Deprecated props position, @@ -74,20 +62,12 @@ const UnconnectedDropdown = ( const [ fallbackPopoverAnchor, setFallbackPopoverAnchor ] = useState< HTMLDivElement | null >( null ); const containerRef = useRef< HTMLDivElement >(); - const [ isOpen, setIsOpen ] = useObservableState( false, onToggle ); - - useEffect( - () => () => { - if ( onToggle && isOpen ) { - onToggle( false ); - } - }, - [ onToggle, isOpen ] - ); - function toggle() { - setIsOpen( ! isOpen ); - } + const [ isOpen, setIsOpen ] = useControlledValue( { + defaultValue: defaultOpen, + value: open, + onChange: onToggle, + } ); /** * Closes the popover when focus leaves it unless the toggle was pressed or @@ -112,13 +92,15 @@ const UnconnectedDropdown = ( } function close() { - if ( onClose ) { - onClose(); - } + onClose?.(); setIsOpen( false ); } - const args = { isOpen, onToggle: toggle, onClose: close }; + const args = { + isOpen: !! isOpen, + onToggle: () => setIsOpen( ! isOpen ), + onClose: close, + }; const popoverPropsHaveAnchor = !! popoverProps?.anchor || // Note: `anchorRef`, `getAnchorRect` and `anchorRect` are deprecated and diff --git a/packages/components/src/dropdown/types.ts b/packages/components/src/dropdown/types.ts index c95953f37b1fb1..1524469ac85c07 100644 --- a/packages/components/src/dropdown/types.ts +++ b/packages/components/src/dropdown/types.ts @@ -111,6 +111,10 @@ export type DropdownProps = { * @deprecated */ position?: PopoverProps[ 'position' ]; + + open?: boolean; + + defaultOpen?: boolean; }; export type DropdownInternalContext = { From d8379d92a99be0d77f1c223d0f02d6313996421e Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 7 Sep 2023 17:45:41 +0200 Subject: [PATCH 02/10] Update Dropdown stories to use controlled mode --- .../src/dropdown/stories/index.story.tsx | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/components/src/dropdown/stories/index.story.tsx b/packages/components/src/dropdown/stories/index.story.tsx index 0b29da916b8d89..5ea6ed3bd6b592 100644 --- a/packages/components/src/dropdown/stories/index.story.tsx +++ b/packages/components/src/dropdown/stories/index.story.tsx @@ -3,6 +3,11 @@ */ import type { Meta, StoryFn } from '@storybook/react'; +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + /** * Internal dependencies */ @@ -25,8 +30,13 @@ const meta: Meta< typeof Dropdown > = { position: { control: { type: null } }, renderContent: { control: { type: null } }, renderToggle: { control: { type: null } }, + open: { control: { type: null } }, + defaultOpen: { control: { type: null } }, + onToggle: { control: { type: null } }, + onClose: { control: { type: null } }, }, parameters: { + actions: { argTypesRegex: '^on.*' }, controls: { expanded: true, }, @@ -35,9 +45,23 @@ const meta: Meta< typeof Dropdown > = { export default meta; const Template: StoryFn< typeof Dropdown > = ( args ) => { + const [ open, setOpen ] = useState( false ); return (
- + { + setOpen( willOpen ); + args.onToggle?.( willOpen ); + } } + // Only used if uncontrolled (ie. no `open` prop passed) + // defaultOpen={ false } + /> + +
); }; From bf72b260d58bbd90dfb8b86767afe335c1c19ff6 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 7 Sep 2023 17:46:04 +0200 Subject: [PATCH 03/10] Allow DropdownMenu open state to be controlled --- packages/components/src/dropdown-menu/index.tsx | 7 +++++++ packages/components/src/dropdown-menu/types.ts | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/packages/components/src/dropdown-menu/index.tsx b/packages/components/src/dropdown-menu/index.tsx index b5b7533e52bc40..b5ccd92d68b907 100644 --- a/packages/components/src/dropdown-menu/index.tsx +++ b/packages/components/src/dropdown-menu/index.tsx @@ -57,6 +57,10 @@ function UnconnectedDropdownMenu( dropdownMenuProps: DropdownMenuProps ) { text, noIcons, + open, + defaultOpen, + onToggle: onToggleProp, + // Context variant, } = useContextSystem< DropdownMenuProps & DropdownMenuInternalContext >( @@ -211,6 +215,9 @@ function UnconnectedDropdownMenu( dropdownMenuProps: DropdownMenuProps ) { ); } } + open={ open } + defaultOpen={ defaultOpen } + onToggle={ onToggleProp } /> ); } diff --git a/packages/components/src/dropdown-menu/types.ts b/packages/components/src/dropdown-menu/types.ts index 1063631c65113e..ce74a81b05c481 100644 --- a/packages/components/src/dropdown-menu/types.ts +++ b/packages/components/src/dropdown-menu/types.ts @@ -140,6 +140,10 @@ export type DropdownMenuProps = { * A valid DropdownMenu must specify a `controls` or `children` prop, or both. */ controls?: DropdownOption[] | DropdownOption[][]; + + open?: boolean; + onToggle?: ( willOpen: boolean ) => void; + defaultOpen?: boolean; }; export type DropdownMenuInternalContext = { From 2d35f365a978a9328eaddab079d5c832a6a3b457 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 7 Sep 2023 17:46:55 +0200 Subject: [PATCH 04/10] Update DropdownMenu stories to use controlled mode --- .../src/dropdown-menu/stories/index.story.tsx | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/components/src/dropdown-menu/stories/index.story.tsx b/packages/components/src/dropdown-menu/stories/index.story.tsx index 0490636cfa2067..3cddcf55169fdc 100644 --- a/packages/components/src/dropdown-menu/stories/index.story.tsx +++ b/packages/components/src/dropdown-menu/stories/index.story.tsx @@ -2,6 +2,11 @@ * External dependencies */ import type { Meta, StoryFn } from '@storybook/react'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; /** * Internal dependencies */ @@ -25,6 +30,7 @@ const meta: Meta< typeof DropdownMenu > = { title: 'Components/DropdownMenu', component: DropdownMenu, parameters: { + actions: { argTypesRegex: '^on.*' }, controls: { expanded: true }, docs: { canvas: { sourceState: 'shown' } }, }, @@ -34,15 +40,34 @@ const meta: Meta< typeof DropdownMenu > = { mapping: { menu, chevronDown, more }, control: { type: 'select' }, }, + open: { control: { type: null } }, + defaultOpen: { control: { type: null } }, + onToggle: { control: { type: null } }, }, }; export default meta; -const Template: StoryFn< typeof DropdownMenu > = ( props ) => ( -
- -
-); +const Template: StoryFn< typeof DropdownMenu > = ( props ) => { + const [ open, setOpen ] = useState( false ); + return ( +
+ { + setOpen( willOpen ); + props.onToggle?.( willOpen ); + } } + // Only used if uncontrolled (ie. no `open` prop passed) + // defaultOpen={ false } + /> + + +
+ ); +}; export const Default = Template.bind( {} ); Default.args = { From c78a46feb47a8c43318f4311f3eddf38793f495b Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 7 Sep 2023 17:47:59 +0200 Subject: [PATCH 05/10] Add temporary storybook example to show how to keep only dropdownmenu open at a time --- .../src/dropdown-menu/stories/index.story.tsx | 74 ++++++++++++++++++- packages/components/src/dropdown/index.tsx | 4 +- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/packages/components/src/dropdown-menu/stories/index.story.tsx b/packages/components/src/dropdown-menu/stories/index.story.tsx index 3cddcf55169fdc..8e26646baa2ffa 100644 --- a/packages/components/src/dropdown-menu/stories/index.story.tsx +++ b/packages/components/src/dropdown-menu/stories/index.story.tsx @@ -6,7 +6,7 @@ import type { Meta, StoryFn } from '@storybook/react'; /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useState, useId, createContext, useContext } from '@wordpress/element'; /** * Internal dependencies */ @@ -136,3 +136,75 @@ WithChildren.args = { ), }; + +const OnlyOneDropdownOpenContext = createContext< { + openDropdownId?: string; + setOpenDropdownId: ( id: string | undefined ) => void; +} >( { + openDropdownId: undefined, + setOpenDropdownId: ( _instanceId ) => {}, +} ); + +const ItemWithDropdown = () => { + const { openDropdownId, setOpenDropdownId } = useContext( + OnlyOneDropdownOpenContext + ); + + const instanceId = useId(); + + return ( +
+ Some text + console.log( 'up!' ), + }, + { + title: 'Second Menu Item Label', + icon: arrowDown, + // eslint-disable-next-line no-console + onClick: () => console.log( 'down!' ), + }, + ] } + open={ openDropdownId === instanceId } + onToggle={ ( willOpen ) => { + if ( willOpen ) { + setOpenDropdownId( instanceId ); + } else { + setOpenDropdownId( undefined ); + } + } } + /> +
+ ); +}; + +export const OnlyOneOpened: StoryFn< typeof DropdownMenu > = () => { + const [ openDropdownId, setOpenDropdownId ] = useState< + string | undefined + >( undefined ); + + return ( + + + + + + + + + + + + + + ); +}; diff --git a/packages/components/src/dropdown/index.tsx b/packages/components/src/dropdown/index.tsx index 0a870049a5b697..43947103dff0ae 100644 --- a/packages/components/src/dropdown/index.tsx +++ b/packages/components/src/dropdown/index.tsx @@ -87,7 +87,9 @@ const UnconnectedDropdown = ( ! containerRef.current.contains( ownerDocument.activeElement ) && ( ! dialog || dialog.contains( containerRef.current ) ) ) { - close(); + // Temporarily disable this functionality to make sure that the storybook + // example works + // close(); } } From 17ad46e201ae91887b2870270e904f993e2e6f46 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 8 Sep 2023 13:57:23 +0200 Subject: [PATCH 06/10] Remove ad-hoc storybook example and restore close callback --- .../src/dropdown-menu/stories/index.story.tsx | 74 +------------------ packages/components/src/dropdown/index.tsx | 4 +- 2 files changed, 2 insertions(+), 76 deletions(-) diff --git a/packages/components/src/dropdown-menu/stories/index.story.tsx b/packages/components/src/dropdown-menu/stories/index.story.tsx index 8e26646baa2ffa..3cddcf55169fdc 100644 --- a/packages/components/src/dropdown-menu/stories/index.story.tsx +++ b/packages/components/src/dropdown-menu/stories/index.story.tsx @@ -6,7 +6,7 @@ import type { Meta, StoryFn } from '@storybook/react'; /** * WordPress dependencies */ -import { useState, useId, createContext, useContext } from '@wordpress/element'; +import { useState } from '@wordpress/element'; /** * Internal dependencies */ @@ -136,75 +136,3 @@ WithChildren.args = { ), }; - -const OnlyOneDropdownOpenContext = createContext< { - openDropdownId?: string; - setOpenDropdownId: ( id: string | undefined ) => void; -} >( { - openDropdownId: undefined, - setOpenDropdownId: ( _instanceId ) => {}, -} ); - -const ItemWithDropdown = () => { - const { openDropdownId, setOpenDropdownId } = useContext( - OnlyOneDropdownOpenContext - ); - - const instanceId = useId(); - - return ( -
- Some text - console.log( 'up!' ), - }, - { - title: 'Second Menu Item Label', - icon: arrowDown, - // eslint-disable-next-line no-console - onClick: () => console.log( 'down!' ), - }, - ] } - open={ openDropdownId === instanceId } - onToggle={ ( willOpen ) => { - if ( willOpen ) { - setOpenDropdownId( instanceId ); - } else { - setOpenDropdownId( undefined ); - } - } } - /> -
- ); -}; - -export const OnlyOneOpened: StoryFn< typeof DropdownMenu > = () => { - const [ openDropdownId, setOpenDropdownId ] = useState< - string | undefined - >( undefined ); - - return ( - - - - - - - - - - - - - - ); -}; diff --git a/packages/components/src/dropdown/index.tsx b/packages/components/src/dropdown/index.tsx index 43947103dff0ae..0a870049a5b697 100644 --- a/packages/components/src/dropdown/index.tsx +++ b/packages/components/src/dropdown/index.tsx @@ -87,9 +87,7 @@ const UnconnectedDropdown = ( ! containerRef.current.contains( ownerDocument.activeElement ) && ( ! dialog || dialog.contains( containerRef.current ) ) ) { - // Temporarily disable this functionality to make sure that the storybook - // example works - // close(); + close(); } } From a5a400cb00ad317eae534101ac06c30dd5723a77 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 8 Sep 2023 13:57:50 +0200 Subject: [PATCH 07/10] Restore uncontrolled storybook examples --- .../src/dropdown-menu/stories/index.story.tsx | 30 +++--------------- .../src/dropdown/stories/index.story.tsx | 31 +++---------------- 2 files changed, 10 insertions(+), 51 deletions(-) diff --git a/packages/components/src/dropdown-menu/stories/index.story.tsx b/packages/components/src/dropdown-menu/stories/index.story.tsx index 3cddcf55169fdc..d4b856380db92a 100644 --- a/packages/components/src/dropdown-menu/stories/index.story.tsx +++ b/packages/components/src/dropdown-menu/stories/index.story.tsx @@ -3,10 +3,6 @@ */ import type { Meta, StoryFn } from '@storybook/react'; -/** - * WordPress dependencies - */ -import { useState } from '@wordpress/element'; /** * Internal dependencies */ @@ -47,27 +43,11 @@ const meta: Meta< typeof DropdownMenu > = { }; export default meta; -const Template: StoryFn< typeof DropdownMenu > = ( props ) => { - const [ open, setOpen ] = useState( false ); - return ( -
- { - setOpen( willOpen ); - props.onToggle?.( willOpen ); - } } - // Only used if uncontrolled (ie. no `open` prop passed) - // defaultOpen={ false } - /> - - -
- ); -}; +const Template: StoryFn< typeof DropdownMenu > = ( props ) => ( +
+ +
+); export const Default = Template.bind( {} ); Default.args = { diff --git a/packages/components/src/dropdown/stories/index.story.tsx b/packages/components/src/dropdown/stories/index.story.tsx index 5ea6ed3bd6b592..894f62ee47f8d7 100644 --- a/packages/components/src/dropdown/stories/index.story.tsx +++ b/packages/components/src/dropdown/stories/index.story.tsx @@ -3,11 +3,6 @@ */ import type { Meta, StoryFn } from '@storybook/react'; -/** - * WordPress dependencies - */ -import { useState } from '@wordpress/element'; - /** * Internal dependencies */ @@ -44,27 +39,11 @@ const meta: Meta< typeof Dropdown > = { }; export default meta; -const Template: StoryFn< typeof Dropdown > = ( args ) => { - const [ open, setOpen ] = useState( false ); - return ( -
- { - setOpen( willOpen ); - args.onToggle?.( willOpen ); - } } - // Only used if uncontrolled (ie. no `open` prop passed) - // defaultOpen={ false } - /> - - -
- ); -}; +const Template: StoryFn< typeof Dropdown > = ( args ) => ( +
+ +
+); export const Default = Template.bind( {} ); Default.args = { From ab6e115ddf7ab1f57d6e80a4c436630bf02b8048 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 8 Sep 2023 13:59:12 +0200 Subject: [PATCH 08/10] CHANGELOG --- packages/components/CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index ca2d278909aa78..5539a28a09e013 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,16 +2,13 @@ ## Unreleased -### Breaking changes - -- Make the `Popover.Slot` optional and render popovers at the bottom of the document's body by default. ([#53889](https://github.com/WordPress/gutenberg/pull/53889), [#53982](https://github.com/WordPress/gutenberg/pull/53982)). - ### Enhancements - Making Circular Option Picker a `listbox`. Note that while this changes some public API, new props are optional, and currently have default values; this will change in another patch ([#52255](https://github.com/WordPress/gutenberg/pull/52255)). - `ToggleGroupControl`: Rewrite backdrop animation using framer motion shared layout animations, add better support for controlled and uncontrolled modes ([#50278](https://github.com/WordPress/gutenberg/pull/50278)). - `Popover`: Add the `is-positioned` CSS class only after the popover has finished animating ([#54178](https://github.com/WordPress/gutenberg/pull/54178)). - `Tooltip`: Replace the existing tooltip to simplify the implementation and improve accessibility while maintaining the same behaviors and API ([#48440](https://github.com/WordPress/gutenberg/pull/48440)). +- `Dropdown` and `DropdownMenu`: support controlled mode for the dropdown's open/closed state ([#54257](https://github.com/WordPress/gutenberg/pull/54257)). ### Bug Fix @@ -32,9 +29,12 @@ ## 25.7.0 (2023-08-31) +### Breaking changes + +- Make the `Popover.Slot` optional and render popovers at the bottom of the document's body by default. ([#53889](https://github.com/WordPress/gutenberg/pull/53889), [#53982](https://github.com/WordPress/gutenberg/pull/53982)). + ### Enhancements -- Make the `Popover.Slot` optional and render popovers at the bottom of the document's body by default. ([#53889](https://github.com/WordPress/gutenberg/pull/53889)). - `ProgressBar`: Add transition to determinate indicator ([#53877](https://github.com/WordPress/gutenberg/pull/53877)). - Prevent nested `SlotFillProvider` from rendering ([#53940](https://github.com/WordPress/gutenberg/pull/53940)). From 1beb75029d34b0c557c612ac256fb82b7d456ca2 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 8 Sep 2023 14:21:23 +0200 Subject: [PATCH 09/10] Add docs for the new dropdown props --- packages/components/src/dropdown/README.md | 16 +++++++++++++--- packages/components/src/dropdown/types.ts | 16 ++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/components/src/dropdown/README.md b/packages/components/src/dropdown/README.md index 3dd321ed900128..5bb3ec2c3b6e0e 100644 --- a/packages/components/src/dropdown/README.md +++ b/packages/components/src/dropdown/README.md @@ -44,6 +44,12 @@ If you want to target the dropdown menu for styling purposes, you need to provid - Required: No +### `defaultOpen`: `boolean` + +The open state of the dropdown when initially rendered. Use when you do not need to control its open state. It will be overridden by the `open` prop if it is specified on the component's first render. + +- Required: No + ### `expandOnMobile`: `boolean` Opt-in prop to show popovers fullscreen on mobile. @@ -74,11 +80,15 @@ A callback invoked when the popover should be closed. - Required: No -### `onToggle`: `( willOpen: boolean ) => void` +### `open`: `boolean` -A callback invoked when the state of the popover changes from open to closed and vice versa. +The controlled open state of the dropdown. Must be used in conjunction with `onToggle`. + +- Required: No + +### `onToggle`: `( willOpen: boolean ) => void` -The callback receives a boolean as a parameter. If `true`, the popover will open. If `false`, the popover will close. +A callback invoked when the state of the dropdown changes from open to closed and vice versa. - Required: No diff --git a/packages/components/src/dropdown/types.ts b/packages/components/src/dropdown/types.ts index 1524469ac85c07..b185ec6fe14f71 100644 --- a/packages/components/src/dropdown/types.ts +++ b/packages/components/src/dropdown/types.ts @@ -62,11 +62,8 @@ export type DropdownProps = { */ onClose?: () => void; /** - * A callback invoked when the state of the popover changes + * A callback invoked when the state of the dropdown changes * from open to closed and vice versa. - * The callback receives a boolean as a parameter. - * If true, the popover will open. - * If false, the popover will close. */ onToggle?: ( willOpen: boolean ) => void; /** @@ -111,9 +108,16 @@ export type DropdownProps = { * @deprecated */ position?: PopoverProps[ 'position' ]; - + /** + * The controlled open state of the dropdown. + * Must be used in conjunction with `onToggle`. + */ open?: boolean; - + /** + * The open state of the dropdown when initially rendered. + * Use when you do not need to control its open state. It will be overridden + * by the `open` prop if it is specified on the component's first render. + */ defaultOpen?: boolean; }; From 982f0fae7cdbc584abb92f6cc35cd3d4ecb1336f Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 8 Sep 2023 14:25:03 +0200 Subject: [PATCH 10/10] Add docs for the new DropdownMenu props --- .../components/src/dropdown-menu/README.md | 18 ++++++++++++++++++ packages/components/src/dropdown-menu/types.ts | 16 ++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/components/src/dropdown-menu/README.md b/packages/components/src/dropdown-menu/README.md index e1e4c7bf031b0f..dcdb30997038eb 100644 --- a/packages/components/src/dropdown-menu/README.md +++ b/packages/components/src/dropdown-menu/README.md @@ -198,3 +198,21 @@ In some contexts, the arrow down key used to open the dropdown menu might need t - Required: No - Default: `false` + +### `defaultOpen`: `boolean` + +The open state of the dropdown menu when initially rendered. Use when you do not need to control its open state. It will be overridden by the `open` prop if it is specified on the component's first render. + +- Required: No + +### `open`: `boolean` + +The controlled open state of the dropdown menu. Must be used in conjunction with `onToggle`. + +- Required: No + +### `onToggle`: `( willOpen: boolean ) => void` + +A callback invoked when the state of the dropdown changes from open to closed and vice versa. + +- Required: No diff --git a/packages/components/src/dropdown-menu/types.ts b/packages/components/src/dropdown-menu/types.ts index ce74a81b05c481..2e70557a7fe43e 100644 --- a/packages/components/src/dropdown-menu/types.ts +++ b/packages/components/src/dropdown-menu/types.ts @@ -140,10 +140,22 @@ export type DropdownMenuProps = { * A valid DropdownMenu must specify a `controls` or `children` prop, or both. */ controls?: DropdownOption[] | DropdownOption[][]; - + /** + * The controlled open state of the dropdown menu. + * Must be used in conjunction with `onToggle`. + */ open?: boolean; - onToggle?: ( willOpen: boolean ) => void; + /** + * The open state of the dropdown menu when initially rendered. + * Use when you do not need to control its open state. It will be overridden + * by the `open` prop if it is specified on the component's first render. + */ defaultOpen?: boolean; + /** + * A callback invoked when the state of the dropdown menu changes + * from open to closed and vice versa. + */ + onToggle?: ( willOpen: boolean ) => void; }; export type DropdownMenuInternalContext = {