├── .eslintignore ├── index.js ├── .flowconfig ├── .gitignore ├── documentation.yml ├── .travis.yml ├── src ├── index.js ├── theme.js ├── ifNotProp.js ├── index.js.flow ├── prop.js ├── withProp.js ├── switchProp.js ├── ifProp.js └── palette.js ├── tsconfig.json ├── .eslintrc ├── .editorconfig ├── babel.config.js ├── test ├── theme.test.js ├── prop.test.js ├── switchProp.test.js ├── withProp.test.js ├── palette.test.js ├── ifNotProp.test.js └── ifProp.test.js ├── LICENSE ├── benchmark └── index.js ├── package.json ├── types ├── index.d.ts └── tests.ts ├── CHANGELOG.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export * from "./src"; 2 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/dist 3 | .*/coverage 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | coverage 4 | dist 5 | *.log 6 | -------------------------------------------------------------------------------- /documentation.yml: -------------------------------------------------------------------------------- 1 | toc: 2 | - prop 3 | - theme 4 | - palette 5 | - ifProp 6 | - ifNotProp 7 | - withProp 8 | - switchProp 9 | - name: Types 10 | children: 11 | - Needle 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - v8 4 | script: 5 | - npm run lint 6 | - npm test 7 | - npm run type-check 8 | cache: 9 | - yarn 10 | after_success: 11 | - bash <(curl -s https://codecov.io/bash) 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export ifNotProp from "./ifNotProp"; 2 | export ifProp from "./ifProp"; 3 | export prop from "./prop"; 4 | export palette from "./palette"; 5 | export switchProp from "./switchProp"; 6 | export theme from "./theme"; 7 | export withProp from "./withProp"; 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "strict": true, 5 | "moduleResolution": "node", 6 | "forceConsistentCasingInFileNames": true, 7 | "noImplicitReturns": true, 8 | "noImplicitThis": true, 9 | "noImplicitAny": true, 10 | "strictNullChecks": true, 11 | "suppressImplicitAnyIndexErrors": true, 12 | "noUnusedLocals": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "airbnb-base", 5 | "plugin:flowtype/recommended", 6 | "plugin:prettier/recommended", 7 | "prettier/flowtype" 8 | ], 9 | "plugins": [ 10 | "flowtype", 11 | "flowtype-errors" 12 | ], 13 | "env": { 14 | "jest": true 15 | }, 16 | "rules": { 17 | "flowtype-errors/show-errors": "error", 18 | "import/no-cycle": "off", 19 | "no-nested-ternary": "off" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const { BABEL_ENV, NODE_ENV } = process.env; 2 | const modules = BABEL_ENV === "cjs" || NODE_ENV === "test" ? "commonjs" : false; 3 | 4 | module.exports = { 5 | presets: [ 6 | [ 7 | "@babel/preset-env", 8 | { 9 | modules, 10 | targets: { 11 | browsers: "defaults" 12 | } 13 | } 14 | ] 15 | ], 16 | plugins: [ 17 | "@babel/plugin-transform-flow-strip-types", 18 | "@babel/plugin-proposal-export-default-from" 19 | ] 20 | }; 21 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import prop from "./prop"; 3 | import type { PropsWithTheme } from "."; 4 | 5 | /** 6 | * Same as `prop`, except that it returns `props.theme[path]` instead of 7 | * `props[path]`. 8 | * @example 9 | * import styled from "styled-components"; 10 | * import { theme } from "styled-tools"; 11 | * 12 | * const Button = styled.button` 13 | * color: ${theme("button.color", "red")}; 14 | * `; 15 | */ 16 | const theme = (path: string, defaultValue?: any) => (props: PropsWithTheme) => 17 | prop(path, defaultValue)(props.theme); 18 | 19 | export default theme; 20 | -------------------------------------------------------------------------------- /src/ifNotProp.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import ifProp from "./ifProp"; 3 | import type { Needle, PropsFn } from "."; 4 | 5 | /** 6 | * Returns `pass` if prop is falsy. Otherwise returns `fail` 7 | * @example 8 | * import styled from "styled-components"; 9 | * import { ifNotProp } from "styled-tools"; 10 | * 11 | * const Button = styled.button` 12 | * font-size: ${ifNotProp("large", "20px", "30px")}; 13 | * `; 14 | */ 15 | const ifNotProp = ( 16 | test: Needle | Needle[] | Object, 17 | pass?: any, 18 | fail?: any 19 | ): PropsFn => ifProp(test, fail, pass); 20 | 21 | export default ifNotProp; 22 | -------------------------------------------------------------------------------- /src/index.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /** 4 | * A Needle is used to map the props to a value. This can either be done with 5 | * a path string `"theme.size.sm"` or with a function 6 | * `(props) => props.theme.size.sm` (these two examples are equivalent). 7 | * 8 | * All of styled-tools can be used as Needles making it possible to do 9 | * composition between functions. ie `ifProp(theme("dark"), "black", "white")` 10 | */ 11 | export type Needle = string | Function; 12 | 13 | export type PropsFn = (props?: Object) => any; 14 | 15 | export type PropsWithTheme = { theme: { [x: string]: any } }; 16 | -------------------------------------------------------------------------------- /test/theme.test.js: -------------------------------------------------------------------------------- 1 | import theme from "../src/theme"; 2 | 3 | test("string argument", () => { 4 | expect(theme("color")({ theme: {} })).toBeUndefined(); 5 | expect(theme("color")({ theme: { color: "red" } })).toBe("red"); 6 | }); 7 | 8 | test("deep string argument", () => { 9 | expect(theme("color.primary")({ theme: { color: {} } })).toBeUndefined(); 10 | expect(theme("color.primary")({ theme: { color: { primary: "red" } } })).toBe( 11 | "red" 12 | ); 13 | }); 14 | 15 | test("defaultValue", () => { 16 | expect(theme("color", "red")({ theme: { color: "blue" } })).toBe("blue"); 17 | expect(theme("color.primary", "red")({ theme: { color: {} } })).toBe("red"); 18 | }); 19 | -------------------------------------------------------------------------------- /test/prop.test.js: -------------------------------------------------------------------------------- 1 | import prop from "../src/prop"; 2 | 3 | test("string argument", () => { 4 | expect(prop("color")()).toBeUndefined(); 5 | expect(prop("color")({})).toBeUndefined(); 6 | expect(prop("color")({ color: "red" })).toBe("red"); 7 | }); 8 | 9 | test("deep string argument", () => { 10 | expect(prop("color.primary")()).toBeUndefined(); 11 | expect(prop("color.primary")({})).toBeUndefined(); 12 | expect(prop("color.primary")({ color: {} })).toBeUndefined(); 13 | expect(prop("color.primary")({ color: { primary: "red" } })).toBe("red"); 14 | }); 15 | 16 | test("defaultValue", () => { 17 | expect(prop("color", "red")()).toBe("red"); 18 | expect(prop("color", "red")({})).toBe("red"); 19 | expect(prop("color", "red")({ color: "blue" })).toBe("blue"); 20 | expect(prop("color.primary", "red")({})).toBe("red"); 21 | }); 22 | -------------------------------------------------------------------------------- /src/prop.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { PropsFn } from "."; 3 | 4 | /** 5 | * Returns the value of `props[path]` or `defaultValue` 6 | * @example 7 | * import styled from "styled-components"; 8 | * import { prop } from "styled-tools"; 9 | * 10 | * const Button = styled.button` 11 | * color: ${prop("color", "red")}; 12 | * `; 13 | */ 14 | const prop = (path: string, defaultValue?: any): PropsFn => (props = {}) => { 15 | if (typeof props[path] !== "undefined") { 16 | return props[path]; 17 | } 18 | 19 | if (path && path.indexOf(".") > 0) { 20 | const paths = path.split("."); 21 | const { length } = paths; 22 | let object = props[paths[0]]; 23 | let index = 1; 24 | 25 | while (object != null && index < length) { 26 | object = object[paths[index]]; 27 | index += 1; 28 | } 29 | 30 | if (typeof object !== "undefined") { 31 | return object; 32 | } 33 | } 34 | 35 | return defaultValue; 36 | }; 37 | 38 | export default prop; 39 | -------------------------------------------------------------------------------- /src/withProp.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import prop from "./prop"; 3 | import type { Needle, PropsFn } from "."; 4 | 5 | /** 6 | * Calls a function passing properties values as arguments. 7 | * @example 8 | * // example with polished 9 | * import styled from "styled-components"; 10 | * import { darken } from "polished"; 11 | * import { withProp, prop } from "styled-tools"; 12 | * 13 | * const Button = styled.button` 14 | * border-color: ${withProp(prop("theme.primaryColor", "blue"), darken(0.5))}; 15 | * font-size: ${withProp("theme.size", size => `${size + 1}px`)}; 16 | * background: ${withProp(["foo", "bar"], (foo, bar) => `${foo}${bar}`)}; 17 | * `; 18 | */ 19 | const withProp = (needle: Needle | Needle[], fn: Function): PropsFn => ( 20 | props = {} 21 | ) => { 22 | if (Array.isArray(needle)) { 23 | const needles = needle.map(arg => withProp(arg, x => x)(props)); 24 | return fn(...needles); 25 | } 26 | if (typeof needle === "function") { 27 | return fn(needle(props)); 28 | } 29 | return fn(prop(needle)(props)); 30 | }; 31 | 32 | export default withProp; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Diego Haz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/switchProp.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import prop from "./prop"; 3 | import type { Needle, PropsFn } from "."; 4 | 5 | /** 6 | * Switches on a given prop. Returns the value or function for a given prop value. Third parameter is default value. 7 | * @example 8 | * import styled, { css } from "styled-components"; 9 | * import { switchProp, prop } from "styled-tools"; 10 | * 11 | * const Button = styled.button` 12 | * font-size: ${switchProp(prop("size", "medium"), { 13 | * small: prop("theme.sizes.sm", "12px"), 14 | * medium: prop("theme.sizes.md", "16px"), 15 | * large: prop("theme.sizes.lg", "20px") 16 | * }, prop("theme.sizes.md", "16px"))}; 17 | * ${switchProp("theme.kind", { 18 | * light: css` 19 | * color: LightBlue; 20 | * `, 21 | * dark: css` 22 | * color: DarkBlue; 23 | * ` 24 | * }, css`color: black;`)} 25 | * `; 26 | * 27 | *