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

[styles] Overload function signature instead of conditional #19320

Merged
merged 3 commits into from
Jan 21, 2020
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
36 changes: 8 additions & 28 deletions packages/material-ui-styles/src/makeStyles/makeStyles.d.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,27 @@
import {
ClassKeyOfStyles,
ClassNameMap,
PropsOfStyles,
Styles,
WithStylesOptions,
} from '@material-ui/styles/withStyles';
import { Omit, IsAny, Or, IsEmptyInterface } from '@material-ui/types';
import { Omit } from '@material-ui/types';
import { DefaultTheme } from '../defaultTheme';

/**
* @internal
*
* If a style callback is given with `theme => stylesOfTheme` then typescript
* infers `Props` to `any`.
* If a static object is given with { ...members } then typescript infers `Props`
* to `{}`.
*
* So we require no props in `useStyles` if `Props` in `makeStyles(styles)` is
* inferred to either `any` or `{}`
* `makeStyles` where the passed `styles` do not depend on props
*/
export type StylesRequireProps<S> = Or<
IsAny<PropsOfStyles<S>>,
IsEmptyInterface<PropsOfStyles<S>>
> extends true
? false
: true;

export default function makeStyles<Theme = DefaultTheme, ClassKey extends string = string>(
style: Styles<Theme, {}, ClassKey>,
options?: Omit<WithStylesOptions<Theme>, 'withTheme'>,
): (props?: any) => ClassNameMap<ClassKey>;
/**
* @internal
*
* `Props` are `any` either by explicit annotation or if there are no callbacks
* from which the typechecker could infer a type so it falls back to `any`.
* See the test cases for examples and implications of explicit `any` annotation
* `makeStyles` where the passed `styles` do depend on props
*/
export type StylesHook<S extends Styles<any, any>> = StylesRequireProps<S> extends false
? (props?: any) => ClassNameMap<ClassKeyOfStyles<S>>
: (props: PropsOfStyles<S>) => ClassNameMap<ClassKeyOfStyles<S>>;

export default function makeStyles<
Theme = DefaultTheme,
Props extends {} = {},
ClassKey extends string = string
>(
styles: Styles<Theme, Props, ClassKey>,
options?: Omit<WithStylesOptions<Theme>, 'withTheme'>,
): StylesHook<Styles<Theme, Props, ClassKey>>;
): (props: Props) => ClassNameMap<ClassKey>;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import * as React from 'react';
import { Theme } from '@material-ui/core';
import { AppBarProps } from '@material-ui/core/AppBar';
import { createStyles, makeStyles } from '@material-ui/styles';
import styled, { StyledProps } from '@material-ui/styles/styled';

// makeStyles
{
Expand Down Expand Up @@ -136,50 +135,3 @@ import styled, { StyledProps } from '@material-ui/styles/styled';
classes.other;
}
}

