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

Move theming APIs to core #1628

Merged
merged 7 commits into from
Nov 18, 2019
Merged
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
6 changes: 6 additions & 0 deletions .changeset/weak-islands-confess.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.
Loading