diff --git a/package.json b/package.json
index 049db82..6c16c89 100644
--- a/package.json
+++ b/package.json
@@ -44,9 +44,9 @@
"fbjs": "^0.8.12",
"jsdom": "^9.8.3",
"mocha": "^3.3.0",
- "prop-types": "^15.5.9",
- "react": "^15.5.4",
- "react-dom": "^15.5.4",
+ "prop-types": "^15.7.2",
+ "react": "^16.3.0",
+ "react-dom": "^16.3.0",
"rimraf": "^2.6.1",
"sinon": "^1.17.6"
},
@@ -64,7 +64,7 @@
},
"license": "MIT",
"peerDependencies": {
- "react": "^0.14.0 || ^15.0.0-0"
+ "react": "^16.3.0-0"
},
"typings": "./index.d.ts"
}
diff --git a/src/components/ThemeProvider.js b/src/components/ThemeProvider.js
index c6e539c..4192eb2 100644
--- a/src/components/ThemeProvider.js
+++ b/src/components/ThemeProvider.js
@@ -1,30 +1,10 @@
-import { Children, Component } from 'react'
-import PropTypes from 'prop-types'
-import themrShape from '../utils/themr-shape'
-
-export default class ThemeProvider extends Component {
- static propTypes = {
- children: PropTypes.element.isRequired,
- theme: PropTypes.object.isRequired
- }
-
- static defaultProps = {
- theme: {}
- }
-
- static childContextTypes = {
- themr: themrShape.isRequired
- }
-
- getChildContext() {
- return {
- themr: {
- theme: this.props.theme
- }
- }
- }
-
- render() {
- return Children.only(this.props.children)
- }
+import React, { Children } from 'react'
+import { ThemeContext } from './themr'
+
+export default function ThemeProvider(props) {
+ return (
+
+ {Children.only(props.children)}
+
+ )
}
diff --git a/src/components/themr.js b/src/components/themr.js
index e19711f..868d7e5 100644
--- a/src/components/themr.js
+++ b/src/components/themr.js
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
import hoistNonReactStatics from 'hoist-non-react-statics'
import invariant from 'invariant'
+export const ThemeContext = React.createContext({ theme: {} })
+
/**
* @typedef {Object.} TReactCSSThemrTheme
*/
@@ -50,16 +52,56 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
localTheme
}
+ function getNamespacedTheme(props) {
+ const { themeNamespace, theme } = props
+ if (!themeNamespace) return theme
+ if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
+ 'themeNamespace prop should be used only with theme prop.')
+
+ return Object.keys(theme)
+ .filter(key => key.startsWith(themeNamespace))
+ .reduce((result, key) => ({ ...result, [removeNamespace(key, themeNamespace)]: theme[key] }), {})
+
+ }
+
+ function getContextTheme(props) {
+ return props.contextTheme
+ ? props.contextTheme.theme[config.componentName]
+ : {}
+ }
+
+ function getThemeNotComposed(props) {
+ if (props.theme) return getNamespacedTheme(props)
+ if (config.localTheme) return config.localTheme
+ return getContextTheme(props)
+ }
+
+ function getTheme(props) {
+ return props.composeTheme === COMPOSE_SOFTLY
+ ? {
+ ...getContextTheme(props),
+ ...config.localTheme,
+ ...getNamespacedTheme(props)
+ }
+ : themeable(
+ themeable(getContextTheme(props), config.localTheme),
+ getNamespacedTheme(props)
+ )
+ }
+
+ function calcTheme(props) {
+ const { composeTheme } = props
+ return composeTheme
+ ? getTheme(props)
+ : getThemeNotComposed(props)
+ }
+
/**
* @property {{wrappedInstance: *}} refs
*/
class Themed extends Component {
static displayName = `Themed${ThemedComponent.name}`;
- static contextTypes = {
- themr: PropTypes.object
- }
-
static propTypes = {
...ThemedComponent.propTypes,
composeTheme: PropTypes.oneOf([ COMPOSE_DEEPLY, COMPOSE_SOFTLY, DONT_COMPOSE ]),
@@ -75,84 +117,46 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
mapThemrProps: optionMapThemrProps
}
- constructor(...args) {
- super(...args)
- this.theme_ = this.calcTheme(this.props)
- }
-
- getWrappedInstance() {
- invariant(true,
- 'DEPRECATED: To access the wrapped instance, you have to pass ' +
- '{ innerRef: fn } and retrieve with a callback ref style.'
- )
-
- return this.refs.wrappedInstance
- }
-
- getNamespacedTheme(props) {
- const { themeNamespace, theme } = props
- if (!themeNamespace) return theme
- if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
- 'themeNamespace prop should be used only with theme prop.')
-
- return Object.keys(theme)
- .filter(key => key.startsWith(themeNamespace))
- .reduce((result, key) => ({ ...result, [removeNamespace(key, themeNamespace)]: theme[key] }), {})
- }
-
- getThemeNotComposed(props) {
- if (props.theme) return this.getNamespacedTheme(props)
- if (config.localTheme) return config.localTheme
- return this.getContextTheme()
- }
-
- getContextTheme() {
- return this.context.themr
- ? this.context.themr.theme[config.componentName]
- : {}
- }
-
- getTheme(props) {
- return props.composeTheme === COMPOSE_SOFTLY
- ? {
- ...this.getContextTheme(),
- ...config.localTheme,
- ...this.getNamespacedTheme(props)
- }
- : themeable(
- themeable(this.getContextTheme(), config.localTheme),
- this.getNamespacedTheme(props)
- )
- }
+ getWrappedInstance() {
+ invariant(true,
+ 'DEPRECATED: To access the wrapped instance, you have to pass ' +
+ '{ innerRef: fn } and retrieve with a callback ref style.'
+ )
- calcTheme(props) {
- const { composeTheme } = props
- return composeTheme
- ? this.getTheme(props)
- : this.getThemeNotComposed(props)
+ return this.refs.wrappedInstance
}
- componentWillReceiveProps(nextProps) {
+ lastProps = {};
+ calcTheme = (props) => {
if (
- nextProps.composeTheme !== this.props.composeTheme ||
- nextProps.theme !== this.props.theme ||
- nextProps.themeNamespace !== this.props.themeNamespace
+ props.composeTheme !== this.lastProps.composeTheme ||
+ props.theme !== this.lastProps.theme ||
+ props.themeNamespace !== this.lastProps.themeNamespace ||
+ props.contextTheme !== this.lastProps.contextTheme
) {
- this.theme_ = this.calcTheme(nextProps)
+ this.lastProps = props
+ this.theme_ = calcTheme(props)
}
+ return this.theme_
}
render() {
- return React.createElement(
- ThemedComponent,
- this.props.mapThemrProps(this.props, this.theme_)
+ return (
+
+ {contextTheme => (
+
+ )}
+
)
}
}
Themed[THEMR_CONFIG] = config
- return hoistNonReactStatics(Themed, ThemedComponent)
+ return hoistNonReactStatics(
+ Themed,
+ ThemedComponent
+ )
}
/**
diff --git a/test/components/ThemeProvider.spec.js b/test/components/ThemeProvider.spec.js
index 478400e..9ee8fe7 100644
--- a/test/components/ThemeProvider.spec.js
+++ b/test/components/ThemeProvider.spec.js
@@ -1,20 +1,21 @@
import React, { Component } from 'react'
+import { render } from 'react-dom'
import expect from 'expect'
-import PropTypes from 'prop-types'
import TestUtils from 'react-dom/test-utils'
import { ThemeProvider } from '../../src/index'
+import { ThemeContext } from '../../src/components/themr'
describe('ThemeProvider', () => {
class Child extends Component {
render() {
- return
+ return (
+
+ {data => JSON.stringify(data)}
+
+ )
}
}
- Child.contextTypes = {
- themr: PropTypes.object.isRequired
- }
-
it('enforces a single child', () => {
const theme = {}
@@ -46,7 +47,7 @@ describe('ThemeProvider', () => {
})
it('should add the theme to the child context', () => {
- const theme = {}
+ const theme = { foo: 'bar' }
TestUtils.renderIntoDocument(
@@ -55,15 +56,16 @@ describe('ThemeProvider', () => {
)
const spy = expect.spyOn(console, 'error')
- const tree = TestUtils.renderIntoDocument(
+ const node = document.createElement('div')
+ render(
-
+ ,
+ node
)
spy.destroy()
expect(spy.calls.length).toBe(0)
- const child = TestUtils.findRenderedComponentWithType(tree, Child)
- expect(child.context.themr.theme).toBe(theme)
+ expect(JSON.parse(node.innerHTML)).toEqual({ theme })
})
})
diff --git a/test/components/themr.spec.js b/test/components/themr.spec.js
index 430bf01..15a3935 100644
--- a/test/components/themr.spec.js
+++ b/test/components/themr.spec.js
@@ -1,11 +1,12 @@
import expect from 'expect'
-import React, { Children, Component } from 'react'
+import React, { Component } from 'react'
import PropTypes from 'prop-types'
import TestUtils from 'react-dom/test-utils'
import sinon from 'sinon'
import { render } from 'react-dom'
import shallowEqual from 'fbjs/lib/shallowEqual'
import { themr, themeable } from '../../src/index'
+import { ThemeProvider } from '../../src/index'
describe('Themr decorator function', () => {
class Passthrough extends Component {
@@ -15,40 +16,13 @@ describe('Themr decorator function', () => {
}
}
- class ProviderMock extends Component {
- static childContextTypes = {
- themr: PropTypes.object.isRequired
- }
-
- getChildContext() {
- return { themr: { theme: this.props.theme } }
- }
+ class ProviderMock extends Component {
render() {
- return Children.only(this.props.children)
+ return
}
}
- it('passes a context theme object using the component\'s context', () => {
- const theme = { Container: { foo: 'foo_1234' } }
-
- @themr('Container')
- class Container extends Component {
- render() {
- return
- }
- }
-
- const tree = TestUtils.renderIntoDocument(
-
-
-
- )
-
- const container = TestUtils.findRenderedComponentWithType(tree, Container)
- expect(container.context.themr.theme).toBe(theme)
- })
-
it('passes a context theme object using the component\'s theme prop', () => {
const containerTheme = { foo: 'foo_1234' }
const theme = { Container: containerTheme }
diff --git a/yarn.lock b/yarn.lock
index 483df9f..09f0e22 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1384,7 +1384,7 @@ fast-levenshtein@~2.0.4:
version "2.0.5"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.5.tgz#bd33145744519ab1c36c3ee9f31f08e9079b67f2"
-fbjs@^0.8.12, fbjs@^0.8.9:
+fbjs@^0.8.12:
version "0.8.12"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
dependencies:
@@ -1904,6 +1904,11 @@ js-tokens@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
+"js-tokens@^3.0.0 || ^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
js-yaml@^3.5.1:
version "3.7.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
@@ -2066,12 +2071,19 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^2.0.0"
-loose-envify@^1.1.0, loose-envify@^1.3.1:
+loose-envify@^1.1.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
dependencies:
js-tokens "^3.0.0"
+loose-envify@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
micromatch@^2.1.5:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
@@ -2212,6 +2224,11 @@ object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
+object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
object-inspect@^1.1.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.2.1.tgz#3b62226eb8f6d441751c7d8f22a20ff80ac9dc3f"
@@ -2344,12 +2361,14 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
-prop-types@^15.5.7, prop-types@^15.5.9, prop-types@~15.5.7:
- version "15.5.9"
- resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.9.tgz#d478eef0e761396942f70c78e772f76e8be747c9"
+prop-types@^15.6.2, prop-types@^15.7.2:
+ version "15.7.2"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
+ integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
- fbjs "^0.8.9"
- loose-envify "^1.3.1"
+ loose-envify "^1.4.0"
+ object-assign "^4.1.1"
+ react-is "^16.8.1"
punycode@^1.4.1:
version "1.4.1"
@@ -2375,23 +2394,30 @@ rc@~1.1.6:
minimist "^1.2.0"
strip-json-comments "~1.0.4"
-react-dom@^15.5.4:
- version "15.5.4"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.5.4.tgz#ba0c28786fd52ed7e4f2135fe0288d462aef93da"
+react-dom@^16.3.0:
+ version "16.8.6"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f"
+ integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==
dependencies:
- fbjs "^0.8.9"
loose-envify "^1.1.0"
- object-assign "^4.1.0"
- prop-types "~15.5.7"
+ object-assign "^4.1.1"
+ prop-types "^15.6.2"
+ scheduler "^0.13.6"
+
+react-is@^16.8.1:
+ version "16.8.6"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
+ integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
-react@^15.5.4:
- version "15.5.4"
- resolved "https://registry.yarnpkg.com/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047"
+react@^16.3.0:
+ version "16.8.6"
+ resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
+ integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==
dependencies:
- fbjs "^0.8.9"
loose-envify "^1.1.0"
- object-assign "^4.1.0"
- prop-types "^15.5.7"
+ object-assign "^4.1.1"
+ prop-types "^15.6.2"
+ scheduler "^0.13.6"
"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.2.2:
version "2.2.9"
@@ -2572,6 +2598,14 @@ sax@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
+scheduler@^0.13.6:
+ version "0.13.6"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889"
+ integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"