material-ui
Version:
Material Design UI components built with React
386 lines (325 loc) • 10.4 kB
JSX
let React = require('react');
let WindowListenable = require('./mixins/window-listenable');
let CssEvent = require('./utils/css-event');
let DOM = require('./utils/dom');
let KeyCode = require('./utils/key-code');
let Transitions = require('./styles/transitions');
let StylePropable = require('./mixins/style-propable');
let FlatButton = require('./flat-button');
let Overlay = require('./overlay');
let Paper = require('./paper');
let ReactTransitionGroup = React.addons.TransitionGroup;
let TransitionItem = React.createClass({
mixins: [StylePropable],
contextTypes: {
muiTheme: React.PropTypes.object,
},
getInitialState() {
return {
style: {},
};
},
componentWillEnter(callback) {
let spacing = this.context.muiTheme.spacing;
this.setState({
style: {
opacity: 1,
transform: 'translate3d(0, ' + spacing.desktopKeylineIncrement + 'px, 0)',
},
});
setTimeout(callback, 450); // matches transition duration
},
componentWillLeave(callback) {
this.setState({
style: {
opacity: 0,
transform: 'translate3d(0, 0, 0)',
},
});
setTimeout(callback, 450); // matches transition duration
},
render() {
return <div style={this.mergeAndPrefix(this.state.style, this.props.style)}>
{this.props.children}
</div>;
},
});
let Dialog = React.createClass({
mixins: [WindowListenable, StylePropable],
contextTypes: {
muiTheme: React.PropTypes.object,
},
propTypes: {
actions: React.PropTypes.array,
autoDetectWindowHeight: React.PropTypes.bool,
autoScrollBodyContent: React.PropTypes.bool,
bodyStyle: React.PropTypes.object,
contentClassName: React.PropTypes.string,
contentStyle: React.PropTypes.object,
modal: React.PropTypes.bool,
openImmediately: React.PropTypes.bool,
onClickAway: React.PropTypes.func,
onDismiss: React.PropTypes.func,
onShow: React.PropTypes.func,
repositionOnUpdate: React.PropTypes.bool,
title: React.PropTypes.node,
},
windowListeners: {
keyup: '_handleWindowKeyUp',
resize: '_positionDialog',
},
getDefaultProps() {
return {
autoDetectWindowHeight: false,
autoScrollBodyContent: false,
actions: [],
modal: false,
repositionOnUpdate: true,
};
},
getInitialState() {
return {
open: this.props.openImmediately || false,
};
},
componentDidMount() {
this._positionDialog();
if (this.props.openImmediately) {
this.refs.dialogOverlay.preventScrolling();
this._onShow();
}
},
componentDidUpdate() {
this._positionDialog();
},
getStyles() {
let spacing = this.context.muiTheme.spacing;
let main = {
position: 'fixed',
boxSizing: 'border-box',
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
zIndex: 10,
top: 0,
left: -10000,
width: '100%',
height: '100%',
transition: Transitions.easeOut('0ms', 'left', '450ms'),
};
let content = {
boxSizing: 'border-box',
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
transition: Transitions.easeOut(),
position: 'relative',
width: '75%',
maxWidth: spacing.desktopKeylineIncrement * 12,
margin: '0 auto',
zIndex: 10,
};
let body = {
padding: spacing.desktopGutter,
overflowY: this.props.autoScrollBodyContent ? 'auto' : 'hidden',
overflowX: 'hidden',
};
let gutter = spacing.desktopGutter + 'px ';
let title = {
margin: 0,
padding: gutter + gutter + '0 ' + gutter,
color: this.context.muiTheme.palette.textColor,
fontSize: 24,
lineHeight: '32px',
fontWeight: '400',
};
if (this.state.open) {
main = this.mergeAndPrefix(main, {
left: 0,
transition: Transitions.easeOut('0ms', 'left', '0ms'),
});
}
return {
main: this.mergeAndPrefix(main, this.props.style),
content: this.mergeAndPrefix(content, this.props.contentStyle),
paper: {
background: this.context.muiTheme.canvasColor,
},
body: this.mergeStyles(body, this.props.bodyStyle),
title: this.mergeStyles(title, this.props.titleStyle),
};
},
render() {
let styles = this.getStyles();
let actions = this._getActionsContainer(this.props.actions);
let title;
if (this.props.title) {
// If the title is a string, wrap in an h3 tag.
// If not, just use it as a node.
title = Object.prototype.toString.call(this.props.title) === '[object String]' ?
<h3 style={styles.title}>{this.props.title}</h3> :
this.props.title;
}
return (
<div ref="container" style={styles.main}>
<ReactTransitionGroup component="div" ref="dialogWindow">
{this.state.open &&
<TransitionItem
className={this.props.contentClassName}
style={styles.content}>
<Paper
style={styles.paper}
zDepth={4}>
{title}
<div ref="dialogContent" style={styles.body}>
{this.props.children}
</div>
{actions}
</Paper>
</TransitionItem>}
</ReactTransitionGroup>
<Overlay
ref="dialogOverlay"
show={this.state.open}
autoLockScrolling={false}
onTouchTap={this._handleOverlayTouchTap} />
</div>
);
},
isOpen() {
return this.state.open;
},
dismiss() {
CssEvent.onTransitionEnd(this.getDOMNode(), () => {
this.refs.dialogOverlay.allowScrolling();
}.bind(this));
this.setState({ open: false });
this._onDismiss();
},
show() {
this.refs.dialogOverlay.preventScrolling();
this.setState({ open: true });
this._onShow();
},
_getAction(actionJSON, key) {
let styles = {marginRight: 8};
let props = {
key: key,
secondary: true,
onClick: actionJSON.onClick,
onTouchTap: () => {
if (actionJSON.onTouchTap) {
actionJSON.onTouchTap.call(undefined);
}
if (!(actionJSON.onClick || actionJSON.onTouchTap)) {
this.dismiss();
}
},
label: actionJSON.text,
style: styles,
};
if (actionJSON.ref) {
props.ref = actionJSON.ref;
props.keyboardFocused = actionJSON.ref === this.props.actionFocus;
}
return (
<FlatButton
{...props} />
);
},
_getActionsContainer(actions) {
let actionContainer;
let actionObjects = [];
let actionStyle = {
boxSizing: 'border-box',
WebkitTapHighlightColor: 'rgba(0,0,0,0)',
padding: 8,
marginBottom: 8,
width: '100%',
textAlign: 'right',
};
if (actions.length) {
for (let i = 0; i < actions.length; i++) {
let currentAction = actions[i];
//if the current action isn't a react object, create one
if (!React.isValidElement(currentAction)) {
currentAction = this._getAction(currentAction, i);
}
actionObjects.push(currentAction);
}
actionContainer = (
<div style={actionStyle}>
{actionObjects}
</div>
);
}
return actionContainer;
},
_positionDialog() {
if (this.state.open) {
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
let container = this.getDOMNode();
let dialogWindow = this.refs.dialogWindow.getDOMNode();
let dialogContent = this.refs.dialogContent.getDOMNode();
let minPaddingTop = 16;
let dialogWindowHeight;
let paddingTop;
let maxDialogWindowHeight;
//Reset the height in case the window was resized.
dialogWindow.style.height = '';
dialogContent.style.height = '';
dialogWindowHeight = dialogWindow.offsetHeight;
paddingTop = ((clientHeight - dialogWindowHeight) / 2) - 64;
if (paddingTop < minPaddingTop) paddingTop = minPaddingTop;
//Vertically center the dialog window, but make sure it doesn't
//transition to that position.
if (this.props.repositionOnUpdate || !container.style.paddingTop) {
container.style.paddingTop = paddingTop + 'px';
}
// Force a height if the dialog is taller than clientHeight
maxDialogWindowHeight = clientHeight - (2 * paddingTop);
if ((this.props.autoDetectWindowHeight || this.props.autoScrollBodyContent) &&
(dialogWindowHeight > maxDialogWindowHeight)) {
dialogWindow.style.height = maxDialogWindowHeight + 'px';
this._updateContentHeight();
}
}
},
_updateContentHeight() {
if (!this.props.autoScrollBodyContent) return;
let dialogWindow = this.refs.dialogWindow.getDOMNode();
let dialogContent = this.refs.dialogContent.getDOMNode();
let container = this.getDOMNode();
let containerOffset = DOM.getStyleAttributeAsNumber(container, 'paddingTop');
let dialogWindowHeight = dialogWindow.offsetHeight - containerOffset;
let dialogContentHeight = dialogContent.offsetHeight;
// If the content is taller than the window can hold, set the height so the content
// will scroll.
if (dialogContentHeight > dialogWindowHeight) {
let dialogContentPadding = DOM.getStyleAttributeAsNumber(dialogContent, 'paddingTop') +
DOM.getStyleAttributeAsNumber(dialogContent, 'paddingBottom');
let contentHeight = dialogWindowHeight - dialogContentPadding;
if (this.props.title) contentHeight -= dialogContent.previousSibling.offsetHeight;
if (this.props.actions) contentHeight -= dialogContent.nextSibling.offsetHeight;
dialogContent.style.height = contentHeight + 'px';
dialogWindow.style.height = dialogWindowHeight + 'px';
}
},
_onShow() {
if (this.props.onShow) this.props.onShow();
},
_onDismiss() {
if (this.props.onDismiss) this.props.onDismiss();
},
_handleOverlayTouchTap(e) {
if (this.props.modal) {
e.stopPropagation();
}
else {
this.dismiss();
if (this.props.onClickAway) this.props.onClickAway();
}
},
_handleWindowKeyUp(e) {
if (e.keyCode === KeyCode.ESC) {
this.dismiss();
}
},
});
module.exports = Dialog;