Merge pull request #30511 from Kovensky/styled-components-cssobject

styled-components: Add more complete support for object styles, tests
This commit is contained in:
Benjamin Lichtman
2018-11-21 08:46:06 -08:00
committed by GitHub
2 changed files with 370 additions and 165 deletions

View File

@@ -9,17 +9,28 @@
/// <reference types="node" />
import * as React from 'react';
import * as CSS from 'csstype';
import * as CSS from "csstype";
import * as React from "react";
export type CSSObject = CSS.Properties<string | number>;
export type CSSObject = CSS.Properties<string | number> &
// Index type to allow selector nesting
// This is "[key in string]" and not "[key: string]" to allow CSSObject to be self-referential
{
// we need the CSS.Properties in here too to ensure the index signature doesn't create impossible values
[key in string]:
| CSS.Properties<string | number>[keyof CSS.Properties<
string | number
>]
| CSSObject
};
export type CSSKeyframes = object & { [key: string]: CSSObject };
export interface ThemeProps<T> {
theme: T;
}
export type ThemedStyledProps<P, T> = P & ThemeProps<T>;
export type StyledProps<P> = ThemedStyledProps<P, any>;
export type StyledProps<P> = ThemedStyledProps<P, AnyIfEmpty<DefaultTheme>>;
export type StyledComponentProps<
// The Component from whose props are derived
@@ -45,27 +56,25 @@ type StyledComponentPropsWithAs<
export type FalseyValue = undefined | null | false;
export type Interpolation<P> =
| FlattenInterpolation<P>
| ReadonlyArray<
FlattenInterpolation<P> | ReadonlyArray<FlattenInterpolation<P>>
>;
export type FlattenInterpolation<P> =
| InterpolationValue
| FlattenInterpolation<P>
| InterpolationFunction<P>;
// must be an interface to be self-referential
export interface FlattenInterpolation<P>
extends ReadonlyArray<Interpolation<P>> {}
export type InterpolationValue =
| string
| number
| Styles
| FalseyValue
| Keyframes
| StyledComponentInterpolation
| CSSObject;
export type SimpleInterpolation =
| InterpolationValue
| ReadonlyArray<InterpolationValue | ReadonlyArray<InterpolationValue>>;
export interface Styles {
[ruleOrSelector: string]: string | number | Styles;
}
| FlattenSimpleInterpolation;
// must be an interface to be self-referential
interface FlattenSimpleInterpolation
extends ReadonlyArray<SimpleInterpolation> {}
export type InterpolationFunction<P> = (props: P) => Interpolation<P>;
@@ -76,19 +85,23 @@ type DeprecatedAttrs<P, A extends Partial<P>, T> = {
[K in keyof A]: ((props: ThemedStyledProps<P, T>) => A[K]) | A[K]
};
export type ThemedGlobalStyledClassProps<P, T> = P & {
suppressMultiMountWarning?: boolean
theme?: T
export type ThemedGlobalStyledClassProps<P, T> = WithOptionalTheme<P, T> & {
suppressMultiMountWarning?: boolean;
};
export interface GlobalStyleComponent<P, T>
extends React.ComponentClass<ThemedGlobalStyledClassProps<P, T>> {}
// remove the call signature from StyledComponent so Interpolation can still infer InterpolationFunction
type StyledComponentInterpolation = Pick<
StyledComponent<any, any>,
keyof StyledComponent<any, any>
>;
type StyledComponentInterpolation =
| Pick<
StyledComponentBase<any, any, any, any>,
keyof StyledComponentBase<any, any>
>
| Pick<
StyledComponentBase<any, any, any>,
keyof StyledComponentBase<any, any>
>;
// abuse Pick to strip the call signature from ForwardRefExoticComponent
type ForwardRefExoticBase<P> = Pick<
@@ -101,7 +114,16 @@ export type AnyStyledComponent =
| StyledComponent<any, any, any, any>
| StyledComponent<any, any, any>;
export interface StyledComponent<
export type StyledComponent<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
T extends object,
O extends object = {},
A extends keyof any = never
> = // the "string" allows this to be used as an object key
// I really want to avoid this if possible but it's the only way to use nesting with object styles...
string & StyledComponentBase<C, T, O, A>;
export interface StyledComponentBase<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
T extends object,
O extends object = {},
@@ -127,7 +149,7 @@ export interface StyledComponent<
*
* String types need to be cast to themselves to become literal types (as={'a' as 'a'}).
*/
as?: keyof JSX.IntrinsicElements | React.ComponentType<any>
as?: keyof JSX.IntrinsicElements | React.ComponentType<any>;
}
): React.ReactElement<StyledComponentProps<C, T, O, A>>;
withComponent<WithC extends AnyStyledComponent>(
@@ -145,49 +167,73 @@ export interface StyledComponent<
): StyledComponent<WithC, T, O, A>;
}
export interface ThemedStyledFunction<
export interface ThemedStyledFunctionBase<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
T extends object,
O extends object = {},
A extends keyof any = never
> {
(
strings: TemplateStringsArray,
...interpolations: Array<
first:
| TemplateStringsArray
| NonNullable<
Interpolation<
ThemedStyledProps<StyledComponentPropsWithRef<C> & O, T>
>
>,
...rest: Array<
Interpolation<
ThemedStyledProps<StyledComponentPropsWithRef<C> & O, T>
>
>
): StyledComponent<C, T, O, A>;
// at least the first argument is required, whatever it is
<U extends object>(
strings: TemplateStringsArray,
...interpolations: Array<
first:
| TemplateStringsArray
| NonNullable<
Interpolation<
ThemedStyledProps<
StyledComponentPropsWithRef<C> & O & U,
T
>
>
>,
...rest: Array<
Interpolation<
ThemedStyledProps<StyledComponentPropsWithRef<C> & O & U, T>
>
>
): StyledComponent<C, T, O & U, A>;
}
export interface ThemedStyledFunction<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
T extends object,
O extends object = {},
A extends keyof any = never
> extends ThemedStyledFunctionBase<C, T, O, A> {
// Fun thing: 'attrs' can also provide a polymorphic 'as' prop
// My head already hurts enough so maybe later...
attrs<
U,
A extends Partial<StyledComponentPropsWithRef<C> & U> & {
[others: string]: any
NewA extends Partial<StyledComponentPropsWithRef<C> & U> & {
[others: string]: any;
} = {}
>(
attrs: Attrs<StyledComponentPropsWithRef<C> & U, A, T>
): ThemedStyledFunction<C, T, O & A, keyof A>;
attrs: Attrs<StyledComponentPropsWithRef<C> & U, NewA, T>
): ThemedStyledFunction<C, T, O & NewA, A | keyof NewA>;
// Only this overload is deprecated
// tslint:disable:unified-signatures
/** @deprecated Prefer using the new single function style, to be removed in v5 */
attrs<
U,
A extends Partial<StyledComponentPropsWithRef<C> & U> & {
[others: string]: any
NewA extends Partial<StyledComponentPropsWithRef<C> & U> & {
[others: string]: any;
} = {}
>(
attrs: DeprecatedAttrs<StyledComponentPropsWithRef<C> & U, A, T>
): ThemedStyledFunction<C, T, O & A, keyof A>;
attrs: DeprecatedAttrs<StyledComponentPropsWithRef<C> & U, NewA, T>
): ThemedStyledFunction<C, T, O & NewA, A | keyof NewA>;
// tslint:enable:unified-signatures
}
@@ -222,17 +268,13 @@ type StyledComponentInnerAttrs<
export interface ThemedBaseStyledInterface<T extends object>
extends ThemedStyledComponentFactories<T> {
<TTag extends keyof JSX.IntrinsicElements>(tag: TTag): ThemedStyledFunction<
TTag,
T
>;
<C extends AnyStyledComponent>(component: C): ThemedStyledFunction<
StyledComponentInnerComponent<C>,
T,
StyledComponentInnerOtherProps<C>,
StyledComponentInnerAttrs<C>
>;
<C extends React.ComponentType<any>>(
<C extends keyof JSX.IntrinsicElements | React.ComponentType<any>>(
// unfortunately using a conditional type to validate that it can receive a `theme?: Theme`
// causes tests to fail in TS 3.1
component: C
@@ -240,60 +282,67 @@ export interface ThemedBaseStyledInterface<T extends object>
}
export type ThemedStyledInterface<T extends object> = ThemedBaseStyledInterface<
Extract<keyof T, string> extends never ? any : T
AnyIfEmpty<T>
>;
export type StyledInterface = ThemedStyledInterface<DefaultTheme>;
export interface BaseThemedCssFunction<T extends object> {
(cssObject: CSSObject): InterpolationValue[];
(
strings: TemplateStringsArray,
...interpolations: SimpleInterpolation[]
): InterpolationValue[];
<P>(
strings: TemplateStringsArray,
first:
| TemplateStringsArray
| NonNullable<Interpolation<ThemedStyledProps<{}, T>>>,
...interpolations: Array<Interpolation<ThemedStyledProps<{}, T>>>
): FlattenInterpolation<ThemedStyledProps<{}, T>>;
<P extends object>(
first:
| TemplateStringsArray
| NonNullable<Interpolation<ThemedStyledProps<P, T>>>,
...interpolations: Array<Interpolation<ThemedStyledProps<P, T>>>
): Array<FlattenInterpolation<ThemedStyledProps<P, T>>>;
<P>(func: InterpolationFunction<ThemedStyledProps<P, T>>): Array<
FlattenInterpolation<ThemedStyledProps<P, T>>
>;
): FlattenInterpolation<ThemedStyledProps<P, T>>;
}
export type ThemedCssFunction<T extends object> = BaseThemedCssFunction<
Extract<keyof T, string> extends never ? any : T
AnyIfEmpty<T>
>;
// Helper type operators
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type DiffBetween<T, U> = Pick<T, Exclude<keyof T, keyof U>> &
Pick<U, Exclude<keyof U, keyof T>>;
type WithOptionalTheme<P extends { theme?: T }, T> = Omit<P, 'theme'> & {
theme?: T
type WithOptionalTheme<P extends { theme?: T }, T> = Omit<P, "theme"> & {
theme?: T;
};
type AnyIfEmpty<T extends object> = keyof T extends never ? any : T;
export interface ThemedStyledComponentsModule<T extends object> {
export interface ThemedStyledComponentsModule<
T extends object,
U extends object = T
> {
default: ThemedStyledInterface<T>;
css: ThemedCssFunction<T>;
keyframes(cssObject: CSSObject): Keyframes;
// unfortunately keyframes can't interpolate props from the theme
keyframes(
strings: TemplateStringsArray,
strings: TemplateStringsArray | CSSKeyframes,
...interpolations: SimpleInterpolation[]
): Keyframes;
createGlobalStyle(cssObject: CSSObject): GlobalStyleComponent<{}, T>;
createGlobalStyle<P = {}>(
strings: TemplateStringsArray,
createGlobalStyle<P extends object = {}>(
first:
| TemplateStringsArray
| NonNullable<Interpolation<ThemedStyledProps<P, T>>>,
...interpolations: Array<Interpolation<ThemedStyledProps<P, T>>>
): GlobalStyleComponent<P, T>;
createGlobalStyle<P = {}>(
func: InterpolationFunction<ThemedStyledProps<P, T>>
): GlobalStyleComponent<P, T>;
withTheme: WithThemeFnInterface<T>;
ThemeProvider: ThemeProviderComponent<T>;
ThemeProvider: ThemeProviderComponent<T, U>;
ThemeConsumer: React.Consumer<T>;
ThemeContext: React.Context<T>;
// This could be made to assert `target is StyledComponent<any, T>` instead, but that feels not type safe
isStyledComponent: typeof isStyledComponent;
ServerStyleSheet: typeof ServerStyleSheet;
StyleSheetManager: typeof StyleSheetManager;
}
declare const styled: StyledInterface;
@@ -310,49 +359,51 @@ export type BaseWithThemeFnInterface<T extends object> = <
WithOptionalTheme<React.ComponentPropsWithRef<C>, T>
>;
export type WithThemeFnInterface<T extends object> = BaseWithThemeFnInterface<
Extract<keyof T, string> extends never ? any : T
AnyIfEmpty<T>
>;
export const withTheme: WithThemeFnInterface<DefaultTheme>;
/**
* This interface can be augmented by users to add types to `styled-components`' default theme
* without needing to reexport `ThemedStyledComponentsModule`.
*/
// Unfortunately, there is no way to write tests for this
// as any augmentation will break the tests for the default case (not augmented).
// tslint:disable-next-line:no-empty-interface
export interface DefaultTheme {}
export interface ThemeProviderProps<T extends object> {
export interface ThemeProviderProps<T extends object, U extends object = T> {
children?: React.ReactChild; // only one child is allowed, goes through React.Children.only
theme: T | ((theme: T) => T);
theme: T | ((theme: U) => T);
}
export type BaseThemeProviderComponent<T extends object> = React.ComponentClass<
ThemeProviderProps<T>
>;
export type BaseThemeProviderComponent<
T extends object,
U extends object = T
> = React.ComponentClass<ThemeProviderProps<T, U>>;
export type ThemeProviderComponent<
T extends object
> = BaseThemeProviderComponent<Extract<keyof T, string> extends never ? any : T>;
export const ThemeProvider: ThemeProviderComponent<DefaultTheme>;
// NOTE: this technically starts as undefined
// Also, this cannot be DefaultTheme as it breaks TypedComponents' assignability
export const ThemeContext: React.Context<any>;
export const ThemeConsumer: typeof ThemeContext['Consumer'];
T extends object,
U extends object = T
> = BaseThemeProviderComponent<AnyIfEmpty<T>, AnyIfEmpty<U>>;
export const ThemeProvider: ThemeProviderComponent<AnyIfEmpty<DefaultTheme>>;
// NOTE: this technically starts as undefined, but allowing undefined is unhelpful when used correctly
export const ThemeContext: React.Context<AnyIfEmpty<DefaultTheme>>;
export const ThemeConsumer: typeof ThemeContext["Consumer"];
export interface Keyframes {
getName(): string;
}
export function keyframes(cssObject: CSSObject): Keyframes;
export function keyframes(
strings: TemplateStringsArray,
strings: TemplateStringsArray | CSSKeyframes,
...interpolations: SimpleInterpolation[]
): Keyframes;
export function createGlobalStyle(
cssObject: CSSObject
): GlobalStyleComponent<{}, DefaultTheme>;
export function createGlobalStyle<P = {}>(
strings: TemplateStringsArray,
export function createGlobalStyle<P extends object = {}>(
first:
| TemplateStringsArray
| NonNullable<Interpolation<ThemedStyledProps<P, DefaultTheme>>>,
...interpolations: Array<Interpolation<ThemedStyledProps<P, DefaultTheme>>>
): GlobalStyleComponent<P, DefaultTheme>;
export function createGlobalStyle<P = {}>(
func: InterpolationFunction<ThemedStyledProps<P, DefaultTheme>>
): GlobalStyleComponent<P, DefaultTheme>;
export function isStyledComponent(
target: any
@@ -373,12 +424,12 @@ export class ServerStyleSheet {
type StyleSheetManagerProps =
| {
sheet: ServerStyleSheet
target?: never
sheet: ServerStyleSheet;
target?: never;
}
| {
sheet?: never
target: HTMLElement
sheet?: never;
target: HTMLElement;
};
export class StyleSheetManager extends React.Component<

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as ReactDOMServer from 'react-dom/server';
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as ReactDOMServer from "react-dom/server";
import styled, {
css,
@@ -14,7 +14,8 @@ import styled, {
withTheme,
ThemeConsumer,
StyledComponent,
} from 'styled-components';
ThemedStyledComponentsModule
} from "styled-components";
/**
* general usage
@@ -72,8 +73,8 @@ const TomatoButton = styled(MyButton)`
const CustomizableButton = styled(MyButton)`
/* Adapt the colors based on primary prop */
background: ${props => (props.primary ? 'palevioletred' : 'white')};
color: ${props => (props.primary ? 'white' : 'palevioletred')};
background: ${props => (props.primary ? "palevioletred" : "white")};
color: ${props => (props.primary ? "white" : "palevioletred")};
font-size: 1em;
margin: 1em;
@@ -86,7 +87,7 @@ const example = css`
font-size: 1.5em;
text-align: center;
color: ${props => props.theme.primary};
border-color: ${'red'};
border-color: ${"red"};
`;
const fadeIn = keyframes`
@@ -99,15 +100,15 @@ const fadeIn = keyframes`
`;
const animationRule = css`
${fadeIn} 1s infinite alternate;
${fadeIn} 1s infinite alternate;
`;
const ComponentWithKeyframe = styled.div`
animation: ${animationRule};
animation: ${animationRule};
`;
const theme = {
main: 'mediumseagreen',
main: "mediumseagreen"
};
const ExampleGlobalStyle = createGlobalStyle`
@@ -130,7 +131,7 @@ class Example extends React.Component {
<Wrapper>
<Title>
Hello World, this is my first styled component!
</Title>
</Title>
<Input placeholder="@mxstbr" type="text" />
<TomatoButton name="demo" />
@@ -143,13 +144,13 @@ class Example extends React.Component {
// css which only uses simple interpolations without functions
const cssWithValues1 = css`
font-size: ${14} ${'pt'};
font-size: ${14} ${"pt"};
`;
// css which uses other simple interpolations without functions
const cssWithValues2 = css`
${cssWithValues1}
${[cssWithValues1, cssWithValues1]}
font-weight: ${'bold'};
font-weight: ${"bold"};
`;
// css which uses function interpolations with common props
@@ -168,7 +169,7 @@ const styledButton = styled.button`
${() => [cssWithFunc1, cssWithFunc2]}
`;
const name = 'hey';
const name = "hey";
const ThemedMyButton = withTheme(MyButton);
@@ -216,7 +217,7 @@ const ComposedLink = () => (
// Create a <LinkFromString> react component that renders an <a> which is
// centered, palevioletred and sized at 1.5em
const LinkFromString = styled('a')`
const LinkFromString = styled("a")`
font-size: 1.5em;
text-align: center;
color: palevioletred;
@@ -235,10 +236,10 @@ interface LinkProps {
canClick: boolean;
}
const LinkFromStringWithProps = styled('a')`
const LinkFromStringWithProps = styled("a")`
font-size: 1.5em;
text-align: center;
color: ${(a: LinkProps) => (a.canClick ? 'palevioletred' : 'gray')};
color: ${(a: LinkProps) => (a.canClick ? "palevioletred" : "gray")};
`;
// A LinkFromStringWithProps instance should be backed by an HTMLAnchorElement
@@ -251,10 +252,10 @@ const MyOtherComponentWithProps = () => (
// Create a <LinkFromStringWithPropsAndGenerics> react component that renders an <a>
// which takes extra props passed as a generic type argument
const LinkFromStringWithPropsAndGenerics = styled('a')<LinkProps>`
const LinkFromStringWithPropsAndGenerics = styled("a")<LinkProps>`
font-size: 1.5em;
text-align: center;
color: ${a => (a.canClick ? 'palevioletred' : 'gray')};
color: ${a => (a.canClick ? "palevioletred" : "gray")};
`;
// A LinkFromStringWithPropsAndGenerics instance should be backed by an HTMLAnchorElement
@@ -274,19 +275,19 @@ interface ObjectStyleProps {
}
const functionReturningStyleObject = (props: ObjectStyleProps) => ({
padding: props.size === 'big' ? '10px' : 2,
padding: props.size === "big" ? "10px" : 2
});
const ObjectStylesBox = styled.div`
${functionReturningStyleObject} ${{
backgroundColor: 'red',
backgroundColor: "red",
// Supports nested objects (pseudo selectors, media queries, etc)
'@media screen and (min-width: 800px)': {
backgroundColor: 'blue',
"@media screen and (min-width: 800px)": {
backgroundColor: "blue"
},
fontSize: 2,
fontSize: 2
}};
`;
@@ -298,11 +299,11 @@ const ObjectStylesBox = styled.div`
const AttrsInput = styled.input.attrs({
// we can define static props
type: 'password',
type: "password",
// or we can define dynamic ones
margin: (props: any) => (props.size as string) || '1em',
padding: (props: any) => (props.size as string) || '1em',
margin: (props: any) => (props.size as string) || "1em",
padding: (props: any) => (props.size as string) || "1em"
})`
color: palevioletred;
font-size: 1em;
@@ -315,11 +316,15 @@ const AttrsInput = styled.input.attrs({
`;
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30042
const AttrsWithOnlyNewProps = styled.h2.attrs({ as: 'h1' })`
color: ${props => props.as === 'h1' ? 'red' : 'blue'};
font-size: ${props => props.as === 'h1' ? 2 : 1};
const AttrsWithOnlyNewProps = styled.h2.attrs({ as: "h1" })`
color: ${props => (props.as === "h1" ? "red" : "blue")};
font-size: ${props => (props.as === "h1" ? 2 : 1)};
`;
const AttrsInputExtra = styled(AttrsInput).attrs({ autoComplete: "off" })``;
<AttrsInputExtra />;
/**
* component type
*/
@@ -356,14 +361,14 @@ const ThemedButton = styled.button`
// Define our `fg` and `bg` on the theme
const theme2 = {
fg: 'palevioletred',
bg: 'white',
fg: "palevioletred",
bg: "white"
};
// This theme swaps `fg` and `bg`
const invertTheme = ({ fg, bg }: { fg: string; bg: string }) => ({
fg: bg,
bg: fg,
bg: fg
});
const MyApp = (
@@ -386,7 +391,7 @@ class MyComponent extends React.Component<ThemeProps<{}>> {
render() {
const { theme } = this.props;
console.log('Current theme: ', theme);
console.log("Current theme: ", theme);
return <h1>Hello</h1>;
}
@@ -400,7 +405,7 @@ const ThemedMyComponent = withTheme(MyComponent);
// ref;
// }}/>;
const themedRef = React.createRef<MyComponent>();
<ThemedMyComponent ref={themedRef}/>;
<ThemedMyComponent ref={themedRef} />;
interface WithThemeProps {
theme: {
@@ -415,12 +420,10 @@ const Component = (props: WithThemeProps) => (
const ComponentWithTheme = withTheme(Component);
<ComponentWithTheme text={'hi'} />; // ok
<ComponentWithTheme text={'hi'} theme={{ color: 'red' }} />; // ok
<ComponentWithTheme text={"hi"} />; // ok
<ComponentWithTheme text={"hi"} theme={{ color: "red" }} />; // ok
<ThemeConsumer>
{(theme) => <Component text="hi" theme={theme} />}
</ThemeConsumer>;
<ThemeConsumer>{theme => <Component text="hi" theme={theme} />}</ThemeConsumer>;
/**
* isStyledComponent utility
@@ -439,7 +442,7 @@ class ClassComponent extends React.Component {
isStyledComponent(StyledComponent);
isStyledComponent(StatelessComponent);
isStyledComponent(ClassComponent);
isStyledComponent('div');
isStyledComponent("div");
/**
* server side rendering
@@ -470,7 +473,7 @@ const css2 = sheet2.getStyleElement();
const sheet3 = new ServerStyleSheet();
const appStream = ReactDOMServer.renderToNodeStream(<Title>Hello world</Title>);
const wrappedCssStream: NodeJS.ReadableStream = sheet3.interleaveWithNodeStream(
appStream,
appStream
);
/**
@@ -511,10 +514,10 @@ class Random extends React.Component<any, any> {
}
}
const WithComponentH2 = WithComponentH1.withComponent('h2');
const WithComponentAbbr = WithComponentH1.withComponent('abbr');
const WithComponentH2 = WithComponentH1.withComponent("h2");
const WithComponentAbbr = WithComponentH1.withComponent("abbr");
const WithComponentAnchor = WithComponentH1.withComponent('a');
const WithComponentAnchor = WithComponentH1.withComponent("a");
const AnchorContainer = () => (
<WithComponentAnchor href="https://example.com">
withComponent Anchor
@@ -524,37 +527,41 @@ const AnchorContainer = () => (
const WithComponentRandomHeading = WithComponentH1.withComponent(Random);
const WithComponentCompA: React.SFC<{ a: number; className?: string }> = ({
className,
className
}) => <div className={className} />;
const WithComponentCompB: React.SFC<{ b: number; className?: string }> = ({
className,
className
}) => <div className={className} />;
const WithComponentStyledA = styled(WithComponentCompA)`
color: ${(props: { color: string }) => props.color};
`;
const WithComponentFirstStyledA = styled(WithComponentStyledA).attrs({
a: 1,
a: 1
})``;
const WithComponentFirstStyledB = WithComponentFirstStyledA.withComponent(
WithComponentCompB,
WithComponentCompB
);
const WithComponentFirstStyledANew = styled(WithComponentStyledA).attrs(props => ({ a: 1 }))``;
const WithComponentFirstStyledANew = styled(WithComponentStyledA).attrs(
props => ({ a: 1 })
)``;
const test = () => [
<WithComponentFirstStyledA color={'black'} />,
<WithComponentFirstStyledB b={2} color={'black'} />,
<WithComponentFirstStyledANew color={'black'} />,
<WithComponentFirstStyledA color={"black"} />,
<WithComponentFirstStyledB b={2} color={"black"} />,
<WithComponentFirstStyledANew color={"black"} />
];
const WithComponentRequired = styled((props: { to: string }) => <a href={props.to}/>)``;
const WithComponentRequired = styled((props: { to: string }) => (
<a href={props.to} />
))``;
// These tests pass in tsservice, but they fail in dtslint. I do not know why.
// <WithComponentRequired href=''/>; // $ExpectError
// <WithComponentRequired to=''/>;
const WithComponentRequired2 = WithComponentRequired.withComponent('a');
const WithComponentRequired2 = WithComponentRequired.withComponent("a");
// These tests pass in tsservice, but they fail in dtslint. I do not know why.
// <WithComponentRequired2 href=''/>;
// <WithComponentRequired2 to=''/>; // $ExpectError
@@ -569,7 +576,7 @@ const asTest = (
);
interface TestContainerProps {
size: 'big' | 'small';
size: "big" | "small";
test?: boolean;
}
const TestContainer = ({ size, test }: TestContainerProps) => {
@@ -581,7 +588,7 @@ const StyledTestContainer = styled(TestContainer)`
`;
interface Test2ContainerProps {
type: 'foo' | 'bar';
type: "foo" | "bar";
}
class Test2Container extends React.Component<Test2ContainerProps> {
render() {
@@ -592,28 +599,175 @@ class Test2Container extends React.Component<Test2ContainerProps> {
const containerTest = (
// TODO (TypeScript 3.2): once the polymorphic overload is un-commented-out this should be the correct test
// <StyledTestContainer as={Test2Container} type='foo' />
<StyledTestContainer as={Test2Container} size='small' />
<StyledTestContainer as={Test2Container} size="small" />
);
// 4.0 refs
const divFnRef = (ref: HTMLDivElement|null) => { /* empty */ };
const divFnRef = (ref: HTMLDivElement | null) => {
/* empty */
};
const divRef = React.createRef<HTMLDivElement>();
const StyledDiv = styled.div``;
<StyledDiv ref={divRef}/>;
<StyledDiv ref={divFnRef}/>;
<StyledDiv ref='string'/>; // $ExpectError
<StyledDiv ref={divRef} />;
<StyledDiv ref={divFnRef} />;
<StyledDiv ref="string" />; // $ExpectError
const StyledStyledDiv = styled(StyledDiv)``;
<StyledStyledDiv ref={divRef}/>;
<StyledStyledDiv ref={divFnRef}/>;
<StyledStyledDiv ref='string'/>; // $ExpectError
<StyledStyledDiv ref={divRef} />;
<StyledStyledDiv ref={divFnRef} />;
<StyledStyledDiv ref="string" />; // $ExpectError
const StyledA = StyledDiv.withComponent('a');
<StyledA ref={divRef}/>; // $ExpectError
<StyledA ref={ref => {
// $ExpectType HTMLAnchorElement | null
ref;
}}/>;
const StyledA = StyledDiv.withComponent("a");
<StyledA ref={divRef} />; // $ExpectError
<StyledA
ref={ref => {
// $ExpectType HTMLAnchorElement | null
ref;
}}
/>;
async function typedThemes() {
const theme = {
color: "green"
};
// abuse "await import(...)" to be able to reference the styled-components namespace
// without actually doing a top level namespace import
const {
default: styled,
css,
createGlobalStyle,
ThemeProvider,
ThemeConsumer
} = (await import("styled-components")) as ThemedStyledComponentsModule<
typeof theme
>;
const ThemedDiv = styled.div`
background: ${props => {
// $ExpectType string
props.theme.color;
// $ExpectType number | undefined
props.tabIndex;
return props.theme.color;
}};
`;
const ThemedDiv2 = styled.div(props => {
// $ExpectType string
props.theme.color;
// $ExpectType number | undefined
props.tabIndex;
return {
background: props.theme.color
};
});
const ThemedDiv3 = styled.div(props => {
// $ExpectType string
props.theme.color;
// $ExpectType number | undefined
props.tabIndex;
return css`
background: ${props.theme.color};
`;
});
const themedCss = css`
background: ${props => {
// $ExpectType string
props.theme.color;
// $ExpectType "theme"
type Keys = keyof typeof props;
return props.theme.color;
}};
`;
const ThemedDiv4 = styled.div(themedCss);
const themedCssWithNesting = css(props => ({
color: props.theme.color,
[ThemedDiv3]: {
color: "green"
}
}));
const Global = createGlobalStyle`
${themedCssWithNesting}
`;
return (
<ThemeProvider theme={theme}>
<>
<Global />
<ThemedDiv />
<ThemedDiv2 />
<ThemedDiv3 />
<ThemedDiv4 />
<ThemeConsumer>
{theme => {
// $ExpectType string
theme.color;
return theme.color;
}}
</ThemeConsumer>
</>
</ThemeProvider>
);
}
async function reexportCompatibility() {
const sc = await import("styled-components");
const themed = sc as ThemedStyledComponentsModule<any>;
let { ...scExports } = sc;
let { ...themedExports } = themed;
// both branches must be assignable to each other
if (Math.random()) {
scExports = themedExports;
} else {
themedExports = scExports;
}
}
async function themeAugmentation() {
interface BaseTheme {
background: string;
}
interface ExtraTheme extends BaseTheme {
accent: string;
}
const base = (await import("styled-components")) as ThemedStyledComponentsModule<
BaseTheme
>;
const extra = (await import("styled-components")) as ThemedStyledComponentsModule<
ExtraTheme,
BaseTheme
>;
return (
<base.ThemeProvider
theme={{
background: "black"
}}
>
<>
<extra.ThemeProvider
theme={base => base} // $ExpectError
>
<extra.ThemeConsumer>{() => null}</extra.ThemeConsumer>
</extra.ThemeProvider>
<extra.ThemeProvider
theme={base => ({
...base,
accent: "blue"
})}
>
<extra.ThemeConsumer>{() => null}</extra.ThemeConsumer>
</extra.ThemeProvider>
</>
</base.ThemeProvider>
);
}