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 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..89cc0d8c34e87b 100644 --- a/packages/material-ui/src/index.d.ts +++ b/packages/material-ui/src/index.d.ts @@ -2,6 +2,15 @@ import * as React from 'react'; import { StyledComponentProps } from './styles'; export { StyledComponentProps }; +export type AnyComponent

= + | (new (props: P) => React.Component) + | ((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 @@ -39,7 +48,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 +63,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/withStyles.d.ts b/packages/material-ui/src/styles/withStyles.d.ts index 4022f2a7e69076..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 { 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: React.ComponentType

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

& Partial>>( + component: AnyComponent

>, + ): React.ComponentType, StyledComponentProps>>; }; diff --git a/packages/material-ui/src/styles/withTheme.d.ts b/packages/material-ui/src/styles/withTheme.d.ts index 71d43b1c8a67ab..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 } from '..'; +import { AnyComponent, ConsistentWith, Overwrite } from '..'; export interface WithTheme { theme: Theme; @@ -7,7 +7,7 @@ export interface WithTheme { } declare const withTheme: () =>

>( - component: React.ComponentType

, -) => React.ComponentClass

; + component: AnyComponent

, +) => React.ComponentType>>; export default withTheme; 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 5ded5189a33bf8..791406f5bc0543 100644 --- a/packages/material-ui/src/withWidth/withWidth.d.ts +++ b/packages/material-ui/src/withWidth/withWidth.d.ts @@ -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 | React.RefObject; } @@ -24,6 +24,6 @@ export function isWidthUp( export default function withWidth( options?: WithWidthOptions, -):

>( - component: React.ComponentType

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

>( + component: AnyComponent

, +) => 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; } diff --git a/packages/material-ui/test/typescript/styles.spec.tsx b/packages/material-ui/test/typescript/styles.spec.tsx index c3a34c0360e3b2..32c31e64b0f003 100644 --- a/packages/material-ui/test/typescript/styles.spec.tsx +++ b/packages/material-ui/test/typescript/styles.spec.tsx @@ -177,6 +177,32 @@ const AllTheComposition = withTheme()( ; +{ + const Foo = withTheme()(class extends React.Component { + render() { + return null; + } + }); + + +} + +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) 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;