Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rewrite themer to use a react context #450

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/__tests__/ensureExports.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const expectedExports = [
'GlobalTheme',
'Normalize',
'SetStyles',
'themer',
'Themer',
'ThemerContext',
'ThemerProvider',
'withTheme',
'themePropTypes',
'responsive',
Expand Down
10 changes: 8 additions & 2 deletions src/components/Link/__tests__/Link.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -64,7 +68,9 @@ describe('Link', () => {
it('re-renders when the active theme changes', () => {
const wrapper = mount(
<StyleRoot>
<Link elementAttributes={{ 'aria-label': 'foo' }}>HI</Link>
<ThemerProvider themer={themer}>
<Link elementAttributes={{ 'aria-label': 'foo' }}>HI</Link>
</ThemerProvider>
</StyleRoot>
)

Expand Down
6 changes: 4 additions & 2 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -101,7 +101,9 @@ export {
Normalize,
SetStyles,
// theming
themer,
Themer,
ThemerContext,
ThemerProvider,
withTheme,
WithThemeInjectedProps,
themePropTypes,
Expand Down
6 changes: 4 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -57,7 +57,9 @@ export {
Normalize,
SetStyles,
// theming
themer,
Themer,
ThemerContext,
ThemerProvider,
withTheme,
themePropTypes,
// grid system
Expand Down
15 changes: 15 additions & 0 deletions src/styles/themer/Themer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Theme } from './utils'

export declare class Themer {
themeConfig: Theme

constructor()

set<TSection extends keyof Theme, TSectionKey extends keyof Theme[TSection]>(
section: TSection,
sectionKey: TSectionKey,
themeValue: string
): void

subscribe(listener: (themeConfig?: Theme) => void): () => void
}
56 changes: 56 additions & 0 deletions src/styles/themer/Themer.js
Original file line number Diff line number Diff line change
@@ -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
}
}
9 changes: 9 additions & 0 deletions src/styles/themer/ThemerContext.d.ts
Original file line number Diff line number Diff line change
@@ -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<ThemerContextInterface>
4 changes: 4 additions & 0 deletions src/styles/themer/ThemerContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createContext } from 'react'
import { Themer } from './Themer'

export const ThemerContext = createContext({ themer: new Themer(), tick: 0 })
10 changes: 10 additions & 0 deletions src/styles/themer/ThemerProvider.d.ts
Original file line number Diff line number Diff line change
@@ -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<ThemerProviderProps>
43 changes: 43 additions & 0 deletions src/styles/themer/ThemerProvider.js
Original file line number Diff line number Diff line change
@@ -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 <ThemerContext.Provider value={themerContext}>{children}</ThemerContext.Provider>
}
}

ThemerProvider.propTypes = {
themer: PropTypes.object,
children: PropTypes.node,
}
Loading