yiekheng 3bfd35ef8d fix(web): PWA notch safe-area + skip autoFocus on touch devices
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.
2026-05-02 21:26:42 +08:00

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>
);
}