refactor(dialog): standard modal layout, NoticeBanner design-element, softer Cancel

Dialog component:
- Title bar gets a subtle background tint + bottom border. Title is
  variant=h2 weight=600. Description tucks under the title with
  variant=body1 secondary color (replaces MUI's DialogContentText).
- Action bar gets a matching subtle tint + top border. Cancel is now
  outlined (was contained-error, which made every modal's Cancel button
  look destructive). Confirm keeps its color (primary by default,
  caller can pass error for destructive flows).
- Content area sits between with its own breathing room baked in, so
  callers no longer need mt={theme.spacing(...)} workarounds.
- The cancelColor prop is removed; sites that genuinely need a
  destructive Cancel can use confirmColor='error' on the Confirm button
  instead.

NoticeBanner design-element:
- Extracted from the inline alert pattern in PageSpeedKeyPriorityFallback
  into a reusable component with severity=info|warning|error.
- Refactored PageSpeedKeyPriorityFallback to use it.

DialogResolution.tsx: drop the now-removed cancelColor prop and the
mt-Box wrapper that's no longer needed (Dialog provides the spacing).
This commit is contained in:
gorkem-bwl committed 2026-04-28 23:53:59 -04:00
1 parent a617bd340a
commit fef3f2c294
5 files changed
+130 -75

No files matched your search

@@ -9,13 +9,14 @@ import {
EmptyMonitorFallback,
} from "@/Components/design-elements/Fallback";
import { EmptyState } from "@/Components/design-elements/EmptyState";
import { NoticeBanner } from "@/Components/design-elements/NoticeBanner";
import { Breadcrumb } from "@/Components/design-elements/Breadcrumb";
import { PageHeader } from "@/Components/design-elements/PageHeader";
import CircularProgress from "@mui/material/CircularProgress";
import { LanguageSelector, SwitchTheme } from "@/Components/inputs";
import type { StackProps } from "@mui/material/Stack";
import { useTheme, alpha } from "@mui/material/styles";
import { useTheme } from "@mui/material/styles";
import { Trans, useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";
import { Typography } from "@mui/material";
@@ -23,60 +24,30 @@ import { Typography } from "@mui/material";
export const PageSpeedKeyPriorityFallback = () => {
const theme = useTheme();
const { t } = useTranslation();
const alertBg = alpha(theme.palette.warning.main, 0.08);
const alertBorder = alpha(theme.palette.warning.main, 0.45);
const alertIcon = theme.palette.warning.main;
return (
<EmptyState
fullscreen
title={t("pages.pageSpeed.fallback.title")}
description={t("pages.pageSpeed.fallback.description")}
alert={
<Stack
direction="row"
alignItems="flex-start"
gap={theme.spacing(LAYOUT.SM)}
sx={{
width: "100%",
p: theme.spacing(LAYOUT.MD),
borderRadius: 1,
border: `1px solid ${alertBorder}`,
backgroundColor: alertBg,
textAlign: "left",
}}
>
<Box
component="span"
sx={{
color: alertIcon,
fontSize: 18,
lineHeight: 1,
mt: "2px",
<NoticeBanner severity="warning">
<Trans
i18nKey="common.alerts.pageSpeedApiKey.content"
components={{
settingsLink: (
<Link
component={RouterLink}
to="/settings"
sx={{
color: theme.palette.primary.main,
fontWeight: 600,
textDecoration: "underline",
}}
/>
),
}}
>
⚠
</Box>
<Typography
sx={{ color: theme.palette.text.primary, lineHeight: 1.55, fontSize: 13 }}
>
<Trans
i18nKey="common.alerts.pageSpeedApiKey.content"
components={{
settingsLink: (
<Link
component={RouterLink}
to="/settings"
sx={{
color: theme.palette.primary.main,
fontWeight: 600,
textDecoration: "underline",
}}
/>
),
}}
/>
</Typography>
</Stack>
/>
</NoticeBanner>
}
/>
);
@@ -0,0 +1,56 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useTheme, alpha } from "@mui/material/styles";
import { LAYOUT } from "@/Utils/Theme/constants";
type Severity = "info" | "warning" | "error";
const SEVERITY_GLYPH: Record<Severity, string> = {
info: "ⓘ",
warning: "⚠",
error: "✕",
};
interface NoticeBannerProps {
severity?: Severity;
children: React.ReactNode;
}
export const NoticeBanner = ({ severity = "info", children }: NoticeBannerProps) => {
const theme = useTheme();
const tone = theme.palette[severity].main;
return (
<Stack
direction="row"
alignItems="flex-start"
gap={theme.spacing(LAYOUT.SM)}
sx={{
width: "100%",
p: theme.spacing(LAYOUT.MD),
borderRadius: 1,
border: `1px solid ${alpha(tone, 0.45)}`,
backgroundColor: alpha(tone, 0.08),
textAlign: "left",
}}
>
<Box
component="span"
sx={{
color: tone,
fontSize: 18,
lineHeight: 1,
mt: "2px",
}}
aria-hidden
>
{SEVERITY_GLYPH[severity]}
</Box>
<Typography
sx={{ color: theme.palette.text.primary, lineHeight: 1.55, fontSize: 13 }}
>
{children}
</Typography>
</Stack>
);
};
@@ -22,5 +22,6 @@ export * from "./Gauge";
export * from "./Tabs";
export * from "./SplitBox";
export * from "./TextLink";
export * from "./NoticeBanner";
export * from "./OfflineBanner";
export * from "./Avatar";
+45 -12
View File
@@ -2,10 +2,12 @@ import Dialog from "@mui/material/Dialog";
import type { DialogProps } from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { Button } from "@/Components/inputs";
import { typographyLevels } from "@/Utils/Theme/Palette";
import { LAYOUT } from "@/Utils/Theme/constants";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
import type { ReactNode } from "react";
@@ -16,7 +18,6 @@ export const DialogInput = ({
onConfirm,
onCancel,
confirmColor = "primary",
cancelColor = "error",
loading = false,
cancelText,
confirmText,
@@ -31,7 +32,6 @@ export const DialogInput = ({
onConfirm?(item: any): any;
onCancel?(item: any): any;
confirmColor?: "error" | "primary";
cancelColor?: "error" | "primary";
loading?: boolean;
cancelText?: string;
confirmText?: string;
@@ -41,6 +41,7 @@ export const DialogInput = ({
additionalButtons?: ReactNode;
}) => {
const { t } = useTranslation();
const theme = useTheme();
return (
<Dialog
open={open}
@@ -50,16 +51,48 @@ export const DialogInput = ({
if (reason !== "backdropClick" && onCancel) onCancel(undefined);
}}
>
{title && <DialogTitle sx={{ fontSize: typographyLevels.l }}>{title}</DialogTitle>}
<DialogContent>
{content && <DialogContentText>{content}</DialogContentText>}
{children}
</DialogContent>
<DialogActions>
{title && (
<DialogTitle
sx={{
pb: theme.spacing(LAYOUT.XS),
backgroundColor: theme.palette.action.hover,
borderBottom: `1px solid ${theme.palette.divider}`,
}}
>
<Typography
component="span"
variant="h2"
sx={{ fontWeight: 600, display: "block" }}
>
{title}
</Typography>
{content && (
<Typography
variant="body1"
color="text.secondary"
sx={{ mt: theme.spacing(LAYOUT.XS), display: "block" }}
>
{content}
</Typography>
)}
</DialogTitle>
)}
{children && (
<DialogContent sx={{ pt: theme.spacing(LAYOUT.MD) }}>
<Box sx={{ pt: theme.spacing(LAYOUT.XS) }}>{children}</Box>
</DialogContent>
)}
<DialogActions
sx={{
p: theme.spacing(LAYOUT.MD),
pt: theme.spacing(LAYOUT.SM),
backgroundColor: theme.palette.action.hover,
borderTop: `1px solid ${theme.palette.divider}`,
}}
>
<Button
loading={loading}
variant="contained"
color={cancelColor}
variant="outlined"
onClick={onCancel}
>
{cancelText ?? t("common.buttons.cancel")}
@@ -1,9 +1,7 @@
import { useState, useEffect } from "react";
import Box from "@mui/material/Box";
import { Dialog, TextField } from "@/Components/inputs";
import { usePut } from "@/Hooks/UseApi";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material";
interface DialogResolutionProps {
open: boolean;
@@ -19,7 +17,6 @@ export const DialogResolution = ({
onResolved,
}: DialogResolutionProps) => {
const { t } = useTranslation();
const theme = useTheme();
const [comment, setComment] = useState("");
const { put: resolveIncident, loading: isResolving } = usePut();
@@ -50,22 +47,19 @@ export const DialogResolution = ({
onCancel={handleCancel}
onConfirm={handleConfirm}
confirmColor="error"
cancelColor="primary"
loading={isResolving}
maxWidth="sm"
fullWidth
>
<Box sx={{ mt: theme.spacing(4) }}>
<TextField
fieldLabel={t("pages.incidents.dialog.resolveIncident.option.comment.label")}
placeholder={t(
"pages.incidents.dialog.resolveIncident.option.comment.placeholder"
)}
value={comment}
onChange={(e) => setComment(e.target.value)}
fullWidth
/>
</Box>
<TextField
fieldLabel={t("pages.incidents.dialog.resolveIncident.option.comment.label")}
placeholder={t(
"pages.incidents.dialog.resolveIncident.option.comment.placeholder"
)}
value={comment}
onChange={(e) => setComment(e.target.value)}
fullWidth
/>
</Dialog>
);
};