fix popover

This commit is contained in:
Josh Hawkins 2025-11-05 22:33:37 -06:00
parent 78e278d5e8
commit 6a011df427

View File

@ -88,8 +88,8 @@ type TabsWithActionsProps = {
config?: FrigateConfig; config?: FrigateConfig;
setSearch: (s: SearchResult | undefined) => void; setSearch: (s: SearchResult | undefined) => void;
setSimilarity?: () => void; setSimilarity?: () => void;
showControls?: boolean; isPopoverOpen: boolean;
setShowControls?: (v: boolean) => void; setIsPopoverOpen: (open: boolean) => void;
}; };
function TabsWithActions({ function TabsWithActions({
@ -100,8 +100,8 @@ function TabsWithActions({
config, config,
setSearch, setSearch,
setSimilarity, setSimilarity,
showControls, isPopoverOpen,
setShowControls, setIsPopoverOpen,
}: TabsWithActionsProps) { }: TabsWithActionsProps) {
const { t } = useTranslation(["views/explore", "views/faceLibrary"]); const { t } = useTranslation(["views/explore", "views/faceLibrary"]);
@ -152,8 +152,8 @@ function TabsWithActions({
{pageToggle === "tracking_details" && ( {pageToggle === "tracking_details" && (
<AnnotationSettingsPopover <AnnotationSettingsPopover
search={search} search={search}
showControls={showControls} open={isPopoverOpen}
setShowControls={setShowControls} setIsOpen={setIsPopoverOpen}
/> />
)} )}
</div> </div>
@ -162,40 +162,69 @@ function TabsWithActions({
type AnnotationSettingsPopoverProps = { type AnnotationSettingsPopoverProps = {
search: SearchResult; search: SearchResult;
showControls?: boolean; open: boolean;
setShowControls?: (v: boolean) => void; setIsOpen: (open: boolean) => void;
}; };
function AnnotationSettingsPopover({ function AnnotationSettingsPopover({
search, search,
showControls, open,
setShowControls, setIsOpen,
}: AnnotationSettingsPopoverProps) { }: AnnotationSettingsPopoverProps) {
const { t } = useTranslation(["views/explore"]); const { t } = useTranslation(["views/explore"]);
const { annotationOffset, setAnnotationOffset } = useDetailStream(); const { annotationOffset, setAnnotationOffset } = useDetailStream();
const [showZones, setShowZones] = useState(true); const [showZones, setShowZones] = useState(true);
const ignoreNextOpenRef = useRef(false);
useEffect(() => {
setIsOpen(false);
ignoreNextOpenRef.current = false;
}, [search, setIsOpen]);
const handleOpenChange = useCallback(
(nextOpen: boolean) => {
if (nextOpen) {
if (ignoreNextOpenRef.current) {
ignoreNextOpenRef.current = false;
return;
}
setIsOpen(true);
} else {
setIsOpen(false);
}
},
[setIsOpen],
);
const registerTriggerCloseIntent = useCallback(() => {
if (open) {
ignoreNextOpenRef.current = true;
}
}, [open]);
return ( return (
<div className="ml-2"> <div className="ml-2">
<Popover modal={true} open={showControls} onOpenChange={setShowControls}> <Popover modal={true} open={open} onOpenChange={handleOpenChange}>
<Tooltip> <PopoverTrigger
<TooltipTrigger asChild> asChild
<PopoverTrigger asChild> onPointerDown={registerTriggerCloseIntent}
<Button onKeyDown={(event) => {
variant={showControls ? "select" : "default"} if (open && (event.key === "Enter" || event.key === " ")) {
className="size-7 p-1.5" registerTriggerCloseIntent();
aria-label={t("trackingDetails.adjustAnnotationSettings")} }
> }}
<LuSettings className="size-5" /> >
</Button> <Button
</PopoverTrigger> type="button"
</TooltipTrigger> className="size-7 p-1.5"
<TooltipPortal> variant={open ? "select" : "default"}
<TooltipContent> aria-label={t("trackingDetails.adjustAnnotationSettings")}
{t("trackingDetails.adjustAnnotationSettings")} aria-expanded={open}
</TooltipContent> >
</TooltipPortal> <LuSettings className="size-5" />
</Tooltip> </Button>
</PopoverTrigger>
<PopoverContent className="w-[90vw] max-w-2xl p-0" align="end"> <PopoverContent className="w-[90vw] max-w-2xl p-0" align="end">
<AnnotationSettingsPane <AnnotationSettingsPane
event={search as unknown as Event} event={search as unknown as Event}
@ -229,8 +258,8 @@ type DialogContentComponentProps = {
setSearch: (s: SearchResult | undefined) => void; setSearch: (s: SearchResult | undefined) => void;
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>; setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
setSimilarity?: () => void; setSimilarity?: () => void;
showControls?: boolean; isPopoverOpen: boolean;
setShowControls?: (v: boolean) => void; setIsPopoverOpen: (open: boolean) => void;
}; };
function DialogContentComponent({ function DialogContentComponent({
@ -245,8 +274,8 @@ function DialogContentComponent({
setSearch, setSearch,
setInputFocused, setInputFocused,
setSimilarity, setSimilarity,
showControls, isPopoverOpen,
setShowControls, setIsPopoverOpen,
}: DialogContentComponentProps) { }: DialogContentComponentProps) {
if (page === "tracking_details") { if (page === "tracking_details") {
return ( return (
@ -263,8 +292,8 @@ function DialogContentComponent({
config={config} config={config}
setSearch={setSearch} setSearch={setSearch}
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
showControls={showControls} isPopoverOpen={isPopoverOpen}
setShowControls={setShowControls} setIsPopoverOpen={setIsPopoverOpen}
/> />
) : undefined ) : undefined
} }
@ -321,8 +350,8 @@ function DialogContentComponent({
config={config} config={config}
setSearch={setSearch} setSearch={setSearch}
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
showControls={showControls} isPopoverOpen={isPopoverOpen}
setShowControls={setShowControls} setIsPopoverOpen={setIsPopoverOpen}
/> />
<div className="scrollbar-container flex-1 overflow-y-auto"> <div className="scrollbar-container flex-1 overflow-y-auto">
<ObjectDetailsTab <ObjectDetailsTab
@ -389,9 +418,11 @@ export default function SearchDetailDialog({
// dialog and mobile page // dialog and mobile page
const [isOpen, setIsOpen] = useState(search != undefined); const [isOpen, setIsOpen] = useState(search != undefined);
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const handleOpenChange = useCallback( const handleOpenChange = useCallback(
(open: boolean) => { (open: boolean) => {
setIsPopoverOpen(open);
setIsOpen(open); setIsOpen(open);
if (!open) { if (!open) {
// short timeout to allow the mobile page animation // short timeout to allow the mobile page animation
@ -410,9 +441,7 @@ export default function SearchDetailDialog({
} }
}, [search]); }, [search]);
// show/hide annotation settings // show/hide annotation settings is handled inside TabsWithActions
const [showControls, setShowControls] = useState(false);
const searchTabs = useMemo(() => { const searchTabs = useMemo(() => {
if (!config || !search) { if (!config || !search) {
@ -521,6 +550,9 @@ export default function SearchDetailDialog({
isMobile && "px-4", isMobile && "px-4",
)} )}
onInteractOutside={(e) => { onInteractOutside={(e) => {
if (isPopoverOpen) {
e.preventDefault();
}
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
if (target.closest(".nav-button")) { if (target.closest(".nav-button")) {
e.preventDefault(); e.preventDefault();
@ -545,8 +577,8 @@ export default function SearchDetailDialog({
config={config} config={config}
setSearch={setSearch} setSearch={setSearch}
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
showControls={showControls} isPopoverOpen={isPopoverOpen}
setShowControls={setShowControls} setIsPopoverOpen={setIsPopoverOpen}
/> />
</div> </div>
)} )}
@ -563,8 +595,8 @@ export default function SearchDetailDialog({
setSearch={setSearch} setSearch={setSearch}
setInputFocused={setInputFocused} setInputFocused={setInputFocused}
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
showControls={showControls} isPopoverOpen={isPopoverOpen}
setShowControls={setShowControls} setIsPopoverOpen={setIsPopoverOpen}
/> />
</Content> </Content>
</Overlay> </Overlay>