use dialog ref for popover portal

This commit is contained in:
Josh Hawkins 2025-11-06 08:48:07 -06:00
parent ee68752756
commit 24b514ec14

View File

@ -6,7 +6,14 @@ import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import { getIconForLabel } from "@/utils/iconUtil"; import { getIconForLabel } from "@/utils/iconUtil";
import { useApiHost } from "@/api"; import { useApiHost } from "@/api";
import { Button } from "../../ui/button"; import { Button } from "../../ui/button";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import axios from "axios"; import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import { Textarea } from "../../ui/textarea"; import { Textarea } from "../../ui/textarea";
@ -91,6 +98,7 @@ type TabsWithActionsProps = {
setSimilarity?: () => void; setSimilarity?: () => void;
isPopoverOpen: boolean; isPopoverOpen: boolean;
setIsPopoverOpen: (open: boolean) => void; setIsPopoverOpen: (open: boolean) => void;
dialogContainer: HTMLDivElement | null;
}; };
function TabsWithActions({ function TabsWithActions({
@ -103,9 +111,16 @@ function TabsWithActions({
setSimilarity, setSimilarity,
isPopoverOpen, isPopoverOpen,
setIsPopoverOpen, setIsPopoverOpen,
dialogContainer,
}: TabsWithActionsProps) { }: TabsWithActionsProps) {
const { t } = useTranslation(["views/explore", "views/faceLibrary"]); const { t } = useTranslation(["views/explore", "views/faceLibrary"]);
useEffect(() => {
if (pageToggle !== "tracking_details" && isPopoverOpen) {
setIsPopoverOpen(false);
}
}, [pageToggle, isPopoverOpen, setIsPopoverOpen]);
if (!search) return null; if (!search) return null;
return ( return (
@ -155,6 +170,7 @@ function TabsWithActions({
search={search} search={search}
open={isPopoverOpen} open={isPopoverOpen}
setIsOpen={setIsPopoverOpen} setIsOpen={setIsPopoverOpen}
container={dialogContainer}
/> />
)} )}
</div> </div>
@ -165,12 +181,14 @@ type AnnotationSettingsProps = {
search: SearchResult; search: SearchResult;
open: boolean; open: boolean;
setIsOpen: (open: boolean) => void; setIsOpen: (open: boolean) => void;
container?: HTMLElement | null;
}; };
function AnnotationSettings({ function AnnotationSettings({
search, search,
open, open,
setIsOpen, setIsOpen,
container,
}: AnnotationSettingsProps) { }: AnnotationSettingsProps) {
const { t } = useTranslation(["views/explore"]); const { t } = useTranslation(["views/explore"]);
const { annotationOffset, setAnnotationOffset } = useDetailStream(); const { annotationOffset, setAnnotationOffset } = useDetailStream();
@ -206,6 +224,9 @@ function AnnotationSettings({
const Overlay = isDesktop ? Popover : Drawer; const Overlay = isDesktop ? Popover : Drawer;
const Trigger = isDesktop ? PopoverTrigger : DrawerTrigger; const Trigger = isDesktop ? PopoverTrigger : DrawerTrigger;
const Content = isDesktop ? PopoverContent : DrawerContent; const Content = isDesktop ? PopoverContent : DrawerContent;
const contentProps = isDesktop
? { align: "end" as const, container: container ?? undefined }
: {};
return ( return (
<div className="ml-2"> <div className="ml-2">
@ -234,7 +255,7 @@ function AnnotationSettings({
? "w-[90vw] max-w-md p-0" ? "w-[90vw] max-w-md p-0"
: "mx-1 max-h-[75dvh] overflow-hidden rounded-t-2xl px-4 pb-4" : "mx-1 max-h-[75dvh] overflow-hidden rounded-t-2xl px-4 pb-4"
} }
{...(isDesktop ? { align: "end" } : {})} {...contentProps}
data-annotation-popover data-annotation-popover
> >
<AnnotationSettingsPane <AnnotationSettingsPane
@ -262,6 +283,7 @@ type DialogContentComponentProps = {
setSimilarity?: () => void; setSimilarity?: () => void;
isPopoverOpen: boolean; isPopoverOpen: boolean;
setIsPopoverOpen: (open: boolean) => void; setIsPopoverOpen: (open: boolean) => void;
dialogContainer: HTMLDivElement | null;
}; };
function DialogContentComponent({ function DialogContentComponent({
@ -278,6 +300,7 @@ function DialogContentComponent({
setSimilarity, setSimilarity,
isPopoverOpen, isPopoverOpen,
setIsPopoverOpen, setIsPopoverOpen,
dialogContainer,
}: DialogContentComponentProps) { }: DialogContentComponentProps) {
if (page === "tracking_details") { if (page === "tracking_details") {
return ( return (
@ -296,6 +319,7 @@ function DialogContentComponent({
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
isPopoverOpen={isPopoverOpen} isPopoverOpen={isPopoverOpen}
setIsPopoverOpen={setIsPopoverOpen} setIsPopoverOpen={setIsPopoverOpen}
dialogContainer={dialogContainer}
/> />
) : undefined ) : undefined
} }
@ -354,6 +378,7 @@ function DialogContentComponent({
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
isPopoverOpen={isPopoverOpen} isPopoverOpen={isPopoverOpen}
setIsPopoverOpen={setIsPopoverOpen} setIsPopoverOpen={setIsPopoverOpen}
dialogContainer={dialogContainer}
/> />
<div className="scrollbar-container flex-1 overflow-y-auto"> <div className="scrollbar-container flex-1 overflow-y-auto">
<ObjectDetailsTab <ObjectDetailsTab
@ -421,12 +446,16 @@ export default function SearchDetailDialog({
const [isOpen, setIsOpen] = useState(search != undefined); const [isOpen, setIsOpen] = useState(search != undefined);
const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const dialogContentRef = useRef<HTMLDivElement | null>(null);
const [dialogContainer, setDialogContainer] = useState<HTMLDivElement | null>(
null,
);
const handleOpenChange = useCallback( const handleOpenChange = useCallback(
(open: boolean) => { (open: boolean) => {
setIsPopoverOpen(open);
setIsOpen(open); setIsOpen(open);
if (!open) { if (!open) {
setIsPopoverOpen(false);
// short timeout to allow the mobile page animation // short timeout to allow the mobile page animation
// to complete before updating the state // to complete before updating the state
setTimeout(() => { setTimeout(() => {
@ -437,6 +466,10 @@ export default function SearchDetailDialog({
[setSearch], [setSearch],
); );
useLayoutEffect(() => {
setDialogContainer(dialogContentRef.current);
}, [isOpen, search?.id]);
useEffect(() => { useEffect(() => {
if (search) { if (search) {
setIsOpen(search != undefined); setIsOpen(search != undefined);
@ -545,6 +578,7 @@ export default function SearchDetailDialog({
</DialogPortal> </DialogPortal>
)} )}
<Content <Content
ref={isDesktop ? dialogContentRef : undefined}
className={cn( className={cn(
"scrollbar-container overflow-y-auto", "scrollbar-container overflow-y-auto",
isDesktop && isDesktop &&
@ -581,6 +615,7 @@ export default function SearchDetailDialog({
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
isPopoverOpen={isPopoverOpen} isPopoverOpen={isPopoverOpen}
setIsPopoverOpen={setIsPopoverOpen} setIsPopoverOpen={setIsPopoverOpen}
dialogContainer={dialogContainer}
/> />
</div> </div>
)} )}
@ -599,6 +634,7 @@ export default function SearchDetailDialog({
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
isPopoverOpen={isPopoverOpen} isPopoverOpen={isPopoverOpen}
setIsPopoverOpen={setIsPopoverOpen} setIsPopoverOpen={setIsPopoverOpen}
dialogContainer={dialogContainer}
/> />
</Content> </Content>
</Overlay> </Overlay>