// styled
{
const StyledButton = styled('button')({
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px',
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
});
const renderedStyledButton = <StyledButton classes={{ root: 'additional-root-class' }} />;
// $ExpectError
const nonExistingClassKey = <StyledButton classes={{ notRoot: 'additional-root-class' }} />;

interface MyTheme {
fontFamily: string;
}
const MyThemeInstance: MyTheme = {
fontFamily: 'monospace',
};
// tslint:disable-next-line: no-empty-interface
interface MyComponentProps extends StyledProps {
defaulted: string;
}
class MyComponent extends React.Component<MyComponentProps> {
static defaultProps = {
defaulted: 'Hello, World!',
};
render() {
const { className, defaulted } = this.props;
return <div className={className}>Greeted?: {defaulted.startsWith('Hello')}</div>;
}
}
const StyledMyComponent = styled<typeof MyComponent>(MyComponent)(
({ theme }: { theme: MyTheme }) => ({
fontFamily: theme.fontFamily,
}),
);
const renderedMyComponent = (
<React.Fragment>
<MyComponent className="test" />
<StyledMyComponent theme={MyThemeInstance} />
</React.Fragment>
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { styled } from '@material-ui/styles';
import { styled, StyledProps } from '@material-ui/styles';

function styledTest() {
function themeTest() {
const style = (props: { value: number }) => ({});
const styleWithTheme = (props: {
value: number;
Expand Down Expand Up @@ -46,3 +46,49 @@ function styledTest() {
// error: property 'zIndex' is missing in type ...
<ComponentWithOptionalThemeStyledWithTheme value={1} theme={{ palette: { primary: '#333' } }} />; // $ExpectError
}

function acceptanceTest() {
const StyledButton = styled('button')({
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px',
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
});
const renderedStyledButton = <StyledButton classes={{ root: 'additional-root-class' }} />;
// $ExpectError
const nonExistingClassKey = <StyledButton classes={{ notRoot: 'additional-root-class' }} />;

interface MyTheme {
fontFamily: string;
}
const MyThemeInstance: MyTheme = {
fontFamily: 'monospace',
};
// tslint:disable-next-line: no-empty-interface
interface MyComponentProps extends StyledProps {
defaulted: string;
}
class MyComponent extends React.Component<MyComponentProps> {
static defaultProps = {
defaulted: 'Hello, World!',
};
render() {
const { className, defaulted } = this.props;
return <div className={className}>Greeted?: {defaulted.startsWith('Hello')}</div>;
}
}
const StyledMyComponent = styled<typeof MyComponent>(MyComponent)(
({ theme }: { theme: MyTheme }) => ({
fontFamily: theme.fontFamily,
}),
);
const renderedMyComponent = (
<React.Fragment>
<MyComponent className="test" />
<StyledMyComponent theme={MyThemeInstance} />
</React.Fragment>
);
}
42 changes: 0 additions & 42 deletions packages/material-ui-styles/test/IsEmptyInterface.spec.ts

This file was deleted.

38 changes: 0 additions & 38 deletions packages/material-ui-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,41 +44,3 @@ export type Omit<T, K extends keyof any> = T extends any ? Pick<T, Exclude<keyof
* @internal
*/
export type Overwrite<T, U> = Omit<T, keyof U> & U;

/**
* Returns true if T is any, otherwise false
*/
// https://stackoverflow.com/a/49928360/3406963 without generic branch types
export type IsAny<T> = 0 extends (1 & T) ? true : false;

export type Or<A, B, C = false> = A extends true
? true
: B extends true
? true
: C extends true
? true
: false;

export type And<A, B, C = true> = A extends true
? B extends true
? C extends true
? true
: false
: false
: false;

/**
* @internal
*
* check if a type is `{}`
*
* 1. false if the given type has any members
* 2. false if the type is `object` which is the only other type with no members
* {} is a top type so e.g. `string extends {}` but not `string extends object`
* 3. false if the given type is `unknown`
*/
export type IsEmptyInterface<T> = And<
keyof T extends never ? true : false,
string extends T ? true : false,
unknown extends T ? false : true
>;
2 changes: 1 addition & 1 deletion packages/material-ui/src/styles/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export {
} from './createPalette';
export { default as createStyles } from './createStyles';
export { TypographyStyle } from './createTypography';
export { default as makeStyles, StylesHook } from './makeStyles';
export { default as makeStyles } from './makeStyles';
export { default as responsiveFontSizes } from './responsiveFontSizes';
export { ComponentsPropsList } from './props';
export * from './transitions';
Expand Down
18 changes: 13 additions & 5 deletions packages/material-ui/src/styles/makeStyles.d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { Theme as DefaultTheme } from './createMuiTheme';
import { Styles, WithStylesOptions } from '@material-ui/styles/withStyles';
import { StylesHook } from '@material-ui/styles/makeStyles';
import { ClassNameMap, Styles, WithStylesOptions } from '@material-ui/styles/withStyles';

import { Omit } from '@material-ui/types';

/**
* `makeStyles` where the passed `styles` do not depend on props
*/
export default function makeStyles<Theme = DefaultTheme, ClassKey extends string = string>(
style: Styles<Theme, {}, ClassKey>,
options?: Omit<WithStylesOptions<Theme>, 'withTheme'>,
): (props?: any) => ClassNameMap<ClassKey>;
/**
* `makeStyles` where the passed `styles` do depend on props
*/
export default function makeStyles<
Theme = DefaultTheme,
Props extends {} = {},
ClassKey extends string = string
>(
styles: Styles<Theme, Props, ClassKey>,
options?: Omit<WithStylesOptions<Theme>, 'withTheme'>,
): StylesHook<Styles<Theme, Props, ClassKey>>;

export { StylesHook };
): (props: Props) => ClassNameMap<ClassKey>;