6e1f5ea50f
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import { lazy, Suspense } from "react";
|
|
import { Outlet, useMatch, useNavigate, useSearchParams } from "react-router-dom";
|
|
import { useTranslation } from "react-i18next";
|
|
import { X } from "lucide-react";
|
|
|
|
import { ObjectsTable } from "./objects-table";
|
|
import { useMediaQuery } from "../lib/use-media-query";
|
|
import { useDocumentTitle } from "../lib/use-document-title";
|
|
import { PageTitle } from "@/components/ui/page-title";
|
|
|
|
const ObjectDetailDrawer = lazy(() =>
|
|
import("./object-detail-drawer").then((m) => ({ default: m.ObjectDetailDrawer })),
|
|
);
|
|
|
|
export function ObjectsPage() {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const [searchParams] = useSearchParams();
|
|
|
|
// The table is the full-width landing view. When a `:id`/`:id/edit` child route
|
|
// is active we surface the nested <Outlet/> as a right-hand pane (wide) or a
|
|
// Drawer sliding from the right (narrow), preserving the table's query string on close.
|
|
const detailMatch = useMatch("/objects/:id");
|
|
const editMatch = useMatch("/objects/:id/edit");
|
|
const open = Boolean(detailMatch ?? editMatch);
|
|
const isWide = useMediaQuery("(min-width: 1024px)");
|
|
|
|
useDocumentTitle(t("nav.objects"));
|
|
|
|
const closeDetail = () => navigate(`/objects?${searchParams}`);
|
|
|
|
const table = (
|
|
<div className="flex h-full flex-col overflow-hidden">
|
|
<PageTitle className="px-4 pt-4 pb-2">{t("nav.objects")}</PageTitle>
|
|
<div className="flex-1 overflow-hidden">
|
|
<ObjectsTable />
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
if (isWide) {
|
|
return (
|
|
<div className={`grid h-full ${open ? "grid-cols-[1fr_28rem]" : "grid-cols-1"}`}>
|
|
{table}
|
|
{open && (
|
|
<div className="flex h-full flex-col overflow-hidden border-l">
|
|
<div className="flex justify-end border-b p-2">
|
|
<button
|
|
type="button"
|
|
onClick={closeDetail}
|
|
aria-label={t("actions.closeDetail")}
|
|
className="rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
>
|
|
<X className="size-4" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
<div className="flex-1 overflow-auto">
|
|
<Outlet />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Narrow: the detail lives in a Drawer, lazy-loaded so Base UI's drawer code stays
|
|
// out of the main entry chunk.
|
|
return (
|
|
<div className="h-full">
|
|
{table}
|
|
{open && (
|
|
<Suspense fallback={null}>
|
|
<ObjectDetailDrawer open={open} onClose={closeDetail} />
|
|
</Suspense>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|