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 5 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
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.5"
},
"devDependencies": {
"@types/react": "^16.0.10",
"cross-env": "^5.0.5",
"emotion": "^8.0.5",
"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
14 changes: 14 additions & 0 deletions packages/react-emotion/typescript_tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"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",
"strict": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"jsx": "react"
},
"include": [
"./*.ts",
"./*.tsx"
]
}
127 changes: 127 additions & 0 deletions packages/react-emotion/typescript_tests/typescript_tests.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from 'react';
import styled, { 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" />;
103 changes: 103 additions & 0 deletions packages/react-emotion/typings/react-emotion.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { StatelessComponent, Component as ClassComponent, CSSProperties } from 'react'
import { Interpolation as EmotionInterpolation } from 'emotion'

export type InterpolationFn<Props = {}> = (
props: Props
) => EmotionInterpolation

export type Interpolation<Props = {}> =
| InterpolationFn<Props>
| EmotionInterpolation
Copy link
Member

Choose a reason for hiding this comment

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

I don't know much about typescript but I don't think this handles the fact that there can be nested function interpolations and they will receive props.

const SomeComponent = styled.div`
  display: ${(p) => props => props.display};
  color: ${[props => 'hotpink']};
`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It works on emotion?
props => props_ => ...

It doesn't make any sense to me. What's this use case for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know exactly all edge cases of emotion API. I'll try to study it to improve the coverage support.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mitchellhamilton I've added support to these two cases you mentioned.

export type InterpolationFn<Props = {}> =
  (props: Props) =>
    | EmotionInterpolation
    | InterpolationFn<Props>

export type InterpolationTypes<Props = {}> =
  | InterpolationFn<Props>
  | EmotionInterpolation

export type Interpolation<Props = {}> =
  | InterpolationTypes<Props>
  | InterpolationTypes<Props>[]

If you know more cases, just tell me, please.


export interface Options {
string?: string,
}

type Component<Props> =
| ClassComponent<Props>
| StatelessComponent<Props>

export type ThemedProps<Props, Theme> = Props & {
theme: Theme,
}

export interface StyledComponent<Props, Theme, IntrinsicProps>
extends
ClassComponent<Props & IntrinsicProps>,
StatelessComponent<Props & IntrinsicProps>
{
withComponent<Tag extends keyof JSX.IntrinsicElements>(tag: Tag):
StyledComponent<Props, Theme, JSX.IntrinsicElements[Tag]>

withComponent(component: Component<Props>):
StyledComponent<Props, Theme, {}>

displayName: string

__emotion_styles: string[]
__emotion_base: string | Component<Props & IntrinsicProps>
__emotion_real: ThemedReactEmotionInterface<Theme>
}

export type ObjectStyleAttributes =
| CSSProperties
| { [key: string]: ObjectStyleAttributes }

export interface CreateStyled<Props, Theme, IntrinsicProps> {
// overload for template string as styles
(
strings: TemplateStringsArray,
...vars: Interpolation<ThemedProps<Props & IntrinsicProps, Theme>>[],
): StyledComponent<Props, Theme, IntrinsicProps>

// overload for object as styles
(
...styles: (
| ObjectStyleAttributes
| ((props: ThemedProps<Props & IntrinsicProps, Theme>) => ObjectStyleAttributes)
)[]
): StyledComponent<Props, Theme, IntrinsicProps>
}

// TODO: find a way to reuse CreateStyled here
// for now I needed to repeat all fn types/overloads
type ShorthandsFactories<Theme> = {
[Tag in keyof JSX.IntrinsicElements]: {
// overload for template string as styles
<Props = {}>(
strings: TemplateStringsArray,
...vars: Interpolation<ThemedProps<Props & JSX.IntrinsicElements[Tag], Theme>>[],
): StyledComponent<Props, Theme, JSX.IntrinsicElements[Tag]>

// overload for object as styles
<Props = {}>(
...styles: (
| ObjectStyleAttributes
| ((props: ThemedProps<Props & JSX.IntrinsicElements[Tag], Theme>) => ObjectStyleAttributes)
)[]
): StyledComponent<Props, Theme, JSX.IntrinsicElements[Tag]>
};
};

export interface ThemedReactEmotionInterface<Theme> extends ShorthandsFactories<Theme> {
// overload for dom tag
<Props, Tag extends keyof JSX.IntrinsicElements>(
tag: Tag | Component<Props>,
options?: Options,
): CreateStyled<Props, Theme, JSX.IntrinsicElements[Tag]>

// overload for component
<Props>(
component: Component<Props>,
options?: Options,
): CreateStyled<Props, Theme, {}>
}

export interface ThemedReactEmotionModule<Theme> {
default: ThemedReactEmotionInterface<Theme>
}

declare const styled: ThemedReactEmotionInterface<any>
export default styled