Skip to content

Commit

Permalink
[core] Fix withStyles ignoring defaultProps
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon committed Aug 27, 2018
1 parent 9f47d35 commit efefed7
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 11 deletions.
36 changes: 36 additions & 0 deletions packages/material-ui/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,42 @@ export type ConsistentWith<T, U> = Pick<U, keyof T & keyof U>;
*/
export type Overwrite<T, U> = Omit<T, keyof U> & U;

/**
* a property P will be present if:
* - it is present in DecorationTargetProps
* - it is present in InjectedProps[P] and can satisfy DecorationTargetProps[P]
* or it is not present in InjectedProps[P]
*
* source: @types/react-redux
*/
export type Matching<DecorationTargetProps, InjectedProps> = {
[P in keyof DecorationTargetProps]: P extends keyof InjectedProps
? InjectedProps[P] extends DecorationTargetProps[P] ? DecorationTargetProps[P] : never
: DecorationTargetProps[P]
};

/**
* a property P will be present if :
* - it is present in both DecorationTargetProps and InjectedProps
* - InjectedProps[P] can satisfy DecorationTargetProps[P]
* ie: decorated component can accept more types than decorator is injecting
*
* For decoration, inject props or ownProps are all optionally
* required by the decorated (right hand side) component.
* But any property required by the decorated component must be satisfied by the injected property.
*
* source: @types/react-redux
*/
export type Shared<
InjectedProps,
DecorationTargetProps extends Shared<InjectedProps, DecorationTargetProps>
> = {
[P in Extract<
keyof InjectedProps,
keyof DecorationTargetProps
>]?: InjectedProps[P] extends DecorationTargetProps[P] ? DecorationTargetProps[P] : never
};

export namespace PropTypes {
type Alignment = 'inherit' | 'left' | 'center' | 'right' | 'justify';
type Color = 'inherit' | 'primary' | 'secondary' | 'default';
Expand Down
16 changes: 12 additions & 4 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 { AnyComponent, ConsistentWith, Overwrite, Omit } from '..';
import { Matching, Omit, PropsOf, Shared } from '..';
import { Theme } from './createMuiTheme';
import * as CSS from 'csstype';
import * as JSS from 'jss';
Expand Down Expand Up @@ -60,6 +60,14 @@ export default function withStyles<
>(
style: StyleRulesCallback<ClassKey> | StyleRules<ClassKey>,
options?: Options,
): <P extends ConsistentWith<P, StyledComponentProps<ClassKey> & Partial<WithTheme>>>(
component: AnyComponent<P & WithStyles<ClassKey, Options['withTheme']>>,
) => React.ComponentType<Overwrite<Omit<P, 'theme'>, StyledComponentProps<ClassKey>>>;
): <
C extends React.ComponentType<Matching<PropsOf<C>, WithStyles<ClassKey, Options['withTheme']>>>
>(
component: C,
) => React.ComponentType<
Omit<
JSX.LibraryManagedAttributes<C, PropsOf<C>>,
keyof Shared<WithStyles<ClassKey, Options['withTheme']>, PropsOf<C>>
> &
StyledComponentProps<ClassKey>
>;
88 changes: 81 additions & 7 deletions packages/material-ui/test/typescript/styles.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { StandardProps } from '@material-ui/core';
import { TypographyStyle } from '@material-ui/core/styles/createTypography';

// Shared types for examples
interface ComponentProps {
interface ComponentProps extends WithStyles<typeof styles> {
text: string;
}

Expand All @@ -31,7 +31,7 @@ const styles = ({ palette, spacing }: Theme) => ({
},
});

const StyledExampleOne = withStyles(styles)<ComponentProps>(({ classes, text }) => (
const StyledExampleOne = withStyles(styles)(({ classes, text }: ComponentProps) => (
<div className={classes.root}>{text}</div>
));
<StyledExampleOne text="I am styled!" />;
Expand Down Expand Up @@ -70,7 +70,7 @@ const stylesAsPojo = {

const AnotherStyledSFC = withStyles({
root: { backgroundColor: 'hotpink' },
})(({ classes }) => <div className={classes.root}>Stylish!</div>);
})(({ classes }: WithStyles<'root'>) => <div className={classes.root}>Stylish!</div>);

// Overriding styles
const theme = createMuiTheme({
Expand Down Expand Up @@ -201,7 +201,7 @@ declare const themed: boolean;
);
<Foo />;

const Bar = withStyles({}, { withTheme: true })(({ theme }) => (
const Bar = withStyles({}, { withTheme: true })(({ theme }: WithStyles<string, true>) => (
<div style={{ margin: theme.spacing.unit }} />
));
<Bar />;
Expand Down Expand Up @@ -263,12 +263,13 @@ withStyles(theme =>
});

interface ListItemContentProps extends WithStyles<typeof styles> {
children?: React.ReactElement<any>;
inset?: boolean;
row?: boolean;
}

const ListItemContent = withStyles(styles, { name: 'ui-ListItemContent' })<ListItemContentProps>(
({ children, classes, inset, row }) => (
const ListItemContent = withStyles(styles, { name: 'ui-ListItemContent' })(
({ children, classes, inset, row }: ListItemContentProps) => (
<div className={classes.root} color="textSecondary">
{children}
</div>
Expand All @@ -282,7 +283,7 @@ withStyles(theme =>
b: boolean;
}

const ListItemContent = withStyles({ x: {}, y: {} })<FooProps>(props => <div />);
const ListItemContent = withStyles({ x: {}, y: {} })((props: FooProps) => <div />);
}

{
Expand Down Expand Up @@ -354,3 +355,76 @@ withStyles(theme =>
text: theme.typography.body2,
});
}

{
// can't provide own `classes` type
interface Props {
classes: number;
}

class Component extends React.Component<Props & WithStyles<typeof styles>> {}
// $ExpectError
const StyledComponent = withStyles(styles)(Component);
}

{
// https://github.com/mui-org/material-ui/issues/12670
interface Props {
noDefault: string;
withDefaultProps: number;
}

class MyButton extends React.Component<Props & WithStyles<typeof styles>> {
static defaultProps = {
withDefaultProps: 0,
};

render() {
const { classes, noDefault, withDefaultProps } = this.props;
return (
<Button className={classes.btn}>
{withDefaultProps}, {noDefault}
</Button>
);
}
}

const styles = () =>
createStyles({
btn: {
color: 'red',
},
});

const StyledMyButton = withStyles(styles)(MyButton);

const CorrectUsage = () => <StyledMyButton noDefault="2" />;
// Property 'noDefault' is missing in type '{}'
const MissingPropUsage = () => <StyledMyButton />; // $ExpectError
}

{
// union props
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)(
class extends React.Component<Props> {
render() {
const props = this.props;
return (
<div className={props.classes.root}>
{props.category === 'book' ? props.author : props.artist}
</div>
);
}
},
);
}

0 comments on commit efefed7

Please sign in to comment.