Skip to content

Commit b95f766

Browse files
authored
Use PressResponder in RAC overlays and document MenuTrigger long press (#5065)
1 parent a77030a commit b95f766

File tree

7 files changed

+48
-21
lines changed

7 files changed

+48
-21
lines changed

packages/@react-aria/interactions/src/PressResponder.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,11 @@ export const PressResponder = React.forwardRef(({children, ...props}: PressRespo
5252
</PressResponderContext.Provider>
5353
);
5454
});
55+
56+
export function ClearPressResponder({children}: {children: ReactNode}) {
57+
return (
58+
<PressResponderContext.Provider value={undefined}>
59+
{children}
60+
</PressResponderContext.Provider>
61+
);
62+
}

packages/@react-aria/interactions/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
export {Pressable} from './Pressable';
14-
export {PressResponder} from './PressResponder';
14+
export {PressResponder, ClearPressResponder} from './PressResponder';
1515
export {useFocus} from './useFocus';
1616
export {
1717
isFocusVisible,

packages/@react-aria/overlays/src/Overlay.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13+
import {ClearPressResponder} from '@react-aria/interactions';
1314
import {FocusScope} from '@react-aria/focus';
1415
import React, {ReactNode, useContext, useMemo, useState} from 'react';
1516
import ReactDOM from 'react-dom';
@@ -53,23 +54,23 @@ export function Overlay(props: OverlayProps) {
5354
return null;
5455
}
5556

56-
let contents;
57+
let contents = props.children;
5758
if (!props.disableFocusManagement) {
5859
contents = (
59-
<OverlayContext.Provider value={contextValue}>
60-
<FocusScope restoreFocus contain={contain && !isExiting}>
61-
{props.children}
62-
</FocusScope>
63-
</OverlayContext.Provider>
64-
);
65-
} else {
66-
contents = (
67-
<OverlayContext.Provider value={contextValue}>
68-
{props.children}
69-
</OverlayContext.Provider>
60+
<FocusScope restoreFocus contain={contain && !isExiting}>
61+
{contents}
62+
</FocusScope>
7063
);
7164
}
7265

66+
contents = (
67+
<OverlayContext.Provider value={contextValue}>
68+
<ClearPressResponder>
69+
{contents}
70+
</ClearPressResponder>
71+
</OverlayContext.Provider>
72+
);
73+
7374
return ReactDOM.createPortal(contents, portalContainer);
7475
}
7576

packages/@react-aria/utils/src/useSyncRef.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ export function useSyncRef<T>(context: ContextValue<T>, ref: RefObject<T>) {
2626
context.ref.current = null;
2727
};
2828
}
29-
}, [context, ref]);
29+
});
3030
}

