From c387a631cdb99d07c54fbf5bf5518eeeabc576d3 Mon Sep 17 00:00:00 2001 From: Grogu Date: Fri, 28 Apr 2023 02:46:12 +0800 Subject: [PATCH] fix: fix popover position, closes #441 (#442) Co-authored-by: Innei --- src/components/common/CharacterFloatCard.tsx | 43 ++++---- src/components/common/CharacterList.tsx | 53 ++++++---- src/components/common/Portal.tsx | 48 +++++++++ src/components/ui/Modal.tsx | 103 ++++++++++--------- src/hooks/useClient.ts | 10 ++ 5 files changed, 172 insertions(+), 85 deletions(-) create mode 100644 src/components/common/Portal.tsx create mode 100644 src/hooks/useClient.ts diff --git a/src/components/common/CharacterFloatCard.tsx b/src/components/common/CharacterFloatCard.tsx index d494ecf9..1d2fe6b3 100644 --- a/src/components/common/CharacterFloatCard.tsx +++ b/src/components/common/CharacterFloatCard.tsx @@ -15,6 +15,8 @@ import { import { CharacterCard } from "~/components/common/CharacterCard" +import { Portal } from "./Portal" + export const CharacterFloatCard: React.FC<{ siteId?: string children: JSX.Element @@ -27,6 +29,7 @@ export const CharacterFloatCard: React.FC<{ onOpenChange: setOpen, middleware: [offset(5), flip(), shift({ padding: 8 })], whileElementsMounted: autoUpdate, + strategy: "fixed", }) const { getReferenceProps, getFloatingProps } = useInteractions([ @@ -51,25 +54,27 @@ export const CharacterFloatCard: React.FC<{ {children} {isMounted && ( - - - + + + + + )} ) diff --git a/src/components/common/CharacterList.tsx b/src/components/common/CharacterList.tsx index 5750f32d..d8f6bccf 100644 --- a/src/components/common/CharacterList.tsx +++ b/src/components/common/CharacterList.tsx @@ -1,11 +1,12 @@ import { useTranslation } from "next-i18next" -import React from "react" +import React, { useRef } from "react" import { Virtuoso } from "react-virtuoso" import { Modal } from "~/components/ui/Modal" import { Button } from "../ui/Button" import CharacterListItem from "./CharacterListItem" +import { PortalProvider } from "./Portal" export const CharacterList: React.FC<{ open: boolean @@ -23,26 +24,40 @@ export const CharacterList: React.FC<{ [] as any[], ) as any[] + const $modalEl = useRef(null) + return ( - + {list?.length ? ( - hasMore && loadMore()} - components={{ - Footer: hasMore ? Loader : undefined, - }} - data={flattenList} - itemContent={(index, sub) => { - const character = sub?.character || sub?.fromCharacter - return ( - - ) - }} - > + + hasMore && loadMore()} + components={{ + Footer: hasMore ? Loader : undefined, + }} + data={flattenList} + itemContent={(index, sub) => { + const character = sub?.character || sub?.fromCharacter + return ( + + ) + }} + > + ) : (
diff --git a/src/components/common/Portal.tsx b/src/components/common/Portal.tsx new file mode 100644 index 00000000..2c5c0cc4 --- /dev/null +++ b/src/components/common/Portal.tsx @@ -0,0 +1,48 @@ +import { PropsWithChildren, createContext, useContext, useMemo } from "react" +import { createPortal } from "react-dom" + +import { useIsClient } from "~/hooks/useClient" +import { isServerSide } from "~/lib/utils" + +export const usePortal = () => { + const ctx = useContext(RootPortalContext) + if (isServerSide()) { + return null + } + return ctx.to || document.body +} + +const RootPortalContext = createContext<{ + to?: HTMLElement | undefined +}>({ + to: undefined, +}) +export const PortalProvider = ( + props: PropsWithChildren<{ + to: HTMLElement + }>, +) => ( + ({ + to: props.to, + }), + [props.to], + )} + > + {props.children} + +) + +export const Portal = ( + props: PropsWithChildren<{ + to?: HTMLElement + }>, +) => { + const client = useIsClient() + const to = usePortal() + + if (!client) return null + + return createPortal(props.children, props.to || to || document.body) +} diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index 6c11cee4..65e496ab 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -1,11 +1,11 @@ import clsx from "clsx" -import React from "react" +import React, { forwardRef } from "react" import { Dialog } from "@headlessui/react" import { cn } from "~/lib/utils" -export const Modal: React.FC<{ +interface ModalProps { open: boolean setOpen: (open: boolean) => void children: React.ReactNode @@ -13,49 +13,58 @@ export const Modal: React.FC<{ titleIcon?: React.ReactNode size?: "md" | "lg" | "sm" zIndex?: number -}> = ({ open, setOpen, children, title, titleIcon, size = "md", zIndex }) => { - return ( - setOpen(false)} - className={clsx("relative", zIndex ? `z-${zIndex}` : "z-50")} - > - {/* The backdrop, rendered as a fixed sibling to the panel container */} -
- - {/* Full-screen container to center the panel */} -
- {/* The actual dialog panel */} - - {title && ( - - {titleIcon && {titleIcon}} - {title} - setOpen(false)} - > - - - - )} - {children} - -
-
- ) } + +export const Modal = forwardRef( + ({ open, setOpen, children, title, titleIcon, size = "md", zIndex }, ref) => { + return ( + setOpen(false)} + className={clsx("relative", zIndex ? `z-${zIndex}` : "z-50")} + > + {/* The backdrop, rendered as a fixed sibling to the panel container */} +
+ + {/* Full-screen container to center the panel */} +
+ {/* The actual dialog panel */} + + {title && ( + + {titleIcon && {titleIcon}} + + {title} + + setOpen(false)} + > + + + + )} + {children} + +
+
+ ) + }, +) + +Modal.displayName = "Modal" diff --git a/src/hooks/useClient.ts b/src/hooks/useClient.ts new file mode 100644 index 00000000..560c48ac --- /dev/null +++ b/src/hooks/useClient.ts @@ -0,0 +1,10 @@ +import { useEffect, useState } from "react" + +export const useIsClient = () => { + const [isClient, setIsClient] = useState(false) + + useEffect(() => { + setIsClient(true) + }, []) + return isClient +}