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

[typescript] Style typing improvements #12492

Merged
merged 8 commits into from
Aug 13, 2018
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
33 changes: 0 additions & 33 deletions docs/src/pages/guides/typescript/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,39 +146,6 @@ const DecoratedClass = withStyles(styles)(

Unfortunately due to a [current limitation of TypeScript decorators](https://github.com/Microsoft/TypeScript/issues/4881), `withStyles(styles)` can't be used as a decorator in TypeScript.

### Union props

When your `props` are a union, Typescript needs you to explicitly tell it the type, by providing a generic `<Props>` parameter to `decorate`:

```tsx
interface Book {
category: "book";
author: string;
}

interface Painting {
category: "painting";
artist: string;
}

type BookOrPainting = Book | Painting;

type Props = BookOrPainting & WithStyles<typeof styles>;

const DecoratedUnionProps = withStyles(styles)<BookOrPainting>( // <-- without the type argument, we'd get a compiler error!
class extends React.Component<Props> {
render() {
const props = this.props;
return (
<Typography classes={props.classes}>
{props.category === "book" ? props.author : props.artist}
</Typography>
);
}
}
);
```

## Customization of `Theme`

When adding custom properties to the `Theme`, you may continue to use it in a strongly typed way by exploiting
Expand Down
3 changes: 1 addition & 2 deletions packages/material-ui/src/ButtonBase/TouchRipple.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import * as React from 'react';
import { TransitionGroup } from 'react-transition-group';
import { StandardProps } from '..';

export interface TouchRippleProps
extends StandardProps<TransitionGroup.TransitionGroupProps, TouchRippleClassKey> {
export type TouchRippleProps = StandardProps<TransitionGroup.TransitionGroupProps, TouchRippleClassKey> & {
center?: boolean;
}

Expand Down
13 changes: 11 additions & 2 deletions packages/material-ui/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import * as React from 'react';
import { StyledComponentProps } from './styles';
export { StyledComponentProps };

export type AnyComponent<P = any> =
| (new (props: P) => React.Component)
| ((props: P & { children?: React.ReactNode }) => React.ReactElement<P> | null);

export type PropsOf<C extends AnyComponent> =
C extends new (props: infer P) => React.Component ? P :
C extends (props: infer P) => React.ReactElement<any> | null ? P :
never;

/**
* All standard components exposed by `material-ui` are `StyledComponents` with
* certain `classes`, on which one can also set a top-level `className` and inline
Expand Down Expand Up @@ -39,7 +48,7 @@ export interface Color {
*
* @internal
*/
export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
export type Omit<T, K extends keyof any> = T extends any ? Pick<T, Exclude<keyof T, K>> : never;

/**
* `T extends ConsistentWith<T, U>` means that where `T` has overlapping properties with
Expand All @@ -54,7 +63,7 @@ export type ConsistentWith<T, U> = Pick<U, keyof T & keyof U>;
*
* @internal
*/
export type Overwrite<T, U> = (U extends ConsistentWith<U, T> ? T : Omit<T, keyof U>) & U;
export type Overwrite<T, U> = Omit<T, keyof U> & U;

export namespace PropTypes {
type Alignment = 'inherit' | 'left' | 'center' | 'right' | 'justify';
Expand Down
26 changes: 13 additions & 13 deletions packages/material-ui/src/styles/withStyles.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { WithTheme } from '../styles/withTheme';
import { ConsistentWith, Overwrite } from '..';
import { AnyComponent, ConsistentWith, Overwrite, Omit } from '..';
import { Theme } from './createMuiTheme';
import * as CSS from 'csstype';
import * as JSS from 'jss';
Expand Down Expand Up @@ -38,13 +38,13 @@ export interface WithStylesOptions<ClassKey extends string = string>

export type ClassNameMap<ClassKey extends string = string> = Record<ClassKey, string>;

export type WithStyles<T extends string | StyleRules | StyleRulesCallback = string> = Partial<
WithTheme
> & {
classes: ClassNameMap<
T extends string
? T
: T extends StyleRulesCallback<infer K> ? K : T extends StyleRules<infer K> ? K : never
export type WithStyles<T extends string | StyleRules | StyleRulesCallback = string, IncludeTheme extends boolean | undefined = undefined> =
(IncludeTheme extends true ? WithTheme : Partial<WithTheme>)
& {
classes: ClassNameMap<
T extends string
? T
: T extends StyleRulesCallback<infer K> ? K : T extends StyleRules<infer K> ? K : never
>;
};

Expand All @@ -53,11 +53,11 @@ export interface StyledComponentProps<ClassKey extends string = string> {
innerRef?: React.Ref<any> | React.RefObject<any>;
}

export default function withStyles<ClassKey extends string>(
export default function withStyles<ClassKey extends string, Options extends WithStylesOptions<ClassKey>>(
style: StyleRulesCallback<ClassKey> | StyleRules<ClassKey>,
options?: WithStylesOptions<ClassKey>,
options?: Options,
): {
<P extends ConsistentWith<P, StyledComponentProps<ClassKey>>>(
component: React.ComponentType<P & WithStyles<ClassKey>>,
): React.ComponentType<Overwrite<P, StyledComponentProps<ClassKey>>>;
<P extends ConsistentWith<P, StyledComponentProps<ClassKey> & Partial<WithTheme>>>(
component: AnyComponent<P & WithStyles<ClassKey, Options['withTheme']>>,
): React.ComponentType<Overwrite<Omit<P, 'theme'>, StyledComponentProps<ClassKey>>>;
};
6 changes: 3 additions & 3 deletions packages/material-ui/src/styles/withTheme.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Theme } from './createMuiTheme';
import { ConsistentWith } from '..';
import { AnyComponent, ConsistentWith, Overwrite } from '..';

export interface WithTheme {
theme: Theme;
innerRef?: React.Ref<any> | React.RefObject<any>;
}

declare const withTheme: () => <P extends ConsistentWith<P, WithTheme>>(
component: React.ComponentType<P & WithTheme>,
) => React.ComponentClass<P>;
component: AnyComponent<P & WithTheme>,
) => React.ComponentType<Overwrite<P, Partial<WithTheme>>>;

export default withTheme;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Breakpoint } from '../styles/createBreakpoints';
import { WithWidthProps } from '../withWidth';
import { WithWidth } from '../withWidth';

export interface WithMobileDialogOptions {
breakpoint: Breakpoint;
Expand All @@ -12,5 +12,5 @@ export interface InjectedProps {
export default function withMobileDialog<P = {}>(
options?: WithMobileDialogOptions,
): (
component: React.ComponentType<P & InjectedProps & Partial<WithWidthProps>>,
) => React.ComponentType<P & Partial<WithWidthProps>>;
component: React.ComponentType<P & InjectedProps & Partial<WithWidth>>,
) => React.ComponentType<P & Partial<WithWidth>>;
10 changes: 5 additions & 5 deletions packages/material-ui/src/withWidth/withWidth.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Breakpoint } from '../styles/createBreakpoints';
import { ConsistentWith, Overwrite } from '..';
import { AnyComponent, ConsistentWith, Overwrite } from '..';

export interface WithWidthOptions {
resizeInterval: number;
}

export interface WithWidthProps {
export interface WithWidth {
width: Breakpoint;
innerRef?: React.Ref<any> | React.RefObject<any>;
}
Expand All @@ -24,6 +24,6 @@ export function isWidthUp(

export default function withWidth(
options?: WithWidthOptions,
): <P extends ConsistentWith<P, WithWidthProps>>(
component: React.ComponentType<P & WithWidthProps>,
) => React.ComponentClass<Overwrite<P, Partial<WithWidthProps>>>;
): <P extends ConsistentWith<P, WithWidth>>(
component: AnyComponent<P & WithWidth>,
) => React.ComponentType<Overwrite<P, Partial<WithWidth>>>;
4 changes: 2 additions & 2 deletions packages/material-ui/src/withWidth/withWidth.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { Grid } from '..';
import { Theme, createStyles } from '../styles';
import withStyles, { WithStyles } from '../styles/withStyles';
import withWidth, { WithWidthProps } from '../withWidth';
import withWidth, { WithWidth } from '../withWidth';

const styles = (theme: Theme) =>
createStyles({
Expand All @@ -13,7 +13,7 @@ const styles = (theme: Theme) =>
},
});

interface IHelloProps extends WithWidthProps, WithStyles<typeof styles> {
interface IHelloProps extends WithWidth, WithStyles<typeof styles> {
name?: string;
}

Expand Down
26 changes: 26 additions & 0 deletions packages/material-ui/test/typescript/styles.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,32 @@ const AllTheComposition = withTheme()(

<AllTheComposition />;

{
const Foo = withTheme()(class extends React.Component<WithTheme> {
render() {
return null;
}
});

<Foo />
}

declare const themed: boolean;
{
// Test that withTheme: true guarantees the presence of the theme
const Foo = withStyles({}, { withTheme: true })(class extends React.Component<WithTheme> {
render() {
return <div style={{ margin: this.props.theme.spacing.unit }} />;
}
});
<Foo />;

const Bar = withStyles({}, { withTheme: true })(({ theme }) => (
<div style={{ margin: theme.spacing.unit }} />
));
<Bar />;
}

// Can't use withStyles effectively as a decorator in TypeScript
// due to https://github.com/Microsoft/TypeScript/issues/4881
//@withStyles(styles)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ interface Painting {

type ArtProps = Book | Painting;

const DecoratedUnionProps = withStyles(styles)<ArtProps>( // <-- without the type argument, we'd get a compiler error!
const DecoratedUnionProps = withStyles(styles)(
class extends React.Component<ArtProps & WithStyles<typeof styles>> {
render() {
const props = this.props;
Expand Down