add prev/next buttons on desktop

This commit is contained in:
Josh Hawkins 2025-11-03 14:58:44 -06:00
parent 32f1d85a6f
commit 8ed13c0b08

View File

@ -28,6 +28,8 @@ import {
FaArrowRight, FaArrowRight,
FaCheckCircle, FaCheckCircle,
FaChevronDown, FaChevronDown,
FaChevronLeft,
FaChevronRight,
FaDownload, FaDownload,
FaHistory, FaHistory,
FaImage, FaImage,
@ -80,6 +82,7 @@ import { CgTranscript } from "react-icons/cg";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel"; import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
import { PiPath } from "react-icons/pi"; import { PiPath } from "react-icons/pi";
import Heading from "@/components/ui/heading"; import Heading from "@/components/ui/heading";
import { DialogPortal } from "@radix-ui/react-dialog";
const SEARCH_TABS = ["snapshot", "tracking_details"] as const; const SEARCH_TABS = ["snapshot", "tracking_details"] as const;
export type SearchTab = (typeof SEARCH_TABS)[number]; export type SearchTab = (typeof SEARCH_TABS)[number];
@ -91,6 +94,8 @@ type SearchDetailDialogProps = {
setSearchPage: (page: SearchTab) => void; setSearchPage: (page: SearchTab) => void;
setSimilarity?: () => void; setSimilarity?: () => void;
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>; setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
onPrevious?: () => void;
onNext?: () => void;
}; };
export default function SearchDetailDialog({ export default function SearchDetailDialog({
search, search,
@ -99,6 +104,8 @@ export default function SearchDetailDialog({
setSearchPage, setSearchPage,
setSimilarity, setSimilarity,
setInputFocused, setInputFocused,
onPrevious,
onNext,
}: SearchDetailDialogProps) { }: SearchDetailDialogProps) {
const { t } = useTranslation(["views/explore", "views/faceLibrary"]); const { t } = useTranslation(["views/explore", "views/faceLibrary"]);
const { data: config } = useSWR<FrigateConfig>("config", { const { data: config } = useSWR<FrigateConfig>("config", {
@ -227,6 +234,57 @@ export default function SearchDetailDialog({
onOpenChange={handleOpenChange} onOpenChange={handleOpenChange}
enableHistoryBack={true} enableHistoryBack={true}
> >
{isDesktop && onPrevious && onNext && (
<DialogPortal>
<div className="pointer-events-none fixed inset-0 z-[200] flex items-center justify-center">
<div
className={cn(
"relative flex items-center justify-between",
"w-full",
// match dialog's max-width classes
"sm:max-w-xl md:max-w-4xl lg:max-w-4xl xl:max-w-7xl",
page == "tracking_details" && "lg:max-w-[75%] xl:max-w-[80%]",
)}
>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={(e) => {
e.stopPropagation();
onPrevious?.();
}}
className="nav-button pointer-events-auto absolute -left-16 rounded-lg border bg-secondary/60 p-2 text-primary-variant shadow-lg backdrop-blur-sm hover:bg-secondary/80 hover:text-primary"
aria-label={t("searchResult.previousTrackedObject")}
>
<FaChevronLeft className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>
{t("searchResult.previousTrackedObject")}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={(e) => {
e.stopPropagation();
onNext?.();
}}
className="nav-button pointer-events-auto absolute -right-16 rounded-lg border bg-secondary/60 p-2 text-primary-variant shadow-lg backdrop-blur-sm hover:bg-secondary/80 hover:text-primary"
aria-label={t("searchResult.nextTrackedObject")}
>
<FaChevronRight className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent>
{t("searchResult.nextTrackedObject")}
</TooltipContent>
</Tooltip>
</div>
</div>
</DialogPortal>
)}
<Content <Content
className={cn( className={cn(
"scrollbar-container overflow-y-auto", "scrollbar-container overflow-y-auto",
@ -237,11 +295,18 @@ export default function SearchDetailDialog({
"lg:max-w-[75%] xl:max-w-[80%]", "lg:max-w-[75%] xl:max-w-[80%]",
isMobile && "px-4", isMobile && "px-4",
)} )}
onInteractOutside={(e) => {
const target = e.target as HTMLElement;
if (target.closest(".nav-button")) {
e.preventDefault();
}
}}
> >
<Header> <Header>
<Title>{t("trackedObjectDetails")}</Title> <Title>{t("trackedObjectDetails")}</Title>
<Description className="sr-only"> <Description className="sr-only">
{t("trackedObjectDetails")} {t("trackedObjectDetails")}
<span className="sr-only" tabIndex={0} />
</Description> </Description>
</Header> </Header>
{isDesktop ? ( {isDesktop ? (