This repository has been archived by the owner on Dec 13, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 208
/
with-theme.js
108 lines (96 loc) Β· 3.13 KB
/
with-theme.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import React, {Component} from 'react'
import {CHANNEL} from './constants'
import {PropTypes} from './react-compat'
function generateWarningMessage(Comp) {
const componentName = Comp.displayName || Comp.name || 'FunctionComponent'
// eslint-disable-next-line max-len
return `glamorous warning: Expected component called "${componentName}" which uses withTheme to be within a ThemeProvider but none was found.`
}
export default function withTheme(
ComponentToTheme,
{noWarn = false, createElement = true} = {},
) {
class ThemedComponent extends Component {
static propTypes = {
theme: PropTypes.object,
}
warned = noWarn
state = {theme: {}}
setTheme = theme => this.setState({theme})
// eslint-disable-next-line complexity
componentWillMount() {
if (!this.context[CHANNEL]) {
if (process.env.NODE_ENV !== 'production' && !this.warned) {
this.warned = true
// eslint-disable-next-line no-console
console.warn(generateWarningMessage(ComponentToTheme))
}
}
const {theme} = this.props
if (this.context[CHANNEL]) {
// if a theme is provided via props,
// it takes precedence over context
this.setTheme(theme ? theme : this.context[CHANNEL].getState())
} else {
this.setTheme(theme || {})
}
}
componentWillReceiveProps(nextProps) {
if (this.props.theme !== nextProps.theme) {
this.setTheme(nextProps.theme)
}
}
componentDidMount() {
if (this.context[CHANNEL] && !this.props.theme) {
// subscribe to future theme changes
this.subscriptionId = this.context[CHANNEL].subscribe(this.setTheme)
}
}
componentWillUnmount() {
// cleanup subscription
this.subscriptionId &&
this.context[CHANNEL].unsubscribe(this.subscriptionId)
}
render() {
if (createElement) {
return <ComponentToTheme {...this.props} {...this.state} />
} else {
// this allows us to effectively use the GlamorousComponent
// as our `render` method without going through lifecycle hooks.
// Also allows us to forward the context in the scenario where
// a user wants to add more context.
// eslint-disable-next-line babel/new-cap
return ComponentToTheme.call(
this,
{...this.props, ...this.state},
this.context,
)
}
}
}
const defaultContextTypes = {
[CHANNEL]: PropTypes.object,
}
let userDefinedContextTypes = null
// configure the contextTypes to be settable by the user,
// however also retaining the glamorous channel.
Object.defineProperty(ThemedComponent, 'contextTypes', {
enumerable: true,
configurable: true,
set(value) {
userDefinedContextTypes = value
},
get() {
// if the user has provided a contextTypes definition,
// merge the default context types with the provided ones.
if (userDefinedContextTypes) {
return {
...defaultContextTypes,
...userDefinedContextTypes,
}
}
return defaultContextTypes
},
})
return ThemedComponent
}