From 9520729bd7a012a9d09f46c16b09c64e890fae2d Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sun, 14 Apr 2024 08:31:53 +0530 Subject: [PATCH] feat: make popout openable with anchor coordinates instead of anchor element (#38) BREAKING CHANGE: popout children forward ref and open prop is now removed with anchor prop --- src/components/pop-out/PopOut.stories.tsx | 43 ++++++++++++++++------- src/components/pop-out/PopOut.tsx | 43 ++++++++--------------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/components/pop-out/PopOut.stories.tsx b/src/components/pop-out/PopOut.stories.tsx index f490a78..0f7b61e 100644 --- a/src/components/pop-out/PopOut.stories.tsx +++ b/src/components/pop-out/PopOut.stories.tsx @@ -1,5 +1,5 @@ import FocusTrap from "focus-trap-react"; -import React, { useState } from "react"; +import React, { MouseEventHandler, useState } from "react"; import { ComponentMeta, ComponentStory } from "@storybook/react"; import { Text } from "../text"; import { PopOut } from "./PopOut"; @@ -8,6 +8,7 @@ import { Icon, Icons } from "../icon"; import { IconButton } from "../icon-button"; import { config } from "../../theme/config.css"; import { Box } from "../box"; +import { RectCords } from "../util"; export default { title: "PopOut", @@ -15,18 +16,39 @@ export default { } as ComponentMeta; const Template: ComponentStory = (args) => { - const [open, setOpen] = useState(false); + const [anchor, setAnchor] = useState(); + + const handleOpen: MouseEventHandler = (evt) => { + const rect = evt.currentTarget?.getBoundingClientRect(); + setAnchor(anchor ? undefined : rect); + }; + const handleContextOpen: MouseEventHandler = (evt) => { + evt.preventDefault(); + const rect = { + x: evt.clientX, + y: evt.clientY, + width: 0, + height: 0, + }; + setAnchor(anchor ? undefined : rect); + }; return ( - + setOpen(false), + onDeactivate: () => setAnchor(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === "ArrowDown", isKeyBackward: (evt: KeyboardEvent) => evt.key === "ArrowUp", @@ -45,13 +67,10 @@ const Template: ComponentStory = (args) => { } - > - {(ref) => ( - setOpen((state) => !state)} ref={ref}> - - - )} - + /> + + + ); }; diff --git a/src/components/pop-out/PopOut.tsx b/src/components/pop-out/PopOut.tsx index 4543921..a90e196 100644 --- a/src/components/pop-out/PopOut.tsx +++ b/src/components/pop-out/PopOut.tsx @@ -1,32 +1,24 @@ import classNames from "classnames"; -import React, { - ReactNode, - RefCallback, - useCallback, - useEffect, - useLayoutEffect, - useRef, -} from "react"; +import React, { ReactNode, useCallback, useEffect, useLayoutEffect, useRef } from "react"; import { as } from "../as"; import { Portal } from "../portal"; -import { Align, getRelativeFixedPosition, Position } from "../util"; +import { Align, getRelativeFixedPosition, Position, RectCords } from "../util"; import * as css from "./PopOut.css"; export interface PopOutProps { - open: boolean; + anchor?: RectCords; position?: Position; align?: Align; offset?: number; alignOffset?: number; content: ReactNode; - children: (anchorRef: RefCallback) => ReactNode; } export const PopOut = as<"div", PopOutProps>( ( { as: AsPopOut = "div", className, - open, + anchor, position = "Bottom", align = "Center", offset = 10, @@ -37,17 +29,14 @@ export const PopOut = as<"div", PopOutProps>( }, ref ) => { - const anchorRef = useRef(null); const baseRef = useRef(null); const positionPopOut = useCallback(() => { - const anchor = anchorRef.current; const baseEl = baseRef.current; - if (!anchor) return; - if (!baseEl) return; + if (!baseEl || !anchor) return; const pCSS = getRelativeFixedPosition( - anchor.getBoundingClientRect(), + anchor, baseEl.getBoundingClientRect(), position, align, @@ -58,7 +47,7 @@ export const PopOut = as<"div", PopOutProps>( baseEl.style.bottom = pCSS.bottom ?? "unset"; baseEl.style.left = pCSS.left ?? "unset"; baseEl.style.right = pCSS.right ?? "unset"; - }, [position, align, offset, alignOffset]); + }, [anchor, position, align, offset, alignOffset]); useEffect(() => { window.addEventListener("resize", positionPopOut); @@ -68,25 +57,21 @@ export const PopOut = as<"div", PopOutProps>( }, [positionPopOut]); useLayoutEffect(() => { - if (open) positionPopOut(); - }, [open, positionPopOut]); - - const handleAnchorRef: RefCallback = useCallback((element) => { - anchorRef.current = element; - }, []); + positionPopOut(); + }, [positionPopOut]); return ( <> - {children(handleAnchorRef)} - - {open && ( + {children} + {anchor && ( +
{content}
- )} -
+
+ )} ); }