Repository: sergeybekrin/styled-email-components Branch: main Commit: ce798e3d21f8 Files: 24 Total size: 46.6 KB Directory structure: gitextract_4t0ert70/ ├── .github/ │ └── workflows/ │ └── test.yml ├── .gitignore ├── .gitmodules ├── .vscode/ │ └── settings.json ├── LICENSE ├── babel.config.js ├── jest.config.js ├── package.json ├── readme.md ├── rollup.config.js ├── src/ │ ├── css-to-style.js │ ├── index.js │ ├── inline-style.js │ ├── is-vml-tag.js │ ├── styled-email-component.js │ ├── stylesheet.js │ └── xhtml-elements.js ├── styled-email-components.d.ts ├── test/ │ ├── namespace.spec.js │ ├── specific.spec.js │ ├── styled.spec.js │ ├── theme.spec.js │ └── typings.spec.tsx └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: submodules: recursive - uses: actions/setup-node@v2 with: node-version: 14 - run: yarn - run: yarn build - run: yarn test ================================================ FILE: .gitignore ================================================ node_modules/ dist/ *.log ================================================ FILE: .gitmodules ================================================ [submodule "styled-components"] path = vendor/styled-components url = https://github.com/styled-components/styled-components.git branch = legacy-v5 ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": [ "camelcase" ] } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Sergey Bekrin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: babel.config.js ================================================ module.exports = { presets: ["./vendor/styled-components/babel-preset"], plugins: [ // Replace `styled-components/src/*` imports with relative `./vendor/styled-components/*` to let rollup bundle the original sources [ "babel-plugin-module-resolver", { alias: { "styled-components/src": "./vendor/styled-components/packages/styled-components/src", }, }, ], ], }; ================================================ FILE: jest.config.js ================================================ module.exports = { testPathIgnorePatterns: ["/node_modules/", "/vendor/"], transformIgnorePatterns: ["/node_modules/(?!styled-components).+\\.js$"], setupFiles: ['./vendor/styled-components/packages/styled-components/src/test/globals.js'] } ================================================ FILE: package.json ================================================ { "name": "styled-email-components", "version": "5.0.3", "description": "💌 styled-components for emails", "repository": "sbekrin/styled-email-components", "author": "Sergey Bekrin ", "license": "MIT", "main": "dist/styled-email-components.cjs.js", "jsnext:main": "dist/styled-email-components.esm.js", "module": "dist/styled-email-components.esm.js", "browser": { "./dist/styled-email-components.esm.js": "./dist/styled-email-components.browser.esm.js", "./dist/styled-email-components.cjs.js": "./dist/styled-email-components.browser.cjs.js" }, "files": [ "dist", "styled-email-components.d.ts" ], "types": "./styled-email-components.d.ts", "scripts": { "prepare": "yarn --cwd vendor/styled-components/packages/styled-components generateErrors", "test": "jest", "build": "rollup -c" }, "dependencies": { "@emotion/is-prop-valid": "1.1.0", "@emotion/stylis": "0.8.5", "@emotion/unitless": "0.7.5", "color-shorthand-hex-to-six-digit": "3.0.7", "css-shorthand-expand": "1.2.0", "hoist-non-react-statics": "3.3.2", "lodash.camelcase": "4.3.0", "postcss-safe-parser": "5.0.2", "shallowequal": "1.1.0" }, "devDependencies": { "@babel/cli": "7.0.0", "@babel/core": "7.0.0", "@babel/plugin-external-helpers": "7.0.0", "@babel/plugin-proposal-class-properties": "7.0.0", "@babel/plugin-proposal-object-rest-spread": "7.0.0", "@babel/preset-env": "7.0.0", "@babel/preset-flow": "7.12.13", "@babel/preset-react": "7.0.0", "@types/jest": "26.0.22", "babel-jest": "26.5.2", "babel-plugin-add-module-exports": "1.0.2", "babel-plugin-module-resolver": "4.1.0", "babel-plugin-tester": "10.0.0", "babel-plugin-transform-react-remove-prop-types": "0.4.24", "jest": "26.6.3", "react": "17.0.1", "react-dom": "17.0.1", "react-is": "17.0.1", "react-test-renderer": "17.0.1", "rollup": "1.13.1", "rollup-plugin-babel": "4.3.2", "rollup-plugin-commonjs": "10.0.0", "rollup-plugin-flow": "github:probablyup/rollup-plugin-flow#breaking-update-flow-remove-types", "rollup-plugin-json": "4.0.0", "rollup-plugin-node-resolve": "5.0.1", "rollup-plugin-replace": "2.2.0", "rollup-plugin-sourcemaps": "0.4.2", "rollup-plugin-terser": "5.0.0", "styled-components": "file:./vendor/styled-components/packages/styled-components" } } ================================================ FILE: readme.md ================================================ # 💌 styled-email-components [![npm Version](https://img.shields.io/npm/v/styled-email-components.svg)](https://www.npmjs.com/package/styled-email-components) [![Build Status](https://img.shields.io/travis/sbekrin/styled-email-components.svg)](https://travis-ci.org/sbekrin/styled-email-components) [![dependencies Status](https://img.shields.io/david/sbekrin/styled-email-components.svg)](https://david-dm.org/sbekrin/styled-email-components) [![devDependencies Status](https://img.shields.io/david/dev/sbekrin/styled-email-components.svg)](https://david-dm.org/sbekrin/styled-email-components?type=dev) Extension of [`styled-components (v5.x)`](https://www.styled-components.com/) with essential features for building email components. ## Features - Styles are injected inline - [Shorthand rules](./src/css-to-style.js#L6) are expanded - [`styled.*` aliases](./src/utils/xhtml-elements.js) are XHTML compliant - Supports [Outlook-specific elements](#outlook-specific-vml-elements) - Compatible with [original APIs](https://www.styled-components.com/docs/api) - Provides TypeScript typings ## Motivation `styled-components` is a universal styling solution with great developer experience and low learning curve. Unfortunately, there's no native support for inline styling which is essential for building emails. This module adds all necessary features to build mail-first components. ## Installation yarn: ```sh yarn add styled-email-components ``` npm: ```sh npm install --save styled-email-components ``` ## Getting Started Check original [Getting Started](https://www.styled-components.com/docs/basics#getting-started) for more examples. ```js import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import styled from 'styled-email-components'; const Link = styled.a` font-family: sans-serif; background: blue; color: white; `; renderToStaticMarkup(Hey), // 👇 output // Hey ``` ## API ### `styled.*` This module sets list of XHTML 1.0 Transitional [element aliases](./src/utils/xhtml-elements.js) instead of the original HTML5 set, which is a widely used doctype in emails. ### Outlook-specific VML elements In addition to XHTML elements, `styled.vml.*`, `styled.wml.*` and `styled.office.*` aliases are available. These are simple proxies and pass tag names as-is with `v:`, `w:` and `o:` prefixes respectively. ### Other APIs [Original APIs](https://www.styled-components.com/docs/api) are mirrored without any modifications from `styled-components`. Make sure to check [server-side rendering](https://www.styled-components.com/docs/advanced#server-side-rendering) guide for rendering the final email. ## License MIT © [Sergey Bekrin](http://bekrin.me/) ================================================ FILE: rollup.config.js ================================================ import nodeResolve from "rollup-plugin-node-resolve"; import replace from "rollup-plugin-replace"; import commonjs from "rollup-plugin-commonjs"; import babel from "rollup-plugin-babel"; import json from "rollup-plugin-json"; import flow from "rollup-plugin-flow"; import { terser } from "rollup-plugin-terser"; import sourceMaps from "rollup-plugin-sourcemaps"; import pkg from "./package.json"; /** * NODE_ENV explicit replacement is only needed for standalone packages, as webpack * automatically will replace it otherwise in the downstream build. */ const cjs = { exports: "named", format: "cjs", sourcemap: true, }; const esm = { format: "esm", sourcemap: true, }; const getCJS = (override) => ({ ...cjs, ...override }); const getESM = (override) => ({ ...esm, ...override }); const commonPlugins = [ flow({ // needed for sourcemaps to be properly generated pretty: true, }), sourceMaps(), json(), nodeResolve(), babel({ configFile: require.resolve("./babel.config.js"), exclude: ["node_modules/**"], }), commonjs({ ignoreGlobal: true, namedExports: { "react-is": ["isElement", "isValidElementType", "ForwardRef", "typeof"], }, }), replace({ __VERSION__: JSON.stringify(pkg.version), }), ]; // this should always be last const minifierPlugin = terser({ compress: { passes: 2, }, sourcemap: true, }); const configBase = { input: "./src/index.js", // \0 is rollup convention for generated in memory modules external: (id) => !id.startsWith("\0") && !id.startsWith(".") && !id.startsWith("/"), plugins: commonPlugins, }; const serverConfig = { ...configBase, output: [ getESM({ file: "dist/styled-email-components.esm.js" }), getCJS({ file: "dist/styled-email-components.cjs.js" }), ], plugins: configBase.plugins.concat( replace({ __SERVER__: JSON.stringify(true), }), minifierPlugin ), }; const browserConfig = { ...configBase, output: [ getESM({ file: "dist/styled-email-components.browser.esm.js" }), getCJS({ file: "dist/styled-email-components.browser.cjs.js" }), ], plugins: configBase.plugins.concat( replace({ __SERVER__: JSON.stringify(false), }), minifierPlugin ), }; export default [serverConfig, browserConfig]; ================================================ FILE: src/css-to-style.js ================================================ import expandRule from "css-shorthand-expand"; import { conv as expandHexColor } from "color-shorthand-hex-to-six-digit"; import camelCase from "lodash.camelcase"; function shouldExpandRule(name) { return [ "background", "font", "padding", "margin", "border", "border-width", "border-style", "border-color", "border-top", "border-right", "border-bottom", "border-left", "border-radius", "outline", ].includes(name); } function convertCssPairsToStyle(rules) { return ( rules // Expand shorthand rules .reduce((out, [name, value]) => { if (shouldExpandRule(name)) { const expanded = expandRule(name, value); const next = Object.keys(expanded).map((rule) => [ rule, expanded[rule], ]); return [...out, ...next]; } return [...out, [name, value]]; }, []) // Expand colors to 6 digits .reduce((out, [name, value]) => { return { ...out, [camelCase(name)]: /color/i.test(name) ? expandHexColor(value) : value, }; }, {}) ); } export default convertCssPairsToStyle; ================================================ FILE: src/index.js ================================================ import constructWithOptions from "styled-components/src/constructors/constructWithOptions"; import xhtmlElements from "./xhtml-elements"; import StyleSheet from "./stylesheet"; import createInlineStyle from "./inline-style"; import createStyledEmailComponent from "./styled-email-component"; const InlineStyle = createInlineStyle(StyleSheet); const StyledEmailComponent = createStyledEmailComponent(InlineStyle); const styled = (tag) => constructWithOptions(StyledEmailComponent, tag); // Set xhtml element aliases xhtmlElements.forEach((element) => Object.defineProperty(styled, element, { enumerable: true, configurable: false, get() { return styled(element); }, }) ); // Set VML (v:*), WML (w:*) and Office (o:*) dynamic aliases ["vml", "wml", "office"].forEach((namespace) => { Object.defineProperty(styled, namespace, { enumerable: true, configurable: false, get() { const target = {}; return new Proxy(target, { get(object, property) { if (property in object) { return object[property]; } if (typeof property === "string") { return styled(`${namespace.charAt(0)}:${property}`); } return undefined; }, }); }, }); }); export { createGlobalStyle, css, isStyledComponent, keyframes, ServerStyleSheet, StyleSheetConsumer, StyleSheetContext, StyleSheetManager, ThemeConsumer, ThemeContext, ThemeProvider, useTheme, version, withTheme, } from "styled-components/src/base"; export default styled; ================================================ FILE: src/inline-style.js ================================================ import generateComponentId from "styled-components/src/utils/generateComponentId"; import flatten from "styled-components/src/utils/flatten"; import parse from "postcss-safe-parser"; import cssToStyle from "./css-to-style"; const generated = new Map(); export const resetStyleCache = () => { generated.clear(); }; export default (stylesheet) => { return class MailInlineStyle { constructor(rules) { this.rules = rules; } generateStyleObject(executionContext) { const flatStyles = flatten(this.rules, executionContext).join(""); const hash = generateComponentId(flatStyles); if (!generated.has(hash)) { const rules = []; parse(flatStyles).each((node) => { switch (node.type) { case "decl": rules.push([node.prop, node.value]); return; case "comment": return; default: if (process.env.NODE_ENV !== "production") { console.warn( `Node of type ${node.type} not supported as an inline style` ); } } }); const styles = cssToStyle(rules); const instance = stylesheet.create({ generated: styles }); generated.set(hash, instance.generated); } return generated.get(hash); } }; }; ================================================ FILE: src/is-vml-tag.js ================================================ export default function isVmlTag(tag) { return typeof tag === "string" && /^(v|w|o):/i.test(tag); } ================================================ FILE: src/styled-email-component.js ================================================ import validAttr from "@emotion/is-prop-valid"; import hoist from "hoist-non-react-statics"; import React from "react"; import determineTheme from "styled-components/src/utils/determineTheme"; import { EMPTY_ARRAY, EMPTY_OBJECT } from "styled-components/src/utils/empties"; import generateComponentId from "styled-components/src/utils/generateComponentId"; import generateDisplayName from "styled-components/src/utils/generateDisplayName"; import getComponentName from "styled-components/src/utils/getComponentName"; import isTag from "styled-components/src/utils/isTag"; import isFunction from "styled-components/src/utils/isFunction"; import isStyledComponent from "styled-components/src/utils/isStyledComponent"; import merge from "styled-components/src/utils/mixinDeep"; import { ThemeContext } from "styled-components/src/models/ThemeProvider"; import StyleSheet from "./stylesheet"; import isVmlTag from "./is-vml-tag"; const identifiers = {}; /* We depend on components having unique IDs */ function generateId(displayName, parentComponentId) { const name = typeof displayName !== "string" ? "sc" : escape(displayName); // Ensure that no displayName can lead to duplicate componentIds identifiers[name] = (identifiers[name] || 0) + 1; const componentId = `${name}-${generateComponentId( name + identifiers[name] )}`; return parentComponentId ? `${parentComponentId}-${componentId}` : componentId; } function useResolvedAttrs(theme = EMPTY_OBJECT, props, attrs) { // NOTE: can't memoize this // returns [context, resolvedAttrs] // where resolvedAttrs is only the things injected by the attrs themselves const context = { ...props, theme }; const resolvedAttrs = {}; attrs.forEach((attrDef) => { let resolvedAttrDef = attrDef; let key; if (isFunction(resolvedAttrDef)) { resolvedAttrDef = resolvedAttrDef(context); } /* eslint-disable guard-for-in */ for (key in resolvedAttrDef) { context[key] = resolvedAttrs[key] = resolvedAttrDef[key]; } /* eslint-enable guard-for-in */ }); return [context, resolvedAttrs]; } function useStyledComponentImpl(forwardedComponent, props, forwardedRef) { const { attrs: componentAttrs, inlineStyle, defaultProps, shouldForwardProp, target, } = forwardedComponent; // NOTE: the non-hooks version only subscribes to this when !componentStyle.isStatic, // but that'd be against the rules-of-hooks. We could be naughty and do it anyway as it // should be an immutable value, but behave for now. const theme = determineTheme( props, React.useContext(ThemeContext), defaultProps ); const [context, attrs] = useResolvedAttrs( theme || EMPTY_OBJECT, props, componentAttrs ); const generatedStyles = inlineStyle.generateStyleObject(context); const refToForward = forwardedRef; const elementToBeCreated = attrs.$as || props.$as || attrs.as || props.as || target; const isTargetTag = isTag(elementToBeCreated); const isTargetVmlTag = isVmlTag(elementToBeCreated); const computedProps = attrs !== props ? { ...props, ...attrs } : props; const propsForElement = {}; // eslint-disable-next-line guard-for-in for (const key in computedProps) { if (key[0] === "$" || key === "as") continue; else if (key === "forwardedAs") { propsForElement.as = computedProps[key]; } else if ( shouldForwardProp ? shouldForwardProp(key, validAttr, elementToBeCreated) : isTargetVmlTag ? true : isTargetTag ? validAttr(key) : true ) { propsForElement[key] = computedProps[key]; } } propsForElement.style = StyleSheet.flatten([ generatedStyles, props.style, attrs.style, ]); propsForElement.ref = refToForward; return React.createElement(elementToBeCreated, propsForElement); } export default (MailInlineStyle) => { const createStyledEmailComponent = (target, options, rules) => { const isTargetStyledComp = isStyledComponent(target); const { displayName = generateDisplayName(target), componentId = generateId(options.displayName, options.parentComponentId), attrs = EMPTY_ARRAY, } = options; const styledComponentId = options.displayName && options.componentId ? `${escape(options.displayName)}-${options.componentId}` : options.componentId || componentId; // fold the underlying StyledComponent attrs up (implicit extend) const finalAttrs = isTargetStyledComp && target.attrs ? target.attrs.concat(attrs).filter(Boolean) : attrs; // eslint-disable-next-line prefer-destructuring let shouldForwardProp = options.shouldForwardProp; if (isTargetStyledComp && target.shouldForwardProp) { if (options.shouldForwardProp) { // compose nested shouldForwardProp calls shouldForwardProp = (prop, filterFn, elementToBeCreated) => target.shouldForwardProp(prop, filterFn, elementToBeCreated) && options.shouldForwardProp(prop, filterFn, elementToBeCreated); } else { // eslint-disable-next-line prefer-destructuring shouldForwardProp = target.shouldForwardProp; } } /** * forwardRef creates a new interim component, which we'll take advantage of * instead of extending ParentComponent to create _another_ interim class */ let WrappedStyledComponent; // eslint-disable-next-line react-hooks/rules-of-hooks const forwardRef = (props, ref) => useStyledComponentImpl(WrappedStyledComponent, props, ref); forwardRef.displayName = displayName; WrappedStyledComponent = React.forwardRef(forwardRef); WrappedStyledComponent.attrs = finalAttrs; WrappedStyledComponent.inlineStyle = new MailInlineStyle( isTargetStyledComp ? target.inlineStyle.rules.concat(rules) : rules ); WrappedStyledComponent.displayName = displayName; WrappedStyledComponent.shouldForwardProp = shouldForwardProp; WrappedStyledComponent.styledComponentId = styledComponentId; // fold the underlying StyledComponent target up since we folded the styles WrappedStyledComponent.target = isTargetStyledComp ? target.target : target; WrappedStyledComponent.withComponent = function withComponent(tag) { const { componentId: previousComponentId, ...optionsToCopy } = options; const newComponentId = previousComponentId && `${previousComponentId}-${escape(getComponentName(tag))}`; const newOptions = { ...optionsToCopy, attrs: finalAttrs, componentId: newComponentId, }; return createStyledEmailComponent(tag, newOptions, rules); }; Object.defineProperty(WrappedStyledComponent, "defaultProps", { get() { return this._foldedDefaultProps; }, set(obj) { this._foldedDefaultProps = isTargetStyledComp ? merge({}, target.defaultProps, obj) : obj; }, }); hoist(WrappedStyledComponent, target, { // all SC-specific things should not be hoisted attrs: true, inlineStyle: true, displayName: true, shouldForwardProp: true, styledComponentId: true, target: true, withComponent: true, }); return WrappedStyledComponent; }; return createStyledEmailComponent; }; ================================================ FILE: src/stylesheet.js ================================================ let lastId = 0; const registry = {}; const guid = () => lastId++; const registerStyle = (style) => { const id = guid(); registry[id] = style; return id; }; const resolveStyle = (id) => registry[id]; const create = (styles) => { const result = {}; Object.keys(styles).forEach((key) => { result[key] = registerStyle(styles[key]); }); return result; }; const merge = (left = {}, right = {}) => ({ ...left, ...right }); const flatten = (input) => { if (Array.isArray(input)) { return input.reduce((acc, val) => merge(acc, flatten(val)), {}); } else if (typeof input === "number") { return resolveStyle(input); } else if (!input) { return undefined; } return input; }; const resolve = (style) => flatten(style); const StyleSheet = { hairlineWidth: 1, absoluteFill: registerStyle({ position: "absolute", top: 0, left: 0, bottom: 0, right: 0, }), create, flatten, resolve, }; export default StyleSheet; ================================================ FILE: src/xhtml-elements.js ================================================ // List of XHTML 1.0 Transitional elements export default [ "a", "abbr", "acronym", "address", "applet", "area", "b", "base", "basefont", "bdo", "big", "blockquote", "body", "br", "button", "caption", "center", "cite", "code", "col", "colgroup", "dd", "del", "dfn", "dir", "div", "dl", "dt", "em", "fieldset", "font", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "label", "legend", "li", "link", "map", "menu", "meta", "noframes", "noscript", "object", "ol", "optgroup", "option", "p", "param", "pre", "q", "s", "samp", "script", "select", "small", "span", "strike", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "title", "tr", "tt", "u", "ul", "var", ]; ================================================ FILE: styled-email-components.d.ts ================================================ import * as React from "react"; import { css, keyframes, injectGlobal, isStyledComponent, consolidateStreamedStyles, ThemeProvider, withTheme, ServerStyleSheet, StyleSheetManager, ThemedStyledFunction, StyledComponentClass, } from "styled-components"; // #region xhtmltypes export interface XHTMLElements { a: React.DetailedHTMLProps< React.AnchorHTMLAttributes, HTMLAnchorElement >; abbr: React.DetailedHTMLProps, HTMLElement>; acronym: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; address: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; applet: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; area: React.DetailedHTMLProps< React.HTMLAttributes, HTMLAreaElement >; b: React.DetailedHTMLProps, HTMLElement>; base: React.DetailedHTMLProps< React.BaseHTMLAttributes, HTMLBaseElement >; basefont: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; bdo: React.DetailedHTMLProps, HTMLElement>; big: React.DetailedHTMLProps, HTMLElement>; blockquote: React.DetailedHTMLProps< React.BlockquoteHTMLAttributes, HTMLElement >; body: React.DetailedHTMLProps< React.HTMLAttributes, HTMLBodyElement >; br: React.DetailedHTMLProps< React.HTMLAttributes, HTMLBRElement >; button: React.DetailedHTMLProps< React.ButtonHTMLAttributes, HTMLButtonElement >; caption: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; center: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; cite: React.DetailedHTMLProps, HTMLElement>; code: React.DetailedHTMLProps, HTMLElement>; col: React.DetailedHTMLProps< React.ColHTMLAttributes, HTMLTableColElement >; colgroup: React.DetailedHTMLProps< React.ColgroupHTMLAttributes, HTMLTableColElement >; dd: React.DetailedHTMLProps, HTMLElement>; del: React.DetailedHTMLProps< React.DelHTMLAttributes, HTMLElement >; dfn: React.DetailedHTMLProps, HTMLElement>; dir: React.DetailedHTMLProps, HTMLElement>; div: React.DetailedHTMLProps< React.HTMLAttributes, HTMLDivElement >; dl: React.DetailedHTMLProps< React.HTMLAttributes, HTMLDListElement >; dt: React.DetailedHTMLProps, HTMLElement>; em: React.DetailedHTMLProps, HTMLElement>; fieldset: React.DetailedHTMLProps< React.FieldsetHTMLAttributes, HTMLFieldSetElement >; font: React.DetailedHTMLProps< React.HTMLAttributes, HTMLFontElement >; form: React.DetailedHTMLProps< React.FormHTMLAttributes, HTMLFormElement >; h1: React.DetailedHTMLProps< React.HTMLAttributes, HTMLHeadingElement >; h2: React.DetailedHTMLProps< React.HTMLAttributes, HTMLHeadingElement >; h3: React.DetailedHTMLProps< React.HTMLAttributes, HTMLHeadingElement >; h4: React.DetailedHTMLProps< React.HTMLAttributes, HTMLHeadingElement >; h5: React.DetailedHTMLProps< React.HTMLAttributes, HTMLHeadingElement >; h6: React.DetailedHTMLProps< React.HTMLAttributes, HTMLHeadingElement >; head: React.DetailedHTMLProps< React.HTMLAttributes, HTMLHeadElement >; hr: React.DetailedHTMLProps< React.HTMLAttributes, HTMLHRElement >; html: React.DetailedHTMLProps< React.HtmlHTMLAttributes, HTMLHtmlElement >; i: React.DetailedHTMLProps, HTMLElement>; iframe: React.DetailedHTMLProps< React.IframeHTMLAttributes, HTMLIFrameElement >; img: React.DetailedHTMLProps< React.ImgHTMLAttributes, HTMLImageElement >; input: React.DetailedHTMLProps< React.InputHTMLAttributes, HTMLInputElement >; ins: React.DetailedHTMLProps< React.InsHTMLAttributes, HTMLModElement >; isindex: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; kbd: React.DetailedHTMLProps, HTMLElement>; label: React.DetailedHTMLProps< React.LabelHTMLAttributes, HTMLLabelElement >; legend: React.DetailedHTMLProps< React.HTMLAttributes, HTMLLegendElement >; li: React.DetailedHTMLProps< React.LiHTMLAttributes, HTMLLIElement >; link: React.DetailedHTMLProps< React.LinkHTMLAttributes, HTMLLinkElement >; map: React.DetailedHTMLProps< React.MapHTMLAttributes, HTMLMapElement >; menu: React.DetailedHTMLProps< React.MenuHTMLAttributes, HTMLElement >; meta: React.DetailedHTMLProps< React.MetaHTMLAttributes, HTMLMetaElement >; noframes: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; noscript: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; object: React.DetailedHTMLProps< React.ObjectHTMLAttributes, HTMLObjectElement >; ol: React.DetailedHTMLProps< React.OlHTMLAttributes, HTMLOListElement >; optgroup: React.DetailedHTMLProps< React.OptgroupHTMLAttributes, HTMLOptGroupElement >; option: React.DetailedHTMLProps< React.OptionHTMLAttributes, HTMLOptionElement >; p: React.DetailedHTMLProps< React.HTMLAttributes, HTMLParagraphElement >; param: React.DetailedHTMLProps< React.ParamHTMLAttributes, HTMLParamElement >; pre: React.DetailedHTMLProps< React.HTMLAttributes, HTMLPreElement >; q: React.DetailedHTMLProps< React.QuoteHTMLAttributes, HTMLQuoteElement >; s: React.DetailedHTMLProps, HTMLElement>; samp: React.DetailedHTMLProps, HTMLElement>; script: React.DetailedHTMLProps< React.ScriptHTMLAttributes, HTMLScriptElement >; select: React.DetailedHTMLProps< React.SelectHTMLAttributes, HTMLSelectElement >; small: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; span: React.DetailedHTMLProps< React.HTMLAttributes, HTMLSpanElement >; strike: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; strong: React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement >; style: React.DetailedHTMLProps< React.StyleHTMLAttributes, HTMLStyleElement >; sub: React.DetailedHTMLProps, HTMLElement>; sup: React.DetailedHTMLProps, HTMLElement>; table: React.DetailedHTMLProps< React.TableHTMLAttributes, HTMLTableElement >; tbody: React.DetailedHTMLProps< React.HTMLAttributes, HTMLTableSectionElement >; td: React.DetailedHTMLProps< React.TdHTMLAttributes, HTMLTableDataCellElement >; textarea: React.DetailedHTMLProps< React.TextareaHTMLAttributes, HTMLTextAreaElement >; tfoot: React.DetailedHTMLProps< React.HTMLAttributes, HTMLTableSectionElement >; th: React.DetailedHTMLProps< React.ThHTMLAttributes, HTMLTableHeaderCellElement >; thead: React.DetailedHTMLProps< React.HTMLAttributes, HTMLTableSectionElement >; title: React.DetailedHTMLProps< React.HTMLAttributes, HTMLTitleElement >; tr: React.DetailedHTMLProps< React.HTMLAttributes, HTMLTableRowElement >; tt: React.DetailedHTMLProps, HTMLElement>; u: React.DetailedHTMLProps, HTMLElement>; ul: React.DetailedHTMLProps< React.HTMLAttributes, HTMLUListElement >; var: React.DetailedHTMLProps, HTMLElement>; } // #endregion // #region vmlelements export type VMLAttrs = { [attr: string]: any }; export interface VMLElements { shape: React.DetailedHTMLProps; shapetype: React.DetailedHTMLProps; group: React.DetailedHTMLProps; background: React.DetailedHTMLProps; path: React.DetailedHTMLProps; formulas: React.DetailedHTMLProps; handles: React.DetailedHTMLProps; fill: React.DetailedHTMLProps; stroke: React.DetailedHTMLProps; shadow: React.DetailedHTMLProps; textbox: React.DetailedHTMLProps; textpath: React.DetailedHTMLProps; imagedata: React.DetailedHTMLProps; line: React.DetailedHTMLProps; polyline: React.DetailedHTMLProps; curve: React.DetailedHTMLProps; roundrect: React.DetailedHTMLProps; oval: React.DetailedHTMLProps; arc: React.DetailedHTMLProps; image: React.DetailedHTMLProps; } // #endregion // Helpers, copied from styled-components.d.ts type KeyofBase = keyof any; type Diff = ({ [P in T]: P } & { [P in U]: never })[T]; type Omit = Pick>; type WithOptionalTheme

