UNPKG

material-ui

Version:

Material Design UI components built with React

409 lines (345 loc) 11 kB
const React = require('react'); const ReactDOM = require('react-dom'); const WindowListenable = require('./mixins/window-listenable'); const CssEvent = require('./utils/css-event'); const KeyCode = require('./utils/key-code'); const Transitions = require('./styles/transitions'); const StylePropable = require('./mixins/style-propable'); const FlatButton = require('./flat-button'); const Overlay = require('./overlay'); const Paper = require('./paper'); const DefaultRawTheme = require('./styles/raw-themes/light-raw-theme'); const ThemeManager = require('./styles/theme-manager'); const ReactTransitionGroup = require('react-addons-transition-group'); const TransitionItem = React.createClass({ mixins: [StylePropable], contextTypes: { muiTheme: React.PropTypes.object, }, //for passing default theme context to children childContextTypes: { muiTheme: React.PropTypes.object, }, getChildContext () { return { muiTheme: this.state.muiTheme, }; }, getInitialState() { return { style: {}, 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) { let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({muiTheme: newMuiTheme}); }, componentWillEnter(callback) { let spacing = this.state.muiTheme.rawTheme.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(() => { if (this.isMounted()) callback(); }, 450); // matches transition duration }, render() { let { style, ...other, } = this.props; return <div {...other} style={this.prepareStyles(this.state.style, style)}> {this.props.children} </div>; }, }); let Dialog = React.createClass({ mixins: [WindowListenable, StylePropable], contextTypes: { muiTheme: React.PropTypes.object, }, //for passing default theme context to children childContextTypes: { muiTheme: React.PropTypes.object, }, getChildContext () { return { muiTheme: this.state.muiTheme, }; }, 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, 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) { let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({muiTheme: newMuiTheme}); }, componentDidMount() { this._positionDialog(); if (this.props.openImmediately) { this.refs.dialogOverlay.preventScrolling(); this._onShow(); } }, componentDidUpdate() { this._positionDialog(); }, getStyles() { let spacing = this.state.muiTheme.rawTheme.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.state.muiTheme.rawTheme.palette.textColor, fontSize: 24, lineHeight: '32px', fontWeight: '400', }; if (this.state.open) { main = this.mergeStyles(main, { left: 0, transition: Transitions.easeOut('0ms', 'left', '0ms'), }); } return { main: this.mergeStyles(main, this.props.style), content: this.mergeStyles(content, this.props.contentStyle), paper: { background: this.state.muiTheme.rawTheme.palette.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={this.prepareStyles(styles.title)}>{this.props.title}</h3> : this.props.title; } return ( <div ref="container" style={this.prepareStyles(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={this.prepareStyles(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(ReactDOM.findDOMNode(this), () => { this.refs.dialogOverlay.allowScrolling(); }); 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; } if (actionJSON.id) { props.id = actionJSON.id; } 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={this.prepareStyles(actionStyle)}> {actionObjects} </div> ); } return actionContainer; }, _positionDialog() { if (this.state.open) { let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; let container = ReactDOM.findDOMNode(this); let dialogWindow = ReactDOM.findDOMNode(this.refs.dialogWindow); let dialogContent = ReactDOM.findDOMNode(this.refs.dialogContent); let minPaddingTop = 16; //Reset the height in case the window was resized. dialogWindow.style.height = ''; dialogContent.style.height = ''; let dialogWindowHeight = dialogWindow.offsetHeight; let 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 if (this.props.autoDetectWindowHeight || this.props.autoScrollBodyContent) { let styles = this.getStyles(); let maxDialogContentHeight = clientHeight - 2 * (styles.body.padding + 64); if (this.props.title) maxDialogContentHeight -= dialogContent.previousSibling.offsetHeight; if (this.props.actions.length) maxDialogContentHeight -= dialogContent.nextSibling.offsetHeight; dialogContent.style.maxHeight = maxDialogContentHeight + '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.props.modal) { this.dismiss(); } }, }); module.exports = Dialog;