Adds viewportFit: 'cover' so the PWA can draw under the notch / Dynamic Island when installed. Nav and Toast read env(safe-area-inset-*) to keep their content out of the hardware cutouts (no-op on browsers without a notch — env() resolves to 0). Replaces autoFocus on the first field of CreateAccountDialog and CreateUserDialog with a useEffect that only focuses on pointer devices (matchMedia '(hover: hover) and (pointer: fine)'). Phones no longer get the soft keyboard popping the instant a dialog opens.
64 lines
1.8 KiB
TypeScript
64 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
|
|
export type ToastMessage = {
|
|
type: "success" | "error";
|
|
message: string;
|
|
};
|
|
|
|
/**
|
|
* Simple top-centered toast. Auto-dismisses after `durationMs` (default
|
|
* 3s). Owners hold the ToastMessage state; this component reads it and
|
|
* calls onDismiss when the timer fires (or when the toast object
|
|
* changes — useEffect's cleanup clears any in-flight timer).
|
|
*/
|
|
export default function Toast({
|
|
toast,
|
|
onDismiss,
|
|
durationMs = 3000,
|
|
}: {
|
|
toast: ToastMessage | null;
|
|
onDismiss: () => void;
|
|
durationMs?: number;
|
|
}) {
|
|
useEffect(() => {
|
|
if (!toast) return;
|
|
const id = setTimeout(onDismiss, durationMs);
|
|
return () => clearTimeout(id);
|
|
}, [toast, onDismiss, durationMs]);
|
|
|
|
if (!toast) return null;
|
|
|
|
const styles =
|
|
toast.type === "success"
|
|
? "bg-emerald-50 text-emerald-800 ring-emerald-200"
|
|
: "bg-red-50 text-red-800 ring-red-200";
|
|
|
|
return (
|
|
<div
|
|
role="status"
|
|
aria-live="polite"
|
|
className="fixed left-1/2 z-50 -translate-x-1/2 transform px-4"
|
|
style={{
|
|
// Stay below the notch when running as an installed PWA.
|
|
// calc(safe-area + 1rem) keeps the toast 1rem below the safe-area
|
|
// edge — and 1rem below the top in browsers without a notch.
|
|
top: "calc(env(safe-area-inset-top) + 1rem)",
|
|
}}
|
|
>
|
|
<div
|
|
className={`flex items-center gap-2 rounded-full px-4 py-2 shadow-sm ring-1 ${styles}`}
|
|
>
|
|
<span
|
|
aria-hidden="true"
|
|
className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-current/10 text-xs font-bold"
|
|
>
|
|
{toast.type === "success" ? "✓" : "!"}
|
|
</span>
|
|
<span className="text-sm font-medium">{toast.message}</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|