refactor(web): shared DetailDrawer; objects-page uses it (#58)
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { renderApp } from "../test/render";
|
||||
import { DetailDrawer } from "./detail-drawer";
|
||||
|
||||
test("renders children in a named drawer and closes via the close button", async () => {
|
||||
const onClose = vi.fn();
|
||||
renderApp(
|
||||
<DetailDrawer open onClose={onClose} ariaLabel="Object detail">
|
||||
<p>detail body</p>
|
||||
</DetailDrawer>,
|
||||
);
|
||||
|
||||
const body = within(document.body);
|
||||
expect(await body.findByText("detail body")).toBeInTheDocument();
|
||||
await userEvent.click(body.getByRole("button", { name: /close detail/i }));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
@@ -1,21 +1,22 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
import type { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
/**
|
||||
* Narrow-viewport object detail: the nested <Outlet/> inside a Base UI Drawer that
|
||||
* slides from the right. Lazy-loaded so Base UI's drawer code (swipe/snap machinery)
|
||||
* splits out of the main entry chunk — the wide pane path never pays for it.
|
||||
*/
|
||||
export function ObjectDetailDrawer({
|
||||
/** A right-sliding Base UI Drawer for a master/detail "detail" on narrow viewports.
|
||||
* Provides the close affordance + an accessible dialog name; the caller supplies the content. */
|
||||
export function DetailDrawer({
|
||||
open,
|
||||
onClose,
|
||||
ariaLabel,
|
||||
children,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
ariaLabel: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -27,7 +28,7 @@ export function ObjectDetailDrawer({
|
||||
}}
|
||||
swipeDirection="right"
|
||||
>
|
||||
<DrawerContent aria-label={t("objects.detailTitle")}>
|
||||
<DrawerContent aria-label={ariaLabel}>
|
||||
<div className="flex justify-end border-b p-2">
|
||||
<DrawerClose
|
||||
aria-label={t("actions.closeDetail")}
|
||||
@@ -36,9 +37,7 @@ export function ObjectDetailDrawer({
|
||||
<X className="size-4" aria-hidden="true" />
|
||||
</DrawerClose>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Outlet />
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">{children}</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
@@ -1,19 +1,15 @@
|
||||
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 { DetailDrawer } from "../components/detail-drawer";
|
||||
import { useMediaQuery } from "../lib/use-media-query";
|
||||
import { useDocumentTitle } from "../lib/use-document-title";
|
||||
import { useBreadcrumb } from "../shell/use-breadcrumb";
|
||||
import { Button } from "@/components/ui/button";
|
||||
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();
|
||||
@@ -66,15 +62,14 @@ export function ObjectsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Narrow: the detail lives in a Drawer, lazy-loaded so Base UI's drawer code stays
|
||||
// out of the main entry chunk.
|
||||
// Narrow: the detail lives in a Drawer sliding from the right.
|
||||
return (
|
||||
<div className="h-full">
|
||||
{table}
|
||||
{open && (
|
||||
<Suspense fallback={null}>
|
||||
<ObjectDetailDrawer open={open} onClose={closeDetail} />
|
||||
</Suspense>
|
||||
<DetailDrawer open={open} onClose={closeDetail} ariaLabel={t("objects.detailTitle")}>
|
||||
<Outlet />
|
||||
</DetailDrawer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user