d2-ui
Version:
383 lines (326 loc) • 9.75 kB
JSX
import React from 'react';
import StylePropable from './mixins/style-propable';
import Transitions from './styles/transitions';
import ClickAwayable from './mixins/click-awayable';
import FlatButton from './flat-button';
import getMuiTheme from './styles/getMuiTheme';
import ContextPure from './mixins/context-pure';
import StyleResizable from './mixins/style-resizable';
import warning from 'warning';
import deprecated from './utils/deprecatedPropType';
const Snackbar = React.createClass({
propTypes: {
/**
* The label for the action on the snackbar.
*/
action: React.PropTypes.string,
/**
* The number of milliseconds to wait before automatically dismissing.
* If no value is specified the snackbar will dismiss normally.
* If a value is provided the snackbar can still be dismissed normally.
* If a snackbar is dismissed before the timer expires, the timer will be cleared.
*/
autoHideDuration: React.PropTypes.number,
/**
* Override the inline-styles of the body element.
*/
bodyStyle: React.PropTypes.object,
/**
* The css class name of the root element.
*/
className: React.PropTypes.string,
/**
* The message to be displayed.
*/
message: React.PropTypes.node.isRequired,
/**
* Fired when the action button is touchtapped.
*
* @param {object} event Action button event.
*/
onActionTouchTap: React.PropTypes.func,
/**
* Fired when the `Snackbar` is dismissed.
*/
onDismiss: deprecated(React.PropTypes.func,
'Instead, use the open property to control the component.'),
/**
* Fired when the `Snackbar` is requested to be closed by a click outside the `Snackbar`, or after the
* `autoHideDuration` timer expires.
*
* Typically `onRequestClose` is used to set state in the parent component, which is used to control the `Snackbar`
* `open` prop.
*
* The `reason` parameter can optionally be used to control the response to `onRequestClose`,
* for example ignoring `clickaway`.
*
* @param {string} reason Can be:`"timeout"` (`autoHideDuration` expired) or: `"clickaway"`
*/
onRequestClose: React.PropTypes.func.isRequired,
/**
* Fired when the `Snackbar` is shown.
*/
onShow: deprecated(React.PropTypes.func,
'Instead, use the open property to control the component.'),
/**
* Controls whether the `Snackbar` is opened or not.
*/
open: React.PropTypes.bool.isRequired,
/**
* If true, the `Snackbar` will open once mounted.
*/
openOnMount: deprecated(React.PropTypes.bool,
'Instead, use the open property to control the component.'),
/**
* Override the inline-styles of the root element.
*/
style: React.PropTypes.object,
},
contextTypes: {
muiTheme: React.PropTypes.object,
},
//for passing default theme context to children
childContextTypes: {
muiTheme: React.PropTypes.object,
},
mixins: [
StylePropable,
StyleResizable,
ClickAwayable,
ContextPure,
],
statics: {
getRelevantContextKeys(muiTheme) {
const theme = muiTheme.snackbar;
const spacing = muiTheme.rawTheme.spacing;
return {
textColor: theme.textColor,
backgroundColor: theme.backgroundColor,
desktopGutter: spacing.desktopGutter,
desktopSubheaderHeight: spacing.desktopSubheaderHeight,
actionColor: theme.actionColor,
};
},
getChildrenClasses() {
return [
FlatButton,
];
},
},
getInitialState() {
let open = this.props.open;
if (open === null) {
open = this.props.openOnMount;
}
return {
open: open,
message: this.props.message,
action: this.props.action,
muiTheme: this.context.muiTheme || getMuiTheme(),
};
},
getChildContext() {
return {
muiTheme: this.state.muiTheme,
};
},
componentDidMount() {
if (this.state.open) {
this._setAutoHideTimer();
//Only Bind clickaway after transition finishes
this.timerTransitionId = setTimeout(() => {
this._bindClickAway();
}, 400);
}
},
componentWillReceiveProps(nextProps, nextContext) {
const newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme;
this.setState({
muiTheme: newMuiTheme,
});
if (this.state.open && nextProps.open === this.props.open &&
(nextProps.message !== this.props.message || nextProps.action !== this.props.action)) {
this.setState({
open: false,
});
clearTimeout(this.timerOneAtTheTimeId);
this.timerOneAtTheTimeId = setTimeout(() => {
this.setState({
message: nextProps.message,
action: nextProps.action,
open: true,
});
}, 400);
} else {
const open = nextProps.open;
this.setState({
open: open !== null ? open : this.state.open,
message: nextProps.message,
action: nextProps.action,
});
}
},
componentDidUpdate(prevProps, prevState) {
if (prevState.open !== this.state.open) {
if (this.state.open) {
this._setAutoHideTimer();
//Only Bind clickaway after transition finishes
this.timerTransitionId = setTimeout(() => {
this._bindClickAway();
}, 400);
} else {
clearTimeout(this.timerAutoHideId);
this._unbindClickAway();
}
}
},
componentWillUnmount() {
clearTimeout(this.timerAutoHideId);
clearTimeout(this.timerTransitionId);
clearTimeout(this.timerOneAtTheTimeId);
this._unbindClickAway();
},
manuallyBindClickAway: true,
timerAutoHideId: undefined,
timerTransitionId: undefined,
timerOneAtTheTimeId: undefined,
componentClickAway() {
if (this.props.open !== null && this.props.onRequestClose) {
this.props.onRequestClose('clickaway');
} else {
this.setState({open: false});
}
},
getStyles() {
const {
textColor,
backgroundColor,
desktopGutter,
desktopSubheaderHeight,
actionColor,
} = this.constructor.getRelevantContextKeys(this.state.muiTheme);
const isSmall = this.state.deviceSize === this.constructor.Sizes.SMALL;
const styles = {
root: {
position: 'fixed',
left: 0,
display: 'flex',
right: 0,
bottom: 0,
zIndex: this.state.muiTheme.zIndex.snackbar,
visibility: 'hidden',
transform: 'translate3d(0, ' + desktopSubheaderHeight + 'px, 0)',
transition:
Transitions.easeOut('400ms', 'transform') + ',' +
Transitions.easeOut('400ms', 'visibility'),
},
rootWhenOpen: {
visibility: 'visible',
transform: 'translate3d(0, 0, 0)',
},
body: {
backgroundColor: backgroundColor,
padding: '0 ' + desktopGutter + 'px',
height: desktopSubheaderHeight,
lineHeight: desktopSubheaderHeight + 'px',
borderRadius: isSmall ? 0 : 2,
maxWidth: isSmall ? 'inherit' : 568,
minWidth: isSmall ? 'inherit' : 288,
flexGrow: isSmall ? 1 : 0,
margin: 'auto',
},
content: {
fontSize: 14,
color: textColor,
opacity: 0,
transition: Transitions.easeOut('400ms', 'opacity'),
},
contentWhenOpen: {
opacity: 1,
transition: Transitions.easeOut('500ms', 'opacity', '100ms'),
},
action: {
color: actionColor,
float: 'right',
marginTop: 6,
marginRight: -16,
marginLeft: desktopGutter,
backgroundColor: 'transparent',
},
};
return styles;
},
show() {
warning(false, 'show has been deprecated in favor of explicitly setting the open property.');
this.setState({
open: true,
});
if (this.props.onShow) {
this.props.onShow();
}
},
_onDismiss() {
if (this.props.onDismiss) {
this.props.onDismiss();
}
},
dismiss() {
warning(false, 'dismiss has been deprecated in favor of explicitly setting the open property.');
this.setState({
open: false,
}, this._onDismiss);
},
_setAutoHideTimer() {
const autoHideDuration = this.props.autoHideDuration;
if (autoHideDuration > 0) {
clearTimeout(this.timerAutoHideId);
this.timerAutoHideId = setTimeout(() => {
if (this.props.open !== null && this.props.onRequestClose) {
this.props.onRequestClose('timeout');
} else {
this.setState({open: false});
}
}, autoHideDuration);
}
},
render() {
const {
onActionTouchTap,
style,
bodyStyle,
...others,
} = this.props;
const styles = this.getStyles();
const {
open,
action,
message,
} = this.state;
const rootStyles = open ?
this.mergeStyles(styles.root, styles.rootWhenOpen, style) :
this.mergeStyles(styles.root, style);
let actionButton;
if (action) {
actionButton = (
<FlatButton
style={styles.action}
label={action}
onTouchTap={onActionTouchTap}
/>
);
}
const mergedBodyStyle = this.mergeStyles(styles.body, bodyStyle);
const contentStyle = open ? this.mergeStyles(styles.content, styles.contentWhenOpen) : styles.content;
return (
<div {...others} style={rootStyles}>
<div style={mergedBodyStyle}>
<div style={contentStyle}>
<span>{message}</span>
{actionButton}
</div>
</div>
</div>
);
},
});
export default Snackbar;