diff --git a/src/__tests__/ensureExports.spec.js b/src/__tests__/ensureExports.spec.js
index 2a936b03..d5149384 100644
--- a/src/__tests__/ensureExports.spec.js
+++ b/src/__tests__/ensureExports.spec.js
@@ -9,7 +9,9 @@ const expectedExports = [
'GlobalTheme',
'Normalize',
'SetStyles',
- 'themer',
+ 'Themer',
+ 'ThemerContext',
+ 'ThemerProvider',
'withTheme',
'themePropTypes',
'responsive',
diff --git a/src/components/Link/__tests__/Link.spec.js b/src/components/Link/__tests__/Link.spec.js
index cd2295a6..eba6674f 100644
--- a/src/components/Link/__tests__/Link.spec.js
+++ b/src/components/Link/__tests__/Link.spec.js
@@ -4,10 +4,14 @@ import { StyleRoot } from '@instacart/radium'
import { mount } from 'enzyme'
import { spy } from 'sinon'
import Link from '../Link'
-import themer from '../../../styles/themer'
+import { Themer, ThemerProvider } from '../../../styles/themer'
import { defaultTheme } from '../../../styles/themer/utils'
describe('Link', () => {
+ let themer
+ beforeEach(() => {
+ themer = new Themer()
+ })
it('renders without throwing', () => {
const tree = renderer
.create(
@@ -64,7 +68,9 @@ describe('Link', () => {
it('re-renders when the active theme changes', () => {
const wrapper = mount(
- HI
+
+ HI
+
)
diff --git a/src/index.d.ts b/src/index.d.ts
index 64eab99b..95b5791b 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -44,7 +44,7 @@ import MenuItem from './components/Menus/MenuItem'
import MenuDivider from './components/Menus/MenuDivider'
import DropdownMenu from './components/Menus/DropdownMenu'
import zIndex from './styles/zIndex'
-import themer from './styles/themer/index'
+import { Themer, ThemerContext, ThemerProvider } from './styles/themer/index'
import withTheme, { WithThemeInjectedProps } from './styles/themer/withTheme'
import Slide from './components/Transitions/Slide'
import Grow from './components/Transitions/Grow'
@@ -101,7 +101,9 @@ export {
Normalize,
SetStyles,
// theming
- themer,
+ Themer,
+ ThemerContext,
+ ThemerProvider,
withTheme,
WithThemeInjectedProps,
themePropTypes,
diff --git a/src/index.js b/src/index.js
index e34fb59d..f1822d30 100644
--- a/src/index.js
+++ b/src/index.js
@@ -39,7 +39,7 @@ import MenuItem from './components/Menus/MenuItem'
import MenuDivider from './components/Menus/MenuDivider'
import DropdownMenu from './components/Menus/DropdownMenu'
import zIndex from './styles/zIndex'
-import themer from './styles/themer/index'
+import { Themer, ThemerContext, ThemerProvider } from './styles/themer/index'
import withTheme from './styles/themer/withTheme'
import Slide from './components/Transitions/Slide'
import Grow from './components/Transitions/Grow'
@@ -57,7 +57,9 @@ export {
Normalize,
SetStyles,
// theming
- themer,
+ Themer,
+ ThemerContext,
+ ThemerProvider,
withTheme,
themePropTypes,
// grid system
diff --git a/src/styles/themer/Themer.d.ts b/src/styles/themer/Themer.d.ts
new file mode 100644
index 00000000..3d301ec0
--- /dev/null
+++ b/src/styles/themer/Themer.d.ts
@@ -0,0 +1,15 @@
+import { Theme } from './utils'
+
+export declare class Themer {
+ themeConfig: Theme
+
+ constructor()
+
+ set(
+ section: TSection,
+ sectionKey: TSectionKey,
+ themeValue: string
+ ): void
+
+ subscribe(listener: (themeConfig?: Theme) => void): () => void
+}
diff --git a/src/styles/themer/Themer.js b/src/styles/themer/Themer.js
new file mode 100644
index 00000000..7908bd97
--- /dev/null
+++ b/src/styles/themer/Themer.js
@@ -0,0 +1,56 @@
+/* eslint-disable no-underscore-dangle */
+import { cleanConfig, defaultTheme, themeTemplate, validConfigValue } from './utils'
+
+export class Themer {
+ constructor() {
+ this._themeConfig = defaultTheme
+ this._onChangeListeners = []
+ }
+
+ _callListeners() {
+ this._onChangeListeners.forEach(listener => {
+ listener(this._themeConfig)
+ })
+ }
+
+ get themeConfig() {
+ return this._themeConfig
+ }
+
+ set themeConfig(themeConfig) {
+ this._themeConfig = cleanConfig(themeConfig)
+ this._callListeners()
+ }
+
+ get(section, sectionKey) {
+ if (!this._themeConfig) {
+ console.warn(
+ 'Snacks theme error: No themeConfig defined. Please use Themer template: ',
+ themeTemplate
+ )
+ } else if (validConfigValue(section, sectionKey)) {
+ return this._themeConfig[section][sectionKey]
+ }
+ }
+
+ set(section, sectionKey, themeValue) {
+ if (validConfigValue(section, sectionKey)) {
+ this._themeConfig[section][sectionKey] = themeValue
+ this._callListeners()
+ }
+ }
+
+ subscribe(listener) {
+ this._onChangeListeners.push(listener)
+
+ const unsubscribe = () => {
+ const index = this._onChangeListeners.indexOf(listener)
+ if (index === -1) {
+ return
+ }
+ this._onChangeListeners.splice(index, 1)
+ }
+
+ return unsubscribe
+ }
+}
diff --git a/src/styles/themer/ThemerContext.d.ts b/src/styles/themer/ThemerContext.d.ts
new file mode 100644
index 00000000..b297ae70
--- /dev/null
+++ b/src/styles/themer/ThemerContext.d.ts
@@ -0,0 +1,9 @@
+import { Context } from 'react'
+import { Themer } from './Themer'
+
+declare interface ThemerContextInterface {
+ tick: number // for propogating theme updates through react
+ themer: Themer
+}
+
+export declare const ThemerContext: Context
diff --git a/src/styles/themer/ThemerContext.js b/src/styles/themer/ThemerContext.js
new file mode 100644
index 00000000..99a74edd
--- /dev/null
+++ b/src/styles/themer/ThemerContext.js
@@ -0,0 +1,4 @@
+import { createContext } from 'react'
+import { Themer } from './Themer'
+
+export const ThemerContext = createContext({ themer: new Themer(), tick: 0 })
diff --git a/src/styles/themer/ThemerProvider.d.ts b/src/styles/themer/ThemerProvider.d.ts
new file mode 100644
index 00000000..217f0286
--- /dev/null
+++ b/src/styles/themer/ThemerProvider.d.ts
@@ -0,0 +1,10 @@
+import { Context } from 'react'
+import { Themer } from './Themer'
+import * as React from 'react'
+
+export interface ThemerProviderProps {
+ children?: React.ReactNode
+ themer: Themer
+}
+
+export declare const ThemerProvider: React.ComponentType
diff --git a/src/styles/themer/ThemerProvider.js b/src/styles/themer/ThemerProvider.js
new file mode 100644
index 00000000..c66c7211
--- /dev/null
+++ b/src/styles/themer/ThemerProvider.js
@@ -0,0 +1,43 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Themer } from './Themer'
+import { ThemerContext } from './ThemerContext'
+
+export class ThemerProvider extends React.Component {
+ static propTypes = {
+ themer: PropTypes.instanceOf(Themer),
+ children: PropTypes.node,
+ }
+
+ static contextType = ThemerContext
+
+ state = {
+ tick: 0,
+ }
+
+ componentDidMount() {
+ this.unsubscribe = this.context.themer.subscribe(this.onThemeChange)
+ }
+
+ componentWillUnmount() {
+ this.unsubscribe()
+ }
+
+ onThemeChange = () => {
+ const { tick } = this.state
+ // increment tick to force an update on the context
+ this.setState({ tick: tick + 1 })
+ }
+
+ render() {
+ const { themer, children } = this.props
+ // this creates a new reference every time render gets called
+ const themerContext = { themer }
+ return {children}
+ }
+}
+
+ThemerProvider.propTypes = {
+ themer: PropTypes.object,
+ children: PropTypes.node,
+}
diff --git a/src/styles/themer/__tests__/themer.spec.js b/src/styles/themer/__tests__/themer.spec.js
index 3d3ef0ef..d297acec 100644
--- a/src/styles/themer/__tests__/themer.spec.js
+++ b/src/styles/themer/__tests__/themer.spec.js
@@ -1,298 +1,305 @@
import { spy } from 'sinon'
-import themer from '../index'
+import { Themer } from '../Themer'
import { defaultTheme, themeTemplate } from '../utils'
-it('should instantiate with default theme', () => {
- expect(themer.themeConfig).toEqual(defaultTheme)
-})
-
-it('should warn when no config is set', () => {
- const oldWarn = console.warn
- console.warn = spy()
- themer._themeConfig = undefined // NOTE: never actually do this
- themer.get('colors', 'action')
- expect(
- console.warn.calledWith(
- `Snacks theme error: No themeConfig defined. Please use Themer template: `,
- themeTemplate
- )
- ).toBeTruthy()
-
- console.warn = oldWarn
-})
-
-it('should set new, partial config', () => {
- themer.themeConfig = {
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#4a4gsa',
- primaryForeground: '#fff',
- },
- }
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#4a4gsa',
- primaryForeground: '#fff',
- },
+describe('themer', () => {
+ let themer
+ beforeEach(() => {
+ themer = new Themer()
})
-})
-it('should set new, full config', () => {
- themer.themeConfig = {
- colors: {
- action: '#ccc',
- primaryBackground: '#ededed',
- primaryForeground: '#eee',
- secondaryBackground: '#fff',
- secondaryForeground: '#000',
- },
- }
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#ccc',
- primaryBackground: '#ededed',
- primaryForeground: '#eee',
- secondaryBackground: '#fff',
- secondaryForeground: '#000',
- },
+ it('should instantiate with default theme', () => {
+ expect(themer.themeConfig).toEqual(defaultTheme)
})
-})
-it('should warn and clean config on full config set', () => {
- const oldWarn = console.warn
- console.warn = spy()
-
- themer.themeConfig = {
- colors: {
- action: '#ccc',
- primaryBackground: '#ededed',
- primaryForeground: '#eee',
- secondaryBackground: '#fff',
- secondaryForeground: '#000',
- madness: 'pure madness',
- },
- stuff: {
- whatever: 'chaos',
- },
- }
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#ccc',
- primaryBackground: '#ededed',
- primaryForeground: '#eee',
- secondaryBackground: '#fff',
- secondaryForeground: '#000',
- },
+ it('should warn when no config is set', () => {
+ const oldWarn = console.warn
+ console.warn = spy()
+ themer._themeConfig = undefined // NOTE: never actually do this
+ themer.get('colors', 'action')
+ expect(
+ console.warn.calledWith(
+ `Snacks theme error: No themeConfig defined. Please use Themer template: `,
+ themeTemplate
+ )
+ ).toBeTruthy()
+
+ console.warn = oldWarn
})
- expect(console.warn.calledTwice).toBeTruthy()
- expect(
- console.warn.calledWith(
- 'Snacks theme error: "madness" not a valid config key. This value will not be set. Please use Themer template: ',
- themeTemplate
- )
- ).toBeTruthy()
- expect(
- console.warn.calledWith(
- 'Snacks theme error: "stuff" not a valid config section. These values will not be set. Please use Themer template: ',
- themeTemplate
- )
- ).toBeTruthy()
-
- console.warn = oldWarn
-})
-
-it('should set specifc value in config', () => {
- themer.themeConfig = {
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
- }
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
+ it('should set new, partial config', () => {
+ themer.themeConfig = {
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#4a4gsa',
+ primaryForeground: '#fff',
+ },
+ }
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#4a4gsa',
+ primaryForeground: '#fff',
+ },
+ })
})
- themer.set('colors', 'action', '#fff')
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#fff',
- primaryBackground: '#sg444h',
- },
+ it('should set new, full config', () => {
+ themer.themeConfig = {
+ colors: {
+ action: '#ccc',
+ primaryBackground: '#ededed',
+ primaryForeground: '#eee',
+ secondaryBackground: '#fff',
+ secondaryForeground: '#000',
+ },
+ }
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#ccc',
+ primaryBackground: '#ededed',
+ primaryForeground: '#eee',
+ secondaryBackground: '#fff',
+ secondaryForeground: '#000',
+ },
+ })
})
- themer.set('colors', 'primaryForeground', '#eee')
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#fff',
- primaryBackground: '#sg444h',
- primaryForeground: '#eee',
- },
+ it('should warn and clean config on full config set', () => {
+ const oldWarn = console.warn
+ console.warn = spy()
+
+ themer.themeConfig = {
+ colors: {
+ action: '#ccc',
+ primaryBackground: '#ededed',
+ primaryForeground: '#eee',
+ secondaryBackground: '#fff',
+ secondaryForeground: '#000',
+ madness: 'pure madness',
+ },
+ stuff: {
+ whatever: 'chaos',
+ },
+ }
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#ccc',
+ primaryBackground: '#ededed',
+ primaryForeground: '#eee',
+ secondaryBackground: '#fff',
+ secondaryForeground: '#000',
+ },
+ })
+
+ expect(console.warn.calledTwice).toBeTruthy()
+ expect(
+ console.warn.calledWith(
+ 'Snacks theme error: "madness" not a valid config key. This value will not be set. Please use Themer template: ',
+ themeTemplate
+ )
+ ).toBeTruthy()
+ expect(
+ console.warn.calledWith(
+ 'Snacks theme error: "stuff" not a valid config section. These values will not be set. Please use Themer template: ',
+ themeTemplate
+ )
+ ).toBeTruthy()
+
+ console.warn = oldWarn
})
-})
-it('should not set bad value in config and warn', () => {
- const oldWarn = console.warn
- console.warn = spy()
-
- themer.themeConfig = {
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
- }
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
+ it('should set specifc value in config', () => {
+ themer.themeConfig = {
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ }
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ })
+
+ themer.set('colors', 'action', '#fff')
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#fff',
+ primaryBackground: '#sg444h',
+ },
+ })
+
+ themer.set('colors', 'primaryForeground', '#eee')
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#fff',
+ primaryBackground: '#sg444h',
+ primaryForeground: '#eee',
+ },
+ })
})
- themer.set('colors', 'madness', 'pure madness')
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
+ it('should not set bad value in config and warn', () => {
+ const oldWarn = console.warn
+ console.warn = spy()
+
+ themer.themeConfig = {
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ }
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ })
+
+ themer.set('colors', 'madness', 'pure madness')
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ })
+
+ expect(
+ console.warn.calledWith(
+ 'Snacks theme error: "madness" not a valid config key. This value will not be set. Please use Themer template: ',
+ themeTemplate
+ )
+ ).toBeTruthy()
+
+ themer.set('stuff', 'whatever', 'chaos')
+
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ })
+
+ expect(
+ console.warn.calledWith(
+ 'Snacks theme error: "stuff" not a valid config section. These values will not be set. Please use Themer template: ',
+ themeTemplate
+ )
+ ).toBeTruthy()
+
+ console.warn = oldWarn
})
- expect(
- console.warn.calledWith(
- 'Snacks theme error: "madness" not a valid config key. This value will not be set. Please use Themer template: ',
- themeTemplate
- )
- ).toBeTruthy()
+ it('should get specifc value in config', () => {
+ themer.themeConfig = {
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ }
- themer.set('stuff', 'whatever', 'chaos')
-
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
+ expect(themer.get('colors', 'action')).toEqual('#4a4gsa')
})
- expect(
- console.warn.calledWith(
- 'Snacks theme error: "stuff" not a valid config section. These values will not be set. Please use Themer template: ',
- themeTemplate
- )
- ).toBeTruthy()
-
- console.warn = oldWarn
-})
-
-it('should get specifc value in config', () => {
- themer.themeConfig = {
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
- }
+ it('should not get bad value in config', () => {
+ const oldWarn = console.warn
+ console.warn = spy()
+
+ themer.themeConfig = {
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ }
+
+ expect(themer.get('stuff', 'whatever')).toEqual(undefined)
+ expect(
+ console.warn.calledWith(
+ 'Snacks theme error: "stuff" not a valid config section. These values will not be set. Please use Themer template: ',
+ themeTemplate
+ )
+ ).toBeTruthy()
+
+ console.warn = oldWarn
+ })
- expect(themer.get('colors', 'action')).toEqual('#4a4gsa')
-})
+ it('should add listener to list', () => {
+ const listener = theme => {}
+ themer.subscribe(listener)
-it('should not get bad value in config', () => {
- const oldWarn = console.warn
- console.warn = spy()
-
- themer.themeConfig = {
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
- }
-
- expect(themer.get('stuff', 'whatever')).toEqual(undefined)
- expect(
- console.warn.calledWith(
- 'Snacks theme error: "stuff" not a valid config section. These values will not be set. Please use Themer template: ',
- themeTemplate
- )
- ).toBeTruthy()
-
- console.warn = oldWarn
-})
-
-it('should add listener to list', () => {
- const listener = theme => {}
- themer.subscribe(listener)
+ expect(themer._onChangeListeners.length).toEqual(1)
+ expect(themer._onChangeListeners[0]).toEqual(listener)
+ })
- expect(themer._onChangeListeners.length).toEqual(1)
- expect(themer._onChangeListeners[0]).toEqual(listener)
-})
+ it('should remove listener from list', () => {
+ themer._onChangeListeners.length = 0
-it('should remove listener from list', () => {
- themer._onChangeListeners.length = 0
+ const listener = theme => {}
+ const unsubscribe = themer.subscribe(listener)
- const listener = theme => {}
- const unsubscribe = themer.subscribe(listener)
+ expect(themer._onChangeListeners.length).toEqual(1)
+ expect(themer._onChangeListeners[0]).toEqual(listener)
- expect(themer._onChangeListeners.length).toEqual(1)
- expect(themer._onChangeListeners[0]).toEqual(listener)
+ unsubscribe()
- unsubscribe()
+ expect(themer._onChangeListeners.includes(listener)).toBeFalsy()
+ expect(themer._onChangeListeners.length).toEqual(0)
+ })
- expect(themer._onChangeListeners.includes(listener)).toBeFalsy()
- expect(themer._onChangeListeners.length).toEqual(0)
-})
+ it('should call listener callback on theme config change', () => {
+ themer._onChangeListeners.length = 0
-it('should call listener callback on theme config change', () => {
- themer._onChangeListeners.length = 0
+ const listener = spy()
+ const unsubscribe = themer.subscribe(listener)
- const listener = spy()
- const unsubscribe = themer.subscribe(listener)
+ themer.themeConfig = {
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ }
- themer.themeConfig = {
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
- }
+ expect(themer.themeConfig).toEqual({
+ colors: {
+ action: '#4a4gsa',
+ primaryBackground: '#sg444h',
+ },
+ })
- expect(themer.themeConfig).toEqual({
- colors: {
- action: '#4a4gsa',
- primaryBackground: '#sg444h',
- },
+ expect(listener.calledWith(themer.themerConfig))
+ expect(listener.calledOnce).toBeTruthy()
})
- expect(listener.calledWith(themer.themerConfig))
- expect(listener.calledOnce).toBeTruthy()
-})
-
-it('should call listener callback on theme config set call', () => {
- themer._onChangeListeners.length = 0
+ it('should call listener callback on theme config set call', () => {
+ themer._onChangeListeners.length = 0
- const listener = spy()
- const unsubscribe = themer.subscribe(listener)
+ const listener = spy()
+ const unsubscribe = themer.subscribe(listener)
- themer.set('colors', 'action', '#fff')
+ themer.set('colors', 'action', '#fff')
- expect(listener.calledWith(themer.themerConfig))
- expect(listener.calledOnce).toBeTruthy()
-})
+ expect(listener.calledWith(themer.themerConfig))
+ expect(listener.calledOnce).toBeTruthy()
+ })
-it('should handle bad listener array remove', () => {
- themer._onChangeListeners.length = 0
+ it('should handle bad listener array remove', () => {
+ themer._onChangeListeners.length = 0
- const listener = spy()
- const unsubscribe = themer.subscribe(listener)
+ const listener = spy()
+ const unsubscribe = themer.subscribe(listener)
- themer._onChangeListeners.length = 0 // oh no!
+ themer._onChangeListeners.length = 0 // oh no!
- unsubscribe() // should not cause an error
+ unsubscribe() // should not cause an error
+ })
})
diff --git a/src/styles/themer/__tests__/withTheme.spec.js b/src/styles/themer/__tests__/withTheme.spec.js
index f6d6e17f..bf5892c0 100644
--- a/src/styles/themer/__tests__/withTheme.spec.js
+++ b/src/styles/themer/__tests__/withTheme.spec.js
@@ -35,6 +35,7 @@ describe('while in production mode', () => {
jest.resetModules()
process.env.NODE_ENV = 'production'
+ // eslint-disable-next-line global-require
TestComponent = require('../withTheme').default(Test)
})
@@ -59,6 +60,7 @@ describe('while in production mode', () => {
})
it('falls back to active themer theme if props are invalid', () => {
+ // eslint-disable-next-line array-callback-return
;[null, undefined].map(invalidTheme => {
const tree = renderer.create().toJSON()
expect(tree.props.style.backgroundColor).toBe(defaultTheme.colors.primaryBackground)
diff --git a/src/styles/themer/index.d.ts b/src/styles/themer/index.d.ts
index a641f856..484f05ee 100644
--- a/src/styles/themer/index.d.ts
+++ b/src/styles/themer/index.d.ts
@@ -1,24 +1,3 @@
-import { Theme } from './utils'
-
-declare class Themer {
- themeConfig: Theme
-
- constructor()
-
- get(
- section: TSection,
- sectionKey: TSectionKey
- ): Theme[TSection][TSectionKey]
-
- set(
- section: TSection,
- sectionKey: TSectionKey,
- themeValue: string
- ): void
-
- subscribe(listener: (themeConfig?: Theme) => void): () => void
-}
-
-declare const themer: Themer
-
-export default themer
+export { Themer } from './Themer'
+export { ThemerContext } from './ThemerContext'
+export { ThemerProvider } from './ThemerProvider'
diff --git a/src/styles/themer/index.js b/src/styles/themer/index.js
index c669ba83..484f05ee 100644
--- a/src/styles/themer/index.js
+++ b/src/styles/themer/index.js
@@ -1,58 +1,3 @@
-/* eslint-disable no-underscore-dangle */
-import { cleanConfig, defaultTheme, themeTemplate, validConfigValue } from './utils'
-
-class Themer {
- constructor() {
- this._themeConfig = defaultTheme
- this._onChangeListeners = []
- }
-
- _callListeners() {
- this._onChangeListeners.forEach(listener => {
- listener(this._themeConfig)
- })
- }
-
- get themeConfig() {
- return this._themeConfig
- }
-
- set themeConfig(themeConfig) {
- this._themeConfig = cleanConfig(themeConfig)
- this._callListeners()
- }
-
- get(section, sectionKey) {
- if (!this._themeConfig) {
- console.warn(
- 'Snacks theme error: No themeConfig defined. Please use Themer template: ',
- themeTemplate
- )
- } else if (validConfigValue(section, sectionKey)) {
- return this._themeConfig[section][sectionKey]
- }
- }
-
- set(section, sectionKey, themeValue) {
- if (validConfigValue(section, sectionKey)) {
- this._themeConfig[section][sectionKey] = themeValue
- this._callListeners()
- }
- }
-
- subscribe(listener) {
- this._onChangeListeners.push(listener)
-
- const unsubscribe = () => {
- const index = this._onChangeListeners.indexOf(listener)
- if (index === -1) {
- return
- }
- this._onChangeListeners.splice(index, 1)
- }
-
- return unsubscribe
- }
-}
-
-export default new Themer()
+export { Themer } from './Themer'
+export { ThemerContext } from './ThemerContext'
+export { ThemerProvider } from './ThemerProvider'
diff --git a/src/styles/themer/withTheme.js b/src/styles/themer/withTheme.js
index ac3e4085..d5f3b0ea 100644
--- a/src/styles/themer/withTheme.js
+++ b/src/styles/themer/withTheme.js
@@ -3,7 +3,7 @@
import React, { Component } from 'react'
import { isValidElementType } from 'react-is'
import PropTypes from 'prop-types'
-import themer from './index'
+import { ThemerContext } from './ThemerContext'
import { cleanConfig, themePropTypes } from './utils'
function withTheme(options = {}) {
@@ -27,8 +27,10 @@ function withTheme(options = {}) {
snacksTheme: themePropTypes,
}
+ static contextType = ThemerContext
+
componentDidMount() {
- this.unsubscribe = themer.subscribe(this.onThemeChange)
+ this.unsubscribe = this.context.themer.subscribe(this.onThemeChange)
this.validateSnacksTheme()
}
@@ -60,7 +62,7 @@ function withTheme(options = {}) {
render() {
const { snacksTheme, forwardedRef, ...rest } = this.props
- const theme = this.themeIsValid() ? snacksTheme : themer.themeConfig
+ const theme = this.themeIsValid() ? snacksTheme : this.context.themer.themeConfig
return
}