Skip to content

chore: Port ConfirmDialog from v1 #1228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions site/src/components/ConfirmDialog/ConfirmDialog.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ComponentMeta, Story } from "@storybook/react"
import React from "react"
import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog"

export default {
title: "Components/Dialogs/ConfirmDialog",
component: ConfirmDialog,
argTypes: {
onClose: {
action: "onClose",
},
onConfirm: {
action: "onConfirm",
},
open: {
control: "boolean",
defaultValue: true,
},
title: {
defaultValue: "Confirm Dialog",
},
},
} as ComponentMeta<typeof ConfirmDialog>

const Template: Story<ConfirmDialogProps> = (args) => <ConfirmDialog {...args} />

export const DeleteDialog = Template.bind({})
DeleteDialog.args = {
description: "Do you really want to delete me?",
hideCancel: false,
type: "delete",
}

export const InfoDialog = Template.bind({})
InfoDialog.args = {
description: "Information is cool!",
hideCancel: true,
type: "info",
}
152 changes: 152 additions & 0 deletions site/src/components/ConfirmDialog/ConfirmDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import ThemeProvider from "@material-ui/styles/ThemeProvider"
import { fireEvent, render } from "@testing-library/react"
import React from "react"
import { act } from "react-dom/test-utils"
import { light } from "../../theme"
import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog"

namespace Helpers {
export const Component: React.FC<ConfirmDialogProps> = (props: ConfirmDialogProps) => {
return (
<ThemeProvider theme={light}>
<ConfirmDialog {...props} />
</ThemeProvider>
)
}
}

