Skip to content

Commit 3f58674

Browse files
renatoribKye Hohenberger
authored and
Kye Hohenberger
committed
Add types to react-emotion (#398)
* 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
1 parent dc3cf65 commit 3f58674

File tree

6 files changed

+455
-2
lines changed

6 files changed

+455
-2
lines changed

docs/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@
2525
- [Extracting Static Styles](https://github.com/emotion-js/emotion/tree/master/docs/extract-static.md)
2626
- [Using `withProps` To Attach Props](https://github.com/emotion-js/emotion/tree/master/docs/with-props.md) (styled-components `.attrs` api)
2727
- [Usage with babel-macros](https://github.com/tkh44/emotion/tree/master/docs/babel-macros.md)
28+
- [TypeScript](https://github.com/emotion-js/emotion/tree/master/docs/typescript.md)

docs/typescript.md

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
## TypeScript
2+
3+
Emotion includes TypeScript definitions for `styled` components and has type inferences for both html elements and React components.
4+
5+
### html elements
6+
7+
```jsx
8+
import styled from 'react-emotion'
9+
10+
const Link = styled('a')`
11+
color: red;
12+
`
13+
14+
const App = () => (
15+
<Link href="#">Click me</Link>
16+
)
17+
```
18+
19+
```jsx
20+
import styled from 'react-emotion'
21+
22+
const NotALink = styled('div')`
23+
color: red;
24+
`
25+
26+
const App = () => (
27+
<NotALink href="#">Click me</NotALink>
28+
^^^^^^^^ Property 'href' does not exist [...]
29+
)
30+
```
31+
32+
### `withComponent`
33+
34+
```jsx
35+
import styled from 'react-emotion'
36+
37+
const NotALink = styled('div')`
38+
color: red,
39+
`
40+
41+
const Link = NotALink.withComponent('a')
42+
43+
const App = () => (
44+
<Link href="#">Click me</Link>
45+
)
46+
47+
// No errors!
48+
```
49+
50+
### Passing Props
51+
52+
You can type the props of your styled components.
53+
Unfortunately, you will need to pass a second parameter with the tag name because TypeScript is unable to infer the tagname.
54+
55+
```jsx
56+
import styled from 'react-emotion'
57+
58+
type ImageProps = {
59+
src: string,
60+
}
61+
62+
const Image = styled<ImageProps, 'div'>('div')`
63+
background: url(${props => props.src}) center center;
64+
background-size: contain;
65+
`
66+
```
67+
68+
### Object Styles
69+
70+
```jsx
71+
import styled from 'react-emotion'
72+
73+
type ImageProps = {
74+
src: string,
75+
}
76+
77+
const Image = styled<ImageProps, 'div'>('div')({
78+
backgroundSize: contain;
79+
}, ({ src }) => ({
80+
background: `url(${src}) center center`,
81+
}))
82+
83+
// Or shorthand
84+
85+
const Image = styled.div<ImageProps>({
86+
backgroundSize: contain;
87+
}, ({ src }) => ({
88+
background: `url(${src}) center center`,
89+
}))
90+
91+
```
92+
93+
* Note that in shorthand example you don't need to pass the tag name argument.
94+
* The shorthand only works with object styles due to https://github.com/Microsoft/TypeScript/issues/11947.
95+
96+
### React Components
97+
98+
```jsx
99+
import React, { SFC } from 'react'
100+
import styled from 'react-emotion'
101+
102+
type ComponentProps = {
103+
className?: string,
104+
label: string,
105+
}
106+
107+
const Component: SFC = ({ label, className }) => (
108+
<div className={className}>
109+
{label}
110+
</div>
111+
)
112+
113+
const StyledComponent = styled(Component)`
114+
color: red;
115+
`
116+
117+
const App = () => (
118+
<StyledComponent label="Yea! No need to re-type this label prop." />
119+
)
120+
```
121+
122+
### Passing props when styling a React component
123+
124+
```jsx
125+
import React, { SFC } from 'react'
126+
import styled from 'react-emotion'
127+
128+
type ComponentProps = {
129+
className?: string,
130+
label: string,
131+
}
132+
133+
const Component: SFC = ({ label, className }) => (
134+
<div className={className}>
135+
{label}
136+
</div>
137+
)
138+
139+
type StyledComponentProps = {
140+
bgColor: string,
141+
} & ComponentProps
142+
// ^^^ You will need this
143+
144+
const StyledComponent = styled<StyledComponentProps>(Component)`
145+
color: red;
146+
background: ${props => props.bgColor};
147+
`
148+
149+
const App = () => (
150+
<StyledComponent bgColor="red" label="Oh, needs to re-type label prop =(" />
151+
)
152+
```
153+
154+
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.
155+
156+
### Define a Theme
157+
158+
By default, the `props.theme` has `any` type annotation and works without error.
159+
However, you can define a theme type by creating a another `styled` instance.
160+
161+
*styled.tsx*
162+
```jsx
163+
import styled, { ThemedReactEmotionInterface } from 'react-emotion'
164+
165+
type Theme = {
166+
color: {
167+
primary: string,
168+
positive: string,
169+
negative: string,
170+
},
171+
// ...
172+
}
173+
174+
export default styled as ThemedReactEmotionInterface<Theme>
175+
```
176+
177+
*Button.tsx*
178+
```jsx
179+
import styled from '../pathto/styled'
180+
181+
const Button = styled('button')`
182+
padding: 20px;
183+
background-color: ${props => props.theme.primary};
184+
border-radius: 3px;
185+
`
186+
187+
export default Button
188+
```

packages/react-emotion/package.json

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
"description": "The Next Generation of CSS-in-JS, for React projects.",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.es.js",
7+
"types": "typings/react-emotion.d.ts",
78
"files": [
89
"src",
910
"dist",
10-
"macro.js"
11+
"macro.js",
12+
"typings"
1113
],
1214
"scripts": {
1315
"build": "npm-run-all clean rollup rollup:umd",
16+
"test:typescript": "tsc --noEmit -p typescript_tests/tsconfig.json",
17+
"pretest:typescript": "npm run build",
1418
"clean": "rimraf dist",
1519
"rollup": "rollup -c ../../rollup.config.js",
1620
"watch": "rollup -c ../../rollup.config.js --watch",
@@ -24,11 +28,13 @@
2428
"emotion": "^8.0.6"
2529
},
2630
"devDependencies": {
31+
"@types/react": "^16.0.10",
2732
"cross-env": "^5.0.5",
2833
"emotion": "^8.0.6",
2934
"npm-run-all": "^4.0.2",
3035
"rimraf": "^2.6.1",
31-
"rollup": "^0.43.0"
36+
"rollup": "^0.43.0",
37+
"typescript": "^2.0.0"
3238
},
3339
"author": "Kye Hohenberger",
3440
"homepage": "https://github.com/tkh44/emotion#readme",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"module": "es2015",
5+
"declaration": true,
6+
"strict": true,
7+
"allowSyntheticDefaultImports": true,
8+
"moduleResolution": "node",
9+
"jsx": "react"
10+
},
11+
"include": [
12+
"./*.ts",
13+
"./*.tsx"
14+
]
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React from 'react';
2+
import styled, { flush, ThemedReactEmotionInterface } from '../';
3+
4+
let Component;
5+
let mount;
6+
7+
/*
8+
* Inference HTML Tag Props
9+
*/
10+
Component = styled.div({ color: 'red' });
11+
mount = <Component onClick={event => event} />;
12+
13+
Component = styled('div')({ color: 'red' });
14+
mount = <Component onClick={event => event} />;
15+
16+
Component = styled.div`color: red;`;
17+
mount = <Component onClick={(e) => e} />;
18+
19+
Component = styled('div')`color: red;`;
20+
mount = <Component onClick={(e) => e} />;
21+
22+
Component = styled.a({ color: 'red' });
23+
mount = <Component href="#" />;
24+
25+
Component = styled('a')({ color: 'red' });
26+
mount = <Component href="#" />;
27+
28+
/*
29+
* Passing custom props
30+
*/
31+
type CustomProps = { lookColor: string };
32+
33+
Component = styled.div<CustomProps>(
34+
{ color: 'blue' },
35+
props => ({
36+
background: props.lookColor,
37+
}),
38+
props => ({
39+
border: `1px solid ${props.lookColor}`,
40+
}),
41+
);
42+
mount = <Component lookColor="red" />;
43+
44+
Component = styled<CustomProps, 'div'>('div')(
45+
{ color: 'blue' },
46+
props => ({
47+
background: props.lookColor,
48+
}),
49+
);
50+
mount = <Component lookColor="red" />;
51+
52+
const anotherColor = 'blue';
53+
Component = styled<CustomProps, 'div'>('div')`
54+
background: ${props => props.lookColor};
55+
color: ${anotherColor};
56+
`
57+
mount = <Component lookColor="red" />;
58+
59+
/*
60+
* With other components
61+
*/
62+
type CustomProps2 = { customProp: string };
63+
type SFCComponentProps = { className?: string, foo: string };
64+
65+
const SFCComponent: React.StatelessComponent<SFCComponentProps> = props => (
66+
<div className={props.className}>{props.children} {props.foo}</div>
67+
);
68+
69+
// infer SFCComponentProps
70+
Component = styled(SFCComponent)({ color: 'red' });
71+
mount = <Component foo="bar" />;
72+
73+
// infer SFCComponentProps
74+
Component = styled(SFCComponent)`color: red`;
75+
mount = <Component foo="bar" />;
76+
77+
// do not infer SFCComponentProps with pass CustomProps, need to pass both
78+
Component = styled<CustomProps2 & SFCComponentProps>(SFCComponent)({
79+
color: 'red',
80+
}, props => ({
81+
background: props.customProp,
82+
}));
83+
mount = <Component customProp="red" foo="bar" />;
84+
85+
// do not infer SFCComponentProps with pass CustomProps, need to pass both
86+
Component = styled<CustomProps2 & SFCComponentProps>(SFCComponent)`
87+
color: red;
88+
background: ${props => props.customProp};
89+
`;
90+
mount = <Component customProp="red" foo="bar" />;
91+
92+
93+
/*
94+
* With explicit theme
95+
*/
96+
97+
type Theme = {
98+
color: {
99+
primary: string,
100+
secondary: string,
101+
}
102+
};
103+
104+
const _styled = styled as ThemedReactEmotionInterface<Theme>;
105+
106+
Component = _styled.div`
107+
color: ${props => props.theme.color.primary}
108+
`;
109+
mount = <Component onClick={event => event} />;
110+
111+
/*
112+
* withComponent
113+
*/
114+
115+
type CustomProps3 = {
116+
bgColor: string,
117+
};
118+
119+
Component = styled.div<CustomProps3>(props => ({
120+
bgColor: props.bgColor,
121+
}));
122+
123+
let Link = Component.withComponent('a');
124+
mount = <Link href="#" bgColor="red" />;
125+
126+
let Button = Component.withComponent('button');
127+
mount = <Button type="submit" bgColor="red" />;
128+
129+
/*
130+
* Can use emotion helpers importing from react-emotion
131+
*/
132+
133+
flush();

0 commit comments

Comments
 (0)