Skip to content

Commit

Permalink
Move theming APIs to core (#1628)
Browse files Browse the repository at this point in the history
* Move theming APIs to core

* Add changeset

* Remove emotion-theming link to the README from the docc

* Update weak-islands-confess.md

* Apply suggestions from code review

Co-Authored-By: Mitchell Hamilton <mitchell@hamil.town>

* Re-expose ThemeContext from @emotion/core
  • Loading branch information
Andarist committed Nov 18, 2019
1 parent 8ad4af4 commit cbb8b79
Show file tree
Hide file tree
Showing 53 changed files with 231 additions and 473 deletions.
6 changes: 6 additions & 0 deletions .changeset/weak-islands-confess.md
@@ -0,0 +1,6 @@
---
'@emotion/core': major
'emotion-theming': major
---

`emotion-theming` has been removed and all its exports were moved to `@emotion/core` package. Please import them like this `import { useTheme, ThemeProvider, withTheme } from '@emotion/core'` from now on.
1 change: 0 additions & 1 deletion docs/docs.yaml
Expand Up @@ -41,7 +41,6 @@
- babel-plugin-emotion
- eslint-plugin-emotion
- emotion-server
- emotion-theming
- jest-emotion
- '@emotion/native'
- '@emotion/primitives'
Expand Down
125 changes: 114 additions & 11 deletions docs/theming.mdx
Expand Up @@ -2,13 +2,19 @@
title: 'Theming'
---

Theming is provided by the library [`emotion-theming`](https://emotion.sh/docs/emotion-theming).
Theming is included in the [`@emotion/core`](https://emotion.sh/docs/@emotion/core) package.

```bash
npm install -S emotion-theming
```
Add `ThemeProvider` to the top level of your app and access the theme with `props.theme` in a styled component or provide a function that accepts the theme as the css prop.

## Table of Contents

Add `ThemeProvider` to the top level of your app and access the theme with `props.theme` in a styled component or provide a function that accepts the theme as the css prop. The api is laid out in detail [in the documentation](https://emotion.sh/docs/emotion-theming).
- [Examples](#examples)
- [Usage](#usage)
- [API](#api)
- [ThemeProvider](#themeprovider-reactcomponenttype)
- [withTheme](#withthemecomponent-reactcomponenttype-reactcomponenttype)
- [useTheme](#usetheme)
- [Credits](#credits)

## Examples

Expand All @@ -17,8 +23,7 @@ Add `ThemeProvider` to the top level of your app and access the theme with `prop
```jsx
// @live
/** @jsx jsx */
import { jsx } from '@emotion/core'
import { ThemeProvider } from 'emotion-theming'
import { jsx, ThemeProvider } from '@emotion/core'

const theme = {
colors: {
Expand All @@ -40,9 +45,8 @@ render(
```jsx
// @live
/** @jsx jsx */
import { jsx } from '@emotion/core'
import { jsx, ThemeProvider } from '@emotion/core'
import styled from '@emotion/styled'
import { ThemeProvider } from 'emotion-theming'

const theme = {
colors: {
Expand All @@ -66,8 +70,7 @@ render(
```jsx
// @live
/** @jsx jsx */
import { jsx } from '@emotion/core'
import { ThemeProvider, useTheme } from 'emotion-theming'
import { jsx, ThemeProvider, useTheme } from '@emotion/core'

const theme = {
colors: {
Expand All @@ -92,3 +95,103 @@ render(
)
```

## API

### ThemeProvider: React.ComponentType

A React component that passes the theme object down the component tree via [context](https://reactjs.org/docs/context.html). Additional `ThemeProvider` components can be added deeper in the tree to override the original theme. The theme object will be merged into its ancestor as if by [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). If a function is passed instead of an object it will be called with the ancestor theme and the result will be the new theme.

_Accepts:_

- **`children`: React.Node**
- **`theme`: Object|Object => Object** - An object or function that provides an object.

```jsx
import * as React from 'react'
import styled from '@emotion/styled'
import { ThemeProvider, withTheme } from '@emotion/core'

// object-style theme

const theme = {
backgroundColor: 'green',
color: 'red'
}

// function-style theme; note that if multiple <ThemeProvider> are used,
// the parent theme will be passed as a function argument

const adjustedTheme = ancestorTheme => ({ ...ancestorTheme, color: 'blue' })

class Container extends React.Component {
render() {
return (
<ThemeProvider theme={theme}>
<ThemeProvider theme={adjustedTheme}>
<Text>Boom shaka laka!</Text>
</ThemeProvider>
</ThemeProvider>
)
}
}
```

> Note:
>
> Make sure to hoist your theme out of render otherwise you may have performance problems.
### withTheme(component: React.ComponentType): React.ComponentType

A higher-order component that provides the current theme as a prop to the wrapped child and listens for changes. If the theme is updated, the child component will be re-rendered accordingly.

```jsx
import * as PropTypes from 'prop-types'
import * as React from 'react'
import { withTheme } from '@emotion/core'

class TellMeTheColor extends React.Component {
render() {
return <div>The color is {this.props.theme.color}.</div>
}
}

TellMeTheColor.propTypes = {
theme: PropTypes.shape({
color: PropTypes.string
})
}

const TellMeTheColorWithTheme = withTheme(TellMeTheColor)
```

### useTheme

A React hook that provides the current theme as its value. If the theme is updated, the child component will be re-rendered accordingly.

```jsx
// @live
/** @jsx jsx */
import { jsx, ThemeProvider, useTheme } from '@emotion/core'
import styled from '@emotion/styled'

const theme = {
colors: {
primary: 'hotpink'
}
}

function SomeText(props) {
const theme = useTheme()
return <div css={{ color: theme.colors.primary }} {...props} />
}

render(
<ThemeProvider theme={theme}>
<SomeText>some text</SomeText>
</ThemeProvider>
)
```

## Credits

Thanks goes to the [styled-components team](https://github.com/styled-components/styled-components) and [their contributors](https://github.com/styled-components/styled-components/graphs/contributors) who designed this API.
8 changes: 4 additions & 4 deletions docs/typescript.mdx
Expand Up @@ -254,7 +254,7 @@ _styled.tsx_

```tsx
import styled, { CreateStyled } from '@emotion/styled'
import * as emotionTheming from 'emotion-theming'
import { useTheme, ThemeProvider, EmotionTheming } from '@emotion/core'

type Theme = {
color: {
Expand All @@ -268,8 +268,8 @@ type Theme = {
export default styled as CreateStyled<Theme>

// You can also create themed versions of all the other theme helpers and hooks
const { ThemeProvider, withTheme, useTheme } = emotionTheming as emotionTheming.EmotionTheming<Theme>
export { ThemeProvider, withTheme, useTheme }
const { ThemeProvider, useTheme } = { ThemeProvider, useTheme } as EmotionTheming<Theme>
export { ThemeProvider, useTheme }
```

_Button.tsx_
Expand Down Expand Up @@ -308,4 +308,4 @@ const StyledComponent0 = styled(Component)`
background: ${(props: StyledComponentProps & ComponentProps) =>
props.bgColor};
`
```
```
3 changes: 1 addition & 2 deletions packages/core/__tests__/class-names.js
@@ -1,8 +1,7 @@
// @flow
import * as React from 'react'
import 'test-utils/next-env'
import { ClassNames } from '@emotion/core'
import { ThemeProvider } from 'emotion-theming'
import { ClassNames, ThemeProvider } from '@emotion/core'
import renderer from 'react-test-renderer'

test('css', () => {
Expand Down
3 changes: 1 addition & 2 deletions packages/core/__tests__/css.js
Expand Up @@ -2,8 +2,7 @@
/** @jsx jsx */
import 'test-utils/next-env'
import * as React from 'react'
import { jsx, css, CacheProvider } from '@emotion/core'
import { ThemeProvider } from 'emotion-theming'
import { jsx, css, CacheProvider, ThemeProvider } from '@emotion/core'
import { render } from '@testing-library/react'
import renderer from 'react-test-renderer'
import createCache from '@emotion/cache'
Expand Down
3 changes: 1 addition & 2 deletions packages/core/__tests__/global-with-theme.js
@@ -1,9 +1,8 @@
// @flow
import 'test-utils/dev-mode'
import * as React from 'react'
import { ThemeProvider } from 'emotion-theming'
import { render, unmountComponentAtNode } from 'react-dom'
import { Global } from '@emotion/core'
import { Global, ThemeProvider } from '@emotion/core'

beforeEach(() => {
// $FlowFixMe
Expand Down
Expand Up @@ -4,8 +4,7 @@ import 'test-utils/next-env'
import 'test-utils/dev-mode'
import { throwIfFalsy, safeQuerySelector } from 'test-utils'
import * as React from 'react'
import { ThemeProvider } from 'emotion-theming'
import { jsx } from '@emotion/core'
import { jsx, ThemeProvider } from '@emotion/core'
import { render } from 'react-dom'

test('provider with theme value that changes', () => {
Expand Down
Expand Up @@ -2,8 +2,7 @@
/** @jsx jsx */
import 'test-utils/next-env'
import { ignoreConsoleErrors } from 'test-utils'
import { ThemeProvider } from 'emotion-theming'
import { jsx } from '@emotion/core'
import { jsx, ThemeProvider } from '@emotion/core'
import renderer from 'react-test-renderer'
import cases from 'jest-in-case'

Expand Down
Expand Up @@ -2,8 +2,7 @@
/** @jsx jsx */
import 'test-utils/next-env'
import * as renderer from 'react-test-renderer'
import { jsx } from '@emotion/core'
import { useTheme, ThemeProvider } from 'emotion-theming'
import { jsx, useTheme, ThemeProvider } from '@emotion/core'

test('useTheme works', () => {
function TestComponent(props) {
Expand Down
@@ -1,7 +1,7 @@
// @flow
import * as React from 'react'
import * as renderer from 'react-test-renderer'
import { withTheme, ThemeProvider } from 'emotion-theming'
import { withTheme, ThemeProvider } from '@emotion/core'

test('withTheme works', () => {
class SomeComponent extends React.Component<{ theme: Object }> {
Expand Down
5 changes: 3 additions & 2 deletions packages/core/package.json
Expand Up @@ -27,7 +27,9 @@
"@emotion/css": "^11.0.0-next.3",
"@emotion/serialize": "^0.11.15-next.1",
"@emotion/sheet": "0.9.3",
"@emotion/utils": "0.11.2"
"@emotion/utils": "0.11.2",
"@emotion/weak-memoize": "0.2.4",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
"@babel/core": "^7.0.0",
Expand All @@ -45,7 +47,6 @@
"dtslint": "^0.3.0",
"emotion": "^11.0.0-next.0",
"emotion-server": "^11.0.0-next.0",
"emotion-theming": "^11.0.0-next.5",
"html-tag-names": "^1.1.2",
"react": "^16.11.0",
"svg-tag-names": "^1.1.1"
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/class-names.js
@@ -1,9 +1,9 @@
// @flow
import * as React from 'react'
import { useContext } from 'react'
import { getRegisteredStyles, insertStyles } from '@emotion/utils'
import { serializeStyles } from '@emotion/serialize'
import { withEmotionCache, ThemeContext } from './context'
import { withEmotionCache } from './context'
import { ThemeContext } from './theming'
import { isBrowser } from './utils'

type ClassNameArg =
Expand Down Expand Up @@ -112,7 +112,7 @@ export const ClassNames: React.AbstractComponent<
let content = {
css,
cx,
theme: useContext(ThemeContext)
theme: React.useContext(ThemeContext)
}
let ele = props.children(content)
hasRendered = true
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/context.js
Expand Up @@ -15,7 +15,6 @@ let EmotionCacheContext: React.Context<EmotionCache | null> = React.createContex
typeof HTMLElement !== 'undefined' ? createCache() : null
)

export let ThemeContext = React.createContext<Object>({})
export let CacheProvider = EmotionCacheContext.Provider

let withEmotionCache = function withEmotionCache<Props, Ref: React.Ref<*>>(
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/global.js
@@ -1,6 +1,7 @@
// @flow
import * as React from 'react'
import { withEmotionCache, ThemeContext } from './context'
import { withEmotionCache } from './context'
import { ThemeContext } from './theming'
import { insertStyles } from '@emotion/utils'
import { isBrowser } from './utils'

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/index.js
@@ -1,7 +1,8 @@
// @flow
export { withEmotionCache, CacheProvider, ThemeContext } from './context'
export { withEmotionCache, CacheProvider } from './context'
export { jsx } from './jsx'
export { Global } from './global'
export { keyframes } from './keyframes'
export { ClassNames } from './class-names'
export { ThemeContext, useTheme, ThemeProvider, withTheme } from './theming'
export { default as css } from './css'
3 changes: 2 additions & 1 deletion packages/core/src/jsx.js
@@ -1,6 +1,7 @@
// @flow
import * as React from 'react'
import { withEmotionCache, ThemeContext } from './context'
import { withEmotionCache } from './context'
import { ThemeContext } from './theming'
import { getRegisteredStyles, insertStyles } from '@emotion/utils'
import { isBrowser } from './utils'
import { serializeStyles } from '@emotion/serialize'
Expand Down
@@ -1,9 +1,13 @@
// @flow
import * as React from 'react'
import { ThemeContext } from '@emotion/core'
import weakMemoize from '@emotion/weak-memoize'
import hoistNonReactStatics from 'hoist-non-react-statics'

let getTheme = (outerTheme: Object, theme: Object | (Object => Object)) => {
export const ThemeContext = React.createContext<Object>({})

export const useTheme = () => React.useContext(ThemeContext)

const getTheme = (outerTheme: Object, theme: Object | (Object => Object)) => {
if (typeof theme === 'function') {
const mergedTheme = theme(outerTheme)
if (
Expand Down Expand Up @@ -36,12 +40,12 @@ let createCacheWithTheme = weakMemoize(outerTheme => {
})
})

type Props = {
type ThemeProviderProps = {
theme: Object | (Object => Object),
children: React.Node
}

let ThemeProvider = (props: Props) => {
export const ThemeProvider = (props: ThemeProviderProps) => {
let theme = React.useContext(ThemeContext)

if (props.theme !== theme) {
Expand All @@ -54,4 +58,19 @@ let ThemeProvider = (props: Props) => {
)
}

export default ThemeProvider
export function withTheme<Config: {}>(
Component: React.AbstractComponent<Config>
): React.AbstractComponent<$Diff<Config, { theme: Object }>> {
const componentName = Component.displayName || Component.name || 'Component'
let render = (props, ref) => {
let theme = React.useContext(ThemeContext)

return <Component theme={theme} ref={ref} {...props} />
}
// $FlowFixMe
let WithTheme = React.forwardRef(render)

WithTheme.displayName = `WithTheme(${componentName})`

return hoistNonReactStatics(WithTheme, Component)
}
File renamed without changes.

0 comments on commit cbb8b79

Please sign in to comment.