From c0f98ee96083dd7dc5c1987a62ed4381cfbe4378 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Thu, 21 Jan 2016 21:02:24 +0100 Subject: [PATCH 1/5] [Style] Only display the warning once --- src/menus/menu.jsx | 8 ++-- src/refresh-indicator.jsx | 24 +++++------ src/ripples/circle-ripple.jsx | 8 ++-- src/ripples/focus-ripple.jsx | 4 +- src/styles/auto-prefix.js | 53 +++++++++++++++--------- src/transition-groups/scale-in-child.jsx | 8 ++-- src/transition-groups/slide-in-child.jsx | 8 ++-- src/utils/styles.js | 6 +-- 8 files changed, 64 insertions(+), 55 deletions(-) diff --git a/src/menus/menu.jsx b/src/menus/menu.jsx index 6c526480594482..be2cedab151513 100644 --- a/src/menus/menu.jsx +++ b/src/menus/menu.jsx @@ -4,7 +4,7 @@ import update from 'react-addons-update'; import Controllable from '../mixins/controllable'; import StylePropable from '../mixins/style-propable'; import ClickAwayable from '../mixins/click-awayable'; -import AutoPrefix from '../styles/auto-prefix'; +import autoPrefix from '../styles/auto-prefix'; import Transitions from '../styles/transitions'; import KeyCode from '../utils/key-code'; import PropTypes from '../utils/prop-types'; @@ -206,7 +206,7 @@ const Menu = React.createClass({ rootStyle.transition = Transitions.easeOut('250ms', ['opacity', 'transform']); rootStyle.transform = 'translate3d(0,-8px,0)'; rootStyle.opacity = 0; - rootStyle = AutoPrefix.all(rootStyle); + rootStyle = autoPrefix.all(rootStyle); setTimeout(() => { if (this.isMounted()) callback(); }, 250); @@ -240,8 +240,8 @@ const Menu = React.createClass({ let scrollContainerStyle = ReactDOM.findDOMNode(this.refs.scrollContainer).style; let menuContainers = ReactDOM.findDOMNode(this.refs.list).childNodes; - AutoPrefix.set(rootStyle, 'transform', 'scaleX(1)'); - AutoPrefix.set(scrollContainerStyle, 'transform', 'scaleY(1)'); + autoPrefix.set(rootStyle, 'transform', 'scaleX(1)'); + autoPrefix.set(scrollContainerStyle, 'transform', 'scaleY(1)'); scrollContainerStyle.opacity = 1; for (let i = 0; i < menuContainers.length; ++i) { diff --git a/src/refresh-indicator.jsx b/src/refresh-indicator.jsx index 37e6da32a12361..a5d5075cb25e3b 100644 --- a/src/refresh-indicator.jsx +++ b/src/refresh-indicator.jsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import StylePropable from './mixins/style-propable'; -import AutoPrefix from './styles/auto-prefix'; +import autoPrefix from './styles/auto-prefix'; import Transitions from './styles/transitions'; import Paper from './paper'; import DefaultRawTheme from './styles/raw-themes/light-raw-theme'; @@ -309,9 +309,9 @@ const RefreshIndicator = React.createClass({ transitionDuration = '850ms'; } - AutoPrefix.set(path.style, 'strokeDasharray', strokeDasharray); - AutoPrefix.set(path.style, 'strokeDashoffset', strokeDashoffset); - AutoPrefix.set(path.style, 'transitionDuration', transitionDuration); + autoPrefix.set(path.style, 'strokeDasharray', strokeDasharray); + autoPrefix.set(path.style, 'strokeDashoffset', strokeDashoffset); + autoPrefix.set(path.style, 'transitionDuration', transitionDuration); this.scalePathTimer = setTimeout(() => this._scalePath(path, currStep + 1), currStep ? 750 : 250); }, @@ -319,23 +319,19 @@ const RefreshIndicator = React.createClass({ _rotateWrapper(wrapper) { if (this.props.status !== 'loading') return; - AutoPrefix.set(wrapper.style, 'transform', null); - AutoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)'); - AutoPrefix.set(wrapper.style, 'transitionDuration', '0ms'); + autoPrefix.set(wrapper.style, 'transform', null); + autoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)'); + autoPrefix.set(wrapper.style, 'transitionDuration', '0ms'); this.rotateWrapperSecondTimer = setTimeout(() => { - AutoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)'); - AutoPrefix.set(wrapper.style, 'transitionDuration', '10s'); - AutoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear'); + autoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)'); + autoPrefix.set(wrapper.style, 'transitionDuration', '10s'); + autoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear'); }, 50); this.rotateWrapperTimer = setTimeout(() => this._rotateWrapper(wrapper), 10050); }, - prefixed(key) { - return AutoPrefix.single(key); - }, - render() { const rootStyle = this._getRootStyle(); return ( diff --git a/src/ripples/circle-ripple.jsx b/src/ripples/circle-ripple.jsx index dd31979df19a34..d5e57a04f40930 100644 --- a/src/ripples/circle-ripple.jsx +++ b/src/ripples/circle-ripple.jsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import PureRenderMixin from 'react-addons-pure-render-mixin'; import StylePropable from '../mixins/style-propable'; -import AutoPrefix from '../styles/auto-prefix'; +import autoPrefix from '../styles/auto-prefix'; import Transitions from '../styles/transitions'; import Colors from '../styles/colors'; @@ -60,14 +60,14 @@ const CircleRipple = React.createClass({ Transitions.easeOut('2s', 'opacity') + ',' + Transitions.easeOut('1s', 'transform') ); - AutoPrefix.set(style, 'transition', transitionValue); - AutoPrefix.set(style, 'transform', 'scale(1)'); + autoPrefix.set(style, 'transition', transitionValue); + autoPrefix.set(style, 'transform', 'scale(1)'); }, _initializeAnimation(callback) { let style = ReactDOM.findDOMNode(this).style; style.opacity = this.props.opacity; - AutoPrefix.set(style, 'transform', 'scale(0)'); + autoPrefix.set(style, 'transform', 'scale(0)'); setTimeout(() => { if (this.isMounted()) callback(); }, 0); diff --git a/src/ripples/focus-ripple.jsx b/src/ripples/focus-ripple.jsx index 65715702ae8892..7c089206b1e341 100644 --- a/src/ripples/focus-ripple.jsx +++ b/src/ripples/focus-ripple.jsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import PureRenderMixin from 'react-addons-pure-render-mixin'; import StylePropable from '../mixins/style-propable'; -import AutoPrefix from '../styles/auto-prefix'; +import autoPrefix from '../styles/auto-prefix'; import Colors from '../styles/colors'; import Transitions from '../styles/transitions'; import ScaleInTransitionGroup from '../transition-groups/scale-in'; @@ -85,7 +85,7 @@ const FocusRipple = React.createClass({ nextScale = currentScale === startScale ? endScale : startScale; - AutoPrefix.set(innerCircle.style, 'transform', nextScale); + autoPrefix.set(innerCircle.style, 'transform', nextScale); this._timeout = setTimeout(this._pulsate, pulsateDuration); }, diff --git a/src/styles/auto-prefix.js b/src/styles/auto-prefix.js index 09c5b72de7c7e4..d8cd92184fac67 100644 --- a/src/styles/auto-prefix.js +++ b/src/styles/auto-prefix.js @@ -3,28 +3,39 @@ import warning from 'warning'; const prefixers = {}; -export default { +let hasWarnedAboutNavigator = false; - getPrefixer() { - // Server-side renderer needs to supply user agent - if (typeof navigator === 'undefined') { - warning(false, `Material-UI expects the global navigator.userAgent to be defined - for server-side rendering. Set this property when receiving the request headers.`); +function getPrefixer() { + // Server-side renderer needs to supply user agent + if (typeof navigator === 'undefined' && !hasWarnedAboutNavigator) { + warning(false, `Material-UI expects the global navigator.userAgent to be defined + for server-side rendering. Set this property when receiving the request headers.`); - return null; - } + hasWarnedAboutNavigator = true; - const userAgent = navigator.userAgent; + return null; + } - // Get prefixing instance for this user agent - let prefixer = prefixers[userAgent]; - // None found, create a new instance - if (!prefixer) { - prefixer = new InlineStylePrefixer({userAgent: userAgent}); - prefixers[userAgent] = prefixer; - } + const userAgent = navigator.userAgent; + + // Get prefixing instance for this user agent + let prefixer = prefixers[userAgent]; + // None found, create a new instance + if (!prefixer) { + prefixer = new InlineStylePrefixer({ + userAgent: userAgent, + }); + prefixers[userAgent] = prefixer; + } - return prefixer; + return prefixer; +} + +export default { + + getPrefixer() { + warning(false, `getPrefixer() is private to this lib. Do not use it.`); + return getPrefixer(); }, all(style) { @@ -32,7 +43,7 @@ export default { return {}; } - const prefixer = this.getPrefixer(); + const prefixer = getPrefixer(); if (prefixer) { return prefixer.prefix(style); @@ -44,7 +55,7 @@ export default { set(style, key, value) { style[key] = value; - const prefixer = this.getPrefixer(); + const prefixer = getPrefixer(); if (prefixer) { style = prefixer.prefix(style); @@ -54,10 +65,12 @@ export default { }, getPrefix(key) { + warning(false, `getPrefix() is no longer used, it will be removed. Do not use it`); + let style = {}; style[key] = true; - const prefixer = this.getPrefixer(); + const prefixer = getPrefixer(); let prefixes; if (prefixer) { diff --git a/src/transition-groups/scale-in-child.jsx b/src/transition-groups/scale-in-child.jsx index 892b96f8ee994f..eb6ef7bb073242 100644 --- a/src/transition-groups/scale-in-child.jsx +++ b/src/transition-groups/scale-in-child.jsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import PureRenderMixin from 'react-addons-pure-render-mixin'; import StylePropable from '../mixins/style-propable'; -import AutoPrefix from '../styles/auto-prefix'; +import autoPrefix from '../styles/auto-prefix'; import Transitions from '../styles/transitions'; import DefaultRawTheme from '../styles/raw-themes/light-raw-theme'; import ThemeManager from '../styles/theme-manager'; @@ -83,7 +83,7 @@ const ScaleInChild = React.createClass({ let style = ReactDOM.findDOMNode(this).style; style.opacity = '0'; - AutoPrefix.set(style, 'transform', 'scale(' + this.props.minScale + ')'); + autoPrefix.set(style, 'transform', 'scale(' + this.props.minScale + ')'); setTimeout(() => { if (this.isMounted()) callback(); @@ -94,14 +94,14 @@ const ScaleInChild = React.createClass({ let style = ReactDOM.findDOMNode(this).style; style.opacity = '1'; - AutoPrefix.set(style, 'transform', 'scale(' + this.props.maxScale + ')'); + autoPrefix.set(style, 'transform', 'scale(' + this.props.maxScale + ')'); }, _initializeAnimation(callback) { let style = ReactDOM.findDOMNode(this).style; style.opacity = '0'; - AutoPrefix.set(style, 'transform', 'scale(0)'); + autoPrefix.set(style, 'transform', 'scale(0)'); setTimeout(() => { if (this.isMounted()) callback(); diff --git a/src/transition-groups/slide-in-child.jsx b/src/transition-groups/slide-in-child.jsx index 52c75e8d788aaf..7eefba2875e9ad 100644 --- a/src/transition-groups/slide-in-child.jsx +++ b/src/transition-groups/slide-in-child.jsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import StylePropable from '../mixins/style-propable'; -import AutoPrefix from '../styles/auto-prefix'; +import autoPrefix from '../styles/auto-prefix'; import Transitions from '../styles/transitions'; import DefaultRawTheme from '../styles/raw-themes/light-raw-theme'; import ThemeManager from '../styles/theme-manager'; @@ -67,7 +67,7 @@ const SlideInChild = React.createClass({ this.props.direction === 'down' ? '-100%' : '0'; style.opacity = '0'; - AutoPrefix.set(style, 'transform', 'translate3d(' + x + ',' + y + ',0)'); + autoPrefix.set(style, 'transform', 'translate3d(' + x + ',' + y + ',0)'); setTimeout(() => { if (this.isMounted()) callback(); @@ -77,7 +77,7 @@ const SlideInChild = React.createClass({ componentDidEnter() { let style = ReactDOM.findDOMNode(this).style; style.opacity = '1'; - AutoPrefix.set(style, 'transform', 'translate3d(0,0,0)'); + autoPrefix.set(style, 'transform', 'translate3d(0,0,0)'); }, componentWillLeave(callback) { @@ -89,7 +89,7 @@ const SlideInChild = React.createClass({ direction === 'down' ? '100%' : '0'; style.opacity = '0'; - AutoPrefix.set(style, 'transform', 'translate3d(' + x + ',' + y + ',0)'); + autoPrefix.set(style, 'transform', 'translate3d(' + x + ',' + y + ',0)'); setTimeout(() => { if (this.isMounted()) callback(); diff --git a/src/utils/styles.js b/src/utils/styles.js index fa31ffa47951f7..96bd88dcb59a95 100644 --- a/src/utils/styles.js +++ b/src/utils/styles.js @@ -1,4 +1,4 @@ -import AutoPrefix from '../styles/auto-prefix'; +import autoPrefix from '../styles/auto-prefix'; import update from 'react-addons-update'; import warning from 'warning'; @@ -113,7 +113,7 @@ export function mergeStyles(base, ...args) { export function mergeAndPrefix(...args) { warning(false, 'Use of mergeAndPrefix() has been deprecated. ' + 'Please use mergeStyles() for merging styles, and then prepareStyles() for prefixing and ensuring direction.'); - return AutoPrefix.all(mergeStyles(...args)); + return autoPrefix.all(mergeStyles(...args)); } /** @@ -134,7 +134,7 @@ export function prepareStyles(muiTheme, style = {}, ...styles) { } const flipped = ensureDirection(muiTheme, style); - return AutoPrefix.all(flipped); + return autoPrefix.all(flipped); } export default { From 805ee6f5232f8ce3fbaeb357a536e16b27864b87 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 22 Jan 2016 00:18:58 +0100 Subject: [PATCH 2/5] [Style] Move the prefix tool to the muiTheme context --- src/before-after-wrapper.jsx | 10 ++-- src/circular-progress.jsx | 14 ++--- src/enhanced-button.jsx | 2 + src/enhanced-switch.jsx | 2 + src/left-nav.jsx | 4 +- src/menus/menu.jsx | 6 +-- src/mixins/style-propable.js | 4 +- src/refresh-indicator.jsx | 18 +++---- src/ripples/circle-ripple.jsx | 12 +++-- src/ripples/focus-ripple.jsx | 8 ++- src/ripples/touch-ripple.jsx | 7 +++ src/slider.jsx | 1 + src/styles/auto-prefix.js | 65 ++++++++++++++++-------- src/styles/getMuiTheme.js | 13 ++++- src/transition-groups/scale-in-child.jsx | 6 +-- src/transition-groups/slide-in-child.jsx | 6 +-- src/utils/styles.js | 2 +- 17 files changed, 121 insertions(+), 59 deletions(-) diff --git a/src/before-after-wrapper.jsx b/src/before-after-wrapper.jsx index e3fe94b26adcad..29e4945adc1aeb 100644 --- a/src/before-after-wrapper.jsx +++ b/src/before-after-wrapper.jsx @@ -1,6 +1,5 @@ import React from 'react'; import StylePropable from './mixins/style-propable'; -import AutoPrefix from './styles/auto-prefix'; import DefaultRawTheme from './styles/raw-themes/light-raw-theme'; import ThemeManager from './styles/theme-manager'; @@ -105,8 +104,13 @@ const BeforeAfterWrapper = React.createClass({ let beforeElement; let afterElement; - beforeStyle = AutoPrefix.all({boxSizing: 'border-box'}); - afterStyle = AutoPrefix.all({boxSizing: 'border-box'}); + beforeStyle = { + boxSizing: 'border-box', + }; + + afterStyle = { + boxSizing: 'border-box', + }; if (this.props.beforeStyle) beforeElement = React.createElement(this.props.beforeElementType, diff --git a/src/circular-progress.jsx b/src/circular-progress.jsx index 51454d2aa26b47..7b1157b75f791e 100644 --- a/src/circular-progress.jsx +++ b/src/circular-progress.jsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import StylePropable from './mixins/style-propable'; -import AutoPrefix from './styles/auto-prefix'; +import autoPrefix from './styles/auto-prefix'; import Transitions from './styles/transitions'; import DefaultRawTheme from './styles/raw-themes/light-raw-theme'; import ThemeManager from './styles/theme-manager'; @@ -144,13 +144,13 @@ const CircularProgress = React.createClass({ _rotateWrapper(wrapper) { if (this.props.mode !== 'indeterminate') return; - AutoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)'); - AutoPrefix.set(wrapper.style, 'transitionDuration', '0ms'); + autoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)', this.state.muiTheme); + autoPrefix.set(wrapper.style, 'transitionDuration', '0ms', this.state.muiTheme); setTimeout(() => { - AutoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)'); - AutoPrefix.set(wrapper.style, 'transitionDuration', '10s'); - AutoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear'); + autoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)', this.state.muiTheme); + autoPrefix.set(wrapper.style, 'transitionDuration', '10s', this.state.muiTheme); + autoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear', this.state.muiTheme); }, 50); this.rotateWrapperTimer = setTimeout(() => this._rotateWrapper(wrapper), 10050); @@ -197,7 +197,7 @@ const CircularProgress = React.createClass({ }, }; - AutoPrefix.set(styles.wrapper, 'transitionTimingFunction', 'linear'); + autoPrefix.set(styles.wrapper, 'transitionTimingFunction', 'linear', this.state.muiTheme); if (this.props.mode === 'determinate') { let relVal = this._getRelativeValue(); diff --git a/src/enhanced-button.jsx b/src/enhanced-button.jsx index 40e3bc3ef415ce..0f1e3238126ee2 100644 --- a/src/enhanced-button.jsx +++ b/src/enhanced-button.jsx @@ -176,6 +176,7 @@ const EnhancedButton = React.createClass({ const focusRipple = isKeyboardFocused && !disabled && !disableFocusRipple && !disableKeyboardFocus ? ( @@ -186,6 +187,7 @@ const EnhancedButton = React.createClass({ {children} diff --git a/src/enhanced-switch.jsx b/src/enhanced-switch.jsx index 6dda22777e85a0..a30e01eaf89cba 100644 --- a/src/enhanced-switch.jsx +++ b/src/enhanced-switch.jsx @@ -397,6 +397,7 @@ const EnhancedSwitch = React.createClass({ key="touchRipple" style={rippleStyle} color={rippleColor} + muiTheme={this.state.muiTheme} centerRipple={true} /> ); @@ -406,6 +407,7 @@ const EnhancedSwitch = React.createClass({ key="focusRipple" innerStyle={rippleStyle} color={rippleColor} + muiTheme={this.state.muiTheme} show={this.state.isKeyboardFocused} /> ); diff --git a/src/left-nav.jsx b/src/left-nav.jsx index 1e0f63567001a8..7f8955d2556201 100644 --- a/src/left-nav.jsx +++ b/src/left-nav.jsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import KeyCode from './utils/key-code'; import StylePropable from './mixins/style-propable'; -import AutoPrefix from './styles/auto-prefix'; +import autoPrefix from './styles/auto-prefix'; import Transitions from './styles/transitions'; import WindowListenable from './mixins/window-listenable'; import Overlay from './overlay'; @@ -413,7 +413,7 @@ const LeftNav = React.createClass({ const leftNav = ReactDOM.findDOMNode(this.refs.clickAwayableElement); const transformCSS = 'translate3d(' + (this._getTranslateMultiplier() * translateX) + 'px, 0, 0)'; this.refs.overlay.setOpacity(1 - translateX / this._getMaxTranslateX()); - AutoPrefix.set(leftNav.style, 'transform', transformCSS); + autoPrefix.set(leftNav.style, 'transform', transformCSS, this.state.muiTheme); }, _getTranslateX(currentX) { diff --git a/src/menus/menu.jsx b/src/menus/menu.jsx index be2cedab151513..be0fe92ee5b88b 100644 --- a/src/menus/menu.jsx +++ b/src/menus/menu.jsx @@ -206,7 +206,7 @@ const Menu = React.createClass({ rootStyle.transition = Transitions.easeOut('250ms', ['opacity', 'transform']); rootStyle.transform = 'translate3d(0,-8px,0)'; rootStyle.opacity = 0; - rootStyle = autoPrefix.all(rootStyle); + rootStyle = autoPrefix.all(rootStyle, this.state.muiTheme); setTimeout(() => { if (this.isMounted()) callback(); }, 250); @@ -240,8 +240,8 @@ const Menu = React.createClass({ let scrollContainerStyle = ReactDOM.findDOMNode(this.refs.scrollContainer).style; let menuContainers = ReactDOM.findDOMNode(this.refs.list).childNodes; - autoPrefix.set(rootStyle, 'transform', 'scaleX(1)'); - autoPrefix.set(scrollContainerStyle, 'transform', 'scaleY(1)'); + autoPrefix.set(rootStyle, 'transform', 'scaleX(1)', this.state.muiTheme); + autoPrefix.set(scrollContainerStyle, 'transform', 'scaleY(1)', this.state.muiTheme); scrollContainerStyle.opacity = 1; for (let i = 0; i < menuContainers.length; ++i) { diff --git a/src/mixins/style-propable.js b/src/mixins/style-propable.js index 5777800d9189c6..e64ba9ed83cf2d 100644 --- a/src/mixins/style-propable.js +++ b/src/mixins/style-propable.js @@ -22,6 +22,8 @@ export default { mergeAndPrefix, prepareStyles(...args) { - return prepare((this.state && this.state.muiTheme) || this.context.muiTheme, ...args); + return prepare((this.state && this.state.muiTheme) || + this.context.muiTheme || + (this.props && this.props.muiTheme), ...args); }, }; diff --git a/src/refresh-indicator.jsx b/src/refresh-indicator.jsx index a5d5075cb25e3b..dd34b7a090bdc9 100644 --- a/src/refresh-indicator.jsx +++ b/src/refresh-indicator.jsx @@ -309,9 +309,9 @@ const RefreshIndicator = React.createClass({ transitionDuration = '850ms'; } - autoPrefix.set(path.style, 'strokeDasharray', strokeDasharray); - autoPrefix.set(path.style, 'strokeDashoffset', strokeDashoffset); - autoPrefix.set(path.style, 'transitionDuration', transitionDuration); + autoPrefix.set(path.style, 'strokeDasharray', strokeDasharray, this.state.muiTheme); + autoPrefix.set(path.style, 'strokeDashoffset', strokeDashoffset, this.state.muiTheme); + autoPrefix.set(path.style, 'transitionDuration', transitionDuration, this.state.muiTheme); this.scalePathTimer = setTimeout(() => this._scalePath(path, currStep + 1), currStep ? 750 : 250); }, @@ -319,14 +319,14 @@ const RefreshIndicator = React.createClass({ _rotateWrapper(wrapper) { if (this.props.status !== 'loading') return; - autoPrefix.set(wrapper.style, 'transform', null); - autoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)'); - autoPrefix.set(wrapper.style, 'transitionDuration', '0ms'); + autoPrefix.set(wrapper.style, 'transform', null, this.state.muiTheme); + autoPrefix.set(wrapper.style, 'transform', 'rotate(0deg)', this.state.muiTheme); + autoPrefix.set(wrapper.style, 'transitionDuration', '0ms', this.state.muiTheme); this.rotateWrapperSecondTimer = setTimeout(() => { - autoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)'); - autoPrefix.set(wrapper.style, 'transitionDuration', '10s'); - autoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear'); + autoPrefix.set(wrapper.style, 'transform', 'rotate(1800deg)', this.state.muiTheme); + autoPrefix.set(wrapper.style, 'transitionDuration', '10s', this.state.muiTheme); + autoPrefix.set(wrapper.style, 'transitionTimingFunction', 'linear', this.state.muiTheme); }, 50); this.rotateWrapperTimer = setTimeout(() => this._rotateWrapper(wrapper), 10050); diff --git a/src/ripples/circle-ripple.jsx b/src/ripples/circle-ripple.jsx index d5e57a04f40930..ac770720a3e766 100644 --- a/src/ripples/circle-ripple.jsx +++ b/src/ripples/circle-ripple.jsx @@ -10,6 +10,12 @@ const CircleRipple = React.createClass({ propTypes: { color: React.PropTypes.string, + + /** + * The material-ui theme applied to this component. + */ + muiTheme: React.PropTypes.object.isRequired, + opacity: React.PropTypes.number, /** @@ -60,14 +66,14 @@ const CircleRipple = React.createClass({ Transitions.easeOut('2s', 'opacity') + ',' + Transitions.easeOut('1s', 'transform') ); - autoPrefix.set(style, 'transition', transitionValue); - autoPrefix.set(style, 'transform', 'scale(1)'); + autoPrefix.set(style, 'transition', transitionValue, this.props.muiTheme); + autoPrefix.set(style, 'transform', 'scale(1)', this.props.muiTheme); }, _initializeAnimation(callback) { let style = ReactDOM.findDOMNode(this).style; style.opacity = this.props.opacity; - autoPrefix.set(style, 'transform', 'scale(0)'); + autoPrefix.set(style, 'transform', 'scale(0)', this.props.muiTheme); setTimeout(() => { if (this.isMounted()) callback(); }, 0); diff --git a/src/ripples/focus-ripple.jsx b/src/ripples/focus-ripple.jsx index 7c089206b1e341..65989eec0965ad 100644 --- a/src/ripples/focus-ripple.jsx +++ b/src/ripples/focus-ripple.jsx @@ -14,6 +14,12 @@ const FocusRipple = React.createClass({ propTypes: { color: React.PropTypes.string, innerStyle: React.PropTypes.object, + + /** + * The material-ui theme applied to this component. + */ + muiTheme: React.PropTypes.object.isRequired, + opacity: React.PropTypes.number, show: React.PropTypes.bool, @@ -85,7 +91,7 @@ const FocusRipple = React.createClass({ nextScale = currentScale === startScale ? endScale : startScale; - autoPrefix.set(innerCircle.style, 'transform', nextScale); + autoPrefix.set(innerCircle.style, 'transform', nextScale, this.props.muiTheme); this._timeout = setTimeout(this._pulsate, pulsateDuration); }, diff --git a/src/ripples/touch-ripple.jsx b/src/ripples/touch-ripple.jsx index 5bbd31a7ac8eff..449c9474ca3a36 100644 --- a/src/ripples/touch-ripple.jsx +++ b/src/ripples/touch-ripple.jsx @@ -22,6 +22,12 @@ const TouchRipple = React.createClass({ centerRipple: React.PropTypes.bool, children: React.PropTypes.node, color: React.PropTypes.string, + + /** + * The material-ui theme applied to this component. + */ + muiTheme: React.PropTypes.object.isRequired, + opacity: React.PropTypes.number, /** @@ -64,6 +70,7 @@ const TouchRipple = React.createClass({ ripples = push(ripples, ( ); diff --git a/src/styles/auto-prefix.js b/src/styles/auto-prefix.js index d8cd92184fac67..eaf669dde64137 100644 --- a/src/styles/auto-prefix.js +++ b/src/styles/auto-prefix.js @@ -5,19 +5,20 @@ const prefixers = {}; let hasWarnedAboutNavigator = false; -function getPrefixer() { - // Server-side renderer needs to supply user agent - if (typeof navigator === 'undefined' && !hasWarnedAboutNavigator) { - warning(false, `Material-UI expects the global navigator.userAgent to be defined - for server-side rendering. Set this property when receiving the request headers.`); +function getPrefixer(userAgent) { + if (typeof navigator !== 'undefined') { + userAgent = navigator.userAgent; + } + + if (userAgent === null && !hasWarnedAboutNavigator) { + warning(false, `Material-UI: userAgent should be supplied in the muiTheme context + for server-side rendering.`); hasWarnedAboutNavigator = true; return null; } - const userAgent = navigator.userAgent; - // Get prefixing instance for this user agent let prefixer = prefixers[userAgent]; // None found, create a new instance @@ -34,38 +35,60 @@ function getPrefixer() { export default { getPrefixer() { - warning(false, `getPrefixer() is private to this lib. Do not use it.`); + warning(false, `Material UI: getPrefixer() is private to this lib. Do not use it.`); return getPrefixer(); }, - all(style) { + getTransform(userAgent) { + const prefixer = getPrefixer(userAgent); + + if (prefixer) { + return prefixer.prefix; + } else { + return InlineStylePrefixer.prefixAll; + } + }, + + all(style, muiTheme) { if (!style) { return {}; } - const prefixer = getPrefixer(); - - if (prefixer) { - return prefixer.prefix(style); + if (muiTheme) { + return muiTheme.prefix(style); } else { - return InlineStylePrefixer.prefixAll(style); + warning(false, `Material UI: you need to provide the muiTheme to the autoPrefix.all()`); + + const prefixer = getPrefixer(); + + if (prefixer) { + return prefixer.prefix(style); + } else { + return InlineStylePrefixer.prefixAll(style); + } } }, - set(style, key, value) { + set(style, key, value, muiTheme) { style[key] = value; - const prefixer = getPrefixer(); - - if (prefixer) { - style = prefixer.prefix(style); + if (muiTheme) { + style = muiTheme.prefix(style); } else { - style = InlineStylePrefixer.prefixAll(style); + warning(false, `Material UI: you need to provide the muiTheme to the autoPrefix.set()`); + + const prefixer = getPrefixer(); + + if (prefixer) { + style = prefixer.prefix(style); + } else { + style = InlineStylePrefixer.prefixAll(style); + } } }, getPrefix(key) { - warning(false, `getPrefix() is no longer used, it will be removed. Do not use it`); + warning(false, `Material UI: getPrefix() is no longer used, it will be removed. Do not use it`); let style = {}; style[key] = true; diff --git a/src/styles/getMuiTheme.js b/src/styles/getMuiTheme.js index fd2e562e306507..3607e402c8ac28 100644 --- a/src/styles/getMuiTheme.js +++ b/src/styles/getMuiTheme.js @@ -1,6 +1,7 @@ import merge from 'lodash.merge'; import Colors from './colors'; import ColorManipulator from '../utils/color-manipulator'; +import autoPrefix from './auto-prefix'; import lightBaseTheme from './baseThemes/lightBaseTheme'; import zIndex from './zIndex'; @@ -12,10 +13,14 @@ import zIndex from './zIndex'; */ export default function getMuiTheme(baseTheme, muiTheme) { baseTheme = merge({}, lightBaseTheme, baseTheme); - const {palette, spacing} = baseTheme; + const { + palette, + spacing, + } = baseTheme; - return merge({ + muiTheme = merge({ isRtl: false, + userAgent: null, zIndex, baseTheme, rawTheme: baseTheme, // To provide backward compatibility. @@ -224,4 +229,8 @@ export default function getMuiTheme(baseTheme, muiTheme) { borderColor: palette.borderColor, }, }, muiTheme); + + muiTheme.prefix = autoPrefix.getTransform(muiTheme.userAgent); + + return muiTheme; } diff --git a/src/transition-groups/scale-in-child.jsx b/src/transition-groups/scale-in-child.jsx index eb6ef7bb073242..23b88cc5bebc35 100644 --- a/src/transition-groups/scale-in-child.jsx +++ b/src/transition-groups/scale-in-child.jsx @@ -83,7 +83,7 @@ const ScaleInChild = React.createClass({ let style = ReactDOM.findDOMNode(this).style; style.opacity = '0'; - autoPrefix.set(style, 'transform', 'scale(' + this.props.minScale + ')'); + autoPrefix.set(style, 'transform', 'scale(' + this.props.minScale + ')', this.state.muiTheme); setTimeout(() => { if (this.isMounted()) callback(); @@ -94,14 +94,14 @@ const ScaleInChild = React.createClass({ let style = ReactDOM.findDOMNode(this).style; style.opacity = '1'; - autoPrefix.set(style, 'transform', 'scale(' + this.props.maxScale + ')'); + autoPrefix.set(style, 'transform', 'scale(' + this.props.maxScale + ')', this.state.muiTheme); }, _initializeAnimation(callback) { let style = ReactDOM.findDOMNode(this).style; style.opacity = '0'; - autoPrefix.set(style, 'transform', 'scale(0)'); + autoPrefix.set(style, 'transform', 'scale(0)', this.state.muiTheme); setTimeout(() => { if (this.isMounted()) callback(); diff --git a/src/transition-groups/slide-in-child.jsx b/src/transition-groups/slide-in-child.jsx index 7eefba2875e9ad..e9b12c1ed92f80 100644 --- a/src/transition-groups/slide-in-child.jsx +++ b/src/transition-groups/slide-in-child.jsx @@ -67,7 +67,7 @@ const SlideInChild = React.createClass({ this.props.direction === 'down' ? '-100%' : '0'; style.opacity = '0'; - autoPrefix.set(style, 'transform', 'translate3d(' + x + ',' + y + ',0)'); + autoPrefix.set(style, 'transform', 'translate3d(' + x + ',' + y + ',0)', this.state.muiTheme); setTimeout(() => { if (this.isMounted()) callback(); @@ -77,7 +77,7 @@ const SlideInChild = React.createClass({ componentDidEnter() { let style = ReactDOM.findDOMNode(this).style; style.opacity = '1'; - autoPrefix.set(style, 'transform', 'translate3d(0,0,0)'); + autoPrefix.set(style, 'transform', 'translate3d(0,0,0)', this.state.muiTheme); }, componentWillLeave(callback) { @@ -89,7 +89,7 @@ const SlideInChild = React.createClass({ direction === 'down' ? '100%' : '0'; style.opacity = '0'; - autoPrefix.set(style, 'transform', 'translate3d(' + x + ',' + y + ',0)'); + autoPrefix.set(style, 'transform', 'translate3d(' + x + ',' + y + ',0)', this.state.muiTheme); setTimeout(() => { if (this.isMounted()) callback(); diff --git a/src/utils/styles.js b/src/utils/styles.js index 96bd88dcb59a95..fecdc5dc72e1d3 100644 --- a/src/utils/styles.js +++ b/src/utils/styles.js @@ -134,7 +134,7 @@ export function prepareStyles(muiTheme, style = {}, ...styles) { } const flipped = ensureDirection(muiTheme, style); - return autoPrefix.all(flipped); + return muiTheme.prefix(flipped); } export default { From 800a365eb12e9788ffd22ffa46ad3b83f87cb221 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 22 Jan 2016 09:34:01 +0100 Subject: [PATCH 3/5] [Style] Remove the usage of autoprefix.all() --- src/auto-complete.jsx | 2 +- src/menus/menu.jsx | 15 --------------- src/styles/auto-prefix.js | 18 +++++++----------- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/auto-complete.jsx b/src/auto-complete.jsx index 22f08130d574d7..0d6869c5038354 100644 --- a/src/auto-complete.jsx +++ b/src/auto-complete.jsx @@ -440,7 +440,7 @@ const AutoComplete = React.createClass({ useLayerForClickAway={false} onRequestClose={this._close} > - {menu} + {menu} ); diff --git a/src/menus/menu.jsx b/src/menus/menu.jsx index be0fe92ee5b88b..6d19e777cf6752 100644 --- a/src/menus/menu.jsx +++ b/src/menus/menu.jsx @@ -197,21 +197,6 @@ const Menu = React.createClass({ if (this.props.autoWidth) this._setWidth(); }, - componentDidEnter() { - this._animateOpen(); - }, - - componentWillLeave(callback) { - let rootStyle = ReactDOM.findDOMNode(this).style; - rootStyle.transition = Transitions.easeOut('250ms', ['opacity', 'transform']); - rootStyle.transform = 'translate3d(0,-8px,0)'; - rootStyle.opacity = 0; - rootStyle = autoPrefix.all(rootStyle, this.state.muiTheme); - setTimeout(() => { - if (this.isMounted()) callback(); - }, 250); - }, - componentClickAway(e) { if (e.defaultPrevented) return; diff --git a/src/styles/auto-prefix.js b/src/styles/auto-prefix.js index eaf669dde64137..4c18722ba67fdd 100644 --- a/src/styles/auto-prefix.js +++ b/src/styles/auto-prefix.js @@ -49,23 +49,19 @@ export default { } }, - all(style, muiTheme) { + all(style) { if (!style) { return {}; } - if (muiTheme) { - return muiTheme.prefix(style); - } else { - warning(false, `Material UI: you need to provide the muiTheme to the autoPrefix.all()`); + warning(false, `Material UI: all() is no longer used, it will be removed. Do not use it`); - const prefixer = getPrefixer(); + const prefixer = getPrefixer(); - if (prefixer) { - return prefixer.prefix(style); - } else { - return InlineStylePrefixer.prefixAll(style); - } + if (prefixer) { + return prefixer.prefix(style); + } else { + return InlineStylePrefixer.prefixAll(style); } }, From a6c09853e85908dca5625ec4fb51338183cbfaf3 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 22 Jan 2016 09:53:41 +0100 Subject: [PATCH 4/5] [Style] Add option for user agent false and all --- src/styles/auto-prefix.js | 78 +++++++++++++++++++++------------------ src/styles/getMuiTheme.js | 2 +- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/styles/auto-prefix.js b/src/styles/auto-prefix.js index 4c18722ba67fdd..5658a9482b4e8a 100644 --- a/src/styles/auto-prefix.js +++ b/src/styles/auto-prefix.js @@ -3,50 +3,56 @@ import warning from 'warning'; const prefixers = {}; -let hasWarnedAboutNavigator = false; +let hasWarnedAboutUserAgent = false; -function getPrefixer(userAgent) { - if (typeof navigator !== 'undefined') { - userAgent = navigator.userAgent; - } - - if (userAgent === null && !hasWarnedAboutNavigator) { - warning(false, `Material-UI: userAgent should be supplied in the muiTheme context - for server-side rendering.`); +export default { - hasWarnedAboutNavigator = true; + getTransform(userAgent) { + if (userAgent === undefined && typeof navigator !== 'undefined') { + userAgent = navigator.userAgent; + } - return null; - } + if (userAgent === undefined && !hasWarnedAboutUserAgent) { + warning(false, `Material UI: userAgent should be supplied in the muiTheme context + for server-side rendering.`); - // Get prefixing instance for this user agent - let prefixer = prefixers[userAgent]; - // None found, create a new instance - if (!prefixer) { - prefixer = new InlineStylePrefixer({ - userAgent: userAgent, - }); - prefixers[userAgent] = prefixer; - } + hasWarnedAboutUserAgent = true; + } - return prefixer; -} + if (userAgent === false) { // Disabled autoprefixer + return (style) => style; + } else if (userAgent === 'all' || userAgent === undefined) { // Prefix for all user agent + return InlineStylePrefixer.prefixAll; + } else { + const prefixer = new InlineStylePrefixer({ + userAgent: userAgent, + }); -export default { + return prefixer.prefix; + } + }, getPrefixer() { - warning(false, `Material UI: getPrefixer() is private to this lib. Do not use it.`); - return getPrefixer(); - }, + warning(false, `Material UI: getPrefixer() is no longer used. Do not use it.`); - getTransform(userAgent) { - const prefixer = getPrefixer(userAgent); + if (typeof navigator === 'undefined') { + warning(false, `Material UI expects the global navigator.userAgent to be defined + for server-side rendering. Set this property when receiving the request headers.`); - if (prefixer) { - return prefixer.prefix; - } else { - return InlineStylePrefixer.prefixAll; + return null; } + + const userAgent = navigator.userAgent; + + // Get prefixing instance for this user agent + let prefixer = prefixers[userAgent]; + // None found, create a new instance + if (!prefixer) { + prefixer = new InlineStylePrefixer({userAgent: userAgent}); + prefixers[userAgent] = prefixer; + } + + return prefixer; }, all(style) { @@ -56,7 +62,7 @@ export default { warning(false, `Material UI: all() is no longer used, it will be removed. Do not use it`); - const prefixer = getPrefixer(); + const prefixer = this.getPrefixer(); if (prefixer) { return prefixer.prefix(style); @@ -73,7 +79,7 @@ export default { } else { warning(false, `Material UI: you need to provide the muiTheme to the autoPrefix.set()`); - const prefixer = getPrefixer(); + const prefixer = this.getPrefixer(); if (prefixer) { style = prefixer.prefix(style); @@ -89,7 +95,7 @@ export default { let style = {}; style[key] = true; - const prefixer = getPrefixer(); + const prefixer = this.getPrefixer(); let prefixes; if (prefixer) { diff --git a/src/styles/getMuiTheme.js b/src/styles/getMuiTheme.js index 3607e402c8ac28..bc2464543476bc 100644 --- a/src/styles/getMuiTheme.js +++ b/src/styles/getMuiTheme.js @@ -20,7 +20,7 @@ export default function getMuiTheme(baseTheme, muiTheme) { muiTheme = merge({ isRtl: false, - userAgent: null, + userAgent: undefined, zIndex, baseTheme, rawTheme: baseTheme, // To provide backward compatibility. From a10b76b4de4bb0bb54205ae6be7f3495b24e1652 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Fri, 22 Jan 2016 13:07:49 +0100 Subject: [PATCH 5/5] [Docs] Add a Server Rendering section --- docs/src/app/app-routes.jsx | 2 + docs/src/app/components/app-left-nav.jsx | 1 + .../pages/get-started/ServerRendering.jsx | 9 +++ .../pages/get-started/serverRendering.md | 55 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 docs/src/app/components/pages/get-started/ServerRendering.jsx create mode 100644 docs/src/app/components/pages/get-started/serverRendering.md diff --git a/docs/src/app/app-routes.jsx b/docs/src/app/app-routes.jsx index b857c2f31eb0cb..78414e6c73589b 100644 --- a/docs/src/app/app-routes.jsx +++ b/docs/src/app/app-routes.jsx @@ -13,6 +13,7 @@ import Prerequisites from './components/pages/get-started/Prerequisites'; import Installation from './components/pages/get-started/Installation'; import Usage from './components/pages/get-started/Usage'; import Examples from './components/pages/get-started/Examples'; +import ServerRendering from './components/pages/get-started/ServerRendering'; import Colors from './components/pages/customization/colors'; import Themes from './components/pages/customization/themes'; @@ -77,6 +78,7 @@ const AppRoutes = ( + diff --git a/docs/src/app/components/app-left-nav.jsx b/docs/src/app/components/app-left-nav.jsx index d7ccc601277cbd..7ccacc926dad93 100644 --- a/docs/src/app/components/app-left-nav.jsx +++ b/docs/src/app/components/app-left-nav.jsx @@ -93,6 +93,7 @@ const AppLeftNav = React.createClass({ , , , + , ]} /> ( + +); + +export default ServerRendering; diff --git a/docs/src/app/components/pages/get-started/serverRendering.md b/docs/src/app/components/pages/get-started/serverRendering.md new file mode 100644 index 00000000000000..325df363931eca --- /dev/null +++ b/docs/src/app/components/pages/get-started/serverRendering.md @@ -0,0 +1,55 @@ +## Server Rendering + +When using Material UI with server rendering, we must use the same environment for the server and the client. +This has two technical implications. + +### Autoprefixer + +First, Material UI has to use the same user agent for the auto prefixer. +On the client side, the default value is `navigator.userAgent`. +But on the server side, the `navigator` is `undefined`. You need to provide it to Material UI. + +The `userAgent` can take one of the following values: +- a regular user agent like +`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36` +- `'all'` to prefix for all user agents +- `false` to disable the prefixer + +We rely on the [muiTheme](/#/customization/themes) context to spread the user agent to all of our component. +For instance, you can provide it like this: + +```js +import getMuiTheme from 'material-ui/lib/styles/getMuiTheme'; +import themeDecorator from 'material-ui/lib/styles/theme-decorator'; +import colors from 'material-ui/lib/styles/colors'; + +const muiTheme = getMuiTheme({ + palette: { + primary1Color: colors.green500, + primary2Color: colors.green700, + primary3Color: colors.green100, + }, +}, { + avatar: { + borderColor: null, + }, + userAgent: req.headers['user-agent'], +}); + +class Main extends React.Component { + render() { + return ( +
Hello world
+ ); + } +} + +export default themeDecorator(muiTheme)(Main) +``` + +### process.env.NODE_ENV + +You also need to use the same `process.env.NODE_ENV` value for the client side and server side. +Otherwise, the checksums won't match. +In order to make sure our style transformations are only applied once, +we add an additional property to each style when `process.env.NODE_ENV !== 'production'`.