Skip to content

Commit 2043d1a

Browse files
chore: Port ConfirmDialog from v1 (#1228)
1 parent 4ff5734 commit 2043d1a

File tree

7 files changed

+677
-0
lines changed

7 files changed

+677
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import React from "react"
3+
import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog"
4+
5+
export default {
6+
title: "Components/Dialogs/ConfirmDialog",
7+
component: ConfirmDialog,
8+
argTypes: {
9+
onClose: {
10+
action: "onClose",
11+
},
12+
onConfirm: {
13+
action: "onConfirm",
14+
},
15+
open: {
16+
control: "boolean",
17+
defaultValue: true,
18+
},
19+
title: {
20+
defaultValue: "Confirm Dialog",
21+
},
22+
},
23+
} as ComponentMeta<typeof ConfirmDialog>
24+
25+
const Template: Story<ConfirmDialogProps> = (args) => <ConfirmDialog {...args} />
26+
27+
export const DeleteDialog = Template.bind({})
28+
DeleteDialog.args = {
29+
description: "Do you really want to delete me?",
30+
hideCancel: false,
31+
type: "delete",
32+
}
33+
34+
export const InfoDialog = Template.bind({})
35+
InfoDialog.args = {
36+
description: "Information is cool!",
37+
hideCancel: true,
38+
type: "info",
39+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import ThemeProvider from "@material-ui/styles/ThemeProvider"
2+
import { fireEvent, render } from "@testing-library/react"
3+
import React from "react"
4+
import { act } from "react-dom/test-utils"
5+
import { light } from "../../theme"
6+
import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog"
7+
8+
namespace Helpers {
9+
export const Component: React.FC<ConfirmDialogProps> = (props: ConfirmDialogProps) => {
10+
return (
11+
<ThemeProvider theme={light}>
12+
<ConfirmDialog {...props} />
13+
</ThemeProvider>
14+
)
15+
}
16+
}
17+
18+
describe("ConfirmDialog", () => {
19+
it("renders", () => {
20+
// Given
21+
const onCloseMock = jest.fn()
22+
const props = {
23+
onClose: onCloseMock,
24+
open: true,
25+
title: "Test",
26+
}
27+
28+
// When
29+
const { getByRole } = render(<Helpers.Component {...props} />)
30+
31+
// Then
32+
expect(getByRole("dialog")).toBeDefined()
33+
})
34+
35+
it("does not display cancel for info dialogs", () => {
36+
// Given (note that info is the default)
37+
const onCloseMock = jest.fn()
38+
const props = {
39+
cancelText: "CANCEL",
40+
onClose: onCloseMock,
41+
open: true,
42+
title: "Test",
43+
}
44+
45+
// When
46+
const { queryByText } = render(<Helpers.Component {...props} />)
47+
48+
// Then
49+
expect(queryByText("CANCEL")).toBeNull()
50+
})
51+
52+
it("can display cancel when normally hidden", () => {
53+
// Given
54+
const onCloseMock = jest.fn()
55+
const props = {
56+
cancelText: "CANCEL",
57+
onClose: onCloseMock,
58+
open: true,
59+
title: "Test",
60+
hideCancel: false,
61+
}
62+
63+
// When
64+
const { getByText } = render(<Helpers.Component {...props} />)
65+
66+
// Then
67+
expect(getByText("CANCEL")).toBeDefined()
68+
})
69+
70+
it("displays cancel for delete dialogs", () => {
71+
// Given
72+
const onCloseMock = jest.fn()
73+
const props: ConfirmDialogProps = {
74+
cancelText: "CANCEL",
75+
onClose: onCloseMock,
76+
open: true,
77+
title: "Test",
78+
type: "delete",
79+
}
80+
81+
// When
82+
const { getByText } = render(<Helpers.Component {...props} />)
83+
84+
// Then
85+
expect(getByText("CANCEL")).toBeDefined()
86+
})
87+
88+
it("can hide cancel when normally visible", () => {
89+
// Given
90+
const onCloseMock = jest.fn()
91+
const props: ConfirmDialogProps = {
92+
cancelText: "CANCEL",
93+
onClose: onCloseMock,
94+
open: true,
95+
title: "Test",
96+
hideCancel: true,
97+
type: "delete",
98+
}
99+
100+
// When
101+
const { queryByText } = render(<Helpers.Component {...props} />)
102+
103+
// Then
104+
expect(queryByText("CANCEL")).toBeNull()
105+
})
106+
107+
it("onClose is called when cancelled", () => {
108+
// Given
109+
const onCloseMock = jest.fn()
110+
const props = {
111+
cancelText: "CANCEL",
112+
hideCancel: false,
113+
onClose: onCloseMock,
114+
open: true,
115+
title: "Test",
116+
}
117+
118+
// When
119+
const { getByText } = render(<Helpers.Component {...props} />)
120+
act(() => {
121+
fireEvent.click(getByText("CANCEL"))
122+
})
123+
124+
// Then
125+
expect(onCloseMock).toBeCalledTimes(1)
126+
})
127+
128+
it("onConfirm is called when confirmed", () => {
129+
// Given
130+
const onCloseMock = jest.fn()
131+
const onConfirmMock = jest.fn()
132+
const props = {
133+
cancelText: "CANCEL",
134+
confirmText: "CONFIRM",
135+
hideCancel: false,
136+
onClose: onCloseMock,
137+
onConfirm: onConfirmMock,
138+
open: true,
139+
title: "Test",
140+
}
141+
142+
// When
143+
const { getByText } = render(<Helpers.Component {...props} />)
144+
act(() => {
145+
fireEvent.click(getByText("CONFIRM"))
146+
})
147+
148+
// Then
149+
expect(onCloseMock).toBeCalledTimes(0)
150+
expect(onConfirmMock).toBeCalledTimes(1)
151+
})
152+
})
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import DialogActions from "@material-ui/core/DialogActions"
2+
import { fade, makeStyles } from "@material-ui/core/styles"
3+
import Typography from "@material-ui/core/Typography"
4+
import React, { ReactNode } from "react"
5+
import { Dialog, DialogActionButtons, DialogActionButtonsProps } from "../Dialog/Dialog"
6+
import { ConfirmDialogType } from "../Dialog/types"
7+
8+
interface ConfirmDialogTypeConfig {
9+
confirmText: ReactNode
10+
hideCancel: boolean
11+
}
12+
13+
const CONFIRM_DIALOG_DEFAULTS: Record<ConfirmDialogType, ConfirmDialogTypeConfig> = {
14+
delete: {
15+
confirmText: "Delete",
16+
hideCancel: false,
17+
},
18+
info: {
19+
confirmText: "OK",
20+
hideCancel: true,
21+
},
22+
}
23+
24+
export interface ConfirmDialogProps extends Omit<DialogActionButtonsProps, "color" | "confirmDialog" | "onCancel"> {
25+
readonly description?: React.ReactNode
26+
/**
27+
* hideCancel hides the cancel button when set true, and shows the cancel
28+
* button when set to false. When undefined:
29+
* - cancel is not displayed for "info" dialogs
30+
* - cancel is displayed for "delete" dialogs
31+
*/
32+
readonly hideCancel?: boolean
33+
/**
34+
* onClose is called when canceling (if cancel is showing).
35+
*
36+
* Additionally, if onConfirm is not defined onClose will be used in its place
37+
* when confirming.
38+
*/
39+
readonly onClose: () => void
40+
readonly open: boolean
41+
readonly title: string
42+
}
43+
44+
interface StyleProps {
45+
type: ConfirmDialogType
46+
}
47+
48+
const useStyles = makeStyles((theme) => ({
49+
dialogWrapper: (props: StyleProps) => ({
50+
"& .MuiPaper-root": {
51+
background:
52+
props.type === "info"
53+
? theme.palette.confirmDialog.info.background
54+
: theme.palette.confirmDialog.error.background,
55+
},
56+
}),
57+
dialogContent: (props: StyleProps) => ({
58+
color: props.type === "info" ? theme.palette.confirmDialog.info.text : theme.palette.confirmDialog.error.text,
59+
padding: theme.spacing(6),
60+
textAlign: "center",
61+
}),
62+
titleText: {
63+
marginBottom: theme.spacing(3),
64+
},
65+
description: (props: StyleProps) => ({
66+
color:
67+
props.type === "info"
68+
? fade(theme.palette.confirmDialog.info.text, 0.75)
69+
: fade(theme.palette.confirmDialog.error.text, 0.75),
70+
lineHeight: "160%",
71+
72+
"& strong": {
73+
color:
74+
props.type === "info"
75+
? fade(theme.palette.confirmDialog.info.text, 0.95)
76+
: fade(theme.palette.confirmDialog.error.text, 0.95),
77+
},
78+
}),
79+
}))
80+
81+
/**
82+
* Quick-use version of the Dialog component with slightly alternative styles,
83+
* great to use for dialogs that don't have any interaction beyond yes / no.
84+
*/
85+
export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
86+
cancelText,
87+
confirmLoading,
88+
confirmText,
89+
description,
90+
hideCancel,
91+
onClose,
92+
onConfirm,
93+
open = false,
94+
title,
95+
type = "info",
96+
}) => {
97+
const styles = useStyles({ type })
98+
99+
const defaults = CONFIRM_DIALOG_DEFAULTS[type]
100+
101+
if (typeof hideCancel === "undefined") {
102+
hideCancel = defaults.hideCancel
103+
}
104+
105+
return (
106+
<Dialog className={styles.dialogWrapper} maxWidth="sm" onClose={onClose} open={open}>
107+
<div className={styles.dialogContent}>
108+
<Typography className={styles.titleText} variant="h3">
109+
{title}
110+
</Typography>
111+
112+
{description && (
113+
<Typography className={styles.description} variant="body2">
114+
{description}
115+
</Typography>
116+
)}
117+
</div>
118+
119+
<DialogActions>
120+
<DialogActionButtons
121+
cancelText={cancelText}
122+
confirmDialog
123+
confirmLoading={confirmLoading}
124+
confirmText={confirmText || defaults.confirmText}
125+
onCancel={!hideCancel ? onClose : undefined}
126+
onConfirm={onConfirm || onClose}
127+
type={type}
128+
/>
129+
</DialogActions>
130+
</Dialog>
131+
)
132+
}

0 commit comments

Comments
 (0)