diff --git a/docs/src/app/components/pages/components/snackbar.jsx b/docs/src/app/components/pages/components/snackbar.jsx index d92c56227acdac..ece71d615ce48d 100644 --- a/docs/src/app/components/pages/components/snackbar.jsx +++ b/docs/src/app/components/pages/components/snackbar.jsx @@ -10,10 +10,12 @@ export default class SnackbarPage extends React.Component { constructor() { super(); this._handleClick = this._handleClick.bind(this); + this._handleClickDouble = this._handleClickDouble.bind(this); this._updateAutoHideDuration = this._updateAutoHideDuration.bind(this); this.state = { autoHideDuration: 0, + message: 'Event added to your calendar', }; } @@ -101,6 +103,13 @@ export default class SnackbarPage extends React.Component { onTouchTap={this._handleClick} label="Add to my calendar" /> +
+
+ + +
@@ -123,6 +132,18 @@ export default class SnackbarPage extends React.Component { this.refs.snackbar.show(); } + _handleClickDouble() { + this.refs.snackbar.show(); + + const duration = this.state.autoHideDuration / 2 || 2000; + + setTimeout(() => { + this.setState({ + message: 'Event ' + Math.round(Math.random() * 100) + ' added to your calendar', + }); + }, duration); + } + _handleAction() { //We can add more code here! In this example, we'll just include an alert. window.alert("We removed the event from your calendar."); diff --git a/src/snackbar.jsx b/src/snackbar.jsx index 36ad17d4fd00dd..2d1d81ab25640c 100644 --- a/src/snackbar.jsx +++ b/src/snackbar.jsx @@ -1,28 +1,61 @@ const React = require('react'); -const ReactDOM = require('react-dom'); -const CssEvent = require('./utils/css-event'); const StylePropable = require('./mixins/style-propable'); const Transitions = require('./styles/transitions'); const ClickAwayable = require('./mixins/click-awayable'); const FlatButton = require('./flat-button'); const DefaultRawTheme = require('./styles/raw-themes/light-raw-theme'); const ThemeManager = require('./styles/theme-manager'); +const ContextPure = require('./mixins/context-pure'); +const StyleResizable = require('./mixins/style-resizable'); const Snackbar = React.createClass({ - mixins: [StylePropable, ClickAwayable], + mixins: [ + StylePropable, + StyleResizable, + ClickAwayable, + ContextPure, + ], manuallyBindClickAway: true, // ID of the active timer. _autoHideTimerId: undefined, + _oneAtTheTimeTimerId: undefined, + contextTypes: { muiTheme: React.PropTypes.object, }, + getDefaultProps: function() { + return { + openOnMount: false, + }; + }, + + 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, + ]; + }, + }, + propTypes: { - message: React.PropTypes.string.isRequired, + message: React.PropTypes.node.isRequired, action: React.PropTypes.string, autoHideDuration: React.PropTypes.number, onActionTouchTap: React.PropTypes.func, @@ -44,16 +77,40 @@ const Snackbar = React.createClass({ getInitialState() { return { - open: this.props.openOnMount || false, + open: this.props.openOnMount, + message: this.props.message, + action: this.props.action, muiTheme: this.context.muiTheme ? this.context.muiTheme : ThemeManager.getMuiTheme(DefaultRawTheme), }; }, - //to update theme inside state whenever a new theme is passed down - //from the parent / owner using context - componentWillReceiveProps (nextProps, nextContext) { + componentWillReceiveProps(nextProps, nextContext) { + //to update theme inside state whenever a new theme is passed down + //from the parent / owner using context let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({muiTheme: newMuiTheme}); + + if (this.state.open) { + this.setState({ + open: false, + }); + + clearTimeout(this._oneAtTheTimeTimerId); + this._oneAtTheTimeTimerId = setTimeout(() => { + if (this.isMounted()) { + this.setState({ + message: nextProps.message, + action: nextProps.action, + open: true, + }); + } + }, 400); + } else { + this.setState({ + message: nextProps.message, + action: nextProps.action, + }); + } }, componentDidMount() { @@ -73,86 +130,103 @@ const Snackbar = React.createClass({ this._setAutoHideTimer(); //Only Bind clickaway after transition finishes - CssEvent.onTransitionEnd(ReactDOM.findDOMNode(this), () => { - this._bindClickAway(); - }); - } - else { + setTimeout(() => { + if (this.isMounted()) { + this._bindClickAway(); + } + }, 400); + } else { + clearTimeout(this._autoHideTimerId); this._unbindClickAway(); } } }, componentWillUnmount() { - this._clearAutoHideTimer(); + clearTimeout(this._autoHideTimerId); this._unbindClickAway(); }, - getTheme() { - return this.state.muiTheme.snackbar; - }, + getStyles() { + const { + textColor, + backgroundColor, + desktopGutter, + desktopSubheaderHeight, + actionColor, + } = this.constructor.getRelevantContextKeys(this.state.muiTheme); - getSpacing() { - return this.state.muiTheme.rawTheme.spacing; - }, + const isSmall = this.state.deviceSize === this.constructor.Sizes.SMALL; - getStyles() { const styles = { root: { - color: this.getTheme().textColor, - backgroundColor: this.getTheme().backgroundColor, - borderRadius: 2, - padding: '0px ' + this.getSpacing().desktopGutter + 'px', - height: this.getSpacing().desktopSubheaderHeight, - lineHeight: this.getSpacing().desktopSubheaderHeight + 'px', - minWidth: 288, - maxWidth: 568, - position: 'fixed', - zIndex: 10, - bottom: this.getSpacing().desktopGutter, - marginLeft: this.getSpacing().desktopGutter, - left: 0, - opacity: 0, + display: '-webkit-box; display: -webkit-flex; display: flex', + right: 0, + bottom: 0, + zIndex: 10, visibility: 'hidden', - transform: 'translate3d(0, 20px, 0)', + transform: 'translate3d(0, ' + desktopSubheaderHeight + 'px, 0)', transition: - Transitions.easeOut('0ms', 'left', '400ms') + ',' + - Transitions.easeOut('400ms', 'opacity') + ',' + 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: this.getTheme().actionColor, + color: actionColor, float: 'right', marginTop: 6, marginRight: -16, - marginLeft: this.getSpacing().desktopGutter, + marginLeft: desktopGutter, backgroundColor: 'transparent', }, - rootWhenOpen: { - opacity: 1, - visibility: 'visible', - transform: 'translate3d(0, 0, 0)', - transition: - Transitions.easeOut('0ms', 'left', '0ms') + ',' + - Transitions.easeOut('400ms', 'opacity', '0ms') + ',' + - Transitions.easeOut('400ms', 'transform', '0ms') + ',' + - Transitions.easeOut('400ms', 'visibility', '0ms'), - }, }; return styles; }, render() { - const {action, message, onActionTouchTap, style, ...others } = this.props; + const { + onActionTouchTap, + style, + ...others, + } = this.props; const styles = this.getStyles(); - const rootStyles = this.state.open ? - this.prepareStyles(styles.root, styles.rootWhenOpen, style) : - this.prepareStyles(styles.root, style); + 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) { @@ -164,35 +238,48 @@ const Snackbar = React.createClass({ ); } + const contentStyle = open ? this.mergeStyles(styles.content, styles.contentWhenOpen) : styles.content; + return ( - - {message} - {actionButton} - +
+
+
+ {message} + {actionButton} +
+
+
); }, show() { - this.setState({ open: true }); - if (this.props.onShow) this.props.onShow(); + this.setState({ + open: true, + }); + + if (this.props.onShow) { + this.props.onShow(); + } }, dismiss() { - this._clearAutoHideTimer(); - this.setState({ open: false }); - if (this.props.onDismiss) this.props.onDismiss(); - }, + this.setState({ + open: false, + }); - _clearAutoHideTimer() { - if (this._autoHideTimerId !== undefined) { - this._autoHideTimerId = clearTimeout(this._autoHideTimerId); + if (this.props.onDismiss) { + this.props.onDismiss(); } }, _setAutoHideTimer() { if (this.props.autoHideDuration > 0) { - this._clearAutoHideTimer(); - this._autoHideTimerId = setTimeout(() => { this.dismiss(); }, this.props.autoHideDuration); + clearTimeout(this._autoHideTimerId); + this._autoHideTimerId = setTimeout(() => { + if (this.isMounted()) { + this.dismiss(); + } + }, this.props.autoHideDuration); } },