Skip to content

Commit

Permalink
fix(rac): Tooltip positioning is incorrect
Browse files Browse the repository at this point in the history
Change-Id: Ie3f9605c28c7f9fed24ba5764f20e4680ee44802
GitOrigin-RevId: 9ca8e6d9400720d07c0ad4d4a0c6a546f7c22ece
  • Loading branch information
sarahsga authored and actions-user committed Oct 29, 2024
1 parent 34b96b0 commit c4f748d
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 68 deletions.
46 changes: 24 additions & 22 deletions plasmicpkgs/react-aria/src/registerButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,32 @@ interface BaseButtonProps
submitsForm?: boolean;
}

export function BaseButton(props: BaseButtonProps) {
const { submitsForm, resetsForm, children, plasmicUpdateVariant, ...rest } =
props;
const BaseButton = React.forwardRef(
(props: BaseButtonProps, ref: React.Ref<HTMLButtonElement>) => {
const { submitsForm, resetsForm, children, plasmicUpdateVariant, ...rest } =
props;

const type = submitsForm ? "submit" : resetsForm ? "reset" : "button";
const type = submitsForm ? "submit" : resetsForm ? "reset" : "button";

return (
<Button type={type} {...rest}>
{({ isHovered, isPressed, isFocused, isFocusVisible, isDisabled }) =>
withObservedValues(
children,
{
hovered: isHovered,
pressed: isPressed,
focused: isFocused,
focusVisible: isFocusVisible,
disabled: isDisabled,
},
plasmicUpdateVariant
)
}
</Button>
);
}
return (
<Button type={type} ref={ref} {...rest}>
{({ isHovered, isPressed, isFocused, isFocusVisible, isDisabled }) =>
withObservedValues(
children,
{
hovered: isHovered,
pressed: isPressed,
focused: isFocused,
focusVisible: isFocusVisible,
disabled: isDisabled,
},
plasmicUpdateVariant
)
}
</Button>
);
}
);

export const BUTTON_COMPONENT_NAME = makeComponentName("button");

Expand Down
131 changes: 85 additions & 46 deletions plasmicpkgs/react-aria/src/registerTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,77 +1,97 @@
import { usePlasmicCanvasComponentInfo } from "@plasmicapp/host";
import { mergeProps } from "@react-aria/utils";
import React from "react";
import { useTooltipTrigger } from "react-aria";
import { TooltipProps } from "react-aria-components";
import { AriaButtonProps, useButton } from "react-aria";
import { Tooltip, TooltipProps, TooltipTrigger } from "react-aria-components";
import flattenChildren from "react-keyed-flatten-children";
import { TooltipTriggerProps, useTooltipTriggerState } from "react-stately";
import { TooltipTriggerProps } from "react-stately";
import {
CodeComponentMetaOverrides,
Registerable,
registerComponentHelper,
} from "./utils";

function isForwardRefComponent(element: any): element is React.ReactElement {
return element?.type?.$$typeof === Symbol.for("react.forward_ref");
}

export interface BaseTooltipProps extends TooltipTriggerProps, TooltipProps {
children?: React.ReactElement<HTMLElement>;
tooltipContent?: React.ReactElement;
resetClassName?: string;
className?: string;
}

function TooltipButton(props: AriaButtonProps) {
const ref = React.useRef<HTMLButtonElement | null>(null);
const { buttonProps } = useButton(props, ref);
const { children } = props;
if (!isForwardRefComponent(children)) {
// The tooltip will not be triggered because the trigger component needs to be a forward ref.
return children;
}

return React.cloneElement(children, {
...buttonProps,
ref,
});
}

export function BaseTooltip(props: BaseTooltipProps) {
const { children, tooltipContent, className, resetClassName, ...restProps } =
props;
const {
children,
isDisabled,
delay,
closeDelay,
trigger,
isOpen,
defaultOpen,
tooltipContent,
resetClassName,
placement,
offset,
crossOffset,
shouldFlip,
arrowBoundaryOffset,
className,
onOpenChange,
} = props;

const { isSelected, selectedSlotName } =
usePlasmicCanvasComponentInfo(props) ?? {};
const isAutoOpen = selectedSlotName !== "children" && isSelected;

const state = useTooltipTriggerState(restProps);
const ref = React.useRef(null);
const { triggerProps, tooltipProps } = useTooltipTrigger(
restProps,
state,
ref
);

const hasContent =
tooltipContent &&
(tooltipContent.type as any).name !== "CanvasSlotPlaceholder";

/** We are only accepting a single child here, so we can just use the first one.
* This is because the trigger props will be applied to the child to enable the triggering of the tooltip.
* If there has to be more than one things here, wrap them in a horizontal stack for instance.
* */
const focusableChild = flattenChildren(children)[0];
const _isOpen = isAutoOpen || isOpen;

return (
<div
// this is to ensure that the absolutely positioned tooltip can be positioned correctly within this relatively positioned container.
style={{ position: "relative" }}
className={resetClassName}
<TooltipTrigger
isDisabled={isDisabled}
delay={delay}
closeDelay={closeDelay}
trigger={trigger}
isOpen={_isOpen}
defaultOpen={defaultOpen}
onOpenChange={onOpenChange}
>
{React.isValidElement(focusableChild)
? React.cloneElement(focusableChild, {
ref,
...mergeProps(
focusableChild.props as Record<string, any>,
triggerProps
),
} as Record<string, any> & { ref?: React.Ref<HTMLElement> })
: null}
{(isAutoOpen || state.isOpen) && (
<>
{React.cloneElement(
hasContent ? (
tooltipContent
) : (
<p>Add some content to the tooltip...</p>
),
mergeProps(tooltipProps, tooltipContent?.props.attrs, { className })
)}
</>
)}
</div>
<TooltipButton>{focusableChild}</TooltipButton>
<Tooltip
isOpen={_isOpen}
offset={offset}
crossOffset={crossOffset}
shouldFlip={shouldFlip}
arrowBoundaryOffset={arrowBoundaryOffset}
defaultOpen={defaultOpen}
className={`${className} ${resetClassName}`}
onOpenChange={onOpenChange}
placement={placement}
>
{tooltipContent}
</Tooltip>
</TooltipTrigger>
);
}

Expand All @@ -92,11 +112,14 @@ export function registerTooltip(
props: {
children: {
type: "slot",
displayName: "Trigger",
mergeWithParent: true,
displayName: "Trigger",
defaultValue: {
type: "text",
value: "Hover me!",
styles: {
width: "hug",
},
},
},
tooltipContent: {
Expand Down Expand Up @@ -131,14 +154,30 @@ export function registerTooltip(
options: ["focus", "focus and hover"],
defaultValueHint: "focus and hover",
},
placement: {
type: "choice",
description:
"Default placement of the popover relative to the trigger, if there is enough space",
defaultValueHint: "bottom",
options: ["top", "bottom", "left", "right"],
},
isOpen: {
type: "boolean",
editOnly: true,
uncontrolledProp: "defaultOpen",
description: "Whether the overlay is open by default",
defaultValueHint: false,
hidden: () => true,
},
onOpenChange: {
type: "eventHandler",
argTypes: [{ name: "isOpen", type: "boolean" }],
},
},
states: {
isOpen: {
type: "readonly",
type: "writable",
valueProp: "isOpen",
onChangeProp: "onOpenChange",
variableType: "boolean",
},
Expand Down

0 comments on commit c4f748d

Please sign in to comment.