= Omit & { theme?: T; }; // Re-define all types with XHTML elements type ThemedStyledComponentFactories = { [TTag in keyof XHTMLElements]: ThemedStyledFunction; }; export interface ThemedBaseStyledInterface extends ThemedStyledComponentFactories { (tag: TTag): ThemedStyledFunction< P, T, P & XHTMLElements[TTag] >; (component: StyledComponentClass): ThemedStyledFunction< P, T, O >;

( component: React.ComponentType

): ThemedStyledFunction>; } export type BaseStyledInterface = ThemedBaseStyledInterface; export type ThemedStyledInterface = ThemedBaseStyledInterface; // Define vml.*, wml.* and office.* proxy type AnyAttrs = { [attr: string]: T }; type NamespacedElementFactory = { [tag: string]: ThemedStyledFunction< React.DetailedHTMLProps< React.HTMLAttributes & AnyAttrs, HTMLElement >, T >; }; type VMLNamespaceFactory = { [TTag in keyof VMLElements]: ThemedStyledFunction; }; type WMLNamespaceFactory = { [tag: string]: ThemedStyledFunction; }; type OfficeNamespaceFactory = { [tag: string]: ThemedStyledFunction; }; interface NamespacedStyledInterface extends ThemedStyledInterface { vml: VMLNamespaceFactory; wml: WMLNamespaceFactory; office: OfficeNamespaceFactory; } // Re-export it export type StyledInterface = NamespacedStyledInterface; declare const styled: StyledInterface; export * from "styled-components"; export default styled; ================================================ FILE: test/namespace.spec.js ================================================ import React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import prettier from "prettier"; import styled from "../src"; const format = (html) => prettier.format(html, { parser: "html" }); describe("namespace", () => { it("supports vml, wml, and office namespaces", () => { const RoundRect = styled.vml.roundrect.attrs({ arcsize: "10%", strokecolor: "#1e3650", fill: "true", })` v-text-anchor: middle; height: 40px; width: 200px; `; const Lock = styled.wml.anchorlock``; const Fill = styled.vml.fill.attrs({ type: "tile", src: "https://i.imgur.com/0xPEf.gif", color: "#556270", })``; const Text = styled.center` font-family: sans-serif; font-weight: bold; font-size: 13px; color: #fff; `; const Button = ({ children, ...rest }) => { children; return ( {children} ); }; expect( format( renderToStaticMarkup( ) ) ).toMatchInlineSnapshot(` "

