fix(ui): complete P5-00 shell readiness gates
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { createContext, useCallback, useContext, useMemo, useState } from "react";
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { Alert, Button, Snackbar } from "@mui/material";
|
||||
import { Alert, Button, GlobalStyles, Snackbar } from "@mui/material";
|
||||
|
||||
type Severity = "success" | "info" | "warning" | "error";
|
||||
|
||||
@@ -24,6 +24,19 @@ const ToastContext = createContext<ToastApi | null>(null);
|
||||
|
||||
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
||||
const [t, setT] = useState<Toast>({ open: false, message: "", severity: "info" });
|
||||
const [element, setElement] = useState<HTMLDivElement | null>(null);
|
||||
const [clearance, setClearance] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!element) { setClearance(0); return; }
|
||||
// Reserve the actual wrapped message height, bottom offset and a 24px gap.
|
||||
const measure = () => setClearance(Math.ceil(element.getBoundingClientRect().height) + 48);
|
||||
measure();
|
||||
if (typeof ResizeObserver === "undefined") return;
|
||||
const observer = new ResizeObserver(measure);
|
||||
observer.observe(element);
|
||||
return () => observer.disconnect();
|
||||
}, [element]);
|
||||
|
||||
const toast = useCallback(
|
||||
(message: string, severity: Severity = "info", action?: { label: string; onClick: () => void }) => {
|
||||
@@ -42,12 +55,19 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={value}>
|
||||
<GlobalStyles styles={{
|
||||
":root": { "--app-toast-clearance": `${clearance}px` },
|
||||
// Native focus/scrollIntoView must also leave the feedback lane clear.
|
||||
html: { scrollPaddingBottom: "calc(max(96px, var(--app-toast-clearance, 0px)) + env(safe-area-inset-bottom, 0px))" },
|
||||
}} />
|
||||
{children}
|
||||
<Snackbar
|
||||
ref={setElement}
|
||||
open={t.open}
|
||||
autoHideDuration={3500}
|
||||
onClose={() => setT((p) => ({ ...p, open: false }))}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
sx={{ bottom: { xs: "calc(8px + env(safe-area-inset-bottom, 0px))", sm: "calc(24px + env(safe-area-inset-bottom, 0px))" } }}
|
||||
>
|
||||
<Alert
|
||||
onClose={() => setT((p) => ({ ...p, open: false }))}
|
||||
|
||||
Reference in New Issue
Block a user