Skip to content

Commit

Permalink
Expose arrowBoundaryOffset and internalize arrowSize calculation…
Browse files Browse the repository at this point in the history
… on RAC `<Popover />` and `<Tooltip />` (#5936)

Expose `arrowBoundaryOffset ` and internalize `arrowSize` calculation on RAC `<Popover />` and `<Tooltip />` (#5936)
  • Loading branch information
sookmax authored Mar 15, 2024
1 parent df1cf46 commit 0a84ded
Show file tree
Hide file tree
Showing 4 changed files with 585 additions and 11 deletions.
21 changes: 16 additions & 5 deletions packages/react-aria-components/src/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@

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

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

function PopoverInner({state, isExiting, UNSTABLE_portalContainer, ...props}: PopoverInnerProps) {
// Calculate the arrow size internally (and remove props.arrowSize from PopoverProps)
// Referenced from: packages/@react-spectrum/tooltip/src/TooltipTrigger.tsx
let arrowRef = useRef<HTMLDivElement>(null);
let [arrowWidth, setArrowWidth] = useState(0);
useLayoutEffect(() => {
if (arrowRef.current && state.isOpen) {
setArrowWidth(arrowRef.current.getBoundingClientRect().width);
}
}, [state.isOpen, arrowRef]);

let {popoverProps, underlayProps, arrowProps, placement} = usePopover({
...props,
offset: props.offset ?? 8
offset: props.offset ?? 8,
arrowSize: arrowWidth
}, state);

let ref = props.popoverRef as RefObject<HTMLDivElement>;
Expand Down Expand Up @@ -164,7 +175,7 @@ function PopoverInner({state, isExiting, UNSTABLE_portalContainer, ...props}: Po
data-entering={isEntering || undefined}
data-exiting={isExiting || undefined}>
{!props.isNonModal && <DismissButton onDismiss={state.close} />}
<OverlayArrowContext.Provider value={{...arrowProps, placement}}>
<OverlayArrowContext.Provider value={{...arrowProps, placement, ref: arrowRef}}>
{renderProps.children}
</OverlayArrowContext.Provider>
<DismissButton onDismiss={state.close} />
Expand Down
23 changes: 18 additions & 5 deletions packages/react-aria-components/src/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@
*/

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

export interface TooltipTriggerComponentProps extends TooltipTriggerProps {
children: ReactNode
}

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

// Calculate the arrow size internally
// Referenced from: packages/@react-spectrum/tooltip/src/TooltipTrigger.tsx
let arrowRef = useRef<HTMLDivElement>(null);
let [arrowWidth, setArrowWidth] = useState(0);
useLayoutEffect(() => {
if (arrowRef.current && state.isOpen) {
setArrowWidth(arrowRef.current.getBoundingClientRect().width);
}
}, [state.isOpen, arrowRef]);

let {overlayProps, arrowProps, placement} = useOverlayPosition({
placement: props.placement || 'top',
targetRef: props.triggerRef!,
overlayRef: props.tooltipRef,
offset: props.offset,
crossOffset: props.crossOffset,
isOpen: state.isOpen
isOpen: state.isOpen,
arrowSize: arrowWidth,
arrowBoundaryOffset: props.arrowBoundaryOffset
});

let isEntering = useEnterAnimation(props.tooltipRef, !!placement) || props.isEntering || false;
Expand All @@ -151,7 +164,7 @@ function TooltipInner(props: TooltipProps & {isExiting: boolean, tooltipRef: Ref
data-placement={placement}
data-entering={isEntering || undefined}
data-exiting={props.isExiting || undefined}>
<OverlayArrowContext.Provider value={{...arrowProps, placement}}>
<OverlayArrowContext.Provider value={{...arrowProps, placement, ref: arrowRef}}>
{renderProps.children}
</OverlayArrowContext.Provider>
</div>
Expand Down
Loading

1 comment on commit 0a84ded

@rspbot
Copy link

@rspbot rspbot commented on 0a84ded Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.