Skip to content

Commit

Permalink
Add types to react-emotion (#398)
Browse files Browse the repository at this point in the history
* Add typings to react-emotion package

* Fix CSSProperties and add more tests

* Remove --jsx flag from tsc command

* Add withComponent to typescript_tests

* Fix a mistake in withComponent typing

* Add support to more interpolations edge cases

* Added declaration to tsconfig

* Can use emotion helpers importing from react-emotion in ts

* Creates typescript documentation

* Adds typescript link in docs readme

* Update typescript.md

* Update typescript.md

* Update typescript.md
  • Loading branch information
renatorib authored and Kye Hohenberger committed Oct 16, 2017
1 parent dc3cf65 commit 3f58674
Show file tree
Hide file tree
Showing 6 changed files with 455 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
- [Extracting Static Styles](https://github.com/emotion-js/emotion/tree/master/docs/extract-static.md)
- [Using `withProps` To Attach Props](https://github.com/emotion-js/emotion/tree/master/docs/with-props.md) (styled-components `.attrs` api)
- [Usage with babel-macros](https://github.com/tkh44/emotion/tree/master/docs/babel-macros.md)
- [TypeScript](https://github.com/emotion-js/emotion/tree/master/docs/typescript.md)
188 changes: 188 additions & 0 deletions docs/typescript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
## TypeScript

Emotion includes TypeScript definitions for `styled` components and has type inferences for both html elements and React components.

### html elements

```jsx
import styled from 'react-emotion'

const Link = styled('a')`
color: red;
`

const App = () => (
<Link href="#">Click me</Link>
)
```

```jsx
import styled from 'react-emotion'

const NotALink = styled('div')`
color: red;
`

const App = () => (
<NotALink href="#">Click me</NotALink>
^^^^^^^^ Property 'href' does not exist [...]
)
```

### `withComponent`

```jsx
import styled from 'react-emotion'

const NotALink = styled('div')`
color: red,
`

const Link = NotALink.withComponent('a')

const App = () => (
<Link href="#">Click me</Link>
)

// No errors!
```

### Passing Props

You can type the props of your styled components.
Unfortunately, you will need to pass a second parameter with the tag name because TypeScript is unable to infer the tagname.

```jsx
import styled from 'react-emotion'

type ImageProps = {
src: string,
}

const Image = styled<ImageProps, 'div'>('div')`
background: url(${props => props.src}) center center;
background-size: contain;
`
```

### Object Styles

```jsx
import styled from 'react-emotion'

type ImageProps = {
src: string,
}

const Image = styled<ImageProps, 'div'>('div')({
backgroundSize: contain;
}, ({ src }) => ({
background: `url(${src}) center center`,
}))

// Or shorthand

const Image = styled.div<ImageProps>({
backgroundSize: contain;
}, ({ src }) => ({
background: `url(${src}) center center`,
}))

```

* Note that in shorthand example you don't need to pass the tag name argument.
* The shorthand only works with object styles due to https://github.com/Microsoft/TypeScript/issues/11947.

### React Components

```jsx
import React, { SFC } from 'react'
import styled from 'react-emotion'

type ComponentProps = {
className?: string,
label: string,
}

const Component: SFC = ({ label, className }) => (
<div className={className}>
{label}
</div>
)

const StyledComponent = styled(Component)`
color: red;
`

const App = () => (
<StyledComponent label="Yea! No need to re-type this label prop." />
)
```

### Passing props when styling a React component

```jsx
import React, { SFC } from 'react'
import styled from 'react-emotion'

type ComponentProps = {
className?: string,
label: string,
}

const Component: SFC = ({ label, className }) => (
<div className={className}>
{label}
</div>
)

type StyledComponentProps = {
bgColor: string,
} & ComponentProps
// ^^^ You will need this

const StyledComponent = styled<StyledComponentProps>(Component)`
color: red;
background: ${props => props.bgColor};
`

const App = () => (
<StyledComponent bgColor="red" label="Oh, needs to re-type label prop =(" />
)
```

Unfortunately, when you pass custom props to a styled component, TypeScript will stop inferring your Component props, and you will need to re-type them.

### Define a Theme

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

*styled.tsx*
```jsx
import styled, { ThemedReactEmotionInterface } from 'react-emotion'

type Theme = {
color: {
primary: string,
positive: string,
negative: string,
},
// ...
}

export default styled as ThemedReactEmotionInterface<Theme>
```

*Button.tsx*
```jsx
import styled from '../pathto/styled'

const Button = styled('button')`
padding: 20px;
background-color: ${props => props.theme.primary};
border-radius: 3px;
`

export default Button
```
10 changes: 8 additions & 2 deletions packages/react-emotion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
"description": "The Next Generation of CSS-in-JS, for React projects.",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"types": "typings/react-emotion.d.ts",
"files": [
"src",
"dist",
"macro.js"
"macro.js",
"typings"
],
"scripts": {
"build": "npm-run-all clean rollup rollup:umd",
"test:typescript": "tsc --noEmit -p typescript_tests/tsconfig.json",
"pretest:typescript": "npm run build",
"clean": "rimraf dist",
"rollup": "rollup -c ../../rollup.config.js",
"watch": "rollup -c ../../rollup.config.js --watch",
Expand All @@ -24,11 +28,13 @@
"emotion": "^8.0.6"
},
"devDependencies": {
"@types/react": "^16.0.10",
"cross-env": "^5.0.5",
"emotion": "^8.0.6",
"npm-run-all": "^4.0.2",
"rimraf": "^2.6.1",
"rollup": "^0.43.0"
"rollup": "^0.43.0",
"typescript": "^2.0.0"
},
"author": "Kye Hohenberger",
"homepage": "https://github.com/tkh44/emotion#readme",
Expand Down
15 changes: 15 additions & 0 deletions packages/react-emotion/typescript_tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"declaration": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"jsx": "react"
},
"include": [
"./*.ts",
"./*.tsx"
]
}
133 changes: 133 additions & 0 deletions packages/react-emotion/typescript_tests/typescript_tests.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import styled, { flush, ThemedReactEmotionInterface } from '../';

let Component;
let mount;

/*
* Inference HTML Tag Props
*/
Component = styled.div({ color: 'red' });
mount = <Component onClick={event => event} />;

Component = styled('div')({ color: 'red' });
mount = <Component onClick={event => event} />;

Component = styled.div`color: red;`;
mount = <Component onClick={(e) => e} />;

Component = styled('div')`color: red;`;
mount = <Component onClick={(e) => e} />;

Component = styled.a({ color: 'red' });
mount = <Component href="#" />;

Component = styled('a')({ color: 'red' });
mount = <Component href="#" />;

/*
* Passing custom props
*/
type CustomProps = { lookColor: string };

Component = styled.div<CustomProps>(
{ color: 'blue' },
props => ({
background: props.lookColor,
}),
props => ({
border: `1px solid ${props.lookColor}`,
}),
);
mount = <Component lookColor="red" />;

Component = styled<CustomProps, 'div'>('div')(
{ color: 'blue' },
props => ({
background: props.lookColor,
}),
);
mount = <Component lookColor="red" />;

const anotherColor = 'blue';
Component = styled<CustomProps, 'div'>('div')`
background: ${props => props.lookColor};
color: ${anotherColor};
`
mount = <Component lookColor="red" />;

/*
* With other components
*/
type CustomProps2 = { customProp: string };
type SFCComponentProps = { className?: string, foo: string };

const SFCComponent: React.StatelessComponent<SFCComponentProps> = props => (
<div className={props.className}>{props.children} {props.foo}</div>
);

// infer SFCComponentProps
Component = styled(SFCComponent)({ color: 'red' });
mount = <Component foo="bar" />;

// infer SFCComponentProps
Component = styled(SFCComponent)`color: red`;
mount = <Component foo="bar" />;

// do not infer SFCComponentProps with pass CustomProps, need to pass both
Component = styled<CustomProps2 & SFCComponentProps>(SFCComponent)({
color: 'red',
}, props => ({
background: props.customProp,
}));
mount = <Component customProp="red" foo="bar" />;

// do not infer SFCComponentProps with pass CustomProps, need to pass both
Component = styled<CustomProps2 & SFCComponentProps>(SFCComponent)`
color: red;
background: ${props => props.customProp};
`;
mount = <Component customProp="red" foo="bar" />;


/*
* With explicit theme
*/

type Theme = {
color: {
primary: string,
secondary: string,
}
};

const _styled = styled as ThemedReactEmotionInterface<Theme>;

Component = _styled.div`
color: ${props => props.theme.color.primary}
`;
mount = <Component onClick={event => event} />;

/*
* withComponent
*/

type CustomProps3 = {
bgColor: string,
};

Component = styled.div<CustomProps3>(props => ({
bgColor: props.bgColor,
}));

let Link = Component.withComponent('a');
mount = <Link href="#" bgColor="red" />;

let Button = Component.withComponent('button');
mount = <Button type="submit" bgColor="red" />;

/*
* Can use emotion helpers importing from react-emotion
*/

flush();
Loading

0 comments on commit 3f58674

Please sign in to comment.