Click me
" `); }); }); ================================================ FILE: test/specific.spec.js ================================================ import React from "react"; import ReactTestRenderer from "react-test-renderer"; import styled from "../src"; const render = (element) => ReactTestRenderer.create(element).toJSON(); describe("specific", () => { it("supports mail-specific props", () => { const MsoComponent = styled.center` mso-hide: all; `; expect(render()).toMatchInlineSnapshot(`
`); }); it("supports vml specific elements", () => { const AnchorLock = styled("w:anchorlock")``; expect(render()).toMatchInlineSnapshot(` `); }); it("supports custom attributes", () => { const RoundRect = styled.vml.roundrect.attrs({ "xmlns:v": "urn:schemas-microsoft-com:vml", "xmlns:w": "urn:schemas-microsoft-com:office:word", })``; expect(render()).toMatchInlineSnapshot(` `); }); it("outputs correct units", () => { const Box = styled.center` padding: 10px; margin: 10in; left: -1em; right: 5pt; line-height: 1.2; `; expect(render()).toMatchInlineSnapshot(`
`); }); it("expands color to 6 digits", () => { const Color = styled.p` color: #000; border: 1px solid #333; `; expect(render()).toMatchInlineSnapshot(`

`); }); }); ================================================ FILE: test/styled.spec.js ================================================ import React from "react"; import ReactTestRenderer from "react-test-renderer"; import styled, { css } from "../src"; const render = (element) => ReactTestRenderer.create(element).toJSON(); describe("styled", () => { it("works with `styled.component`", () => { const Link = styled.a` font-weight: bold; color: blue; `; expect(render(Hey)).toMatchInlineSnapshot(` Hey `); }); it("works with `styled(Component)`", () => { const Link = styled((props) => )` font-weight: bold; color: green; `; expect(render(Hey)).toMatchInlineSnapshot(` Hey `); }); it("interpolates props-based values correctly", () => { const Text = styled.p` color: #333; font-size: ${(props) => (props.big ? 32 : 16)}px; `; expect(render(Hey)).toMatchInlineSnapshot(`

Hey

`); }); it("composes components", () => { const Link = styled.a` text-decoration: underline; font-size: 16px; color: blue; `; const Button = styled(Link)` text-decoration: none; font-weight: bold; padding: 5px 10px; background: blue; color: white; `; const BigButton = styled(Button)` padding: 10px 20px; font-size: 20px; `; expect(render(Click me)) .toMatchInlineSnapshot(` Click me `); }); it("works with `.attrs`", () => { const Action = styled.a.attrs({ href: "https://example.com", target: "blank", })` color: blue; `; expect(render(Click me)).toMatchInlineSnapshot(` Click me `); }); it("works with `.withComponent`", () => { const Text = styled.p` color: green; `; const Heading = Text.withComponent("h1"); expect(render(Hey)).toMatchInlineSnapshot(`

Hey

`); }); it("works with `css` helper", () => { const mixin = css` color: ${(props) => (props.primary ? "blue" : "#333")}; `; const Link = styled.a` font-size: 16px; ${mixin}; `; expect(render(Click)).toMatchInlineSnapshot(` Click `); }); it("warns about nested rules", () => { let lastConsoleWarn; const originalConsoleWarn = console.warn; console.warn = (...message) => (lastConsoleWarn = message.join(" ")); const Invalid = styled.div` color: #333; // This won't work .nested { color: blue; } `; expect(render()).toMatchInlineSnapshot(`
`); expect(lastConsoleWarn).toMatchInlineSnapshot( `"Node of type rule not supported as an inline style"` ); console.warn = originalConsoleWarn; }); it("expands style rules", () => { const Box = styled.p` border: 1px solid #333; background: #333; `; expect(render()).toMatchInlineSnapshot(`

