From 002e3140e5170232c9d91be15860bddb0a0400b2 Mon Sep 17 00:00:00 2001 From: Evan Scott Date: Sat, 18 Aug 2018 01:13:10 -0500 Subject: [PATCH] add styled-components --- types/styled-components/index.d.ts | 212 +++++++ .../styled-components-tests.tsx | 548 ++++++++++++++++++ types/styled-components/tsconfig.json | 17 + types/styled-components/tslint.json | 1 + 4 files changed, 778 insertions(+) create mode 100644 types/styled-components/index.d.ts create mode 100644 types/styled-components/styled-components-tests.tsx create mode 100644 types/styled-components/tsconfig.json create mode 100644 types/styled-components/tslint.json diff --git a/types/styled-components/index.d.ts b/types/styled-components/index.d.ts new file mode 100644 index 0000000000..9654334af2 --- /dev/null +++ b/types/styled-components/index.d.ts @@ -0,0 +1,212 @@ +// Type definitions for styled-components 3.0 +// Project: https://github.com/styled-components/styled-components +// Definitions by: Igor Oleinikov +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.8 + +/// + +import * as React from 'react'; + +export interface ThemeProps { + theme: T; +} + +export type ThemedStyledProps = P & ThemeProps; +export type StyledProps

= ThemedStyledProps; + +export type ThemedOuterStyledProps = P & { + theme?: T; + innerRef?: + | ((instance: any) => void) + | React.RefObject; +}; +export type OuterStyledProps

= ThemedOuterStyledProps; + +export type FalseyValue = undefined | null | false; +export type Interpolation

= + | FlattenInterpolation

+ | ReadonlyArray< + FlattenInterpolation

| ReadonlyArray> + >; +export type FlattenInterpolation

= + | InterpolationValue + | InterpolationFunction

; +export type InterpolationValue = + | string + | number + | Styles + | FalseyValue + | StyledComponentClass; +export type SimpleInterpolation = + | InterpolationValue + | ReadonlyArray>; +export interface Styles { + [ruleOrSelector: string]: string | number | Styles; +} + +export type InterpolationFunction

= (props: P) => Interpolation

; + +type Attrs, T> = { + [K in keyof A]: ((props: ThemedStyledProps) => A[K]) | A[K] +}; + +export interface StyledComponentClass + extends React.ComponentClass> { + extend: ThemedStyledFunction; + + withComponent( + tag: K, + ): StyledComponentClass< + JSX.IntrinsicElements[K], + T, + JSX.IntrinsicElements[K] & O + >; + withComponent( + element: React.ComponentType, + ): StyledComponentClass; +} + +export interface ThemedStyledFunction { + ( + strings: TemplateStringsArray, + ...interpolations: Array>> + ): StyledComponentClass; + ( + strings: TemplateStringsArray, + ...interpolations: Array>> + ): StyledComponentClass

; + attrs = {}>( + attrs: Attrs

, + ): ThemedStyledFunction, T, DiffBetween>; +} + +export type StyledFunction

= ThemedStyledFunction; + +type ThemedStyledComponentFactories = { + [TTag in keyof JSX.IntrinsicElements]: ThemedStyledFunction< + JSX.IntrinsicElements[TTag], + T + > +}; + +export interface ThemedBaseStyledInterface + extends ThemedStyledComponentFactories { + ( + tag: TTag, + ): ThemedStyledFunction; + (component: StyledComponentClass): ThemedStyledFunction< + P, + T, + O + >; +

( + component: React.ComponentType

, + ): ThemedStyledFunction>; +} +export type BaseStyledInterface = ThemedBaseStyledInterface; + +export type ThemedStyledInterface = ThemedBaseStyledInterface; +export type StyledInterface = ThemedStyledInterface; + +export interface ThemeProviderProps { + theme?: T | ((theme: T) => T); +} +export type ThemeProviderComponent = React.ComponentClass< + ThemeProviderProps +>; + +export interface ThemedCssFunction { + ( + strings: TemplateStringsArray, + ...interpolations: SimpleInterpolation[] + ): InterpolationValue[]; +

( + strings: TemplateStringsArray, + ...interpolations: Array>> + ): Array>>; +} + +// Helper type operators +type KeyofBase = keyof any; +type Diff = ({ [P in T]: P } & + { [P in U]: never })[T]; +type Omit = Pick>; +type DiffBetween = Pick> & + Pick>; +type WithOptionalTheme

