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

Add types to react-emotion #398

Merged
merged 19 commits into from
Oct 16, 2017
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
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",

Choose a reason for hiding this comment

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

For glamorous we've added declaration: true to help make sure we're exporting everything required for use in libraries microsoft/TypeScript#5938.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! Added.
No warnings
😄

"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