Skip to content

Commit efbbb15

Browse files
Merge pull request mui#1668 from oliviertassinari/snackebar
[Snackbar] Update for the new material specification
2 parents f61d24d + aa5dd75 commit efbbb15

File tree

2 files changed

+178
-70
lines changed

2 files changed

+178
-70
lines changed

docs/src/app/components/pages/components/snackbar.jsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ export default class SnackbarPage extends React.Component {
1010
constructor() {
1111
super();
1212
this._handleClick = this._handleClick.bind(this);
13+
this._handleClickDouble = this._handleClickDouble.bind(this);
1314
this._updateAutoHideDuration = this._updateAutoHideDuration.bind(this);
1415

1516
this.state = {
1617
autoHideDuration: 0,
18+
message: 'Event added to your calendar',
1719
};
1820
}
1921

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

106+
<br />
107+
<br />
108+
109+
<RaisedButton
110+
onTouchTap={this._handleClickDouble}
111+
label="Add to my calendar two times" />
112+
104113
<br />
105114

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

111120
<Snackbar
112121
ref="snackbar"
113-
message="Event added to your calendar"
122+
message={this.state.message}
114123
action="undo"
115124
autoHideDuration={this.state.autoHideDuration}
116125
onActionTouchTap={this._handleAction} />
@@ -123,6 +132,18 @@ export default class SnackbarPage extends React.Component {
123132
this.refs.snackbar.show();
124133
}
125134

135+
_handleClickDouble() {
136+
this.refs.snackbar.show();
137+
138+
const duration = this.state.autoHideDuration / 2 || 2000;
139+
140+
setTimeout(() => {
141+
this.setState({
142+
message: 'Event ' + Math.round(Math.random() * 100) + ' added to your calendar',
143+
});
144+
}, duration);
145+
}
146+
126147
_handleAction() {
127148
//We can add more code here! In this example, we'll just include an alert.
128149
window.alert("We removed the event from your calendar.");

src/snackbar.jsx

Lines changed: 156 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,61 @@
11
const React = require('react');
2-
const ReactDOM = require('react-dom');
3-
const CssEvent = require('./utils/css-event');
42
const StylePropable = require('./mixins/style-propable');
53
const Transitions = require('./styles/transitions');
64
const ClickAwayable = require('./mixins/click-awayable');
75
const FlatButton = require('./flat-button');
86
const DefaultRawTheme = require('./styles/raw-themes/light-raw-theme');
97
const ThemeManager = require('./styles/theme-manager');
8+
const ContextPure = require('./mixins/context-pure');
9+
const StyleResizable = require('./mixins/style-resizable');
1010

1111
const Snackbar = React.createClass({
1212

13-
mixins: [StylePropable, ClickAwayable],
13+
mixins: [
14+
StylePropable,
15+
StyleResizable,
16+
ClickAwayable,
17+
ContextPure,
18+
],
1419

1520
manuallyBindClickAway: true,
1621

1722
// ID of the active timer.
1823
_autoHideTimerId: undefined,
1924

25+
_oneAtTheTimeTimerId: undefined,
26+
2027
contextTypes: {
2128
muiTheme: React.PropTypes.object,
2229
},
2330

31+
getDefaultProps: function() {
32+
return {
33+
openOnMount: false,
34+
};
35+
},
36+
37+
statics: {
38+
getRelevantContextKeys(muiTheme) {
39+
const theme = muiTheme.snackbar;
40+
const spacing = muiTheme.rawTheme.spacing;
41+
42+
return {
43+
textColor: theme.textColor,
44+
backgroundColor: theme.backgroundColor,
45+
desktopGutter: spacing.desktopGutter,
46+
desktopSubheaderHeight: spacing.desktopSubheaderHeight,
47+
actionColor: theme.actionColor,
48+
};
49+
},
50+
getChildrenClasses() {
51+
return [
52+
FlatButton,
53+
];
54+
},
55+
},
56+
2457
propTypes: {
25-
message: React.PropTypes.string.isRequired,
58+
message: React.PropTypes.node.isRequired,
2659
action: React.PropTypes.string,
2760
autoHideDuration: React.PropTypes.number,
2861
onActionTouchTap: React.PropTypes.func,
@@ -44,16 +77,40 @@ const Snackbar = React.createClass({
4477

4578
getInitialState() {
4679
return {
47-
open: this.props.openOnMount || false,
80+
open: this.props.openOnMount,
81+
message: this.props.message,
82+
action: this.props.action,
4883
muiTheme: this.context.muiTheme ? this.context.muiTheme : ThemeManager.getMuiTheme(DefaultRawTheme),
4984
};
5085
},
5186

52-
//to update theme inside state whenever a new theme is passed down
53-
//from the parent / owner using context
54-
componentWillReceiveProps (nextProps, nextContext) {
87+
componentWillReceiveProps(nextProps, nextContext) {
88+
//to update theme inside state whenever a new theme is passed down
89+
//from the parent / owner using context
5590
let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme;
5691
this.setState({muiTheme: newMuiTheme});
92+
93+
if (this.state.open) {
94+
this.setState({
95+
open: false,
96+
});
97+
98+
clearTimeout(this._oneAtTheTimeTimerId);
99+
this._oneAtTheTimeTimerId = setTimeout(() => {
100+
if (this.isMounted()) {
101+
this.setState({
102+
message: nextProps.message,
103+
action: nextProps.action,
104+
open: true,
105+
});
106+
}
107+
}, 400);
108+
} else {
109+
this.setState({
110+
message: nextProps.message,
111+
action: nextProps.action,
112+
});
113+
}
57114
},
58115

59116
componentDidMount() {
@@ -73,86 +130,103 @@ const Snackbar = React.createClass({
73130
this._setAutoHideTimer();
74131

75132
//Only Bind clickaway after transition finishes
76-
CssEvent.onTransitionEnd(ReactDOM.findDOMNode(this), () => {
77-
this._bindClickAway();
78-
});
79-
}
80-
else {
133+
setTimeout(() => {
134+
if (this.isMounted()) {
135+
this._bindClickAway();
136+
}
137+
}, 400);
138+
} else {
139+
clearTimeout(this._autoHideTimerId);
81140
this._unbindClickAway();
82141
}
83142
}
84143
},
85144

86145
componentWillUnmount() {
87-
this._clearAutoHideTimer();
146+
clearTimeout(this._autoHideTimerId);
88147
this._unbindClickAway();
89148
},
90149

91-
getTheme() {
92-
return this.state.muiTheme.snackbar;
93-
},
150+
getStyles() {
151+
const {
152+
textColor,
153+
backgroundColor,
154+
desktopGutter,
155+
desktopSubheaderHeight,
156+
actionColor,
157+
} = this.constructor.getRelevantContextKeys(this.state.muiTheme);
94158

95-
getSpacing() {
96-
return this.state.muiTheme.rawTheme.spacing;
97-
},
159+
const isSmall = this.state.deviceSize === this.constructor.Sizes.SMALL;
98160

99-
getStyles() {
100161
const styles = {
101162
root: {
102-
color: this.getTheme().textColor,
103-
backgroundColor: this.getTheme().backgroundColor,
104-
borderRadius: 2,
105-
padding: '0px ' + this.getSpacing().desktopGutter + 'px',
106-
height: this.getSpacing().desktopSubheaderHeight,
107-
lineHeight: this.getSpacing().desktopSubheaderHeight + 'px',
108-
minWidth: 288,
109-
maxWidth: 568,
110-
111163
position: 'fixed',
112-
zIndex: 10,
113-
bottom: this.getSpacing().desktopGutter,
114-
marginLeft: this.getSpacing().desktopGutter,
115-
116164
left: 0,
117-
opacity: 0,
165+
display: '-webkit-box; display: -webkit-flex; display: flex',
166+
right: 0,
167+
bottom: 0,
168+
zIndex: 10,
118169
visibility: 'hidden',
119-
transform: 'translate3d(0, 20px, 0)',
170+
transform: 'translate3d(0, ' + desktopSubheaderHeight + 'px, 0)',
120171
transition:
121-
Transitions.easeOut('0ms', 'left', '400ms') + ',' +
122-
Transitions.easeOut('400ms', 'opacity') + ',' +
123172
Transitions.easeOut('400ms', 'transform') + ',' +
124173
Transitions.easeOut('400ms', 'visibility'),
125174
},
175+
rootWhenOpen: {
176+
visibility: 'visible',
177+
transform: 'translate3d(0, 0, 0)',
178+
},
179+
body: {
180+
backgroundColor: backgroundColor,
181+
padding: '0 ' + desktopGutter + 'px',
182+
height: desktopSubheaderHeight,
183+
lineHeight: desktopSubheaderHeight + 'px',
184+
borderRadius: isSmall ? 0 : 2,
185+
maxWidth: isSmall ? 'inherit' : 568,
186+
minWidth: isSmall ? 'inherit' : 288,
187+
flexGrow: isSmall ? 1 : 0,
188+
margin: 'auto',
189+
},
190+
content: {
191+
fontSize: 14,
192+
color: textColor,
193+
opacity: 0,
194+
transition: Transitions.easeOut('400ms', 'opacity'),
195+
},
196+
contentWhenOpen: {
197+
opacity: 1,
198+
transition: Transitions.easeOut('500ms', 'opacity', '100ms'),
199+
},
126200
action: {
127-
color: this.getTheme().actionColor,
201+
color: actionColor,
128202
float: 'right',
129203
marginTop: 6,
130204
marginRight: -16,
131-
marginLeft: this.getSpacing().desktopGutter,
205+
marginLeft: desktopGutter,
132206
backgroundColor: 'transparent',
133207
},
134-
rootWhenOpen: {
135-
opacity: 1,
136-
visibility: 'visible',
137-
transform: 'translate3d(0, 0, 0)',
138-
transition:
139-
Transitions.easeOut('0ms', 'left', '0ms') + ',' +
140-
Transitions.easeOut('400ms', 'opacity', '0ms') + ',' +
141-
Transitions.easeOut('400ms', 'transform', '0ms') + ',' +
142-
Transitions.easeOut('400ms', 'visibility', '0ms'),
143-
},
144208
};
145209

146210
return styles;
147211
},
148212

149213
render() {
150-
const {action, message, onActionTouchTap, style, ...others } = this.props;
214+
const {
215+
onActionTouchTap,
216+
style,
217+
...others,
218+
} = this.props;
151219
const styles = this.getStyles();
152220

153-
const rootStyles = this.state.open ?
154-
this.prepareStyles(styles.root, styles.rootWhenOpen, style) :
155-
this.prepareStyles(styles.root, style);
221+
const {
222+
open,
223+
action,
224+
message,
225+
} = this.state;
226+
227+
const rootStyles = open ?
228+
this.mergeStyles(styles.root, styles.rootWhenOpen, style) :
229+
this.mergeStyles(styles.root, style);
156230

157231
let actionButton;
158232
if (action) {
@@ -164,35 +238,48 @@ const Snackbar = React.createClass({
164238
);
165239
}
166240

241+
const contentStyle = open ? this.mergeStyles(styles.content, styles.contentWhenOpen) : styles.content;
242+
167243
return (
168-
<span {...others} style={rootStyles}>
169-
<span>{message}</span>
170-
{actionButton}
171-
</span>
244+
<div {...others} style={rootStyles}>
245+
<div style={styles.body}>
246+
<div style={contentStyle}>
247+
<span>{message}</span>
248+
{actionButton}
249+
</div>
250+
</div>
251+
</div>
172252
);
173253
},
174254

175255
show() {
176-
this.setState({ open: true });
177-
if (this.props.onShow) this.props.onShow();
256+
this.setState({
257+
open: true,
258+
});
259+
260+
if (this.props.onShow) {
261+
this.props.onShow();
262+
}
178263
},
179264

180265
dismiss() {
181-
this._clearAutoHideTimer();
182-
this.setState({ open: false });
183-
if (this.props.onDismiss) this.props.onDismiss();
184-
},
266+
this.setState({
267+
open: false,
268+
});
185269

186-
_clearAutoHideTimer() {
187-
if (this._autoHideTimerId !== undefined) {
188-
this._autoHideTimerId = clearTimeout(this._autoHideTimerId);
270+
if (this.props.onDismiss) {
271+
this.props.onDismiss();
189272
}
190273
},
191274

192275
_setAutoHideTimer() {
193276
if (this.props.autoHideDuration > 0) {
194-
this._clearAutoHideTimer();
195-
this._autoHideTimerId = setTimeout(() => { this.dismiss(); }, this.props.autoHideDuration);
277+
clearTimeout(this._autoHideTimerId);
278+
this._autoHideTimerId = setTimeout(() => {
279+
if (this.isMounted()) {
280+
this.dismiss();
281+
}
282+
}, this.props.autoHideDuration);
196283
}
197284
},
198285

0 commit comments

Comments
 (0)