Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

### Bug Fixes

- `Modal`: Prevent background page scrolling without relying on WordPress global styles when using the default `bodyOpenClassName` ([#81164](https://github.com/WordPress/gutenberg/pull/81164)).
- `Menu`: Return focus to the root trigger after closing a legacy `Modal` opened from a menu item, while preserving the menu-to-Modal scroll-lock handoff ([#81164](https://github.com/WordPress/gutenberg/pull/81164)).
- `Button`: Suppress the browser focus ring when keyboard-focused and pressed ([#81113](https://github.com/WordPress/gutenberg/pull/81113)).

### TypeScript
Expand Down
12 changes: 7 additions & 5 deletions packages/components/src/menu/checkbox-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { WordPressComponentProps } from '../context';
import { Context } from './context';
import type { CheckboxItemProps } from './types';
import * as Styled from './styles';
import { useMenuItemHideOnClick } from './use-menu-item-hide-on-click';

export const CheckboxItem = forwardRef<
HTMLDivElement,
Expand All @@ -14,24 +15,25 @@ export const CheckboxItem = forwardRef<
ref
) {
const menuContext = useContext( Context );
const store = menuContext?.store;
const computedHideOnClick = useMenuItemHideOnClick( store, hideOnClick );

if ( ! menuContext?.store ) {
if ( ! store ) {
throw new Error(
'Menu.CheckboxItem can only be rendered inside a Menu component'
);
}

return (
<Styled.CheckboxItem
ref={ ref }
{ ...props }
accessibleWhenDisabled
disabled={ disabled }
hideOnClick={ hideOnClick }
store={ menuContext.store }
store={ store }
hideOnClick={ computedHideOnClick }
>
<Ariakit.MenuItemCheck
store={ menuContext.store }
store={ store }
render={ <Styled.ItemPrefixWrapper /> }
// Override some ariakit inline styles
style={ { width: 'auto', height: 'auto' } }
Expand Down
12 changes: 8 additions & 4 deletions packages/components/src/menu/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { WordPressComponentProps } from '../context';
import type { ItemProps } from './types';
import * as Styled from './styles';
import { Context } from './context';
import { useMenuItemHideOnClick } from './use-menu-item-hide-on-click';

export const Item = forwardRef<
HTMLDivElement,
Expand All @@ -20,8 +21,13 @@ export const Item = forwardRef<
ref
) {
const menuContext = useContext( Context );
const computedStore = store ?? menuContext?.store;
const computedHideOnClick = useMenuItemHideOnClick(
computedStore,
hideOnClick
);

if ( ! menuContext?.store ) {
if ( ! menuContext?.store || ! computedStore ) {
throw new Error(
'Menu.Item can only be rendered inside a Menu component'
);
Expand All @@ -31,16 +37,14 @@ export const Item = forwardRef<
// created by the top-level menu component). But in rare cases (ie.
// `Menu.SubmenuTriggerItem`), the context store wouldn't be correct. This is
// why the component accepts a `store` prop to override the context store.
const computedStore = store ?? menuContext.store;

return (
<Styled.Item
ref={ ref }
{ ...props }
accessibleWhenDisabled
disabled={ disabled }
hideOnClick={ hideOnClick }
store={ computedStore }
hideOnClick={ computedHideOnClick }
>
<Styled.ItemPrefixWrapper>{ prefix }</Styled.ItemPrefixWrapper>

Expand Down
12 changes: 7 additions & 5 deletions packages/components/src/menu/radio-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { WordPressComponentProps } from '../context';
import { Context } from './context';
import type { RadioItemProps } from './types';
import * as Styled from './styles';
import { useMenuItemHideOnClick } from './use-menu-item-hide-on-click';

const radioCheck = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
Expand All @@ -21,24 +22,25 @@ export const RadioItem = forwardRef<
ref
) {
const menuContext = useContext( Context );
const store = menuContext?.store;
const computedHideOnClick = useMenuItemHideOnClick( store, hideOnClick );

if ( ! menuContext?.store ) {
if ( ! store ) {
throw new Error(
'Menu.RadioItem can only be rendered inside a Menu component'
);
}

return (
<Styled.RadioItem
ref={ ref }
{ ...props }
accessibleWhenDisabled
disabled={ disabled }
hideOnClick={ hideOnClick }
store={ menuContext.store }
store={ store }
hideOnClick={ computedHideOnClick }
>
<Ariakit.MenuItemCheck
store={ menuContext.store }
store={ store }
render={ <Styled.ItemPrefixWrapper /> }
// Override some ariakit inline styles
style={ { width: 'auto', height: 'auto' } }
Expand Down
55 changes: 8 additions & 47 deletions packages/components/src/menu/stories/index.story.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { StoryObj, Meta } from '@storybook/react-vite';
import { css } from '@emotion/react';
import { fn } from 'storybook/test';
import { customLink, formatCapitalize } from '@wordpress/icons';
import { useState, useMemo, useContext } from '@wordpress/element';
import { useCx } from '../../utils';
import { Menu } from '..';
import Icon from '../../icon';
import Button from '../../button';
Expand Down Expand Up @@ -390,19 +388,9 @@ export const WithRadios: StoryObj< typeof Menu > = {
},
};

const modalOnTopOfMenuPopover = css`
&& {
z-index: 1000000;
}
`;

export const WithModals: StoryObj< typeof Menu > = {
render: function WithModals( props: Props ) {
const [ isOuterModalOpen, setOuterModalOpen ] = useState( false );
const [ isInnerModalOpen, setInnerModalOpen ] = useState( false );

const cx = useCx();
const modalOverlayClassName = cx( modalOnTopOfMenuPopover );
export const WithModal: StoryObj< typeof Menu > = {
render: function WithModal( props: Props ) {
const [ isModalOpen, setModalOpen ] = useState( false );

return (
<>
Expand All @@ -415,42 +403,15 @@ export const WithModals: StoryObj< typeof Menu > = {
Open menu
</Menu.TriggerButton>
<Menu.Popover>
<Menu.Item
onClick={ () => setOuterModalOpen( true ) }
hideOnClick={ false }
>
<Menu.ItemLabel>Open outer modal</Menu.ItemLabel>
<Menu.Item onClick={ () => setModalOpen( true ) }>
<Menu.ItemLabel>Open modal</Menu.ItemLabel>
</Menu.Item>
<Menu.Item
onClick={ () => setInnerModalOpen( true ) }
hideOnClick={ false }
>
<Menu.ItemLabel>Open inner modal</Menu.ItemLabel>
</Menu.Item>
{ isInnerModalOpen && (
<Modal
onRequestClose={ () =>
setInnerModalOpen( false )
}
overlayClassName={ modalOverlayClassName }
>
Modal&apos;s contents
<button
onClick={ () => setInnerModalOpen( false ) }
>
Close
</button>
</Modal>
) }
</Menu.Popover>
</Menu>
{ isOuterModalOpen && (
<Modal
onRequestClose={ () => setOuterModalOpen( false ) }
overlayClassName={ modalOverlayClassName }
>
{ isModalOpen && (
<Modal onRequestClose={ () => setModalOpen( false ) }>
Modal&apos;s contents
<button onClick={ () => setOuterModalOpen( false ) }>
<button onClick={ () => setModalOpen( false ) }>
Close
</button>
</Modal>
Expand Down
Loading
Loading