packages/react-aria-components/docs/Menu.mdx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,23 @@ import {Text, Keyboard} from 'react-aria-components';
594594
</MyMenuButton>
595595
```
596596

597+
## Long press
598+
599+
By default, MenuTrigger opens by pressing the trigger element or activating it via the <Keyboard>Space</Keyboard> or <Keyboard>Enter</Keyboard> keys. However, there may be cases in which your trigger element should perform a separate default action on press, and should only display the Menu when long pressed. This behavior can be changed by providing `"longPress"` to the `trigger` prop. With this prop, the Menu will only be opened upon pressing and holding the trigger element or by using the <Keyboard>Option</Keyboard> (<Keyboard>Alt</Keyboard> on Windows) + <Keyboard>Down Arrow</Keyboard>/<Keyboard>Up Arrow</Keyboard> keys while focusing the trigger element.
600+
601+
```tsx example
602+
<MenuTrigger trigger="longPress">
603+
<Button onPress={() => alert('crop')}>Crop</Button>
604+
<Popover>
605+
<Menu onAction={alert}>
606+
<Item id="rotate">Rotate</Item>
607+
<Item id="slice">Slice</Item>
608+
<Item id="clone-stamp">Clone stamp</Item>
609+
</Menu>
610+
</Popover>
611+
</MenuTrigger>
612+
```
613+
597614
## Disabled items
598615

599616
`Menu` supports marking items as disabled using the `disabledKeys` prop. Each key in this list

packages/react-aria-components/src/Dialog.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212
import {AriaDialogProps, useDialog, useOverlayTrigger} from 'react-aria';
13-
import {ButtonContext} from './Button';
1413
import {ContextValue, forwardRefType, Provider, SlotProps, StyleProps, useContextProps} from './utils';
1514
import {filterDOMProps} from '@react-aria/utils';
1615
import {HeadingContext} from './Heading';
1716
import {OverlayTriggerProps, OverlayTriggerState, useOverlayTriggerState} from 'react-stately';
1817
import {PopoverContext} from './Popover';
18+
import {PressResponder} from '@react-aria/interactions';
1919
import React, {createContext, ForwardedRef, forwardRef, ReactNode, useContext, useRef} from 'react';
2020

2121
export interface DialogTriggerProps extends OverlayTriggerProps {
@@ -48,10 +48,11 @@ export function DialogTrigger(props: DialogTriggerProps) {
4848
values={[
4949
[OverlayTriggerStateContext, state],
5050
[DialogContext, overlayProps],
51-
[ButtonContext, {...triggerProps, isPressed: state.isOpen, ref: buttonRef}],
5251
[PopoverContext, {triggerRef: buttonRef}]
5352
]}>
54-
{props.children}
53+
<PressResponder {...triggerProps} ref={buttonRef} isPressed={state.isOpen}>
54+
{props.children}
55+
</PressResponder>
5556
</Provider>
5657
);
5758
}
@@ -78,7 +79,6 @@ function Dialog(props: DialogProps, ref: ForwardedRef<HTMLElement>) {
7879
className={props.className ?? 'react-aria-Dialog'}>
7980
<Provider
8081
values={[
81-
[ButtonContext, undefined],
8282
// TODO: clear context within dialog content?
8383
[HeadingContext, {...titleProps, level: 2}]
8484
]}>

packages/react-aria-components/src/Menu.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
import {AriaMenuProps, mergeProps, useFocusRing, useMenu, useMenuItem, useMenuSection, useMenuTrigger} from 'react-aria';
1515
import {BaseCollection, CollectionProps, ItemProps, useCachedChildren, useCollection} from './Collection';
1616
import {MenuTriggerProps as BaseMenuTriggerProps, Node, TreeState, useMenuTriggerState, useTreeState} from 'react-stately';
17-
import {ButtonContext} from './Button';
1817
import {ContextValue, forwardRefType, Provider, SlotProps, StyleProps, useContextProps, useRenderProps, useSlot} from './utils';
1918
import {filterDOMProps, mergeRefs, useObjectRef} from '@react-aria/utils';
2019
import {Header} from './Header';
2120
import {KeyboardContext} from './Keyboard';
2221
import {OverlayTriggerStateContext} from './Dialog';
2322
import {PopoverContext} from './Popover';
23+
import {PressResponder} from '@react-aria/interactions';
2424
import React, {createContext, ForwardedRef, forwardRef, ReactNode, RefObject, useContext, useRef} from 'react';
2525
import {Separator, SeparatorContext} from './Separator';
2626
import {TextContext} from './Text';
@@ -45,11 +45,12 @@ export function MenuTrigger(props: MenuTriggerProps) {
4545
<Provider
4646
values={[
4747
[MenuContext, menuProps],
48-
[ButtonContext, {...menuTriggerProps, ref, isPressed: state.isOpen}],
4948
[OverlayTriggerStateContext, state],
5049
[PopoverContext, {triggerRef: ref, placement: 'bottom start'}]
5150
]}>
52-
{props.children}
51+
<PressResponder {...menuTriggerProps} ref={ref} isPressed={state.isOpen}>
52+
{props.children}
53+
</PressResponder>
5354
</Provider>
5455
);
5556
}

0 commit comments

Comments
 (0)