Fit the login form to the picture's box
The form now sits in the same 16:9 box as the snapshot, so a card keeps its height whichever it shows. A compact variant drops the aside about which user to create and tightens the spacing, since that space is fixed; the camera page keeps the full form, where there is room. Anything that still doesn't fit scrolls inside the box rather than stretching the card. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6a663a5d82
commit
f953f38716
@ -270,6 +270,10 @@ describe("CameraCard", () => {
|
|||||||
expect(screen.queryByRole("link", { name: "Pop out Porch" })).toBeNull();
|
expect(screen.queryByRole("link", { name: "Pop out Porch" })).toBeNull();
|
||||||
// The status still says what is wrong, where the picture would have been.
|
// The status still says what is wrong, where the picture would have been.
|
||||||
expect(container.querySelector("[data-status='auth']")).toBeTruthy();
|
expect(container.querySelector("[data-status='auth']")).toBeTruthy();
|
||||||
|
|
||||||
|
// Same box as the picture, and the aside that wouldn't fit is left out.
|
||||||
|
expect(container.querySelector(".aspect-video")).toBeTruthy();
|
||||||
|
expect(container.textContent).not.toContain("Use the ONVIF user you created");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps a username the user typed over the saved one", async () => {
|
it("keeps a username the user typed over the saved one", async () => {
|
||||||
|
|||||||
@ -178,14 +178,17 @@ export default function CameraCard({
|
|||||||
<li className="flex flex-col gap-3 rounded-lg border border-zinc-200 p-4 dark:border-zinc-800">
|
<li className="flex flex-col gap-3 rounded-lg border border-zinc-200 p-4 dark:border-zinc-800">
|
||||||
{/* A camera that can't log in has no picture to show, so the form takes its place. */}
|
{/* A camera that can't log in has no picture to show, so the form takes its place. */}
|
||||||
{showLogin ? (
|
{showLogin ? (
|
||||||
<div className="flex flex-col gap-2 rounded bg-zinc-100 p-3 dark:bg-zinc-900">
|
<div className="flex aspect-video flex-col gap-2 overflow-hidden rounded bg-zinc-100 p-2 dark:bg-zinc-900">
|
||||||
<StatusBadge status={status} className="self-start" />
|
<StatusBadge status={status} className="self-start" />
|
||||||
<CredentialsForm
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||||
cam={cam}
|
<CredentialsForm
|
||||||
reason={problem?.kind === "auth" ? problem.message : undefined}
|
cam={cam}
|
||||||
onSaved={() => setEditingLogin(false)}
|
reason={problem?.kind === "auth" ? problem.message : undefined}
|
||||||
onCancel={problem?.kind === "auth" ? undefined : () => setEditingLogin(false)}
|
onSaved={() => setEditingLogin(false)}
|
||||||
/>
|
onCancel={problem?.kind === "auth" ? undefined : () => setEditingLogin(false)}
|
||||||
|
compact
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Snapshot
|
<Snapshot
|
||||||
|
|||||||
@ -31,12 +31,15 @@ export function CredentialsForm({
|
|||||||
reason,
|
reason,
|
||||||
onSaved,
|
onSaved,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
compact = false,
|
||||||
}: {
|
}: {
|
||||||
cam: CameraSummary;
|
cam: CameraSummary;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
/** Called after a save or clear; the camera's queries have already been reset. */
|
/** Called after a save or clear; the camera's queries have already been reset. */
|
||||||
onSaved: () => void;
|
onSaved: () => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
|
/** For the dashboard card, where the form stands in for the picture. */
|
||||||
|
compact?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const status = useCredentialStatus(cam.id);
|
const status = useCredentialStatus(cam.id);
|
||||||
const save = useSaveCredentials(cam.id);
|
const save = useSaveCredentials(cam.id);
|
||||||
@ -63,16 +66,22 @@ export function CredentialsForm({
|
|||||||
const busy = save.isPending || clear.isPending;
|
const busy = save.isPending || clear.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={submit} className="flex flex-col gap-2 rounded border border-amber-400/60 p-3 text-sm">
|
<form
|
||||||
|
onSubmit={submit}
|
||||||
|
className={`flex h-full flex-col rounded border border-amber-400/60 text-sm ${compact ? "gap-1.5 p-2" : "gap-2 p-3"}`}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-1.5 font-medium">
|
<div className="flex items-center gap-1.5 font-medium">
|
||||||
<KeyRound aria-hidden className="size-4 shrink-0" />
|
<KeyRound aria-hidden className="size-4 shrink-0" />
|
||||||
Camera login
|
Camera login
|
||||||
</div>
|
</div>
|
||||||
{reason && <div className="text-xs text-zinc-500">{reason}</div>}
|
{reason && <div className="text-xs text-zinc-500">{reason}</div>}
|
||||||
<div className="text-xs text-zinc-500">
|
{/* On a card this has to fit where the picture would be, so the aside goes. */}
|
||||||
Use the ONVIF user you created on the camera (
|
{!compact && (
|
||||||
<WebUiLink cam={cam}>camera web page</WebUiLink>).
|
<div className="text-xs text-zinc-500">
|
||||||
</div>
|
Use the ONVIF user you created on the camera (
|
||||||
|
<WebUiLink cam={cam}>camera web page</WebUiLink>).
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<input
|
<input
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setTypedUsername(e.target.value)}
|
onChange={(e) => setTypedUsername(e.target.value)}
|
||||||
|
|||||||
@ -59,6 +59,8 @@ describe("CameraLoginControls", () => {
|
|||||||
expect(screen.queryByText("Camera login")).toBeNull();
|
expect(screen.queryByText("Camera login")).toBeNull();
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Change login" }));
|
fireEvent.click(screen.getByRole("button", { name: "Change login" }));
|
||||||
expect(await screen.findByText("Camera login")).toBeTruthy();
|
expect(await screen.findByText("Camera login")).toBeTruthy();
|
||||||
|
// With room to spare here, the form keeps its aside about which user to use.
|
||||||
|
expect(screen.getByText(/Use the ONVIF user you created/)).toBeTruthy();
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||||
expect(screen.queryByText("Camera login")).toBeNull();
|
expect(screen.queryByText("Camera login")).toBeNull();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user