feat: Update event modal UI with header icons and improved actions layout
- Add expand and close icons to ResponsiveDialog header in normal mode - Change 'Show More' button to 'More options' with Add icon - Hide Cancel button in normal mode, show only in extended mode - Add showHeaderActions prop to ResponsiveDialog for UI control - Preserve EventDisplayPreview modal with old UI (no header icons) - Update all affected test cases to match new UI behavior
This commit is contained in:
committed by
Benoit TELLIER
parent
4936b89e8b
commit
4c18bf3a52
@@ -95,6 +95,9 @@ describe("EventDuplication", () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByText("Duplicate event"));
|
fireEvent.click(screen.getByText("Duplicate event"));
|
||||||
|
|
||||||
|
// Cancel button only appears in expanded mode
|
||||||
|
fireEvent.click(screen.getByText("More options"));
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: /Cancel/i }));
|
fireEvent.click(screen.getByRole("button", { name: /Cancel/i }));
|
||||||
|
|
||||||
expect(handleClose).toHaveBeenCalled();
|
expect(handleClose).toHaveBeenCalled();
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ async function setupEventPopover(
|
|||||||
const titleInput = screen.getByLabelText("Title");
|
const titleInput = screen.getByLabelText("Title");
|
||||||
fireEvent.change(titleInput, { target: { value: "Meeting" } });
|
fireEvent.change(titleInput, { target: { value: "Meeting" } });
|
||||||
|
|
||||||
// Click Show More to expand the dialog
|
// Click More options to expand the dialog
|
||||||
const showMoreButton = screen.getByText("Show More");
|
const showMoreButton = screen.getByText("More options");
|
||||||
fireEvent.click(showMoreButton);
|
fireEvent.click(showMoreButton);
|
||||||
|
|
||||||
// Check Repeat checkbox to show repeat options
|
// Check Repeat checkbox to show repeat options
|
||||||
|
|||||||
@@ -295,4 +295,80 @@ describe("ResponsiveDialog", () => {
|
|||||||
expect(screen.getByText("Extended Content")).toBeInTheDocument();
|
expect(screen.getByText("Extended Content")).toBeInTheDocument();
|
||||||
expect(screen.getByLabelText("show less")).toBeInTheDocument();
|
expect(screen.getByLabelText("show less")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders expand and close icons in normal mode when showHeaderActions is true", () => {
|
||||||
|
render(
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
title="Test"
|
||||||
|
isExpanded={false}
|
||||||
|
onExpandToggle={mockOnExpandToggle}
|
||||||
|
showHeaderActions={true}
|
||||||
|
>
|
||||||
|
<div>Content</div>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("expand")).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText("close")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render header icons when showHeaderActions is false", () => {
|
||||||
|
render(
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
title="Test Title"
|
||||||
|
isExpanded={false}
|
||||||
|
onExpandToggle={mockOnExpandToggle}
|
||||||
|
showHeaderActions={false}
|
||||||
|
>
|
||||||
|
<div>Content</div>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByLabelText("expand")).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByLabelText("close")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Test Title")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onClose when close icon is clicked", () => {
|
||||||
|
render(
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
title="Test"
|
||||||
|
isExpanded={false}
|
||||||
|
showHeaderActions={true}
|
||||||
|
>
|
||||||
|
<div>Content</div>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeButton = screen.getByLabelText("close");
|
||||||
|
fireEvent.click(closeButton);
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onExpandToggle when expand icon is clicked", () => {
|
||||||
|
render(
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
title="Test"
|
||||||
|
isExpanded={false}
|
||||||
|
onExpandToggle={mockOnExpandToggle}
|
||||||
|
showHeaderActions={true}
|
||||||
|
>
|
||||||
|
<div>Content</div>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
|
||||||
|
const expandButton = screen.getByLabelText("expand");
|
||||||
|
fireEvent.click(expandButton);
|
||||||
|
|
||||||
|
expect(mockOnExpandToggle).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -325,9 +325,12 @@ describe("CalendarPopover - Tabs Scenarios", () => {
|
|||||||
const input = screen.getByLabelText(/CalDAV access/i);
|
const input = screen.getByLabelText(/CalDAV access/i);
|
||||||
expect(input).toHaveValue("https://cal.example.org/calendars/user1/cal1");
|
expect(input).toHaveValue("https://cal.example.org/calendars/user1/cal1");
|
||||||
|
|
||||||
// Click copy button
|
// Click copy button (find button containing ContentCopyIcon)
|
||||||
const copyButton = screen.getAllByRole("button")[0];
|
const copyIcon = screen.getByTestId("ContentCopyIcon");
|
||||||
fireEvent.click(copyButton);
|
const copyButton = copyIcon.closest("button");
|
||||||
|
if (copyButton) {
|
||||||
|
fireEvent.click(copyButton);
|
||||||
|
}
|
||||||
|
|
||||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
|
||||||
"https://cal.example.org/calendars/user1/cal1"
|
"https://cal.example.org/calendars/user1/cal1"
|
||||||
|
|||||||
@@ -124,10 +124,10 @@ describe("EventPopover", () => {
|
|||||||
expect(calendarSelect).toBeInTheDocument();
|
expect(calendarSelect).toBeInTheDocument();
|
||||||
|
|
||||||
// Check button
|
// Check button
|
||||||
expect(screen.getByText("Show More")).toBeInTheDocument();
|
expect(screen.getByText("More options")).toBeInTheDocument();
|
||||||
|
|
||||||
// Extended mode
|
// Extended mode
|
||||||
fireEvent.click(screen.getByText("Show More"));
|
fireEvent.click(screen.getByText("More options"));
|
||||||
|
|
||||||
// Back button appears
|
// Back button appears
|
||||||
expect(screen.getByLabelText("show less")).toBeInTheDocument();
|
expect(screen.getByLabelText("show less")).toBeInTheDocument();
|
||||||
@@ -330,6 +330,9 @@ describe("EventPopover", () => {
|
|||||||
it("calls onClose when Cancel clicked", () => {
|
it("calls onClose when Cancel clicked", () => {
|
||||||
renderPopover();
|
renderPopover();
|
||||||
|
|
||||||
|
// Cancel button only appears in expanded mode
|
||||||
|
fireEvent.click(screen.getByText("More options"));
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Cancel"));
|
fireEvent.click(screen.getByText("Cancel"));
|
||||||
|
|
||||||
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
SxProps,
|
SxProps,
|
||||||
Theme,
|
Theme,
|
||||||
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import OpenInFullIcon from "@mui/icons-material/OpenInFull";
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,6 +76,8 @@ interface ResponsiveDialogProps
|
|||||||
dialogTitleProps?: Omit<DialogTitleProps, "sx">;
|
dialogTitleProps?: Omit<DialogTitleProps, "sx">;
|
||||||
/** Whether to display dividers between title/content/actions */
|
/** Whether to display dividers between title/content/actions */
|
||||||
dividers?: boolean;
|
dividers?: boolean;
|
||||||
|
/** Whether to show header action icons (expand/close) in normal mode (default: true) */
|
||||||
|
showHeaderActions?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ResponsiveDialog({
|
function ResponsiveDialog({
|
||||||
@@ -93,6 +98,7 @@ function ResponsiveDialog({
|
|||||||
dialogContentProps,
|
dialogContentProps,
|
||||||
dialogTitleProps,
|
dialogTitleProps,
|
||||||
dividers = false,
|
dividers = false,
|
||||||
|
showHeaderActions = true,
|
||||||
sx,
|
sx,
|
||||||
...otherDialogProps
|
...otherDialogProps
|
||||||
}: ResponsiveDialogProps) {
|
}: ResponsiveDialogProps) {
|
||||||
@@ -150,6 +156,29 @@ function ResponsiveDialog({
|
|||||||
>
|
>
|
||||||
<ArrowBackIcon />
|
<ArrowBackIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
) : showHeaderActions ? (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<Box>{title}</Box>
|
||||||
|
<Box>
|
||||||
|
{onExpandToggle && (
|
||||||
|
<IconButton
|
||||||
|
onClick={onExpandToggle}
|
||||||
|
aria-label="expand"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<OpenInFullIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<IconButton onClick={onClose} aria-label="close" size="small">
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
title
|
title
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export default function EventPreviewModal({
|
|||||||
<ResponsiveDialog
|
<ResponsiveDialog
|
||||||
open={open && !hidePreview}
|
open={open && !hidePreview}
|
||||||
onClose={() => onClose({}, "backdropClick")}
|
onClose={() => onClose({}, "backdropClick")}
|
||||||
|
showHeaderActions={false}
|
||||||
style={{ overflow: "auto" }}
|
style={{ overflow: "auto" }}
|
||||||
title={
|
title={
|
||||||
event.title && (
|
event.title && (
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
|
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
|
||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
@@ -23,6 +21,8 @@ import {
|
|||||||
ContentCopy as CopyIcon,
|
ContentCopy as CopyIcon,
|
||||||
Close as DeleteIcon,
|
Close as DeleteIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
import { Box, Button } from "@mui/material";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import React, {
|
import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
@@ -409,12 +409,16 @@ function EventPopover({
|
|||||||
const dialogActions = (
|
const dialogActions = (
|
||||||
<Box display="flex" justifyContent="space-between" width="100%" px={2}>
|
<Box display="flex" justifyContent="space-between" width="100%" px={2}>
|
||||||
{!showMore && (
|
{!showMore && (
|
||||||
<Button onClick={() => setShowMore(!showMore)}>Show More</Button>
|
<Button startIcon={<AddIcon />} onClick={() => setShowMore(!showMore)}>
|
||||||
|
More options
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Box display="flex" gap={1} ml={showMore ? "auto" : 0}>
|
<Box display="flex" gap={1} ml={showMore ? "auto" : 0}>
|
||||||
<Button variant="outlined" onClick={handleClose}>
|
{showMore && (
|
||||||
Cancel
|
<Button variant="outlined" onClick={handleClose}>
|
||||||
</Button>
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button variant="contained" onClick={handleSave} disabled={!title}>
|
<Button variant="contained" onClick={handleSave} disabled={!title}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Box, Button } from "@mui/material";
|
import { Box, Button } from "@mui/material";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import React, {
|
import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
@@ -658,12 +659,16 @@ function EventUpdateModal({
|
|||||||
const dialogActions = (
|
const dialogActions = (
|
||||||
<Box display="flex" justifyContent="space-between" width="100%" px={2}>
|
<Box display="flex" justifyContent="space-between" width="100%" px={2}>
|
||||||
{!showMore && (
|
{!showMore && (
|
||||||
<Button onClick={() => setShowMore(!showMore)}>Show More</Button>
|
<Button startIcon={<AddIcon />} onClick={() => setShowMore(!showMore)}>
|
||||||
|
More options
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Box display="flex" gap={1} ml={showMore ? "auto" : 0}>
|
<Box display="flex" gap={1} ml={showMore ? "auto" : 0}>
|
||||||
<Button variant="outlined" onClick={handleClose}>
|
{showMore && (
|
||||||
Cancel
|
<Button variant="outlined" onClick={handleClose}>
|
||||||
</Button>
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button variant="contained" onClick={handleSave} disabled={!title}>
|
<Button variant="contained" onClick={handleSave} disabled={!title}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user