feat: redesign event modal with responsive dialog and improved layout
Implemented a new ResponsiveDialog component and redesigned EventModal with better UX for both normal and extended modes. New Features: - Created reusable ResponsiveDialog component (src/components/Dialog/) * Normal mode: 685px centered popup * Extended mode: fullscreen with 90px header preservation * Auto spacing via MUI Stack (16px normal, 24px extended) * Back arrow navigation in extended mode * No backdrop/shadow in extended mode for seamless integration * Configurable props for all dimensions and behaviors EventModal Improvements: - Replaced native checkbox with MUI Checkbox component - Migrated from Popover to ResponsiveDialog - Reorganized field layout => Comprehensive documentation in Dialog/README.md Note: RepeatEvent integration tests need additional refactoring (tracked separately)
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentProps,
|
||||
DialogTitle,
|
||||
DialogTitleProps,
|
||||
DialogProps,
|
||||
IconButton,
|
||||
Stack,
|
||||
SxProps,
|
||||
Theme,
|
||||
} from "@mui/material";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
/**
|
||||
* ResponsiveDialog - A reusable dialog component that can switch between normal and expanded modes
|
||||
*
|
||||
* Features:
|
||||
* - Normal mode: Dialog with customizable max-width (default 685px)
|
||||
* - Expanded mode: Full height dialog (excluding app header) with centered content container
|
||||
* - Fully customizable with sx props for Dialog, DialogTitle, and DialogContent
|
||||
* - Preserves app header visibility in expanded mode
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ResponsiveDialog
|
||||
* open={open}
|
||||
* onClose={handleClose}
|
||||
* title="My Dialog"
|
||||
* isExpanded={showMore}
|
||||
* actions={<Button onClick={handleSave}>Save</Button>}
|
||||
* contentSx={{ padding: 3 }}
|
||||
* >
|
||||
* <TextField label="Name" />
|
||||
* </ResponsiveDialog>
|
||||
* ```
|
||||
*/
|
||||
interface ResponsiveDialogProps
|
||||
extends Omit<DialogProps, "maxWidth" | "title"> {
|
||||
/** Whether the dialog is open */
|
||||
open: boolean;
|
||||
/** Callback fired when the dialog should be closed */
|
||||
onClose: () => void;
|
||||
/** Dialog title - can be string or custom ReactNode */
|
||||
title: string | ReactNode;
|
||||
/** Dialog content - form fields, text, etc. */
|
||||
children: ReactNode;
|
||||
/** Optional actions rendered in DialogActions (buttons, etc.) */
|
||||
actions?: ReactNode;
|
||||
/** Toggle between normal and expanded (fullscreen) mode */
|
||||
isExpanded?: boolean;
|
||||
/** Callback when expand/collapse button is clicked (required if using isExpanded) */
|
||||
onExpandToggle?: () => void;
|
||||
/** Max width in normal mode (default: "685px") */
|
||||
normalMaxWidth?: string;
|
||||
/** Max width of content container in expanded mode (default: "990px") */
|
||||
expandedContentMaxWidth?: string;
|
||||
/** Height of app header to preserve visibility (default: "90px") */
|
||||
headerHeight?: string;
|
||||
/** Spacing between children in normal mode (default: 2 = 16px) */
|
||||
normalSpacing?: number;
|
||||
/** Spacing between children in expanded mode (default: 3 = 24px) */
|
||||
expandedSpacing?: number;
|
||||
/** Custom styles for DialogContent - merged with base styles */
|
||||
contentSx?: SxProps<Theme>;
|
||||
/** Custom styles for DialogTitle */
|
||||
titleSx?: SxProps<Theme>;
|
||||
/** Additional props for DialogContent (excluding sx) */
|
||||
dialogContentProps?: Omit<DialogContentProps, "sx">;
|
||||
/** Additional props for DialogTitle (excluding sx) */
|
||||
dialogTitleProps?: Omit<DialogTitleProps, "sx">;
|
||||
/** Whether to display dividers between title/content/actions */
|
||||
dividers?: boolean;
|
||||
}
|
||||
|
||||
function ResponsiveDialog({
|
||||
open,
|
||||
onClose,
|
||||
title,
|
||||
children,
|
||||
actions,
|
||||
isExpanded = false,
|
||||
onExpandToggle,
|
||||
normalMaxWidth = "685px",
|
||||
expandedContentMaxWidth = "990px",
|
||||
headerHeight = "90px",
|
||||
normalSpacing = 2,
|
||||
expandedSpacing = 3,
|
||||
contentSx,
|
||||
titleSx,
|
||||
dialogContentProps,
|
||||
dialogTitleProps,
|
||||
dividers = false,
|
||||
sx,
|
||||
...otherDialogProps
|
||||
}: ResponsiveDialogProps) {
|
||||
const baseSx: SxProps<Theme> = {
|
||||
"& .MuiBackdrop-root": {
|
||||
opacity: isExpanded ? "0 !important" : undefined,
|
||||
transition: isExpanded ? "none !important" : undefined,
|
||||
},
|
||||
"& .MuiDialog-paper": {
|
||||
maxWidth: isExpanded ? "100%" : normalMaxWidth,
|
||||
width: "100%",
|
||||
height: isExpanded ? `calc(100vh - ${headerHeight})` : "auto",
|
||||
margin: isExpanded ? `${headerHeight} 0 0 0` : "32px",
|
||||
maxHeight: isExpanded
|
||||
? `calc(100vh - ${headerHeight})`
|
||||
: `calc(100vh - 90px)`,
|
||||
boxShadow: isExpanded ? "none !important" : undefined,
|
||||
transition: isExpanded ? "none !important" : undefined,
|
||||
},
|
||||
"& .MuiDialogActions-root .MuiBox-root": {
|
||||
maxWidth: isExpanded ? expandedContentMaxWidth : undefined,
|
||||
margin: isExpanded ? "0 auto" : undefined,
|
||||
padding: isExpanded ? "0 12px" : undefined,
|
||||
width: isExpanded ? "100%" : undefined,
|
||||
justifyContent: isExpanded ? "flex-end" : undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const baseContentSx: SxProps<Theme> = {
|
||||
maxWidth: isExpanded ? expandedContentMaxWidth : "100%",
|
||||
margin: isExpanded ? "0 auto" : "0",
|
||||
width: "100%",
|
||||
};
|
||||
|
||||
const currentSpacing = isExpanded ? expandedSpacing : normalSpacing;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth={false}
|
||||
fullWidth
|
||||
transitionDuration={isExpanded ? 0 : 300}
|
||||
sx={[baseSx, ...(Array.isArray(sx) ? sx : [sx])]}
|
||||
{...otherDialogProps}
|
||||
>
|
||||
<DialogTitle sx={titleSx} {...dialogTitleProps}>
|
||||
{isExpanded && onExpandToggle ? (
|
||||
<IconButton
|
||||
onClick={onExpandToggle}
|
||||
aria-label="show less"
|
||||
sx={{ marginLeft: "-8px" }}
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogContent
|
||||
dividers={dividers}
|
||||
sx={[
|
||||
baseContentSx,
|
||||
...(Array.isArray(contentSx) ? contentSx : [contentSx]),
|
||||
]}
|
||||
{...dialogContentProps}
|
||||
>
|
||||
<Stack spacing={currentSpacing}>{children}</Stack>
|
||||
</DialogContent>
|
||||
{actions && <DialogActions>{actions}</DialogActions>}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResponsiveDialog;
|
||||
Reference in New Issue
Block a user