react-themable-hoc
Version:
React higher-order-components that allow for css-in-js-style themes.
83 lines (68 loc) • 3.27 kB
JSX
import { ON_THEME_CHANGE } from './events';
import PropTypes from 'prop-types';
import React from 'react';
import ThemeEvents from './ThemeEvents';
import ThemeManager from './ThemeManager';
import hoistStatics from 'hoist-non-react-statics';
import shallowequal from 'shallowequal';
function getBaseClass(isPure) {
return isPure ? React.PureComponent : React.Component;
}
/**
* Themable HOC to provide themed stylesheets for a component
* @param {*} createStyles Function that takes the current theme and the props passed
* to this component, and returns an object with properties for each set of styles.
* @param {*} options Options for creating the HOC
*/
export default function themed(createStyles, { pure, shouldUpdateStyles, classesPropName = 'classNames' } = {}) {
return WrappedComponent => {
const dependsOnProps = createStyles.length > 1;
const BaseClass = getBaseClass(pure);
class ThemableHOC extends BaseClass {
constructor(props) {
super(props);
this.unsubscribeFromTheme = ThemeEvents.subscribe(ON_THEME_CHANGE, this.onThemeChange.bind(this));
this.state = {
stylesToPass: this.getThemedStyles(ThemeManager.getCurrentTheme())
};
}
componentWillReceiveProps(nextProps) {
if (dependsOnProps) {
// Use shouldUpdateStyles if available.
// If pure, perform shallow equal comparison on the props
const willUpdateStyles = shouldUpdateStyles
? shouldUpdateStyles(this.props, nextProps)
: pure ? shallowequal(nextProps, this.props) : true;
if (willUpdateStyles) {
this.setState({ stylesToPass: this.getThemedStyles(ThemeManager.getCurrentTheme(), nextProps) });
}
}
}
componentWillUnmount() {
if (this.unsubscribeFromTheme) {
this.unsubscribeFromTheme();
}
}
render() {
const { stylesToPass } = this.state;
const extraProps = {
ref: this.props.innerRef,
[classesPropName]: stylesToPass
};
return <WrappedComponent {...this.props} {...extraProps} />;
}
onThemeChange(theme) {
this.setState({ stylesToPass: this.getThemedStyles(theme) });
}
getThemedStyles(theme, props = this.props) {
// Allow users to pass a POJO instead of a function if
// the styles aren't reliant upon the theme or props
const styles = typeof createStyles === 'function' ? createStyles(theme, props) : createStyles;
return ThemeManager.css(styles || {});
}
}
const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Unknown';
ThemableHOC.displayName = `Themed(${componentName})`;
return hoistStatics(ThemableHOC, WrappedComponent);
};
}