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
|
||||
}
|
||||
}
|
||||
confirmDialog: {
|
||||
error: {
|
||||
background: string
|
||||
text: string
|
||||
}
|
||||
info: {
|
||||
background: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
navbar: {
|
||||
main: string
|
||||
}
|
||||
@ -40,6 +50,16 @@ declare module "@material-ui/core/styles/createPalette" {
|
||||
contrastText: string
|
||||
}
|
||||
}
|
||||
confirmDialog: {
|
||||
error: {
|
||||
background: string
|
||||
text: string
|
||||
}
|
||||
info: {
|
||||
background: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
navbar: {
|
||||
main: string
|
||||
}
|
||||
@ -58,6 +78,7 @@ export type CustomPalette = Pick<
|
||||
| "action"
|
||||
| "background"
|
||||
| "codeBlock"
|
||||
| "confirmDialog"
|
||||
| "divider"
|
||||
| "error"
|
||||
| "hero"
|
||||
@ -90,6 +111,16 @@ export const lightPalette: CustomPalette = {
|
||||
contrastText: "#000",
|
||||
},
|
||||
},
|
||||
confirmDialog: {
|
||||
error: {
|
||||
background: "#912F42",
|
||||
text: "#FFF",
|
||||
},
|
||||
info: {
|
||||
background: "#000",
|
||||
text: "#FFF",
|
||||
},
|
||||
},
|
||||
primary: {
|
||||
main: "#519A54",
|
||||
light: "#A2E0A5",
|
||||
@ -159,6 +190,13 @@ export const darkPalette: CustomPalette = {
|
||||
contrastText: "#FFF",
|
||||
},
|
||||
},
|
||||
confirmDialog: {
|
||||
error: lightPalette.confirmDialog.error,
|
||||
info: {
|
||||
background: "rgba(255, 255, 255, 0.95)",
|
||||
text: "rgb(31, 33, 35)",
|
||||
},
|
||||
},
|
||||
hero: {
|
||||
main: "#141414",
|
||||
button: "#333333",
|
||||
|
Reference in New Issue
Block a user