UNPKG

ca-ui-react-themer

Version:
265 lines (198 loc) 10.7 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _caUiThemer = require('ca-ui-themer'); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _mapProps = require('recompose/mapProps'); var _mapProps2 = _interopRequireDefault(_mapProps); var _isEmpty = require('lodash/isEmpty'); var _isEmpty2 = _interopRequireDefault(_isEmpty); var _isEqual = require('lodash/isEqual'); var _isEqual2 = _interopRequireDefault(_isEqual); var _utils = require('../utils'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /** * Copyright (c) 2017 CA. All rights reserved. * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ /** * Create variants prop mapper with `recompsoe/mapProps` * @type {HigherOrderComponent} */ /** * Define Higher Order Component type * @type {WithThemeDecorator} */ var applyVariantsDecorator = (0, _mapProps2.default)(_caUiThemer.applyVariantsProps); /** * Build themer attributes based on input component and current theme. * * @param {any} component Input component * @param {Object} theme Current theme * @return {Object} New object with `component` and `themes` array */ var getRawThemerAttrs = function getRawThemerAttrs(component, theme) { if (component && component.rawThemerAttrs && component.rawThemerAttrs.component && component.rawThemerAttrs.themes && Array.isArray(component.rawThemerAttrs.themes)) { return { component: component.rawThemerAttrs.component, themes: [].concat(_toConsumableArray(component.rawThemerAttrs.themes), [theme]) }; } return { component: component, themes: [theme] }; }; /** * Check if component exists. * @param {any} component Component to check * @return {void} Throws an error if component is falsy */ var validateComponent = function validateComponent(component) { if (!component) { throw new Error('ca-ui-react-themer: a component is required'); } }; /** * Create `withTheme` decorator based using the provided Themer instance * @param {Object} themerInstance Themer instance that will be used to resolve themes * @return {Function} `withTheme` decorator */ var createWithTheme = function createWithTheme(themerInstance) { return function (theme) { return function (component) { validateComponent(component); var rawThemerAttrs = getRawThemerAttrs(component, theme); /** * Resolved theme attributes are cached after first mount of this component type. * We assume that all instances of this themed components will share the same theme context. */ var resolvedAttrsCache = void 0; /** * Maintain a record of the current theme to compare future iterations against */ var currentTheme = void 0; /** * Create decorated class component */ var DecoratedClassComponent = function (_Component) { _inherits(DecoratedClassComponent, _Component); function DecoratedClassComponent() { _classCallCheck(this, DecoratedClassComponent); return _possibleConstructorReturn(this, (DecoratedClassComponent.__proto__ || Object.getPrototypeOf(DecoratedClassComponent)).apply(this, arguments)); } _createClass(DecoratedClassComponent, [{ key: 'componentWillMount', /** * Resolve component themes when component mounts for the first time * @return {void} */ /** * Set static attribute `rawThemerAttrs` to allow themes to be extended * @type {Object} */ value: function componentWillMount() { // Check if global theme defines any variables var globalTheme = this.context.theme; currentTheme = globalTheme; // Get global theme ID var globalThemeId = globalTheme && globalTheme.id ? globalTheme.id : undefined; // check if resolved theme attributes are already available // check if theme ID has changed if (resolvedAttrsCache && resolvedAttrsCache.id === globalThemeId) { return; } // Check if global theme defines any variables var globalVars = globalTheme && globalTheme.variables ? globalTheme.variables : undefined; // apply variants decorator var componentWithVariants = applyVariantsDecorator(rawThemerAttrs.component); // Fetch the resolved Component and theme from the themerInstance resolvedAttrsCache = themerInstance.resolveAttributes(componentWithVariants, rawThemerAttrs.themes, globalVars); // cache global theme ID resolvedAttrsCache.id = globalThemeId; } /** * Resolve component themes when component updates and a new theme is present * @return {void} */ /** * Define context types to get global theme object * @type {Object} */ /** * Wrap `displayName` of original component with Themer() * @type {string} */ }, { key: 'componentDidUpdate', value: function componentDidUpdate() { // Check if global theme defines any variables var globalTheme = this.context.theme; // Get global theme ID var globalThemeId = globalTheme && globalTheme.id ? globalTheme.id : undefined; // check if currentTheme associated the the component matches the global. // If not, update the currentTheme if ((0, _isEmpty2.default)(currentTheme) || (0, _isEmpty2.default)(globalTheme) || (0, _isEqual2.default)(currentTheme, globalTheme)) { return; } currentTheme = globalTheme; // Check if global theme defines any variables var globalVars = globalTheme && globalTheme.variables ? globalTheme.variables : undefined; // apply variants decorator var componentWithVariants = applyVariantsDecorator(rawThemerAttrs.component); // Fetch the resolved Component and theme from the themerInstance resolvedAttrsCache = themerInstance.resolveAttributes(componentWithVariants, rawThemerAttrs.themes, globalVars); // cache global theme ID resolvedAttrsCache.id = globalThemeId; } /** * Render themed component * @return {React.Element<*>} */ }, { key: 'render', value: function render() { return _react2.default.createElement(resolvedAttrsCache.snippet, (0, _caUiThemer.mapThemeProps)(this.props, resolvedAttrsCache.theme)); } }]); return DecoratedClassComponent; }(_react.Component); /** * Return decorated component. * * We disable Flow for this line because `react-flow-types` only supports * Higher Order Components that return Functional components (not class components). */ // $FlowFixMe DecoratedClassComponent.displayName = 'Themer(' + (0, _utils.getDisplayName)(rawThemerAttrs.component) + ')'; DecoratedClassComponent.rawThemerAttrs = rawThemerAttrs; DecoratedClassComponent.contextTypes = { theme: _propTypes2.default.object }; return DecoratedClassComponent; }; }; }; /** * Create `withTheme` decorator and assigns themer instance * to `withTheme.themer` attribute for convenience. * * @param {Themer} customThemer custom instance of `Themer` that will be used to resolve themes * @return {Function} `withTheme` decorator with `themer` attribute */ var createDecorator = function createDecorator(customThemer) { var themerInstance = customThemer || _caUiThemer.themer; var withTheme = createWithTheme(themerInstance); withTheme.themer = themerInstance; return withTheme; }; exports.default = createDecorator;