Skip to content

Commit

Permalink
Merge pull request mui#1668 from oliviertassinari/snackebar
Browse files Browse the repository at this point in the history
[Snackbar] Update for the new material specification
  • Loading branch information
oliviertassinari committed Oct 26, 2015
2 parents f61d24d + aa5dd75 commit efbbb15
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 70 deletions.
23 changes: 22 additions & 1 deletion docs/src/app/components/pages/components/snackbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
}

Expand Down Expand Up @@ -101,6 +103,13 @@ export default class SnackbarPage extends React.Component {
onTouchTap={this._handleClick}
label="Add to my calendar" />

<br />
<br />

<RaisedButton
onTouchTap={this._handleClickDouble}
label="Add to my calendar two times" />

<br />

<TextField
Expand All @@ -110,7 +119,7 @@ export default class SnackbarPage extends React.Component {

<Snackbar
ref="snackbar"
message="Event added to your calendar"
message={this.state.message}
action="undo"
autoHideDuration={this.state.autoHideDuration}
onActionTouchTap={this._handleAction} />
Expand All @@ -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.");
Expand Down
225 changes: 156 additions & 69 deletions src/snackbar.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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() {
Expand All @@ -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) {
Expand All @@ -164,35 +238,48 @@ const Snackbar = React.createClass({
);
}

const contentStyle = open ? this.mergeStyles(styles.content, styles.contentWhenOpen) : styles.content;

return (
<span {...others} style={rootStyles}>
<span>{message}</span>
{actionButton}
</span>
<div {...others} style={rootStyles}>
<div style={styles.body}>
<div style={contentStyle}>
<span>{message}</span>
{actionButton}
</div>
</div>
</div>
);
},

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);
}
},

Expand Down

0 comments on commit efbbb15

Please sign in to comment.