mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
chore: Port ConfirmDialog from v1 (#1228)
This commit is contained in:
39
site/src/components/ConfirmDialog/ConfirmDialog.stories.tsx
Normal file
39
site/src/components/ConfirmDialog/ConfirmDialog.stories.tsx
Normal file
@ -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
site/src/components/ConfirmDialog/ConfirmDialog.test.tsx
Normal file
152
site/src/components/ConfirmDialog/ConfirmDialog.test.tsx
Normal file
@ -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
site/src/components/ConfirmDialog/ConfirmDialog.tsx
Normal file
132
site/src/components/ConfirmDialog/ConfirmDialog.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
307
site/src/components/Dialog/Dialog.tsx
Normal file
307
site/src/components/Dialog/Dialog.tsx
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
import MuiDialog, { DialogProps as MuiDialogProps } from "@material-ui/core/Dialog"
|
||||||
|
import MuiDialogTitle from "@material-ui/core/DialogTitle"
|
||||||
|
import InputAdornment from "@material-ui/core/InputAdornment"
|
||||||
|
import OutlinedInput, { OutlinedInputProps } from "@material-ui/core/OutlinedInput"
|
||||||
|
import { darken, fade, makeStyles } from "@material-ui/core/styles"
|
||||||
|
import SvgIcon from "@material-ui/core/SvgIcon"
|
||||||
|
import * as React from "react"
|
||||||
|
import { combineClasses } from "../../util/combineClasses"
|
||||||
|
import { SearchIcon } from "../Icons/SearchIcon"
|
||||||
|
import { LoadingButton, LoadingButtonProps } from "../LoadingButton/LoadingButton"
|
||||||
|
import { ConfirmDialogType } from "./types"
|
||||||
|
|
||||||
|
export interface DialogTitleProps {
|
||||||
|
/** Title for display */
|
||||||
|
title: React.ReactNode
|
||||||
|
/** Optional icon to display faded to the right of the title */
|
||||||
|
icon?: typeof SvgIcon
|
||||||
|
/** Smaller text to display above the title */
|
||||||
|
superTitle?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override of Material UI's DialogTitle that allows for a supertitle and background icon
|
||||||
|
*/
|
||||||
|
export const DialogTitle: React.FC<DialogTitleProps> = ({ title, icon: Icon, superTitle }) => {
|
||||||
|
const styles = useTitleStyles()
|
||||||
|
return (
|
||||||
|
<MuiDialogTitle disableTypography>
|
||||||
|
<div className={styles.titleWrapper}>
|
||||||
|
{superTitle && <div className={styles.superTitle}>{superTitle}</div>}
|
||||||
|
<div className={styles.title}>{title}</div>
|
||||||
|
</div>
|
||||||
|
{Icon && <Icon className={styles.icon} />}
|
||||||
|
</MuiDialogTitle>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useTitleStyles = makeStyles(
|
||||||
|
(theme) => ({
|
||||||
|
title: {
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 2,
|
||||||
|
fontSize: theme.typography.h3.fontSize,
|
||||||
|
fontWeight: theme.typography.h3.fontWeight,
|
||||||
|
lineHeight: "40px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
superTitle: {
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 2,
|
||||||
|
fontSize: theme.typography.body2.fontSize,
|
||||||
|
fontWeight: 500,
|
||||||
|
letterSpacing: 1.5,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
},
|
||||||
|
titleWrapper: {
|
||||||
|
padding: `${theme.spacing(2)}px 0`,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
height: 84,
|
||||||
|
width: 84,
|
||||||
|
color: fade(theme.palette.action.disabled, 0.4),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "CdrDialogTitle" },
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface DialogActionButtonsProps {
|
||||||
|
/** Text to display in the cancel button */
|
||||||
|
cancelText?: string
|
||||||
|
/** Text to display in the confirm button */
|
||||||
|
confirmText?: React.ReactNode
|
||||||
|
/** Whether or not confirm is loading, also disables cancel when true */
|
||||||
|
confirmLoading?: boolean
|
||||||
|
/** Whether or not this is a confirm dialog */
|
||||||
|
confirmDialog?: boolean
|
||||||
|
/** Called when cancel is clicked */
|
||||||
|
onCancel?: () => void
|
||||||
|
/** Called when confirm is clicked */
|
||||||
|
onConfirm?: () => void
|
||||||
|
type?: ConfirmDialogType
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeToColor = (type: ConfirmDialogType): LoadingButtonProps["color"] => {
|
||||||
|
if (type === "delete") {
|
||||||
|
return "secondary"
|
||||||
|
}
|
||||||
|
return "primary"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quickly handels most modals actions, some combination of a cancel and confirm button
|
||||||
|
*/
|
||||||
|
export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
|
||||||
|
cancelText = "Cancel",
|
||||||
|
confirmText = "Confirm",
|
||||||
|
confirmLoading = false,
|
||||||
|
confirmDialog,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
type = "info",
|
||||||
|
}) => {
|
||||||
|
const styles = useButtonStyles({ type })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{onCancel && (
|
||||||
|
<LoadingButton
|
||||||
|
className={combineClasses({
|
||||||
|
[styles.dialogButton]: true,
|
||||||
|
[styles.cancelButton]: true,
|
||||||
|
[styles.confirmDialogCancelButton]: confirmDialog,
|
||||||
|
})}
|
||||||
|
disabled={confirmLoading}
|
||||||
|
onClick={onCancel}
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{cancelText}
|
||||||
|
</LoadingButton>
|
||||||
|
)}
|
||||||
|
{onConfirm && (
|
||||||
|
<LoadingButton
|
||||||
|
variant="contained"
|
||||||
|
onClick={onConfirm}
|
||||||
|
color={typeToColor(type)}
|
||||||
|
loading={confirmLoading}
|
||||||
|
type="submit"
|
||||||
|
className={combineClasses([
|
||||||
|
styles.dialogButton,
|
||||||
|
styles.submitButton,
|
||||||
|
type === "delete" ? styles.errorButton : "",
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
{confirmText}
|
||||||
|
</LoadingButton>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StyleProps {
|
||||||
|
type: ConfirmDialogType
|
||||||
|
}
|
||||||
|
|
||||||
|
const useButtonStyles = makeStyles((theme) => ({
|
||||||
|
dialogButton: {
|
||||||
|
borderRadius: 0,
|
||||||
|
fontSize: theme.typography.h6.fontSize,
|
||||||
|
fontWeight: theme.typography.h5.fontWeight,
|
||||||
|
padding: theme.spacing(2.25),
|
||||||
|
width: "100%",
|
||||||
|
boxShadow: "none",
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
background: fade(theme.palette.primary.main, 0.1),
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
|
||||||
|
"&:hover": {
|
||||||
|
background: fade(theme.palette.primary.main, 0.3),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
confirmDialogCancelButton: (props: StyleProps) => {
|
||||||
|
const color = props.type === "info" ? theme.palette.confirmDialog.info.text : theme.palette.confirmDialog.error.text
|
||||||
|
return {
|
||||||
|
background: fade(color, 0.15),
|
||||||
|
color,
|
||||||
|
|
||||||
|
"&:hover": {
|
||||||
|
background: fade(color, 0.3),
|
||||||
|
},
|
||||||
|
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
background: fade(color, 0.15),
|
||||||
|
color: fade(color, 0.5),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitButton: {
|
||||||
|
// Override disabled to keep background color, change loading spinner to contrast color
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
"&.MuiButton-containedPrimary": {
|
||||||
|
background: theme.palette.primary.dark,
|
||||||
|
|
||||||
|
"& .MuiCircularProgress-root": {
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"&.CdrButton-error.MuiButton-contained": {
|
||||||
|
background: darken(theme.palette.error.main, 0.3),
|
||||||
|
|
||||||
|
"& .MuiCircularProgress-root": {
|
||||||
|
color: theme.palette.error.contrastText,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errorButton: {
|
||||||
|
"&.MuiButton-contained": {
|
||||||
|
backgroundColor: theme.palette.error.main,
|
||||||
|
color: theme.palette.error.contrastText,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: darken(theme.palette.error.main, 0.3),
|
||||||
|
"@media (hover: none)": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
backgroundColor: theme.palette.action.disabledBackground,
|
||||||
|
color: fade(theme.palette.text.disabled, 0.5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"&.MuiButton-outlined": {
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
borderColor: theme.palette.error.main,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: fade(theme.palette.error.main, theme.palette.action.hoverOpacity),
|
||||||
|
"@media (hover: none)": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
color: fade(theme.palette.text.disabled, 0.5),
|
||||||
|
borderColor: theme.palette.action.disabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"&.MuiButton-text": {
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: fade(theme.palette.error.main, theme.palette.action.hoverOpacity),
|
||||||
|
"@media (hover: none)": {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
color: fade(theme.palette.text.disabled, 0.5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
export type DialogSearchProps = Omit<OutlinedInputProps, "className" | "fullWidth" | "labelWidth" | "startAdornment">
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a search bar right below the title of a Dialog. Passes all props
|
||||||
|
* through to the Material UI OutlinedInput component contained within.
|
||||||
|
*/
|
||||||
|
export const DialogSearch: React.FC<DialogSearchProps> = (props) => {
|
||||||
|
const styles = useSearchStyles()
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<OutlinedInput
|
||||||
|
{...props}
|
||||||
|
fullWidth
|
||||||
|
labelWidth={0}
|
||||||
|
className={styles.input}
|
||||||
|
startAdornment={
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon className={styles.icon} />
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSearchStyles = makeStyles(
|
||||||
|
(theme) => ({
|
||||||
|
root: {
|
||||||
|
position: "relative",
|
||||||
|
padding: `${theme.spacing(2)}px ${theme.spacing(4)}px`,
|
||||||
|
boxShadow: `0 2px 6px ${fade("#1D407E", 0.2)}`,
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "CdrDialogSearch" },
|
||||||
|
)
|
||||||
|
|
||||||
|
export type DialogProps = MuiDialogProps
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around Material UI's Dialog component. Conveniently exports all of
|
||||||
|
* Dialog's components in one import, so for example `<DialogContent />` becomes
|
||||||
|
* `<Dialog.Content />` etc. Also contains some custom Dialog components listed below.
|
||||||
|
*
|
||||||
|
* See original component's Material UI documentation here: https://material-ui.com/components/dialogs/
|
||||||
|
*/
|
||||||
|
export const Dialog: React.FC<DialogProps> = (props) => {
|
||||||
|
// Wrapped so we can add custom attributes below
|
||||||
|
return <MuiDialog {...props} />
|
||||||
|
}
|
1
site/src/components/Dialog/types.ts
Normal file
1
site/src/components/Dialog/types.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type ConfirmDialogType = "delete" | "info"
|
8
site/src/components/Icons/SearchIcon.tsx
Normal file
8
site/src/components/Icons/SearchIcon.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import SvgIcon from "@material-ui/core/SvgIcon"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export const SearchIcon: typeof SvgIcon = (props) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 16 16">
|
||||||
|
<path d="M15.707 13.293L13 10.586C13.63 9.536 14 8.311 14 7C14 3.14 10.859 0 7 0C3.141 0 0 3.14 0 7C0 10.86 3.141 14 7 14C8.312 14 9.536 13.631 10.586 13L13.293 15.707C13.488 15.902 13.744 16 14 16C14.256 16 14.512 15.902 14.707 15.707L15.707 14.707C16.098 14.316 16.098 13.684 15.707 13.293ZM7 12C4.239 12 2 9.761 2 7C2 4.239 4.239 2 7 2C9.761 2 12 4.239 12 7C12 9.761 9.761 12 7 12Z" />
|
||||||
|
</SvgIcon>
|
||||||
|
)
|
@ -18,6 +18,16 @@ declare module "@material-ui/core/styles/createPalette" {
|
|||||||
contrastText: string
|
contrastText: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
confirmDialog: {
|
||||||
|
error: {
|
||||||
|
background: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
info: {
|
||||||
|
background: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
}
|
||||||
navbar: {
|
navbar: {
|
||||||
main: string
|
main: string
|
||||||
}
|
}
|
||||||
@ -40,6 +50,16 @@ declare module "@material-ui/core/styles/createPalette" {
|
|||||||
contrastText: string
|
contrastText: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
confirmDialog: {
|
||||||
|
error: {
|
||||||
|
background: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
info: {
|
||||||
|
background: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
}
|
||||||
navbar: {
|
navbar: {
|
||||||
main: string
|
main: string
|
||||||
}
|
}
|
||||||
@ -58,6 +78,7 @@ export type CustomPalette = Pick<
|
|||||||
| "action"
|
| "action"
|
||||||
| "background"
|
| "background"
|
||||||
| "codeBlock"
|
| "codeBlock"
|
||||||
|
| "confirmDialog"
|
||||||
| "divider"
|
| "divider"
|
||||||
| "error"
|
| "error"
|
||||||
| "hero"
|
| "hero"
|
||||||
@ -90,6 +111,16 @@ export const lightPalette: CustomPalette = {
|
|||||||
contrastText: "#000",
|
contrastText: "#000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
confirmDialog: {
|
||||||
|
error: {
|
||||||
|
background: "#912F42",
|
||||||
|
text: "#FFF",
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
background: "#000",
|
||||||
|
text: "#FFF",
|
||||||
|
},
|
||||||
|
},
|
||||||
primary: {
|
primary: {
|
||||||
main: "#519A54",
|
main: "#519A54",
|
||||||
light: "#A2E0A5",
|
light: "#A2E0A5",
|
||||||
@ -159,6 +190,13 @@ export const darkPalette: CustomPalette = {
|
|||||||
contrastText: "#FFF",
|
contrastText: "#FFF",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
confirmDialog: {
|
||||||
|
error: lightPalette.confirmDialog.error,
|
||||||
|
info: {
|
||||||
|
background: "rgba(255, 255, 255, 0.95)",
|
||||||
|
text: "rgb(31, 33, 35)",
|
||||||
|
},
|
||||||
|
},
|
||||||
hero: {
|
hero: {
|
||||||
main: "#141414",
|
main: "#141414",
|
||||||
button: "#333333",
|
button: "#333333",
|
||||||
|
Reference in New Issue
Block a user