From b3eb13a78580da430c3311de493fac76adcaed26 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Tue, 23 Jun 2020 13:47:30 +0200 Subject: [PATCH 01/12] update MessageManager --- assets/images/error-icon.svg | 5 + assets/images/info-icon.svg | 3 + assets/images/success-icon.svg | 4 + assets/images/warning-icon.svg | 5 + src/components/messages/MessageManager.tsx | 229 +++++++++++++-------- src/components/messages/index.ts | 2 + src/components/messages/styles.ts | 114 ++++++++++ src/theme.ts | 7 +- 8 files changed, 279 insertions(+), 90 deletions(-) create mode 100644 assets/images/error-icon.svg create mode 100644 assets/images/info-icon.svg create mode 100644 assets/images/success-icon.svg create mode 100644 assets/images/warning-icon.svg create mode 100644 src/components/messages/styles.ts 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 } }, From 49df188703b9437638307c9bae9070a074f1f032 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Tue, 23 Jun 2020 18:02:59 +0200 Subject: [PATCH 02/12] add expandedText prop to MessageManager --- src/components/messages/MessageManager.tsx | 102 ++++++++++++++------- src/components/messages/index.ts | 6 +- src/components/messages/styles.ts | 65 ++++++++++++- src/theme.ts | 5 +- 4 files changed, 139 insertions(+), 39 deletions(-) diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index 241117f2b..a09ccf52b 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -24,11 +24,13 @@ export const MessageManager = props => { text: "" }); const [opened, setOpened] = useState(false); + const [expand, setExpand] = useState(false); const classes = useStyles({}); const { - action, - autohide = 3000, + actionBtn, + autohide = 9000, + expandText, title, text, key, @@ -105,47 +107,79 @@ export const MessageManager = props => { } title={title} action={[ - !!onUndo ? ( - - ) : ( - undefined - ), - !!action ? ( - +
+

{expandText}

+
+ +
) : ( undefined ), +
+ {!!onUndo && ( + + )} + {!!actionBtn && ( + + )} +
, , diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index 1ea935669..ebb434f6d 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -1,8 +1,12 @@ import { createContext } from "react"; export interface IMessage { - action?: () => void; + actionBtn?: { + label: string; + action: () => void; + }; autohide?: number; + expandText?: string; title?: string; text: string; onUndo?: () => void; diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index 5743651cf..e86c2ec10 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -15,10 +15,22 @@ export const useStyles = makeStyles( from: { transform: "translateX(-100%)" }, to: { transform: "translateX(0)" } }, + actionContainer: { + marginLeft: -8 + }, closeBtn: { + "& svg": { + maxHeight: 18, + maxWidth: 18 + }, + color: "#fff", + padding: 10, position: "absolute", right: 0, - top: 0 + top: 7 + }, + closeBtnInfo: { + color: theme.palette.text.primary }, error: { "& > div": { @@ -28,11 +40,60 @@ export const useStyles = makeStyles( "&:before": { backgroundImage: `url(${errorIcon})` }, - backgroundColor: theme.palette.error.main, color: "#fff" } }, + expandBtn: { + "&:before": { + borderLeft: "4px solid transparent", + borderRight: "4px solid transparent", + borderTop: "8px solid #fff", + content: "''", + display: "block", + height: 0, + position: "absolute", + right: 0, + top: "50%", + transform: "translateY(-50%)", + width: 0 + }, + background: "transparent", + border: "none", + color: "#fff", + cursor: "pointer", + fontSize: theme.spacing(2), + outline: "none", + padding: 0, + paddingRight: 15, + position: "relative" + }, + expandBtnInfo: { + "&:before": { + borderTop: `8px solid ${theme.palette.text.primary}` + }, + color: theme.palette.text.primary + }, + expandedContainer: { + "& p": { + margin: theme.spacing(1, 0) + }, + color: "#fff", + marginBottom: 5 + }, + expandedContainerContent: { + overflow: "hidden", + transition: "max-height .6s ease" + }, + expandedContainerInfo: { + color: theme.palette.text.secondary + }, + expandedText: { + maxHeight: 500 + }, + hiddenText: { + maxHeight: 0 + }, progressBar: { backgroundColor: infoColor, height: 8, diff --git a/src/theme.ts b/src/theme.ts index e02b3aba4..bddcd3d94 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -344,8 +344,9 @@ export default (colors: IThemeColors): Theme => } }, display: "block", - paddingBottom: 15, - paddingLeft: 0 + paddingBottom: 10, + paddingLeft: 0, + paddingRight: 45 }, message: { fontSize: 16 From 58c0a5859a45ee0ca7ad8869fbfa179bf9d5d9da Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Wed, 24 Jun 2020 13:44:35 +0200 Subject: [PATCH 03/12] use react-alert to render multiple notifications --- package-lock.json | 58 ++++++------ package.json | 2 + src/components/messages/MessageManager.tsx | 101 +++++---------------- src/components/messages/styles.ts | 57 +++++++----- src/hooks/useNotifier.ts | 11 ++- src/index.tsx | 86 ++++++++++-------- src/storybook/Decorator.tsx | 12 ++- src/theme.ts | 3 +- 8 files changed, 155 insertions(+), 175 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46769d8b4..0db110917 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3587,6 +3587,14 @@ "csstype": "^2.2.0" } }, + "@types/react-alert": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@types/react-alert/-/react-alert-5.2.0.tgz", + "integrity": "sha512-1cyu/vQ4X0CJVC3X4CrJHRBc/9BvTqOF9FH6iy/4TLwPng++GC4ftxS/UaM91FqsKjao7pPtqxUlW1cRafNHLw==", + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "16.9.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", @@ -11582,8 +11590,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -11601,13 +11608,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11620,18 +11625,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -11734,8 +11736,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -11745,7 +11746,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -11758,20 +11758,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -11788,7 +11785,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -11861,8 +11857,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -11872,7 +11867,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -11948,8 +11942,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -11979,7 +11972,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -11997,7 +11989,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -12036,13 +12027,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -17687,6 +17676,15 @@ "prop-types": "^15.6.2" } }, + "react-alert": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-alert/-/react-alert-7.0.1.tgz", + "integrity": "sha512-pHtMtP8gFQ4hLVQ/j7HIaayjvVNoMgoZHCag8YYBUpsLcamj0+435PJdZR7oh7AVbW0C2JPvNmS5+Md1Y59oew==", + "requires": { + "prop-types": "^15.7.2", + "react-transition-group": "^4.3.0" + } + }, "react-apollo": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-3.1.5.tgz", diff --git a/package.json b/package.json index 9c25281c6..7bb7e173a 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "moment-timezone": "^0.5.26", "qs": "^6.9.0", "react": "^16.12.0", + "react-alert": "^7.0.1", "react-apollo": "^3.1.4", "react-dom": "^16.9.0", "react-dropzone": "^8.2.0", @@ -100,6 +101,7 @@ "@types/react-sortable-hoc": "^0.7.1", "@types/react-sortable-tree": "^0.3.6", "@types/react-test-renderer": "^16.8.2", + "@types/react-alert": "^5.2.0", "@types/semver-compare": "^1.0.1", "@types/storybook__addon-storyshots": "^3.4.9", "@types/storybook__react": "^4.0.2", diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index a09ccf52b..cfe811814 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -1,91 +1,37 @@ import Button from "@material-ui/core/Button"; import IconButton from "@material-ui/core/IconButton"; -import Snackbar from "@material-ui/core/Snackbar"; +import SnackbarContent from "@material-ui/core/SnackbarContent"; import Typography from "@material-ui/core/Typography"; import CloseIcon from "@material-ui/icons/Close"; import classNames from "classnames"; import React, { useState } from "react"; +import { AlertComponentPropsWithStyle } from "react-alert"; import { FormattedMessage } from "react-intl"; -import { IMessage, MessageContext } from "./"; +import { IMessage } from "./"; import { useStyles } from "./styles"; -interface Message extends IMessage { - key: string; +export interface IMessageManagerProps extends AlertComponentPropsWithStyle { + message: IMessage; } -export const MessageManager = props => { - const { children } = props; +export const MessageManager: React.FC = props => { + const { + close, + options: { timeout }, + message: { actionBtn, expandText, status, title, text, onUndo } + } = props; - const [message, setMessage] = useState({ - key: "0", - onUndo: undefined, - status: "info", - text: "" - }); - const [opened, setOpened] = useState(false); const [expand, setExpand] = useState(false); const classes = useStyles({}); - const { - actionBtn, - autohide = 9000, - expandText, - title, - text, - key, - onUndo, - status = "info" - } = message; - - const queue = []; - - const handleClose = (_, reason) => { - if (reason === "clickaway") { - return; - } - setOpened(false); - }; - - const processQueue = () => { - if (queue.length > 0) { - setMessage(queue.shift()); - setOpened(true); - } - }; - - const handleExited = () => { - processQueue(); - }; - - const pushMessage = (message: IMessage) => { - queue.push({ - key: new Date().getTime(), - ...message - }); - - if (opened) { - setOpened(false); - } else { - processQueue(); - } - }; return ( - <> - + { } - title={title} action={[ !!expandText ? (
{ key="undo" color="default" size="small" - onClick={handleClose as any} + onClick={close} data-tc="button-undo" > { key="close" aria-label="Close" color="inherit" - onClick={handleClose as any} + onClick={close} className={classNames(classes.closeBtn, { [classes.closeBtnInfo]: status === "info" })} @@ -186,20 +132,17 @@ export const MessageManager = props => {
]} /> - - {children} - - +
); }; diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index e86c2ec10..04301a84f 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -36,13 +36,15 @@ export const useStyles = makeStyles( "& > div": { "& button span": { color: "#fff" - }, + } + }, + "& > div:first-child": { "&:before": { backgroundImage: `url(${errorIcon})` - }, - backgroundColor: theme.palette.error.main, - color: "#fff" - } + } + }, + backgroundColor: theme.palette.error.main, + color: "#fff" }, expandBtn: { "&:before": { @@ -122,6 +124,9 @@ export const useStyles = makeStyles( }, snackbar: { "& > div": { + paddingLeft: 60 + }, + "& > div:first-child": { "&:before": { backgroundImage: `url(${infoIcon})`, backgroundRepeat: "no-repeat", @@ -134,22 +139,30 @@ export const useStyles = makeStyles( top: 13, width: 32 }, - paddingLeft: 60, + paddingTop: 10, position: "relative" - } + }, + borderRadius: 4 + }, + snackbarContainer: { + borderRadius: 4, + display: "block", + margin: theme.spacing(2, 2, 0, 0), + maxWidth: 480, + pointerEvents: "all", + position: "relative" }, success: { - "& > div": { - "& button span": { - color: "#fff" - }, + "& > div:first-child": { "&:before": { backgroundImage: `url(${successIcon})` - }, - - backgroundColor: successColor, + } + }, + "& button span": { color: "#fff" - } + }, + backgroundColor: successColor, + color: "#fff" }, text: { color: "#fff", @@ -159,16 +172,16 @@ export const useStyles = makeStyles( paddingTop: 5 }, warning: { - "& > div": { - "& button span": { - color: "#fff" - }, + "& > div:first-child": { "&:before": { backgroundImage: `url(${warningIcon})` - }, - backgroundColor: warningColor, + } + }, + "& button span": { color: "#fff" - } + }, + backgroundColor: warningColor, + color: "#fff" } }), { name: "MessageManager" } diff --git a/src/hooks/useNotifier.ts b/src/hooks/useNotifier.ts index af927991f..d5e3ed55f 100644 --- a/src/hooks/useNotifier.ts +++ b/src/hooks/useNotifier.ts @@ -1,9 +1,12 @@ -import { IMessageContext, MessageContext } from "@saleor/components/messages"; -import { useContext } from "react"; +import { IMessage } from "@saleor/components/messages"; +import { useAlert } from "react-alert"; -export type UseNotifierResult = IMessageContext; +export type UseNotifierResult = (options: IMessage) => void; function useNotifier(): UseNotifierResult { - const notify = useContext(MessageContext); + const alert = useAlert(); + const notify = (options: IMessage) => { + alert.show(options, options.autohide && { timeout: options.autohide }); + }; return notify; } export default useNotifier; diff --git a/src/index.tsx b/src/index.tsx index 2c30b3571..3ca1ada8a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,65 +1,66 @@ -import Navigator from "@saleor/components/Navigator"; -import useAppState from "@saleor/hooks/useAppState"; -import { defaultDataIdFromObject, InMemoryCache } from "apollo-cache-inmemory"; +import { API_URI, APP_MOUNT_URI, GTM_ID } from "./config"; +import { Provider as AlertProvider, positions } from "react-alert"; +import Auth, { getAuthToken, removeAuthToken } from "./auth"; +import { BrowserRouter, Route, Switch } from "react-router-dom"; +import ConfigurationSection, { createConfigurationMenu } from "./configuration"; +import { ErrorResponse, onError } from "apollo-link-error"; +import { InMemoryCache, defaultDataIdFromObject } from "apollo-cache-inmemory"; + import { ApolloClient } from "apollo-client"; import { ApolloLink } from "apollo-link"; -import { BatchHttpLink } from "apollo-link-batch-http"; -import { setContext } from "apollo-link-context"; -import { ErrorResponse, onError } from "apollo-link-error"; -import { createUploadLink } from "apollo-upload-client"; -import React from "react"; import { ApolloProvider } from "react-apollo"; -import { render } from "react-dom"; -import ErrorBoundary from "react-error-boundary"; -import TagManager from "react-gtm-module"; -import { useIntl } from "react-intl"; -import { BrowserRouter, Route, Switch } from "react-router-dom"; - +import AppLayout from "./components/AppLayout"; +import AppStateProvider from "./containers/AppState"; import AttributeSection from "./attributes"; -import { attributeSection } from "./attributes/urls"; -import Auth, { getAuthToken, removeAuthToken } from "./auth"; import AuthProvider from "./auth/AuthProvider"; -import LoginLoading from "./auth/components/LoginLoading/LoginLoading"; -import SectionRoute from "./auth/components/SectionRoute"; -import { isJwtError } from "./auth/errors"; -import { hasPermission } from "./auth/misc"; +import BackgroundTasksProvider from "./containers/BackgroundTasks"; +import { BatchHttpLink } from "apollo-link-batch-http"; import CategorySection from "./categories"; import CollectionSection from "./collections"; -import AppLayout from "./components/AppLayout"; -import { DateProvider } from "./components/Date"; -import { LocaleProvider } from "./components/Locale"; -import { MessageManager } from "./components/messages"; -import { ShopProvider } from "./components/Shop"; -import ThemeProvider from "./components/Theme"; -import { WindowTitle } from "./components/WindowTitle"; -import { API_URI, APP_MOUNT_URI, GTM_ID } from "./config"; -import ConfigurationSection, { createConfigurationMenu } from "./configuration"; -import AppStateProvider from "./containers/AppState"; -import BackgroundTasksProvider from "./containers/BackgroundTasks"; import { CustomerSection } from "./customers"; +import { DateProvider } from "./components/Date"; import DiscountSection from "./discounts"; +import ErrorBoundary from "react-error-boundary"; import HomePage from "./home"; -import { commonMessages } from "./intl"; +import { LocaleProvider } from "./components/Locale"; +import LoginLoading from "./auth/components/LoginLoading/LoginLoading"; +import { MessageManager } from "./components/messages"; import NavigationSection from "./navigation"; -import { navigationSection } from "./navigation/urls"; +import Navigator from "@saleor/components/Navigator"; import { NotFound } from "./NotFound"; import OrdersSection from "./orders"; import PageSection from "./pages"; +import { PermissionEnum } from "./types/globalTypes"; import PermissionGroupSection from "./permissionGroups"; import PluginsSection from "./plugins"; import ProductSection from "./products"; import ProductTypesSection from "./productTypes"; +import React from "react"; +import SectionRoute from "./auth/components/SectionRoute"; import ServiceSection from "./services"; -import { serviceSection } from "./services/urls"; import ShippingSection from "./shipping"; +import { ShopProvider } from "./components/Shop"; import SiteSettingsSection from "./siteSettings"; import StaffSection from "./staff"; +import TagManager from "react-gtm-module"; import TaxesSection from "./taxes"; +import ThemeProvider from "./components/Theme"; import TranslationsSection from "./translations"; -import { PermissionEnum } from "./types/globalTypes"; import WarehouseSection from "./warehouses"; -import { warehouseSection } from "./warehouses/urls"; import WebhooksSection from "./webhooks"; +import { WindowTitle } from "./components/WindowTitle"; +import { attributeSection } from "./attributes/urls"; +import { commonMessages } from "./intl"; +import { createUploadLink } from "apollo-upload-client"; +import { hasPermission } from "./auth/misc"; +import { isJwtError } from "./auth/errors"; +import { navigationSection } from "./navigation/urls"; +import { render } from "react-dom"; +import { serviceSection } from "./services/urls"; +import { setContext } from "apollo-link-context"; +import useAppState from "@saleor/hooks/useAppState"; +import { useIntl } from "react-intl"; +import { warehouseSection } from "./warehouses/urls"; interface ResponseError extends ErrorResponse { networkError?: Error & { @@ -126,6 +127,15 @@ const apolloClient = new ApolloClient({ link: invalidTokenLink.concat(authLink.concat(link)) }); +const notificationOptions = { + containerStyle: { + zIndex: 1000 + }, + offset: "20px", + position: positions.TOP_RIGHT, + timeout: 3000 +}; + const App: React.FC = () => { const isDark = localStorage.getItem("theme") === "true"; @@ -135,7 +145,7 @@ const App: React.FC = () => { - + @@ -143,7 +153,7 @@ const App: React.FC = () => { - + diff --git a/src/storybook/Decorator.tsx b/src/storybook/Decorator.tsx index 4819962cb..fb3f019c2 100644 --- a/src/storybook/Decorator.tsx +++ b/src/storybook/Decorator.tsx @@ -7,6 +7,16 @@ import { MessageManager } from "../components/messages"; import ThemeProvider from "../components/Theme"; import { TimezoneProvider } from "../components/Timezone"; +const messageProps = { + close: () => undefined, + id: "id", + message: { + text: "Test" + }, + options: {}, + style: {} +}; + export const Decorator = storyFn => ( ( - +
"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: "block", - maxWidth: 480 + maxWidth: 480, + padding: 0 } }, MuiSwitch: { From 2921d14b75c1b9731f7d6ecf65da262e75571ee7 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Wed, 24 Jun 2020 16:17:56 +0200 Subject: [PATCH 04/12] update messages stories --- src/components/messages/MessageManager.tsx | 3 +- src/components/messages/index.ts | 15 +- src/components/messages/styles.ts | 15 +- src/index.tsx | 13 +- src/storybook/Decorator.tsx | 17 +-- .../__snapshots__/Stories.test.ts.snap | 140 ++++++++++++++++++ src/storybook/stories/components/messages.tsx | 63 +++++++- src/theme.ts | 3 +- 8 files changed, 225 insertions(+), 44 deletions(-) diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index cfe811814..309d2e10d 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -19,7 +19,7 @@ export const MessageManager: React.FC = props => { const { close, options: { timeout }, - message: { actionBtn, expandText, status, title, text, onUndo } + message: { actionBtn, expandText, status = "info", title, text, onUndo } } = props; const [expand, setExpand] = useState(false); @@ -33,6 +33,7 @@ export const MessageManager: React.FC = props => { key={props.id} aria-describedby="message-id" className={classNames(classes.snackbar, { + [classes.info]: status === "info", [classes.error]: status === "error", [classes.success]: status === "success", [classes.warning]: status === "warning" diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index ebb434f6d..c86fde986 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -1,5 +1,4 @@ -import { createContext } from "react"; - +import { positions } from "react-alert"; export interface IMessage { actionBtn?: { label: string; @@ -12,8 +11,14 @@ export interface IMessage { onUndo?: () => void; status?: "success" | "error" | "info" | "warning"; } -export type IMessageContext = (message: IMessage) => void; -export const MessageContext = createContext(undefined); + +export const notificationOptions = { + containerStyle: { + zIndex: 1000 + }, + offset: "20px", + position: positions.TOP_RIGHT, + timeout: 3000 +}; export * from "./MessageManager"; -export default MessageContext.Consumer; diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index 04301a84f..934309e85 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -26,7 +26,7 @@ export const useStyles = makeStyles( color: "#fff", padding: 10, position: "absolute", - right: 0, + right: 5, top: 7 }, closeBtnInfo: { @@ -96,6 +96,13 @@ export const useStyles = makeStyles( hiddenText: { maxHeight: 0 }, + info: { + "& > div:first-child": { + "&:before": { + backgroundImage: `url(${infoIcon})` + } + } + }, progressBar: { backgroundColor: infoColor, height: 8, @@ -128,7 +135,6 @@ export const useStyles = makeStyles( }, "& > div:first-child": { "&:before": { - backgroundImage: `url(${infoIcon})`, backgroundRepeat: "no-repeat", backgroundSize: "contain", content: "''", @@ -139,10 +145,11 @@ export const useStyles = makeStyles( top: 13, width: 32 }, - paddingTop: 10, + paddingTop: 16, position: "relative" }, - borderRadius: 4 + borderRadius: 4, + paddingBottom: 15 }, snackbarContainer: { borderRadius: 4, diff --git a/src/index.tsx b/src/index.tsx index 3ca1ada8a..211cfa7b7 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,11 +1,12 @@ import { API_URI, APP_MOUNT_URI, GTM_ID } from "./config"; -import { Provider as AlertProvider, positions } from "react-alert"; import Auth, { getAuthToken, removeAuthToken } from "./auth"; import { BrowserRouter, Route, Switch } from "react-router-dom"; import ConfigurationSection, { createConfigurationMenu } from "./configuration"; import { ErrorResponse, onError } from "apollo-link-error"; import { InMemoryCache, defaultDataIdFromObject } from "apollo-cache-inmemory"; +import { MessageManager, notificationOptions } from "./components/messages"; +import { Provider as AlertProvider } from "react-alert"; import { ApolloClient } from "apollo-client"; import { ApolloLink } from "apollo-link"; import { ApolloProvider } from "react-apollo"; @@ -24,7 +25,6 @@ import ErrorBoundary from "react-error-boundary"; import HomePage from "./home"; import { LocaleProvider } from "./components/Locale"; import LoginLoading from "./auth/components/LoginLoading/LoginLoading"; -import { MessageManager } from "./components/messages"; import NavigationSection from "./navigation"; import Navigator from "@saleor/components/Navigator"; import { NotFound } from "./NotFound"; @@ -127,15 +127,6 @@ const apolloClient = new ApolloClient({ link: invalidTokenLink.concat(authLink.concat(link)) }); -const notificationOptions = { - containerStyle: { - zIndex: 1000 - }, - offset: "20px", - position: positions.TOP_RIGHT, - timeout: 3000 -}; - const App: React.FC = () => { const isDark = localStorage.getItem("theme") === "true"; diff --git a/src/storybook/Decorator.tsx b/src/storybook/Decorator.tsx index fb3f019c2..9f8426de2 100644 --- a/src/storybook/Decorator.tsx +++ b/src/storybook/Decorator.tsx @@ -1,22 +1,13 @@ import { Locale, RawLocaleProvider } from "@saleor/components/Locale"; import React from "react"; +import { Provider as AlertProvider } from "react-alert"; import { IntlProvider } from "react-intl"; import { Provider as DateProvider } from "../components/Date/DateContext"; -import { MessageManager } from "../components/messages"; +import { MessageManager, notificationOptions } from "../components/messages"; import ThemeProvider from "../components/Theme"; import { TimezoneProvider } from "../components/Timezone"; -const messageProps = { - close: () => undefined, - id: "id", - message: { - text: "Test" - }, - options: {}, - style: {} -}; - export const Decorator = storyFn => ( ( - +
( > {storyFn()}
-
+
diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 3e65e29ed..3a3bb39b8 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -2612,6 +2612,118 @@ exports[`Storyshots Generics / Global messages default 1`] = `
`; +exports[`Storyshots Generics / Global messages with action 1`] = ` +
+
+
+ +
+
+
+`; + +exports[`Storyshots Generics / Global messages with error status 1`] = ` +
+
+
+ +
+
+
+`; + +exports[`Storyshots Generics / Global messages with expandText 1`] = ` +
+
+
+ +
+
+
+`; + +exports[`Storyshots Generics / Global messages with success status 1`] = ` +
+
+
+ +
+
+
+`; + exports[`Storyshots Generics / Global messages with undo action 1`] = `
`; +exports[`Storyshots Generics / Global messages with warning status 1`] = ` +
+
+
+ +
+
+
+`; + exports[`Storyshots Generics / Link with choices default 1`] = `
= ({ undo }) => { +const props = { + text: "This is message", + title: "Title" +}; +const Story: React.FC = ({ + actionBtn, + expandText, + onUndo, + status, + title, + text +}) => { const pushMessage = useNotifier(); return ( @@ -18,8 +27,12 @@ const Story: React.FC = ({ undo }) => { variant="contained" onClick={() => pushMessage({ - onUndo: undo ? () => undefined : undefined, - text: "This is message" + actionBtn, + expandText, + onUndo: onUndo ? () => undefined : undefined, + status, + text, + title }) } style={{ display: "block", margin: "auto" }} @@ -32,5 +45,39 @@ const Story: React.FC = ({ undo }) => { storiesOf("Generics / Global messages", module) .addDecorator(CardDecorator) .addDecorator(Decorator) - .add("default", () => ) - .add("with undo action", () => ); + .add("default", () => ) + .add("with undo action", () => undefined} {...props} />) + .add("with expandText", () => ( + + )) + .add("with action", () => ( + undefined, label: "Action" }} + {...props} + /> + )) + .add("with success status", () => ( + undefined, label: "Action" }} + status="success" + title="Success!" + /> + )) + .add("with error status", () => ( + undefined, label: "Action" }} + expandText={"Some expanded text"} + status="error" + title="Error" + /> + )) + .add("with warning status", () => ( + + )); diff --git a/src/theme.ts b/src/theme.ts index 610cdcd77..bddcd3d94 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -357,8 +357,7 @@ export default (colors: IThemeColors): Theme => "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: "block", - maxWidth: 480, - padding: 0 + maxWidth: 480 } }, MuiSwitch: { From f247e79369f122ec79b0f249e73c3876d6574f59 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Fri, 26 Jun 2020 11:25:12 +0200 Subject: [PATCH 05/12] update defaultMessages --- locale/defaultMessages.json | 12 ++++++++++++ src/components/messages/index.ts | 4 ++-- src/components/messages/styles.ts | 8 +++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index b10a8815f..c97881d0b 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1420,6 +1420,18 @@ "context": "weight", "string": "{value} {unit}" }, + "src_dot_components_dot_messages_dot_1219076963": { + "context": "snackbar expand", + "string": "Expand" + }, + "src_dot_components_dot_messages_dot_2473863536": { + "context": "snackbar button undo", + "string": "Undo" + }, + "src_dot_components_dot_messages_dot_3444275093": { + "context": "snackbar collapse", + "string": "Collapse" + }, "src_dot_configuration": { "context": "configuration section name", "string": "Configuration" diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index c86fde986..5bd306c5b 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -14,9 +14,9 @@ export interface IMessage { export const notificationOptions = { containerStyle: { - zIndex: 1000 + zIndex: 1200 }, - offset: "20px", + position: positions.TOP_RIGHT, timeout: 3000 }; diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index 934309e85..80cac9113 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -149,13 +149,15 @@ export const useStyles = makeStyles( position: "relative" }, borderRadius: 4, - paddingBottom: 15 + paddingBottom: 15, + paddingRight: 40, + position: "relative" }, snackbarContainer: { borderRadius: 4, display: "block", - margin: theme.spacing(2, 2, 0, 0), - maxWidth: 480, + margin: theme.spacing(2, 2, 0, 2), + maxWidth: 450, pointerEvents: "all", position: "relative" }, From 88da4cab1d40dadaaaa3021b82e3a85c8df64ae5 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Fri, 26 Jun 2020 11:35:19 +0200 Subject: [PATCH 06/12] MessageManager - update classNames --- src/components/messages/MessageManager.tsx | 1 - src/components/messages/styles.ts | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index 309d2e10d..1ea25d2c9 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -133,7 +133,6 @@ export const MessageManager: React.FC = props => {
Date: Mon, 29 Jun 2020 12:22:59 +0200 Subject: [PATCH 07/12] set notifications column width to widest element --- src/components/messages/index.ts | 3 +++ src/components/messages/styles.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index 5bd306c5b..9e98a4d2c 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -14,6 +14,9 @@ export interface IMessage { export const notificationOptions = { containerStyle: { + display: "grid", + gridTemplateRows: "repeat(auto-fill, minmax(90px, 1fr)", + justifyContent: "end", zIndex: 1200 }, diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index 434daa9c0..2375c0a5c 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -148,7 +148,7 @@ export const useStyles = makeStyles( }, borderRadius: 4, paddingBottom: 15, - paddingRight: 40, + paddingRight: 45, position: "relative" }, snackbarContainer: { From adebcc445221a02c3db61eeac4c7a9370409fa38 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Tue, 30 Jun 2020 19:41:43 +0200 Subject: [PATCH 08/12] remove react-alert lib, create MessageManagerProvider --- package-lock.json | 17 -- package.json | 2 - src/components/messages/Container.tsx | 12 ++ src/components/messages/MessageManager.tsx | 20 ++- .../messages/MessageManagerProvider.tsx | 156 ++++++++++++++++++ src/components/messages/Transition.tsx | 31 ++++ src/components/messages/index.ts | 50 ++++-- src/components/messages/styles.ts | 21 ++- src/hooks/useNotifier.ts | 17 +- src/index.tsx | 83 +++++----- src/storybook/Decorator.tsx | 7 +- 11 files changed, 327 insertions(+), 89 deletions(-) create mode 100644 src/components/messages/Container.tsx create mode 100644 src/components/messages/MessageManagerProvider.tsx create mode 100644 src/components/messages/Transition.tsx diff --git a/package-lock.json b/package-lock.json index 0db110917..816a78f27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3587,14 +3587,6 @@ "csstype": "^2.2.0" } }, - "@types/react-alert": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@types/react-alert/-/react-alert-5.2.0.tgz", - "integrity": "sha512-1cyu/vQ4X0CJVC3X4CrJHRBc/9BvTqOF9FH6iy/4TLwPng++GC4ftxS/UaM91FqsKjao7pPtqxUlW1cRafNHLw==", - "requires": { - "@types/react": "*" - } - }, "@types/react-dom": { "version": "16.9.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", @@ -17676,15 +17668,6 @@ "prop-types": "^15.6.2" } }, - "react-alert": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/react-alert/-/react-alert-7.0.1.tgz", - "integrity": "sha512-pHtMtP8gFQ4hLVQ/j7HIaayjvVNoMgoZHCag8YYBUpsLcamj0+435PJdZR7oh7AVbW0C2JPvNmS5+Md1Y59oew==", - "requires": { - "prop-types": "^15.7.2", - "react-transition-group": "^4.3.0" - } - }, "react-apollo": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-3.1.5.tgz", diff --git a/package.json b/package.json index 7bb7e173a..9c25281c6 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "moment-timezone": "^0.5.26", "qs": "^6.9.0", "react": "^16.12.0", - "react-alert": "^7.0.1", "react-apollo": "^3.1.4", "react-dom": "^16.9.0", "react-dropzone": "^8.2.0", @@ -101,7 +100,6 @@ "@types/react-sortable-hoc": "^0.7.1", "@types/react-sortable-tree": "^0.3.6", "@types/react-test-renderer": "^16.8.2", - "@types/react-alert": "^5.2.0", "@types/semver-compare": "^1.0.1", "@types/storybook__addon-storyshots": "^3.4.9", "@types/storybook__react": "^4.0.2", diff --git a/src/components/messages/Container.tsx b/src/components/messages/Container.tsx new file mode 100644 index 000000000..c45cece2c --- /dev/null +++ b/src/components/messages/Container.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +import { useStyles } from "./styles"; + +const Container = ({ children }) => { + const classes = useStyles({}); + return ( + !!children.length &&
{children}
+ ); +}; + +export default Container; diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index 1ea25d2c9..aee7b651e 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -5,19 +5,22 @@ import Typography from "@material-ui/core/Typography"; import CloseIcon from "@material-ui/icons/Close"; import classNames from "classnames"; import React, { useState } from "react"; -import { AlertComponentPropsWithStyle } from "react-alert"; import { FormattedMessage } from "react-intl"; -import { IMessage } from "./"; +import { IMessage, INotification } from "./"; import { useStyles } from "./styles"; -export interface IMessageManagerProps extends AlertComponentPropsWithStyle { +export interface IMessageManagerProps extends INotification { message: IMessage; + onMouseEnter: () => void; + onMouseLeave: () => void; } -export const MessageManager: React.FC = props => { +export const MessageManagerTemplate: React.FC = props => { const { close, + onMouseEnter, + onMouseLeave, options: { timeout }, message: { actionBtn, expandText, status = "info", title, text, onUndo } } = props; @@ -27,7 +30,12 @@ export const MessageManager: React.FC = props => { const classes = useStyles({}); return ( -
+
= props => {
); }; - -export default MessageManager; diff --git a/src/components/messages/MessageManagerProvider.tsx b/src/components/messages/MessageManagerProvider.tsx new file mode 100644 index 000000000..769df0802 --- /dev/null +++ b/src/components/messages/MessageManagerProvider.tsx @@ -0,0 +1,156 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { TransitionGroup } from "react-transition-group"; + +import { + INotification, + INotificationContext, + ITimer, + MessageContext, + MessageManagerTemplate, + types +} from "."; +import Container from "./Container"; +import Transition from "./Transition"; + +const containerStyle = { + display: "grid", + gridTemplateRows: "repeat(auto-fill, minmax(90px, 1fr)", + justifyContent: "end", + zIndex: 1200 +}; + +const MessageManagerProvider = ({ children }) => { + const root = useRef(null); + const notificationContext = useRef(null); + const timersArr = useRef([]); + const [notifications, setNotifications] = useState([]); + + useEffect(() => { + root.current = document.createElement("div"); + root.current.id = "__message-manager__"; + document.body.appendChild(root.current); + const timersArrRef = timersArr.current; + + return () => { + timersArrRef.forEach(timer => clearTimeout(timer.timeoutId)); + if (root.current) { + document.body.removeChild(root.current); + } + }; + }, []); + + const timerCallback = (notification: INotification) => { + remove(notification); + timersArr.current = timersArr.current.filter( + timer => timer.id !== notification.id + ); + }; + + const remove = useCallback(notification => { + setNotifications(currentNotifications => + currentNotifications.filter(n => n.id !== notification.id) + ); + }, []); + + const show = useCallback( + (message = {}, options = {}) => { + const id = Math.random() + .toString(36) + .substr(2, 9); + + const notificationOptions = { + timeout: 4000, + type: types.INFO, + ...options + }; + + const notification = { + close: () => remove(alert), + id, + message, + options: notificationOptions + }; + + const { + options: { timeout } + } = notification; + + const timeoutId = window.setTimeout(() => { + timerCallback(notification); + }, timeout); + + timersArr.current.push({ + id: notification.id, + notification, + remaining: timeout, + start: new Date().getTime(), + timeoutId + }); + + setNotifications(state => [notification, ...state]); + + return notification; + }, + [remove] + ); + + const getCurrentTimer = (notification: INotification) => { + const currentTimerIndex = timersArr.current.findIndex( + timer => timer.id === notification.id + ); + return timersArr.current[currentTimerIndex]; + }; + + const pauseTimer = (notification: INotification) => { + const currentTimer = getCurrentTimer(notification); + if (currentTimer) { + currentTimer.remaining = + currentTimer.remaining - (new Date().getTime() - currentTimer.start); + window.clearTimeout(currentTimer.timeoutId); + } + }; + const resumeTimer = (notification: INotification) => { + const currentTimer = getCurrentTimer(notification); + if (currentTimer) { + currentTimer.start = new Date().getTime(); + currentTimer.timeoutId = window.setTimeout( + () => timerCallback(notification), + currentTimer.remaining + ); + } + }; + + notificationContext.current = { + remove, + show + }; + + return ( + + {children} + {root.current && + createPortal( + + {!!notifications.length && + notifications.map(notification => ( + + pauseTimer(notification)} + onMouseLeave={() => resumeTimer(notification)} + {...notification} + /> + + ))} + , + root.current + )} + + ); +}; + +export default MessageManagerProvider; diff --git a/src/components/messages/Transition.tsx b/src/components/messages/Transition.tsx new file mode 100644 index 000000000..e14c12483 --- /dev/null +++ b/src/components/messages/Transition.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { Transition as MessageManagerTransition } from "react-transition-group"; + +const duration = 250; + +const defaultStyle = { + opacity: 0, + transition: `opacity ${duration}ms ease` +}; + +const transitionStyles = { + entered: { opacity: 1 }, + entering: { opacity: 0 } +}; + +const Transition = ({ children, ...props }) => ( + + {state => ( +
+ {children} +
+ )} +
+); + +export default Transition; diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index 9e98a4d2c..84cd6265a 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -1,4 +1,6 @@ -import { positions } from "react-alert"; +import { createContext } from "react"; + +export type Status = "success" | "error" | "info" | "warning"; export interface IMessage { actionBtn?: { label: string; @@ -9,19 +11,45 @@ export interface IMessage { title?: string; text: string; onUndo?: () => void; - status?: "success" | "error" | "info" | "warning"; + status?: Status; } -export const notificationOptions = { - containerStyle: { - display: "grid", - gridTemplateRows: "repeat(auto-fill, minmax(90px, 1fr)", - justifyContent: "end", - zIndex: 1200 - }, +export interface IOptions { + timeout: number; + type?: Status; +} - position: positions.TOP_RIGHT, - timeout: 3000 +export interface INotification { + id: string; + message: IMessage; + options: IOptions; + close: () => void; +} + +export interface ITimer { + id: string; + notification: INotification; + remaining: number; + start: number; + timeoutId: number; +} + +export const types = { + ERROR: "error", + INFO: "info", + SUCCESS: "success", + WARNING: "warning" }; +export interface INotificationContext { + show: (message: IMessage, options?: IOptions) => void; + remove: (notification: INotification) => void; +} + +export type IMessageContext = (message: IMessage) => void; +export const MessageContext = createContext< + React.MutableRefObject +>(null); export * from "./MessageManager"; +export * from "./MessageManagerProvider"; +export { default } from "./MessageManagerProvider"; diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index 2375c0a5c..f15716624 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -8,6 +8,7 @@ import { darken } from "@material-ui/core/styles/colorManipulator"; const successColor = "#60DAA0"; const warningColor = "#FFB84E"; const infoColor = "#CAD8DF"; +const errorColor = "#FE6E76"; export const useStyles = makeStyles( theme => ({ @@ -32,6 +33,17 @@ export const useStyles = makeStyles( closeBtnInfo: { color: theme.palette.text.primary }, + container: { + alignItems: "flex-end", + display: "flex", + flexDirection: "column", + justifyContent: "center", + left: 0, + pointerEvents: "none", + position: "fixed", + top: 0, + width: "100%" + }, error: { "& > div": { "& button span": { @@ -43,7 +55,7 @@ export const useStyles = makeStyles( backgroundImage: `url(${errorIcon})` } }, - backgroundColor: theme.palette.error.main, + backgroundColor: errorColor, color: "#fff" }, expandBtn: { @@ -119,7 +131,7 @@ export const useStyles = makeStyles( width: "calc(100%)" }, progressBarError: { - backgroundColor: darken(theme.palette.error.main, 0.2) + backgroundColor: darken(errorColor, 0.2) }, progressBarSuccess: { backgroundColor: darken(successColor, 0.2) @@ -146,6 +158,11 @@ export const useStyles = makeStyles( paddingTop: 16, position: "relative" }, + "&:hover": { + "& [class*='progressBar']": { + animationPlayState: "paused" + } + }, borderRadius: 4, paddingBottom: 15, paddingRight: 45, diff --git a/src/hooks/useNotifier.ts b/src/hooks/useNotifier.ts index d5e3ed55f..a6388f7dd 100644 --- a/src/hooks/useNotifier.ts +++ b/src/hooks/useNotifier.ts @@ -1,11 +1,20 @@ import { IMessage } from "@saleor/components/messages"; -import { useAlert } from "react-alert"; +import { IMessageContext, MessageContext } from "@saleor/components/messages"; +import { useContext, useMemo } from "react"; + +export type UseNotifierResult = IMessageContext; -export type UseNotifierResult = (options: IMessage) => void; function useNotifier(): UseNotifierResult { - const alert = useAlert(); + const notificationContext = useContext(MessageContext); + const notification = useMemo(() => notificationContext.current, [ + notificationContext + ]); + const notify = (options: IMessage) => { - alert.show(options, options.autohide && { timeout: options.autohide }); + notification.show( + options, + options.autohide && { timeout: options.autohide } + ); }; return notify; } diff --git a/src/index.tsx b/src/index.tsx index 211cfa7b7..9cc19d811 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,66 +1,65 @@ -import { API_URI, APP_MOUNT_URI, GTM_ID } from "./config"; -import Auth, { getAuthToken, removeAuthToken } from "./auth"; -import { BrowserRouter, Route, Switch } from "react-router-dom"; -import ConfigurationSection, { createConfigurationMenu } from "./configuration"; -import { ErrorResponse, onError } from "apollo-link-error"; -import { InMemoryCache, defaultDataIdFromObject } from "apollo-cache-inmemory"; -import { MessageManager, notificationOptions } from "./components/messages"; - -import { Provider as AlertProvider } from "react-alert"; +import Navigator from "@saleor/components/Navigator"; +import useAppState from "@saleor/hooks/useAppState"; +import { defaultDataIdFromObject, InMemoryCache } from "apollo-cache-inmemory"; import { ApolloClient } from "apollo-client"; import { ApolloLink } from "apollo-link"; -import { ApolloProvider } from "react-apollo"; -import AppLayout from "./components/AppLayout"; -import AppStateProvider from "./containers/AppState"; -import AttributeSection from "./attributes"; -import AuthProvider from "./auth/AuthProvider"; -import BackgroundTasksProvider from "./containers/BackgroundTasks"; import { BatchHttpLink } from "apollo-link-batch-http"; +import { setContext } from "apollo-link-context"; +import { ErrorResponse, onError } from "apollo-link-error"; +import { createUploadLink } from "apollo-upload-client"; +import React from "react"; +import { ApolloProvider } from "react-apollo"; +import { render } from "react-dom"; +import ErrorBoundary from "react-error-boundary"; +import TagManager from "react-gtm-module"; +import { useIntl } from "react-intl"; +import { BrowserRouter, Route, Switch } from "react-router-dom"; + +import AttributeSection from "./attributes"; +import { attributeSection } from "./attributes/urls"; +import Auth, { getAuthToken, removeAuthToken } from "./auth"; +import AuthProvider from "./auth/AuthProvider"; +import LoginLoading from "./auth/components/LoginLoading/LoginLoading"; +import SectionRoute from "./auth/components/SectionRoute"; +import { isJwtError } from "./auth/errors"; +import { hasPermission } from "./auth/misc"; import CategorySection from "./categories"; import CollectionSection from "./collections"; -import { CustomerSection } from "./customers"; +import AppLayout from "./components/AppLayout"; import { DateProvider } from "./components/Date"; -import DiscountSection from "./discounts"; -import ErrorBoundary from "react-error-boundary"; -import HomePage from "./home"; import { LocaleProvider } from "./components/Locale"; -import LoginLoading from "./auth/components/LoginLoading/LoginLoading"; +import MessageManagerProvider from "./components/messages"; +import { ShopProvider } from "./components/Shop"; +import ThemeProvider from "./components/Theme"; +import { WindowTitle } from "./components/WindowTitle"; +import { API_URI, APP_MOUNT_URI, GTM_ID } from "./config"; +import ConfigurationSection, { createConfigurationMenu } from "./configuration"; +import AppStateProvider from "./containers/AppState"; +import BackgroundTasksProvider from "./containers/BackgroundTasks"; +import { CustomerSection } from "./customers"; +import DiscountSection from "./discounts"; +import HomePage from "./home"; +import { commonMessages } from "./intl"; import NavigationSection from "./navigation"; -import Navigator from "@saleor/components/Navigator"; +import { navigationSection } from "./navigation/urls"; import { NotFound } from "./NotFound"; import OrdersSection from "./orders"; import PageSection from "./pages"; -import { PermissionEnum } from "./types/globalTypes"; import PermissionGroupSection from "./permissionGroups"; import PluginsSection from "./plugins"; import ProductSection from "./products"; import ProductTypesSection from "./productTypes"; -import React from "react"; -import SectionRoute from "./auth/components/SectionRoute"; import ServiceSection from "./services"; +import { serviceSection } from "./services/urls"; import ShippingSection from "./shipping"; -import { ShopProvider } from "./components/Shop"; import SiteSettingsSection from "./siteSettings"; import StaffSection from "./staff"; -import TagManager from "react-gtm-module"; import TaxesSection from "./taxes"; -import ThemeProvider from "./components/Theme"; import TranslationsSection from "./translations"; +import { PermissionEnum } from "./types/globalTypes"; import WarehouseSection from "./warehouses"; -import WebhooksSection from "./webhooks"; -import { WindowTitle } from "./components/WindowTitle"; -import { attributeSection } from "./attributes/urls"; -import { commonMessages } from "./intl"; -import { createUploadLink } from "apollo-upload-client"; -import { hasPermission } from "./auth/misc"; -import { isJwtError } from "./auth/errors"; -import { navigationSection } from "./navigation/urls"; -import { render } from "react-dom"; -import { serviceSection } from "./services/urls"; -import { setContext } from "apollo-link-context"; -import useAppState from "@saleor/hooks/useAppState"; -import { useIntl } from "react-intl"; import { warehouseSection } from "./warehouses/urls"; +import WebhooksSection from "./webhooks"; interface ResponseError extends ErrorResponse { networkError?: Error & { @@ -136,7 +135,7 @@ const App: React.FC = () => { - + @@ -144,7 +143,7 @@ const App: React.FC = () => { - + diff --git a/src/storybook/Decorator.tsx b/src/storybook/Decorator.tsx index 9f8426de2..66e548747 100644 --- a/src/storybook/Decorator.tsx +++ b/src/storybook/Decorator.tsx @@ -1,10 +1,9 @@ import { Locale, RawLocaleProvider } from "@saleor/components/Locale"; import React from "react"; -import { Provider as AlertProvider } from "react-alert"; import { IntlProvider } from "react-intl"; import { Provider as DateProvider } from "../components/Date/DateContext"; -import { MessageManager, notificationOptions } from "../components/messages"; +import MessageManagerProvider from "../components/messages"; import ThemeProvider from "../components/Theme"; import { TimezoneProvider } from "../components/Timezone"; @@ -19,7 +18,7 @@ export const Decorator = storyFn => ( - +
( > {storyFn()}
-
+
From a3d16975da7fdc1f19d87acb5cd1e05911f89800 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Tue, 30 Jun 2020 21:32:20 +0200 Subject: [PATCH 09/12] MessageManager clean up --- package-lock.json | 41 +++++++--- src/components/messages/MessageManager.tsx | 7 +- .../messages/MessageManagerProvider.tsx | 77 +++++++------------ src/components/messages/index.ts | 13 +--- src/components/messages/styles.ts | 3 +- src/hooks/useNotifier.ts | 10 +-- 6 files changed, 68 insertions(+), 83 deletions(-) diff --git a/package-lock.json b/package-lock.json index 816a78f27..46769d8b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11582,7 +11582,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -11600,11 +11601,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11617,15 +11620,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -11728,7 +11734,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -11738,6 +11745,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -11750,17 +11758,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -11777,6 +11788,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -11849,7 +11861,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -11859,6 +11872,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -11934,7 +11948,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -11964,6 +11979,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -11981,6 +11997,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -12019,11 +12036,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index aee7b651e..20b1d8db3 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -7,11 +7,10 @@ import classNames from "classnames"; import React, { useState } from "react"; import { FormattedMessage } from "react-intl"; -import { IMessage, INotification } from "./"; +import { INotification } from "./"; import { useStyles } from "./styles"; export interface IMessageManagerProps extends INotification { - message: IMessage; onMouseEnter: () => void; onMouseLeave: () => void; } @@ -21,8 +20,8 @@ export const MessageManagerTemplate: React.FC = props => { close, onMouseEnter, onMouseLeave, - options: { timeout }, - message: { actionBtn, expandText, status = "info", title, text, onUndo } + message: { actionBtn, expandText, status = "info", title, text, onUndo }, + timeout } = props; const [expand, setExpand] = useState(false); diff --git a/src/components/messages/MessageManagerProvider.tsx b/src/components/messages/MessageManagerProvider.tsx index 769df0802..e1bdb38b1 100644 --- a/src/components/messages/MessageManagerProvider.tsx +++ b/src/components/messages/MessageManagerProvider.tsx @@ -4,11 +4,9 @@ import { TransitionGroup } from "react-transition-group"; import { INotification, - INotificationContext, ITimer, MessageContext, - MessageManagerTemplate, - types + MessageManagerTemplate } from "."; import Container from "./Container"; import Transition from "./Transition"; @@ -22,7 +20,6 @@ const containerStyle = { const MessageManagerProvider = ({ children }) => { const root = useRef(null); - const notificationContext = useRef(null); const timersArr = useRef([]); const [notifications, setNotifications] = useState([]); @@ -41,59 +38,46 @@ const MessageManagerProvider = ({ children }) => { }, []); const timerCallback = (notification: INotification) => { - remove(notification); + remove(notification.id); timersArr.current = timersArr.current.filter( timer => timer.id !== notification.id ); }; - const remove = useCallback(notification => { + const remove = useCallback(notificationId => { setNotifications(currentNotifications => - currentNotifications.filter(n => n.id !== notification.id) + currentNotifications.filter(n => n.id !== notificationId) ); }, []); - const show = useCallback( - (message = {}, options = {}) => { - const id = Math.random() - .toString(36) - .substr(2, 9); + const show = useCallback((message = {}, timeout = 3000) => { + const id = Math.random() + .toString(36) + .substr(2, 9); - const notificationOptions = { - timeout: 4000, - type: types.INFO, - ...options - }; + const notification = { + close: () => remove(id), + id, + message, + timeout + }; - const notification = { - close: () => remove(alert), - id, - message, - options: notificationOptions - }; + const timeoutId = window.setTimeout(() => { + timerCallback(notification); + }, timeout); - const { - options: { timeout } - } = notification; + timersArr.current.push({ + id: notification.id, + notification, + remaining: timeout, + start: new Date().getTime(), + timeoutId + }); - const timeoutId = window.setTimeout(() => { - timerCallback(notification); - }, timeout); + setNotifications(state => [notification, ...state]); - timersArr.current.push({ - id: notification.id, - notification, - remaining: timeout, - start: new Date().getTime(), - timeoutId - }); - - setNotifications(state => [notification, ...state]); - - return notification; - }, - [remove] - ); + return notification; + }, []); const getCurrentTimer = (notification: INotification) => { const currentTimerIndex = timersArr.current.findIndex( @@ -121,13 +105,8 @@ const MessageManagerProvider = ({ children }) => { } }; - notificationContext.current = { - remove, - show - }; - return ( - + {children} {root.current && createPortal( diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index 84cd6265a..f041f2103 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -14,15 +14,10 @@ export interface IMessage { status?: Status; } -export interface IOptions { - timeout: number; - type?: Status; -} - export interface INotification { id: string; message: IMessage; - options: IOptions; + timeout: number; close: () => void; } @@ -41,14 +36,12 @@ export const types = { WARNING: "warning" }; export interface INotificationContext { - show: (message: IMessage, options?: IOptions) => void; + show: (message: IMessage, timeout?: number) => void; remove: (notification: INotification) => void; } export type IMessageContext = (message: IMessage) => void; -export const MessageContext = createContext< - React.MutableRefObject ->(null); +export const MessageContext = createContext(null); export * from "./MessageManager"; export * from "./MessageManagerProvider"; diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index f15716624..e6022b2fa 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -42,7 +42,8 @@ export const useStyles = makeStyles( pointerEvents: "none", position: "fixed", top: 0, - width: "100%" + width: "100%", + zIndex: 1200 }, error: { "& > div": { diff --git a/src/hooks/useNotifier.ts b/src/hooks/useNotifier.ts index a6388f7dd..ebafc5512 100644 --- a/src/hooks/useNotifier.ts +++ b/src/hooks/useNotifier.ts @@ -1,20 +1,14 @@ import { IMessage } from "@saleor/components/messages"; import { IMessageContext, MessageContext } from "@saleor/components/messages"; -import { useContext, useMemo } from "react"; +import { useContext } from "react"; export type UseNotifierResult = IMessageContext; function useNotifier(): UseNotifierResult { const notificationContext = useContext(MessageContext); - const notification = useMemo(() => notificationContext.current, [ - notificationContext - ]); const notify = (options: IMessage) => { - notification.show( - options, - options.autohide && { timeout: options.autohide } - ); + notificationContext.show(options, options.autohide); }; return notify; } From 3f903b852558d1cea939fa7902130884fcbc2e42 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Thu, 2 Jul 2020 14:44:03 +0200 Subject: [PATCH 10/12] remove timer from error notification --- src/components/messages/MessageManager.tsx | 10 ++--- .../messages/MessageManagerProvider.tsx | 45 +++++++++---------- src/components/messages/index.ts | 6 +-- src/components/messages/styles.ts | 8 ++-- src/hooks/useNotifier.ts | 3 +- 5 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index 20b1d8db3..ba7b3f3a9 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -11,8 +11,8 @@ import { INotification } from "./"; import { useStyles } from "./styles"; export interface IMessageManagerProps extends INotification { - onMouseEnter: () => void; - onMouseLeave: () => void; + onMouseEnter?: () => void; + onMouseLeave?: () => void; } export const MessageManagerTemplate: React.FC = props => { @@ -32,11 +32,11 @@ export const MessageManagerTemplate: React.FC = props => {
{ const root = useRef(null); const timersArr = useRef([]); @@ -51,28 +44,26 @@ const MessageManagerProvider = ({ children }) => { }, []); const show = useCallback((message = {}, timeout = 3000) => { - const id = Math.random() - .toString(36) - .substr(2, 9); - + const id = Date.now(); const notification = { close: () => remove(id), id, message, timeout }; + if (timeout !== null) { + const timeoutId = window.setTimeout(() => { + timerCallback(notification); + }, timeout); - const timeoutId = window.setTimeout(() => { - timerCallback(notification); - }, timeout); - - timersArr.current.push({ - id: notification.id, - notification, - remaining: timeout, - start: new Date().getTime(), - timeoutId - }); + timersArr.current.push({ + id: notification.id, + notification, + remaining: timeout, + start: new Date().getTime(), + timeoutId + }); + } setNotifications(state => [notification, ...state]); @@ -112,16 +103,20 @@ const MessageManagerProvider = ({ children }) => { createPortal( {!!notifications.length && notifications.map(notification => ( pauseTimer(notification)} - onMouseLeave={() => resumeTimer(notification)} {...notification} + {...(!!notification.timeout + ? { + onMouseEnter: () => pauseTimer(notification), + onMouseLeave: () => resumeTimer(notification) + } + : {})} /> ))} diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index f041f2103..264a4fa35 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -15,14 +15,14 @@ export interface IMessage { } export interface INotification { - id: string; + id: number; message: IMessage; timeout: number; close: () => void; } export interface ITimer { - id: string; + id: number; notification: INotification; remaining: number; start: number; @@ -36,7 +36,7 @@ export const types = { WARNING: "warning" }; export interface INotificationContext { - show: (message: IMessage, timeout?: number) => void; + show: (message: IMessage, timeout?: number | null) => void; remove: (notification: INotification) => void; } diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index e6022b2fa..d462a93b7 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -34,10 +34,9 @@ export const useStyles = makeStyles( color: theme.palette.text.primary }, container: { - alignItems: "flex-end", - display: "flex", - flexDirection: "column", - justifyContent: "center", + display: "grid", + gridTemplateRows: "repeat(auto-fill, minmax(90px, 1fr))", + justifyContent: "end", left: 0, pointerEvents: "none", position: "fixed", @@ -166,6 +165,7 @@ export const useStyles = makeStyles( }, borderRadius: 4, paddingBottom: 15, + paddingLeft: 5, paddingRight: 45, position: "relative" }, diff --git a/src/hooks/useNotifier.ts b/src/hooks/useNotifier.ts index ebafc5512..b18186511 100644 --- a/src/hooks/useNotifier.ts +++ b/src/hooks/useNotifier.ts @@ -8,7 +8,8 @@ function useNotifier(): UseNotifierResult { const notificationContext = useContext(MessageContext); const notify = (options: IMessage) => { - notificationContext.show(options, options.autohide); + const timeout = options.status === "error" ? null : options.autohide; + notificationContext.show(options, timeout); }; return notify; } From ce19f6aab6eb49b9c24eb22c1ae2cf8ab4216fb6 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Thu, 2 Jul 2020 14:55:39 +0200 Subject: [PATCH 11/12] MessageManager - update data-test --- src/components/messages/MessageManager.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index ba7b3f3a9..274dfcd21 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -46,7 +46,7 @@ export const MessageManagerTemplate: React.FC = props => { [classes.warning]: status === "warning" })} message={ - + {title && ( {title} @@ -106,7 +106,7 @@ export const MessageManagerTemplate: React.FC = props => { color="default" size="small" onClick={close} - data-tc="button-undo" + data-test="button-undo" > = props => { color="default" size="small" onClick={actionBtn.action} - data-tc="button-action" + data-test="button-action" > {actionBtn.label} From 4cf6a64449c6ec875c071459b5fcbc49cb124567 Mon Sep 17 00:00:00 2001 From: AlicjaSzu Date: Thu, 2 Jul 2020 15:39:11 +0200 Subject: [PATCH 12/12] MessageManager - fix identical operands error --- src/components/messages/MessageManager.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index 274dfcd21..fd2953607 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -27,18 +27,19 @@ export const MessageManagerTemplate: React.FC = props => { const [expand, setExpand] = useState(false); const classes = useStyles({}); + const id = props.id.toString(); return (
= props => { [classes.warning]: status === "warning" })} message={ - + {title && ( {title}