mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 22:01:06 +00:00
@@ -1,115 +1,53 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { css } from 'glamor';
|
||||
|
||||
const propTypes = {
|
||||
intent: PropTypes.string.isRequired,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
children: PropTypes.string,
|
||||
isRemovable: PropTypes.bool,
|
||||
};
|
||||
|
||||
class Alert extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.containerStyle = css({
|
||||
borderRadius: '3px',
|
||||
backgroundColor: '#fff',
|
||||
padding: '10px 16px',
|
||||
display: 'flex',
|
||||
boxSizing: 'border-box',
|
||||
boxShadow: 'rgba(67, 90, 111, 0.3) 0px 0px 1px, rgba(67, 90, 111, 0.47) 0px 8px 10px -4px',
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
});
|
||||
|
||||
this.containerBorderSuccess = css({
|
||||
borderLeft: '3px solid rgb(71, 184, 129)'
|
||||
});
|
||||
|
||||
this.containerBorderWarn = css({
|
||||
borderLeft: '3px solid rgb(217, 130, 43)'
|
||||
});
|
||||
|
||||
this.containerBorderDanger = css({
|
||||
borderLeft: '3px solid rgb(236, 76, 71)'
|
||||
});
|
||||
|
||||
this.containerBorderNotify = css({
|
||||
borderLeft: '3px solid rgb(16, 112, 202)'
|
||||
});
|
||||
|
||||
|
||||
this.toastTextTitle = css({
|
||||
fontWeight: '600',
|
||||
fontSize: '14px',
|
||||
color: '#435a6f',
|
||||
margin: '0'
|
||||
});
|
||||
|
||||
this.toastTextChild = css({
|
||||
fontSize: '14px',
|
||||
color: '#999',
|
||||
margin: '0'
|
||||
});
|
||||
this.toastClose = css({
|
||||
marginLeft: '15px',
|
||||
height: '24px',
|
||||
width: '24px',
|
||||
lineHeight: '22px',
|
||||
fontWeight: '700',
|
||||
textAlign: 'center',
|
||||
fontSize: '20px',
|
||||
color: '#000',
|
||||
cursor: 'pointer',
|
||||
opacity: '0.5',
|
||||
':hover': {
|
||||
opacity: 1
|
||||
}
|
||||
});
|
||||
|
||||
this.toastIcon = css({
|
||||
marginRight: '10px',
|
||||
width: '14px',
|
||||
height: '20px',
|
||||
lineHeight: '20px'
|
||||
});
|
||||
}
|
||||
|
||||
getContainerStyle(intent) {
|
||||
getIconClass(intent) {
|
||||
switch (intent) {
|
||||
case 'success':
|
||||
return { borderStyle: this.containerBorderSuccess, iconColor: css({color: 'rgb(71, 184, 129)'}), iconClass: 'fa fa-check-circle' };
|
||||
return 'fa fa-check-circle';
|
||||
case 'warning':
|
||||
return { borderStyle: this.containerBorderWarn, iconColor: css({color: 'rgb(217, 130, 43)'}) , iconClass: 'fa fa-exclamation-triangle' };
|
||||
return 'fa fa-exclamation-triangle';
|
||||
case 'none':
|
||||
return { borderStyle: this.containerBorderNotify, iconColor: css({color: 'rgb(16, 112, 202)'}), iconClass: 'fa fa-exclamation-circle' };
|
||||
case 'notify-in-progress':
|
||||
return { borderStyle: this.containerBorderNotify, iconColor: css({width: '15px', height: '15px', margin: '3px'}), iconClass: 'loading-icon' };
|
||||
case 'danger':
|
||||
return { borderStyle: this.containerBorderDanger, iconColor: css({color: 'rgb(236, 76, 71)'}), iconClass: 'fa fa-exclamation-circle' };
|
||||
return 'fa fa-exclamation-circle';
|
||||
case 'danger':
|
||||
return 'fa fa-exclamation-circle';
|
||||
default:
|
||||
return 'fa fa-check-circle';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const toastStyle = this.getContainerStyle(this.props.intent);
|
||||
const { intent, title, children, isRemovable, onRemove } = this.props;
|
||||
const iconClass = this.getIconClass(intent);
|
||||
return (
|
||||
<div {...css(toastStyle.borderStyle, this.containerStyle)}>
|
||||
<div className={this.toastIcon} >
|
||||
<i className={toastStyle.iconClass} {...toastStyle.iconColor}/>
|
||||
<div className={`seahub-toast-alert-container ${intent || 'success'}`}>
|
||||
<div className="toast-alert-icon">
|
||||
<i className={iconClass} />
|
||||
</div>
|
||||
<div className={this.toastTextContainer}>
|
||||
<p className={this.toastTextTitle}>{this.props.title}</p>
|
||||
{this.props.children ? <p className={this.toastTextChild}>{this.props.children}</p> : null}
|
||||
</div>
|
||||
<div onClick={this.props.onRemove} className={this.toastClose}>
|
||||
<span>×</span>
|
||||
<div className="toast-text-container">
|
||||
<p className="toast-text-title">{title}</p>
|
||||
{children ? <p className="toast-text-child">{children}</p> : null}
|
||||
</div>
|
||||
{isRemovable && (
|
||||
<div onClick={onRemove} className="toast-close">
|
||||
<span>×</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Alert.propTypes = {
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
children: PropTypes.any,
|
||||
title: PropTypes.string.isRequired,
|
||||
intent: PropTypes.string.isRequired,
|
||||
};
|
||||
Alert.propTypes = propTypes;
|
||||
|
||||
export default Alert;
|
||||
|
@@ -1,52 +1,10 @@
|
||||
import React from 'react';
|
||||
import { css } from 'glamor';
|
||||
import PropTypes from 'prop-types';
|
||||
import Transition from 'react-transition-group/Transition';
|
||||
import Alert from './alert';
|
||||
|
||||
const animationEasing = {
|
||||
deceleration: 'cubic-bezier(0.0, 0.0, 0.2, 1)',
|
||||
acceleration: 'cubic-bezier(0.4, 0.0, 1, 1)',
|
||||
spring: 'cubic-bezier(0.175, 0.885, 0.320, 1.175)'
|
||||
};
|
||||
|
||||
const ANIMATION_DURATION = 240;
|
||||
|
||||
const openAnimation = css.keyframes('openAnimation', {
|
||||
from: {
|
||||
opacity: 0,
|
||||
transform: 'translateY(-120%)'
|
||||
},
|
||||
to: {
|
||||
transform: 'translateY(0)'
|
||||
}
|
||||
});
|
||||
|
||||
const closeAnimation = css.keyframes('closeAnimation', {
|
||||
from: {
|
||||
transform: 'scale(1)',
|
||||
opacity: 1
|
||||
},
|
||||
to: {
|
||||
transform: 'scale(0.9)',
|
||||
opacity: 0
|
||||
}
|
||||
});
|
||||
|
||||
const animationStyles = css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
height: 0,
|
||||
transition: `all ${ANIMATION_DURATION}ms ${animationEasing.deceleration}`,
|
||||
'&[data-state="entering"], &[data-state="entered"]': {
|
||||
animation: `${openAnimation} ${ANIMATION_DURATION}ms ${animationEasing.spring} both`
|
||||
},
|
||||
'&[data-state="exiting"]': {
|
||||
animation: `${closeAnimation} 120ms ${animationEasing.acceleration} both`
|
||||
}
|
||||
});
|
||||
|
||||
export default class Toast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
/**
|
||||
@@ -67,7 +25,7 @@ export default class Toast extends React.PureComponent {
|
||||
/**
|
||||
* The type of the alert.
|
||||
*/
|
||||
intent: PropTypes.oneOf(['none', 'notify-in-progress', 'success', 'warning', 'danger']).isRequired,
|
||||
intent: PropTypes.oneOf(['none', 'success', 'warning', 'danger']).isRequired,
|
||||
|
||||
/**
|
||||
* The title of the alert.
|
||||
@@ -116,7 +74,11 @@ export default class Toast extends React.PureComponent {
|
||||
this.clearCloseTimer();
|
||||
}
|
||||
|
||||
close = () => {
|
||||
close = (event) => {
|
||||
if (event) {
|
||||
event.nativeEvent.stopImmediatePropagation();
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.clearCloseTimer();
|
||||
this.setState({
|
||||
isShown: false
|
||||
@@ -168,7 +130,7 @@ export default class Toast extends React.PureComponent {
|
||||
{state => (
|
||||
<div
|
||||
data-state={state}
|
||||
className={animationStyles}
|
||||
className={`seahub-toast-container ${state}`}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
style={{
|
||||
@@ -181,9 +143,9 @@ export default class Toast extends React.PureComponent {
|
||||
<Alert
|
||||
intent={this.props.intent}
|
||||
title={this.props.title}
|
||||
children={this.props.children}
|
||||
isRemoveable={this.props.hasCloseButton}
|
||||
onRemove={() => this.close()}
|
||||
children={this.props.children || ''}
|
||||
isRemovable={this.props.hasCloseButton}
|
||||
onRemove={(event) => this.close(event)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,20 +1,7 @@
|
||||
import React from 'react';
|
||||
import { css } from 'glamor';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Toast from './toast';
|
||||
|
||||
const wrapperClass = css({
|
||||
maxWidth: 560,
|
||||
margin: '0 auto',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
position: 'fixed',
|
||||
zIndex: 999999,
|
||||
});
|
||||
|
||||
|
||||
const hasCustomId = settings => Object.hasOwnProperty.call(settings, 'id');
|
||||
|
||||
export default class ToastManager extends React.PureComponent {
|
||||
@@ -83,12 +70,22 @@ export default class ToastManager extends React.PureComponent {
|
||||
const uniqueId = ++ToastManager.idCounter;
|
||||
const id = hasCustomId(settings) ? `${settings.id}-${uniqueId}` : uniqueId;
|
||||
|
||||
let hasCloseButton = settings.hasCloseButton || true;
|
||||
let duration = settings.duration || 2;
|
||||
if (settings.hasCloseButton !== undefined) {
|
||||
hasCloseButton = settings.hasCloseButton;
|
||||
}
|
||||
|
||||
if (settings.duration !== undefined) {
|
||||
duration = settings.duration;
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
description: settings.description,
|
||||
hasCloseButton: settings.hasCloseButton || true,
|
||||
duration: settings.duration || 2,
|
||||
hasCloseButton: hasCloseButton,
|
||||
duration: duration,
|
||||
close: () => this.closeToast(id),
|
||||
intent: settings.intent
|
||||
};
|
||||
@@ -124,7 +121,7 @@ export default class ToastManager extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span className={wrapperClass}>
|
||||
<div className="seahub-toast-manager">
|
||||
{this.state.toasts.map(({ id, description, ...props }) => {
|
||||
return (
|
||||
<Toast key={id} onRemove={() => this.removeToast(id)} {...props}>
|
||||
@@ -132,7 +129,7 @@ export default class ToastManager extends React.PureComponent {
|
||||
</Toast>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
116
frontend/src/components/toast/toaster.css
Normal file
116
frontend/src/components/toast/toaster.css
Normal file
@@ -0,0 +1,116 @@
|
||||
.seahub-toast-manager {
|
||||
position: fixed;
|
||||
margin: 0 auto;
|
||||
max-width: 560px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
.seahub-toast-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 0;
|
||||
transition: all 240ms cubic-bezier(0.0, 0.0, 0.2, 1);
|
||||
}
|
||||
|
||||
@keyframes openAnimation {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-120%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes closeAnimation {
|
||||
from {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.seahub-toast-container.entering,
|
||||
.seahub-toast-container.entered {
|
||||
animation: openAnimation 240ms cubic-bezier(0.175, 0.885, 0.320, 1.175) both;
|
||||
}
|
||||
|
||||
.seahub-toast-container.exiting {
|
||||
animation: closeAnimation 120ms cubic-bezier(0.4, 0.0, 1, 1) both;
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
box-shadow: rgba(67, 90, 111, 0.3) 0px 0px 1px, rgba(67, 90, 111, 0.47) 0px 8px 10px -4px;
|
||||
padding: 10px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container.success {
|
||||
border-left: 3px solid rgb(71, 184, 129);
|
||||
color: rgb(71, 184, 129);
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container.warning {
|
||||
border-left: 3px solid rgb(217, 130, 43);
|
||||
color: rgb(217, 130, 43);
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container.none {
|
||||
border-left: 3px solid rgb(16, 112, 202);
|
||||
color: rgb(16, 112, 202);
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container.danger {
|
||||
border-left: 3px solid rgb(236, 76, 71);
|
||||
color: rgb(236, 76, 71);
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container .toast-alert-icon {
|
||||
margin-right: 10px;
|
||||
width: 14px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container .toast-text-title {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #435a6f;
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container .toast-text-child {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container .toast-close {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-left: 15px;
|
||||
line-height: 22px;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
color: #000;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.seahub-toast-alert-container .toast-close:hover {
|
||||
opacity: 1;
|
||||
}
|
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDom from 'react-dom';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ToastManager from './toastManager';
|
||||
|
||||
const isBrowser =
|
||||
typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
||||
import './toaster.css';
|
||||
|
||||
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
||||
|
||||
/**
|
||||
* The Toaster manages the interactionsb between
|
||||
@@ -17,7 +18,7 @@ export default class Toaster {
|
||||
container.setAttribute('data-evergreen-toaster-container', '');
|
||||
document.body.appendChild(container);
|
||||
|
||||
ReactDom.render(
|
||||
ReactDOM.render(
|
||||
<ToastManager
|
||||
bindNotify={this._bindNotify}
|
||||
bindGetToasts={this._bindGetToasts}
|
||||
@@ -51,10 +52,6 @@ export default class Toaster {
|
||||
return this.notifyHandler(title, { ...settings, intent: 'none' });
|
||||
};
|
||||
|
||||
notifyInProgress = (title, settings = {}) => {
|
||||
return this.notifyHandler(title, { ...settings, intent: 'notify-in-progress' });
|
||||
};
|
||||
|
||||
success = (title, settings = {}) => {
|
||||
return this.notifyHandler(title, { ...settings, intent: 'success' });
|
||||
};
|
||||
|
Reference in New Issue
Block a user