Skip to content
10 changes: 5 additions & 5 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@

## Unreleased

### Breaking changes
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this entry to the previous release section, since I realised that the corresponding PR was merged before August 31st


- 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

Expand All @@ -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)).

Expand Down
18 changes: 18 additions & 0 deletions packages/components/src/dropdown-menu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions packages/components/src/dropdown-menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ function UnconnectedDropdownMenu( dropdownMenuProps: DropdownMenuProps ) {
text,
noIcons,

open,
defaultOpen,
onToggle: onToggleProp,

// Context
variant,
} = useContextSystem< DropdownMenuProps & DropdownMenuInternalContext >(
Expand Down Expand Up @@ -211,6 +215,9 @@ function UnconnectedDropdownMenu( dropdownMenuProps: DropdownMenuProps ) {
</NavigableMenu>
);
} }
open={ open }
defaultOpen={ defaultOpen }
onToggle={ onToggleProp }
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import type { Meta, StoryFn } from '@storybook/react';

/**
* Internal dependencies
*/
Expand All @@ -25,6 +26,7 @@ const meta: Meta< typeof DropdownMenu > = {
title: 'Components/DropdownMenu',
component: DropdownMenu,
parameters: {
actions: { argTypesRegex: '^on.*' },
controls: { expanded: true },
docs: { canvas: { sourceState: 'shown' } },
},
Expand All @@ -34,6 +36,9 @@ 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;
Expand Down
16 changes: 16 additions & 0 deletions packages/components/src/dropdown-menu/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +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;
/**
* 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 = {
Expand Down
16 changes: 13 additions & 3 deletions packages/components/src/dropdown/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
50 changes: 16 additions & 34 deletions packages/components/src/dropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,18 @@ 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';

/**
* 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 >
Expand All @@ -51,6 +36,9 @@ const UnconnectedDropdown = (
onToggle,
style,

open,
defaultOpen,

// Deprecated props
position,

Expand All @@ -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 ]
);
Comment on lines -79 to -86
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one, this is the removal that I was missing from my experiment 🎉


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
Expand All @@ -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
Expand Down
17 changes: 10 additions & 7 deletions packages/components/src/dropdown/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,25 @@ 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,
},
},
};
export default meta;

const Template: StoryFn< typeof Dropdown > = ( args ) => {
return (
<div style={ { height: 150 } }>
<Dropdown { ...args } />
</div>
);
};
const Template: StoryFn< typeof Dropdown > = ( args ) => (
<div style={ { height: 150 } }>
<Dropdown { ...args } />
</div>
);

export const Default = Template.bind( {} );
Default.args = {
Expand Down
16 changes: 12 additions & 4 deletions packages/components/src/dropdown/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand Down Expand Up @@ -111,6 +108,17 @@ 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;
};

export type DropdownInternalContext = {
Expand Down