Skip to content

Commit

Permalink
[Menu] Fix openOnHover issues (#1191)
Browse files Browse the repository at this point in the history
  • Loading branch information
atomiks authored Jan 8, 2025
1 parent 7c95b65 commit 1eec235
Show file tree
Hide file tree
Showing 19 changed files with 253 additions and 190 deletions.
4 changes: 4 additions & 0 deletions docs/reference/generated/menu-popup.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"data-closed": {
"description": "Present when the menu is closed."
},
"data-instant": {
"description": "Present if animations should be instant.",
"type": "'click' | 'dismiss'"
},
"data-side": {
"description": "Indicates which side the menu is positioned relative to the trigger.",
"type": "'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'"
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/generated/popover-popup.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
"data-closed": {
"description": "Present when the popup is closed."
},
"data-instant": {
"description": "Present if animations should be instant.",
"type": "'click' | 'dismiss'"
},
"data-side": {
"description": "Indicates which side the popup is positioned relative to the trigger.",
"type": "'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'"
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/generated/tooltip-popup.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"description": "Present when the tooltip is closed."
},
"data-instant": {
"description": "Indicates the instant type of the tooltip popup.",
"description": "Present if animations should be instant.",
"type": "'delay' | 'dismiss' | 'focus'"
},
"data-side": {
Expand Down
32 changes: 1 addition & 31 deletions packages/react/src/menu/checkbox-item/MenuCheckboxItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,8 @@ import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
import { fireEvent, act, waitFor } from '@mui/internal-test-utils';
import { FloatingRootContext, FloatingTree } from '@floating-ui/react';
import { Menu } from '@base-ui-components/react/menu';
import { describeConformance, createRenderer } from '../../../test';
import { MenuRootContext } from '../root/MenuRootContext';

const testRootContext: MenuRootContext = {
floatingRootContext: {} as FloatingRootContext,
getPopupProps: (p) => ({ ...p }),
getTriggerProps: (p) => ({ ...p }),
getItemProps: (p) => ({ ...p }),
parentContext: undefined,
nested: false,
setTriggerElement: () => {},
setPositionerElement: () => {},
activeIndex: null,
disabled: false,
itemDomElements: { current: [] },
itemLabels: { current: [] },
open: true,
setOpen: () => {},
popupRef: { current: null },
mounted: true,
transitionStatus: undefined,
typingRef: { current: false },
modal: false,
positionerRef: { current: null },
allowMouseUpTriggerRef: { current: false },
};

describe('<Menu.CheckboxItem />', () => {
const { render, clock } = createRenderer({
Expand All @@ -42,11 +16,7 @@ describe('<Menu.CheckboxItem />', () => {

describeConformance(<Menu.CheckboxItem />, () => ({
render: (node) => {
return render(
<FloatingTree>
<MenuRootContext.Provider value={testRootContext}>{node}</MenuRootContext.Provider>
</FloatingTree>,
);
return render(<Menu.Root open>{node}</Menu.Root>);
},
refInstanceof: window.HTMLDivElement,
}));
Expand Down
32 changes: 1 addition & 31 deletions packages/react/src/menu/item/MenuItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,8 @@ import { expect } from 'chai';
import { spy } from 'sinon';
import { MemoryRouter, Route, Routes, Link, useLocation } from 'react-router-dom';
import { act, screen, waitFor } from '@mui/internal-test-utils';
import { FloatingRootContext, FloatingTree } from '@floating-ui/react';
import { Menu } from '@base-ui-components/react/menu';
import { describeConformance, createRenderer } from '#test-utils';
import { MenuRootContext } from '../root/MenuRootContext';

const testRootContext: MenuRootContext = {
floatingRootContext: {} as FloatingRootContext,
getPopupProps: (p) => ({ ...p }),
getTriggerProps: (p) => ({ ...p }),
getItemProps: (p) => ({ ...p }),
parentContext: undefined,
nested: false,
setTriggerElement: () => {},
setPositionerElement: () => {},
activeIndex: null,
disabled: false,
itemDomElements: { current: [] },
itemLabels: { current: [] },
open: true,
setOpen: () => {},
popupRef: { current: null },
mounted: true,
transitionStatus: undefined,
typingRef: { current: false },
modal: false,
positionerRef: { current: null },
allowMouseUpTriggerRef: { current: false },
};

describe('<Menu.Item />', () => {
const { render, clock } = createRenderer({
Expand All @@ -43,11 +17,7 @@ describe('<Menu.Item />', () => {

describeConformance(<Menu.Item />, () => ({
render: (node) => {
return render(
<FloatingTree>
<MenuRootContext.Provider value={testRootContext}>{node}</MenuRootContext.Provider>
</FloatingTree>,
);
return render(<Menu.Root open>{node}</Menu.Root>);
},
refInstanceof: window.HTMLDivElement,
}));
Expand Down
16 changes: 13 additions & 3 deletions packages/react/src/menu/popup/MenuPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,17 @@ const MenuPopup = React.forwardRef(function MenuPopup(
) {
const { render, className, ...other } = props;

const { open, setOpen, popupRef, transitionStatus, nested, getPopupProps, modal, mounted } =
useMenuRootContext();
const {
open,
setOpen,
popupRef,
transitionStatus,
nested,
getPopupProps,
modal,
mounted,
instantType,
} = useMenuRootContext();
const { side, align, floatingContext } = useMenuPositionerContext();

const { events: menuEvents } = useFloatingTree()!;
Expand All @@ -52,8 +61,9 @@ const MenuPopup = React.forwardRef(function MenuPopup(
align,
open,
nested,
instant: instantType,
}),
[transitionStatus, side, align, open, nested],
[transitionStatus, side, align, open, nested, instantType],
);

const { renderElement } = useComponentRenderer({
Expand Down
5 changes: 5 additions & 0 deletions packages/react/src/menu/popup/MenuPopupDataAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ export enum MenuPopupDataAttributes {
* @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}
*/
side = 'data-side',
/**
* Present if animations should be instant.
* @type {'click' | 'dismiss'}
*/
instant = 'data-instant',
}
34 changes: 3 additions & 31 deletions packages/react/src/menu/positioner/MenuPositioner.test.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,19 @@
import * as React from 'react';
import { expect } from 'chai';
import { FloatingRootContext, FloatingTree } from '@floating-ui/react';
import userEvent from '@testing-library/user-event';
import { flushMicrotasks } from '@mui/internal-test-utils';
import { Menu } from '@base-ui-components/react/menu';
import { describeConformance, createRenderer } from '#test-utils';
import { MenuRootContext } from '../root/MenuRootContext';

const testRootContext: MenuRootContext = {
floatingRootContext: undefined as unknown as FloatingRootContext,
getPopupProps: (p) => ({ ...p }),
getTriggerProps: (p) => ({ ...p }),
getItemProps: (p) => ({ ...p }),
parentContext: undefined,
nested: false,
setTriggerElement: () => {},
setPositionerElement: () => {},
activeIndex: null,
disabled: false,
itemDomElements: { current: [] },
itemLabels: { current: [] },
open: true,
setOpen: () => {},
popupRef: { current: null },
mounted: true,
transitionStatus: undefined,
typingRef: { current: false },
modal: false,
positionerRef: { current: null },
allowMouseUpTriggerRef: { current: false },
};

describe('<Menu.Positioner />', () => {
const { render } = createRenderer();

describeConformance(<Menu.Positioner />, () => ({
render: (node) => {
return render(
<FloatingTree>
<MenuRootContext.Provider value={testRootContext}>
<Menu.Portal>{node}</Menu.Portal>
</MenuRootContext.Provider>
</FloatingTree>,
<Menu.Root open>
<Menu.Portal>{node}</Menu.Portal>
</Menu.Root>,
);
},
refInstanceof: window.HTMLDivElement,
Expand Down
38 changes: 5 additions & 33 deletions packages/react/src/menu/radio-item/MenuRadioItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,9 @@ import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
import { fireEvent, act, waitFor } from '@mui/internal-test-utils';
import { FloatingRootContext, FloatingTree } from '@floating-ui/react';
import { Menu } from '@base-ui-components/react/menu';
import { describeConformance, createRenderer } from '#test-utils';
import { MenuRadioGroupContext } from '../radio-group/MenuRadioGroupContext';
import { MenuRootContext } from '../root/MenuRootContext';

const testRootContext: MenuRootContext = {
floatingRootContext: {} as FloatingRootContext,
getPopupProps: (p) => ({ ...p }),
getTriggerProps: (p) => ({ ...p }),
getItemProps: (p) => ({ ...p }),
parentContext: undefined,
nested: false,
setTriggerElement: () => {},
setPositionerElement: () => {},
activeIndex: null,
disabled: false,
itemDomElements: { current: [] },
itemLabels: { current: [] },
open: true,
setOpen: () => {},
popupRef: { current: null },
mounted: true,
transitionStatus: undefined,
typingRef: { current: false },
modal: false,
positionerRef: { current: null },
allowMouseUpTriggerRef: { current: false },
};

const testRadioGroupContext = {
value: '0',
Expand All @@ -49,13 +23,11 @@ describe('<Menu.RadioItem />', () => {
describeConformance(<Menu.RadioItem value="0" />, () => ({
render: (node) => {
return render(
<FloatingTree>
<MenuRootContext.Provider value={testRootContext}>
<MenuRadioGroupContext.Provider value={testRadioGroupContext}>
{node}
</MenuRadioGroupContext.Provider>
</MenuRootContext.Provider>
</FloatingTree>,
<Menu.Root open>
<MenuRadioGroupContext.Provider value={testRadioGroupContext}>
{node}
</MenuRadioGroupContext.Provider>
</Menu.Root>,
);
},
refInstanceof: window.HTMLDivElement,
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/menu/root/MenuRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FloatingTree } from '@floating-ui/react';
import { useDirection } from '../../direction-provider/DirectionContext';
import { MenuRootContext, useMenuRootContext } from './MenuRootContext';
import { MenuOrientation, useMenuRoot } from './useMenuRoot';
import type { OpenChangeReason } from '../../utils/translateOpenChangeReason';

/**
* Groups all parts of the menu.
Expand Down Expand Up @@ -107,7 +108,7 @@ namespace MenuRoot {
/**
* Event handler called when the menu is opened or closed.
*/
onOpenChange?: (open: boolean, event?: Event) => void;
onOpenChange?: (open: boolean, event?: Event, reason?: OpenChangeReason) => void;
/**
* Event handler called after any exit animations finish when the menu is closed.
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/menu/root/MenuRootContext.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
'use client';
import * as React from 'react';
import type { useMenuRoot } from './useMenuRoot';
import type { OpenChangeReason } from '../../utils/translateOpenChangeReason';

export interface MenuRootContext extends useMenuRoot.ReturnValue {
disabled: boolean;
nested: boolean;
parentContext: MenuRootContext | undefined;
typingRef: React.RefObject<boolean>;
modal: boolean;
openReason: OpenChangeReason | null;
}

export const MenuRootContext = React.createContext<MenuRootContext | undefined>(undefined);
Expand Down
Loading

0 comments on commit 1eec235

Please sign in to comment.