Skip to content

Commit

Permalink
Refactor TS types to use builtin Theme interface which can be augment…
Browse files Browse the repository at this point in the history
…ed by consumers (#1609)

* Updated types to support global Theme definition

* Updated documentation to reflect new theme typings

* Simplified MuiTheme import syntax in docs

* Use previous theme declaration in docs

- [side-effect] Updated back to 2 space indentation

* Update Button.tsx import declaration for styled

* Updated type definitions to default to any if not defined

* Quick pass at removing theme generic param

* Fixed issue with Global

* Fixed most of the test issues

- Added TODO for one possible issue

* Updated global Theme type declaration to module specific

* Removed exports from test files

* Use the incomplete theme prop testcase

- Add test case for fully overriding theme

* Added note about where styled tests get their theme declaration from

* tweak docs

* Remove InterpolationWithTheme type

* Move adding Theme to Styled interpolations inline (and not as part of StyleProps)

* Cleanup Interpolation-related types

* Add changeset
  • Loading branch information
tomsseisums authored and Andarist committed Dec 7, 2019
1 parent 0b7ebdf commit c643107
Show file tree
Hide file tree
Showing 15 changed files with 185 additions and 240 deletions.
15 changes: 15 additions & 0 deletions .changeset/four-cars-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@emotion/core': major
'@emotion/styled': major
---

Reworked TypeScript definitions so it's easier to provide a type for Theme. Instead of creating custom instances (like before) you can augment builtin Theme interface like this:

```ts
declare module '@emotion/core' {
export interface Theme {
primaryColor: string
secondaryColor: string
}
}
```
5 changes: 5 additions & 0 deletions .changeset/tricky-bears-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@emotion/serialize': minor
---

Reworked Interpolation-related types. Correct types should now be provided to all flavours of emotion.
49 changes: 29 additions & 20 deletions docs/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -247,35 +247,34 @@ const App = () => (

### Define a Theme

By default, `props.theme` has an `any` type annotation and works without any errors.
However, you can define a theme type by creating another `styled` instance.

_styled.tsx_

```tsx
import styled, { CreateStyled } from '@emotion/styled'
import { useTheme, ThemeProvider, EmotionTheming } from '@emotion/core'

type Theme = {
color: {
primary: string
positive: string
negative: string
By default, `props.theme` is an empty object because it's the only thing that is type-safe as a default.
You can define a theme type by extending our type declarations via your own declarations file.

_emotion.d.ts_

```typescript
declare module '@emotion/core' {
export interface Theme {
color: {
primary: string
positive: string
negative: string
}
}
// ...
}

export default styled as CreateStyled<Theme>
// You are also able to use a 3rd party theme this way:
import { MuiTheme } from 'material-ui'

// You can also create themed versions of all the other theme helpers and hooks
const { ThemeProvider, useTheme } = { ThemeProvider, useTheme } as EmotionTheming<Theme>
export { ThemeProvider, useTheme }
declare module '@emotion/core' {
export interface Theme extends MuiTheme {}
}
```

_Button.tsx_

```tsx
import styled from '../path/to/styled'
import styled from '@emotion/styled'

const Button = styled('button')`
padding: 20px;
Expand All @@ -286,6 +285,16 @@ const Button = styled('button')`
export default Button
```

If you were previously relying on `theme` being an `any` type, you have to restore compatibility with:

_emotion.d.ts_

```ts
declare module '@emotion/core' {
export interface Theme extends Record<string, any> {}
}
```

### TypeScript < 2.9

For Typescript <2.9, the generic type version only works with object styles due to https://github.com/Microsoft/TypeScript/issues/11947.
Expand Down
40 changes: 17 additions & 23 deletions packages/core/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
FunctionInterpolation,
Interpolation,
Keyframes,
ObjectInterpolation,
SerializedStyles
} from '@emotion/serialize'
import {
Expand All @@ -32,13 +31,15 @@ export {
EmotionCache,
FunctionInterpolation,
Interpolation,
ObjectInterpolation,
SerializedStyles
}

export * from './theming'
export * from './helper'

// tslint:disable-next-line: no-empty-interface
export interface Theme {}

export const ThemeContext: Context<object>
export const CacheProvider: Provider<EmotionCache>
export function withEmotionCache<Props, RefType = any>(
Expand All @@ -53,26 +54,21 @@ export function css(
): SerializedStyles
export function css(...args: Array<CSSInterpolation>): SerializedStyles

export type InterpolationWithTheme<Theme> =
| Interpolation
| ((theme: Theme) => Interpolation)

export interface GlobalProps<Theme> {
styles: InterpolationWithTheme<Theme>
export interface GlobalProps {
styles: Interpolation<Theme>
}

/**
* @desc
* JSX generic are supported only after TS@2.9
*/
export function Global<Theme extends {} = any>(
props: GlobalProps<Theme>
): ReactElement
export function Global(props: GlobalProps): ReactElement

export function keyframes(
template: TemplateStringsArray,
...args: Array<Interpolation>
...args: Array<CSSInterpolation>
): Keyframes
export function keyframes(...args: Array<Interpolation>): Keyframes
export function keyframes(...args: Array<CSSInterpolation>): Keyframes

export interface ArrayClassNamesArg extends Array<ClassNamesArg> {}
export type ClassNamesArg =
Expand All @@ -83,26 +79,24 @@ export type ClassNamesArg =
| { [className: string]: boolean | null | undefined }
| ArrayClassNamesArg

export interface ClassNamesContent<Theme> {
css(template: TemplateStringsArray, ...args: Array<Interpolation>): string
css(...args: Array<Interpolation>): string
export interface ClassNamesContent {
css(template: TemplateStringsArray, ...args: Array<CSSInterpolation>): string
css(...args: Array<CSSInterpolation>): string
cx(...args: Array<ClassNamesArg>): string
theme: Theme
}
export interface ClassNamesProps<Theme> {
children(content: ClassNamesContent<Theme>): ReactNode
export interface ClassNamesProps {
children(content: ClassNamesContent): ReactNode
}
/**
* @desc
* JSX generic are supported only after TS@2.9
*/
export function ClassNames<Theme extends {} = any>(
props: ClassNamesProps<Theme>
): ReactElement
export function ClassNames(props: ClassNamesProps): ReactElement

declare module 'react' {
interface DOMAttributes<T> {
css?: InterpolationWithTheme<any>
css?: Interpolation<Theme>
}
}

Expand All @@ -114,7 +108,7 @@ declare global {
*/

interface IntrinsicAttributes {
css?: InterpolationWithTheme<any>
css?: Interpolation<Theme>
}
}
}
69 changes: 6 additions & 63 deletions packages/core/types/tests-theming.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,9 @@
// TypeScript Version: 3.1

import * as React from 'react'
import {
useTheme,
ThemeProvider,
withTheme,
EmotionTheming,
WithTheme
} from '@emotion/core'
import styled, { CreateStyled } from '@emotion/styled'
import { Interpolation, ObjectInterpolation } from '@emotion/styled/base'

interface Theme {
primary: string
secondary: string
}
import { useTheme, ThemeProvider, withTheme, Theme } from '@emotion/core'
import { Interpolation, CSSObject } from '@emotion/styled/base'

declare const theme: Theme

interface Props {
Expand Down Expand Up @@ -48,8 +37,6 @@ class CompCWithDefault extends React.Component<Props> {

{
const theme: Theme = useTheme()

const themeFail: Theme = useTheme<number>() // $ExpectError
}

const ThemedFCWithDefault = withTheme(CompFCWithDefault)
Expand All @@ -60,23 +47,6 @@ const ThemedCompWithDefault = withTheme(CompCWithDefault)
;<ThemedCompWithDefault />
;<ThemedCompWithDefault theme={theme} />

const { ThemeProvider: TypedThemeProvider, withTheme: typedWithTheme } = {
ThemeProvider,
withTheme
} as EmotionTheming<Theme>
;<TypedThemeProvider theme={theme} />
// $ExpectError
;<TypedThemeProvider theme={{ primary: 5 }} />

typedWithTheme(CompFC)

/**
* @todo
* Following line should report an error.
*/

typedWithTheme((props: { value: number }) => null)

{
interface Book {
kind: 'book'
Expand All @@ -102,34 +72,10 @@ typedWithTheme((props: { value: number }) => null)
;<Readable kind="book" author="Hejlsberg" />
;<ThemedReadable kind="book" author="Hejlsberg" />
;<Readable kind="magazine" author="Hejlsberg" /> // $ExpectError
;<ThemedReadable kind="magazine" author="Hejlsberg" /> // $ExpectError
}

const themedStyled = styled as CreateStyled<Theme>

const StyledCompC = themedStyled(WrappedCompC)({})
const AdditionallyStyledCompC = themedStyled(StyledCompC)({})
;<StyledCompC prop={true} />
;<AdditionallyStyledCompC prop={true} />

const StyledDiv = themedStyled('div')({})
;<StyledDiv />
// $ExpectError
;<StyledDiv theme={{ primary: 0, secondary: 0 }} />
const AdditionallyStyledDiv = themedStyled(StyledDiv)({})
;<AdditionallyStyledDiv />
// $ExpectError
;<AdditionallyStyledDiv theme={{ primary: 0, secondary: 0 }} />

const StyledDiv2 = themedStyled.div({})
;<StyledDiv2 />
// $ExpectError
;<StyledDiv2 theme={{ primary: 0, secondary: 0 }} />

export type StyleDefinition<T = {}> = Interpolation<WithTheme<T, Theme>>
export type ObjectStyleDefinition<T = {}> = ObjectInterpolation<
WithTheme<T, Theme>
>
type StyleDefinition = Interpolation<{ theme: Theme }>
type ObjectStyleDefinition = CSSObject

const style: StyleDefinition = ({ theme }) => ({
color: theme.primary
Expand All @@ -139,7 +85,4 @@ const style2: ObjectStyleDefinition = {
}

// Can use ThemeProvider
;<ThemeProvider theme={{ prop: 'val' }} />
;<TypedThemeProvider theme={{ primary: '', secondary: '' }} />
// $ExpectError
;<TypedThemeProvider theme={{ nope: 'string' }} />
;<ThemeProvider theme={{ primary: 'val' }} />
28 changes: 16 additions & 12 deletions packages/core/types/tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
import { ComponentClass } from 'react'
import {
ClassNames,
ClassNamesContent,
Global,
css,
jsx,
keyframes,
withEmotionCache
} from '@emotion/core'
;<Global styles={[]} />

interface TestTheme0 {
resetStyle: any
declare module '@emotion/core' {
// tslint:disable-next-line: strict-export-declare-modifiers
export interface Theme {
primary: string
secondary: string
primaryColor: string
secondaryColor: string
}
}

;<Global styles={(theme: TestTheme0) => [theme.resetStyle]} />
;<Global styles={[]} />
;<Global styles={theme => [theme.primaryColor]} />

declare const getRandomColor: () => string

Expand Down Expand Up @@ -92,14 +97,8 @@ const anim1 = keyframes`
}}
world="of-world"
/>

interface TestTheme1 {
primaryColor: string
secondaryColor: string
}

;<ClassNames>
{({ css, cx, theme }: ClassNamesContent<TestTheme1>) => {
{({ css, cx, theme }) => {
return (
<div>
<span className={cx('a', undefined, 'b', null, [['abc']])} />
Expand All @@ -121,3 +120,8 @@ interface TestTheme1 {
)
}}
</ClassNames>
;<div
css={theme => css`
color: ${theme.secondaryColor};
`}
/>

0 comments on commit c643107

Please sign in to comment.