Files
jobtrackingapp/job-tracker-ui/src/toast.tsx
T
cesnimda 24c7d68490
CI and Deploy / test (push) Successful in 2m14s
CI and Deploy / deploy (push) Successful in 37s
style(ui): float dialogs/menus/popovers, round chips and tooltips globally
Every Dialog/Menu/Popover in the app renders its content via MuiPaper,
which is deliberately kept flat (1px border, no shadow -- Paper is
used too broadly, e.g. as a plain content divider, to safely restyle
globally). That meant every modal (confirm/prompt, AddJobModal,
EditJobDialog, JobDetailsDialog) and every dropdown/Select menu in the
app was still rendering flat-bordered despite every other screen this
session moving to the floating-shadow mockup look.

Added targeted MuiDialog/MuiPopover/MuiMenu paper overrides -- these
win on specificity over MuiPaper's own defaults without touching
MuiPaper itself, so every dialog and dropdown in the app picks up the
rounded floating-shadow treatment from this one change instead of
patching each dialog file individually. Also added MuiChip (full pill
radius, matching every status/skill pill in the mockups) and
MuiTooltip (matching corner radius) overrides, and gave the toast
Snackbar/Alert a consistent radius + weight.
2026-07-13 14:28:22 +02:00

84 lines
2.2 KiB
TypeScript

import React, { createContext, useCallback, useContext, useMemo, useState } from "react";
import { Alert, Button, Snackbar } from "@mui/material";
type Severity = "success" | "info" | "warning" | "error";
type Toast = {
open: boolean;
message: string;
severity: Severity;
actionLabel?: string;
onAction?: () => void;
};
type ToastApi = {
toast: (
message: string,
severity?: Severity,
action?: { label: string; onClick: () => void },
) => void;
};
const ToastContext = createContext<ToastApi | null>(null);
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [t, setT] = useState<Toast>({ open: false, message: "", severity: "info" });
const toast = useCallback(
(message: string, severity: Severity = "info", action?: { label: string; onClick: () => void }) => {
setT({
open: true,
message,
severity,
actionLabel: action?.label,
onAction: action?.onClick,
});
},
[],
);
const value = useMemo(() => ({ toast }), [toast]);
return (
<ToastContext.Provider value={value}>
{children}
<Snackbar
open={t.open}
autoHideDuration={3500}
onClose={() => setT((p) => ({ ...p, open: false }))}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert
onClose={() => setT((p) => ({ ...p, open: false }))}
severity={t.severity}
variant="filled"
sx={{ maxWidth: 420, borderRadius: 3, fontWeight: 600 }}
action={
t.actionLabel && t.onAction ? (
<Button
color="inherit"
size="small"
onClick={() => {
setT((p) => ({ ...p, open: false }));
t.onAction?.();
}}
>
{t.actionLabel}
</Button>
) : undefined
}
>
{t.message}
</Alert>
</Snackbar>
</ToastContext.Provider>
);
}
export function useToast(): ToastApi {
const v = useContext(ToastContext);
if (!v) throw new Error("useToast must be used within ToastProvider");
return v;
}