diff --git a/types/styled-components/package.json b/types/styled-components/package.json index 4d9ec4a338..66adb7e3ce 100644 --- a/types/styled-components/package.json +++ b/types/styled-components/package.json @@ -2,5 +2,9 @@ "private": true, "dependencies": { "csstype": "^2.2.0" + }, + "types": "index", + "typesVersions": { + ">=3.7.0-0": { "*": ["ts3.7/*"] } } } diff --git a/types/styled-components/ts3.7/cssprop.d.ts b/types/styled-components/ts3.7/cssprop.d.ts new file mode 100644 index 0000000000..e19b03dc2c --- /dev/null +++ b/types/styled-components/ts3.7/cssprop.d.ts @@ -0,0 +1,19 @@ +import {} from "react"; +import { CSSProp } from "."; + +declare module "react" { + interface Attributes { + // NOTE: unlike the plain javascript version, it is not possible to get access + // to the element's own attributes inside function interpolations. + // Only theme will be accessible, and only with the DefaultTheme due to the global + // nature of this declaration. + // If you are writing this inline you already have access to all the attributes anyway, + // no need for the extra indirection. + /** + * If present, this React element will be converted by + * `babel-plugin-styled-components` into a styled component + * with the given css as its styles. + */ + css?: CSSProp; + } +} diff --git a/types/styled-components/ts3.7/index.d.ts b/types/styled-components/ts3.7/index.d.ts new file mode 100644 index 0000000000..756272a382 --- /dev/null +++ b/types/styled-components/ts3.7/index.d.ts @@ -0,0 +1,483 @@ +// forward declarations +declare global { + namespace NodeJS { + // tslint:disable-next-line:no-empty-interface + interface ReadableStream {} + } +} + +import * as CSS from "csstype"; +import * as React from "react"; + +export type CSSProperties = CSS.Properties; + +export type CSSPseudos = { [K in CSS.Pseudos]?: CSSObject }; + +export interface CSSObject extends CSSProperties, CSSPseudos { + [key: string]: CSSObject | string | number | undefined; +} + +export type CSSKeyframes = object & { [key: string]: CSSObject }; + +export interface ThemeProps { + theme: T; +} + +export type ThemedStyledProps = P & ThemeProps; +export type StyledProps

= ThemedStyledProps>; + +// Any prop that has a default prop becomes optional, but its type is unchanged +// Undeclared default props are augmented into the resulting allowable attributes +// If declared props have indexed properties, ignore default props entirely as keyof gets widened +// Wrap in an outer-level conditional type to allow distribution over props that are unions +type Defaultize = P extends any + ? string extends keyof P ? P : + & Pick> + & Partial>> + & Partial>> + : never; + +type ReactDefaultizedProps = C extends { defaultProps: infer D; } + ? Defaultize + : P; + +export type StyledComponentProps< + // The Component from whose props are derived + C extends keyof JSX.IntrinsicElements | React.ComponentType, + // The Theme from the current context + T extends object, + // The other props added by the template + O extends object, + // The props that are made optional by .attrs + A extends keyof any +> = + // Distribute O if O is a union type + O extends object + ? WithOptionalTheme< + Omit> & O, A> & + Partial & O, A>>, + T + > & + WithChildrenIfReactComponentClass + : never; + +// Because of React typing quirks, when getting props from a React.ComponentClass, +// we need to manually add a `children` field. +// See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/31945 +// and https://github.com/DefinitelyTyped/DefinitelyTyped/pull/32843 +type WithChildrenIfReactComponentClass< + C extends keyof JSX.IntrinsicElements | React.ComponentType +> = C extends React.ComponentClass ? { children?: React.ReactNode } : {}; + +type StyledComponentPropsWithAs< + C extends keyof JSX.IntrinsicElements | React.ComponentType, + T extends object, + O extends object, + A extends keyof any +> = StyledComponentProps & { as?: C }; + +export type FalseyValue = undefined | null | false; +export type Interpolation

= + | InterpolationValue + | InterpolationFunction

+ | FlattenInterpolation

; +// cannot be made a self-referential interface, breaks WithPropNested +// see https://github.com/microsoft/TypeScript/issues/34796 +export type FlattenInterpolation

= ReadonlyArray>; +export type InterpolationValue = + | string + | number + | FalseyValue + | Keyframes + | StyledComponentInterpolation + | CSSObject; +export type SimpleInterpolation = + | InterpolationValue + | FlattenSimpleInterpolation; +export type FlattenSimpleInterpolation = ReadonlyArray; + +export type InterpolationFunction

= (props: P) => Interpolation

; + +type Attrs, T> = + | ((props: ThemedStyledProps) => A) + | A; +type DeprecatedAttrs, T> = { + [K in keyof A]: ((props: ThemedStyledProps) => A[K]) | A[K] +}; + +export type ThemedGlobalStyledClassProps = WithOptionalTheme & { + suppressMultiMountWarning?: boolean; +}; + +export interface GlobalStyleComponent + extends React.ComponentClass> {} + +// remove the call signature from StyledComponent so Interpolation can still infer InterpolationFunction +type StyledComponentInterpolation = + | Pick< + StyledComponentBase, + keyof StyledComponentBase + > + | Pick< + StyledComponentBase, + keyof StyledComponentBase + >; + +// abuse Pick to strip the call signature from ForwardRefExoticComponent +type ForwardRefExoticBase

= Pick< + React.ForwardRefExoticComponent

, + keyof React.ForwardRefExoticComponent +>; + +// extracts React defaultProps +type ReactDefaultProps = C extends { defaultProps: infer D; } ? D : never; + +// any doesn't count as assignable to never in the extends clause, and we default A to never +export type AnyStyledComponent = + | StyledComponent + | StyledComponent; + +export type StyledComponent< + C extends keyof JSX.IntrinsicElements | React.ComponentType, + 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; + +export interface StyledComponentBase< + C extends keyof JSX.IntrinsicElements | React.ComponentType, + T extends object, + O extends object = {}, + A extends keyof any = never +> extends ForwardRefExoticBase> { + // add our own fake call signature to implement the polymorphic 'as' prop + ( + props: StyledComponentProps & { as?: never } + ): React.ReactElement>; + = C>( + props: StyledComponentPropsWithAs + ): React.ReactElement>; + + withComponent( + component: WithC + ): StyledComponent< + StyledComponentInnerComponent, + T, + O & StyledComponentInnerOtherProps, + A | StyledComponentInnerAttrs + >; + withComponent< + WithC extends keyof JSX.IntrinsicElements | React.ComponentType + >( + component: WithC + ): StyledComponent; +} + +export interface ThemedStyledFunctionBase< + C extends keyof JSX.IntrinsicElements | React.ComponentType, + T extends object, + O extends object = {}, + A extends keyof any = never +> { + (first: TemplateStringsArray): StyledComponent; + ( + first: + | TemplateStringsArray + | CSSObject + | InterpolationFunction< + ThemedStyledProps & O, T> + >, + ...rest: Array< + Interpolation< + ThemedStyledProps & O, T> + > + > + ): StyledComponent; + ( + first: + | TemplateStringsArray + | CSSObject + | InterpolationFunction< + ThemedStyledProps & O & U, T> + >, + ...rest: Array< + Interpolation< + ThemedStyledProps & O & U, T> + > + > + ): StyledComponent; +} + +export interface ThemedStyledFunction< + C extends keyof JSX.IntrinsicElements | React.ComponentType, + T extends object, + O extends object = {}, + A extends keyof any = never +> extends ThemedStyledFunctionBase { + // Fun thing: 'attrs' can also provide a polymorphic 'as' prop + // My head already hurts enough so maybe later... + attrs< + U, + NewA extends Partial & U> & { + [others: string]: any; + } = {} + >( + attrs: Attrs & U, NewA, T> + ): ThemedStyledFunction; + // Only this overload is deprecated + // tslint:disable:unified-signatures + /** @deprecated Prefer using the new single function style, to be removed in v5 */ + attrs< + U, + NewA extends Partial & U> & { + [others: string]: any; + } = {} + >( + attrs: DeprecatedAttrs & U, NewA, T> + ): ThemedStyledFunction; + // tslint:enable:unified-signatures +} + +export type StyledFunction< + C extends keyof JSX.IntrinsicElements | React.ComponentType +> = ThemedStyledFunction; + +type ThemedStyledComponentFactories = { + [TTag in keyof JSX.IntrinsicElements]: ThemedStyledFunction +}; + +export type StyledComponentInnerComponent< + C extends React.ComponentType +> = C extends + | StyledComponent + | StyledComponent + ? I + : C; +export type StyledComponentPropsWithRef< + C extends keyof JSX.IntrinsicElements | React.ComponentType +> = C extends AnyStyledComponent + ? React.ComponentPropsWithRef> + : React.ComponentPropsWithRef; +export type StyledComponentInnerOtherProps = C extends + | StyledComponent + | StyledComponent + ? O + : never; +export type StyledComponentInnerAttrs< + C extends AnyStyledComponent +> = C extends StyledComponent ? A : never; + +export interface ThemedBaseStyledInterface + extends ThemedStyledComponentFactories { + (component: C): ThemedStyledFunction< + StyledComponentInnerComponent, + T, + StyledComponentInnerOtherProps, + StyledComponentInnerAttrs + >; + >( + // unfortunately using a conditional type to validate that it can receive a `theme?: Theme` + // causes tests to fail in TS 3.1 + component: C + ): ThemedStyledFunction; +} + +export type ThemedStyledInterface = ThemedBaseStyledInterface< + AnyIfEmpty +>; +export type StyledInterface = ThemedStyledInterface; + +export interface BaseThemedCssFunction { + ( + first: TemplateStringsArray | CSSObject, + ...interpolations: SimpleInterpolation[] + ): FlattenSimpleInterpolation; + ( + first: + | TemplateStringsArray + | CSSObject + | InterpolationFunction>, + ...interpolations: Array>> + ): FlattenInterpolation>; +

( + first: + | TemplateStringsArray + | CSSObject + | InterpolationFunction>, + ...interpolations: Array>> + ): FlattenInterpolation>; +} + +export type ThemedCssFunction = BaseThemedCssFunction< + AnyIfEmpty +>; + +// Helper type operators +type Omit = Pick>; +type WithOptionalTheme

= Omit & { + theme?: T; +}; +type AnyIfEmpty = keyof T extends never ? any : T; + +export interface ThemedStyledComponentsModule< + T extends object, + U extends object = T +> { + default: ThemedStyledInterface; + + css: ThemedCssFunction; + + // unfortunately keyframes can't interpolate props from the theme + keyframes( + strings: TemplateStringsArray | CSSKeyframes, + ...interpolations: SimpleInterpolation[] + ): Keyframes; + + createGlobalStyle

( + first: + | TemplateStringsArray + | CSSObject + | InterpolationFunction>, + ...interpolations: Array>> + ): GlobalStyleComponent; + + withTheme: WithThemeFnInterface; + ThemeProvider: ThemeProviderComponent; + ThemeConsumer: React.Consumer; + ThemeContext: React.Context; + + // This could be made to assert `target is StyledComponent` instead, but that feels not type safe + isStyledComponent: typeof isStyledComponent; + + ServerStyleSheet: typeof ServerStyleSheet; + StyleSheetManager: typeof StyleSheetManager; +} + +declare const styled: StyledInterface; + +export const css: ThemedCssFunction; + +export type BaseWithThemeFnInterface = < + C extends React.ComponentType +>( + // this check is roundabout because the extends clause above would + // not allow any component that accepts _more_ than theme as a prop + component: React.ComponentProps extends { theme?: T } ? C : never +) => React.ForwardRefExoticComponent< + WithOptionalTheme, T> +>; +export type WithThemeFnInterface = BaseWithThemeFnInterface< + AnyIfEmpty +>; +export const withTheme: WithThemeFnInterface; + +/** + * 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 { + children?: React.ReactChild; // only one child is allowed, goes through React.Children.only + theme: T | ((theme: U) => T); +} +export type BaseThemeProviderComponent< + T extends object, + U extends object = T +> = React.ComponentClass>; +export type ThemeProviderComponent< + T extends object, + U extends object = T +> = BaseThemeProviderComponent, AnyIfEmpty>; +export const ThemeProvider: ThemeProviderComponent>; +// NOTE: this technically starts as undefined, but allowing undefined is unhelpful when used correctly +export const ThemeContext: React.Context>; +export const ThemeConsumer: typeof ThemeContext["Consumer"]; + +export interface Keyframes { + getName(): string; +} + +export function keyframes( + strings: TemplateStringsArray | CSSKeyframes, + ...interpolations: SimpleInterpolation[] +): Keyframes; + +export function createGlobalStyle

( + first: + | TemplateStringsArray + | CSSObject + | InterpolationFunction>, + ...interpolations: Array>> +): GlobalStyleComponent; + +export function isStyledComponent( + target: any +): target is StyledComponent; + +export class ServerStyleSheet { + collectStyles( + tree: React.ReactNode + ): React.ReactElement<{ sheet: ServerStyleSheet }>; + + getStyleTags(): string; + getStyleElement(): Array>; + interleaveWithNodeStream( + readableStream: NodeJS.ReadableStream + ): NodeJS.ReadableStream; + readonly instance: this; + seal(): void; +} + +type StyleSheetManagerProps = + | { + sheet: ServerStyleSheet; + target?: never; + } + | { + sheet?: never; + target: HTMLElement; + }; + +export class StyleSheetManager extends React.Component< + StyleSheetManagerProps +> {} + +/** + * The CSS prop is not declared by default in the types as it would cause 'css' to be present + * on the types of anything that uses styled-components indirectly, even if they do not use the + * babel plugin. + * + * You can load a default declaration by using writing this special import from + * a typescript file. This module does not exist in reality, which is why the {} is important: + * + * ```ts + * import {} from 'styled-components/cssprop' + * ``` + * + * Or you can declare your own module augmentation, which allows you to specify the type of Theme: + * + * ```ts + * import { CSSProp } from 'styled-components' + * + * interface MyTheme {} + * + * declare module 'react' { + * interface Attributes { + * css?: CSSProp + * } + * } + * ``` + */ +// ONLY string literals and inline invocations of css`` are supported, anything else crashes the plugin +export type CSSProp> = + | string + | CSSObject + | FlattenInterpolation>; + +export default styled; diff --git a/types/styled-components/ts3.7/macro.d.ts b/types/styled-components/ts3.7/macro.d.ts new file mode 100644 index 0000000000..51f6cced26 --- /dev/null +++ b/types/styled-components/ts3.7/macro.d.ts @@ -0,0 +1,7 @@ +export { default } from '.'; +export * from '.'; + +/** + * Recommended: also `import {} from 'styled-components/cssprop'`, + * or augment react's `Attribute` interface with your own version. + */ diff --git a/types/styled-components/ts3.7/native.d.ts b/types/styled-components/ts3.7/native.d.ts new file mode 100644 index 0000000000..d6423caf5c --- /dev/null +++ b/types/styled-components/ts3.7/native.d.ts @@ -0,0 +1,229 @@ +import * as ReactNative from 'react-native'; +import * as React from 'react'; + +export { + css, + DefaultTheme, + isStyledComponent, + ThemeConsumer, + ThemeContext, + ThemeProps, + ThemeProvider, + withTheme, +} from './index'; + +import { + AnyStyledComponent, + DefaultTheme, + isStyledComponent, + StyledComponentInnerAttrs, + StyledComponentInnerComponent, + StyledComponentInnerOtherProps, + ThemedCssFunction, + ThemedStyledFunction, + ThemedStyledInterface, + ThemeProviderComponent, + WithThemeFnInterface +} from './index'; + +type AnyIfEmpty = keyof T extends never ? any : T; + +export type ReactNativeThemedStyledFunction< + C extends React.ComponentType, + T extends object, +> = ThemedStyledFunction; + +// Copied over from "ThemedBaseStyledInterface" in index.d.ts in order to remove DOM element typings +interface ReactNativeThemedBaseStyledInterface { + (component: C): ThemedStyledFunction< + StyledComponentInnerComponent, + T, + StyledComponentInnerOtherProps, + StyledComponentInnerAttrs + >; + >( + // unfortunately using a conditional type to validate that it can receive a `theme?: Theme` + // causes tests to fail in TS 3.1 + component: C + ): ThemedStyledFunction; +} + +type ReactNativeThemedStyledInterface = ReactNativeThemedBaseStyledInterface< + AnyIfEmpty +>; + +export interface ReactNativeStyledInterface extends ReactNativeThemedStyledInterface { + ActivityIndicator: ReactNativeThemedStyledFunction< + typeof ReactNative.ActivityIndicator, + T + >; + ActivityIndicatorIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.ActivityIndicator, + T + >; + Button: ReactNativeThemedStyledFunction< + typeof ReactNative.Button, + T + >; + DatePickerIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.DatePickerIOS, + T + >; + DrawerLayoutAndroid: ReactNativeThemedStyledFunction< + typeof ReactNative.DrawerLayoutAndroid, + T + >; + Image: ReactNativeThemedStyledFunction< + typeof ReactNative.Image, + T + >; + ImageBackground: ReactNativeThemedStyledFunction< + typeof ReactNative.ImageBackground, + T + >; + KeyboardAvoidingView: ReactNativeThemedStyledFunction< + typeof ReactNative.KeyboardAvoidingView, + T + >; + ListView: ReactNativeThemedStyledFunction< + typeof ReactNative.ListView, + T + >; + Modal: ReactNativeThemedStyledFunction< + typeof ReactNative.Modal, + T + >; + NavigatorIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.NavigatorIOS, + T + >; + Picker: ReactNativeThemedStyledFunction< + typeof ReactNative.Picker, + T + >; + PickerIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.PickerIOS, + T + >; + ProgressBarAndroid: ReactNativeThemedStyledFunction< + typeof ReactNative.ProgressBarAndroid, + T + >; + ProgressViewIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.ProgressViewIOS, + T + >; + ScrollView: ReactNativeThemedStyledFunction< + typeof ReactNative.ScrollView, + T + >; + SegmentedControlIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.SegmentedControlIOS, + T + >; + Slider: ReactNativeThemedStyledFunction< + typeof ReactNative.Slider, + T + >; + SliderIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.Slider, + T + >; + SnapshotViewIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.SnapshotViewIOS, + T + >; + Switch: ReactNativeThemedStyledFunction< + typeof ReactNative.Switch, + T + >; + RecyclerViewBackedScrollView: ReactNativeThemedStyledFunction< + typeof ReactNative.RecyclerViewBackedScrollView, + T + >; + RefreshControl: ReactNativeThemedStyledFunction< + typeof ReactNative.RefreshControl, + T + >; + SafeAreaView: ReactNativeThemedStyledFunction< + typeof ReactNative.SafeAreaView, + T + >; + StatusBar: ReactNativeThemedStyledFunction< + typeof ReactNative.StatusBar, + T + >; + SwipeableListView: ReactNativeThemedStyledFunction< + typeof ReactNative.SwipeableListView, + T + >; + SwitchAndroid: ReactNativeThemedStyledFunction< + typeof ReactNative.Switch, + T + >; + SwitchIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.SwitchIOS, + T + >; + TabBarIOS: ReactNativeThemedStyledFunction< + typeof ReactNative.TabBarIOS, + T + >; + Text: ReactNativeThemedStyledFunction< + typeof ReactNative.Text, + T + >; + TextInput: ReactNativeThemedStyledFunction< + typeof ReactNative.TextInput, + T + >; + ToolbarAndroid: ReactNativeThemedStyledFunction< + typeof ReactNative.ToolbarAndroid, + T + >; + TouchableHighlight: ReactNativeThemedStyledFunction< + typeof ReactNative.TouchableHighlight, + T + >; + TouchableNativeFeedback: ReactNativeThemedStyledFunction< + typeof ReactNative.TouchableNativeFeedback, + T + >; + TouchableOpacity: ReactNativeThemedStyledFunction< + typeof ReactNative.TouchableOpacity, + T + >; + TouchableWithoutFeedback: ReactNativeThemedStyledFunction< + typeof ReactNative.TouchableWithoutFeedback, + T + >; + View: ReactNativeThemedStyledFunction< + typeof ReactNative.View, + T + >; + ViewPagerAndroid: ReactNativeThemedStyledFunction< + typeof ReactNative.ViewPagerAndroid, + T + >; +} + +export interface ReactNativeThemedStyledComponentsModule< + T extends object, + U extends object = T +> { + default: ReactNativeStyledInterface; + + css: ThemedCssFunction; + + withTheme: WithThemeFnInterface; + ThemeProvider: ThemeProviderComponent; + ThemeConsumer: React.Consumer; + ThemeContext: React.Context; + + // This could be made to assert `target is StyledComponent` instead, but that feels not type safe + isStyledComponent: typeof isStyledComponent; +} + +declare const styled: ReactNativeStyledInterface; + +export default styled; diff --git a/types/styled-components/ts3.7/test/index.tsx b/types/styled-components/ts3.7/test/index.tsx new file mode 100644 index 0000000000..2dd95ea46c --- /dev/null +++ b/types/styled-components/ts3.7/test/index.tsx @@ -0,0 +1,1087 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import * as ReactDOMServer from "react-dom/server"; + +import styled, { + css, + createGlobalStyle, + isStyledComponent, + keyframes, + ServerStyleSheet, + StyleSheetManager, + ThemeProps, + ThemeProvider, + withTheme, + ThemeConsumer, + StyledComponent, + ThemedStyledComponentsModule, + FlattenSimpleInterpolation, + SimpleInterpolation, + FlattenInterpolation +} from "styled-components"; +import {} from "styled-components/cssprop"; + +/** + * general usage + */ + +// Create a react component that renders an <h1> which is +// centered, palevioletred and sized at 1.5em +const Title = styled.h1` + font-size: 1.5em; + text-align: center; + color: palevioletred; +`; + +// Create a <Wrapper> react component that renders a <section> with +// some padding and a papayawhip background +const Wrapper = styled.section` + padding: 4em; + background: papayawhip; +`; + +const Input = styled.input` + font-size: 1.25em; + padding: 0.5em; + margin: 0.5em; + color: palevioletred; + background: papayawhip; + border: none; + border-radius: 3px; + + &:hover { + box-shadow: inset 1px 1px 2px rgba(0, 0, 0, 0.1); + } +`; + +interface MyTheme { + primary: string; +} + +interface ButtonProps { + name: string; + primary?: boolean; + theme: MyTheme; +} + +class MyButton extends React.Component<ButtonProps> { + render() { + return <button>Custom button</button>; + } +} + +const TomatoButton = styled(MyButton)` + color: tomato; + border-color: tomato; +`; + +const CustomizableButton = styled(MyButton)` + /* Adapt the colors based on primary prop */ + background: ${props => (props.primary ? "palevioletred" : "white")}; + color: ${props => (props.primary ? "white" : "palevioletred")}; + + font-size: 1em; + margin: 1em; + padding: 0.25em 1em; + border: 2px solid ${props => props.theme.primary}; + border-radius: 3px; +`; + +const example = css` + font-size: 1.5em; + text-align: center; + color: ${props => props.theme.primary}; + border-color: ${"red"}; +`; + +const fadeIn = keyframes` + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +`; + +const showAnimation = css` + opacity: 1; + transform: scale(1) translateY(0); +`; + +const hideAnimation = css` + opacity: 0; + transform: scale(0.95, 0.8) translateY(20px); +`; + +const entryAnimation = keyframes` + from { ${hideAnimation} } + to { ${showAnimation} } +`; + +const animationRule = css` + ${fadeIn} 1s infinite alternate; +`; + +const ComponentWithKeyframe = styled.div` + animation: ${animationRule}; +`; + +const theme = { + main: "mediumseagreen" +}; + +const ExampleGlobalStyle = createGlobalStyle` + @font-face { + font-family: 'Operator Mono'; + src: url('../fonts/Operator-Mono.ttf'); + } + + body { + margin: 0; + } +`; + +class Example extends React.Component { + render() { + return ( + <ThemeProvider theme={theme}> + <> + <ExampleGlobalStyle /> + <Wrapper> + <Title> + Hello World, this is my first styled component! + + + + + + + + ); + } +} + +// css which only uses simple interpolations without functions +const cssWithValues1 = css` + font-size: ${14} ${"pt"}; +`; +// css which uses other simple interpolations without functions +const cssWithValues2 = css` + ${cssWithValues1} + ${[cssWithValues1, cssWithValues1]} + font-weight: ${"bold"}; +`; + +// css which uses function interpolations with common props +const cssWithFunc1 = css` + font-size: ${props => props.theme.fontSizePt}pt; +`; +const cssWithFunc2 = css` + ${cssWithFunc1} + ${props => cssWithFunc1} + ${[cssWithFunc1, cssWithValues1]} +`; +// such css can be used in styled components +const styledButton = styled.button` + ${cssWithValues1} ${[cssWithValues1, cssWithValues2]} + ${cssWithFunc1} ${[cssWithFunc1, cssWithFunc2]} + ${() => [cssWithFunc1, cssWithFunc2]} +`; + +const name = "hey"; + +const ThemedMyButton = withTheme(MyButton); +; + +/** + * nested styles + */ + +const Link = styled.a` + color: red; +`; + +const AlternativeLink = styled.a` + color: blue; +`; + +const freeStyles = css` + background-color: black; + color: white; + ${Link} { + color: blue; + } +`; + +const Article = styled.section` + color: red; + ${freeStyles} + & > ${Link} { + color: green; + } + ${p => (p.theme.useAlternativeLink ? AlternativeLink : Link)} { + color: black + } +`; + +// A Link instance should be backed by an HTMLAnchorElement +const ComposedLink = () => ( + ) => undefined} /> +); + +/** + * construction via string tag + */ + +// Create a react component that renders an which is +// centered, palevioletred and sized at 1.5em +const LinkFromString = styled("a")` + font-size: 1.5em; + text-align: center; + color: palevioletred; +`; + +// A LinkFromString instance should be backed by an HTMLAnchorElement +const MyOtherComponent = () => ( + ) => undefined} + /> +); + +// Create a react component that renders an +// which takes extra props +interface LinkProps { + canClick: boolean; +} + +const LinkFromStringWithProps = styled("a")` + font-size: 1.5em; + text-align: center; + color: ${(a: LinkProps) => (a.canClick ? "palevioletred" : "gray")}; +`; + +// A LinkFromStringWithProps instance should be backed by an HTMLAnchorElement +const MyOtherComponentWithProps = () => ( + ) => undefined} + /> +); + +// Create a react component that renders an +// which takes extra props passed as a generic type argument +const LinkFromStringWithPropsAndGenerics = styled("a")` + font-size: 1.5em; + text-align: center; + color: ${a => (a.canClick ? "palevioletred" : "gray")}; +`; + +// A LinkFromStringWithPropsAndGenerics instance should be backed by an HTMLAnchorElement +const MyOtherComponentWithPropsAndGenerics = () => ( + ) => undefined} + /> +); + +/** + * object styles + */ + +interface ObjectStyleProps { + size: string; +} + +const functionReturningStyleObject = (props: ObjectStyleProps) => ({ + padding: props.size === "big" ? "10px" : 2 +}); + +const ObjectStylesBox = styled.div` + ${functionReturningStyleObject} ${{ + backgroundColor: "red", + + // Supports nested objects (pseudo selectors, media queries, etc) + "@media screen and (min-width: 800px)": { + backgroundColor: "blue" + }, + + fontSize: 2 + }}; +`; +; + +/** + * attrs + */ + +const AttrsInput = styled.input.attrs({ + // we can define static props + type: "password", + + // or we can define dynamic ones + margin: (props: any) => (props.size as string) || "1em", + padding: (props: any) => (props.size as string) || "1em" +})` + color: palevioletred; + font-size: 1em; + border: 2px solid palevioletred; + border-radius: 3px; + + /* here we use the dynamically computed props */ + margin: ${props => props.margin}; + padding: ${props => props.padding}; +`; + +// 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 AttrsInputExtra = styled(AttrsInput).attrs({ autoComplete: "off" })``; +; + +/** + * component type + */ + +declare const A: React.ComponentClass; +declare const B: React.StatelessComponent; +declare const C: React.ComponentType; + +styled(A); // succeeds +styled(B); // succeeds +styled(C); // used to fail; see issue trail linked below + +// https://github.com/mui-org/material-ui/pull/8781#issuecomment-349460247 +// https://github.com/mui-org/material-ui/issues/9838 +// https://github.com/styled-components/styled-components/pull/1420 +// https://github.com/Microsoft/TypeScript/issues/21175 +// https://github.com/styled-components/styled-components/pull/1427 + +/** + * function themes + */ + +// Define our button, but with the use of props.theme this time +const ThemedButton = styled.button` + color: ${props => props.theme.fg}; + border: 2px solid ${props => props.theme.fg}; + background: ${props => props.theme.bg}; + + font-size: 1em; + margin: 1em; + padding: 0.25em 1em; + border-radius: 3px; +`; + +// Define our `fg` and `bg` on the theme +const theme2 = { + fg: "palevioletred", + bg: "white" +}; + +// This theme swaps `fg` and `bg` +const invertTheme = ({ fg, bg }: { fg: string; bg: string }) => ({ + fg: bg, + bg: fg +}); + +const MyApp = ( + +

+ Default Theme + + + Inverted Theme + +
+ +); + +/** + * withTheme HOC + */ + +class MyComponent extends React.Component> { + render() { + const { theme } = this.props; + + console.log("Current theme: ", theme); + + return

Hello

; + } +} + +const ThemedMyComponent = withTheme(MyComponent); + + { + // $ExpectType MyComponent | null + ref; +}}/>; +const themedRef = React.createRef(); +; + +interface WithThemeProps { + theme: { + color: string; + }; + text: string; +} + +const Component = (props: WithThemeProps) => ( +
{props.text}
+); + +const ComponentWithTheme = withTheme(Component); +; // ok +; // ok +{theme => }; + +/** + * isStyledComponent utility + */ + +const StyledComponent = styled.h1``; + +const StatelessComponent = () =>
; + +class ClassComponent extends React.Component { + render() { + return
; + } +} + +isStyledComponent(StyledComponent); +isStyledComponent(StatelessComponent); +isStyledComponent(ClassComponent); +isStyledComponent("div"); + +/** + * server side rendering + */ + +const SSRTitle = styled.h1` + font-size: 1.5em; + text-align: center; + color: palevioletred; +`; + +const sheet = new ServerStyleSheet(); +const html = sheet.collectStyles(Hello world); +const styleHtml = sheet.getStyleTags(); +const styleElement = sheet.getStyleElement(); +sheet.seal(); + +const sheet2 = new ServerStyleSheet(); +const element = ( + + Hello world + +); + +const css2 = sheet2.getStyleElement(); + +// Wrapping a node stream returned from renderToNodeStream with interleaveWithNodeStream + +const sheet3 = new ServerStyleSheet(); +const appStream = ReactDOMServer.renderToNodeStream(Hello world); +const wrappedCssStream: NodeJS.ReadableStream = sheet3.interleaveWithNodeStream( + appStream +); + +/** + * StyledComponent.withComponent + */ + +const WithComponentH1 = styled.h1` + color: palevioletred; + font-size: 1em; +`; + +function getRandomInt(min: number, max: number) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min)) + min; +} + +class Random extends React.Component { + render() { + const i = getRandomInt(1, 6); + + switch (i) { + case 1: + return

Hello World

; + case 2: + return

Hello World

; + case 3: + return

Hello World

; + case 4: + return

Hello World

; + case 5: + return
Hello World
; + case 6: + return
Hello World
; + default: + return null; + } + } +} + +const WithComponentH2 = WithComponentH1.withComponent("h2"); +const WithComponentAbbr = WithComponentH1.withComponent("abbr"); + +const WithComponentAnchor = WithComponentH1.withComponent("a"); +const AnchorContainer = () => ( + + withComponent Anchor + +); + +const WithComponentRandomHeading = WithComponentH1.withComponent(Random); + +const WithComponentCompA: React.SFC<{ a: number; className?: string }> = ({ + className +}) =>
; +const WithComponentCompB: React.SFC<{ b: number; className?: string }> = ({ + className +}) =>
; +const WithComponentStyledA = styled(WithComponentCompA)` + color: ${(props: { color: string }) => props.color}; +`; + +const WithComponentFirstStyledA = styled(WithComponentStyledA).attrs({ + a: 1 +})``; + +const WithComponentFirstStyledB = WithComponentFirstStyledA.withComponent( + WithComponentCompB +); + +const WithComponentFirstStyledANew = styled(WithComponentStyledA).attrs( + props => ({ a: 1 }) +)``; + +const test = () => [ + , + , + +]; + +const WithComponentRequired = styled((props: { to: string }) => ( + +))``; +// These tests pass in tsservice, but they fail in dtslint. I do not know why. +// ; // $ExpectError +// ; + +const WithComponentRequired2 = WithComponentRequired.withComponent("a"); +// These tests pass in tsservice, but they fail in dtslint. I do not know why. +// ; +// ; // $ExpectError + +// 4.0 With Component + +const asTest = ( + <> + + + +); + +interface TestContainerProps { + size: "big" | "small"; + test?: boolean; +} +const TestContainer = ({ size, test }: TestContainerProps) => { + return null; +}; + +const StyledTestContainer = styled(TestContainer)` + background: red; +`; + +interface Test2ContainerProps { + type: "foo" | "bar"; +} +class Test2Container extends React.Component { + render() { + return null; + } +} + +const containerTest = ( + +); + +// 4.0 refs + +const divFnRef = (ref: HTMLDivElement | null) => { + /* empty */ +}; +const divRef = React.createRef(); + +const StyledDiv = styled.div``; +; +; +; // $ExpectError + +const StyledStyledDiv = styled(StyledDiv)``; +; +; +; // $ExpectError + +const StyledA = StyledDiv.withComponent("a"); +// No longer generating a type error as of Feb. 6th, 2019 +// ; // $ExpectError + { + // $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 any 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; + }}; + `; + // can't use a FlattenInterpolation as the first argument, would make broken css + // $ExpectError + const ThemedDiv4 = styled.div(themedCss); + + const themedCssWithNesting = css(props => ({ + color: props.theme.color, + [ThemedDiv3]: { + color: "green" + } + })); + + const Global = createGlobalStyle` + ${themedCssWithNesting} + `; + + const WithProp = styled.div` + ${({ ok, theme: { color } }: { ok: boolean; theme: typeof theme }) => + ok && + css` + color: ${color}; + `} + `; + + const WithPropNested = styled.div` + ${({ ok }: { ok: boolean }) => + ok && + css` + color: ${({ theme: { color } }) => color}; + `} + `; + + return ( + + <> + + + + + + {theme => { + // $ExpectType string + theme.color; + return theme.color; + }} + + + + + + ); +} + +async function reexportCompatibility() { + const sc = await import("styled-components"); + const themed = sc as ThemedStyledComponentsModule; + + 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 any as ThemedStyledComponentsModule< + BaseTheme + >; + const extra = (await import("styled-components")) as any as ThemedStyledComponentsModule< + ExtraTheme, + BaseTheme + >; + + return ( + + <> + base} // $ExpectError + > + {() => null} + + ({ + ...base, + accent: "blue" + })} + > + {() => null} + + + + ); +} + +// NOTE: this is needed for some tests inside cssProp, +// but actually running this module augmentation will cause +// tests elsewhere to break, and there is no way to contain it. +// Uncomment out as needed to run tests. + +// declare module "styled-components" { +// interface DefaultTheme { +// background: string; +// } +// } + +function cssProp() { + function Custom(props: React.ComponentPropsWithoutRef<"div">) { + return
; + } + + const myCss = "background: blue;"; + + return ( + <> +
+
+
+
+
+
+
"blue"}; + `} + /> +
{ + // This requires the DefaultTheme augmentation + // // $ExpectType string + // props.theme.background; + return props.theme.background; + }}; + `} + /> + + + + "blue"}; + `} + /> + { + // This requires the DefaultTheme augmentation + // // $ExpectType string + // props.theme.background; + return props.theme.background; + }}; + `} + /> + + ); +} + +function validateArgumentsAndReturns() { + const t1: FlattenSimpleInterpolation[] = [ + css({ color: "blue" }), + css` + color: blue; + `, + css` + color: ${"blue"}; + ` + ]; + const t4: FlattenInterpolation = [ + css` + color: ${() => "blue"}; + `, + css(() => ({ color: "blue" })), + css( + () => + css` + color: "blue"; + ` + ) + ]; + + // if the first argument is array-like it's always treated as a string[], this breaks things + css( + // $ExpectError + css` + ${{ color: "blue" }} + ` + ); + // _technically_ valid as styled-components doesn't look at .raw but best not to support it + // $ExpectError + css([]); + + styled.div({ color: "blue" }); + styled.div(props => ({ color: props.theme.color })); + styled.div` + color: ${"blue"}; + `; + // These don't work for the same reason css doesn't work + styled.div( + // $ExpectError + css` + ${{ color: "blue" }} + ` + ); + // $ExpectError + styled.div([]); + + createGlobalStyle({ + ":root": { + color: "blue" + } + }); + createGlobalStyle` + :root { + color: blue; + } + `; + createGlobalStyle(() => ({ + ":root": { + color: "blue" + } + })); + // these are invalid for the same reason as in styled.div + // $ExpectError + createGlobalStyle(css` + :root { + color: ${() => "blue"}; + } + `); + // $ExpectError + createGlobalStyle([]); +} + +function validateDefaultProps() { + interface Props { + requiredProp: boolean; + optionalProp: string; // Shouldn't need to be optional here + } + + class MyComponent extends React.PureComponent { + static defaultProps = { + optionalProp: 'fallback' + }; + + render() { + const { requiredProp, optionalProp } = this.props; + return ( + + {requiredProp.toString()} + {optionalProp.toString()} + + ); + } + } + + const StyledComponent = styled(MyComponent)` + color: red + `; + + ; + + ; + + ; + + // still respects the type of optionalProp + ; // $ExpectError + + // example of a simple helper that sets defaultProps and update the type + type WithDefaultProps = C & { defaultProps: D }; + function withDefaultProps(component: C, defaultProps: D): WithDefaultProps { + (component as WithDefaultProps).defaultProps = defaultProps; + return component as WithDefaultProps; + } + + const OtherStyledComponent = withDefaultProps( + styled(MyComponent)` color: red `, + { requiredProp: true } + ); + + ; + + ; // $ExpectError +} + +interface WrapperProps { + className?: string; +} +export class WrapperClass extends React.Component { + render() { return
; } +} +const StyledWrapperClass = styled(WrapperClass)``; +// React.Component typings always add `children` to props, so this should accept children +const wrapperClass = Text; + +export class WrapperClassFuncChild extends React.Component any}> { + render() { return
; } +} +const StyledWrapperClassFuncChild = styled(WrapperClassFuncChild)``; +// React.Component typings always add `children` to props, so this should accept children +const wrapperClassNoChildrenGood = {() => "text"}; +const wrapperClassNoChildren = Text; // $ExpectError + +const WrapperFunction: React.FunctionComponent = () =>
; +const StyledWrapperFunction = styled(WrapperFunction)``; +// React.FunctionComponent typings always add `children` to props, so this should accept children +const wrapperFunction = Text; + +const WrapperFunc = (props: WrapperProps) =>
; +const StyledWrapperFunc = styled(WrapperFunc)``; +// No `children` in props, so this should generate an error +const wrapperFunc = Text; // $ExpectError + +function unionTest() { + interface Book { + kind: 'book'; + author: string; + } + + interface Magazine { + kind: 'magazine'; + issue: number; + } + + type SomethingToRead = (Book | Magazine); + + const Readable: React.FunctionComponent = props => { + if (props.kind === 'magazine') { + return
magazine #{props.issue}
; + } + + return
magazine #{props.author}
; + }; + + const StyledReadable = styled(Readable)` + font-size: ${props => props.kind === 'book' ? 16 : 14} + `; + + // undesired, fix was reverted because of https://github.com/Microsoft/TypeScript/issues/30663 + ; // $ExpectError + ; // $ExpectError +} + +function unionTest2() { + // Union of two non-overlapping types + type Props = { + foo: number, bar?: undefined + } | { + foo?: undefined, bar: string + }; + + const C = styled.div``; + + ; + ; + ; // $ExpectError + ; // $ExpectError +} diff --git a/types/styled-components/ts3.7/test/macro.tsx b/types/styled-components/ts3.7/test/macro.tsx new file mode 100644 index 0000000000..59acd127a3 --- /dev/null +++ b/types/styled-components/ts3.7/test/macro.tsx @@ -0,0 +1,20 @@ +import styled, { createGlobalStyle } from 'styled-components/macro'; + +// Check that the default export works. +const TitleFromMacro = styled.h1` + font-size: 1.5em; + text-align: center; + color: palevioletred; +`; + +// Check that named exports work as well. +const GlobalStyleFromMacro = createGlobalStyle` + @font-face { + font-family: 'Operator Mono'; + src: url('../fonts/Operator-Mono.ttf'); + } + + body { + margin: 0; + } +`; diff --git a/types/styled-components/ts3.7/test/native.tsx b/types/styled-components/ts3.7/test/native.tsx new file mode 100644 index 0000000000..da2ba83390 --- /dev/null +++ b/types/styled-components/ts3.7/test/native.tsx @@ -0,0 +1,198 @@ +import * as React from "react"; +import * as ReactNative from "react-native"; +import * as ReactDOMServer from "react-dom/server"; + +import styled, { + css, + isStyledComponent, + ThemeProps, + ThemeProvider, + withTheme, + ThemeConsumer, + ReactNativeThemedStyledComponentsModule, +} from "styled-components/native"; +import {} from "styled-components/cssprop"; + +const StyledView = styled.View` + background-color: papayawhip; +`; + +const StyledText = styled(ReactNative.Text)` + color: palevioletred; +`; + +class MyReactNativeComponent extends React.Component { + render() { + return ( + + Hello World! + + ); + } +} + +const ValidProp = ; +const invalidProp = ; // $ExpectError +const invalidTag = styled.div` margin-top: 5px; `; // $ExpectError + +interface MyTheme { + primary: string; +} + +interface ButtonProps { + name: string; + primary?: boolean; + theme: MyTheme; +} + +class MyButton extends React.Component { + render() { + return Custom button; + } +} + +const TomatoButton = styled(MyButton)` + color: tomato; + border-color: tomato; +`; + +// needs name prop, but not theme prop +const tomatoElement = ; + +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, + ThemeProvider, + ThemeConsumer + } = (await import("styled-components/native")) as any as ReactNativeThemedStyledComponentsModule< + typeof theme + >; + + const ThemedView = styled.View` + background: ${props => { + // $ExpectType string + props.theme.color; + // $ExpectType string | undefined + props.testID; + return props.theme.color; + }}; + `; + const ThemedView2 = styled.View(props => { + // $ExpectType string + props.theme.color; + // $ExpectType string | undefined + props.testID; + + return { + background: props.theme.color + }; + }); + const ThemedView3 = styled.View(props => { + // $ExpectType string + props.theme.color; + // $ExpectType string | undefined + props.testID; + + 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; + }}; +`; +// can't use a FlattenInterpolation as the first argument, would make broken css +// $ExpectError +const ThemedView4 = styled.View(themedCss); + +const themedCssWithNesting = css(props => ({ + color: props.theme.color, + [ThemedView3]: { + color: "green" + } +})); + +return ( + + <> + + + + + {theme => { + // $ExpectType string + theme.color; + return theme.color; + }} + + + + ); +} + +async function reexportCompatibility() { + const sc = await import("styled-components/native"); + const themed = sc as ReactNativeThemedStyledComponentsModule; + + 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/native")) as any as ReactNativeThemedStyledComponentsModule< + BaseTheme + >; + const extra = (await import("styled-components/native")) as any as ReactNativeThemedStyledComponentsModule< + ExtraTheme, + BaseTheme + >; + + return ( + + <> + base} // $ExpectError + > + {() => null} + + ({ + ...base, + accent: "blue" + })} + > + {() => null} + + + + ); +} diff --git a/types/styled-components/ts3.7/tsconfig.json b/types/styled-components/ts3.7/tsconfig.json new file mode 100644 index 0000000000..cc3fb68c6c --- /dev/null +++ b/types/styled-components/ts3.7/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "baseUrl": "../../", + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": [ + "es6" + ], + "module": "commonjs", + "noEmit": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "typeRoots": [ + "../../" + ], + "types": [] + }, + "files": [ + "index.d.ts", + "native.d.ts", + "macro.d.ts", + "cssprop.d.ts", + "test/index.tsx", + "test/native.tsx", + "test/macro.tsx" + ] +} \ No newline at end of file diff --git a/types/styled-components/ts3.7/tslint.json b/types/styled-components/ts3.7/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/styled-components/ts3.7/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }