Merge pull request #581 from mirumee/feature/update-message-manager
update message manager
This commit is contained in:
commit
7204687d80
17 changed files with 803 additions and 119 deletions
5
assets/images/error-icon.svg
Normal file
5
assets/images/error-icon.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F5FAFB"/>
|
||||||
|
<rect x="10.2726" y="9.00024" width="17.9987" height="1.79987" transform="rotate(45 10.2726 9.00024)" fill="#FE6D76"/>
|
||||||
|
<rect x="23" y="10.2727" width="17.9987" height="1.79987" transform="rotate(135 23 10.2727)" fill="#FE6D76"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 380 B |
3
assets/images/info-icon.svg
Normal file
3
assets/images/info-icon.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32ZM15 20V8H17V20H15ZM15 24V22H17V24H15Z" fill="#CAD8DF"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 321 B |
4
assets/images/success-icon.svg
Normal file
4
assets/images/success-icon.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F5FAFB"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.9167 10.306L12.5417 23L7 16.0699L8.48477 14.8326L12.611 20.0641L22.4919 9L23.9167 10.306Z" fill="#60DAA0"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 312 B |
5
assets/images/warning-icon.svg
Normal file
5
assets/images/warning-icon.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="16" cy="16" r="16" fill="#F5FAFB"/>
|
||||||
|
<rect x="10.2726" y="9.00024" width="17.9987" height="1.79987" transform="rotate(45 10.2726 9.00024)" fill="#D98E1D"/>
|
||||||
|
<rect x="23" y="10.2727" width="17.9987" height="1.79987" transform="rotate(135 23 10.2727)" fill="#D98E1D"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 380 B |
|
@ -1420,6 +1420,18 @@
|
||||||
"context": "weight",
|
"context": "weight",
|
||||||
"string": "{value} {unit}"
|
"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": {
|
"src_dot_configuration": {
|
||||||
"context": "configuration section name",
|
"context": "configuration section name",
|
||||||
"string": "Configuration"
|
"string": "Configuration"
|
||||||
|
|
12
src/components/messages/Container.tsx
Normal file
12
src/components/messages/Container.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { useStyles } from "./styles";
|
||||||
|
|
||||||
|
const Container = ({ children }) => {
|
||||||
|
const classes = useStyles({});
|
||||||
|
return (
|
||||||
|
!!children.length && <div className={classes.container}>{children}</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Container;
|
|
@ -1,117 +1,155 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
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 Typography from "@material-ui/core/Typography";
|
||||||
import CloseIcon from "@material-ui/icons/Close";
|
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 { INotification } from "./";
|
||||||
|
import { useStyles } from "./styles";
|
||||||
|
|
||||||
interface Message extends IMessage {
|
export interface IMessageManagerProps extends INotification {
|
||||||
key: string;
|
onMouseEnter?: () => void;
|
||||||
}
|
onMouseLeave?: () => void;
|
||||||
interface MessageManagerState {
|
|
||||||
message: Message;
|
|
||||||
opened: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MessageManager extends React.Component<{}, MessageManagerState> {
|
export const MessageManagerTemplate: React.FC<IMessageManagerProps> = props => {
|
||||||
state: MessageManagerState = {
|
const {
|
||||||
message: { key: "0", onUndo: undefined, text: "" },
|
close,
|
||||||
opened: false
|
onMouseEnter,
|
||||||
};
|
onMouseLeave,
|
||||||
queue = [];
|
message: { actionBtn, expandText, status = "info", title, text, onUndo },
|
||||||
|
timeout
|
||||||
|
} = props;
|
||||||
|
|
||||||
handleClose = (_, reason) => {
|
const [expand, setExpand] = useState(false);
|
||||||
if (reason === "clickaway") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({ opened: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleExited = () => {
|
const classes = useStyles({});
|
||||||
this.processQueue();
|
const id = props.id.toString();
|
||||||
};
|
|
||||||
|
|
||||||
pushMessage = (message: IMessage) => {
|
|
||||||
this.queue.push({
|
|
||||||
key: new Date().getTime(),
|
|
||||||
...message
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.state.opened) {
|
|
||||||
this.setState({ opened: false });
|
|
||||||
} else {
|
|
||||||
this.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 (
|
return (
|
||||||
<>
|
<div
|
||||||
<Snackbar
|
key={props.id}
|
||||||
key={key}
|
className={classes.snackbarContainer}
|
||||||
anchorOrigin={{
|
onMouseEnter={onMouseEnter}
|
||||||
horizontal: "right",
|
onMouseLeave={onMouseLeave}
|
||||||
vertical: "top"
|
>
|
||||||
}}
|
<SnackbarContent
|
||||||
open={this.state.opened}
|
id={id}
|
||||||
autoHideDuration={autohide}
|
key={id}
|
||||||
onClose={this.handleClose}
|
aria-describedby={`message-id-${id}`}
|
||||||
onExited={this.handleExited}
|
className={classNames(classes.snackbar, {
|
||||||
ContentProps={{
|
[classes.info]: status === "info",
|
||||||
"aria-describedby": "message-id"
|
[classes.error]: status === "error",
|
||||||
}}
|
[classes.success]: status === "success",
|
||||||
|
[classes.warning]: status === "warning"
|
||||||
|
})}
|
||||||
message={
|
message={
|
||||||
<span id="message-id" data-test="notification">
|
<span id={`message-id-${id}`} data-test="notification">
|
||||||
{title && (
|
{title && (
|
||||||
<Typography variant="h5" style={{ marginBottom: "1rem" }}>
|
<Typography variant="h5" style={{ fontWeight: "bold" }}>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
<Typography
|
||||||
|
className={status === "info" ? classes.textInfo : classes.text}
|
||||||
|
>
|
||||||
{text}
|
{text}
|
||||||
|
</Typography>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
title={title}
|
|
||||||
action={[
|
action={[
|
||||||
!!onUndo ? (
|
!!expandText ? (
|
||||||
<Button
|
<div
|
||||||
key="undo"
|
key="expanded"
|
||||||
color="secondary"
|
className={classNames(classes.expandedContainer, {
|
||||||
size="small"
|
[classes.expandedContainerInfo]: status === "info"
|
||||||
onClick={this.handleClose as any}
|
})}
|
||||||
data-test="button-undo"
|
|
||||||
>
|
>
|
||||||
UNDO
|
<div
|
||||||
</Button>
|
className={classNames(
|
||||||
|
classes.expandedContainerContent,
|
||||||
|
expand ? classes.expandedText : classes.hiddenText
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p>{expandText}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className={classNames(classes.expandBtn, {
|
||||||
|
[classes.expandBtnInfo]: status === "info"
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
setExpand(expand => !expand);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!expand ? (
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Expand"
|
||||||
|
description="snackbar expand"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Collapse"
|
||||||
|
description="snackbar collapse"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
undefined
|
undefined
|
||||||
),
|
),
|
||||||
|
<div key="actions" className={classes.actionContainer}>
|
||||||
|
{!!onUndo && (
|
||||||
|
<Button
|
||||||
|
key="undo"
|
||||||
|
color="default"
|
||||||
|
size="small"
|
||||||
|
onClick={close}
|
||||||
|
data-test="button-undo"
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Undo"
|
||||||
|
description="snackbar button undo"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!!actionBtn && (
|
||||||
|
<Button
|
||||||
|
key="action"
|
||||||
|
color="default"
|
||||||
|
size="small"
|
||||||
|
onClick={actionBtn.action}
|
||||||
|
data-test="button-action"
|
||||||
|
>
|
||||||
|
{actionBtn.label}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>,
|
||||||
<IconButton
|
<IconButton
|
||||||
key="close"
|
key="close"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={this.handleClose as any}
|
onClick={close}
|
||||||
|
className={classNames(classes.closeBtn, {
|
||||||
|
[classes.closeBtnInfo]: status === "info"
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>,
|
||||||
|
<div className={classes.progressBarContainer} key="progressBar">
|
||||||
|
<div
|
||||||
|
className={classNames(classes.progressBar, {
|
||||||
|
[classes.progressBarSuccess]: status === "success",
|
||||||
|
[classes.progressBarWarning]: status === "warning",
|
||||||
|
[classes.progressBarError]: status === "error"
|
||||||
|
})}
|
||||||
|
style={{ ["--animationTime" as any]: `${timeout}ms` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<MessageContext.Provider value={this.pushMessage}>
|
</div>
|
||||||
{this.props.children}
|
|
||||||
</MessageContext.Provider>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
export default MessageManager;
|
|
||||||
|
|
130
src/components/messages/MessageManagerProvider.tsx
Normal file
130
src/components/messages/MessageManagerProvider.tsx
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { TransitionGroup } from "react-transition-group";
|
||||||
|
|
||||||
|
import {
|
||||||
|
INotification,
|
||||||
|
ITimer,
|
||||||
|
MessageContext,
|
||||||
|
MessageManagerTemplate
|
||||||
|
} from ".";
|
||||||
|
import Container from "./Container";
|
||||||
|
import Transition from "./Transition";
|
||||||
|
|
||||||
|
const MessageManagerProvider = ({ children }) => {
|
||||||
|
const root = useRef(null);
|
||||||
|
const timersArr = useRef<ITimer[]>([]);
|
||||||
|
const [notifications, setNotifications] = useState<INotification[]>([]);
|
||||||
|
|
||||||
|
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.id);
|
||||||
|
timersArr.current = timersArr.current.filter(
|
||||||
|
timer => timer.id !== notification.id
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = useCallback(notificationId => {
|
||||||
|
setNotifications(currentNotifications =>
|
||||||
|
currentNotifications.filter(n => n.id !== notificationId)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const show = useCallback((message = {}, timeout = 3000) => {
|
||||||
|
const id = Date.now();
|
||||||
|
const notification = {
|
||||||
|
close: () => remove(id),
|
||||||
|
id,
|
||||||
|
message,
|
||||||
|
timeout
|
||||||
|
};
|
||||||
|
if (timeout !== null) {
|
||||||
|
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;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MessageContext.Provider value={{ remove, show }}>
|
||||||
|
{children}
|
||||||
|
{root.current &&
|
||||||
|
createPortal(
|
||||||
|
<TransitionGroup
|
||||||
|
appear
|
||||||
|
options={{ position: "top right" }}
|
||||||
|
component={Container}
|
||||||
|
>
|
||||||
|
{!!notifications.length &&
|
||||||
|
notifications.map(notification => (
|
||||||
|
<Transition key={notification.id}>
|
||||||
|
<MessageManagerTemplate
|
||||||
|
{...notification}
|
||||||
|
{...(!!notification.timeout
|
||||||
|
? {
|
||||||
|
onMouseEnter: () => pauseTimer(notification),
|
||||||
|
onMouseLeave: () => resumeTimer(notification)
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
))}
|
||||||
|
</TransitionGroup>,
|
||||||
|
root.current
|
||||||
|
)}
|
||||||
|
</MessageContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MessageManagerProvider;
|
31
src/components/messages/Transition.tsx
Normal file
31
src/components/messages/Transition.tsx
Normal file
|
@ -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 }) => (
|
||||||
|
<MessageManagerTransition {...props} timeout={duration}>
|
||||||
|
{state => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...defaultStyle,
|
||||||
|
...transitionStyles[state]
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</MessageManagerTransition>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Transition;
|
|
@ -1,13 +1,48 @@
|
||||||
import { createContext } from "react";
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export type Status = "success" | "error" | "info" | "warning";
|
||||||
export interface IMessage {
|
export interface IMessage {
|
||||||
|
actionBtn?: {
|
||||||
|
label: string;
|
||||||
|
action: () => void;
|
||||||
|
};
|
||||||
autohide?: number;
|
autohide?: number;
|
||||||
|
expandText?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
text: string;
|
text: string;
|
||||||
onUndo?: () => void;
|
onUndo?: () => void;
|
||||||
|
status?: Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INotification {
|
||||||
|
id: number;
|
||||||
|
message: IMessage;
|
||||||
|
timeout: number;
|
||||||
|
close: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITimer {
|
||||||
|
id: number;
|
||||||
|
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, timeout?: number | null) => void;
|
||||||
|
remove: (notification: INotification) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export type IMessageContext = (message: IMessage) => void;
|
export type IMessageContext = (message: IMessage) => void;
|
||||||
export const MessageContext = createContext<IMessageContext>(undefined);
|
export const MessageContext = createContext<INotificationContext>(null);
|
||||||
|
|
||||||
export * from "./MessageManager";
|
export * from "./MessageManager";
|
||||||
export default MessageContext.Consumer;
|
export * from "./MessageManagerProvider";
|
||||||
|
export { default } from "./MessageManagerProvider";
|
||||||
|
|
213
src/components/messages/styles.ts
Normal file
213
src/components/messages/styles.ts
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
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";
|
||||||
|
const errorColor = "#FE6E76";
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
"@keyframes bar": {
|
||||||
|
from: { transform: "translateX(-100%)" },
|
||||||
|
to: { transform: "translateX(0)" }
|
||||||
|
},
|
||||||
|
actionContainer: {
|
||||||
|
marginLeft: -8
|
||||||
|
},
|
||||||
|
closeBtn: {
|
||||||
|
"& svg": {
|
||||||
|
maxHeight: 18,
|
||||||
|
maxWidth: 18
|
||||||
|
},
|
||||||
|
color: "#fff",
|
||||||
|
padding: 10,
|
||||||
|
position: "absolute",
|
||||||
|
right: 5,
|
||||||
|
top: 7
|
||||||
|
},
|
||||||
|
closeBtnInfo: {
|
||||||
|
color: theme.palette.text.primary
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateRows: "repeat(auto-fill, minmax(90px, 1fr))",
|
||||||
|
justifyContent: "end",
|
||||||
|
left: 0,
|
||||||
|
pointerEvents: "none",
|
||||||
|
position: "fixed",
|
||||||
|
top: 0,
|
||||||
|
width: "100%",
|
||||||
|
zIndex: 1200
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
"& > div": {
|
||||||
|
"& button span": {
|
||||||
|
color: "#fff"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"& > div:first-child": {
|
||||||
|
"&:before": {
|
||||||
|
backgroundImage: `url(${errorIcon})`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: errorColor,
|
||||||
|
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
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
"& > div:first-child": {
|
||||||
|
"&:before": {
|
||||||
|
backgroundImage: `url(${infoIcon})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
progressBar: {
|
||||||
|
animation: `$bar var(--animationTime) ease both`,
|
||||||
|
backgroundColor: infoColor,
|
||||||
|
height: 8,
|
||||||
|
transform: "translateX(-100%)",
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
progressBarContainer: {
|
||||||
|
borderRadius: "0 0 4px 4px",
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
position: "absolute",
|
||||||
|
width: "calc(100%)"
|
||||||
|
},
|
||||||
|
progressBarError: {
|
||||||
|
backgroundColor: darken(errorColor, 0.2)
|
||||||
|
},
|
||||||
|
progressBarSuccess: {
|
||||||
|
backgroundColor: darken(successColor, 0.2)
|
||||||
|
},
|
||||||
|
progressBarWarning: {
|
||||||
|
backgroundColor: darken(warningColor, 0.2)
|
||||||
|
},
|
||||||
|
snackbar: {
|
||||||
|
"& > div": {
|
||||||
|
paddingLeft: 60
|
||||||
|
},
|
||||||
|
"& > div:first-child": {
|
||||||
|
"&:before": {
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundSize: "contain",
|
||||||
|
content: "''",
|
||||||
|
display: "block",
|
||||||
|
height: 32,
|
||||||
|
left: 15,
|
||||||
|
position: "absolute",
|
||||||
|
top: 13,
|
||||||
|
width: 32
|
||||||
|
},
|
||||||
|
paddingTop: 16,
|
||||||
|
position: "relative"
|
||||||
|
},
|
||||||
|
"&:hover": {
|
||||||
|
"& [class*='progressBar']": {
|
||||||
|
animationPlayState: "paused"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
borderRadius: 4,
|
||||||
|
paddingBottom: 15,
|
||||||
|
paddingLeft: 5,
|
||||||
|
paddingRight: 45,
|
||||||
|
position: "relative"
|
||||||
|
},
|
||||||
|
snackbarContainer: {
|
||||||
|
borderRadius: 4,
|
||||||
|
display: "block",
|
||||||
|
margin: theme.spacing(2, 2, 0, 2),
|
||||||
|
maxWidth: 450,
|
||||||
|
pointerEvents: "all",
|
||||||
|
position: "relative"
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
"& > div:first-child": {
|
||||||
|
"&:before": {
|
||||||
|
backgroundImage: `url(${successIcon})`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"& button span": {
|
||||||
|
color: "#fff"
|
||||||
|
},
|
||||||
|
backgroundColor: successColor,
|
||||||
|
color: "#fff"
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
color: "#fff",
|
||||||
|
paddingTop: 5
|
||||||
|
},
|
||||||
|
textInfo: {
|
||||||
|
paddingTop: 5
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
"& > div:first-child": {
|
||||||
|
"&:before": {
|
||||||
|
backgroundImage: `url(${warningIcon})`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"& button span": {
|
||||||
|
color: "#fff"
|
||||||
|
},
|
||||||
|
backgroundColor: warningColor,
|
||||||
|
color: "#fff"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "MessageManager" }
|
||||||
|
);
|
|
@ -1,9 +1,16 @@
|
||||||
|
import { IMessage } from "@saleor/components/messages";
|
||||||
import { IMessageContext, MessageContext } from "@saleor/components/messages";
|
import { IMessageContext, MessageContext } from "@saleor/components/messages";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
export type UseNotifierResult = IMessageContext;
|
export type UseNotifierResult = IMessageContext;
|
||||||
|
|
||||||
function useNotifier(): UseNotifierResult {
|
function useNotifier(): UseNotifierResult {
|
||||||
const notify = useContext(MessageContext);
|
const notificationContext = useContext(MessageContext);
|
||||||
|
|
||||||
|
const notify = (options: IMessage) => {
|
||||||
|
const timeout = options.status === "error" ? null : options.autohide;
|
||||||
|
notificationContext.show(options, timeout);
|
||||||
|
};
|
||||||
return notify;
|
return notify;
|
||||||
}
|
}
|
||||||
export default useNotifier;
|
export default useNotifier;
|
||||||
|
|
|
@ -28,7 +28,7 @@ import CollectionSection from "./collections";
|
||||||
import AppLayout from "./components/AppLayout";
|
import AppLayout from "./components/AppLayout";
|
||||||
import { DateProvider } from "./components/Date";
|
import { DateProvider } from "./components/Date";
|
||||||
import { LocaleProvider } from "./components/Locale";
|
import { LocaleProvider } from "./components/Locale";
|
||||||
import { MessageManager } from "./components/messages";
|
import MessageManagerProvider from "./components/messages";
|
||||||
import { ShopProvider } from "./components/Shop";
|
import { ShopProvider } from "./components/Shop";
|
||||||
import ThemeProvider from "./components/Theme";
|
import ThemeProvider from "./components/Theme";
|
||||||
import { WindowTitle } from "./components/WindowTitle";
|
import { WindowTitle } from "./components/WindowTitle";
|
||||||
|
@ -135,7 +135,7 @@ const App: React.FC = () => {
|
||||||
<ThemeProvider isDefaultDark={isDark}>
|
<ThemeProvider isDefaultDark={isDark}>
|
||||||
<DateProvider>
|
<DateProvider>
|
||||||
<LocaleProvider>
|
<LocaleProvider>
|
||||||
<MessageManager>
|
<MessageManagerProvider>
|
||||||
<BackgroundTasksProvider>
|
<BackgroundTasksProvider>
|
||||||
<AppStateProvider>
|
<AppStateProvider>
|
||||||
<ShopProvider>
|
<ShopProvider>
|
||||||
|
@ -143,7 +143,7 @@ const App: React.FC = () => {
|
||||||
</ShopProvider>
|
</ShopProvider>
|
||||||
</AppStateProvider>
|
</AppStateProvider>
|
||||||
</BackgroundTasksProvider>
|
</BackgroundTasksProvider>
|
||||||
</MessageManager>
|
</MessageManagerProvider>
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
</DateProvider>
|
</DateProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React from "react";
|
||||||
import { IntlProvider } from "react-intl";
|
import { IntlProvider } from "react-intl";
|
||||||
|
|
||||||
import { Provider as DateProvider } from "../components/Date/DateContext";
|
import { Provider as DateProvider } from "../components/Date/DateContext";
|
||||||
import { MessageManager } from "../components/messages";
|
import MessageManagerProvider from "../components/messages";
|
||||||
import ThemeProvider from "../components/Theme";
|
import ThemeProvider from "../components/Theme";
|
||||||
import { TimezoneProvider } from "../components/Timezone";
|
import { TimezoneProvider } from "../components/Timezone";
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ export const Decorator = storyFn => (
|
||||||
<DateProvider value={+new Date("2018-08-07T14:30:44+00:00")}>
|
<DateProvider value={+new Date("2018-08-07T14:30:44+00:00")}>
|
||||||
<TimezoneProvider value="America/New_York">
|
<TimezoneProvider value="America/New_York">
|
||||||
<ThemeProvider isDefaultDark={false}>
|
<ThemeProvider isDefaultDark={false}>
|
||||||
<MessageManager>
|
<MessageManagerProvider>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: 24
|
padding: 24
|
||||||
|
@ -26,7 +26,7 @@ export const Decorator = storyFn => (
|
||||||
>
|
>
|
||||||
{storyFn()}
|
{storyFn()}
|
||||||
</div>
|
</div>
|
||||||
</MessageManager>
|
</MessageManagerProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</TimezoneProvider>
|
</TimezoneProvider>
|
||||||
</DateProvider>
|
</DateProvider>
|
||||||
|
|
|
@ -2612,6 +2612,118 @@ exports[`Storyshots Generics / Global messages default 1`] = `
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Storyshots Generics / Global messages with action 1`] = `
|
||||||
|
<div
|
||||||
|
style="padding:24px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
||||||
|
style="margin:auto;overflow:visible;position:relative;width:400px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCardContent-root-id"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
|
||||||
|
style="display:block;margin:auto"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label-id"
|
||||||
|
>
|
||||||
|
Push message
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Storyshots Generics / Global messages with error status 1`] = `
|
||||||
|
<div
|
||||||
|
style="padding:24px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
||||||
|
style="margin:auto;overflow:visible;position:relative;width:400px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCardContent-root-id"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
|
||||||
|
style="display:block;margin:auto"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label-id"
|
||||||
|
>
|
||||||
|
Push message
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Storyshots Generics / Global messages with expandText 1`] = `
|
||||||
|
<div
|
||||||
|
style="padding:24px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
||||||
|
style="margin:auto;overflow:visible;position:relative;width:400px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCardContent-root-id"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
|
||||||
|
style="display:block;margin:auto"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label-id"
|
||||||
|
>
|
||||||
|
Push message
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Storyshots Generics / Global messages with success status 1`] = `
|
||||||
|
<div
|
||||||
|
style="padding:24px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
||||||
|
style="margin:auto;overflow:visible;position:relative;width:400px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCardContent-root-id"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
|
||||||
|
style="display:block;margin:auto"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label-id"
|
||||||
|
>
|
||||||
|
Push message
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Storyshots Generics / Global messages with undo action 1`] = `
|
exports[`Storyshots Generics / Global messages with undo action 1`] = `
|
||||||
<div
|
<div
|
||||||
style="padding:24px"
|
style="padding:24px"
|
||||||
|
@ -2640,6 +2752,34 @@ exports[`Storyshots Generics / Global messages with undo action 1`] = `
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Storyshots Generics / Global messages with warning status 1`] = `
|
||||||
|
<div
|
||||||
|
style="padding:24px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
||||||
|
style="margin:auto;overflow:visible;position:relative;width:400px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiCardContent-root-id"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
|
||||||
|
style="display:block;margin:auto"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label-id"
|
||||||
|
>
|
||||||
|
Push message
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Storyshots Generics / Link with choices default 1`] = `
|
exports[`Storyshots Generics / Link with choices default 1`] = `
|
||||||
<div
|
<div
|
||||||
style="padding:24px"
|
style="padding:24px"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
|
import { IMessage } from "@saleor/components/messages";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -6,10 +7,18 @@ import React from "react";
|
||||||
import CardDecorator from "../../CardDecorator";
|
import CardDecorator from "../../CardDecorator";
|
||||||
import Decorator from "../../Decorator";
|
import Decorator from "../../Decorator";
|
||||||
|
|
||||||
interface StoryProps {
|
const props = {
|
||||||
undo: boolean;
|
text: "This is message",
|
||||||
}
|
title: "Title"
|
||||||
const Story: React.FC<StoryProps> = ({ undo }) => {
|
};
|
||||||
|
const Story: React.FC<IMessage> = ({
|
||||||
|
actionBtn,
|
||||||
|
expandText,
|
||||||
|
onUndo,
|
||||||
|
status,
|
||||||
|
title,
|
||||||
|
text
|
||||||
|
}) => {
|
||||||
const pushMessage = useNotifier();
|
const pushMessage = useNotifier();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -18,8 +27,12 @@ const Story: React.FC<StoryProps> = ({ undo }) => {
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
pushMessage({
|
pushMessage({
|
||||||
onUndo: undo ? () => undefined : undefined,
|
actionBtn,
|
||||||
text: "This is message"
|
expandText,
|
||||||
|
onUndo: onUndo ? () => undefined : undefined,
|
||||||
|
status,
|
||||||
|
text,
|
||||||
|
title
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
style={{ display: "block", margin: "auto" }}
|
style={{ display: "block", margin: "auto" }}
|
||||||
|
@ -32,5 +45,39 @@ const Story: React.FC<StoryProps> = ({ undo }) => {
|
||||||
storiesOf("Generics / Global messages", module)
|
storiesOf("Generics / Global messages", module)
|
||||||
.addDecorator(CardDecorator)
|
.addDecorator(CardDecorator)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <Story undo={false} />)
|
.add("default", () => <Story {...props} />)
|
||||||
.add("with undo action", () => <Story undo={true} />);
|
.add("with undo action", () => <Story onUndo={() => undefined} {...props} />)
|
||||||
|
.add("with expandText", () => (
|
||||||
|
<Story expandText={"Some expanded text"} {...props} />
|
||||||
|
))
|
||||||
|
.add("with action", () => (
|
||||||
|
<Story
|
||||||
|
actionBtn={{ action: () => undefined, label: "Action" }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
.add("with success status", () => (
|
||||||
|
<Story
|
||||||
|
{...props}
|
||||||
|
actionBtn={{ action: () => undefined, label: "Action" }}
|
||||||
|
status="success"
|
||||||
|
title="Success!"
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
.add("with error status", () => (
|
||||||
|
<Story
|
||||||
|
{...props}
|
||||||
|
actionBtn={{ action: () => undefined, label: "Action" }}
|
||||||
|
expandText={"Some expanded text"}
|
||||||
|
status="error"
|
||||||
|
title="Error"
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
.add("with warning status", () => (
|
||||||
|
<Story
|
||||||
|
{...props}
|
||||||
|
expandText={"Some expanded text"}
|
||||||
|
status="warning"
|
||||||
|
title="Warning"
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
|
@ -343,7 +343,10 @@ export default (colors: IThemeColors): Theme =>
|
||||||
color: colors.font.default
|
color: colors.font.default
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
alignSelf: "baseline"
|
display: "block",
|
||||||
|
paddingBottom: 10,
|
||||||
|
paddingLeft: 0,
|
||||||
|
paddingRight: 45
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
fontSize: 16
|
fontSize: 16
|
||||||
|
@ -353,8 +356,7 @@ export default (colors: IThemeColors): Theme =>
|
||||||
boxShadow:
|
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)",
|
"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,
|
color: colors.font.default,
|
||||||
display: "grid",
|
display: "block",
|
||||||
gridTemplateColumns: "1fr 56px",
|
|
||||||
maxWidth: 480
|
maxWidth: 480
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue