UNPKG

d2-ui

Version:
335 lines (295 loc) 8.71 kB
import React from 'react'; import PureRenderMixin from 'react-addons-pure-render-mixin'; import StylePropable from './mixins/style-propable'; import Colors from './styles/colors'; import Children from './utils/children'; import Events from './utils/events'; import KeyCode from './utils/key-code'; import FocusRipple from './ripples/focus-ripple'; import TouchRipple from './ripples/touch-ripple'; import getMuiTheme from './styles/getMuiTheme'; let styleInjected = false; let listening = false; let tabPressed = false; function injectStyle() { if (!styleInjected) { // Remove inner padding and border in Firefox 4+. let style = document.createElement('style'); style.innerHTML = ` button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } `; document.body.appendChild(style); styleInjected = true; } } function listenForTabPresses() { if (!listening) { Events.on(window, 'keydown', (e) => { tabPressed = e.keyCode === KeyCode.TAB; }); listening = true; } } const EnhancedButton = React.createClass({ propTypes: { centerRipple: React.PropTypes.bool, children: React.PropTypes.node, containerElement: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.element, ]), disableFocusRipple: React.PropTypes.bool, disableKeyboardFocus: React.PropTypes.bool, disableTouchRipple: React.PropTypes.bool, disabled: React.PropTypes.bool, focusRippleColor: React.PropTypes.string, focusRippleOpacity: React.PropTypes.number, keyboardFocused: React.PropTypes.bool, linkButton: React.PropTypes.bool, onBlur: React.PropTypes.func, onFocus: React.PropTypes.func, onKeyDown: React.PropTypes.func, onKeyUp: React.PropTypes.func, onKeyboardFocus: React.PropTypes.func, onTouchTap: React.PropTypes.func, /** * Override the inline-styles of the root element. */ style: React.PropTypes.object, tabIndex: React.PropTypes.number, touchRippleColor: React.PropTypes.string, touchRippleOpacity: React.PropTypes.number, type: React.PropTypes.string, }, contextTypes: { muiTheme: React.PropTypes.object, }, //for passing default theme context to children childContextTypes: { muiTheme: React.PropTypes.object, }, mixins: [PureRenderMixin, StylePropable], getDefaultProps() { return { containerElement: 'button', onBlur: () => {}, onFocus: () => {}, onKeyboardFocus: () => {}, onKeyDown: () => {}, onKeyUp: () => {}, onTouchTap: () => {}, tabIndex: 0, type: 'button', }; }, getInitialState() { return { isKeyboardFocused: !this.props.disabled && this.props.keyboardFocused && !this.props.disableKeyboardFocus, muiTheme: this.context.muiTheme || getMuiTheme(), }; }, getChildContext() { return { muiTheme: this.state.muiTheme, }; }, componentDidMount() { injectStyle(); listenForTabPresses(); }, componentWillReceiveProps(nextProps, nextContext) { let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({muiTheme: newMuiTheme}); if ((nextProps.disabled || nextProps.disableKeyboardFocus) && this.state.isKeyboardFocused) { this.setState({isKeyboardFocused: false}); if (nextProps.onKeyboardFocus) { nextProps.onKeyboardFocus(null, false); } } }, isKeyboardFocused() { return this.state.isKeyboardFocused; }, removeKeyboardFocus(e) { if (this.state.isKeyboardFocused) { this.setState({isKeyboardFocused: false}); this.props.onKeyboardFocus(e, false); } }, setKeyboardFocus(e) { if (!this.state.isKeyboardFocused) { this.setState({isKeyboardFocused: true}); this.props.onKeyboardFocus(e, true); } }, _cancelFocusTimeout() { if (this._focusTimeout) { clearTimeout(this._focusTimeout); this._focusTimeout = null; } }, _createButtonChildren() { const { centerRipple, children, disabled, disableFocusRipple, disableKeyboardFocus, disableTouchRipple, focusRippleColor, focusRippleOpacity, touchRippleColor, touchRippleOpacity, } = this.props; const {isKeyboardFocused} = this.state; //Focus Ripple const focusRipple = isKeyboardFocused && !disabled && !disableFocusRipple && !disableKeyboardFocus ? ( <FocusRipple color={focusRippleColor} muiTheme={this.state.muiTheme} opacity={focusRippleOpacity} show={isKeyboardFocused} /> ) : undefined; //Touch Ripple const touchRipple = !disabled && !disableTouchRipple ? ( <TouchRipple centerRipple={centerRipple} color={touchRippleColor} muiTheme={this.state.muiTheme} opacity={touchRippleOpacity} > {children} </TouchRipple> ) : undefined; return Children.create({ focusRipple, touchRipple, children: touchRipple ? undefined : children, }); }, _handleKeyDown(e) { if (!this.props.disabled && !this.props.disableKeyboardFocus) { if (e.keyCode === KeyCode.ENTER && this.state.isKeyboardFocused) { this._handleTouchTap(e); } } this.props.onKeyDown(e); }, _handleKeyUp(e) { if (!this.props.disabled && !this.props.disableKeyboardFocus) { if (e.keyCode === KeyCode.SPACE && this.state.isKeyboardFocused) { this._handleTouchTap(e); } } this.props.onKeyUp(e); }, _handleBlur(e) { this._cancelFocusTimeout(); this.removeKeyboardFocus(e); this.props.onBlur(e); }, _handleFocus(e) { if (!this.props.disabled && !this.props.disableKeyboardFocus) { //setTimeout is needed because the focus event fires first //Wait so that we can capture if this was a keyboard focus //or touch focus this._focusTimeout = setTimeout(() => { if (tabPressed) { this.setKeyboardFocus(e); } }, 150); this.props.onFocus(e); } }, _handleTouchTap(e) { this._cancelFocusTimeout(); if (!this.props.disabled) { tabPressed = false; this.removeKeyboardFocus(e); this.props.onTouchTap(e); } }, render() { const { centerRipple, children, containerElement, disabled, disableFocusRipple, disableKeyboardFocus, disableTouchRipple, focusRippleColor, focusRippleOpacity, linkButton, touchRippleColor, touchRippleOpacity, onBlur, onFocus, onKeyUp, onKeyDown, onTouchTap, style, tabIndex, type, ...other, } = this.props; const mergedStyles = this.mergeStyles({ border: 10, background: 'none', boxSizing: 'border-box', display: 'inline-block', font: 'inherit', fontFamily: this.state.muiTheme.rawTheme.fontFamily, tapHighlightColor: Colors.transparent, appearance: linkButton ? null : 'button', cursor: disabled ? 'default' : 'pointer', textDecoration: 'none', outline: 'none', /* This is needed so that ripples do not bleed past border radius. See: http://stackoverflow.com/questions/17298739/ css-overflow-hidden-not-working-in-chrome-when-parent-has-border-radius-and-chil */ transform: disableTouchRipple && disableFocusRipple ? null : 'translate3d(0, 0, 0)', verticalAlign: other.hasOwnProperty('href') ? 'middle' : null, }, style); if (disabled && linkButton) { return ( <span {...other} style={mergedStyles} > {children} </span> ); } const buttonProps = { ...other, style: this.prepareStyles(mergedStyles), disabled: disabled, onBlur: this._handleBlur, onFocus: this._handleFocus, onTouchTap: this._handleTouchTap, onKeyUp: this._handleKeyUp, onKeyDown: this._handleKeyDown, tabIndex: tabIndex, type: type, }; const buttonChildren = this._createButtonChildren(); // Provides backward compatibity. Added to support wrapping around <a> element. const targetLinkElement = buttonProps.hasOwnProperty('href') ? 'a' : 'span'; return React.isValidElement(containerElement) ? React.cloneElement(containerElement, buttonProps, buttonChildren) : React.createElement(linkButton ? targetLinkElement : containerElement, buttonProps, buttonChildren); }, }); export default EnhancedButton;