UNPKG

react-ionicons

Version:

A React SVG ionicon component

222 lines (184 loc) 6.71 kB
// @flow import { Component, createElement } from 'react' import PropTypes from 'prop-types' import type { Theme } from './ThemeProvider' import isTag from '../utils/isTag' import isStyledComponent from '../utils/isStyledComponent' import getComponentName from '../utils/getComponentName' import determineTheme from '../utils/determineTheme' import type { RuleSet, Target } from '../types' import { CHANNEL, CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from './ThemeProvider' export default (constructWithOptions: Function, InlineStyle: Function) => { class BaseStyledNativeComponent extends Component { static target: Target static styledComponentId: string static attrs: Object static inlineStyle: Object root: ?Object attrs = {} state = { theme: null, generatedStyles: undefined, } unsubscribeId: number = -1 unsubscribeFromContext() { if (this.unsubscribeId !== -1) { this.context[CHANNEL_NEXT].unsubscribe(this.unsubscribeId) } } buildExecutionContext(theme: any, props: any) { const { attrs } = this.constructor const context = { ...props, theme } if (attrs === undefined) { return context } this.attrs = Object.keys(attrs).reduce((acc, key) => { const attr = attrs[key] // eslint-disable-next-line no-param-reassign acc[key] = typeof attr === 'function' ? attr(context) : attr return acc }, {}) return { ...context, ...this.attrs } } generateAndInjectStyles(theme: any, props: any) { const { inlineStyle } = this.constructor const executionContext = this.buildExecutionContext(theme, props) return inlineStyle.generateStyleObject(executionContext) } componentWillMount() { // If there is a theme in the context, subscribe to the event emitter. This // is necessary due to pure components blocking context updates, this circumvents // that by updating when an event is emitted const styledContext = this.context[CHANNEL_NEXT] if (styledContext !== undefined) { const { subscribe } = styledContext this.unsubscribeId = subscribe(nextTheme => { // This will be called once immediately const theme = determineTheme(this.props, nextTheme, this.constructor.defaultProps) const generatedStyles = this.generateAndInjectStyles(theme, this.props) this.setState({ theme, generatedStyles }) }) } else { // eslint-disable-next-line react/prop-types const theme = this.props.theme || {} const generatedStyles = this.generateAndInjectStyles( theme, this.props, ) this.setState({ theme, generatedStyles }) } } componentWillReceiveProps(nextProps: { theme?: Theme, [key: string]: any }) { this.setState((oldState) => { const theme = determineTheme(nextProps, oldState.theme, this.constructor.defaultProps) const generatedStyles = this.generateAndInjectStyles(theme, nextProps) return { theme, generatedStyles } }) } componentWillUnmount() { this.unsubscribeFromContext() } setNativeProps(nativeProps: Object) { if (this.root !== undefined) { // $FlowFixMe this.root.setNativeProps(nativeProps) } else { const { displayName } = this.constructor // eslint-disable-next-line no-console console.warn( 'setNativeProps was called on a Styled Component wrapping a stateless functional component. ' + 'In this case no ref will be stored, and instead an innerRef prop will be passed on.\n' + `Check whether the stateless functional component is passing on innerRef as a ref in ${displayName}.`, ) } } onRef = (node: any) => { // eslint-disable-next-line react/prop-types const { innerRef } = this.props this.root = node if (typeof innerRef === 'function') { innerRef(node) } } render() { // eslint-disable-next-line react/prop-types const { children, style } = this.props const { generatedStyles } = this.state const { target } = this.constructor const propsForElement = { ...this.attrs, ...this.props, style: [generatedStyles, style], } if ( !isStyledComponent(target) && ( // NOTE: We can't pass a ref to a stateless functional component typeof target !== 'function' || (target.prototype && 'isReactComponent' in target.prototype) ) ) { propsForElement.ref = this.onRef delete propsForElement.innerRef } else { propsForElement.innerRef = this.onRef } return createElement(target, propsForElement, children) } } const createStyledNativeComponent = ( target: Target, options: Object, rules: RuleSet, ) => { const { displayName = isTag(target) ? `styled.${target}` : `Styled(${getComponentName(target)})`, ParentComponent = BaseStyledNativeComponent, rules: extendingRules, attrs, } = options const inlineStyle = new InlineStyle( extendingRules === undefined ? rules : extendingRules.concat(rules), ) class StyledNativeComponent extends ParentComponent { static displayName = displayName static target = target static attrs = attrs static inlineStyle = inlineStyle static contextTypes = { [CHANNEL]: PropTypes.func, [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE, } // NOTE: This is so that isStyledComponent passes for the innerRef unwrapping static styledComponentId = 'StyledNativeComponent' static withComponent(tag) { const { displayName: _, componentId: __, ...optionsToCopy } = options const newOptions = { ...optionsToCopy, ParentComponent: StyledNativeComponent } return createStyledNativeComponent(tag, newOptions, rules) } static get extend() { const { displayName: _, componentId: __, rules: rulesFromOptions, ...optionsToCopy } = options const newRules = rulesFromOptions === undefined ? rules : rulesFromOptions.concat(rules) const newOptions = { ...optionsToCopy, rules: newRules, ParentComponent: StyledNativeComponent, } return constructWithOptions( createStyledNativeComponent, target, newOptions, ) } } return StyledNativeComponent } return createStyledNativeComponent }