-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathuse-aria-popover.ts
126 lines (117 loc) · 3.15 KB
/
use-aria-popover.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import {RefObject, useEffect} from "react";
import {
AriaPopoverProps,
useOverlay,
PopoverAria,
useOverlayPosition,
AriaOverlayProps,
} from "@react-aria/overlays";
import {
OverlayPlacement,
ariaHideOutside,
toReactAriaPlacement,
ariaShouldCloseOnInteractOutside,
} from "@nextui-org/aria-utils";
import {OverlayTriggerState} from "@react-stately/overlays";
import {mergeProps} from "@react-aria/utils";
import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect";
export interface Props {
/**
* Whether the element should render an arrow.
* @default false
*/
showArrow?: boolean;
/**
* The placement of the element with respect to its anchor element.
* @default 'top'
*/
placement?: OverlayPlacement;
/**
* A ref for the scrollable region within the overlay.
* @default popoverRef
*/
scrollRef?: RefObject<HTMLElement>;
/**
* List of dependencies to update the position of the popover.
* @default []
*/
updatePositionDeps?: any[];
}
export type ReactAriaPopoverProps = Props & Omit<AriaPopoverProps, "placement"> & AriaOverlayProps;
/**
* Provides the behavior and accessibility implementation for a popover component.
* A popover is an overlay element positioned relative to a trigger.
*/
export function useReactAriaPopover(
props: ReactAriaPopoverProps,
state: OverlayTriggerState,
): PopoverAria {
const {
triggerRef,
popoverRef,
showArrow,
offset = 7,
crossOffset = 0,
scrollRef,
shouldFlip,
boundaryElement,
isDismissable = true,
shouldCloseOnBlur = true,
placement: placementProp = "top",
containerPadding,
shouldCloseOnInteractOutside,
isNonModal: isNonModalProp,
isKeyboardDismissDisabled,
updatePositionDeps = [],
...otherProps
} = props;
const isNonModal = isNonModalProp ?? true;
const {overlayProps, underlayProps} = useOverlay(
{
isOpen: state.isOpen,
onClose: state.close,
shouldCloseOnBlur,
isDismissable,
isKeyboardDismissDisabled,
shouldCloseOnInteractOutside: shouldCloseOnInteractOutside
? shouldCloseOnInteractOutside
: (element: Element) => ariaShouldCloseOnInteractOutside(element, triggerRef, state),
},
popoverRef,
);
const {
overlayProps: positionProps,
arrowProps,
placement,
updatePosition,
} = useOverlayPosition({
...otherProps,
shouldFlip,
crossOffset,
targetRef: triggerRef,
overlayRef: popoverRef,
isOpen: state.isOpen,
scrollRef,
boundaryElement,
containerPadding,
placement: toReactAriaPlacement(placementProp),
offset: showArrow ? offset + 3 : offset,
onClose: isNonModal ? state.close : () => {},
});
useSafeLayoutEffect(() => {
if (!updatePositionDeps.length) return;
// force update position when deps change
updatePosition();
}, updatePositionDeps);
useEffect(() => {
if (state.isOpen && !isNonModal && popoverRef.current) {
return ariaHideOutside([popoverRef.current]);
}
}, [isNonModal, state.isOpen, popoverRef]);
return {
popoverProps: mergeProps(overlayProps, positionProps),
arrowProps,
underlayProps,
placement,
};
}