`); }); it("overrides and merges styles by value from `style` prop", () => { const Link = styled.a` font-size: 16px; color: red; `; expect(render()).toMatchInlineSnapshot(` `); }); it("overrides and merges styles by value from `.attrs`", () => { const Link = styled.a.attrs({ style: { color: "red", }, })` font-size: 16px; color: blue; `; expect(render()).toMatchInlineSnapshot(` `); }); it("works with styles-returning function API", () => { const Link = styled.a(({ color }) => ({ color })); expect(render()).toMatchInlineSnapshot(` `); }); }); ================================================ FILE: test/theme.spec.js ================================================ import React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import styled, { withTheme, ThemeProvider } from "../src"; describe("theme", () => { it("works with `styled.element`", () => { const Link = styled.a` color: ${(props) => props.theme.color}; `; expect( renderToStaticMarkup( Link ) ).toMatchInlineSnapshot(`"Link"`); }); it("works with `styled(Component)`", () => { const Heading = (props) =>

; const StyledHeading = styled(Heading)` font-size: ${(props) => props.theme.size}; `; expect( renderToStaticMarkup( Hey ) ).toMatchInlineSnapshot(`"

Hey

"`); }); it("works with `withTheme`", () => { const Component = ({ theme, innerRef, ...rest }) =>
; const ThemedComponent = withTheme(Component); const WrapperComponent = (props) => ; const StyledComponent = styled(WrapperComponent)``; expect( renderToStaticMarkup( ) ).toMatchInlineSnapshot(`"
"`); }); }); ================================================ FILE: test/typings.spec.tsx ================================================ // @ts-check import styled from "../"; describe("typings", () => { it("does not report ts error", () => { const StyledCenter = styled.center``; const StyledFont = styled.font``; const StyledVmlRoundRect = styled.vml.roundrect``; const StyledAnchorLock = styled.wml.anchorlock``; const StyledVmlRoundRectAttrs = styled.vml.roundrect.attrs({ "xmlns:v": "urn:schemas-microsoft-com:vml", "xmlns:w": "urn:schemas-microsoft-com:office:word", })``; }); }); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "module": "commonjs", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true } }