diff --git a/assets/images/error-icon.svg b/assets/images/error-icon.svg new file mode 100644 index 000000000..0918ec675 --- /dev/null +++ b/assets/images/error-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/info-icon.svg b/assets/images/info-icon.svg new file mode 100644 index 000000000..425fc218d --- /dev/null +++ b/assets/images/info-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/success-icon.svg b/assets/images/success-icon.svg new file mode 100644 index 000000000..7f7965403 --- /dev/null +++ b/assets/images/success-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/warning-icon.svg b/assets/images/warning-icon.svg new file mode 100644 index 000000000..f073d2756 --- /dev/null +++ b/assets/images/warning-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index 27b4b0210..241117f2b 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -3,115 +3,170 @@ import IconButton from "@material-ui/core/IconButton"; import Snackbar from "@material-ui/core/Snackbar"; import Typography from "@material-ui/core/Typography"; import CloseIcon from "@material-ui/icons/Close"; -import React from "react"; +import classNames from "classnames"; +import React, { useState } from "react"; +import { FormattedMessage } from "react-intl"; import { IMessage, MessageContext } from "./"; +import { useStyles } from "./styles"; interface Message extends IMessage { key: string; } -interface MessageManagerState { - message: Message; - opened: boolean; -} -export class MessageManager extends React.Component<{}, MessageManagerState> { - state: MessageManagerState = { - message: { key: "0", onUndo: undefined, text: "" }, - opened: false - }; - queue = []; +export const MessageManager = props => { + const { children } = props; - handleClose = (_, reason) => { + const [message, setMessage] = useState({ + key: "0", + onUndo: undefined, + status: "info", + text: "" + }); + const [opened, setOpened] = useState(false); + + const classes = useStyles({}); + const { + action, + autohide = 3000, + title, + text, + key, + onUndo, + status = "info" + } = message; + + const queue = []; + + const handleClose = (_, reason) => { if (reason === "clickaway") { return; } - this.setState({ opened: false }); + setOpened(false); }; - handleExited = () => { - this.processQueue(); + const processQueue = () => { + if (queue.length > 0) { + setMessage(queue.shift()); + setOpened(true); + } }; - pushMessage = (message: IMessage) => { - this.queue.push({ + const handleExited = () => { + processQueue(); + }; + + const pushMessage = (message: IMessage) => { + queue.push({ key: new Date().getTime(), ...message }); - if (this.state.opened) { - this.setState({ opened: false }); + if (opened) { + setOpened(false); } else { - this.processQueue(); + processQueue(); } }; - processQueue = () => { - if (this.queue.length > 0) { - this.setState({ - message: this.queue.shift(), - opened: true - }); - } - }; - - render() { - const { autohide = 3000, title, text, key, onUndo } = this.state.message; - return ( - <> - - {title && ( - - {title} - - )} - {text} - - } - title={title} - action={[ - !!onUndo ? ( - - ) : ( - undefined - ), - + + {title && ( + + {title} + + )} + - - - ]} - /> - - {this.props.children} - - - ); - } -} + {text} + + + } + title={title} + action={[ + !!onUndo ? ( + + ) : ( + undefined + ), + !!action ? ( + + ) : ( + undefined + ), + + + , +
+
+
+ ]} + /> + + {children} + + + ); +}; + export default MessageManager; diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index 95424f09f..1ea935669 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -1,10 +1,12 @@ import { createContext } from "react"; export interface IMessage { + action?: () => void; autohide?: number; title?: string; text: string; onUndo?: () => void; + status?: "success" | "error" | "info" | "warning"; } export type IMessageContext = (message: IMessage) => void; export const MessageContext = createContext(undefined); diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts new file mode 100644 index 000000000..5743651cf --- /dev/null +++ b/src/components/messages/styles.ts @@ -0,0 +1,114 @@ +import errorIcon from "@assets/images/error-icon.svg"; +import infoIcon from "@assets/images/info-icon.svg"; +import successIcon from "@assets/images/success-icon.svg"; +import warningIcon from "@assets/images/warning-icon.svg"; +import { makeStyles } from "@material-ui/core/styles"; +import { darken } from "@material-ui/core/styles/colorManipulator"; + +const successColor = "#60DAA0"; +const warningColor = "#FFB84E"; +const infoColor = "#CAD8DF"; + +export const useStyles = makeStyles( + theme => ({ + "@keyframes bar": { + from: { transform: "translateX(-100%)" }, + to: { transform: "translateX(0)" } + }, + closeBtn: { + position: "absolute", + right: 0, + top: 0 + }, + error: { + "& > div": { + "& button span": { + color: "#fff" + }, + "&:before": { + backgroundImage: `url(${errorIcon})` + }, + + backgroundColor: theme.palette.error.main, + color: "#fff" + } + }, + progressBar: { + backgroundColor: infoColor, + height: 8, + transform: "translateX(-100%)", + width: "100%" + }, + progressBarActive: { + animation: `$bar var(--animationTime) ease both` + }, + progressBarContainer: { + borderRadius: "0 0 4px 4px", + bottom: 0, + left: 0, + overflow: "hidden", + position: "absolute", + width: "calc(100%)" + }, + progressBarError: { + backgroundColor: darken(theme.palette.error.main, 0.2) + }, + progressBarSuccess: { + backgroundColor: darken(successColor, 0.2) + }, + progressBarWarning: { + backgroundColor: darken(warningColor, 0.2) + }, + snackbar: { + "& > div": { + "&:before": { + backgroundImage: `url(${infoIcon})`, + backgroundRepeat: "no-repeat", + backgroundSize: "contain", + content: "''", + display: "block", + height: 32, + left: 15, + position: "absolute", + top: 13, + width: 32 + }, + paddingLeft: 60, + position: "relative" + } + }, + success: { + "& > div": { + "& button span": { + color: "#fff" + }, + "&:before": { + backgroundImage: `url(${successIcon})` + }, + + backgroundColor: successColor, + color: "#fff" + } + }, + text: { + color: "#fff", + paddingTop: 5 + }, + textInfo: { + paddingTop: 5 + }, + warning: { + "& > div": { + "& button span": { + color: "#fff" + }, + "&:before": { + backgroundImage: `url(${warningIcon})` + }, + backgroundColor: warningColor, + color: "#fff" + } + } + }), + { name: "MessageManager" } +); diff --git a/src/theme.ts b/src/theme.ts index 42a02f7b1..e02b3aba4 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -343,7 +343,9 @@ export default (colors: IThemeColors): Theme => color: colors.font.default } }, - alignSelf: "baseline" + display: "block", + paddingBottom: 15, + paddingLeft: 0 }, message: { fontSize: 16 @@ -353,8 +355,7 @@ export default (colors: IThemeColors): Theme => boxShadow: "0 6px 10px 0px rgba(0, 0, 0, 0.15), 0 1px 18px 0px rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.10)", color: colors.font.default, - display: "grid", - gridTemplateColumns: "1fr 56px", + display: "block", maxWidth: 480 } },