Skip to content

Commit

Permalink
[typescript] Improve type definition for withStyles (#8320)
Browse files Browse the repository at this point in the history
* Improve type definition for withStyles

* Capture the props enhancer type as WithStyles

* Reexport WithStyles from styles

* Reexport WithStyles from root

* Update tests

* Provide defaults for Names type parameter for backwards compat

* Minor refactors

* Default type argument

* Remove unnecessary StyleRules annotation

* Simplify definition of StyleRules, add semicolons

* Use anonymous class in DecoratedComponent testcase
  • Loading branch information
pelotom authored and oliviertassinari committed Sep 22, 2017
1 parent 8c84d42 commit 4cdfc94
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 57 deletions.
12 changes: 7 additions & 5 deletions src/index.d.ts
@@ -1,5 +1,7 @@
import * as React from 'react';

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

/**
* Component exposed by `material-ui` are usually wrapped
* with the `withStyles` HOC and allow customization via
Expand All @@ -10,14 +12,14 @@ import * as React from 'react';
* - `style`
* - `innerRef`
*/
export interface StyledComponentProps<StyleClasses> {
export interface StyledComponentProps<Names extends string = string> {
className?: string;
classes?: StyleClasses;
classes?: ClassNameMap<Names>;
style?: Partial<React.CSSProperties>;
innerRef?: React.Ref<any>;
}
export class StyledComponent<P, C = Object> extends React.Component<
P & StyledComponentProps<C>
export class StyledComponent<P = {}, Names extends string = string> extends React.Component<
P & StyledComponentProps<Names>
> {}

export type Contrast = 'light' | 'dark' | 'brown';
Expand Down Expand Up @@ -94,7 +96,7 @@ export { CircularProgress, LinearProgress } from './Progress';
export { default as Radio, RadioGroup } from './Radio';
export { default as Select } from './Select';
export { default as Snackbar, SnackbarContent } from './Snackbar';
export { MuiThemeProvider, withStyles, withTheme, createMuiTheme } from './styles';
export { MuiThemeProvider, withStyles, WithStyles, withTheme, createMuiTheme } from './styles';

import * as colors from './colors';

Expand Down
2 changes: 1 addition & 1 deletion src/styles/index.d.ts
Expand Up @@ -3,6 +3,6 @@ export { default as createBreakpoints } from './createBreakpoints';
export { default as createMuiTheme, Theme } from './createMuiTheme';
export { default as createPalette } from './createPalette';
export { default as createTypography } from './createTypography';
export { default as withStyles } from './withStyles';
export { default as withStyles, WithStyles } from './withStyles';
export { StyleRules, StyleRulesCallback } from './withStyles';
export { default as withTheme } from './withTheme';
34 changes: 12 additions & 22 deletions src/styles/withStyles.d.ts
@@ -1,5 +1,5 @@
import * as React from 'react';
import { StyledComponentProps } from '..';
import { ClassNameMap, StyledComponentProps } from '..';
import { Theme } from './createMuiTheme';

/**
Expand All @@ -9,33 +9,23 @@ import { Theme } from './createMuiTheme';
* - the `keys` are the class (names) that will be created
* - the `values` are objects that represent CSS rules (`React.CSSProperties`).
*/
export interface StyleRules {
[displayName: string]: Partial<React.CSSProperties>;
}
export type StyleRules<Names extends string = string> = Record<Names, Partial<React.CSSProperties>>;

export type StyleRulesCallback = (theme: Theme) => StyleRules;
export type StyleRulesCallback<Names extends string = string> = (theme: Theme) => StyleRules<Names>;

export interface WithStylesOptions {
withTheme?: boolean;
name?: string;
}

declare function withStyles(
style: StyleRules | StyleRulesCallback,
options?: WithStylesOptions
): <
C extends React.ComponentType<P & { classes: ClassNames; theme?: Theme }>,
P = {},
ClassNames = {}
>(
component: C
) => C & React.ComponentClass<P & StyledComponentProps<ClassNames>>
export type WithStyles<P, Names extends string = string> = P & {
classes: ClassNameMap<Names>
theme?: Theme
};

declare function withStyles<P = {}, ClassNames = {}>(
style: StyleRules | StyleRulesCallback,
export default function withStyles<Names extends string>(
style: StyleRules<Names> | StyleRulesCallback<Names>,
options?: WithStylesOptions
): (
component: React.ComponentType<P & { classes: ClassNames; theme?: Theme }>
) => React.ComponentClass<P & StyledComponentProps<ClassNames>>

export default withStyles;
): <P>(
component: React.ComponentType<WithStyles<P, Names>>
) => React.ComponentType<P & StyledComponentProps<Names>>;
2 changes: 1 addition & 1 deletion test/typescript/components.spec.tsx
Expand Up @@ -632,7 +632,7 @@ const StepperTest = () =>
};

const TableTest = () => {
const styles: StyleRulesCallback = theme => ({
const styles: StyleRulesCallback<'paper'> = theme => ({
paper: {
width: '100%',
marginTop: theme.spacing.unit * 3,
Expand Down
56 changes: 28 additions & 28 deletions test/typescript/styles.spec.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import {
withStyles,
StyleRules,
WithStyles,
createMuiTheme,
MuiThemeProvider,
Theme,
Expand All @@ -25,29 +25,25 @@ interface StyledComponentProps {
text: string;
}

const Component: React.SFC<
StyledComponentProps & { classes: StyledComponentClassNames }
> = ({ classes, text }) =>
<div className={classes.root}>
{text}
</div>;

const StyledComponent = withStyles<
StyledComponentProps,
StyledComponentClassNames
>(styles)(Component);
const StyledComponent = withStyles(styles)<StyledComponentProps>(
({ classes, text }) => (
<div className={classes.root}>
{text}
</div>
)
);

<StyledComponent text="I am styled!" />;

// Also works with a plain object

const stylesAsPojo: StyleRules = {
const stylesAsPojo = {
root: {
background: 'hotpink',
},
};

const AnotherStyledComponent = withStyles<{}, StyledComponentClassNames>({
const AnotherStyledComponent = withStyles({
root: { background: 'hotpink' },
})(({ classes }) => <div className={classes.root}>Stylish!</div>);

Expand Down Expand Up @@ -107,20 +103,24 @@ const AllTheStyles: React.SFC<AllTheProps> = ({ theme, classes }) =>
</div>;

const AllTheComposition = withTheme(
withStyles<{ theme: Theme }, StyledComponentClassNames>(styles)(AllTheStyles)
withStyles(styles)(AllTheStyles)
);

// As decorator
@withStyles(styles)
class DecoratedComponent extends React.Component<
StyledComponentProps & { classes: StyledComponentClassNames }
> {
render() {
const { classes, text } = this.props;
return (
<div className={classes.root}>
{text}
</div>
);
// Can't use withStyles effectively as a decorator in TypeScript
// due to https://github.com/Microsoft/TypeScript/issues/4881
// @withStyles(styles)
const DecoratedComponent = withStyles(styles)(
class extends React.Component<WithStyles<StyledComponentProps, 'root'>> {
render() {
const { classes, text } = this.props;
return (
<div className={classes.root}>
{text}
</div>
);
}
}
}
);

// no 'classes' property required at element creation time (#8267)
<DecoratedComponent text="foo" />

0 comments on commit 4cdfc94

Please sign in to comment.