From b5383beb63374778a20370c01fb67f46e7dd2c5c Mon Sep 17 00:00:00 2001 From: Tom Crockett Date: Sun, 12 Aug 2018 21:14:46 -0700 Subject: [PATCH 1/8] Make Omit work with unions --- packages/material-ui/src/ButtonBase/TouchRipple.d.ts | 3 +-- packages/material-ui/src/index.d.ts | 4 ++-- packages/material-ui/src/styles/withTheme.d.ts | 4 ++-- packages/material-ui/test/typescript/styles.spec.tsx | 10 ++++++++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/material-ui/src/ButtonBase/TouchRipple.d.ts b/packages/material-ui/src/ButtonBase/TouchRipple.d.ts index 3c4940f861129c..6aca5abc38530a 100644 --- a/packages/material-ui/src/ButtonBase/TouchRipple.d.ts +++ b/packages/material-ui/src/ButtonBase/TouchRipple.d.ts @@ -2,8 +2,7 @@ import * as React from 'react'; import { TransitionGroup } from 'react-transition-group'; import { StandardProps } from '..'; -export interface TouchRippleProps - extends StandardProps { +export type TouchRippleProps = StandardProps & { center?: boolean; } diff --git a/packages/material-ui/src/index.d.ts b/packages/material-ui/src/index.d.ts index f5f70f083d043e..77bad3f248cd35 100644 --- a/packages/material-ui/src/index.d.ts +++ b/packages/material-ui/src/index.d.ts @@ -39,7 +39,7 @@ export interface Color { * * @internal */ -export type Omit = Pick>; +export type Omit = T extends any ? Pick> : never; /** * `T extends ConsistentWith` means that where `T` has overlapping properties with @@ -54,7 +54,7 @@ export type ConsistentWith = Pick; * * @internal */ -export type Overwrite = (U extends ConsistentWith ? T : Omit) & U; +export type Overwrite = Omit & U; export namespace PropTypes { type Alignment = 'inherit' | 'left' | 'center' | 'right' | 'justify'; diff --git a/packages/material-ui/src/styles/withTheme.d.ts b/packages/material-ui/src/styles/withTheme.d.ts index 71d43b1c8a67ab..636c46971afd56 100644 --- a/packages/material-ui/src/styles/withTheme.d.ts +++ b/packages/material-ui/src/styles/withTheme.d.ts @@ -1,5 +1,5 @@ import { Theme } from './createMuiTheme'; -import { ConsistentWith } from '..'; +import { ConsistentWith, Overwrite } from '..'; export interface WithTheme { theme: Theme; @@ -8,6 +8,6 @@ export interface WithTheme { declare const withTheme: () =>

>( component: React.ComponentType

, -) => React.ComponentClass

; +) => React.ComponentClass>>; export default withTheme; diff --git a/packages/material-ui/test/typescript/styles.spec.tsx b/packages/material-ui/test/typescript/styles.spec.tsx index c3a34c0360e3b2..44e672b0e7e6aa 100644 --- a/packages/material-ui/test/typescript/styles.spec.tsx +++ b/packages/material-ui/test/typescript/styles.spec.tsx @@ -177,6 +177,16 @@ const AllTheComposition = withTheme()( ; +{ + const Foo = withTheme()(class extends React.Component { + render() { + return null; + } + }); + + +} + // Can't use withStyles effectively as a decorator in TypeScript // due to https://github.com/Microsoft/TypeScript/issues/4881 //@withStyles(styles) From 366b2a9a0bddb4f1e621de51493e7fb8f718647b Mon Sep 17 00:00:00 2001 From: Tom Crockett Date: Sun, 12 Aug 2018 21:16:16 -0700 Subject: [PATCH 2/8] Use React.ComponentType instead of React.ComponentClass --- packages/material-ui/src/styles/withTheme.d.ts | 2 +- packages/material-ui/src/withWidth/withWidth.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/material-ui/src/styles/withTheme.d.ts b/packages/material-ui/src/styles/withTheme.d.ts index 636c46971afd56..a07bab65a07174 100644 --- a/packages/material-ui/src/styles/withTheme.d.ts +++ b/packages/material-ui/src/styles/withTheme.d.ts @@ -8,6 +8,6 @@ export interface WithTheme { declare const withTheme: () =>

>( component: React.ComponentType

, -) => React.ComponentClass>>; +) => React.ComponentType>>; export default withTheme; diff --git a/packages/material-ui/src/withWidth/withWidth.d.ts b/packages/material-ui/src/withWidth/withWidth.d.ts index 5ded5189a33bf8..6ed64f3373b5e9 100644 --- a/packages/material-ui/src/withWidth/withWidth.d.ts +++ b/packages/material-ui/src/withWidth/withWidth.d.ts @@ -26,4 +26,4 @@ export default function withWidth( options?: WithWidthOptions, ):

>( component: React.ComponentType

, -) => React.ComponentClass>>; +) => React.ComponentType>>; From 9534b149b6a6eb7479abe60bd4879aa251f46e4e Mon Sep 17 00:00:00 2001 From: Tom Crockett Date: Sun, 12 Aug 2018 21:17:12 -0700 Subject: [PATCH 3/8] Rename WithWidthProps to WithWidth for consistency with WithTheme and WithStyles --- .../src/withMobileDialog/withMobileDialog.d.ts | 6 +++--- packages/material-ui/src/withWidth/withWidth.d.ts | 8 ++++---- packages/material-ui/src/withWidth/withWidth.spec.tsx | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/material-ui/src/withMobileDialog/withMobileDialog.d.ts b/packages/material-ui/src/withMobileDialog/withMobileDialog.d.ts index e77257151c1663..73f91880f43b90 100644 --- a/packages/material-ui/src/withMobileDialog/withMobileDialog.d.ts +++ b/packages/material-ui/src/withMobileDialog/withMobileDialog.d.ts @@ -1,5 +1,5 @@ import { Breakpoint } from '../styles/createBreakpoints'; -import { WithWidthProps } from '../withWidth'; +import { WithWidth } from '../withWidth'; export interface WithMobileDialogOptions { breakpoint: Breakpoint; @@ -12,5 +12,5 @@ export interface InjectedProps { export default function withMobileDialog

( options?: WithMobileDialogOptions, ): ( - component: React.ComponentType

>, -) => React.ComponentType

>; + component: React.ComponentType

>, +) => React.ComponentType

>; diff --git a/packages/material-ui/src/withWidth/withWidth.d.ts b/packages/material-ui/src/withWidth/withWidth.d.ts index 6ed64f3373b5e9..e7068f03e1483c 100644 --- a/packages/material-ui/src/withWidth/withWidth.d.ts +++ b/packages/material-ui/src/withWidth/withWidth.d.ts @@ -5,7 +5,7 @@ export interface WithWidthOptions { resizeInterval: number; } -export interface WithWidthProps { +export interface WithWidth { width: Breakpoint; innerRef?: React.Ref | React.RefObject; } @@ -24,6 +24,6 @@ export function isWidthUp( export default function withWidth( options?: WithWidthOptions, -):

>( - component: React.ComponentType

, -) => React.ComponentType>>; +):

>( + component: React.ComponentType

, +) => React.ComponentType>>; diff --git a/packages/material-ui/src/withWidth/withWidth.spec.tsx b/packages/material-ui/src/withWidth/withWidth.spec.tsx index efb38577115cd6..4bddf954709ca3 100644 --- a/packages/material-ui/src/withWidth/withWidth.spec.tsx +++ b/packages/material-ui/src/withWidth/withWidth.spec.tsx @@ -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({ @@ -13,7 +13,7 @@ const styles = (theme: Theme) => }, }); -interface IHelloProps extends WithWidthProps, WithStyles { +interface IHelloProps extends WithWidth, WithStyles { name?: string; } From 5061d0a85ee95055a876ec6f5df65a3ae8f76253 Mon Sep 17 00:00:00 2001 From: Tom Crockett Date: Sun, 12 Aug 2018 23:32:42 -0700 Subject: [PATCH 4/8] Add AnyComponent and WithProps helpers to eliminate need for type annotation --- packages/material-ui/src/index.d.ts | 11 +++++++++++ packages/material-ui/src/styles/withStyles.d.ts | 4 ++-- packages/material-ui/src/styles/withTheme.d.ts | 4 ++-- packages/material-ui/src/withWidth/withWidth.d.ts | 4 ++-- .../test/typescript/styling-comparison.spec.tsx | 2 +- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/material-ui/src/index.d.ts b/packages/material-ui/src/index.d.ts index 77bad3f248cd35..e5451139cbc3ed 100644 --- a/packages/material-ui/src/index.d.ts +++ b/packages/material-ui/src/index.d.ts @@ -2,6 +2,17 @@ import * as React from 'react'; import { StyledComponentProps } from './styles'; export { StyledComponentProps }; +export type AnyComponent

= + | (new (p: P) => React.Component) + | ((props: P & { children?: React.ReactNode }) => React.ReactElement

| null) + ; + +export type PropsOf = + C extends new (p: infer P) => React.Component ? P : + C extends (p: infer P) => React.ReactElement | 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 diff --git a/packages/material-ui/src/styles/withStyles.d.ts b/packages/material-ui/src/styles/withStyles.d.ts index 4022f2a7e69076..78d871bfd6f07b 100644 --- a/packages/material-ui/src/styles/withStyles.d.ts +++ b/packages/material-ui/src/styles/withStyles.d.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { WithTheme } from '../styles/withTheme'; -import { ConsistentWith, Overwrite } from '..'; +import { AnyComponent, ConsistentWith, Overwrite } from '..'; import { Theme } from './createMuiTheme'; import * as CSS from 'csstype'; import * as JSS from 'jss'; @@ -58,6 +58,6 @@ export default function withStyles( options?: WithStylesOptions, ): {

>>( - component: React.ComponentType

>, + component: AnyComponent

>, ): React.ComponentType>>; }; diff --git a/packages/material-ui/src/styles/withTheme.d.ts b/packages/material-ui/src/styles/withTheme.d.ts index a07bab65a07174..2f0b90a3ec65cc 100644 --- a/packages/material-ui/src/styles/withTheme.d.ts +++ b/packages/material-ui/src/styles/withTheme.d.ts @@ -1,5 +1,5 @@ import { Theme } from './createMuiTheme'; -import { ConsistentWith, Overwrite } from '..'; +import { AnyComponent, ConsistentWith, Overwrite } from '..'; export interface WithTheme { theme: Theme; @@ -7,7 +7,7 @@ export interface WithTheme { } declare const withTheme: () =>

>( - component: React.ComponentType

, + component: AnyComponent

, ) => React.ComponentType>>; export default withTheme; diff --git a/packages/material-ui/src/withWidth/withWidth.d.ts b/packages/material-ui/src/withWidth/withWidth.d.ts index e7068f03e1483c..791406f5bc0543 100644 --- a/packages/material-ui/src/withWidth/withWidth.d.ts +++ b/packages/material-ui/src/withWidth/withWidth.d.ts @@ -1,5 +1,5 @@ import { Breakpoint } from '../styles/createBreakpoints'; -import { ConsistentWith, Overwrite } from '..'; +import { AnyComponent, ConsistentWith, Overwrite } from '..'; export interface WithWidthOptions { resizeInterval: number; @@ -25,5 +25,5 @@ export function isWidthUp( export default function withWidth( options?: WithWidthOptions, ):

>( - component: React.ComponentType

, + component: AnyComponent

, ) => React.ComponentType>>; diff --git a/packages/material-ui/test/typescript/styling-comparison.spec.tsx b/packages/material-ui/test/typescript/styling-comparison.spec.tsx index b35215daaaf5e0..f838d7fdc16665 100644 --- a/packages/material-ui/test/typescript/styling-comparison.spec.tsx +++ b/packages/material-ui/test/typescript/styling-comparison.spec.tsx @@ -60,7 +60,7 @@ interface Painting { type ArtProps = Book | Painting; -const DecoratedUnionProps = withStyles(styles)( // <-- without the type argument, we'd get a compiler error! +const DecoratedUnionProps = withStyles(styles)( class extends React.Component> { render() { const props = this.props; From 9bccf72ef7a62d45dcc06ded43d3626c8fd4bcce Mon Sep 17 00:00:00 2001 From: Tom Crockett Date: Mon, 13 Aug 2018 00:16:48 -0700 Subject: [PATCH 5/8] Get the typing right when passing withTheme: true --- .../material-ui/src/styles/withStyles.d.ts | 26 +++++++++---------- .../test/typescript/styles.spec.tsx | 16 ++++++++++++ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/material-ui/src/styles/withStyles.d.ts b/packages/material-ui/src/styles/withStyles.d.ts index 78d871bfd6f07b..9d7f01ae67f286 100644 --- a/packages/material-ui/src/styles/withStyles.d.ts +++ b/packages/material-ui/src/styles/withStyles.d.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { WithTheme } from '../styles/withTheme'; -import { AnyComponent, ConsistentWith, Overwrite } from '..'; +import { AnyComponent, ConsistentWith, Overwrite, Omit } from '..'; import { Theme } from './createMuiTheme'; import * as CSS from 'csstype'; import * as JSS from 'jss'; @@ -38,13 +38,13 @@ export interface WithStylesOptions export type ClassNameMap = Record; -export type WithStyles = Partial< - WithTheme -> & { - classes: ClassNameMap< - T extends string - ? T - : T extends StyleRulesCallback ? K : T extends StyleRules ? K : never +export type WithStyles = + (IncludeTheme extends true ? WithTheme : Partial) + & { + classes: ClassNameMap< + T extends string + ? T + : T extends StyleRulesCallback ? K : T extends StyleRules ? K : never >; }; @@ -53,11 +53,11 @@ export interface StyledComponentProps { innerRef?: React.Ref | React.RefObject; } -export default function withStyles( +export default function withStyles>( style: StyleRulesCallback | StyleRules, - options?: WithStylesOptions, + options?: Options, ): { -

>>( - component: AnyComponent

>, - ): React.ComponentType>>; +

& Partial>>( + component: AnyComponent

>, + ): React.ComponentType, StyledComponentProps>>; }; diff --git a/packages/material-ui/test/typescript/styles.spec.tsx b/packages/material-ui/test/typescript/styles.spec.tsx index 44e672b0e7e6aa..32c31e64b0f003 100644 --- a/packages/material-ui/test/typescript/styles.spec.tsx +++ b/packages/material-ui/test/typescript/styles.spec.tsx @@ -187,6 +187,22 @@ const AllTheComposition = withTheme()( } +declare const themed: boolean; +{ + // Test that withTheme: true guarantees the presence of the theme + const Foo = withStyles({}, { withTheme: true })(class extends React.Component { + render() { + return

; + } + }); + ; + + const Bar = withStyles({}, { withTheme: true })(({ theme }) => ( +
+ )); + ; +} + // Can't use withStyles effectively as a decorator in TypeScript // due to https://github.com/Microsoft/TypeScript/issues/4881 //@withStyles(styles) From 1abd428e9fab16f7170c7ac2f0a36a91336d40d3 Mon Sep 17 00:00:00 2001 From: Tom Crockett Date: Mon, 13 Aug 2018 00:18:28 -0700 Subject: [PATCH 6/8] Remove caveat about union props from the docs --- .../src/pages/guides/typescript/typescript.md | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/docs/src/pages/guides/typescript/typescript.md b/docs/src/pages/guides/typescript/typescript.md index 98ab68b3400284..ac5bec3b7be542 100644 --- a/docs/src/pages/guides/typescript/typescript.md +++ b/docs/src/pages/guides/typescript/typescript.md @@ -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 `` parameter to `decorate`: - -```tsx -interface Book { - category: "book"; - author: string; -} - -interface Painting { - category: "painting"; - artist: string; -} - -type BookOrPainting = Book | Painting; - -type Props = BookOrPainting & WithStyles; - -const DecoratedUnionProps = withStyles(styles)( // <-- without the type argument, we'd get a compiler error! - class extends React.Component { - render() { - const props = this.props; - return ( - - {props.category === "book" ? props.author : props.artist} - - ); - } - } -); -``` - ## Customization of `Theme` When adding custom properties to the `Theme`, you may continue to use it in a strongly typed way by exploiting From e387b734385cb1de677c2367a34790c6ab90e975 Mon Sep 17 00:00:00 2001 From: Tom Crockett Date: Mon, 13 Aug 2018 08:23:57 -0700 Subject: [PATCH 7/8] Tweak variable names --- packages/material-ui/src/index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/material-ui/src/index.d.ts b/packages/material-ui/src/index.d.ts index e5451139cbc3ed..96a57438749f69 100644 --- a/packages/material-ui/src/index.d.ts +++ b/packages/material-ui/src/index.d.ts @@ -3,13 +3,13 @@ import { StyledComponentProps } from './styles'; export { StyledComponentProps }; export type AnyComponent

= - | (new (p: P) => React.Component) + | (new (props: P) => React.Component) | ((props: P & { children?: React.ReactNode }) => React.ReactElement

| null) ; export type PropsOf = - C extends new (p: infer P) => React.Component ? P : - C extends (p: infer P) => React.ReactElement | null ? P : + C extends new (props: infer P) => React.Component ? P : + C extends (props: infer P) => React.ReactElement | null ? P : never; From 17ecf9df0dc322f3a10065c5ab16a6f8f818c51a Mon Sep 17 00:00:00 2001 From: Tom Crockett Date: Mon, 13 Aug 2018 08:24:28 -0700 Subject: [PATCH 8/8] Formatting --- packages/material-ui/src/index.d.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/material-ui/src/index.d.ts b/packages/material-ui/src/index.d.ts index 96a57438749f69..89cc0d8c34e87b 100644 --- a/packages/material-ui/src/index.d.ts +++ b/packages/material-ui/src/index.d.ts @@ -4,15 +4,13 @@ export { StyledComponentProps }; export type AnyComponent

= | (new (props: P) => React.Component) - | ((props: P & { children?: React.ReactNode }) => React.ReactElement

| null) - ; + | ((props: P & { children?: React.ReactNode }) => React.ReactElement

| null); export type PropsOf = C extends new (props: infer P) => React.Component ? P : C extends (props: infer P) => React.ReactElement | 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