Skip to content

Commit 0a84ded

Browse files
authored
Expose arrowBoundaryOffset and internalize arrowSize calculation on RAC <Popover /> and <Tooltip /> (#5936)
Expose `arrowBoundaryOffset ` and internalize `arrowSize` calculation on RAC `<Popover />` and `<Tooltip />` (#5936)
1 parent df1cf46 commit 0a84ded

File tree

4 files changed

+585
-11
lines changed

4 files changed

+585
-11
lines changed

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212

1313
import {AriaPopoverProps, DismissButton, Overlay, PlacementAxis, PositionProps, usePopover} from 'react-aria';
1414
import {ContextValue, forwardRefType, HiddenContext, RenderProps, SlotProps, useContextProps, useEnterAnimation, useExitAnimation, useRenderProps} from './utils';
15-
import {filterDOMProps, mergeProps} from '@react-aria/utils';
15+
import {filterDOMProps, mergeProps, useLayoutEffect} from '@react-aria/utils';
1616
import {OverlayArrowContext} from './OverlayArrow';
1717
import {OverlayTriggerProps, OverlayTriggerState, useOverlayTriggerState} from 'react-stately';
1818
import {OverlayTriggerStateContext} from './Dialog';
19-
import React, {createContext, ForwardedRef, forwardRef, RefObject, useContext} from 'react';
19+
import React, {createContext, ForwardedRef, forwardRef, RefObject, useContext, useRef, useState} from 'react';
2020

21-
export interface PopoverProps extends Omit<PositionProps, 'isOpen'>, Omit<AriaPopoverProps, 'popoverRef' | 'triggerRef' | 'offset'>, OverlayTriggerProps, RenderProps<PopoverRenderProps>, SlotProps {
21+
export interface PopoverProps extends Omit<PositionProps, 'isOpen'>, Omit<AriaPopoverProps, 'popoverRef' | 'triggerRef' | 'offset' | 'arrowSize'>, OverlayTriggerProps, RenderProps<PopoverRenderProps>, SlotProps {
2222
/**
2323
* The name of the component that triggered the popover. This is reflected on the element
2424
* as the `data-trigger` attribute, and can be used to provide specific
@@ -130,9 +130,20 @@ interface PopoverInnerProps extends AriaPopoverProps, RenderProps<PopoverRenderP
130130
}
131131

132132
function PopoverInner({state, isExiting, UNSTABLE_portalContainer, ...props}: PopoverInnerProps) {
133+
// Calculate the arrow size internally (and remove props.arrowSize from PopoverProps)
134+
// Referenced from: packages/@react-spectrum/tooltip/src/TooltipTrigger.tsx
135+
let arrowRef = useRef<HTMLDivElement>(null);
136+
let [arrowWidth, setArrowWidth] = useState(0);
137+
useLayoutEffect(() => {
138+
if (arrowRef.current && state.isOpen) {
139+
setArrowWidth(arrowRef.current.getBoundingClientRect().width);
140+
}
141+
}, [state.isOpen, arrowRef]);
142+
133143
let {popoverProps, underlayProps, arrowProps, placement} = usePopover({
134144
...props,
135-
offset: props.offset ?? 8
145+
offset: props.offset ?? 8,
146+
arrowSize: arrowWidth
136147
}, state);
137148

138149
let ref = props.popoverRef as RefObject<HTMLDivElement>;
@@ -164,7 +175,7 @@ function PopoverInner({state, isExiting, UNSTABLE_portalContainer, ...props}: Po
164175
data-entering={isEntering || undefined}
165176
data-exiting={isExiting || undefined}>
166177
{!props.isNonModal && <DismissButton onDismiss={state.close} />}
167-
<OverlayArrowContext.Provider value={{...arrowProps, placement}}>
178+
<OverlayArrowContext.Provider value={{...arrowProps, placement, ref: arrowRef}}>
168179
{renderProps.children}
169180
</OverlayArrowContext.Provider>
170181
<DismissButton onDismiss={state.close} />

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,19 @@
1111
*/
1212

1313
import {AriaLabelingProps, FocusableElement} from '@react-types/shared';
14+
import {AriaPositionProps, mergeProps, OverlayContainer, PlacementAxis, PositionProps, useOverlayPosition, useTooltip, useTooltipTrigger} from 'react-aria';
1415
import {ContextValue, forwardRefType, Provider, RenderProps, useContextProps, useEnterAnimation, useExitAnimation, useRenderProps} from './utils';
1516
import {FocusableProvider} from '@react-aria/focus';
16-
import {mergeProps, OverlayContainer, PlacementAxis, PositionProps, useOverlayPosition, useTooltip, useTooltipTrigger} from 'react-aria';
1717
import {OverlayArrowContext} from './OverlayArrow';
1818
import {OverlayTriggerProps, TooltipTriggerProps, TooltipTriggerState, useTooltipTriggerState} from 'react-stately';
19-
import React, {createContext, ForwardedRef, forwardRef, ReactNode, RefObject, useContext, useRef} from 'react';
19+
import React, {createContext, ForwardedRef, forwardRef, ReactNode, RefObject, useContext, useRef, useState} from 'react';
20+
import {useLayoutEffect} from '@react-aria/utils';
2021

2122
export interface TooltipTriggerComponentProps extends TooltipTriggerProps {
2223
children: ReactNode
2324
}
2425

25-
export interface TooltipProps extends PositionProps, OverlayTriggerProps, AriaLabelingProps, RenderProps<TooltipRenderProps> {
26+
export interface TooltipProps extends PositionProps, Pick<AriaPositionProps, 'arrowBoundaryOffset'>, OverlayTriggerProps, AriaLabelingProps, RenderProps<TooltipRenderProps> {
2627
/**
2728
* The ref for the element which the tooltip positions itself with respect to.
2829
*
@@ -118,13 +119,25 @@ export {_Tooltip as Tooltip};
118119
function TooltipInner(props: TooltipProps & {isExiting: boolean, tooltipRef: RefObject<HTMLDivElement>}) {
119120
let state = useContext(TooltipTriggerStateContext)!;
120121

122+
// Calculate the arrow size internally
123+
// Referenced from: packages/@react-spectrum/tooltip/src/TooltipTrigger.tsx
124+
let arrowRef = useRef<HTMLDivElement>(null);
125+
let [arrowWidth, setArrowWidth] = useState(0);
126+
useLayoutEffect(() => {
127+
if (arrowRef.current && state.isOpen) {
128+
setArrowWidth(arrowRef.current.getBoundingClientRect().width);
129+
}
130+
}, [state.isOpen, arrowRef]);
131+
121132
let {overlayProps, arrowProps, placement} = useOverlayPosition({
122133
placement: props.placement || 'top',
123134
targetRef: props.triggerRef!,
124135
overlayRef: props.tooltipRef,
125136
offset: props.offset,
126137
crossOffset: props.crossOffset,
127-
isOpen: state.isOpen
138+
isOpen: state.isOpen,
139+
arrowSize: arrowWidth,
140+
arrowBoundaryOffset: props.arrowBoundaryOffset
128141
});
129142

130143
let isEntering = useEnterAnimation(props.tooltipRef, !!placement) || props.isEntering || false;
@@ -151,7 +164,7 @@ function TooltipInner(props: TooltipProps & {isExiting: boolean, tooltipRef: Ref
151164
data-placement={placement}
152165
data-entering={isEntering || undefined}
153166
data-exiting={props.isExiting || undefined}>
154-
<OverlayArrowContext.Provider value={{...arrowProps, placement}}>
167+
<OverlayArrowContext.Provider value={{...arrowProps, placement, ref: arrowRef}}>
155168
{renderProps.children}
156169
</OverlayArrowContext.Provider>
157170
</div>

0 commit comments

Comments
 (0)