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

Updated types to support global Theme definition #1609

Merged
merged 19 commits into from
Dec 7, 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
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole docs on Theme usage with TS feels massively outdated now, and I feel like it should be reworded altogether.

Exposing for discussion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - I feel like we can handle it in a separate PR though. Ideally, such docs would present the current solution with its tradeoffs and incorporate stuff from this #973

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not in this PR, it should definitely be a blocker for releasing v11.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is crucial and I plan to get this done before v11 👍


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 [email protected]
*/
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 [email protected]
*/
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' }} />
Andarist marked this conversation as resolved.
Show resolved Hide resolved
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};
`}
/>
Loading