describe("ConfirmDialog", () => {
it("renders", () => {
// Given
const onCloseMock = jest.fn()
const props = {
onClose: onCloseMock,
open: true,
title: "Test",
}

// When
const { getByRole } = render(<Helpers.Component {...props} />)

// Then
expect(getByRole("dialog")).toBeDefined()
})

it("does not display cancel for info dialogs", () => {
// Given (note that info is the default)
const onCloseMock = jest.fn()
const props = {
cancelText: "CANCEL",
onClose: onCloseMock,
open: true,
title: "Test",
}

// When
const { queryByText } = render(<Helpers.Component {...props} />)

// Then
expect(queryByText("CANCEL")).toBeNull()
})

it("can display cancel when normally hidden", () => {
// Given
const onCloseMock = jest.fn()
const props = {
cancelText: "CANCEL",
onClose: onCloseMock,
open: true,
title: "Test",
hideCancel: false,
}

// When
const { getByText } = render(<Helpers.Component {...props} />)

// Then
expect(getByText("CANCEL")).toBeDefined()
})

it("displays cancel for delete dialogs", () => {
// Given
const onCloseMock = jest.fn()
const props: ConfirmDialogProps = {
cancelText: "CANCEL",
onClose: onCloseMock,
open: true,
title: "Test",
type: "delete",
}

// When
const { getByText } = render(<Helpers.Component {...props} />)

// Then
expect(getByText("CANCEL")).toBeDefined()
})

it("can hide cancel when normally visible", () => {
// Given
const onCloseMock = jest.fn()
const props: ConfirmDialogProps = {
cancelText: "CANCEL",
onClose: onCloseMock,
open: true,
title: "Test",
hideCancel: true,
type: "delete",
}

// When
const { queryByText } = render(<Helpers.Component {...props} />)

// Then
expect(queryByText("CANCEL")).toBeNull()
})

it("onClose is called when cancelled", () => {
// Given
const onCloseMock = jest.fn()
const props = {
cancelText: "CANCEL",
hideCancel: false,
onClose: onCloseMock,
open: true,
title: "Test",
}

// When
const { getByText } = render(<Helpers.Component {...props} />)
act(() => {
fireEvent.click(getByText("CANCEL"))
})

// Then
expect(onCloseMock).toBeCalledTimes(1)
})

it("onConfirm is called when confirmed", () => {
// Given
const onCloseMock = jest.fn()
const onConfirmMock = jest.fn()
const props = {
cancelText: "CANCEL",
confirmText: "CONFIRM",
hideCancel: false,
onClose: onCloseMock,
onConfirm: onConfirmMock,
open: true,
title: "Test",
}

// When
const { getByText } = render(<Helpers.Component {...props} />)
act(() => {
fireEvent.click(getByText("CONFIRM"))
})

// Then
expect(onCloseMock).toBeCalledTimes(0)
expect(onConfirmMock).toBeCalledTimes(1)
})
})
132 changes: 132 additions & 0 deletions site/src/components/ConfirmDialog/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import DialogActions from "@material-ui/core/DialogActions"
import { fade, makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography"
import React, { ReactNode } from "react"
import { Dialog, DialogActionButtons, DialogActionButtonsProps } from "../Dialog/Dialog"
import { ConfirmDialogType } from "../Dialog/types"

interface ConfirmDialogTypeConfig {
confirmText: ReactNode
hideCancel: boolean
}

const CONFIRM_DIALOG_DEFAULTS: Record<ConfirmDialogType, ConfirmDialogTypeConfig> = {
delete: {
confirmText: "Delete",
hideCancel: false,
},
info: {
confirmText: "OK",
hideCancel: true,
},
}

export interface ConfirmDialogProps extends Omit<DialogActionButtonsProps, "color" | "confirmDialog" | "onCancel"> {
readonly description?: React.ReactNode
/**
* hideCancel hides the cancel button when set true, and shows the cancel
* button when set to false. When undefined:
* - cancel is not displayed for "info" dialogs
* - cancel is displayed for "delete" dialogs
*/
readonly hideCancel?: boolean
/**
* onClose is called when canceling (if cancel is showing).
*
* Additionally, if onConfirm is not defined onClose will be used in its place
* when confirming.
*/
readonly onClose: () => void
readonly open: boolean
readonly title: string
}

interface StyleProps {
type: ConfirmDialogType
}

const useStyles = makeStyles((theme) => ({
dialogWrapper: (props: StyleProps) => ({
"& .MuiPaper-root": {
background:
props.type === "info"
? theme.palette.confirmDialog.info.background
: theme.palette.confirmDialog.error.background,
},
}),
dialogContent: (props: StyleProps) => ({
color: props.type === "info" ? theme.palette.confirmDialog.info.text : theme.palette.confirmDialog.error.text,
padding: theme.spacing(6),
textAlign: "center",
}),
titleText: {
marginBottom: theme.spacing(3),
},
description: (props: StyleProps) => ({
color:
props.type === "info"
? fade(theme.palette.confirmDialog.info.text, 0.75)
: fade(theme.palette.confirmDialog.error.text, 0.75),
lineHeight: "160%",

"& strong": {
color:
props.type === "info"
? fade(theme.palette.confirmDialog.info.text, 0.95)
: fade(theme.palette.confirmDialog.error.text, 0.95),
},
}),
}))

/**
* Quick-use version of the Dialog component with slightly alternative styles,
* great to use for dialogs that don't have any interaction beyond yes / no.
*/
export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
cancelText,
confirmLoading,
confirmText,
description,
hideCancel,
onClose,
onConfirm,
open = false,
title,
type = "info",
}) => {
const styles = useStyles({ type })

const defaults = CONFIRM_DIALOG_DEFAULTS[type]

if (typeof hideCancel === "undefined") {
hideCancel = defaults.hideCancel
}

return (
<Dialog className={styles.dialogWrapper} maxWidth="sm" onClose={onClose} open={open}>
<div className={styles.dialogContent}>
<Typography className={styles.titleText} variant="h3">
{title}
</Typography>

{description && (
<Typography className={styles.description} variant="body2">
{description}
</Typography>
)}
</div>

<DialogActions>
<DialogActionButtons
cancelText={cancelText}
confirmDialog
confirmLoading={confirmLoading}
confirmText={confirmText || defaults.confirmText}
onCancel={!hideCancel ? onClose : undefined}
onConfirm={onConfirm || onClose}
type={type}
/>
</DialogActions>
</Dialog>
)
}
Loading