= Omit & { + theme?: T; +}; + +export interface ThemedStyledComponentsModule { + default: ThemedStyledInterface; + + css: ThemedCssFunction; + keyframes( + strings: TemplateStringsArray, + ...interpolations: SimpleInterpolation[] + ): string; + injectGlobal( + strings: TemplateStringsArray, + ...interpolations: SimpleInterpolation[] + ): void; + withTheme

( + component: React.ComponentType

, + ): React.ComponentClass>; + + ThemeProvider: ThemeProviderComponent; +} + +declare const styled: StyledInterface; + +export const css: ThemedCssFunction; + +export function withTheme

( + component: React.ComponentType

, +): React.ComponentClass>; + +export function keyframes( + strings: TemplateStringsArray, + ...interpolations: SimpleInterpolation[] +): string; + +export function injectGlobal( + strings: TemplateStringsArray, + ...interpolations: SimpleInterpolation[] +): void; + +export function consolidateStreamedStyles(): void; + +export function isStyledComponent( + target: any, +): target is StyledComponentClass<{}, {}>; + +export const ThemeProvider: ThemeProviderComponent; + +interface StylesheetComponentProps { + sheet: ServerStyleSheet; +} + +interface StyleSheetManagerProps { + sheet?: StyleSheet; + target?: Node; +} + +export class StyleSheetManager extends React.Component< + StyleSheetManagerProps +> {} + +export class ServerStyleSheet { + collectStyles( + tree: React.ReactNode, + ): React.ReactElement; + + getStyleTags(): string; + getStyleElement(): Array>; + interleaveWithNodeStream( + readableStream: NodeJS.ReadableStream, + ): NodeJS.ReadableStream; + instance: StyleSheet; +} + +export default styled; diff --git a/types/styled-components/styled-components-tests.tsx b/types/styled-components/styled-components-tests.tsx new file mode 100644 index 0000000000..d2e6cc04cd --- /dev/null +++ b/types/styled-components/styled-components-tests.tsx @@ -0,0 +1,548 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import * as ReactDOMServer from 'react-dom/server'; + +import styled, { + css, + injectGlobal, + isStyledComponent, + keyframes, + ServerStyleSheet, + StyleSheetManager, + ThemeProps, + ThemeProvider, + withTheme, +} from 'styled-components'; + +/** + * 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 theme = { + main: 'mediumseagreen', +}; + +injectGlobal` + @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}> + <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'}; +`; +// injectGlobal accepts simple interpolations if they're not using functions +injectGlobal` + ${'font-size'}: ${10}pt; + ${cssWithValues1} + ${[cssWithValues1, cssWithValues2]} +`; + +// 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]} +`; +// css with function interpolations cannot be used in injectGlobal +/* +injectGlobal` + ${cssWithFunc1} +`; +*/ + +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}; +`; + +/** + * 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 + +/** + * extend + */ + +const ExtendButton = styled.button` + color: palevioletred; + font-size: 1em; + margin: 1em; + padding: 0.25em 1em; + border: 2px solid palevioletred; + border-radius: 3px; +`; + +// We're extending Button with some extra styles +const TomatoExtendButton = ExtendButton.extend` + color: tomato; + border-color: tomato; +`; + +/** + * 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); + +interface WithThemeProps { + theme: { + color: string; + }; + text: string; +} + +const Component = (props: WithThemeProps) => ( +
{props.text}
+); + +const ComponentWithTheme = withTheme(Component); + +; // ok +; // ok + +/** + * 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(); + +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 test = () => [ + , + , +]; diff --git a/types/styled-components/tsconfig.json b/types/styled-components/tsconfig.json new file mode 100644 index 0000000000..26cb1835d5 --- /dev/null +++ b/types/styled-components/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "baseUrl": "../", + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["es6", "dom"], + "module": "commonjs", + "noEmit": true, + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "typeRoots": ["../"], + "types": [] + }, + "files": ["index.d.ts", "styled-components-tests.tsx"] +} diff --git a/types/styled-components/tslint.json b/types/styled-components/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/styled-components/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }