├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── LICENSE.md
├── README.md
├── cssapi-logo.png
├── docs
└── images
│ ├── rhythm-example.png
│ ├── rhythm.png
│ └── workflow.png
├── examples
└── without-theme
│ ├── .babelrc
│ ├── index.html
│ ├── package.json
│ ├── src
│ └── js
│ │ ├── components
│ │ └── App.js
│ │ ├── index.js
│ │ └── styles
│ │ ├── api.js
│ │ └── global.js
│ ├── webpack.dev.config.js
│ └── yarn.lock
├── jest.config.js
├── jsconfig.json
├── package.json
├── src
├── __tests__
│ ├── api
│ │ ├── batching.js
│ │ ├── configuration.js
│ │ ├── declarations.js
│ │ ├── expanders
│ │ │ ├── axisExpander.js
│ │ │ ├── directionsExpander.js
│ │ │ └── minMaxExpander.js
│ │ ├── extend.js
│ │ ├── helpers.js
│ │ ├── mq.js
│ │ └── scope.js
│ ├── breakpoints
│ │ ├── breakpointProvider.js
│ │ └── resolveBreakpoints.js
│ ├── renderers
│ │ └── renderDirectionProps.js
│ ├── testHelpers
│ │ ├── fixtures
│ │ │ ├── breakpoints.js
│ │ │ └── generic.js
│ │ └── matchers
│ │ │ └── customMatchers.js
│ ├── themeHelpers
│ │ ├── api.js
│ │ ├── mixin.js
│ │ └── mq.js
│ ├── transformers
│ │ ├── percentageStringToRatioTransformer.js
│ │ └── ratioToPercentageStringTransformer.js
│ └── utils
│ │ ├── insertSubIntoProp.js
│ │ ├── isValidModifiedMq.js
│ │ ├── parseBreakpoint.js
│ │ ├── replaceTokens.js
│ │ └── transformAllPartsWith.js
├── breakpoints
│ ├── breakpointProvider.js
│ ├── descriptors
│ │ ├── createAtQueryDecriptor.js
│ │ ├── createGtQueryDescriptor.js
│ │ ├── createLtQueryDescriptor.js
│ │ ├── createRangedQueryDescriptor.js
│ │ ├── createSingleQueryDescr.js
│ │ └── createUnrangedQueryDescrptor.js
│ └── resolveBreakpoints.js
├── build
│ ├── createApi.js
│ ├── declarations
│ │ ├── createDeclarationProcessor.js
│ │ ├── createDeclarationProcessors.js
│ │ ├── processDeclarations.js
│ │ └── renderers
│ │ │ ├── renderBaseline.js
│ │ │ ├── renderBatch.js
│ │ │ ├── renderDeclarations.js
│ │ │ ├── renderDirectionProps.js
│ │ │ ├── renderDualFromOneProps.js
│ │ │ ├── renderDualProps.js
│ │ │ ├── renderQuery.js
│ │ │ ├── renderQueryHeader.js
│ │ │ └── renderSingleDeclaration.js
│ ├── ensureBreakpointProvider.js
│ ├── expansion
│ │ ├── expandData.js
│ │ ├── expandProperties.js
│ │ ├── propertyExpanderMap.js
│ │ └── propertyExpanders
│ │ │ ├── axesExpander.js
│ │ │ ├── cornerExpander.js
│ │ │ ├── directionExpander.js
│ │ │ ├── directionsExpander.js
│ │ │ └── minMaxExpander.js
│ └── mergeWithDefaultConfig.js
├── config
│ └── defaultConfig.js
├── const
│ ├── breakpointMapping.js
│ ├── breakpoints.js
│ ├── config.js
│ ├── errors.js
│ ├── expanders.js
│ ├── queryDescriptor.js
│ ├── rangeItem.js
│ ├── regexp.js
│ ├── scope.js
│ ├── templates.js
│ └── units.js
├── cssapi.js
├── errors.js
├── index.js
├── objects
│ ├── breakpointDescriptor.js
│ ├── breakpointMapping.js
│ ├── config.js
│ ├── queryDescriptor.js
│ ├── rangeItem.js
│ └── scope.js
├── resolvers
│ ├── resolveKeyToObjectValue.js
│ ├── resolveKeyToValue.js
│ ├── resolveKeysToObjectValues.js
│ └── resolveKeysToValues.js
├── themeHelpers
│ ├── api.js
│ ├── getApiFromProps.js
│ ├── mixin.js
│ └── mq.js
├── transformers
│ ├── calcTransformer.js
│ ├── composite
│ │ ├── allPartsTransformer.js
│ │ ├── baselineTransformer.js
│ │ ├── partPositionTransformer.js
│ │ └── transformPartsWith.js
│ ├── dataLookupTransformer.js
│ ├── factory
│ │ └── dataLookupTransformers.js
│ ├── gradientTransformer.js
│ ├── lengthToEmsTransformer.js
│ ├── lengthToRemsTransformer.js
│ ├── lengthTransformers.js
│ ├── percentageStringToRatioTransformer.js
│ ├── ratioToPercentageStringTransformer.js
│ ├── rhythmUnitsToEmsTransformer.js
│ ├── rhythmUnitsToLengthTransformer.js
│ ├── rhythmUnitsToRemsTransformer.js
│ ├── rootPxToEmTransformer.js
│ ├── transformTransformer.js
│ ├── transformer.js
│ ├── unitlessNumberToEmsTransformer.js
│ ├── unitlessNumberToLengthTransformer.js
│ ├── unitlessNumberToPxTransformer.js
│ └── unitlessNumberToRemsTransformer.js
└── utils
│ ├── baseline.js
│ ├── breakpointMap.js
│ ├── breakpoints.js
│ ├── converters.js
│ ├── data.js
│ ├── declarations.js
│ ├── expanders.js
│ ├── formatting.js
│ ├── functions.js
│ ├── list.js
│ ├── logic.js
│ ├── numbers.js
│ ├── objects.js
│ ├── parse.js
│ ├── predicate.js
│ ├── queryDescriptor.js
│ ├── range.js
│ ├── scope.js
│ ├── templates.js
│ └── transformers.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-es2015-destructuring",
4 | "transform-object-rest-spread",
5 | "transform-es2017-object-entries"
6 | ],
7 | "env": {
8 | "test": {
9 | "presets": [["env"]]
10 | },
11 | "development": {
12 | "presets": [["env"]]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | dist
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint-config-airbnb-base',
4 | 'plugin:react/recommended',
5 | 'prettier',
6 | ],
7 | plugins: ['prettier'],
8 | env: {
9 | browser: true,
10 | jest: true,
11 | },
12 | parserOptions: {
13 | sourceType: 'module',
14 | ecmaFeatures: {
15 | jsx: true,
16 | },
17 | },
18 | globals: {
19 | l: true,
20 | },
21 | rules: {
22 | 'func-names': ['error', 'never'],
23 | 'no-param-reassign': 'off',
24 | 'import/no-extraneous-dependencies': 'off',
25 | 'no-confusing-arrow': 'off',
26 | quotes: [
27 | 'error',
28 | 'backtick',
29 | { avoidEscape: true, allowTemplateLiterals: true },
30 | ],
31 | 'jsx-quotes': ['error', 'prefer-double'],
32 | 'comma-dangle': ['error', 'always-multiline'],
33 | 'valid-jsdoc': ['error'],
34 | 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'],
35 | 'import/extensions': ['off', 'never'],
36 | 'no-unused-expressions': ['error', { allowTaggedTemplates: true }],
37 | },
38 | }
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn-error.log
3 | coverage
4 | out
5 | dist
6 | lib
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | unsafe-perm = true
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /Users/pedr/Library/Application\ Support/Code/User/snippets/javascript.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true,
6 | "trailingComma": "es5",
7 | "overrides": [
8 | {
9 | "files": ".prettierrc",
10 | "options": { "parser": "json" }
11 | },
12 | {
13 | "files": ".babelrc",
14 | "options": { "parser": "json" }
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | dist: trusty
3 | node_js:
4 | - 6
5 | - 7
6 | - 8
7 | notifications:
8 | email:
9 | on_failure: change
10 | script:
11 | - node --version
12 | - yarn --version
13 | - yarn run lint
14 | - yarn run test:noWatch
15 | - codecov
16 | - yarn run build
17 | cache:
18 | yarn: true
19 | bundler: true
20 | directories:
21 | - node_modules
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Pedr Browne
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/cssapi-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Undistraction/cssapi/8597637b134e3f9937ca35c724d7bc3f0e423e9c/cssapi-logo.png
--------------------------------------------------------------------------------
/docs/images/rhythm-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Undistraction/cssapi/8597637b134e3f9937ca35c724d7bc3f0e423e9c/docs/images/rhythm-example.png
--------------------------------------------------------------------------------
/docs/images/rhythm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Undistraction/cssapi/8597637b134e3f9937ca35c724d7bc3f0e423e9c/docs/images/rhythm.png
--------------------------------------------------------------------------------
/docs/images/workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Undistraction/cssapi/8597637b134e3f9937ca35c724d7bc3f0e423e9c/docs/images/workflow.png
--------------------------------------------------------------------------------
/examples/without-theme/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["transform-es2015-destructuring", "transform-object-rest-spread"],
3 | "presets": ["env", "react"]
4 | }
5 |
--------------------------------------------------------------------------------
/examples/without-theme/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= htmlWebpackPlugin.options.title %>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/without-theme/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cssapi-example",
3 | "version": "0.0.0",
4 | "main": "lib/index.js",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/Undistraction/webpack-template.git"
8 | },
9 | "files": [
10 | "dist",
11 | "src",
12 | "lib"
13 | ],
14 | "keywords": [],
15 | "author": "Pedr Browne",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/Undistraction/webpack-template/issues"
19 | },
20 | "homepage": "https://github.com/Undistraction/webpack-template",
21 | "scripts": {
22 | "start": "webpack-dev-server --config ./webpack.dev.config.js"
23 | },
24 | "devDependencies": {
25 | "babel-cli": "^6.26.0",
26 | "babel-core": "6.26.0",
27 | "babel-eslint": "^8.2.2",
28 | "babel-loader": "^7.1.4",
29 | "babel-plugin-external-helpers": "^6.22.0",
30 | "babel-plugin-transform-class-properties": "^6.24.1",
31 | "babel-plugin-transform-es2015-destructuring": "^6.23.0",
32 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
33 | "babel-preset-env": "^1.6.1",
34 | "babel-preset-react": "^6.24.1",
35 | "codecov": "^3.0.0",
36 | "css-loader": "^0.28.10",
37 | "cssbeautify": "^0.3.1",
38 | "eslint": "^4.11.0",
39 | "eslint-config-airbnb": "^16.1.0",
40 | "eslint-config-airbnb-base": "^12.1.0",
41 | "eslint-config-prettier": "^2.7.0",
42 | "eslint-plugin-class-property": "^1.1.0",
43 | "eslint-plugin-import": "^2.8.0",
44 | "eslint-plugin-jsx-a11y": "^6.0.2",
45 | "eslint-plugin-prettier": "^2.3.1",
46 | "eslint-plugin-react": "^7.7.0",
47 | "extract-text-webpack-plugin": "^4.0.0-beta.0",
48 | "html-webpack-plugin": "^3.0.6",
49 | "husky": "^0.15.0-rc.8",
50 | "prettier": "^1.8.2",
51 | "react-hot-loader": "^4.0.0",
52 | "style-loader": "^0.20.3",
53 | "stylelint": "^9.1.2",
54 | "stylelint-config-prettier": "^3.0.4",
55 | "stylelint-config-standard": "^18.2.0",
56 | "webpack": "^4.1.1",
57 | "webpack-cli": "^2.0.12",
58 | "webpack-dev-server": "^3.1.1"
59 | },
60 | "dependencies": {
61 | "cssapi-baseline": "0.4.0",
62 | "cssapi-fonts": "0.3.1",
63 | "cssapi-mq": "2.1.0",
64 | "cssapi-scale": "0.0.3",
65 | "normalize.css": "^8.0.0",
66 | "react-dom": "^16.2.0",
67 | "styled-components": "^3.2.2",
68 | "styled-normalize": "^4.0.0"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/examples/without-theme/src/js/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import api from '../styles/api'
5 |
6 | const AppWrapper = styled.div`
7 | display: flex;
8 | flex-direction: column;
9 | height: 100%;
10 | ${api({
11 | minWidth: 300,
12 | maxWidth: [`auto`, 1000, `initial`],
13 | padding: [0, `4ru`, 0],
14 | margin: [0, `0 auto`],
15 | })};
16 | `
17 |
18 | const AppHeader = styled.header`
19 | ${api({
20 | backgroundImage: `headerFooter`,
21 | borderRadius: [0, `1ru`, 0],
22 | })};
23 | `
24 |
25 | const AppFooter = styled.footer`
26 | text-align: center;
27 | ${api({
28 | padding: [`1ru`, `4ru`],
29 | backgroundImage: `headerFooter`,
30 | borderRadius: [0, `1ru`, 0],
31 | flexBasis: [`2ru`, `4ru`],
32 | })};
33 | `
34 |
35 | const AppTitle = styled.h1`
36 | ${api({
37 | fontFamily: `title`,
38 | fontSize: [`large`, `large`],
39 | padding: [[`1ru`, `2ru`], [`2ru`, `4ru`]],
40 | })};
41 | `
42 |
43 | const AppBody = styled.div`
44 | ${api({
45 | padding: [[`1ru`, `2ru`], [`2ru`, `4ru`]],
46 | display: `flex`,
47 | flexDirection: [`column`, `row`],
48 | flexGrow: 2,
49 | maxWidth: {
50 | large: 1200,
51 | },
52 | margin: {
53 | large: `0 auto`,
54 | },
55 | })};
56 | `
57 |
58 | const Primary = styled.main`
59 | ${api({
60 | paddingRight: [0, `3ru`],
61 | })};
62 | `
63 |
64 | const Secondary = styled.aside`
65 | ${api({
66 | flexBasis: [`auto`, `auto`, 200, 300],
67 | flexShrink: [`auto`, `auto`, 0],
68 | padding: `1ru`,
69 | backgroundImage: [`transparent`, `headerFooter`],
70 | })};
71 | `
72 |
73 | const PrimaryTitle = styled.h2`
74 | font-style: italic;
75 | ${api({
76 | fontFamily: `title`,
77 | fontSize: [`large`, `large`],
78 | })};
79 | `
80 |
81 | const App = () => (
82 |
83 |
84 | Example: Without Theme
85 |
86 |
87 |
88 | PrimaryTitle
89 |
90 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus
91 | quis erat malesuada, lobortis leo eu, faucibus ante. Sed tempor
92 | tincidunt lectus, ac scelerisque justo tincidunt lobortis. Phasellus
93 | metus elit, porta vel sodales eu, euismod a libero. Duis sagittis,
94 | massa et dignissim eleifend, elit lorem luctus ligula, ut mattis ex
95 | dui non lacus. Nulla facilisi. Etiam et eros consequat, malesuada
96 | mauris at, mollis purus. Donec venenatis, enim vitae commodo
97 | consequat, arcu erat mollis leo, eget dapibus ligula lacus ut augue.
98 |
99 |
100 | Morbi in fringilla ligula, eget congue diam. Ut gravida quam sit amet
101 | diam malesuada malesuada. Donec nibh odio, convallis at velit eu,
102 | lobortis rutrum massa. Fusce cursus, turpis nec mattis lacinia, nibh
103 | ligula mollis turpis, a aliquam mauris tellus eget arcu. Mauris ut
104 | quam ac neque luctus eleifend quis eu arcu. Nunc hendrerit eget leo ut
105 | faucibus. Aenean et quam vel arcu vulputate rutrum at ac turpis.
106 | Interdum et malesuada fames ac ante ipsum primis in faucibus.
107 |
108 |
109 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus
110 | quis erat malesuada, lobortis leo eu, faucibus ante. Sed tempor
111 | tincidunt lectus, ac scelerisque justo tincidunt lobortis. Phasellus
112 | metus elit, porta vel sodales eu, euismod a libero. Duis sagittis,
113 | massa et dignissim eleifend, elit lorem luctus ligula, ut mattis ex
114 | dui non lacus. Nulla facilisi. Etiam et eros consequat, malesuada
115 | mauris at, mollis purus. Donec venenatis, enim vitae commodo
116 | consequat, arcu erat mollis leo, eget dapibus ligula lacus ut augue.
117 |
118 |
119 | Morbi in fringilla ligula, eget congue diam. Ut gravida quam sit amet
120 | diam malesuada malesuada. Donec nibh odio, convallis at velit eu,
121 | lobortis rutrum massa. Fusce cursus, turpis nec mattis lacinia, nibh
122 | ligula mollis turpis, a aliquam mauris tellus eget arcu. Mauris ut
123 | quam ac neque luctus eleifend quis eu arcu. Nunc hendrerit eget leo ut
124 | faucibus. Aenean et quam vel arcu vulputate rutrum at ac turpis.
125 | Interdum et malesuada fames ac ante ipsum primis in faucibus.
126 |
127 |
128 |
129 |
130 | Alpha
131 | Bravo
132 | Charlie
133 | Delta
134 | Echo
135 | Foxtrot
136 | Gamma
137 | Hotel
138 |
139 |
140 |
141 | CSSAPI
142 |
143 | )
144 |
145 | export default App
146 |
--------------------------------------------------------------------------------
/examples/without-theme/src/js/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './components/App'
4 | import global from './styles/global'
5 |
6 | const MOUNT_NODE = document.getElementById(`root`)
7 |
8 | ReactDOM.render( , MOUNT_NODE)
9 |
--------------------------------------------------------------------------------
/examples/without-theme/src/js/styles/api.js:
--------------------------------------------------------------------------------
1 | import configureCSSAPI from '../../../../../src/index'
2 |
3 | const config = {
4 | breakpoints: [[`medium`, 500], [`large`, 1100], [`xLarge`, 1300]],
5 | data: {
6 | color: {
7 | lightGrey: `#DDD`,
8 | black: `#0D0D0D`,
9 | midGrey: `#777`,
10 | background: `#{lightGrey}`,
11 | text: `#{black}`,
12 | headerFooter: `linear-gradient(45deg, #{midGrey} 0%, #{lightGrey} 100%)`,
13 | },
14 | scale: {
15 | small: 10,
16 | medium: 14,
17 | large: 24,
18 | },
19 | font: {
20 | default: `Montserrat, Helvetica, Sans-Serif`,
21 | title: `Raleway, Helvetica, Sans-Serif`,
22 | },
23 | rhythm: 15,
24 | scopes: [
25 | {
26 | resolve: [`medium`, `large`, `xLarge`],
27 | data: {
28 | rhythm: 20,
29 | scale: {
30 | small: 12,
31 | medium: 16,
32 | large: 32,
33 | },
34 | },
35 | },
36 | ],
37 | },
38 | }
39 |
40 | const cssAPI = configureCSSAPI(config)
41 |
42 | export default cssAPI
43 |
--------------------------------------------------------------------------------
/examples/without-theme/src/js/styles/global.js:
--------------------------------------------------------------------------------
1 | import { injectGlobal } from 'styled-components'
2 | import styledNormalize from 'styled-normalize'
3 | import api from './api'
4 |
5 | injectGlobal`
6 | ${styledNormalize}
7 |
8 | html {
9 | box-sizing: border-box;
10 | margin: 0;
11 | padding: 0;
12 | }
13 |
14 | *, *:before, *:after {
15 | box-sizing: inherit;
16 | }
17 |
18 | body {
19 | margin: 0;
20 | padding: 0;
21 | font-family: Helvetica, Arial, Sans-Serif;
22 | ${api({
23 | backgroundColor: [`background`],
24 | fontFamily: `default`,
25 | lineHeight: [`2ru`, `2ru`],
26 | fontSize: [`medium`, `medium`],
27 | })}
28 | }
29 |
30 | h1,
31 | h2,
32 | h3 {
33 | font-weight: normal;
34 | margin: 0;
35 | padding: 0;
36 | }
37 |
38 | p {
39 | ${api({
40 | margin: `2ru 0`,
41 | })}
42 | }
43 |
44 | ul,
45 | ol {
46 | padding: 0,
47 | margin: 0;
48 | }
49 | html,
50 | body,
51 | #root {
52 | min-height: 100%;
53 | }
54 | `
55 |
--------------------------------------------------------------------------------
/examples/without-theme/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require(`webpack`);
2 | const HtmlWebpackPlugin = require(`html-webpack-plugin`);
3 |
4 | module.exports = {
5 | mode: `development`,
6 | entry: [`react-hot-loader/patch`, `./src/js/index.js`],
7 | module: {
8 | rules: [
9 | {
10 | test: /\.(js|jsx)$/,
11 | exclude: /node_modules/,
12 | use: [`babel-loader`],
13 | },
14 | {
15 | test: /\.css$/,
16 | use: [{ loader: `style-loader` }, { loader: `css-loader` }],
17 | },
18 | ],
19 | },
20 | resolve: {
21 | extensions: [`*`, `.js`, `.jsx`],
22 | },
23 | output: {
24 | path: `${__dirname}/dist`,
25 | publicPath: `/`,
26 | filename: `bundle.js`,
27 | },
28 | plugins: [
29 | new webpack.HotModuleReplacementPlugin(), // Adds HMR
30 | new webpack.NamedModulesPlugin(), // Display module path in HMR updates
31 | new HtmlWebpackPlugin({
32 | title: `CSSAPI: WithoutTheme`,
33 | template: `index.html`,
34 | }), // HTML template generation
35 | ],
36 | devServer: {
37 | contentBase: `./dev`, // Serve from dir
38 | hot: true, // Enable HMR
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bail: true,
3 | verbose: true,
4 | collectCoverage: true,
5 | collectCoverageFrom: [`src/**/*.js`],
6 | coveragePathIgnorePatterns: [`src/index.js`],
7 | coverageReporters: [`html`, `lcov`],
8 | setupTestFrameworkScriptFile: `/src/__tests__/testHelpers/matchers/customMatchers.js`,
9 | setupFiles: [],
10 | modulePathIgnorePatterns: [`testHelpers/`],
11 | }
12 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6"
4 | },
5 | "include": ["src/**/*"],
6 | "resolve": {
7 | "modulesDirectories": ["src"]
8 | },
9 | "exclude": ["node_modules/**/*", "dist/**/*", "coverage/**/*", "lib/**/*"]
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cssapi",
3 | "version": "0.9.7",
4 | "main": "lib/index.js",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/Undistraction/cssapi.git"
8 | },
9 | "files": [
10 | "dist",
11 | "src",
12 | "lib"
13 | ],
14 | "keywords": [
15 | "styled-components",
16 | "css",
17 | "css-in-js"
18 | ],
19 | "author": "Pedr Browne",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/Undistraction/cssapi/issues"
23 | },
24 | "homepage": "https://github.com/Undistraction/cssapi",
25 | "scripts": {
26 | "build": "npm run build:lib",
27 | "prebuild:lib": "rimraf lib/*",
28 | "prebuild": "npm run lint",
29 | "build:lib": "babel --out-dir lib --ignore \"__tests__\" src",
30 | "test": "jest --watch",
31 | "test:noWatch": "jest",
32 | "prepublishOnly": "npm run build",
33 | "publish:patch": "npm version patch && sudo npm publish",
34 | "publish:minor": "npm version minor && sudo npm publish",
35 | "publish:major": "npm version major && sudo npm publish",
36 | "lint": "eslint src",
37 | "audit:packages": "yarn outdated || true"
38 | },
39 | "devDependencies": {
40 | "babel-cli": "^6.26.0",
41 | "babel-core": "6.26.0",
42 | "babel-plugin-external-helpers": "^6.22.0",
43 | "babel-plugin-transform-es2015-destructuring": "^6.23.0",
44 | "babel-plugin-transform-es2017-object-entries": "^0.0.4",
45 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
46 | "babel-preset-env": "^1.6.1",
47 | "babel-preset-react": "^6.24.1",
48 | "codecov": "^3.0.0",
49 | "cssbeautify": "^0.3.1",
50 | "eslint": "^4.11.0",
51 | "eslint-config-airbnb": "^16.1.0",
52 | "eslint-config-airbnb-base": "^12.1.0",
53 | "eslint-config-prettier": "^2.7.0",
54 | "eslint-plugin-import": "^2.8.0",
55 | "eslint-plugin-jsx-a11y": "^6.0.2",
56 | "eslint-plugin-prettier": "^2.3.1",
57 | "eslint-plugin-react": "^7.5.1",
58 | "jasmine-expect": "^3.8.3",
59 | "jasmine-multiline-matchers": "^0.2.2",
60 | "jest": "^22.4.3",
61 | "prettier": "^1.8.2",
62 | "rollup": "^0.51.3",
63 | "rollup-plugin-babel": "^3.0.2",
64 | "rollup-plugin-commonjs": "^8.2.6",
65 | "rollup-plugin-node-resolve": "^3.0.0",
66 | "sinon": "^4.4.9"
67 | },
68 | "dependencies": {
69 | "dasherize": "^2.0.0",
70 | "ramda": "^0.25.0",
71 | "ramda-adjunct": "^2.9.0",
72 | "ramda-log": "^0.0.3"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/__tests__/api/batching.js:
--------------------------------------------------------------------------------
1 | import cssapi from '../../index'
2 | import { scope } from '../../utils/scope'
3 | import {
4 | breakpoint1,
5 | breakpoint2,
6 | breakpoint3,
7 | key1,
8 | } from '../testHelpers/fixtures/generic'
9 |
10 | describe(`api()`, () => {
11 | const breakpointMap = [
12 | [breakpoint1, `25em`],
13 | [breakpoint2, `50em`],
14 | [breakpoint3, `75em`],
15 | ]
16 |
17 | const cssApi = cssapi({
18 | breakpoints: breakpointMap,
19 | data: {
20 | color: {
21 | red: `#FA0000`,
22 | green: `#00FA00`,
23 | blue: `#0000FA`,
24 | },
25 | rhythm: 20,
26 | scopes: [
27 | {
28 | resolve: [breakpoint1, breakpoint2],
29 | data: {
30 | rhythm: 24,
31 | },
32 | },
33 | {
34 | resolve: [breakpoint3],
35 | data: {
36 | rhythm: 28,
37 | },
38 | },
39 | ],
40 | },
41 | })
42 |
43 | it(`throws if prop name is not valid`, () => {
44 | expect(() => cssApi({ [key1]: `` })).toThrow(
45 | `[cssapi] api() API doesn't support a property named 'key1'`
46 | )
47 | })
48 |
49 | it(`batches mulitple api functions into media queries`, () => {
50 | const result = cssApi({
51 | padding: `1rem`,
52 | margin: [`1ru`, `2ru`, `3ru`],
53 | borderWidth: [scope`1ru`],
54 | color: [`c:red`, `c:green`, `c:blue`],
55 | })
56 |
57 | const expected = `
58 | padding: 1rem;
59 |
60 | @media (max-width: 24.99em) {
61 | margin: 1.25rem;
62 | border-width: 1.25rem;
63 | color: #FA0000;
64 | }
65 |
66 | @media (min-width: 25em) and (max-width: 49.99em) {
67 | margin: 3rem;
68 | color: #00FA00;
69 | }
70 |
71 | @media (min-width: 50em) {
72 | margin: 4.5rem;
73 | color: #0000FA;
74 | }
75 |
76 | @media (min-width: 25em) and (max-width: 74.99em) {
77 | border-width: 1.5rem;
78 | }
79 |
80 | @media (min-width: 75em) {
81 | border-width: 1.75rem;
82 | }`
83 |
84 | expect(result).toEqualMultiline(expected)
85 | })
86 | })
87 |
--------------------------------------------------------------------------------
/src/__tests__/api/configuration.js:
--------------------------------------------------------------------------------
1 | import cssapi from '../../index'
2 | import {
3 | breakpoint1,
4 | breakpoint2,
5 | breakpoint3,
6 | } from '../testHelpers/fixtures/generic'
7 |
8 | describe(`configuration`, () => {
9 | it(`supports length values for breakpoint map`, () => {
10 | const breakpoints = [
11 | [breakpoint1, `25em`],
12 | [breakpoint2, `50em`],
13 | [breakpoint3, `75em`],
14 | ]
15 |
16 | expect(() => cssapi({ breakpoints })).not.toThrow()
17 | })
18 |
19 | it(`supports unitless values for breakpoint map`, () => {
20 | const breakpoints = [
21 | [breakpoint1, 400],
22 | [breakpoint2, 800],
23 | [breakpoint3, 1200],
24 | ]
25 |
26 | expect(() => cssapi({ breakpoints })).not.toThrow()
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/src/__tests__/api/expanders/axisExpander.js:
--------------------------------------------------------------------------------
1 | import directionExpander from '../../../build/expansion/propertyExpanders/directionExpander'
2 | import { key1, value1, value2 } from '../../testHelpers/fixtures/generic'
3 |
4 | describe(`directionExpander`, () => {
5 | it(`expands one property to five`, () => {
6 | const transformers = [value1, value2]
7 | const mainWrapper = () => {}
8 | const renderer = () => {}
9 |
10 | const style = {
11 | transformers,
12 | renderer,
13 | }
14 |
15 | const expected = {
16 | [key1]: {
17 | transformers: mainWrapper(transformers),
18 | renderer,
19 | },
20 | [`${key1}Top`]: { transformers, renderer },
21 | [`${key1}Right`]: { transformers, renderer },
22 | [`${key1}Bottom`]: { transformers, renderer },
23 | [`${key1}Left`]: { transformers, renderer },
24 | }
25 | const result = directionExpander({ mainWrapper })(key1, style)
26 |
27 | expect(result).toEqual(expected)
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/src/__tests__/api/expanders/directionsExpander.js:
--------------------------------------------------------------------------------
1 | import directionsExpander from '../../../build/expansion/propertyExpanders/directionsExpander'
2 | import { key1, value1, value2 } from '../../testHelpers/fixtures/generic'
3 |
4 | describe(`directionsExpander`, () => {
5 | it(`expands to directions`, () => {
6 | const transformers = [value1, value2]
7 | const mainWrapper = () => {}
8 | const renderer = () => {}
9 |
10 | const style = {
11 | transformers,
12 | renderer,
13 | }
14 |
15 | const expected = {
16 | [`top`]: { transformers, renderer },
17 | [`right`]: { transformers, renderer },
18 | [`bottom`]: { transformers, renderer },
19 | [`left`]: { transformers, renderer },
20 | }
21 | const result = directionsExpander({ mainWrapper })(key1, style)
22 |
23 | expect(result).toEqual(expected)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/src/__tests__/api/expanders/minMaxExpander.js:
--------------------------------------------------------------------------------
1 | import minMaxExpander from '../../../build/expansion/propertyExpanders/minMaxExpander'
2 | import { key1, value1, value2 } from '../../testHelpers/fixtures/generic'
3 |
4 | describe(`minMaxExpander`, () => {
5 | it(`expands one property to three`, () => {
6 | const transformers = () => [value1, value2]
7 | const renderer = () => {}
8 |
9 | const style = {
10 | transformers,
11 | renderer,
12 | }
13 |
14 | const expected = {
15 | [key1]: {
16 | transformers,
17 | renderer,
18 | },
19 | [`minKey1`]: { transformers, renderer },
20 | [`maxKey1`]: { transformers, renderer },
21 | }
22 | const result = minMaxExpander()(key1, style)
23 |
24 | expect(result).toEqual(expected)
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/src/__tests__/api/extend.js:
--------------------------------------------------------------------------------
1 | import cssapi from '../../index'
2 | import {
3 | breakpoint1,
4 | breakpoint2,
5 | breakpoint3,
6 | } from '../testHelpers/fixtures/generic'
7 |
8 | describe(`extend()`, () => {
9 | it(`creates a new api based on old`, () => {
10 | const firstBreakpointMap = [
11 | [breakpoint1, `25em`],
12 | [breakpoint2, `50em`],
13 | [breakpoint3, `75em`],
14 | ]
15 |
16 | const extendedBreakpointMap = [
17 | [breakpoint1, `12.5em`],
18 | [breakpoint2, `25em`],
19 | [breakpoint3, `37.5em`],
20 | ]
21 |
22 | const cssApi = cssapi({
23 | breakpoints: firstBreakpointMap,
24 | })
25 |
26 | const extendedCssApi = cssApi.extend({
27 | breakpoints: extendedBreakpointMap,
28 | data: {
29 | baseFontSize: 10,
30 | },
31 | })
32 |
33 | expect(
34 | cssApi.mq(breakpoint1)({
35 | margin: `1ru`,
36 | padding: `2ru`,
37 | })
38 | ).toEqualMultiline(`
39 | @media (min-width: 25em) {
40 | margin: 1.25rem;
41 | padding: 2.5rem;
42 | }
43 | `)
44 |
45 | expect(
46 | extendedCssApi.mq(breakpoint1)({
47 | margin: `1ru`,
48 | padding: `2ru`,
49 | })
50 | ).toEqualMultiline(`
51 | @media (min-width: 12.5em) {
52 | margin: 2rem;
53 | padding: 4rem;
54 | }
55 | `)
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/src/__tests__/api/helpers.js:
--------------------------------------------------------------------------------
1 | import cssapi from '../..'
2 | import { key1, key2, key3, value1 } from '../testHelpers/fixtures/generic'
3 |
4 | describe(`helpers`, () => {
5 | const breakpointMap = [[key1, `25em`], [key2, `50em`], [key3, `75em`]]
6 | const scaleData = {
7 | scale: {
8 | small: 12,
9 | medium: 16,
10 | large: 22,
11 | },
12 | }
13 | const colorData = {
14 | color: {
15 | [key1]: value1,
16 | },
17 | }
18 |
19 | describe(`box helpers`, () => {
20 | const api = cssapi({
21 | breakpoints: breakpointMap,
22 | data: {
23 | ...colorData,
24 | },
25 | })
26 |
27 | describe(`padding-h`, () => {
28 | describe(`with one arguments`, () => {
29 | it(`returns left and right padding`, () => {
30 | expect(api({ paddingH: [`10px`, `15px`, `20px`] })).toEqualMultiline(`
31 | @media (max-width: 24.99em) {
32 | padding-left: 10px;
33 | padding-right: 10px;
34 | }
35 |
36 | @media (min-width: 25em) and (max-width: 49.99em) {
37 | padding-left: 15px;
38 | padding-right: 15px;
39 | }
40 |
41 | @media (min-width: 50em) {
42 | padding-left: 20px;
43 | padding-right: 20px;
44 | }
45 | `)
46 | })
47 | })
48 |
49 | describe(`with two arguments`, () => {
50 | it(`returns left and right padding`, () => {
51 | expect(api({ paddingH: [`10px 20px`, `15px 30px`, `20px 40px`] }))
52 | .toEqualMultiline(`
53 | @media (max-width: 24.99em) {
54 | padding-left: 10px;
55 | padding-right: 20px;
56 | }
57 |
58 | @media (min-width: 25em) and (max-width: 49.99em) {
59 | padding-left: 15px;
60 | padding-right: 30px;
61 | }
62 |
63 | @media (min-width: 50em) {
64 | padding-left: 20px;
65 | padding-right: 40px;
66 | }`)
67 | })
68 | })
69 | })
70 |
71 | describe(`padding-v`, () => {
72 | describe(`with one arguments`, () => {
73 | it(`returns top and bottom padding`, () => {
74 | expect(api({ paddingV: [`10px`, `15px`, `20px`] })).toEqualMultiline(`
75 | @media (max-width: 24.99em) {
76 | padding-top: 10px;
77 | padding-bottom: 10px;
78 | }
79 |
80 | @media (min-width: 25em) and (max-width: 49.99em) {
81 | padding-top: 15px;
82 | padding-bottom: 15px;
83 | }
84 |
85 | @media (min-width: 50em) {
86 | padding-top: 20px;
87 | padding-bottom: 20px;
88 | }
89 | `)
90 | })
91 | })
92 |
93 | describe(`with two arguments`, () => {
94 | it(`returns top and bottom padding`, () => {
95 | expect(api({ paddingV: [`10px 20px`, `15px 30px`, `20px 40px`] }))
96 | .toEqualMultiline(`
97 | @media (max-width: 24.99em) {
98 | padding-top: 10px;
99 | padding-bottom: 20px;
100 | }
101 |
102 | @media (min-width: 25em) and (max-width: 49.99em) {
103 | padding-top: 15px;
104 | padding-bottom: 30px;
105 | }
106 |
107 | @media (min-width: 50em) {
108 | padding-top: 20px;
109 | padding-bottom: 40px;
110 | }`)
111 | })
112 | })
113 | })
114 |
115 | describe(`border-v`, () => {
116 | it(`returns top and bottom borders`, () => {
117 | expect(
118 | api({
119 | borderV: [`1ru solid c:key1`, `2ru solid black`, `3ru solid black`],
120 | })
121 | ).toEqualMultiline(`
122 | @media (max-width: 24.99em) {
123 | border-top: 1.25rem solid value1;
124 | border-bottom: 1.25rem solid value1;
125 | }
126 |
127 | @media (min-width: 25em) and (max-width: 49.99em) {
128 | border-top: 2.5rem solid black;
129 | border-bottom: 2.5rem solid black;
130 | }
131 |
132 | @media (min-width: 50em) {
133 | border-top: 3.75rem solid black;
134 | border-bottom: 3.75rem solid black;
135 | }
136 | `)
137 | })
138 | })
139 |
140 | describe(`border-h`, () => {
141 | it(`returns left and right borders`, () => {
142 | expect(
143 | api({
144 | borderH: [`1ru solid c:key1`, `2ru solid black`, `3ru solid black`],
145 | })
146 | ).toEqualMultiline(`
147 | @media (max-width: 24.99em) {
148 | border-left: 1.25rem solid value1;
149 | border-right: 1.25rem solid value1;
150 | }
151 |
152 | @media (min-width: 25em) and (max-width: 49.99em) {
153 | border-left: 2.5rem solid black;
154 | border-right: 2.5rem solid black;
155 | }
156 |
157 | @media (min-width: 50em) {
158 | border-left: 3.75rem solid black;
159 | border-right: 3.75rem solid black;
160 | }
161 | `)
162 | })
163 | })
164 | })
165 |
166 | describe(`offset`, () => {
167 | const api = cssapi({ breakpoints: breakpointMap })
168 |
169 | it(`renders a single value`, () => {
170 | const result = api({ offset: `10px` })
171 | expect(result).toEqualMultiline(`
172 | top: 10px;
173 | right: 10px;
174 | bottom: 10px;
175 | left: 10px;
176 | `)
177 | })
178 |
179 | it(`renders two values`, () => {
180 | const result = api({ offset: `10px 20px` })
181 | expect(result).toEqualMultiline(`
182 | top: 10px;
183 | right: 20px;
184 | bottom: 10px;
185 | left: 20px;
186 | `)
187 | })
188 |
189 | it(`renders three values`, () => {
190 | const result = api({
191 | offset: `10px 20px 5px`,
192 | })
193 | expect(result).toEqualMultiline(`
194 | top: 10px;
195 | right: 20px;
196 | bottom: 5px;
197 | left: 20px;
198 | `)
199 | })
200 |
201 | it(`renders four values`, () => {
202 | const result = api({
203 | offset: `10px 20px 5px 2px`,
204 | })
205 | expect(result).toEqualMultiline(`
206 | top: 10px;
207 | right: 20px;
208 | bottom: 5px;
209 | left: 2px;
210 | `)
211 | })
212 | })
213 |
214 | describe(`offsetV`, () => {
215 | const api = cssapi({ breakpoints: breakpointMap })
216 |
217 | it(`renders a single value`, () => {
218 | const result = api({
219 | offsetV: `10px`,
220 | })
221 | expect(result).toEqualMultiline(`
222 | top: 10px;
223 | bottom: 10px;
224 | `)
225 | })
226 |
227 | it(`renders two values`, () => {
228 | const result = api({ offsetV: `10px 20px` })
229 | expect(result).toEqualMultiline(`
230 | top: 10px;
231 | bottom: 20px;
232 | `)
233 | })
234 | })
235 |
236 | describe(`offsetH`, () => {
237 | const api = cssapi({ breakpoints: breakpointMap })
238 |
239 | it(`renders a single value`, () => {
240 | const result = api({ offsetH: `10px` })
241 | expect(result).toEqualMultiline(`
242 | left: 10px;
243 | right: 10px;
244 | `)
245 | })
246 |
247 | it(`renders two values`, () => {
248 | const result = api({ offsetH: `10px 20px` })
249 | expect(result).toEqualMultiline(`
250 | left: 10px;
251 | right: 20px;
252 | `)
253 | })
254 | })
255 |
256 | describe(`baseline`, () => {
257 | describe(`half lines`, () => {
258 | const api = cssapi({
259 | breakpoints: breakpointMap,
260 | data: {
261 | ...scaleData,
262 | },
263 | })
264 |
265 | describe(`with explicit font-size`, () => {
266 | describe(`with explicit lines`, () => {
267 | const result = api({ baseline: `16px 1` })
268 | expect(result).toEqualMultiline(`
269 | font-size: 16px;
270 | line-height: 1.25rem;
271 | `)
272 | })
273 |
274 | describe(`with auto lines`, () => {
275 | expect(api({ baseline: `16px` })).toEqualMultiline(`
276 | font-size: 16px;
277 | line-height: 1.25rem;
278 | `) // 1 lines
279 |
280 | expect(api({ baseline: `20px` })).toEqualMultiline(`
281 | font-size: 20px;
282 | line-height: 1.875rem;
283 | `) // 1.5 lines
284 |
285 | expect(api({ baseline: `21px` })).toEqualMultiline(`
286 | font-size: 21px;
287 | line-height: 1.875rem;
288 | `) // 1.5 lines
289 | })
290 | })
291 |
292 | describe(`with unitless font-size`, () => {
293 | describe(`with explicit lines`, () => {
294 | const result = api({ baseline: `16 1` })
295 | expect(result).toEqualMultiline(`
296 | font-size: 1rem;
297 | line-height: 1.25rem;
298 | `)
299 | })
300 | })
301 |
302 | describe(`with rhythm unit font-size`, () => {
303 | describe(`with explicit lines`, () => {
304 | const result = api({ baseline: `1ru` })
305 | expect(result).toEqualMultiline(`
306 | font-size: 1.25rem;
307 | line-height: 1.875rem;
308 | `)
309 | })
310 | })
311 |
312 | describe(`with font name`, () => {
313 | describe(`with explicit lines`, () => {
314 | const result = api({ baseline: `s:large 1` })
315 | expect(result).toEqualMultiline(`
316 | font-size: 1.375rem;
317 | line-height: 1.25rem;
318 | `)
319 | })
320 | })
321 | })
322 |
323 | describe(`whole lines`, () => {
324 | const api = cssapi({
325 | breakpoints: breakpointMap,
326 | data: {
327 | ...scaleData,
328 | baseline: {
329 | allowHalfLines: false,
330 | },
331 | },
332 | })
333 |
334 | expect(api({ baseline: `20px` })).toEqualMultiline(`
335 | font-size: 20px;
336 | line-height: 2.5rem;
337 | `) // 2 lines
338 | })
339 |
340 | describe(`leading`, () => {
341 | const api = cssapi({
342 | breakpoints: breakpointMap,
343 | data: {
344 | ...scaleData,
345 | baseline: {
346 | minLeading: 4,
347 | },
348 | },
349 | })
350 |
351 | expect(api({ baseline: `17px` })).toEqualMultiline(`
352 | font-size: 17px;
353 | line-height: 1.875rem;
354 | `) // 1.5 lines
355 |
356 | expect(api({ baseline: `16px` })).toEqualMultiline(`
357 | font-size: 16px;
358 | line-height: 1.25rem;
359 | `) // 1 line
360 | })
361 |
362 | describe(`lineHeight`, () => {
363 | const api = cssapi({
364 | breakpoints: breakpointMap,
365 | data: {
366 | ...scaleData,
367 | baseline: {
368 | lineHeight: 24,
369 | },
370 | },
371 | })
372 |
373 | expect(api({ baseline: `22px` })).toEqualMultiline(`
374 | font-size: 22px;
375 | line-height: 1.5rem;
376 | `) // 1 lines
377 |
378 | expect(api({ baseline: `23px` })).toEqualMultiline(`
379 | font-size: 23px;
380 | line-height: 2.25rem;
381 | `) // 1.5 lines
382 | })
383 | })
384 | })
385 |
--------------------------------------------------------------------------------
/src/__tests__/api/mq.js:
--------------------------------------------------------------------------------
1 | import cssapi from '../../index'
2 | import {
3 | breakpoint1,
4 | breakpoint2,
5 | breakpoint3,
6 | } from '../testHelpers/fixtures/generic'
7 |
8 | describe(`mq()`, () => {
9 | const breakpointMap = [
10 | [breakpoint1, `25em`],
11 | [breakpoint2, `50em`],
12 | [breakpoint3, `75em`],
13 | ]
14 |
15 | const api = cssapi({
16 | breakpoints: breakpointMap,
17 | })
18 |
19 | it(`doesn't wrap default breakpoint`, () => {
20 | expect(
21 | api.mq(`default`)({
22 | margin: `1ru`,
23 | padding: `2ru`,
24 | })
25 | ).toEqualMultiline(`
26 | margin: 1.25rem;
27 | padding: 2.5rem;
28 | `)
29 | })
30 |
31 | it(`wraps middle breakpoint`, () => {
32 | expect(
33 | api.mq(breakpoint1)({
34 | margin: `1ru`,
35 | padding: `2ru`,
36 | })
37 | ).toEqualMultiline(`
38 | @media (min-width: 25em) {
39 | margin: 1.25rem;
40 | padding: 2.5rem;
41 | }
42 | `)
43 | })
44 |
45 | describe(`with modifiers and offsets`, () => {
46 | describe(`with <`, () => {
47 | it(`renders`, () => {
48 | expect(
49 | api.mq(``, () => {
62 | it(`renders`, () => {
63 | expect(
64 | api.mq(`>breakpoint1`)({
65 | margin: `1ru`,
66 | padding: `2ru`,
67 | })
68 | ).toEqualMultiline(`
69 | @media (min-width: 25em) {
70 | margin: 1.25rem;
71 | padding: 2.5rem;
72 | }
73 | `)
74 | })
75 | })
76 |
77 | describe(`with @`, () => {
78 | it(`renders`, () => {
79 | expect(
80 | api.mq(`@breakpoint1`)({
81 | margin: `1ru`,
82 | padding: `2ru`,
83 | })
84 | ).toEqualMultiline(`
85 | @media (min-width: 25em) and (max-width: 49.99em) {
86 | margin: 1.25rem;
87 | padding: 2.5rem;
88 | }
89 | `)
90 | })
91 | })
92 |
93 | describe(`with range and offsets`, () => {
94 | it(`renders`, () => {
95 | expect(
96 | api.mq(`>breakpoint1+50 {
111 | expect(() =>
112 | api.mq(breakpoint2)({
113 | margin: [10, 20],
114 | })
115 | ).toThrow(
116 | `[cssapi] api.mq() When using the mq() helper you must supply only a single decaration value but you supplied: [10,20]`
117 | )
118 | })
119 |
120 | it(`throws if an object is supplied for a declaration`, () => {
121 | expect(() =>
122 | api.mq(breakpoint2)({
123 | margin: {
124 | [breakpoint1]: 10,
125 | },
126 | })
127 | ).toThrow(
128 | `[cssapi] api.mq() When using the mq() helper you must supply only a single decaration value but you supplied: {"breakpoint1":10}`
129 | )
130 | })
131 | })
132 |
--------------------------------------------------------------------------------
/src/__tests__/api/scope.js:
--------------------------------------------------------------------------------
1 | import cssapi, { scope } from '../../index'
2 | import {
3 | breakpoint1,
4 | breakpoint2,
5 | breakpoint3,
6 | value1,
7 | value2,
8 | } from '../testHelpers/fixtures/generic'
9 |
10 | describe(`scope`, () => {
11 | const breakpointMap = [
12 | [breakpoint1, 400],
13 | [breakpoint2, 800],
14 | [breakpoint3, 1200],
15 | ]
16 |
17 | const scopedRhythm = {
18 | rhythm: 20,
19 | scopes: [
20 | {
21 | resolve: [breakpoint1, breakpoint2],
22 | data: {
23 | rhythm: 24,
24 | },
25 | },
26 | {
27 | resolve: [breakpoint3],
28 | data: {
29 | rhythm: 28,
30 | },
31 | },
32 | ],
33 | }
34 |
35 | const scopedBaseline = {
36 | scopes: [
37 | {
38 | resolve: [breakpoint1, breakpoint2],
39 | data: {
40 | baseline: {
41 | lineHeight: 24,
42 | },
43 | },
44 | },
45 | {
46 | resolve: [breakpoint3],
47 | data: {
48 | baseline: {
49 | lineHeight: 28,
50 | },
51 | },
52 | },
53 | ],
54 | }
55 |
56 | const scopedScale = {
57 | scale: {
58 | '1': 16,
59 | small: 12,
60 | medium: `s:1`,
61 | large: 22,
62 | },
63 | scopes: [
64 | {
65 | resolve: [breakpoint1, breakpoint2],
66 | data: {
67 | scale: {
68 | '1': 22,
69 | small: 16,
70 | medium: `s:1`,
71 | large: 28,
72 | },
73 | },
74 | },
75 | {
76 | resolve: [breakpoint3],
77 | data: {
78 | scale: {
79 | '1': 26,
80 | small: 18,
81 | medium: `s:1`,
82 | large: 32,
83 | },
84 | },
85 | },
86 | ],
87 | }
88 |
89 | describe(`using separate values for breakpoints`, () => {
90 | describe(`with scoped value`, () => {
91 | const cssApi = cssapi({
92 | breakpoints: breakpointMap,
93 | data: {
94 | ...scopedRhythm,
95 | },
96 | })
97 |
98 | describe(`default breakpoint`, () => {
99 | it(`resolves the default value`, () => {
100 | expect(
101 | cssApi({
102 | padding: `2ru`,
103 | })
104 | ).toEqual(`padding: 2.5rem;`)
105 | })
106 | })
107 |
108 | describe(`default and first breakpoints `, () => {
109 | it(`resolves to the scoped value`, () => {
110 | expect(
111 | cssApi({
112 | padding: [`2ru`, `2ru`],
113 | })
114 | ).toEqualMultiline(`
115 | @media (max-width: 24.99em) {
116 | padding: 2.5rem;
117 | }
118 |
119 | @media (min-width: 25em) {
120 | padding: 3rem;
121 | }`)
122 | })
123 | })
124 |
125 | describe(`default, first and second breakpoints`, () => {
126 | it(`resolves to the scoped value`, () => {
127 | expect(cssApi({ padding: [`2ru`, `2ru`, `2ru`] })).toEqualMultiline(`
128 | @media (max-width: 24.99em) {
129 | padding: 2.5rem;
130 | }
131 |
132 | @media (min-width: 25em) {
133 | padding: 3rem;
134 | }`)
135 | })
136 | })
137 |
138 | describe(`default, second, third and fourth breakpoints`, () => {
139 | it(`resolves to the scoped value`, () => {
140 | expect(cssApi({ padding: [`2ru`, `2ru`, `2ru`, `2ru`] }))
141 | .toEqualMultiline(`
142 | @media (max-width: 24.99em) {
143 | padding: 2.5rem;
144 | }
145 |
146 | @media (min-width: 25em) and (max-width: 74.99em) {
147 | padding: 3rem;
148 | }
149 |
150 | @media (min-width: 75em) {
151 | padding: 3.5rem;
152 | }`)
153 | })
154 | })
155 | })
156 |
157 | describe(`with scoped object`, () => {
158 | const cssApi = cssapi({
159 | breakpoints: breakpointMap,
160 | data: {
161 | ...scopedScale,
162 | },
163 | })
164 |
165 | describe(`default breakpoint`, () => {
166 | it(`resolves the default value`, () => {
167 | expect(cssApi({ fontSize: `s:medium` })).toEqual(`font-size: 1rem;`)
168 | })
169 | })
170 |
171 | describe(`default and second breakpoints`, () => {
172 | it(`resolves to the scoped value`, () => {
173 | expect(cssApi({ fontSize: [`s:medium`, `s:medium`] }))
174 | .toEqualMultiline(`
175 | @media (max-width: 24.99em) {
176 | font-size: 1rem;
177 | }
178 |
179 | @media (min-width: 25em) {
180 | font-size: 1.375rem;
181 | }`)
182 | })
183 | })
184 |
185 | describe(`default, second and third breakpoints`, () => {
186 | it(`resolves to the scoped value`, () => {
187 | expect(cssApi({ fontSize: [`s:medium`, `s:medium`, `s:medium`] }))
188 | .toEqualMultiline(`
189 | @media (max-width: 24.99em) {
190 | font-size: 1rem;
191 | }
192 |
193 | @media (min-width: 25em) {
194 | font-size: 1.375rem;
195 | }`)
196 | })
197 | })
198 |
199 | describe(`default, second, third and fourth breakpoints`, () => {
200 | it(`resolves to the scoped value`, () => {
201 | expect(
202 | cssApi({
203 | fontSize: [`s:medium`, `s:medium`, `s:medium`, `s:medium`],
204 | })
205 | ).toEqualMultiline(`
206 | @media (max-width: 24.99em) {
207 | font-size: 1rem;
208 | }
209 |
210 | @media (min-width: 25em) and (max-width: 74.99em) {
211 | font-size: 1.375rem;
212 | }
213 |
214 | @media (min-width: 75em) {
215 | font-size: 1.625rem;
216 | }`)
217 | })
218 | })
219 | })
220 | })
221 |
222 | describe(`scoped baseline`, () => {
223 | const cssApi = cssapi({
224 | breakpoints: breakpointMap,
225 | data: {
226 | ...scopedBaseline,
227 | },
228 | })
229 |
230 | describe(`default breakpoint`, () => {
231 | it(`resolves the all values`, () => {
232 | expect(cssApi({ baseline: [`16`, `16`, `16`, `16`] }))
233 | .toEqualMultiline(`
234 | @media (max-width: 24.99em) {
235 | font-size: 1rem;
236 | line-height: 1.25rem;
237 | }
238 |
239 | @media (min-width: 25em) and (max-width: 74.99em) {
240 | font-size: 1rem;
241 | line-height: 1.5rem;
242 | }
243 |
244 | @media (min-width: 75em) {
245 | font-size: 1rem;
246 | line-height: 1.75rem;
247 | }
248 | `)
249 | })
250 | })
251 | })
252 |
253 | describe(`scope helper`, () => {
254 | describe(`with scoped value`, () => {
255 | const cssApi = cssapi({
256 | breakpoints: breakpointMap,
257 | data: {
258 | ...scopedRhythm,
259 | },
260 | })
261 |
262 | describe(`default, second, third and fourth breakpoints`, () => {
263 | it(`renders values for every breakpoint`, () => {
264 | expect(cssApi({ padding: scope`2ru` })).toEqualMultiline(`
265 | @media (max-width: 24.99em) {
266 | padding: 2.5rem;
267 | }
268 |
269 | @media (min-width: 25em) and (max-width: 74.99em) {
270 | padding: 3rem;
271 | }
272 |
273 | @media (min-width: 75em) {
274 | padding: 3.5rem;
275 | }`)
276 | })
277 | })
278 | })
279 |
280 | describe(`with scoped object`, () => {
281 | const cssApi = cssapi({
282 | breakpoints: breakpointMap,
283 | data: {
284 | ...scopedScale,
285 | },
286 | })
287 |
288 | describe(`default, second, third and fourth breakpoints`, () => {
289 | it(`resolves to the scoped value`, () => {
290 | expect(cssApi({ fontSize: scope`s:medium` })).toEqualMultiline(`
291 | @media (max-width: 24.99em) {
292 | font-size: 1rem;
293 | }
294 |
295 | @media (min-width: 25em) and (max-width: 74.99em) {
296 | font-size: 1.375rem;
297 | }
298 |
299 | @media (min-width: 75em) {
300 | font-size: 1.625rem;
301 | }`)
302 | })
303 | })
304 | })
305 |
306 | it(`handles interpolated values`, () => {
307 | expect(scope`${value1}`).toEqual({
308 | scope: `value1`,
309 | })
310 |
311 | expect(scope` ${value1}`).toEqual({
312 | scope: ` value1`,
313 | })
314 |
315 | expect(scope`${value1} ${value2} value3`).toEqual({
316 | scope: `value1 value2 value3`,
317 | })
318 | })
319 | })
320 | })
321 |
--------------------------------------------------------------------------------
/src/__tests__/breakpoints/breakpointProvider.js:
--------------------------------------------------------------------------------
1 | import breakpointProvider from '../../breakpoints/breakpointProvider'
2 | import {
3 | key1,
4 | key2,
5 | key3,
6 | value1,
7 | value2,
8 | value3,
9 | } from '../testHelpers/fixtures/generic'
10 |
11 | describe(`breakpointProvider`, () => {
12 | describe(`with length values`, () => {
13 | const breakpointMap = [[key1, `25em`], [key2, `50em`], [key3, `75em`]]
14 | const configuredProvider = breakpointProvider(breakpointMap)
15 | describe(`byIndex()`, () => {
16 | it(`returns an array of name, value pairs`, () => {
17 | const expected = [
18 | {
19 | name: key1,
20 | query: { from: `25em`, to: `50em` },
21 | value: value1,
22 | },
23 | {
24 | name: key2,
25 | query: { from: `50em`, to: `75em` },
26 | value: value2,
27 | },
28 | { name: key3, query: { from: `75em` }, value: value3 },
29 | ]
30 |
31 | expect(configuredProvider.byIndex([value1, value2, value3])).toEqual(
32 | expected
33 | )
34 | })
35 | })
36 |
37 | describe(`byName()`, () => {
38 | it(`returns an array of name, value pairs`, () => {
39 | const expected = [
40 | { name: key1, query: { from: `25em` }, value: value1 },
41 | { name: key2, query: { from: `50em` }, value: value2 },
42 | { name: key3, query: { from: `75em` }, value: value3 },
43 | ]
44 | expect(
45 | configuredProvider.byName({
46 | [key1]: value1,
47 | [key2]: value2,
48 | [key3]: value3,
49 | })
50 | ).toEqual(expected)
51 | })
52 | })
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/src/__tests__/breakpoints/resolveBreakpoints.js:
--------------------------------------------------------------------------------
1 | import breakpointProvider from '../../breakpoints/breakpointProvider'
2 | import resolveBreakpoints from '../../breakpoints/resolveBreakpoints'
3 | import { key1, key2, key3, key4 } from '../testHelpers/fixtures/generic'
4 |
5 | const value1 = `500em`
6 | const value2 = `900em`
7 | const value3 = `1200em`
8 |
9 | describe(`resolveBreakpoints()`, () => {
10 | const breakpointMap = [[key1, value1], [key2, value2], [key3, value3]]
11 | const provider = breakpointProvider(breakpointMap)
12 | const resolver = resolveBreakpoints(provider)
13 |
14 | describe(`missing breakpoints`, () => {
15 | it(`throws when no breakpoint exists for index`, () => {
16 | expect(() => resolver(`a`, `b`, `c`, `d`)).toThrow(
17 | `[cssapi] (config.breakpoints) Couldn't resolve breakpoint at index 3 with args: ["a","b","c","d"]`
18 | )
19 | })
20 |
21 | it(`throws when no breakpoint exists for key`, () => {
22 | expect(() =>
23 | resolver({
24 | [key1]: `a`,
25 | [key2]: `b`,
26 | [key3]: `c`,
27 | [key4]: `d`,
28 | })
29 | ).toThrow(
30 | `[cssapi] (config.breakpoints) Couldn't resolve breakpoint with name 'key4' with args: [{"key1":"a","key2":"b","key3":"c","key4":"d"}]`
31 | )
32 | })
33 | })
34 |
35 | describe(`with separate args`, () => {
36 | it(`returns an array of breakpointName and value`, () => {
37 | expect(resolver(`a`, `b`, `c`)).toEqual([
38 | {
39 | name: key1,
40 | query: { from: `500em`, to: `900em` },
41 | value: `a`,
42 | },
43 | {
44 | name: key2,
45 | query: { from: `900em`, to: `1200em` },
46 | value: `b`,
47 | },
48 | {
49 | name: key3,
50 | query: { from: `1200em` },
51 | value: `c`,
52 | },
53 | ])
54 | })
55 | })
56 |
57 | describe(`with a map`, () => {
58 | it(`returns an array of breakpointName and value`, () => {
59 | expect(
60 | resolver({
61 | [key1]: `a`,
62 | [key2]: `b`,
63 | [key3]: `c`,
64 | })
65 | ).toEqual([
66 | {
67 | name: key1,
68 | query: { from: `500em` },
69 | value: `a`,
70 | },
71 | {
72 | name: key2,
73 | query: { from: `900em` },
74 | value: `b`,
75 | },
76 | {
77 | name: key3,
78 | query: { from: `1200em` },
79 | value: `c`,
80 | },
81 | ])
82 | })
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/src/__tests__/renderers/renderDirectionProps.js:
--------------------------------------------------------------------------------
1 | import renderDirectionProps from '../../build/declarations/renderers/renderDirectionProps'
2 |
3 | describe(`renderDirectionProps()`, () => {
4 | const renderer = renderDirectionProps
5 | it(`renders a single`, () => {
6 | const result = renderer(null, [`10px`])
7 | expect(result).toEqualMultiline(`
8 | top: 10px;
9 | right: 10px;
10 | bottom: 10px;
11 | left: 10px;
12 | `)
13 | })
14 |
15 | it(`renders two values`, () => {
16 | const result = renderer(null, [`10px`, `20px`])
17 | expect(result).toEqualMultiline(`
18 | top: 10px;
19 | right: 20px;
20 | bottom: 10px;
21 | left: 20px;
22 | `)
23 | })
24 |
25 | it(`renders three values`, () => {
26 | const result = renderer(null, [`10px`, `20px`, `5px`])
27 | expect(result).toEqualMultiline(`
28 | top: 10px;
29 | right: 20px;
30 | bottom: 5px;
31 | left: 20px;
32 | `)
33 | })
34 |
35 | it(`renders four values`, () => {
36 | const result = renderer(null, [`10px`, `20px`, `5px`, `2px`])
37 | expect(result).toEqualMultiline(`
38 | top: 10px;
39 | right: 20px;
40 | bottom: 5px;
41 | left: 2px;
42 | `)
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/src/__tests__/testHelpers/fixtures/breakpoints.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Undistraction/cssapi/8597637b134e3f9937ca35c724d7bc3f0e423e9c/src/__tests__/testHelpers/fixtures/breakpoints.js
--------------------------------------------------------------------------------
/src/__tests__/testHelpers/fixtures/generic.js:
--------------------------------------------------------------------------------
1 | export const value1 = `value1`
2 | export const value2 = `value2`
3 | export const value3 = `value3`
4 | export const value4 = `value4`
5 | export const invalidKeyValue = `invalidKeyValue`
6 | export const invalidKeyName = `invalidKeyName`
7 | export const key1 = `key1`
8 | export const key2 = `key2`
9 | export const key3 = `key3`
10 | export const key4 = `key4`
11 | export const key5 = `key5`
12 | export const key6 = `key6`
13 | export const breakpoint1 = `breakpoint1`
14 | export const breakpoint2 = `breakpoint2`
15 | export const breakpoint3 = `breakpoint3`
16 | export const breakpoint4 = `breakpoint4`
17 | export const func1 = () => {}
18 |
--------------------------------------------------------------------------------
/src/__tests__/testHelpers/matchers/customMatchers.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-unused-vars
2 | import JasmineExpect from 'jasmine-expect'
3 | import { toEqualMultiline, toThrowMultiline } from 'jasmine-multiline-matchers'
4 |
5 | expect.extend({
6 | toEqualMultiline,
7 | toThrowMultiline,
8 | })
9 |
--------------------------------------------------------------------------------
/src/__tests__/themeHelpers/api.js:
--------------------------------------------------------------------------------
1 | import configureCssApi, { api } from '../../index'
2 |
3 | describe(`theme helpers`, () => {
4 | describe(`api()`, () => {
5 | const cssApi = configureCssApi()
6 |
7 | describe(`with no theme`, () => {
8 | it(`throws`, () => {
9 | expect(() => api({})({})).toThrow(
10 | `[cssapi] api() There was no theme object available on the props object`
11 | )
12 | })
13 | })
14 |
15 | describe(`with no api function`, () => {
16 | it(`throws`, () => {
17 | expect(() => api({})({ theme: {} })).toThrow(
18 | `[cssapi] api() There was no api function defined on the theme object. Value was: undefined`
19 | )
20 | })
21 | })
22 |
23 | describe(`with api defined on theme`, () => {
24 | it(`resolves declaration object`, () => {
25 | expect(
26 | api({
27 | padding: 16,
28 | })({
29 | theme: {
30 | api: cssApi,
31 | },
32 | })
33 | ).toEqual(`padding: 1rem;`)
34 | })
35 | })
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/src/__tests__/themeHelpers/mixin.js:
--------------------------------------------------------------------------------
1 | import configureCssApi, { mixin } from '../../index'
2 | import { value1, value2, value3 } from '../testHelpers/fixtures/generic'
3 |
4 | describe(`mixin`, () => {
5 | const cssApi = configureCssApi()
6 |
7 | const defaultMixinFunc = () => () => {}
8 |
9 | describe(`with no theme`, () => {
10 | it(`throws`, () => {
11 | const props = {}
12 |
13 | expect(() => mixin(defaultMixinFunc)()(props)).toThrow(
14 | `[cssapi] mixin() There was no theme object available on the props object`
15 | )
16 | })
17 | })
18 |
19 | describe(`with no api function`, () => {
20 | it(`throws`, () => {
21 | const props = { theme: {} }
22 | expect(() => mixin(defaultMixinFunc)()(props)).toThrow(
23 | `[cssapi] mixin() There was no api function defined on the theme object. Value was: undefined`
24 | )
25 | })
26 | })
27 |
28 | describe(`with mixin defined on theme`, () => {
29 | describe(`when mixin accepts args`, () => {
30 | const mock = jest.fn()
31 |
32 | const mixinFunc = (api, props) => (...args) => {
33 | mock(api, props, ...args)
34 | }
35 |
36 | const props = {
37 | theme: {
38 | api: cssApi,
39 | },
40 | }
41 |
42 | it(`resolves declaration object`, () => {
43 | mixin(mixinFunc)(value1, value2, value3)(props)
44 |
45 | expect(mock).toBeCalledWith(cssApi, props, value1, value2, value3)
46 | })
47 | })
48 |
49 | describe(`when mixin doesn't accept args`, () => {
50 | const mock = jest.fn()
51 |
52 | const mixinFunc = (api, props) => () => {
53 | mock(api, props)
54 | }
55 |
56 | const props = {
57 | theme: {
58 | api: cssApi,
59 | },
60 | }
61 |
62 | it(`resolves declaration object`, () => {
63 | mixin(mixinFunc)()(props)
64 |
65 | expect(mock).toBeCalledWith(cssApi, props)
66 | })
67 | })
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/src/__tests__/themeHelpers/mq.js:
--------------------------------------------------------------------------------
1 | import configureCssApi, { mq } from '../../index'
2 | import { breakpoint1 } from '../testHelpers/fixtures/generic'
3 |
4 | describe(`theme helpers`, () => {
5 | describe(`mq()`, () => {
6 | const cssApi = configureCssApi()
7 |
8 | describe(`with no theme`, () => {
9 | it(`throws`, () => {
10 | expect(() => mq(breakpoint1, {})({})).toThrow(
11 | `[cssapi] api() There was no theme object available on the props object`
12 | )
13 | })
14 | })
15 |
16 | describe(`with no api function`, () => {
17 | it(`throws`, () => {
18 | expect(() => mq(breakpoint1, {})({ theme: {} })).toThrow(
19 | `[cssapi] api() There was no api function defined on the theme object. Value was: undefined`
20 | )
21 | })
22 | })
23 |
24 | describe(`with api defined on theme`, () => {
25 | it(`resolves declaration object`, () => {
26 | expect(
27 | mq(`default`, {
28 | padding: 16,
29 | })({
30 | theme: {
31 | api: cssApi,
32 | },
33 | })
34 | ).toEqual(`padding: 1rem;`)
35 | })
36 | })
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/src/__tests__/transformers/percentageStringToRatioTransformer.js:
--------------------------------------------------------------------------------
1 | import { map } from 'ramda'
2 | import { mapIndexed } from 'ramda-adjunct'
3 | import percentageStringToRatioTransformer from '../../transformers/percentageStringToRatioTransformer'
4 |
5 | describe(`percentageStringToRatioTransformer`, () => {
6 | it(`returns non-percentage values untouched`, () => {
7 | const values = [10, -10, 0, `10`, `10rem`, `auto`]
8 | map(value => {
9 | const result = percentageStringToRatioTransformer(value)
10 | expect(result).toEqual(value)
11 | })(values)
12 | })
13 |
14 | it(`transforms percentage value`, () => {
15 | const values = [`50%`, `3%`, `400%`]
16 | const expectedValues = [0.5, 0.03, 4]
17 | mapIndexed((value, idx) => {
18 | const result = percentageStringToRatioTransformer(value)
19 | expect(result).toEqual(expectedValues[idx])
20 | })(values)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/src/__tests__/transformers/ratioToPercentageStringTransformer.js:
--------------------------------------------------------------------------------
1 | import { map } from 'ramda'
2 | import { mapIndexed } from 'ramda-adjunct'
3 | import ratioToPercentageStringTransformer from '../../transformers/ratioToPercentageStringTransformer'
4 |
5 | describe(`ratioToPercentageStringTransformer`, () => {
6 | it(`returns non-fraction values untouched`, () => {
7 | const values = [0, `10`, `10rem`, `10%`, `auto`]
8 | map(value => {
9 | const result = ratioToPercentageStringTransformer(value)
10 | expect(result).toEqual(value)
11 | })(values)
12 | })
13 |
14 | it(`transforms fraction value below 4`, () => {
15 | const values = [0.5, 1 / 4, 4]
16 | const expectedValues = [`50%`, `25%`, `400%`]
17 | mapIndexed((value, idx) => {
18 | const result = ratioToPercentageStringTransformer(value)
19 | expect(result).toEqual(expectedValues[idx])
20 | })(values)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/src/__tests__/utils/insertSubIntoProp.js:
--------------------------------------------------------------------------------
1 | import { insertSubIntoProp } from '../../utils/formatting'
2 | import { value1 } from '../testHelpers/fixtures/generic'
3 |
4 | describe(`insertSubIntoProp`, () => {
5 | it(`inserts the sub into the prop`, () => {
6 | const prop = `alphaBravo`
7 | const expected = `alphaValue1Bravo`
8 | expect(insertSubIntoProp([prop, value1])).toEqual(expected)
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/__tests__/utils/isValidModifiedMq.js:
--------------------------------------------------------------------------------
1 | import { map, test } from 'ramda'
2 | import { RE_MODIFIED_MQ } from '../../const/regexp'
3 |
4 | describe(`isValidModifiedMq`, () => {
5 | it(`returns true for valid values`, () => {
6 | const validValues = [
7 | `alpha`,
8 | `alphaBravo`,
9 | `alphaBravoCharlie`,
10 | `>alpha`,
11 | `alpha+10`,
14 | `alpha+1.5`,
16 | `alpha-10`,
18 | `>alpha-10`,
19 | `>alpha-1.5`,
20 | `>alpha-1.5`,
21 | `>alpha+10em`,
22 | `alpha+1.5em`,
24 | `alpha-1.5em`,
26 | `>alpha-1.5em`,
27 | `alpha-50emalpha-50em {
32 | expect(test(RE_MODIFIED_MQ, value)).toBeTrue()
33 | }, validValues)
34 | })
35 |
36 | it(`returns false for invalid values`, () => {
37 | const invalidValues = [
38 | `<alpha`,
40 | `>>alpha`,
41 | `>bravo`,
45 | `alpha+10>bravo`,
46 | `alpha-50em {
56 | expect(test(RE_MODIFIED_MQ, value)).toBeFalse()
57 | }, invalidValues)
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/src/__tests__/utils/parseBreakpoint.js:
--------------------------------------------------------------------------------
1 | import { map } from 'ramda'
2 | import { GT_MODIFIER, LT_MODIFIER, MODIFIERS } from '../../const/breakpoints'
3 | import { parseBreakpoint } from '../../utils/breakpoints'
4 |
5 | describe(`parseBreakpoint`, () => {
6 | const name1 = `alpha`
7 | const name2 = `bravo`
8 |
9 | it(`throws when syntax is invalid`, () => {
10 | const value = ` parseBreakpoint(value)).toThrow(
13 | `The syntax you used to describe your breakpoint range was invalid for ' {
18 | it(`parses a name`, () => {
19 | const value = name1
20 | expect(parseBreakpoint(value)).toEqual({
21 | name: value,
22 | range: [{ name: value }],
23 | })
24 | })
25 |
26 | map(modifier => {
27 | it(`parses a name with ${modifier} modifier`, () => {
28 | const value = `${modifier}${name1}`
29 | expect(parseBreakpoint(value)).toEqual({
30 | name: value,
31 | range: [{ name: name1, modifier }],
32 | })
33 | })
34 | }, MODIFIERS)
35 |
36 | describe(`with offset modifiers`, () => {
37 | describe(`positive`, () => {
38 | describe(`with unitless value`, () => {
39 | it(`parses with an offset`, () => {
40 | const value = `${name1}+10`
41 | expect(parseBreakpoint(value)).toEqual({
42 | name: value,
43 | range: [{ name: name1, offset: `10` }],
44 | })
45 | })
46 | })
47 |
48 | describe(`with em value`, () => {
49 | it(`parses with an offset`, () => {
50 | const value = `${name1}+10em`
51 | expect(parseBreakpoint(value)).toEqual({
52 | name: value,
53 | range: [{ name: name1, offset: `10em` }],
54 | })
55 | })
56 | })
57 | })
58 |
59 | describe(`negative`, () => {
60 | describe(`with unitless value`, () => {
61 | it(`parses with an offset`, () => {
62 | const value = `${name1}-10`
63 | expect(parseBreakpoint(value)).toEqual({
64 | name: value,
65 | range: [{ name: name1, offset: `-10` }],
66 | })
67 | })
68 | })
69 |
70 | describe(`with em value`, () => {
71 | it(`parses with an offset`, () => {
72 | const value = `${name1}-10em`
73 | expect(parseBreakpoint(value)).toEqual({
74 | name: value,
75 | range: [{ name: name1, offset: `-10em` }],
76 | })
77 | })
78 | })
79 | })
80 | })
81 |
82 | describe(`with modifier and offset modifier`, () => {
83 | describe(`with em value`, () => {
84 | it(`parses with an offset`, () => {
85 | const value = `${LT_MODIFIER}${name1}+10em`
86 | expect(parseBreakpoint(value)).toEqual({
87 | name: value,
88 | range: [{ name: name1, modifier: LT_MODIFIER, offset: `10em` }],
89 | })
90 | })
91 | })
92 | })
93 | })
94 |
95 | describe(`multiple values`, () => {
96 | it(`parses two names`, () => {
97 | const value = `${name1}${LT_MODIFIER}${name2}`
98 | expect(parseBreakpoint(value)).toEqual({
99 | name: value,
100 | range: [{ name: name1 }, { name: name2 }],
101 | })
102 | })
103 |
104 | it(`with modified first name`, () => {
105 | const value = `>${name1}${LT_MODIFIER}${name2}`
106 | expect(parseBreakpoint(value)).toEqual({
107 | name: value,
108 | range: [{ name: name1, modifier: GT_MODIFIER }, { name: name2 }],
109 | })
110 | })
111 |
112 | it(`with modified first name and both offset`, () => {
113 | const value = `>${name1}+15em${LT_MODIFIER}${name2}-40`
114 | expect(parseBreakpoint(value)).toEqual({
115 | name: value,
116 | range: [
117 | { name: name1, modifier: GT_MODIFIER, offset: `15em` },
118 | { name: name2, offset: `-40` },
119 | ],
120 | })
121 | })
122 | })
123 | })
124 |
--------------------------------------------------------------------------------
/src/__tests__/utils/replaceTokens.js:
--------------------------------------------------------------------------------
1 | import { replaceTokens, replaceToken } from '../../utils/formatting'
2 |
3 | describe(`replaceToken`, () => {
4 | it(`replaces a single token`, () => {
5 | const template = `alpha #{bravo}`
6 | expect(replaceToken(template, `bravo`, `charlie`)).toEqual(`alpha charlie`)
7 | })
8 |
9 | it(`replaces a multiple identical tokens`, () => {
10 | const template = `alpha #{bravo} #{bravo}`
11 | expect(replaceToken(template, `bravo`, `charlie`)).toEqual(
12 | `alpha charlie charlie`
13 | )
14 | })
15 | })
16 |
17 | describe(`replaceTokens`, () => {
18 | describe(`with object`, () => {
19 | it(`replaces a single token`, () => {
20 | const template = `alpha #{bravo}`
21 | expect(replaceTokens(template, { bravo: `charlie` })).toEqual(
22 | `alpha charlie`
23 | )
24 | })
25 |
26 | it(`replaces a multiple identical tokens`, () => {
27 | const template = `alpha #{bravo} #{bravo}`
28 | expect(replaceTokens(template, { bravo: `charlie` })).toEqual(
29 | `alpha charlie charlie`
30 | )
31 | })
32 |
33 | it(`replaces a multiple different tokens`, () => {
34 | const template = `alpha #{bravo} #{delta}`
35 | expect(
36 | replaceTokens(template, { bravo: `charlie`, delta: `echo` })
37 | ).toEqual(`alpha charlie echo`)
38 | })
39 |
40 | it(`handles additional values`, () => {
41 | const template = `alpha #{bravo} #{delta}`
42 | expect(
43 | replaceTokens(template, {
44 | bravo: `charlie`,
45 | delta: `echo`,
46 | foxtrot: `gamma`,
47 | })
48 | ).toEqual(`alpha charlie echo`)
49 | })
50 | })
51 |
52 | describe(`with array`, () => {
53 | it(`replaces all tokens`, () => {
54 | const template = `alpha #{1} #{2}`
55 | expect(replaceTokens(template, [`bravo`, `charlie`])).toEqual(
56 | `alpha bravo charlie`
57 | )
58 | })
59 |
60 | it(`handles addtional values`, () => {
61 | const template = `alpha #{1} #{2}`
62 | expect(replaceTokens(template, [`bravo`, `charlie`, `delta`])).toEqual(
63 | `alpha bravo charlie`
64 | )
65 | })
66 | })
67 | })
68 |
--------------------------------------------------------------------------------
/src/__tests__/utils/transformAllPartsWith.js:
--------------------------------------------------------------------------------
1 | import allPartsTransformer from '../../transformers/composite/allPartsTransformer'
2 |
3 | describe(`allPartsTransformer()`, () => {
4 | describe(`with a single part`, () => {
5 | it(`applies the transfomer to that part`, () => {
6 | const value = `a`
7 | const transformer = jest.fn(() => `transformedValue`)
8 | const f = allPartsTransformer(transformer)
9 | const result = f(value)
10 | expect(result).toEqual([`transformedValue`])
11 | expect(transformer).toHaveBeenCalledWith(value, undefined, undefined)
12 | })
13 | })
14 |
15 | describe(`with multiple parts`, () => {
16 | it(`applies the transfomer to all parts`, () => {
17 | const value = `a b c`
18 | const transformer = jest.fn(() => `transformedValue`)
19 | const f = allPartsTransformer(transformer)
20 | const result = f(value)
21 | expect(result).toEqual([
22 | `transformedValue`,
23 | `transformedValue`,
24 | `transformedValue`,
25 | ])
26 | expect(transformer.mock.calls).toEqual([
27 | [`a`, undefined, undefined],
28 | [`b`, undefined, undefined],
29 | [`c`, undefined, undefined],
30 | ])
31 | })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/src/breakpoints/breakpointProvider.js:
--------------------------------------------------------------------------------
1 | import {
2 | apply,
3 | converge,
4 | identity,
5 | insert,
6 | map,
7 | once,
8 | pipe,
9 | reduce,
10 | __,
11 | } from 'ramda'
12 | import { appendFlipped, mapIndexed, reduceIndexed } from 'ramda-adjunct'
13 | import {
14 | assocQuery,
15 | assocValue,
16 | createBreakpointMapping,
17 | propName,
18 | } from '../objects/breakpointMapping'
19 | import {
20 | findBreakpointByIndex,
21 | findBreakpointByName,
22 | } from '../utils/breakpointMap'
23 | import { breakpointValuesToEms, parseBreakpoint } from '../utils/breakpoints'
24 | import { defaultToObj } from '../utils/functions'
25 | import { numKeys } from '../utils/list'
26 | import { reduceObjIndexed } from '../utils/objects'
27 | import {
28 | calculateQueryDescriptor,
29 | calculateQueryDescriptorForRange,
30 | } from '../utils/queryDescriptor'
31 |
32 | // -----------------------------------------------------------------------------
33 | // Process Range
34 | // -----------------------------------------------------------------------------
35 |
36 | const addBreakpointValueToRange = breakpointMap =>
37 | converge(assocValue, [
38 | pipe(propName, findBreakpointByName(breakpointMap)),
39 | identity,
40 | ])
41 |
42 | const addBreakpointValuesToRange = breakpointMap =>
43 | map(addBreakpointValueToRange(breakpointMap))
44 |
45 | const processRange = breakpointMap =>
46 | pipe(
47 | addBreakpointValuesToRange(breakpointMap),
48 | calculateQueryDescriptorForRange(breakpointMap)
49 | )
50 |
51 | const parseBreakpointToMap = (breakpoints, [name, value]) =>
52 | pipe(parseBreakpoint, assocValue(value), appendFlipped(breakpoints))(name)
53 |
54 | const parseBreakpointsToMap = reduceObjIndexed(parseBreakpointToMap, [])
55 |
56 | const createMappingByName = breakpointMap => (
57 | mappings,
58 | { name, range, value }
59 | ) =>
60 | pipe(
61 | processRange(breakpointMap),
62 | createBreakpointMapping(name, value),
63 | appendFlipped(mappings)
64 | )(range)
65 |
66 | const createMappingsByName = breakpointMap =>
67 | reduce(createMappingByName(breakpointMap), [])
68 |
69 | const createMappingByIndex = breakpointMap => (mappings, value, idx) =>
70 | pipe(
71 | findBreakpointByIndex(breakpointMap),
72 | insert(1, value),
73 | apply(createBreakpointMapping),
74 | appendFlipped(mappings)
75 | )(idx)
76 |
77 | const createMappingsByIndex = breakpointMap => values =>
78 | reduceIndexed(createMappingByIndex(breakpointMap), [], values)
79 |
80 | const addQueryDescriptor = mappings => (value, idx) =>
81 | pipe(calculateQueryDescriptor, assocQuery(__, value))(idx, mappings)
82 |
83 | const addQueryDescriptors = mappings =>
84 | mapIndexed(addQueryDescriptor(mappings), mappings)
85 |
86 | // -----------------------------------------------------------------------------
87 | // API
88 | // -----------------------------------------------------------------------------
89 |
90 | const createApi = breakpointMap => {
91 | // Resolve breakpoints for values declared using an object syntax
92 | const byName = pipe(
93 | parseBreakpointsToMap,
94 | createMappingsByName(breakpointMap)
95 | )
96 |
97 | // Resolve breakpoints for values declared using an array syntax
98 | const byIndex = pipe(
99 | createMappingsByIndex(breakpointMap),
100 | addQueryDescriptors
101 | )
102 |
103 | const count = once(() => numKeys(breakpointMap))
104 |
105 | return {
106 | byIndex,
107 | byName,
108 | count,
109 | }
110 | }
111 |
112 | // -----------------------------------------------------------------------------
113 | // Exports
114 | // -----------------------------------------------------------------------------
115 |
116 | const breakpointProvider = pipe(defaultToObj, breakpointValuesToEms, createApi)
117 |
118 | export default breakpointProvider
119 |
--------------------------------------------------------------------------------
/src/breakpoints/descriptors/createAtQueryDecriptor.js:
--------------------------------------------------------------------------------
1 | import { nth, pipe } from 'ramda'
2 | import createQueryDescriptor from '../../objects/queryDescriptor'
3 | import {
4 | findBreakpointIndex,
5 | findNextBreakpointByIndex,
6 | isNotLastBreakpoint,
7 | } from '../../utils/breakpointMap'
8 |
9 | const nextBreakpointByIndex = breakpointMap =>
10 | pipe(findNextBreakpointByIndex(breakpointMap), nth(1))
11 |
12 | const createAtQueryDescriptor = (breakpointMap, rangeItem, rangeItemValue) => {
13 | // We need to limit ourselves using a max of the next query if it exists.
14 | // Use our own index to check if there is a breakpoint after us
15 | const idx = findBreakpointIndex(breakpointMap, rangeItem.name)
16 |
17 | let nextBreakpointValue
18 | if (isNotLastBreakpoint(breakpointMap, idx)) {
19 | nextBreakpointValue = nextBreakpointByIndex(breakpointMap)(idx)
20 | }
21 |
22 | // Otherwise we are the last breakpoint so we don't need a max
23 | return createQueryDescriptor({
24 | from: rangeItemValue,
25 | to: nextBreakpointValue,
26 | })
27 | }
28 |
29 | export default createAtQueryDescriptor
30 |
--------------------------------------------------------------------------------
/src/breakpoints/descriptors/createGtQueryDescriptor.js:
--------------------------------------------------------------------------------
1 | import createQueryDescriptor from '../../objects/queryDescriptor'
2 |
3 | const createGtQueryDescriptor = (breakpointMap, rangeItem, rangeItemValue) =>
4 | createQueryDescriptor({ from: rangeItemValue })
5 |
6 | export default createGtQueryDescriptor
7 |
--------------------------------------------------------------------------------
/src/breakpoints/descriptors/createLtQueryDescriptor.js:
--------------------------------------------------------------------------------
1 | import createQueryDescriptor from '../../objects/queryDescriptor'
2 |
3 | const createLtQueryDescriptor = (breakpointMap, rangeItem, rangeItemValue) =>
4 | createQueryDescriptor({
5 | to: rangeItemValue,
6 | })
7 |
8 | export default createLtQueryDescriptor
9 |
--------------------------------------------------------------------------------
/src/breakpoints/descriptors/createRangedQueryDescriptor.js:
--------------------------------------------------------------------------------
1 | import { nth, pipe } from 'ramda'
2 | import createQueryDescriptor from '../../objects/queryDescriptor'
3 | import { applyOffsetToBreakpointValue } from '../../utils/range'
4 |
5 | const createRangedQueryDescriptor = (
6 | _,
7 | firstRangeItem,
8 | firstItemValue,
9 | range
10 | ) => {
11 | const lastItemValue = pipe(nth(1), applyOffsetToBreakpointValue)(range)
12 | return createQueryDescriptor({ from: firstItemValue, to: lastItemValue })
13 | }
14 |
15 | export default createRangedQueryDescriptor
16 |
--------------------------------------------------------------------------------
/src/breakpoints/descriptors/createSingleQueryDescr.js:
--------------------------------------------------------------------------------
1 | import { cond, either, T } from 'ramda'
2 | import { hasNoModifier } from '../../objects/rangeItem'
3 | import {
4 | modifierIsGtModifier,
5 | modifierIsLtModifier,
6 | } from '../../utils/predicate'
7 | import createAtQueryDescriptor from './createAtQueryDecriptor'
8 | import renderGtQuery from './createGtQueryDescriptor'
9 | import renderLtQuery from './createLtQueryDescriptor'
10 |
11 | const createSingleQueryDescriptor = cond([
12 | [either(hasNoModifier, modifierIsGtModifier), () => renderGtQuery],
13 | [modifierIsLtModifier, () => renderLtQuery],
14 | [T, () => createAtQueryDescriptor],
15 | ])
16 |
17 | export default createSingleQueryDescriptor
18 |
--------------------------------------------------------------------------------
/src/breakpoints/descriptors/createUnrangedQueryDescrptor.js:
--------------------------------------------------------------------------------
1 | import { cond, either, T } from 'ramda'
2 | import { hasNoModifier } from '../../objects/rangeItem'
3 | import {
4 | modifierIsGtModifier,
5 | modifierIsLtModifier,
6 | } from '../../utils/predicate'
7 | import createAtQueryDescriptor from './createAtQueryDecriptor'
8 | import renderGtQuery from './createGtQueryDescriptor'
9 | import renderLtQuery from './createLtQueryDescriptor'
10 |
11 | const createUnrangedQueryDescriptor = cond([
12 | [either(hasNoModifier, modifierIsGtModifier), () => renderGtQuery],
13 | [modifierIsLtModifier, () => renderLtQuery],
14 | [T, () => createAtQueryDescriptor],
15 | ])
16 |
17 | export default createUnrangedQueryDescriptor
18 |
--------------------------------------------------------------------------------
/src/breakpoints/resolveBreakpoints.js:
--------------------------------------------------------------------------------
1 | import { always, compose, cond, head, pipe, T, times, tryCatch } from 'ramda'
2 | import { isPlainObject } from 'ramda-adjunct'
3 | import { invalidBreakpointError, throwBreakpointError } from '../errors'
4 | import { hasScope, propScope } from '../objects/scope'
5 |
6 | const argIsScopeObj = compose(hasScope, head)
7 |
8 | const argIsObj = compose(isPlainObject, head)
9 |
10 | const expandScope = provider => value => times(always(value), provider.count())
11 |
12 | const resolveBreakpointsImpl = provider => breakpoint =>
13 | cond([
14 | [
15 | argIsScopeObj,
16 | pipe(
17 | head,
18 | propScope,
19 | expandScope(provider),
20 | resolveBreakpointsImpl(provider)
21 | // run through and remove values that don't change.
22 | ),
23 | ],
24 | [argIsObj, compose(provider.byName, head)],
25 | [T, provider.byIndex],
26 | ])(breakpoint)
27 |
28 | const resolveBreakpoints = provider => (...args) =>
29 | tryCatch(resolveBreakpointsImpl(provider), message =>
30 | throwBreakpointError(invalidBreakpointError(message, args))
31 | )(args)
32 |
33 | export default resolveBreakpoints
34 |
--------------------------------------------------------------------------------
/src/build/createApi.js:
--------------------------------------------------------------------------------
1 | import { curry, mergeDeepRight, pipe } from 'ramda'
2 | import cssapi from '../index'
3 | import {
4 | addBreakpointToDeclarations,
5 | batchDeclarations,
6 | optimiseDeclarations,
7 | } from '../utils/declarations'
8 | import processDeclarations from './declarations/processDeclarations'
9 | import renderBatch from './declarations/renderers/renderBatch'
10 |
11 | const buildApiFunc = processors =>
12 | pipe(
13 | processDeclarations(processors),
14 | optimiseDeclarations,
15 | batchDeclarations,
16 | renderBatch
17 | )
18 |
19 | const buildMqFunc = api => {
20 | api.mq = curry((breakpoint, declarations) =>
21 | pipe(addBreakpointToDeclarations, api)(breakpoint, declarations)
22 | )
23 | return api
24 | }
25 |
26 | const buildExtendFunc = baseConfig => api => {
27 | api.extend = pipe(mergeDeepRight(baseConfig), cssapi)
28 | return api
29 | }
30 |
31 | const createApi = config =>
32 | pipe(buildApiFunc, buildMqFunc, buildExtendFunc(config))
33 |
34 | export default createApi
35 |
--------------------------------------------------------------------------------
/src/build/declarations/createDeclarationProcessor.js:
--------------------------------------------------------------------------------
1 | import { identity, partial, pipe, reduce, __ } from 'ramda'
2 | import { appendFlipped, ensureArray, list } from 'ramda-adjunct'
3 | import { createBreakpointMapping } from '../../objects/breakpointMapping'
4 | import { transformDeclarationValue } from '../../utils/transformers'
5 | import renderSingleDeclaration from './renderers/renderSingleDeclaration'
6 |
7 | const renderDeclaration = (propName, renderer) =>
8 | pipe(ensureArray, partial(renderer, [propName]), list)
9 |
10 | const processDeclaration = (
11 | propName,
12 | data,
13 | { transformers = identity, renderer = renderSingleDeclaration },
14 | { name, query, value }
15 | ) =>
16 | pipe(
17 | transformDeclarationValue(transformers, name, data),
18 | renderDeclaration(propName, renderer),
19 | createBreakpointMapping(name, __, query)
20 | )(value)
21 |
22 | // Create a map of curried 'processDeclaration' functions for each property,
23 | // ready to render styles when they receive a value
24 | const reducer = (propName, data, style) => (declarations, breakpointMapping) =>
25 | pipe(processDeclaration, appendFlipped(declarations))(
26 | propName,
27 | data,
28 | style,
29 | breakpointMapping
30 | )
31 |
32 | const createDeclarationProcessor = (propName, data, style) =>
33 | reduce(reducer(propName, data, style), [])
34 |
35 | export default createDeclarationProcessor
36 |
--------------------------------------------------------------------------------
/src/build/declarations/createDeclarationProcessors.js:
--------------------------------------------------------------------------------
1 | import { assoc, pipe, __ } from 'ramda'
2 | import resolveBreakpoints from '../../breakpoints/resolveBreakpoints'
3 | import { reduceObjIndexed } from '../../utils/objects'
4 | import createDeclarationProcessor from './createDeclarationProcessor'
5 |
6 | const create = (breakpoints, name, data, style) =>
7 | pipe(
8 | resolveBreakpoints(breakpoints),
9 | createDeclarationProcessor(name, data, style)
10 | )
11 |
12 | const createDeclarationProcessorReducer = (breakpoints, data) => (
13 | acc,
14 | [name, style]
15 | ) => pipe(create, assoc(name, __, acc))(breakpoints, name, data, style)
16 |
17 | const createDeclarationProcessors = ({ breakpoints, data, properties }) =>
18 | reduceObjIndexed(
19 | createDeclarationProcessorReducer(breakpoints, data),
20 | {},
21 | properties
22 | )
23 |
24 | export default createDeclarationProcessors
25 |
--------------------------------------------------------------------------------
/src/build/declarations/processDeclarations.js:
--------------------------------------------------------------------------------
1 | import { apply, isNil, pipe, prop, unnest, when } from 'ramda'
2 | import { appendFlipped, ensureArray } from 'ramda-adjunct'
3 | import { invalidPropertyError, throwAPIError } from '../../errors'
4 | import { reduceObjIndexed } from '../../utils/objects'
5 |
6 | const processDeclaration = processors => (acc, [name, args]) => {
7 | const processor = prop(name, processors)
8 | when(isNil, () => throwAPIError(invalidPropertyError(name)))(processor)
9 | return pipe(ensureArray, apply(processor), appendFlipped(acc))(args)
10 | }
11 |
12 | const processDeclarations = processors =>
13 | pipe(reduceObjIndexed(processDeclaration(processors), []), unnest)
14 |
15 | export default processDeclarations
16 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderBaseline.js:
--------------------------------------------------------------------------------
1 | import { pipe, zip } from 'ramda'
2 | import { joinWithNewline } from '../../../utils/formatting'
3 | import renderDeclarations from './renderDeclarations'
4 |
5 | const PROPS = [`fontSize`, `lineHeight`]
6 |
7 | const renderBaseline = (_, value) =>
8 | pipe(zip(PROPS), renderDeclarations, joinWithNewline)(value)
9 |
10 | export default renderBaseline
11 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderBatch.js:
--------------------------------------------------------------------------------
1 | import { compose, isEmpty, pipe, reduce, unless } from 'ramda'
2 | import { appendFlipped, compact } from 'ramda-adjunct'
3 | import {
4 | joinWithDoubleNewlines,
5 | joinWithNewline,
6 | } from '../../../utils/formatting'
7 | import renderQuery from './renderQuery'
8 |
9 | const writeToString = outputString =>
10 | compose(joinWithDoubleNewlines, compact, appendFlipped([outputString]))
11 |
12 | const renderStyle = (outputString, { query, value }) =>
13 | pipe(
14 | joinWithNewline,
15 | unless(() => isEmpty(query), renderQuery(query)),
16 | writeToString(outputString)
17 | )(value)
18 |
19 | const renderBatch = reduce(renderStyle, ``)
20 |
21 | export default renderBatch
22 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderDeclarations.js:
--------------------------------------------------------------------------------
1 | import { apply, map } from 'ramda'
2 | import renderSingleDeclaration from './renderSingleDeclaration'
3 |
4 | const renderDeclarations = map(apply(renderSingleDeclaration))
5 |
6 | export default renderDeclarations
7 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderDirectionProps.js:
--------------------------------------------------------------------------------
1 | import { pipe, zip } from 'ramda'
2 | import { DIRECTIONS_LIST } from '../../../const/expanders'
3 | import { joinWithNewline } from '../../../utils/formatting'
4 | import renderDeclarations from './renderDeclarations'
5 |
6 | const renderDirectionProps = (name, value) => {
7 | const right = value[1] || value[0]
8 | const bottom = value[2] || value[0]
9 | const left = value[3] || value[1] || value[0]
10 | const directionValues = [value[0], right, bottom, left]
11 |
12 | return pipe(zip(DIRECTIONS_LIST), renderDeclarations, joinWithNewline)(
13 | directionValues
14 | )
15 | }
16 | export default renderDirectionProps
17 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderDualFromOneProps.js:
--------------------------------------------------------------------------------
1 | import { pipe, zip } from 'ramda'
2 | import { joinWithNewline } from '../../../utils/formatting'
3 | import renderDeclarations from './renderDeclarations'
4 |
5 | const renderDualProps = propNames => (_, value) =>
6 | pipe(zip(propNames), renderDeclarations, joinWithNewline)([value, value])
7 |
8 | export default renderDualProps
9 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderDualProps.js:
--------------------------------------------------------------------------------
1 | import { pipe, zip } from 'ramda'
2 | import { joinWithNewline } from '../../../utils/formatting'
3 | import renderDeclarations from './renderDeclarations'
4 |
5 | const renderDualProps = propNames => (_, value) => {
6 | const firstProp = value[0]
7 | const lastProp = value[1] || firstProp
8 | const directionValues = [firstProp, lastProp]
9 |
10 | return pipe(zip(propNames), renderDeclarations, joinWithNewline)(
11 | directionValues
12 | )
13 | }
14 | export default renderDualProps
15 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderQuery.js:
--------------------------------------------------------------------------------
1 | import { indentLines } from '../../../utils/formatting'
2 | import { createQueryFromTemplate } from '../../../utils/templates'
3 | import renderQueryHeader from './renderQueryHeader'
4 |
5 | const renderQuery = query => value =>
6 | createQueryFromTemplate({
7 | query: renderQueryHeader(query),
8 | value: indentLines(value),
9 | })
10 |
11 | export default renderQuery
12 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderQueryHeader.js:
--------------------------------------------------------------------------------
1 | import {
2 | createQueryMaxHeaderFromTemplate,
3 | createQueryMinHeaderFromTemplate,
4 | createQueryMinMaxHeaderFromTemplate,
5 | } from '../../../utils/templates'
6 |
7 | const renderQueryHeader = ({ to, from }) => {
8 | if (from && to) {
9 | return createQueryMinMaxHeaderFromTemplate(to, from)
10 | }
11 |
12 | if (from) {
13 | return createQueryMinHeaderFromTemplate(from)
14 | }
15 |
16 | return createQueryMaxHeaderFromTemplate(to)
17 | }
18 |
19 | export default renderQueryHeader
20 |
--------------------------------------------------------------------------------
/src/build/declarations/renderers/renderSingleDeclaration.js:
--------------------------------------------------------------------------------
1 | import { compose, evolve, pipe } from 'ramda'
2 | import { ensureArray } from 'ramda-adjunct'
3 | import { joinWithSpace, toKebabCase } from '../../../utils/formatting'
4 | import { createDeclarationFromTemplate } from '../../../utils/templates'
5 |
6 | const renderSingleDeclaration = (name, value) =>
7 | pipe(
8 | evolve({
9 | name: toKebabCase,
10 | value: compose(joinWithSpace, ensureArray),
11 | }),
12 | createDeclarationFromTemplate
13 | )({ name, value })
14 |
15 | export default renderSingleDeclaration
16 |
--------------------------------------------------------------------------------
/src/build/ensureBreakpointProvider.js:
--------------------------------------------------------------------------------
1 | import { compose, over } from 'ramda'
2 | import breakpointProvider from '../breakpoints/breakpointProvider'
3 | import { lBreakpoints } from '../objects/config'
4 | import { addDefaultBreakpoint } from '../utils/breakpoints'
5 |
6 | const configureBreakpointProvider = compose(
7 | breakpointProvider,
8 | addDefaultBreakpoint
9 | )
10 |
11 | const ensureBreakpointProvider = over(lBreakpoints, configureBreakpointProvider)
12 |
13 | export default ensureBreakpointProvider
14 |
--------------------------------------------------------------------------------
/src/build/expansion/expandData.js:
--------------------------------------------------------------------------------
1 | import {
2 | assoc,
3 | cond,
4 | identity,
5 | keys,
6 | lensProp,
7 | map,
8 | mergeDeepLeft,
9 | over,
10 | pipe,
11 | prop,
12 | T,
13 | when,
14 | without,
15 | __,
16 | } from 'ramda'
17 | import { concatRight, isNotUndefined, isString } from 'ramda-adjunct'
18 | import CONFIG_FIELD_NAMES from '../../const/config'
19 | import {
20 | missingDataItemKeyError,
21 | missingDataNodeError,
22 | throwDataError,
23 | unrecognisedDataPrefixError,
24 | } from '../../errors'
25 | import { lData, propScopes } from '../../objects/config'
26 | import { splitOnColon } from '../../utils/formatting'
27 | import { reduceWithKeys } from '../../utils/list'
28 | import { whenIsUndefined } from '../../utils/logic'
29 | import {
30 | hasUnnestedWhitespace,
31 | isCSSFunction,
32 | isToken,
33 | } from '../../utils/predicate'
34 | import {
35 | transformFunctionElements,
36 | transformGroup,
37 | } from '../../utils/transformers'
38 |
39 | const { SCOPES, ALIASES } = CONFIG_FIELD_NAMES
40 |
41 | const validPrefixes = (config, data) =>
42 | pipe(
43 | keys,
44 | without([SCOPES, ALIASES]),
45 | concatRight(keys(config.data[ALIASES]))
46 | )(data)
47 |
48 | const expandData = config => {
49 | // Build Function to resolve aliases
50 | const resolveDataAlias = (name, data) =>
51 | pipe(
52 | prop,
53 | whenIsUndefined(() => prop(name, config.data.aliases)),
54 | whenIsUndefined(() => {
55 | throwDataError(
56 | unrecognisedDataPrefixError(name, validPrefixes(config, data))
57 | )
58 | })
59 | )(name, data)
60 |
61 | // ---------------------------------------------------------------------------
62 | // Value Expansion
63 | // ---------------------------------------------------------------------------
64 |
65 | const resolveName = (sourceData, name, key) =>
66 | pipe(
67 | prop(name),
68 | whenIsUndefined(() => throwDataError(missingDataNodeError(name))),
69 | prop(key),
70 | whenIsUndefined(() => throwDataError(missingDataItemKeyError(name, key)))
71 | )(sourceData)
72 |
73 | const expandToken = sourceData => token => {
74 | const [prefix, key] = splitOnColon(token)
75 | const name = resolveDataAlias(prefix, sourceData)
76 | const value = resolveName(sourceData, name, key)
77 | // eslint-disable-next-line no-use-before-define
78 | return expandValue(sourceData)(value)
79 | }
80 |
81 | const expandValues = sourceData =>
82 | // eslint-disable-next-line no-use-before-define
83 | map(expandValue(sourceData))
84 |
85 | const expandValue = sourceData => v =>
86 | cond([
87 | [isToken, expandToken(sourceData)],
88 | [isCSSFunction, transformFunctionElements(expandValues(sourceData))],
89 | [hasUnnestedWhitespace, transformGroup(expandValue(sourceData))],
90 | [T, identity],
91 | ])(v)
92 |
93 | // ---------------------------------------------------------------------------
94 | // Node Expansion
95 | // ---------------------------------------------------------------------------
96 |
97 | const expandNode = (name, expandedRootData) =>
98 | reduceWithKeys((node, key) => {
99 | const sourceData = over(
100 | lensProp(name),
101 | mergeDeepLeft(node),
102 | expandedRootData
103 | )
104 | return over(lensProp(key), when(isString, expandValue(sourceData)))(node)
105 | })
106 |
107 | const expandNodes = expandedRootData =>
108 | reduceWithKeys((acc, name) =>
109 | pipe(expandNode, over(lensProp(name), __, acc))(name, expandedRootData)
110 | )
111 |
112 | // ---------------------------------------------------------------------------
113 | // Scopes
114 | // ---------------------------------------------------------------------------
115 |
116 | const expandScopes = expandedRootData =>
117 | map(over(lData, expandNodes(expandedRootData)))
118 |
119 | // ---------------------------------------------------------------------------
120 | // Data
121 | // ---------------------------------------------------------------------------
122 |
123 | const expand = data => {
124 | // Expand the root node using itself as a data source
125 | const expandedRootData = expandNodes(data)(data)
126 |
127 | // Expand scoped nodes
128 | return pipe(
129 | propScopes,
130 | when(isNotUndefined, expandScopes(expandedRootData)),
131 | assoc(SCOPES, __, expandedRootData)
132 | )(data)
133 | }
134 |
135 | return over(lData, expand, config)
136 | }
137 |
138 | export default expandData
139 |
--------------------------------------------------------------------------------
/src/build/expansion/expandProperties.js:
--------------------------------------------------------------------------------
1 | import { compose, mergeDeepRight, over, prop, __ } from 'ramda'
2 | import { lProperties } from '../../objects/config'
3 | import { reduceObjIndexed } from '../../utils/objects'
4 | import EXPANDER_MAP from './propertyExpanderMap'
5 |
6 | const expand = reduceObjIndexed(
7 | (properties, [propName, expander]) =>
8 | compose(mergeDeepRight(properties), expander)(
9 | propName,
10 | prop(propName, properties)
11 | ),
12 | __,
13 | EXPANDER_MAP
14 | )
15 |
16 | const expandProperties = over(lProperties, expand)
17 |
18 | export default expandProperties
19 |
--------------------------------------------------------------------------------
/src/build/expansion/propertyExpanderMap.js:
--------------------------------------------------------------------------------
1 | import partPositionTransformer from '../../transformers/composite/partPositionTransformer'
2 | import transformPartsWith from '../../transformers/composite/transformPartsWith'
3 | import { wrapWithTransform } from '../../utils/expanders'
4 | import { insertSubIntoProp } from '../../utils/formatting'
5 | import axesExpander from './propertyExpanders/axesExpander'
6 | import cornerExpander from './propertyExpanders/cornerExpander'
7 | import directionExpander from './propertyExpanders/directionExpander'
8 | import directionsExpander from './propertyExpanders/directionsExpander'
9 | import minMaxExpander from './propertyExpanders/minMaxExpander'
10 |
11 | // Expanders take an item described in the config and expand it to multiple
12 | // separate properties. For example 'padding' is expanded to 'padding-top',
13 | // 'padding-right', 'padding-bottom' and 'padding-left', or 'directions' is
14 | // expanded to 'top', 'right', 'bottom' and 'left'.
15 | const EXPANDER_MAP = Object.freeze({
16 | padding: directionExpander(),
17 | margin: directionExpander(),
18 | border: directionExpander({
19 | mainWrapper: transformPartsWith,
20 | subWrapper: transformPartsWith,
21 | }),
22 | borderWidth: directionExpander({
23 | createPropNameStrategy: insertSubIntoProp,
24 | }),
25 | borderStyle: directionExpander({
26 | createPropNameStrategy: insertSubIntoProp,
27 | }),
28 | borderColor: directionExpander({
29 | createPropNameStrategy: insertSubIntoProp,
30 | }),
31 | borderRadius: cornerExpander({
32 | createPropNameStrategy: insertSubIntoProp,
33 | }),
34 | width: minMaxExpander(),
35 | height: minMaxExpander(),
36 | directions: directionsExpander(),
37 | overflow: axesExpander(),
38 | outlineColor: wrapWithTransform(transformPartsWith),
39 | outline: wrapWithTransform(transformPartsWith),
40 | flex: wrapWithTransform(partPositionTransformer(2)),
41 | background: wrapWithTransform(transformPartsWith),
42 | backgroundImage: wrapWithTransform(transformPartsWith),
43 | backgroundPosition: axesExpander({
44 | mainWrapper: transformPartsWith,
45 | subWrapper: transformPartsWith,
46 | }),
47 | boxShadow: wrapWithTransform(transformPartsWith),
48 | transformOrigin: wrapWithTransform(transformPartsWith),
49 | offset: wrapWithTransform(transformPartsWith),
50 | offsetV: wrapWithTransform(transformPartsWith),
51 | offsetH: wrapWithTransform(transformPartsWith),
52 | paddingH: wrapWithTransform(transformPartsWith),
53 | paddingV: wrapWithTransform(transformPartsWith),
54 | marginH: wrapWithTransform(transformPartsWith),
55 | marginV: wrapWithTransform(transformPartsWith),
56 | borderH: wrapWithTransform(transformPartsWith),
57 | borderV: wrapWithTransform(transformPartsWith),
58 | })
59 |
60 | export default EXPANDER_MAP
61 |
--------------------------------------------------------------------------------
/src/build/expansion/propertyExpanders/axesExpander.js:
--------------------------------------------------------------------------------
1 | import { converge, identity, pipe } from 'ramda'
2 | import { AXES_LIST } from '../../../const/expanders'
3 | import { expandMainProp, expandSubProps } from '../../../utils/expanders'
4 | import { appendSubToProp } from '../../../utils/formatting'
5 |
6 | const axesExpander = ({
7 | mainWrapper = identity,
8 | subWrapper = identity,
9 | } = {}) => (propName, style) =>
10 | converge(pipe, [
11 | expandMainProp,
12 | expandSubProps(appendSubToProp, AXES_LIST, subWrapper),
13 | ])(propName, style, mainWrapper)({})
14 |
15 | export default axesExpander
16 |
--------------------------------------------------------------------------------
/src/build/expansion/propertyExpanders/cornerExpander.js:
--------------------------------------------------------------------------------
1 | import { converge, identity, pipe } from 'ramda'
2 | import { CORNERS_LIST } from '../../../const/expanders'
3 | import allPartsTransformer from '../../../transformers/composite/allPartsTransformer'
4 | import { expandMainProp, expandSubProps } from '../../../utils/expanders'
5 | import { appendSubToProp } from '../../../utils/formatting'
6 |
7 | const cornerExpander = ({
8 | mainWrapper = allPartsTransformer,
9 | subWrapper = identity,
10 | createPropNameStrategy = appendSubToProp,
11 | } = {}) => (propName, style) =>
12 | converge(pipe, [
13 | expandMainProp,
14 | expandSubProps(createPropNameStrategy, CORNERS_LIST, subWrapper),
15 | ])(propName, style, mainWrapper)({})
16 |
17 | export default cornerExpander
18 |
--------------------------------------------------------------------------------
/src/build/expansion/propertyExpanders/directionExpander.js:
--------------------------------------------------------------------------------
1 | import { converge, identity, pipe } from 'ramda'
2 | import { DIRECTIONS_LIST } from '../../../const/expanders'
3 | import allPartsTransformer from '../../../transformers/composite/allPartsTransformer'
4 | import { expandMainProp, expandSubProps } from '../../../utils/expanders'
5 | import { appendSubToProp } from '../../../utils/formatting'
6 |
7 | const directionExpander = ({
8 | mainWrapper = allPartsTransformer,
9 | subWrapper = identity,
10 | createPropNameStrategy = appendSubToProp,
11 | } = {}) => (propName, style) =>
12 | converge(pipe, [
13 | expandMainProp,
14 | expandSubProps(createPropNameStrategy, DIRECTIONS_LIST, subWrapper),
15 | ])(propName, style, mainWrapper)({})
16 |
17 | export default directionExpander
18 |
--------------------------------------------------------------------------------
/src/build/expansion/propertyExpanders/directionsExpander.js:
--------------------------------------------------------------------------------
1 | import { DIRECTIONS_LIST } from '../../../const/expanders'
2 | import { expandSubProps } from '../../../utils/expanders'
3 | import { appendSubToProp } from '../../../utils/formatting'
4 |
5 | const directionsExpander = () => (_, style) =>
6 | expandSubProps(appendSubToProp, DIRECTIONS_LIST)(``, style)({})
7 |
8 | export default directionsExpander
9 |
--------------------------------------------------------------------------------
/src/build/expansion/propertyExpanders/minMaxExpander.js:
--------------------------------------------------------------------------------
1 | import { converge, pipe } from 'ramda'
2 | import { MIN_MAX_LIST } from '../../../const/expanders'
3 | import { expandMainProp, expandSubProps } from '../../../utils/expanders'
4 | import { prependSubToProp } from '../../../utils/formatting'
5 |
6 | const minMaxExpander = () => (propName, style) =>
7 | converge(pipe, [
8 | expandMainProp,
9 | expandSubProps(prependSubToProp, MIN_MAX_LIST),
10 | ])(propName, style)({})
11 |
12 | export default minMaxExpander
13 |
--------------------------------------------------------------------------------
/src/build/mergeWithDefaultConfig.js:
--------------------------------------------------------------------------------
1 | import { mergeDeepRight, pipe } from 'ramda'
2 | import defaultConfig from '../config/defaultConfig'
3 | import { defaultToObj } from '../utils/functions'
4 |
5 | const mergeWithDefaultConfig = pipe(defaultToObj, mergeDeepRight(defaultConfig))
6 |
7 | export default mergeWithDefaultConfig
8 |
--------------------------------------------------------------------------------
/src/config/defaultConfig.js:
--------------------------------------------------------------------------------
1 | import renderBaseline from '../build/declarations/renderers/renderBaseline'
2 | import renderDirectionProps from '../build/declarations/renderers/renderDirectionProps'
3 | import renderDualFromOneProps from '../build/declarations/renderers/renderDualFromOneProps'
4 | import renderDualProps from '../build/declarations/renderers/renderDualProps'
5 | import CONFIG_FIELD_NAMES from '../const/config'
6 | import {
7 | DIRECTIONS_LIST_HORIZONTAL,
8 | DIRECTIONS_LIST_VERTICAL,
9 | } from '../const/expanders'
10 | import { LENGTH_UNITS } from '../const/units'
11 | import baselineTransformer from '../transformers/composite/baselineTransformer'
12 | import {
13 | borderLookupTransformer,
14 | boxShadowLookupTransformer,
15 | colorLookupTransformer,
16 | fontLookupTransformer,
17 | gradientLookupTransformer,
18 | imageLookupTransformer,
19 | scaleLookupTransformer,
20 | } from '../transformers/factory/dataLookupTransformers'
21 | import gradientTransformer from '../transformers/gradientTransformer'
22 | import lengthTransformers from '../transformers/lengthTransformers'
23 | import percentageStringToRatioTransformer from '../transformers/percentageStringToRatioTransformer'
24 | import transformTransformer from '../transformers/transformTransformer'
25 |
26 | const { BREAKPOINTS, DATA, ALIASES, SCOPES, PROPERTIES } = CONFIG_FIELD_NAMES
27 |
28 | // -----------------------------------------------------------------------------
29 | // Define API
30 | // -----------------------------------------------------------------------------
31 |
32 | const defaultConfig = {
33 | [BREAKPOINTS]: [],
34 | [DATA]: {
35 | [ALIASES]: {
36 | c: `color`,
37 | g: `gradient`,
38 | s: `scale`,
39 | d: `boxShadow`,
40 | b: `border`,
41 | i: `image`,
42 | f: `font`,
43 | },
44 | lengthUnit: LENGTH_UNITS.REM, // | 'rem' | 'em' | 'px'
45 | baseFontSize: 16, // Font size of your page's root element
46 | rhythm: 20, // Unit of rhythm for use in layout
47 | baseline: {
48 | lineHeight: 20, // Baseline height
49 | minLeading: 2, // Minimum remaining leading before line or half-line added
50 | allowHalfLines: true, // Allow half-lines to be used in baseline calc
51 | },
52 | color: {},
53 | gradient: {},
54 | scale: {},
55 | boxShadow: {},
56 | border: {},
57 | image: {},
58 | font: {},
59 | [SCOPES]: [],
60 | },
61 | [PROPERTIES]: {
62 | // -------------------------------------------------------------------------
63 | // Box Model
64 | // -------------------------------------------------------------------------
65 |
66 | padding: {
67 | transformers: lengthTransformers,
68 | },
69 | margin: {
70 | transformers: lengthTransformers,
71 | },
72 | border: {
73 | transformers: [
74 | borderLookupTransformer,
75 | lengthTransformers,
76 | colorLookupTransformer,
77 | ],
78 | },
79 | borderWidth: {
80 | transformers: lengthTransformers,
81 | },
82 | borderColor: {
83 | transformers: colorLookupTransformer,
84 | },
85 | borderStyle: {},
86 | borderSpacing: {
87 | transformers: lengthTransformers,
88 | },
89 | borderRadius: {
90 | transformers: lengthTransformers,
91 | },
92 | borderImageSource: {
93 | transformers: [
94 | imageLookupTransformer,
95 | gradientLookupTransformer,
96 | gradientTransformer([colorLookupTransformer, ...lengthTransformers]),
97 | ],
98 | },
99 |
100 | // -------------------------------------------------------------------------
101 | // Outline
102 | // -------------------------------------------------------------------------
103 |
104 | outline: {
105 | transformers: [
106 | borderLookupTransformer,
107 | lengthTransformers,
108 | colorLookupTransformer,
109 | ],
110 | },
111 | outlineColor: {
112 | transformers: colorLookupTransformer,
113 | },
114 | outlineOffset: {
115 | transformers: lengthTransformers,
116 | },
117 | outlineStyle: {},
118 | outlineWidth: {
119 | transformers: lengthTransformers,
120 | },
121 |
122 | // -------------------------------------------------------------------------
123 | // Text
124 | // -------------------------------------------------------------------------
125 |
126 | fontFamily: {
127 | transformers: fontLookupTransformer,
128 | },
129 | fontSize: {
130 | transformers: [scaleLookupTransformer, lengthTransformers],
131 | },
132 | fontWeight: {},
133 | fontVarient: {},
134 | fontStretch: {},
135 | fontStyle: {},
136 | lineHeight: {
137 | transformers: lengthTransformers,
138 | },
139 | textAlign: {},
140 | letterSpacing: {
141 | transformers: lengthTransformers,
142 | },
143 | wordWrap: {},
144 | wordSpacing: {},
145 | textDecoration: {},
146 | whiteSpace: {},
147 |
148 | // -------------------------------------------------------------------------
149 | // List
150 | // -------------------------------------------------------------------------
151 |
152 | listStyle: {
153 | transformers: imageLookupTransformer,
154 | },
155 | listStyleImage: {
156 | transformers: imageLookupTransformer,
157 | },
158 | listStylePosition: {},
159 | listStyleType: {},
160 |
161 | // -------------------------------------------------------------------------
162 | // Background
163 | // -------------------------------------------------------------------------
164 |
165 | background: {
166 | transformers: [
167 | colorLookupTransformer,
168 | imageLookupTransformer,
169 | gradientLookupTransformer,
170 | imageLookupTransformer,
171 | gradientTransformer([colorLookupTransformer, ...lengthTransformers]),
172 | ],
173 | },
174 |
175 | backgroundAttachment: {},
176 |
177 | backgroundClip: {},
178 |
179 | backgroundColor: {
180 | transformers: colorLookupTransformer,
181 | },
182 |
183 | backgroundImage: {
184 | transformers: [
185 | gradientLookupTransformer,
186 | imageLookupTransformer,
187 | imageLookupTransformer,
188 | gradientTransformer([colorLookupTransformer, ...lengthTransformers]),
189 | ],
190 | },
191 |
192 | backgroundOrigin: {},
193 |
194 | backgroundPosition: {
195 | transformers: lengthTransformers,
196 | },
197 |
198 | backgroundRepeat: {},
199 |
200 | backgroundSize: {
201 | transformers: lengthTransformers,
202 | },
203 |
204 | // -------------------------------------------------------------------------
205 | // Color / Visibility
206 | // -------------------------------------------------------------------------
207 |
208 | opacity: {
209 | transformers: percentageStringToRatioTransformer,
210 | },
211 | color: {
212 | transformers: colorLookupTransformer,
213 | },
214 | visibility: {},
215 |
216 | // -------------------------------------------------------------------------
217 | // Layout
218 | // -------------------------------------------------------------------------
219 |
220 | display: {},
221 | float: {},
222 | clear: {},
223 | position: {},
224 | directions: {
225 | transformers: lengthTransformers,
226 | },
227 | width: {
228 | transformers: lengthTransformers,
229 | },
230 | height: {
231 | transformers: lengthTransformers,
232 | },
233 |
234 | // -------------------------------------------------------------------------
235 | // Flexbox
236 | // -------------------------------------------------------------------------
237 |
238 | flex: {
239 | transformers: lengthTransformers,
240 | },
241 | flexDirection: {},
242 | justifyContent: {},
243 | alignItems: {},
244 | alignContent: {},
245 | alignSelf: {},
246 | flexBasis: {
247 | transformers: lengthTransformers,
248 | },
249 | flexShrink: {}, // Doesn't support values
250 | flexGrow: {}, // Doesn't support values
251 | flexWrap: {},
252 | order: {},
253 |
254 | // -------------------------------------------------------------------------
255 | // Columns
256 | // -------------------------------------------------------------------------
257 |
258 | columnCount: {},
259 |
260 | columnWidth: {
261 | transformers: lengthTransformers,
262 | },
263 |
264 | columnGap: {
265 | transformers: lengthTransformers,
266 | },
267 |
268 | columnRuleWidth: {
269 | transformers: lengthTransformers,
270 | },
271 |
272 | // -------------------------------------------------------------------------
273 | // Tables
274 | // -------------------------------------------------------------------------
275 |
276 | verticalAlign: {},
277 |
278 | // -------------------------------------------------------------------------
279 | // Misc
280 | // -------------------------------------------------------------------------
281 |
282 | zIndex: {},
283 | zoom: {},
284 | overflow: {},
285 | boxShadow: {
286 | transformers: [
287 | boxShadowLookupTransformer,
288 | colorLookupTransformer,
289 | lengthTransformers,
290 | ],
291 | },
292 | cursor: {},
293 | hyphens: {},
294 |
295 | // -------------------------------------------------------------------------
296 | // Transforms
297 | // -------------------------------------------------------------------------
298 |
299 | transform: { transformers: transformTransformer(lengthTransformers) },
300 | transformBox: {},
301 | transformOrigin: {
302 | transformers: lengthTransformers,
303 | },
304 |
305 | // -------------------------------------------------------------------------
306 | // SVG
307 | // -------------------------------------------------------------------------
308 |
309 | fill: {
310 | transformers: [
311 | colorLookupTransformer,
312 | gradientLookupTransformer,
313 | gradientTransformer([colorLookupTransformer, ...lengthTransformers]),
314 | ],
315 | },
316 |
317 | stroke: {
318 | transformers: [
319 | colorLookupTransformer,
320 | gradientLookupTransformer,
321 | gradientTransformer([colorLookupTransformer, ...lengthTransformers]),
322 | ],
323 | },
324 |
325 | stopColor: {
326 | transformers: [colorLookupTransformer],
327 | },
328 |
329 | // -------------------------------------------------------------------------
330 | // Helpers
331 | // -------------------------------------------------------------------------
332 |
333 | paddingH: {
334 | transformers: lengthTransformers,
335 | renderer: renderDualProps([`paddingLeft`, `paddingRight`]),
336 | },
337 |
338 | paddingV: {
339 | transformers: lengthTransformers,
340 | renderer: renderDualProps([`paddingTop`, `paddingBottom`]),
341 | },
342 |
343 | marginH: {
344 | transformers: lengthTransformers,
345 | renderer: renderDualProps([`marginRight`, `marginLeft`]),
346 | },
347 |
348 | marginV: {
349 | transformers: lengthTransformers,
350 | renderer: renderDualProps([`marginTop`, `marginBottom`]),
351 | },
352 |
353 | borderH: {
354 | transformers: [
355 | borderLookupTransformer,
356 | lengthTransformers,
357 | colorLookupTransformer,
358 | ],
359 | renderer: renderDualFromOneProps([`borderLeft`, `borderRight`]),
360 | },
361 |
362 | borderV: {
363 | transformers: [
364 | borderLookupTransformer,
365 | lengthTransformers,
366 | colorLookupTransformer,
367 | ],
368 | renderer: renderDualFromOneProps([`borderTop`, `borderBottom`]),
369 | },
370 |
371 | offset: {
372 | transformers: lengthTransformers,
373 | renderer: renderDirectionProps,
374 | },
375 |
376 | offsetV: {
377 | transformers: lengthTransformers,
378 | renderer: renderDualProps(DIRECTIONS_LIST_VERTICAL),
379 | },
380 |
381 | offsetH: {
382 | transformers: lengthTransformers,
383 | renderer: renderDualProps(DIRECTIONS_LIST_HORIZONTAL),
384 | },
385 |
386 | borderTopRadius: {
387 | transformers: lengthTransformers,
388 | renderer: renderDualProps([
389 | `borderTopLeftRadius`,
390 | `borderTopRightRadius`,
391 | ]),
392 | },
393 |
394 | borderRightRadius: {
395 | transformers: lengthTransformers,
396 | renderer: renderDualProps([
397 | `borderTopRightRadius`,
398 | `borderBottomRightRadius`,
399 | ]),
400 | },
401 |
402 | borderBottomRadius: {
403 | transformers: lengthTransformers,
404 | renderer: renderDualProps([
405 | `borderBottomRightRadius`,
406 | `borderBottomLeftRadius`,
407 | ]),
408 | },
409 |
410 | borderLeftRadius: {
411 | transformers: lengthTransformers,
412 | renderer: renderDualProps([
413 | `borderBottomLeftRadius`,
414 | `borderTopLeftRadius`,
415 | ]),
416 | },
417 |
418 | baseline: {
419 | transformers: baselineTransformer([
420 | scaleLookupTransformer,
421 | lengthTransformers,
422 | ]),
423 | renderer: renderBaseline,
424 | },
425 | },
426 | }
427 |
428 | export default defaultConfig
429 |
--------------------------------------------------------------------------------
/src/const/breakpointMapping.js:
--------------------------------------------------------------------------------
1 | const BREAKPOINT_MAPPING_FIELDS = Object.freeze({
2 | NAME: `name`,
3 | VALUE: `value`,
4 | QUERY: `query`,
5 | })
6 |
7 | export default BREAKPOINT_MAPPING_FIELDS
8 |
--------------------------------------------------------------------------------
/src/const/breakpoints.js:
--------------------------------------------------------------------------------
1 | export const DEFAULT_BREAKPOINT_NAME = `default`
2 |
3 | export const LT_MODIFIER = `<`
4 |
5 | export const GT_MODIFIER = `>`
6 |
7 | export const AT_MODIFIER = `@`
8 |
9 | export const MODIFIERS = [LT_MODIFIER, GT_MODIFIER, AT_MODIFIER]
10 |
11 | export const POSITIVE_OFFSET = `+`
12 |
13 | export const NEGATIVE_OFFSET = `-`
14 |
15 | export const OFFSETS = [POSITIVE_OFFSET, NEGATIVE_OFFSET]
16 |
--------------------------------------------------------------------------------
/src/const/config.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next- import/prefer-default-export
2 | export default Object.freeze({
3 | DATA: `data`,
4 | SCOPES: `scopes`,
5 | RESOLVE: `resolve`,
6 | BREAKPOINTS: `breakpoints`,
7 | PROPERTIES: `properties`,
8 | TRANSFORMERS: `transformers`,
9 | ALIASES: `aliases`,
10 | })
11 |
--------------------------------------------------------------------------------
/src/const/errors.js:
--------------------------------------------------------------------------------
1 | export const ERROR_PREFIX = `[cssapi]`
2 | export const DATA_PREFIX = `(config.data)`
3 | export const BREAKPOINTS_PREFIX = `(config.breakpoints)`
4 | export const MQ_PREFIX = `api.mq()`
5 | export const API_PREFIX = `api()`
6 | export const MIXIN_PREFIX = `mixin()`
7 | export const PARSE_PREFIX = `(parsing)`
8 |
--------------------------------------------------------------------------------
/src/const/expanders.js:
--------------------------------------------------------------------------------
1 | import { values } from 'ramda'
2 |
3 | export const DIRECTIONS = Object.freeze({
4 | TOP: `top`,
5 | RIGHT: `right`,
6 | BOTTOM: `bottom`,
7 | LEFT: `left`,
8 | })
9 |
10 | export const CORNERS = Object.freeze({
11 | TOP_RIGHT: `topRight`,
12 | BOTTOM_RIGHT: `bottomRight`,
13 | BOTTOM_LEFT: `bottomLeft`,
14 | TOP_LEFT: `topLeft`,
15 | })
16 |
17 | export const DIRECTIONS_LIST = values(DIRECTIONS)
18 |
19 | export const CORNERS_LIST = values(CORNERS)
20 |
21 | export const DIRECTIONS_LIST_HORIZONTAL = [DIRECTIONS.LEFT, DIRECTIONS.RIGHT]
22 |
23 | export const DIRECTIONS_LIST_VERTICAL = [DIRECTIONS.TOP, DIRECTIONS.BOTTOM]
24 |
25 | export const MIN_MAX = {
26 | MIN: `min`,
27 | MAX: `max`,
28 | }
29 |
30 | export const MIN_MAX_LIST = values(MIN_MAX)
31 |
32 | export const AXES = {
33 | X: `x`,
34 | Y: `y`,
35 | }
36 |
37 | export const AXES_LIST = values(AXES)
38 |
--------------------------------------------------------------------------------
/src/const/queryDescriptor.js:
--------------------------------------------------------------------------------
1 | const QUERY_DESCRIPTOR_FIELDS = Object.freeze({
2 | FROM: `from`,
3 | TO: `to`,
4 | })
5 |
6 | export default QUERY_DESCRIPTOR_FIELDS
7 |
--------------------------------------------------------------------------------
/src/const/rangeItem.js:
--------------------------------------------------------------------------------
1 | const RANGE_FIELDS = Object.freeze({
2 | OFFSET: `offset`,
3 | MODIFIER: `modifier`,
4 | })
5 |
6 | export default RANGE_FIELDS
7 |
--------------------------------------------------------------------------------
/src/const/regexp.js:
--------------------------------------------------------------------------------
1 | export const RE_COLOR = /^(#?([0-9a-f]{3}|[0-9a-f]{6})|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d.]+%?\))$/i
2 |
3 | export const RE_RHYTHM_UNITS = /^-?\d*\.?\d*ru$/
4 |
5 | export const RE_START_OF_LINE = /^/gm
6 |
7 | export const RE_WHITESPACE = /\s+/
8 |
9 | export const RE_PERCENT_NUMBER = /^\d+%$/
10 |
11 | export const RE_CAPITAL_LETTERS = /([A-Z])/g
12 |
13 | export const RE_MEDIA_QUERY_STRING = /^@media /
14 |
15 | export const RE_RADIAL_GRADIENT = /^radial-gradient\(.+\)$/
16 |
17 | export const RE_LINEAR_GRADIENT = /^linear-gradient\(.+\)$/
18 |
19 | export const RE_URL = /^url\(.+\)$/
20 |
21 | export const RE_UNNESTED_COMMA = /,(?![^()]*(?:\([^()]*\))?\))/g
22 |
23 | export const RE_ARGUMENTS_OF_CSS_FUNCTION = /^(?:[a-zA-Z\-_\d]*)\((.*)\)$/g
24 |
25 | export const RE_CSS_FUNCTION_NAME = /^([a-zA-Z-]*)\(.*\)$/
26 |
27 | export const RE_UNNESTED_WHITESPACE = /\s(?![^()]*\))/g
28 |
29 | export const RE_COLOR_NAME = /^c:(.*)$/
30 |
31 | // Note we exclude values starting `data:` as they might be images
32 | export const RE_TOKEN = /^(?!(?:data|http|https):)([a-zA-Z\-_\d]*):(.*)$/
33 |
34 | export const RE_CSS_FUNCTION = /^[a-zA-Z-]*\(.*\)$/
35 |
36 | export const RE_TRANSFORM_TRANSLATE_FUNCTION = /^translate(?:x|y|z|3d)?\(.*\)$/
37 |
38 | export const RE_CALC_FUNCTION = /^calc\(.*\)$/
39 |
40 | export const RE_CALC_VALUES = /(-*[0-9a-z.%]+)/g
41 |
42 | export const RE_MODIFIED_MQ = /^(?![<@][^<\s]*<)(?!^]?[a-zA-Z\d]+(?:(?:[+-])\d*\.?\d*(?:em)?)?)(?:<[a-zA-Z\d]+(?:(?:\+|-)\d*\.?\d*(?:em)?)?)?$/
43 |
--------------------------------------------------------------------------------
/src/const/scope.js:
--------------------------------------------------------------------------------
1 | const SCOPE_FIELDS = Object.freeze({
2 | SCOPE: `scope`,
3 | })
4 |
5 | export default SCOPE_FIELDS
6 |
--------------------------------------------------------------------------------
/src/const/templates.js:
--------------------------------------------------------------------------------
1 | export const QUERY_MIN_TEMPLATE = `@media (min-width: #{minWidth})`
2 |
3 | export const QUERY_MAX_TEMPLATE = `@media (max-width: #{maxWidth})`
4 |
5 | export const QUERY_MIN_MAX_TEMPLATE = `@media (min-width: #{minWidth}) and (max-width: #{maxWidth})`
6 |
7 | export const QUERY_TEMPLATE = `#{query} {
8 | #{value}
9 | }`
10 |
11 | export const DECLARATION_TEMPLATE = `#{name}: #{value};`
12 |
13 | export const CSS_FUNCTION_TEMPLATE = `#{typeOfFunction}(#{value})`
14 |
--------------------------------------------------------------------------------
/src/const/units.js:
--------------------------------------------------------------------------------
1 | export const LENGTH_UNITS = Object.freeze({
2 | REM: `rem`,
3 | PX: `px`,
4 | EM: `em`,
5 | })
6 |
7 | export const ANGLE_UNITS = Object.freeze({
8 | DEG: `deg`,
9 | RAD: `rad`,
10 | GRAD: `grad`,
11 | TURN: `turn`,
12 | })
13 |
14 | export const PERCENT_UNIT = `%`
15 |
--------------------------------------------------------------------------------
/src/cssapi.js:
--------------------------------------------------------------------------------
1 | import { pipe } from 'ramda'
2 | import createApi from './build/createApi'
3 | import createDeclarationProcessors from './build/declarations/createDeclarationProcessors'
4 | import ensureBreakpointProvider from './build/ensureBreakpointProvider'
5 | import expandData from './build/expansion/expandData'
6 | import expandProperties from './build/expansion/expandProperties'
7 | import mergeWithDefaultConfig from './build/mergeWithDefaultConfig'
8 |
9 | // -----------------------------------------------------------------------------
10 | // Exports
11 | // -----------------------------------------------------------------------------
12 |
13 | const api = config =>
14 | pipe(
15 | mergeWithDefaultConfig,
16 | expandData,
17 | expandProperties,
18 | ensureBreakpointProvider,
19 | createDeclarationProcessors,
20 | createApi(config)
21 | )(config)
22 |
23 | export default api
24 |
--------------------------------------------------------------------------------
/src/errors.js:
--------------------------------------------------------------------------------
1 | import { compose } from 'ramda'
2 | import { appendFlipped } from 'ramda-adjunct'
3 | import {
4 | API_PREFIX,
5 | BREAKPOINTS_PREFIX,
6 | DATA_PREFIX,
7 | ERROR_PREFIX,
8 | MIXIN_PREFIX,
9 | MQ_PREFIX,
10 | PARSE_PREFIX,
11 | } from './const/errors'
12 | import {
13 | joinWithSpace,
14 | printObj,
15 | wrapWithSingleQuotes,
16 | } from './utils/formatting'
17 | import { whenIsUndefined } from './utils/logic'
18 |
19 | // -----------------------------------------------------------------------------
20 | // Utils
21 | // -----------------------------------------------------------------------------
22 |
23 | export const throwLibError = message => {
24 | throw new Error(joinWithSpace([ERROR_PREFIX, message]))
25 | }
26 |
27 | const throwPrefixedError = prefix =>
28 | compose(throwLibError, joinWithSpace, appendFlipped([prefix]))
29 |
30 | export const throwWhenUndefined = error =>
31 | whenIsUndefined(() => {
32 | throw error
33 | })
34 |
35 | // -----------------------------------------------------------------------------
36 | // Prefixed Errors
37 | // -----------------------------------------------------------------------------
38 |
39 | export const throwDataError = throwPrefixedError(DATA_PREFIX)
40 | export const throwBreakpointError = throwPrefixedError(BREAKPOINTS_PREFIX)
41 | export const throwMQError = throwPrefixedError(MQ_PREFIX)
42 | export const throwAPIError = throwPrefixedError(API_PREFIX)
43 | export const throwMixinError = throwPrefixedError(MIXIN_PREFIX)
44 | export const throwParseError = throwPrefixedError(PARSE_PREFIX)
45 |
46 | // -----------------------------------------------------------------------------
47 | // Messages
48 | // -----------------------------------------------------------------------------
49 |
50 | export const noBreakpointAtIndexError = idx =>
51 | `Couldn't resolve breakpoint at index ${idx}`
52 |
53 | export const noBreakpointWithNameError = name =>
54 | `Couldn't resolve breakpoint with name ${wrapWithSingleQuotes(name)}`
55 |
56 | export const invalidBreakpointSyntaxError = name =>
57 | `The syntax you used to describe your breakpoint range was invalid for ${wrapWithSingleQuotes(
58 | name
59 | )}`
60 |
61 | export const invalidBreakpointError = (message, args) =>
62 | `${message} with args: ${printObj(args)}`
63 |
64 | export const missingDataNodeError = name =>
65 | `There is no data node defined named ${wrapWithSingleQuotes(name)}`
66 |
67 | export const missingDataItemKeyError = (nodeName, keyName) =>
68 | `No item has been defined for data.${nodeName} named ${wrapWithSingleQuotes(
69 | keyName
70 | )}`
71 |
72 | export const unrecognisedDataPrefixError = (prefix, validPrefixes) =>
73 | `Unrecognised prefix encountered: ${wrapWithSingleQuotes(
74 | prefix
75 | )}. Available prefixes are: ${printObj(validPrefixes)}`
76 |
77 | export const unsupportedBreakpointValuesError = declarations =>
78 | `When using the mq() helper you must supply only a single decaration value but you supplied: ${printObj(
79 | declarations
80 | )}`
81 |
82 | export const invalidPropertyError = name =>
83 | `API doesn't support a property named ${wrapWithSingleQuotes(name)}`
84 |
85 | export const noThemeObjectError = () =>
86 | `There was no theme object available on the props object`
87 |
88 | export const noAPIOnThemeError = value =>
89 | `There was no api function defined on the theme object. Value was: ${printObj(
90 | value
91 | )}`
92 |
93 | export const unitedNumberError = value =>
94 | `Supplied value was not a number with a unit: '${wrapWithSingleQuotes(
95 | value
96 | )}'`
97 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import cssapi from './cssapi'
2 |
3 | export default cssapi
4 |
5 | export { default as api } from './themeHelpers/api'
6 | export { default as mixin } from './themeHelpers/mixin'
7 | export { default as mq } from './themeHelpers/mq'
8 | export { scope as s, scope } from './utils/scope'
9 |
--------------------------------------------------------------------------------
/src/objects/breakpointDescriptor.js:
--------------------------------------------------------------------------------
1 | import { curry } from 'ramda'
2 | import { ensureArray } from 'ramda-adjunct'
3 |
4 | const createBreakpointDescriptor = curry((name, range) => ({
5 | name,
6 | range: ensureArray(range),
7 | }))
8 |
9 | export default createBreakpointDescriptor
10 |
--------------------------------------------------------------------------------
/src/objects/breakpointMapping.js:
--------------------------------------------------------------------------------
1 | import { assoc, curry, lensProp, prop } from 'ramda'
2 | import FIELD_NAMES from '../const/breakpointMapping'
3 |
4 | const { NAME, QUERY, VALUE } = FIELD_NAMES
5 |
6 | export const propName = prop(NAME)
7 | export const propValue = prop(VALUE)
8 | export const propQuery = prop(QUERY)
9 |
10 | export const lName = lensProp(NAME)
11 | export const lQuery = lensProp(QUERY)
12 |
13 | export const assocValue = assoc(VALUE)
14 | export const assocQuery = assoc(QUERY)
15 |
16 | export const createBreakpointMapping = curry((name, value, query) => ({
17 | name,
18 | query,
19 | value,
20 | }))
21 |
--------------------------------------------------------------------------------
/src/objects/config.js:
--------------------------------------------------------------------------------
1 | import { lensPath, lensProp, prop } from 'ramda'
2 | import FIELD_NAMES from '../const/config'
3 |
4 | const {
5 | PROPERTIES,
6 | SCOPES,
7 | DATA,
8 | TRANSFORMERS,
9 | BREAKPOINTS,
10 | RESOLVE,
11 | } = FIELD_NAMES
12 |
13 | export const propData = prop(DATA)
14 | export const lDataScopes = lensPath([DATA, SCOPES])
15 | export const lData = lensProp(DATA)
16 | export const propScopes = prop(SCOPES)
17 | export const lProperties = lensProp(PROPERTIES)
18 | export const lTransformers = lensProp(TRANSFORMERS)
19 | export const lBreakpoints = lensProp(BREAKPOINTS)
20 | export const lResolve = lensProp(RESOLVE)
21 |
--------------------------------------------------------------------------------
/src/objects/queryDescriptor.js:
--------------------------------------------------------------------------------
1 | import { anyPass, isNil, reject } from 'ramda'
2 | import { isZero } from '../utils/predicate'
3 |
4 | const createQueryDescriptor = reject(anyPass([isNil, isZero]))
5 |
6 | export default createQueryDescriptor
7 |
--------------------------------------------------------------------------------
/src/objects/rangeItem.js:
--------------------------------------------------------------------------------
1 | import { complement, has, prop } from 'ramda'
2 | import FIELD_NAMES from '../const/rangeItem'
3 |
4 | const { OFFSET, MODIFIER } = FIELD_NAMES
5 |
6 | export const propOffset = prop(OFFSET)
7 |
8 | export const propModifier = prop(MODIFIER)
9 |
10 | export const hasModifier = has(MODIFIER)
11 |
12 | export const hasNoModifier = complement(hasModifier)
13 |
--------------------------------------------------------------------------------
/src/objects/scope.js:
--------------------------------------------------------------------------------
1 | import { has, objOf, prop } from 'ramda'
2 | import FIELDS from '../const/scope'
3 |
4 | const { SCOPE } = FIELDS
5 |
6 | export const createScope = objOf(SCOPE)
7 |
8 | export const propScope = prop(SCOPE)
9 |
10 | export const hasScope = has(SCOPE)
11 |
--------------------------------------------------------------------------------
/src/resolvers/resolveKeyToObjectValue.js:
--------------------------------------------------------------------------------
1 | import { pipe, prop } from 'ramda'
2 | import { missingDataItemKeyError, throwDataError } from '../errors'
3 | import resolveKeyToValue from '../resolvers/resolveKeyToValue'
4 | import { whenIsUndefined } from '../utils/logic'
5 |
6 | const resolveKeyToObjectValue = dataPropName => (
7 | propName,
8 | data,
9 | breakpointName
10 | ) =>
11 | pipe(
12 | resolveKeyToValue(dataPropName),
13 | prop(propName),
14 | whenIsUndefined(() =>
15 | throwDataError(missingDataItemKeyError(dataPropName, propName))
16 | )
17 | )(propName, data, breakpointName)
18 |
19 | export default resolveKeyToObjectValue
20 |
--------------------------------------------------------------------------------
/src/resolvers/resolveKeyToValue.js:
--------------------------------------------------------------------------------
1 | import {
2 | always,
3 | append,
4 | cond,
5 | contains,
6 | defaultTo,
7 | either,
8 | filter,
9 | isNil,
10 | map,
11 | pipe,
12 | prop,
13 | reduce,
14 | reduced,
15 | reverse,
16 | T,
17 | when,
18 | } from 'ramda'
19 | import { isObject, lensSatisfies, mergeRight } from 'ramda-adjunct'
20 | import { DEFAULT_BREAKPOINT_NAME } from '../const/breakpoints'
21 | import { missingDataNodeError, throwDataError } from '../errors'
22 | import { lResolve, propData, propScopes } from '../objects/config'
23 | import { whenIsUndefined } from '../utils/logic'
24 | import { hasScopes, isNotDefaultBreakpoint } from '../utils/predicate'
25 |
26 | const filterResolvingScopes = breakpointName =>
27 | filter(lensSatisfies(contains(breakpointName), lResolve))
28 |
29 | // Find any scopes that resolve to this breakpoint, reversing them so that the
30 | // final scope is first, adding the default scope in last position.
31 | const findResolvingScopes = (breakpointName, data) =>
32 | pipe(
33 | propScopes,
34 | filterResolvingScopes(breakpointName),
35 | map(propData),
36 | reverse,
37 | append(data)
38 | )(data)
39 |
40 | // Merge any props that don't already exist on the object
41 | const resolveObject = acc => when(always(isObject(acc)), mergeRight(acc))
42 |
43 | const findPropOnScope = dataPropName => (acc, scopeData) =>
44 | pipe(
45 | prop(dataPropName),
46 | cond([[isNil, always(acc)], [isObject, resolveObject(acc)], [T, reduced]])
47 | )(scopeData)
48 |
49 | const findPropsOnScope = dataPropName =>
50 | reduce(findPropOnScope(dataPropName), null)
51 |
52 | // Find any matching prop on this dataNode
53 | const resolveScopes = (data, dataPropName) => breakpointName =>
54 | pipe(findResolvingScopes, findPropsOnScope(dataPropName))(
55 | breakpointName,
56 | data
57 | )
58 |
59 | const shouldResolveScopes = data =>
60 | either(isNotDefaultBreakpoint, () => hasScopes(data))
61 |
62 | const findDataProp = (breakpointName, dataPropName) => data =>
63 | pipe(
64 | // Try using scopes
65 | when(shouldResolveScopes(data), resolveScopes(data, dataPropName)),
66 | // Fall back to root
67 | defaultTo(prop(dataPropName, data))
68 | )(breakpointName)
69 |
70 | const resolveKeyToValue = dataPropName => (
71 | value,
72 | data,
73 | breakpointName = DEFAULT_BREAKPOINT_NAME
74 | ) =>
75 | pipe(
76 | findDataProp(breakpointName, dataPropName),
77 | whenIsUndefined(() => throwDataError(missingDataNodeError(dataPropName)))
78 | )(data)
79 |
80 | export default resolveKeyToValue
81 |
--------------------------------------------------------------------------------
/src/resolvers/resolveKeysToObjectValues.js:
--------------------------------------------------------------------------------
1 | import { map } from 'ramda'
2 | import resolveKeyToObjectValue from './resolveKeyToObjectValue'
3 |
4 | const resolveKeysToValues = (name, keys) => (data, breakpointName) =>
5 | map(key => resolveKeyToObjectValue(name)(key, data, breakpointName), keys)
6 |
7 | export default resolveKeysToValues
8 |
--------------------------------------------------------------------------------
/src/resolvers/resolveKeysToValues.js:
--------------------------------------------------------------------------------
1 | import { map } from 'ramda'
2 | import resolveKeyToValue from './resolveKeyToValue'
3 |
4 | const resolveKeysToValues = keys => (value, data, breakpointName) =>
5 | map(key => resolveKeyToValue(key)(value, data, breakpointName), keys)
6 |
7 | export default resolveKeysToValues
8 |
--------------------------------------------------------------------------------
/src/themeHelpers/api.js:
--------------------------------------------------------------------------------
1 | import { apply, flip, pipe } from 'ramda'
2 | import { throwAPIError } from '../errors'
3 | import getApiFromProps from './getApiFromProps'
4 |
5 | const api = declarations =>
6 | pipe(getApiFromProps(throwAPIError), flip(apply)([declarations]))
7 |
8 | export default api
9 |
--------------------------------------------------------------------------------
/src/themeHelpers/getApiFromProps.js:
--------------------------------------------------------------------------------
1 | import { pipe, prop, unless } from 'ramda'
2 | import { isFunction, isPlainObj } from 'ramda-adjunct'
3 | import { noAPIOnThemeError, noThemeObjectError } from '../errors'
4 |
5 | const getApiFromProps = errorFunc => props =>
6 | pipe(
7 | prop(`theme`),
8 | unless(isPlainObj, value => errorFunc(noThemeObjectError(value))),
9 | prop(`api`),
10 | unless(isFunction, value => errorFunc(noAPIOnThemeError(value)))
11 | )(props)
12 |
13 | export default getApiFromProps
14 |
--------------------------------------------------------------------------------
/src/themeHelpers/mixin.js:
--------------------------------------------------------------------------------
1 | import { apply, flip, pair, pipe } from 'ramda'
2 | import { throwMixinError } from '../errors'
3 | import getApiFromProps from './getApiFromProps'
4 |
5 | const mixin = f => (...args) => props =>
6 | pipe(
7 | getApiFromProps(throwMixinError),
8 | flip(pair)(props),
9 | apply(f),
10 | flip(apply)(args)
11 | )(props)
12 |
13 | export default mixin
14 |
--------------------------------------------------------------------------------
/src/themeHelpers/mq.js:
--------------------------------------------------------------------------------
1 | import { apply, flip, pipe, prop } from 'ramda'
2 | import { throwAPIError } from '../errors'
3 | import getApiFromProps from './getApiFromProps'
4 |
5 | const propMq = prop(`mq`)
6 |
7 | const api = (breakpoint, declarations) =>
8 | pipe(
9 | getApiFromProps(throwAPIError),
10 | propMq,
11 | flip(apply)([breakpoint, declarations])
12 | )
13 |
14 | export default api
15 |
--------------------------------------------------------------------------------
/src/transformers/calcTransformer.js:
--------------------------------------------------------------------------------
1 | import { isCalcFunction } from '../utils/predicate'
2 | import { transformCalcElements, transformValue } from '../utils/transformers'
3 | import transformer from './transformer'
4 |
5 | const calcTransformer = transformers =>
6 | transformer(isCalcFunction, (value, data, breakpointName) => {
7 | const r = transformCalcElements(
8 | transformValue(transformers, data, breakpointName)
9 | )(value)
10 | return r
11 | })
12 | export default calcTransformer
13 |
--------------------------------------------------------------------------------
/src/transformers/composite/allPartsTransformer.js:
--------------------------------------------------------------------------------
1 | import { transformValues } from '../../utils/transformers'
2 |
3 | const allPartsTransformer = transformers => (value, data, breakpointName) =>
4 | transformValues(transformers, data, breakpointName, value)
5 |
6 | export default allPartsTransformer
7 |
--------------------------------------------------------------------------------
/src/transformers/composite/baselineTransformer.js:
--------------------------------------------------------------------------------
1 | import { multiply, pipe, T, unless } from 'ramda'
2 | import { isArray } from 'ramda-adjunct'
3 | import resolveKeysToObjectValues from '../../resolvers/resolveKeysToObjectValues'
4 | import resolveKeyToValue from '../../resolvers/resolveKeyToValue'
5 | import { linesForFontsize } from '../../utils/baseline'
6 | import { unitedDimensionToUnitlessPixelValue } from '../../utils/converters'
7 | import { splitOnUnnestedWhitespace } from '../../utils/formatting'
8 | import { whenIsUndefined } from '../../utils/logic'
9 | import { transformValue } from '../../utils/transformers'
10 | import transformer from '../transformer'
11 |
12 | const PROP = `baseline`
13 | const KEYS = [`lineHeight`, `minLeading`, `allowHalfLines`]
14 |
15 | const rhythmUnitsToRemsTransformer = fontSizeToLengthTransformer =>
16 | transformer(T, (value, data, breakpointName) => {
17 | const [lineHeight, minLeading, allowHalfLines] = resolveKeysToObjectValues(
18 | PROP,
19 | KEYS
20 | )(data, breakpointName)
21 |
22 | const baseFontSize = resolveKeyToValue(`baseFontSize`)(
23 | value,
24 | data,
25 | breakpointName
26 | )
27 |
28 | const [fontSize, lines] = unless(isArray, splitOnUnnestedWhitespace)(value)
29 |
30 | const transformedFontSize = transformValue(
31 | fontSizeToLengthTransformer,
32 | data,
33 | breakpointName,
34 | fontSize
35 | )
36 | const fontSizeUnitlessPx = unitedDimensionToUnitlessPixelValue(
37 | transformedFontSize,
38 | baseFontSize
39 | )
40 |
41 | const transformedLines = pipe(
42 | whenIsUndefined(() =>
43 | linesForFontsize(
44 | minLeading,
45 | allowHalfLines,
46 | lineHeight,
47 | fontSizeUnitlessPx
48 | )
49 | ),
50 | multiply(lineHeight),
51 | transformValue(fontSizeToLengthTransformer, data, breakpointName)
52 | )(lines)
53 |
54 | return [transformedFontSize, transformedLines]
55 | })
56 |
57 | export default rhythmUnitsToRemsTransformer
58 |
--------------------------------------------------------------------------------
/src/transformers/composite/partPositionTransformer.js:
--------------------------------------------------------------------------------
1 | import { transformValueAt } from '../../utils/transformers'
2 |
3 | const partPositionTransformer = position => transformers => (
4 | value,
5 | data,
6 | breakpointName
7 | ) => transformValueAt(transformers, data, breakpointName, position)(value)
8 |
9 | export default partPositionTransformer
10 |
--------------------------------------------------------------------------------
/src/transformers/composite/transformPartsWith.js:
--------------------------------------------------------------------------------
1 | import { pipe } from 'ramda'
2 | import { transformValues } from '../../utils/transformers'
3 |
4 | const transformPartsWith = transformers => (value, data, breakpointName) =>
5 | pipe(transformValues(transformers, data, breakpointName))(value)
6 |
7 | export default transformPartsWith
8 |
--------------------------------------------------------------------------------
/src/transformers/dataLookupTransformer.js:
--------------------------------------------------------------------------------
1 | import { when } from 'ramda'
2 | import resolveKeyToObjectValue from '../resolvers/resolveKeyToObjectValue'
3 | import { nameAndAliasesForNodeName } from '../utils/data'
4 | import { tokenName } from '../utils/parse'
5 | import { tokenMatches } from '../utils/predicate'
6 |
7 | // Lookup the value of an object, for example `color.primary`
8 | const dataLookupTransformer = nodeName => (value, data, breakpointName) => {
9 | const nameAndAliases = nameAndAliasesForNodeName(data.aliases, nodeName)
10 |
11 | return when(tokenMatches(nameAndAliases), () => {
12 | const name = tokenName(value)
13 | return resolveKeyToObjectValue(nodeName)(name, data, breakpointName)
14 | })(value)
15 | }
16 |
17 | export default dataLookupTransformer
18 |
--------------------------------------------------------------------------------
/src/transformers/factory/dataLookupTransformers.js:
--------------------------------------------------------------------------------
1 | import { assoc, pipe, reduce, __ } from 'ramda'
2 | import dataLookupTransformer from '../dataLookupTransformer'
3 |
4 | const DATA_NODE_NAMES = [
5 | `color`,
6 | `gradient`,
7 | `font`,
8 | `scale`,
9 | `boxShadow`,
10 | `image`,
11 | `border`,
12 | ]
13 |
14 | export const {
15 | colorLookupTransformer,
16 | gradientLookupTransformer,
17 | fontLookupTransformer,
18 | scaleLookupTransformer,
19 | boxShadowLookupTransformer,
20 | imageLookupTransformer,
21 | borderLookupTransformer,
22 | } = reduce(
23 | (acc, value) =>
24 | pipe(dataLookupTransformer, assoc(`${value}LookupTransformer`, __, acc))(
25 | value
26 | ),
27 | {}
28 | )(DATA_NODE_NAMES)
29 |
--------------------------------------------------------------------------------
/src/transformers/gradientTransformer.js:
--------------------------------------------------------------------------------
1 | import { isGradient } from '../utils/predicate'
2 | import {
3 | transformFunctionElements,
4 | transformValue,
5 | } from '../utils/transformers'
6 | import transformPartsWith from './composite/transformPartsWith'
7 | import transformer from './transformer'
8 |
9 | const impl = transformers => (value, data, breakpointName) =>
10 | transformFunctionElements(
11 | transformValue(transformPartsWith(transformers), data, breakpointName)
12 | )(value)
13 |
14 | const gradientTransformer = transformers => (value, data, breakpointName) =>
15 | transformer(isGradient, impl(transformers))(value, data, breakpointName)
16 |
17 | export default gradientTransformer
18 |
--------------------------------------------------------------------------------
/src/transformers/lengthToEmsTransformer.js:
--------------------------------------------------------------------------------
1 | import rhythmUnitsToEmsTransformer from '../transformers/rhythmUnitsToEmsTransformer'
2 | import unitlessNumberToEmsTransformer from './unitlessNumberToEmsTransformer'
3 |
4 | const lengthToRemsTransformer = [
5 | unitlessNumberToEmsTransformer,
6 | rhythmUnitsToEmsTransformer,
7 | ]
8 |
9 | export default lengthToRemsTransformer
10 |
--------------------------------------------------------------------------------
/src/transformers/lengthToRemsTransformer.js:
--------------------------------------------------------------------------------
1 | import unitlessNumberToRemsTransformer from './unitlessNumberToRemsTransformer'
2 | import rhythmUnitsToRemsTransformer from '../transformers/rhythmUnitsToRemsTransformer'
3 |
4 | const lengthToRemsTransformer = [
5 | unitlessNumberToRemsTransformer,
6 | rhythmUnitsToRemsTransformer,
7 | ]
8 |
9 | export default lengthToRemsTransformer
10 |
--------------------------------------------------------------------------------
/src/transformers/lengthTransformers.js:
--------------------------------------------------------------------------------
1 | import rhythmUnitsToLengthTransformer from '../transformers/rhythmUnitsToLengthTransformer'
2 | import calcTransformer from './calcTransformer'
3 | import unitlessNumberToLengthTransformer from './unitlessNumberToLengthTransformer'
4 |
5 | const lengthTransformers = [
6 | calcTransformer([
7 | unitlessNumberToLengthTransformer,
8 | rhythmUnitsToLengthTransformer,
9 | ]),
10 | unitlessNumberToLengthTransformer,
11 | rhythmUnitsToLengthTransformer,
12 | ]
13 |
14 | export default lengthTransformers
15 |
--------------------------------------------------------------------------------
/src/transformers/percentageStringToRatioTransformer.js:
--------------------------------------------------------------------------------
1 | import { isPercentString } from '../utils/predicate'
2 | import { percentageStringToRatio } from '../utils/converters'
3 | import transformer from './transformer'
4 |
5 | const percentageStringToRatioTransformer = transformer(
6 | isPercentString,
7 | percentageStringToRatio
8 | )
9 |
10 | export default percentageStringToRatioTransformer
11 |
--------------------------------------------------------------------------------
/src/transformers/ratioToPercentageStringTransformer.js:
--------------------------------------------------------------------------------
1 | import { isValidPositiveNumber } from '../utils/predicate'
2 | import { ratioToPercentString } from '../utils/converters'
3 | import transformer from './transformer'
4 |
5 | const ratioToPercentageStringTransformer = transformer(
6 | isValidPositiveNumber,
7 | ratioToPercentString
8 | )
9 |
10 | export default ratioToPercentageStringTransformer
11 |
--------------------------------------------------------------------------------
/src/transformers/rhythmUnitsToEmsTransformer.js:
--------------------------------------------------------------------------------
1 | import { LENGTH_UNITS } from '../const/units'
2 | import resolveKeyToValue from '../resolvers/resolveKeyToValue'
3 | import { mulitplyUnitlessNumbersToDistance } from '../utils/converters'
4 | import { isRhythmUnit } from '../utils/predicate'
5 | import transformer from './transformer'
6 |
7 | const rhythmUnitsToRemsTransformer = transformer(
8 | isRhythmUnit,
9 | (value, data, breakpointName) => {
10 | const rhythm = resolveKeyToValue(`rhythm`)(value, data, breakpointName)
11 | return mulitplyUnitlessNumbersToDistance(
12 | rhythm,
13 | data.baseFontSize,
14 | LENGTH_UNITS.REM
15 | )(value)
16 | }
17 | )
18 |
19 | export default rhythmUnitsToRemsTransformer
20 |
--------------------------------------------------------------------------------
/src/transformers/rhythmUnitsToLengthTransformer.js:
--------------------------------------------------------------------------------
1 | import resolveKeysToValues from '../resolvers/resolveKeysToValues'
2 | import { mulitplyUnitlessNumbersToDistance } from '../utils/converters'
3 | import { isRhythmUnit } from '../utils/predicate'
4 | import transformer from './transformer'
5 |
6 | const rhythmUnitsToRemsTransformer = transformer(
7 | isRhythmUnit,
8 | (value, data, breakpointName) => {
9 | const [rhythm, lengthUnit, baseFontSize] = resolveKeysToValues([
10 | `rhythm`,
11 | `lengthUnit`,
12 | `baseFontSize`,
13 | ])(value, data, breakpointName)
14 |
15 | return mulitplyUnitlessNumbersToDistance(rhythm, lengthUnit, baseFontSize)(
16 | value
17 | )
18 | }
19 | )
20 |
21 | export default rhythmUnitsToRemsTransformer
22 |
--------------------------------------------------------------------------------
/src/transformers/rhythmUnitsToRemsTransformer.js:
--------------------------------------------------------------------------------
1 | import { LENGTH_UNITS } from '../const/units'
2 | import resolveKeyToValue from '../resolvers/resolveKeyToValue'
3 | import { mulitplyUnitlessNumbersToDistance } from '../utils/converters'
4 | import { isRhythmUnit } from '../utils/predicate'
5 | import transformer from './transformer'
6 |
7 | const rhythmUnitsToRemsTransformer = transformer(
8 | isRhythmUnit,
9 | (value, data, breakpointName) => {
10 | const rhythm = resolveKeyToValue(`rhythm`)(value, data, breakpointName)
11 | return mulitplyUnitlessNumbersToDistance(
12 | rhythm,
13 | data.baseFontSize,
14 | LENGTH_UNITS.REM
15 | )(value)
16 | }
17 | )
18 |
19 | export default rhythmUnitsToRemsTransformer
20 |
--------------------------------------------------------------------------------
/src/transformers/rootPxToEmTransformer.js:
--------------------------------------------------------------------------------
1 | import { transformValue } from '../utils/transformers'
2 | import lengthToEmsTransformer from './lengthToEmsTransformer'
3 |
4 | // the base font size will always be 16 for media queries
5 | const rootPxToEmTransformer = transformValue(
6 | lengthToEmsTransformer,
7 | { baseFontSize: 16 },
8 | null
9 | )
10 |
11 | export default rootPxToEmTransformer
12 |
--------------------------------------------------------------------------------
/src/transformers/transformTransformer.js:
--------------------------------------------------------------------------------
1 | import { isTransformTranslateFunction } from '../utils/predicate'
2 | import {
3 | transformFunctionElements,
4 | transformValue,
5 | } from '../utils/transformers'
6 | import transformPartsWith from './composite/transformPartsWith'
7 | import transformer from './transformer'
8 |
9 | const impl = transformers => (value, data, breakpointName) =>
10 | transformFunctionElements(
11 | transformValue(transformPartsWith(transformers), data, breakpointName)
12 | )(value)
13 |
14 | const transformTransformer = transformers => (value, data, breakpointName) =>
15 | transformer(isTransformTranslateFunction, impl(transformers))(
16 | value,
17 | data,
18 | breakpointName
19 | )
20 |
21 | export default transformTransformer
22 |
--------------------------------------------------------------------------------
/src/transformers/transformer.js:
--------------------------------------------------------------------------------
1 | import { when } from 'ramda'
2 | import { transformValue } from '../utils/transformers'
3 |
4 | const transformer = (predicate, transformers) => (
5 | value,
6 | data,
7 | breakpointName
8 | ) =>
9 | when(predicate, () =>
10 | transformValue(transformers, data, breakpointName, value)
11 | )(value)
12 |
13 | export default transformer
14 |
--------------------------------------------------------------------------------
/src/transformers/unitlessNumberToEmsTransformer.js:
--------------------------------------------------------------------------------
1 | import { LENGTH_UNITS } from '../const/units'
2 | import transformer from './transformer'
3 | import { isValidNonZeroNumber } from '../utils/predicate'
4 | import { unitlessNumberToDistance } from '../utils/converters'
5 |
6 | const unitlessNumberToRemsTransformer = transformer(
7 | isValidNonZeroNumber,
8 | (value, data) =>
9 | unitlessNumberToDistance(LENGTH_UNITS.EM, data.baseFontSize)(value)
10 | )
11 | export default unitlessNumberToRemsTransformer
12 |
--------------------------------------------------------------------------------
/src/transformers/unitlessNumberToLengthTransformer.js:
--------------------------------------------------------------------------------
1 | import keysToValueResolver from '../resolvers/resolveKeysToValues'
2 | import { unitlessNumberToDistance } from '../utils/converters'
3 | import { isValidNonZeroNumber } from '../utils/predicate'
4 | import transformer from './transformer'
5 |
6 | const unitlessNumberToLengthTransformer = transformer(
7 | isValidNonZeroNumber,
8 | (value, data, breakpointName) => {
9 | const [lengthUnit, baseFontSize] = keysToValueResolver([
10 | `lengthUnit`,
11 | `baseFontSize`,
12 | ])(value, data, breakpointName)
13 | return unitlessNumberToDistance(lengthUnit, baseFontSize)(value)
14 | }
15 | )
16 |
17 | export default unitlessNumberToLengthTransformer
18 |
--------------------------------------------------------------------------------
/src/transformers/unitlessNumberToPxTransformer.js:
--------------------------------------------------------------------------------
1 | import { LENGTH_UNITS } from '../const/units'
2 | import transformer from './transformer'
3 | import { isValidNonZeroNumber } from '../utils/predicate'
4 | import { unitlessNumberToDistance } from '../utils/converters'
5 |
6 | const unitlessNumberToRemsTransformer = transformer(
7 | isValidNonZeroNumber,
8 | (value, data) =>
9 | unitlessNumberToDistance(LENGTH_UNITS.PX, data.baseFontSize)(value)
10 | )
11 |
12 | export default unitlessNumberToRemsTransformer
13 |
--------------------------------------------------------------------------------
/src/transformers/unitlessNumberToRemsTransformer.js:
--------------------------------------------------------------------------------
1 | import { LENGTH_UNITS } from '../const/units'
2 | import transformer from './transformer'
3 | import { isValidNonZeroNumber } from '../utils/predicate'
4 | import { unitlessNumberToDistance } from '../utils/converters'
5 |
6 | const unitlessNumberToRemsTransformer = transformer(
7 | isValidNonZeroNumber,
8 | (value, data) =>
9 | unitlessNumberToDistance(LENGTH_UNITS.REM, data.baseFontSize)(value)
10 | )
11 |
12 | export default unitlessNumberToRemsTransformer
13 |
--------------------------------------------------------------------------------
/src/utils/baseline.js:
--------------------------------------------------------------------------------
1 | import { isTrue } from 'ramda-adjunct'
2 | import { divideBy2 } from './numbers'
3 |
4 | const wholeLinesForFontSize = (minLeading, fontSize, baselineHeight) => {
5 | const lines = Math.ceil(fontSize / baselineHeight)
6 | return lines * baselineHeight - fontSize >= minLeading ? lines : lines + 1
7 | }
8 |
9 | // eslint-disable-next-line import/prefer-default-export
10 | export const linesForFontsize = (
11 | minLeading,
12 | allowHalfLines,
13 | baselineHeight,
14 | fontSize
15 | ) =>
16 | isTrue(allowHalfLines)
17 | ? divideBy2(
18 | wholeLinesForFontSize(minLeading, fontSize, divideBy2(baselineHeight))
19 | )
20 | : wholeLinesForFontSize(minLeading, fontSize, baselineHeight)
21 |
--------------------------------------------------------------------------------
/src/utils/breakpointMap.js:
--------------------------------------------------------------------------------
1 | import {
2 | curry,
3 | dec,
4 | equals,
5 | find,
6 | findIndex,
7 | flip,
8 | gt,
9 | identity,
10 | inc,
11 | last,
12 | lensIndex,
13 | nth,
14 | pipe,
15 | prop,
16 | useWith,
17 | } from 'ramda'
18 | import { lensSatisfies } from 'ramda-adjunct'
19 | import {
20 | noBreakpointAtIndexError,
21 | noBreakpointWithNameError,
22 | throwWhenUndefined,
23 | } from '../errors'
24 |
25 | export const findBreakpointByName = curry((breakpointMap, name) =>
26 | pipe(
27 | find(lensSatisfies(equals(name), lensIndex(0))),
28 | throwWhenUndefined(noBreakpointWithNameError(name)),
29 | last
30 | )(breakpointMap)
31 | )
32 |
33 | export const findBreakpointIndex = (breakpointMap, name) =>
34 | findIndex(lensSatisfies(equals(name), lensIndex(0)), breakpointMap)
35 |
36 | export const findBreakpointByIndex = curry((breakpointMap, idx) =>
37 | pipe(
38 | flip(nth)(breakpointMap),
39 | throwWhenUndefined(noBreakpointAtIndexError(idx))
40 | )(idx)
41 | )
42 |
43 | export const findNextBreakpointByIndex = curry((breakpointMap, idx) =>
44 | pipe(inc, findBreakpointByIndex(breakpointMap))(idx)
45 | )
46 |
47 | export const isNotLastBreakpoint = useWith(gt, [
48 | pipe(prop(`length`), dec),
49 | identity,
50 | ])
51 |
--------------------------------------------------------------------------------
/src/utils/breakpoints.js:
--------------------------------------------------------------------------------
1 | import {
2 | apply,
3 | identity,
4 | map,
5 | pipe,
6 | prepend,
7 | split,
8 | unless,
9 | useWith,
10 | } from 'ramda'
11 | import { list } from 'ramda-adjunct'
12 | import { DEFAULT_BREAKPOINT_NAME, LT_MODIFIER } from '../const/breakpoints'
13 | import { invalidBreakpointSyntaxError } from '../errors'
14 | import createBreakpointDescriptor from '../objects/breakpointDescriptor'
15 | import rootPxToEmTransformer from '../transformers/rootPxToEmTransformer'
16 | import { isEmString, isRange, isValidModifiedMq } from './predicate'
17 | import { createRangeItem } from './range'
18 |
19 | export const addDefaultBreakpoint = prepend([DEFAULT_BREAKPOINT_NAME, 0])
20 |
21 | export const parseBreakpoint = value => {
22 | if (!isValidModifiedMq(value)) {
23 | throw Error(invalidBreakpointSyntaxError(value))
24 | }
25 |
26 | // A range will always have a < in a position greater than 0
27 | if (!isRange(value)) {
28 | // It's a single item
29 | const rangeItem = createRangeItem(value)
30 | return createBreakpointDescriptor(value, rangeItem)
31 | }
32 |
33 | // Otherwise it's a range (containing two items)
34 | return pipe(
35 | split(LT_MODIFIER),
36 | map(createRangeItem),
37 | createBreakpointDescriptor(value)
38 | )(value)
39 | }
40 |
41 | export const breakpointValuesToEms = map(
42 | apply(useWith(list, [identity, unless(isEmString, rootPxToEmTransformer)]))
43 | )
44 |
--------------------------------------------------------------------------------
/src/utils/converters.js:
--------------------------------------------------------------------------------
1 | import {
2 | compose,
3 | cond,
4 | curry,
5 | divide,
6 | equals,
7 | flip,
8 | multiply,
9 | pipe,
10 | } from 'ramda'
11 | import { concatRight, isNumber } from 'ramda-adjunct'
12 | import { LENGTH_UNITS, PERCENT_UNIT } from '../const/units'
13 | import { joinWithNoSpace } from './formatting'
14 | import { divideBy } from './numbers'
15 | import {
16 | elementsOfUnitedNumberString,
17 | numericPartOfUnitedNumberString,
18 | } from './parse'
19 | import { isUnitRemOrEm } from './predicate'
20 |
21 | const { PX, REM, EM } = LENGTH_UNITS
22 |
23 | export const pxValueToRemOrEmValue = (value, baseFontSize) =>
24 | divide(value, baseFontSize)
25 |
26 | export const pxValueToRemOrEmString = (unit, baseFontSize, value) =>
27 | joinWithNoSpace([flip(pxValueToRemOrEmValue)(baseFontSize)(value), unit])
28 |
29 | export const pxValueToPxString = value => joinWithNoSpace([value, PX])
30 |
31 | export const percentageStringToRatio = compose(
32 | divideBy(100),
33 | numericPartOfUnitedNumberString
34 | )
35 |
36 | export const ratioToPercentString = compose(
37 | concatRight(PERCENT_UNIT),
38 | String,
39 | multiply(100)
40 | )
41 |
42 | export const unitlessNumberToDistance = (unit, baseFontSize) => value =>
43 | cond([
44 | [equals(PX), () => pxValueToPxString(value)],
45 | [equals(REM), () => pxValueToRemOrEmString(REM, baseFontSize, value)],
46 | [equals(EM), () => pxValueToRemOrEmString(EM, baseFontSize, value)],
47 | ])(unit)
48 |
49 | export const mulitplyUnitlessNumbersToDistance = (factor, unit, baseFontSize) =>
50 | pipe(
51 | numericPartOfUnitedNumberString,
52 | multiply(factor),
53 | unitlessNumberToDistance(unit, baseFontSize)
54 | )
55 |
56 | export const remOrEmToPxValue = (value, baseFontSize) =>
57 | multiply(value, baseFontSize)
58 |
59 | export const unitedDimensionToUnitlessPixelValue = (value, baseFontSize) => {
60 | const [number, unit] = elementsOfUnitedNumberString(value)
61 | return isUnitRemOrEm(unit) ? remOrEmToPxValue(number, baseFontSize) : number
62 | }
63 |
64 | export const adjustNumberWithUnit = curry((f, value) => {
65 | // In case a unitless number is passed in
66 | if (isNumber(value)) return f(value)
67 | // Otherwise calculate using the numeric part and reattach the unit
68 | const [n, unit] = elementsOfUnitedNumberString(value)
69 | return `${f(n)}${unit}`
70 | })
71 |
--------------------------------------------------------------------------------
/src/utils/data.js:
--------------------------------------------------------------------------------
1 | import { converge, equals, identity, pipe, prepend } from 'ramda'
2 | import { filterKeys } from '../utils/objects'
3 |
4 | const aliasesForNodeName = aliases => name => filterKeys(equals(name))(aliases)
5 |
6 | // eslint-disable-next-line import/prefer-default-export
7 | export const nameAndAliasesForNodeName = (aliases, name) =>
8 | pipe(converge(prepend, [identity, aliasesForNodeName(aliases)]))(name)
9 |
--------------------------------------------------------------------------------
/src/utils/declarations.js:
--------------------------------------------------------------------------------
1 | import {
2 | always,
3 | append,
4 | assoc,
5 | assocPath,
6 | both,
7 | converge,
8 | curry,
9 | equals,
10 | findIndex,
11 | flip,
12 | gte,
13 | ifElse,
14 | init,
15 | last,
16 | lensIndex,
17 | objOf,
18 | omit,
19 | over,
20 | pipe,
21 | reduce,
22 | unless,
23 | __,
24 | } from 'ramda'
25 | import { concatRight, isNotNil, lensEq } from 'ramda-adjunct'
26 | import BREAKPOINT_MAPPING_FIELDS from '../const/breakpointMapping'
27 | import QUERY_DESCRIPTOR_FIELDS from '../const/queryDescriptor'
28 | import { throwMQError, unsupportedBreakpointValuesError } from '../errors'
29 | import {
30 | createBreakpointMapping,
31 | lName,
32 | lQuery,
33 | propValue,
34 | } from '../objects/breakpointMapping'
35 | import { reduceObjIndexed } from '../utils/objects'
36 | import { isValidMqValue } from '../utils/predicate'
37 |
38 | const { QUERY } = BREAKPOINT_MAPPING_FIELDS
39 |
40 | const { TO } = QUERY_DESCRIPTOR_FIELDS
41 |
42 | const foundMatch = flip(gte)(0)
43 |
44 | const findBatchIndexForMapping = ({ name, query }, batches) =>
45 | findIndex(both(lensEq(lName, name), lensEq(lQuery, query)), batches)
46 |
47 | const createNewBatch = (breakpointMapping, batches) =>
48 | append(breakpointMapping, batches)
49 |
50 | const addToBatch = ({ name, query, value }) =>
51 | pipe(propValue, concatRight(value), createBreakpointMapping(name, __, query))
52 |
53 | const addToBatchAtIndex = curry((breakpointMapping, batches, index) =>
54 | over(lensIndex(index), addToBatch(breakpointMapping), batches)
55 | )
56 |
57 | export const batchDeclarations = reduce(
58 | (batches, breakpointMapping) =>
59 | pipe(
60 | findBatchIndexForMapping,
61 | ifElse(foundMatch, addToBatchAtIndex(breakpointMapping, batches), () =>
62 | createNewBatch(breakpointMapping, batches)
63 | )
64 | )(breakpointMapping, batches),
65 | []
66 | )
67 |
68 | const addBreakpoingToDeclaration = breakpoint => (acc, [name, value]) =>
69 | pipe(
70 | unless(isValidMqValue, () =>
71 | throwMQError(unsupportedBreakpointValuesError(value))
72 | ),
73 | objOf(breakpoint),
74 | assoc(name, __, acc)
75 | )(value)
76 |
77 | export const addBreakpointToDeclarations = (breakpoint, declarations) =>
78 | reduceObjIndexed(addBreakpoingToDeclaration(breakpoint), {}, declarations)
79 |
80 | const assocPathQueryTo = assocPath([QUERY, TO])
81 |
82 | const compareMappings = (previous, current) =>
83 | current.value !== `_` && (!previous || !equals(previous.value, current.value))
84 |
85 | const replaceToValue = newToValue =>
86 | ifElse(
87 | always(isNotNil(newToValue)),
88 | assocPathQueryTo(newToValue),
89 | over(lQuery, omit([TO]))
90 | )
91 |
92 | const updateQuery = newToValue => pipe(last, replaceToValue(newToValue))
93 |
94 | const updateLastMapping = (acc, newToValue) =>
95 | converge(append, [updateQuery(newToValue), init])(acc)
96 |
97 | // This is gross
98 | export const optimiseDeclarations = mappings => {
99 | let lastMapping
100 | return reduce(
101 | (acc, mapping) => {
102 | let result
103 | const shouldAdd = compareMappings(lastMapping, mapping)
104 | if (shouldAdd) {
105 | lastMapping = mapping
106 | result = append(mapping, acc)
107 | } else {
108 | result = updateLastMapping(acc, mapping.query.to)
109 | lastMapping = last(result)
110 | }
111 | return result
112 | },
113 | [],
114 | mappings
115 | )
116 | }
117 |
--------------------------------------------------------------------------------
/src/utils/expanders.js:
--------------------------------------------------------------------------------
1 | import {
2 | assoc,
3 | compose,
4 | identity,
5 | mergeDeepRight,
6 | objOf,
7 | over,
8 | pipe,
9 | reduce,
10 | __,
11 | } from 'ramda'
12 | import { list } from 'ramda-adjunct'
13 | import { lTransformers } from '../objects/config'
14 |
15 | export const toAppendedProps = (propName, style, affixedValues, toProp) =>
16 | reduce(
17 | (acc, direction) =>
18 | compose(assoc(__, style, acc), toProp, list)(propName, direction),
19 | {},
20 | affixedValues
21 | )
22 |
23 | export const expandMainProp = (propName, style, wrapper = identity) =>
24 | assoc(propName, over(lTransformers, wrapper, style))
25 |
26 | export const expandSubProps = (toProp, suffixes, wrapper = identity) => (
27 | propName,
28 | style
29 | ) =>
30 | mergeDeepRight(
31 | toAppendedProps(
32 | propName,
33 | over(lTransformers, wrapper, style),
34 | suffixes,
35 | toProp
36 | )
37 | )
38 |
39 | export const wrapWithTransform = wrapper => (propName, style) =>
40 | pipe(over(lTransformers, wrapper), objOf(propName))(style)
41 |
--------------------------------------------------------------------------------
/src/utils/formatting.js:
--------------------------------------------------------------------------------
1 | import dasherize from 'dasherize'
2 | import {
3 | append,
4 | assoc,
5 | compose,
6 | converge,
7 | curry,
8 | flatten,
9 | head,
10 | inc,
11 | insert,
12 | join,
13 | lensIndex,
14 | map,
15 | nth,
16 | over,
17 | pipe,
18 | prepend,
19 | replace,
20 | reverse,
21 | split,
22 | tail,
23 | toUpper,
24 | trim,
25 | when,
26 | } from 'ramda'
27 | import {
28 | compact,
29 | isArray,
30 | isPlainObj,
31 | lengthGt,
32 | list,
33 | reduceIndexed,
34 | } from 'ramda-adjunct'
35 | import {
36 | RE_ARGUMENTS_OF_CSS_FUNCTION,
37 | RE_CAPITAL_LETTERS,
38 | RE_START_OF_LINE,
39 | RE_UNNESTED_COMMA,
40 | RE_UNNESTED_WHITESPACE,
41 | RE_WHITESPACE,
42 | } from '../const/regexp'
43 | import { condDefault } from './functions'
44 | import { reduceObjIndexed } from './objects'
45 |
46 | // -----------------------------------------------------------------------------
47 | // Chars
48 | // -----------------------------------------------------------------------------
49 |
50 | const NEWLINE = `\n`
51 | const DOUBLE_NEWLINE = `${NEWLINE}${NEWLINE}`
52 | const SPACE = ` `
53 | const COMMA = `,`
54 | const COLON = `:`
55 | const HYPHEN = `-`
56 | const FULL_STOP = `.`
57 | const SINGLE_QUOTE = `'`
58 | const PIPE = `|`
59 | const COMMA_SPACE = `${COMMA}${SPACE}`
60 |
61 | // -----------------------------------------------------------------------------
62 | // Create
63 | // -----------------------------------------------------------------------------
64 |
65 | const toToken = v => new RegExp(`#{(${v})}`, `g`)
66 |
67 | // -----------------------------------------------------------------------------
68 | // Print
69 | // -----------------------------------------------------------------------------
70 |
71 | export const printObj = JSON.stringify
72 |
73 | // -----------------------------------------------------------------------------
74 | // Indent
75 | // -----------------------------------------------------------------------------
76 |
77 | export const indentLines = replace(RE_START_OF_LINE, ` `)
78 |
79 | // -----------------------------------------------------------------------------
80 | // Join
81 | // -----------------------------------------------------------------------------
82 |
83 | export const joinWithSpace = join(SPACE)
84 |
85 | export const joinWithComma = join(COMMA)
86 |
87 | export const joinWithCommaSpace = join(COMMA_SPACE)
88 |
89 | export const joinWithHypen = join(HYPHEN)
90 |
91 | export const joinWithNoSpace = join(``)
92 |
93 | export const joinWithDot = join(FULL_STOP)
94 |
95 | export const joinWithNewline = join(NEWLINE)
96 |
97 | export const joinWithPipe = join(PIPE)
98 |
99 | export const joinWithDoubleNewlines = join(DOUBLE_NEWLINE)
100 |
101 | // -----------------------------------------------------------------------------
102 | // Case
103 | // -----------------------------------------------------------------------------
104 |
105 | export const firstToUpper = compose(
106 | joinWithNoSpace,
107 | over(lensIndex(0), toUpper)
108 | )
109 |
110 | export const toKebabCase = dasherize
111 |
112 | // -----------------------------------------------------------------------------
113 | // Split
114 | // -----------------------------------------------------------------------------
115 |
116 | export const splitOnWhitespace = split(RE_WHITESPACE)
117 |
118 | export const splitOnUnnestedWhitespace = split(RE_UNNESTED_WHITESPACE)
119 |
120 | export const splitOnUnnestedComma = split(RE_UNNESTED_COMMA)
121 |
122 | export const splitOnComma = split(COMMA)
123 |
124 | export const splitCamelcase = compose(
125 | split(` `),
126 | replace(RE_CAPITAL_LETTERS, ` $1`)
127 | )
128 |
129 | export const splitOnColon = split(COLON)
130 |
131 | // -----------------------------------------------------------------------------
132 | // Wrap
133 | // -----------------------------------------------------------------------------
134 |
135 | export const wrapWith = (a, b = a) =>
136 | compose(joinWithNoSpace, prepend(a), append(b), String)
137 |
138 | export const wrapWithSingleQuotes = wrapWith(SINGLE_QUOTE)
139 |
140 | // -----------------------------------------------------------------------------
141 | // Replace
142 | // -----------------------------------------------------------------------------
143 |
144 | export const replaceToken = curry((template, tokenName, value) =>
145 | replace(toToken(tokenName), value, template)
146 | )
147 |
148 | const replaceWithMap = curry((template, valueMap) =>
149 | reduceObjIndexed(
150 | (acc, [key, value]) => replaceToken(acc, key, value),
151 | template,
152 | valueMap
153 | )
154 | )
155 |
156 | const replaceWithArray = curry((template, values) =>
157 | pipe(
158 | reduceIndexed((acc, v, idx) => assoc(inc(idx), v, acc), {}),
159 | replaceWithMap(template)
160 | )(values)
161 | )
162 |
163 | export const replaceTokens = curry((template, value) =>
164 | condDefault([
165 | [isArray, replaceWithArray(template)],
166 | [isPlainObj, replaceWithMap(template)],
167 | ])(value)
168 | )
169 |
170 | // -----------------------------------------------------------------------------
171 | // Extract
172 | // -----------------------------------------------------------------------------
173 |
174 | export const extractFunctionArguments = value => {
175 | const parts = new RegExp(RE_ARGUMENTS_OF_CSS_FUNCTION).exec(value)
176 | return nth(1, parts)
177 | }
178 |
179 | // -----------------------------------------------------------------------------
180 | // Insert / Append / Prepend
181 | // -----------------------------------------------------------------------------
182 |
183 | export const appendSubToProp = compose(
184 | joinWithNoSpace,
185 | flatten,
186 | when(lengthGt(1), converge(list, [head, compose(map(firstToUpper), tail)])),
187 | compact
188 | )
189 |
190 | export const prependSubToProp = compose(appendSubToProp, reverse)
191 |
192 | export const insertSubIntoProp = compose(
193 | converge(compose(appendSubToProp, insert(1)), [
194 | head,
195 | compose(splitCamelcase, joinWithNoSpace, tail),
196 | ]),
197 | reverse
198 | )
199 |
200 | // -----------------------------------------------------------------------------
201 | // Misc
202 | // -----------------------------------------------------------------------------
203 |
204 | export const trimAll = map(trim)
205 |
--------------------------------------------------------------------------------
/src/utils/functions.js:
--------------------------------------------------------------------------------
1 | import { T, identity, cond, curry, append, defaultTo } from 'ramda'
2 |
3 | // eslint-disable-next-line import/prefer-default-export
4 | export const condDefault = curry((list, value) => {
5 | const listWithDefault = append([T, identity], list)
6 | return cond(listWithDefault)(value)
7 | })
8 |
9 | export const defaultToObj = defaultTo({})
10 |
11 | export const defaultToArray = defaultTo([])
12 |
--------------------------------------------------------------------------------
/src/utils/list.js:
--------------------------------------------------------------------------------
1 | import {
2 | compose,
3 | converge,
4 | curry,
5 | defaultTo,
6 | equals,
7 | flip,
8 | head,
9 | identity,
10 | keys,
11 | length,
12 | lensIndex,
13 | nth,
14 | reduce,
15 | } from 'ramda'
16 | import { isNotUndefined, lengthEq, lensSatisfies } from 'ramda-adjunct'
17 |
18 | export const lengthEq1 = lengthEq(1)
19 |
20 | export const headEquals = value => compose(equals(value), head)
21 |
22 | export const nthFlipped = flip(nth)
23 |
24 | export const nthOr = curry((defaultValue, n, a) =>
25 | defaultTo(defaultValue, nth(n, a))
26 | )
27 |
28 | export const reduceWithKeys = reducer =>
29 | converge(reduce(reducer), [identity, keys])
30 |
31 | export const hasIndex = curry((n, l) =>
32 | lensSatisfies(isNotUndefined, lensIndex(n))(l)
33 | )
34 |
35 | export const numKeys = compose(length, keys)
36 |
--------------------------------------------------------------------------------
/src/utils/logic.js:
--------------------------------------------------------------------------------
1 | import { when } from 'ramda'
2 | import { isUndefined } from 'ramda-adjunct'
3 |
4 | // eslint-disable-next-line import/prefer-default-export
5 | export const whenIsUndefined = f => when(isUndefined, () => f())
6 |
--------------------------------------------------------------------------------
/src/utils/numbers.js:
--------------------------------------------------------------------------------
1 | import { clamp, divide, flip } from 'ramda'
2 |
3 | export const divideBy = flip(divide)
4 |
5 | export const divideBy2 = flip(divide)(2)
6 |
7 | export const clampPositive = clamp(0, Number.POSITIVE_INFINITY)
8 |
--------------------------------------------------------------------------------
/src/utils/objects.js:
--------------------------------------------------------------------------------
1 | import {
2 | append,
3 | compose,
4 | curry,
5 | flip,
6 | prop,
7 | reduce,
8 | toPairs,
9 | when,
10 | } from 'ramda'
11 |
12 | export const reduceObjIndexed = curry((f, acc, v) =>
13 | compose(reduce(f, acc), toPairs)(v)
14 | )
15 |
16 | export const propFlipped = flip(prop)
17 |
18 | export const filterKeys = curry((predicate, obj) =>
19 | reduceObjIndexed(
20 | (keys, [key, value]) => when(() => predicate(value), append(key))(keys),
21 | [],
22 | obj
23 | )
24 | )
25 |
--------------------------------------------------------------------------------
/src/utils/parse.js:
--------------------------------------------------------------------------------
1 | import {
2 | any,
3 | compose,
4 | curry,
5 | equals,
6 | gt,
7 | isNil,
8 | last,
9 | lensIndex,
10 | pipe,
11 | unless,
12 | view,
13 | when,
14 | __,
15 | } from 'ramda'
16 | import { throwParseError, unitedNumberError } from '../errors'
17 | import { splitOnColon } from './formatting'
18 | import { clampPositive } from './numbers'
19 |
20 | export const elementsOfUnitedNumberString = value => {
21 | const captures = /^(-?\d+(?:.\d+)?)([a-z|%]+)?$/.exec(value)
22 | if (!captures || any(isNil, [captures, captures[1], captures[2]])) {
23 | throwParseError(unitedNumberError(value))
24 | }
25 | return [Number(captures[1]), captures[2]]
26 | }
27 |
28 | export const numericPartOfUnitedNumberString = compose(
29 | view(lensIndex(0)),
30 | elementsOfUnitedNumberString
31 | )
32 |
33 | export const unitPartOfUnitedNumberString = compose(
34 | view(lensIndex(1)),
35 | elementsOfUnitedNumberString
36 | )
37 |
38 | export const tokenName = pipe(splitOnColon, last)
39 |
40 | export const addEmValues = curry((emValue1, emValue2) => {
41 | const value1 = unless(equals(0), numericPartOfUnitedNumberString)(emValue1)
42 | const value2 = unless(equals(0), numericPartOfUnitedNumberString)(emValue2)
43 | const result = clampPositive(value1 + value2)
44 | return when(gt(__, 0), v => `${v}em`, result)
45 | })
46 |
--------------------------------------------------------------------------------
/src/utils/predicate.js:
--------------------------------------------------------------------------------
1 | import {
2 | anyPass,
3 | both,
4 | complement,
5 | compose,
6 | contains,
7 | curry,
8 | either,
9 | equals,
10 | flip,
11 | gt,
12 | has,
13 | indexOf,
14 | length,
15 | pipe,
16 | test,
17 | unless,
18 | values,
19 | __,
20 | } from 'ramda'
21 | import {
22 | ensureArray,
23 | isArray,
24 | isNotArray,
25 | isNotString,
26 | isPositive,
27 | isString,
28 | isValidNumber,
29 | } from 'ramda-adjunct'
30 | import {
31 | AT_MODIFIER,
32 | DEFAULT_BREAKPOINT_NAME,
33 | GT_MODIFIER,
34 | LT_MODIFIER,
35 | NEGATIVE_OFFSET,
36 | POSITIVE_OFFSET,
37 | } from '../const/breakpoints'
38 | import CONFIG_FIELD_NAMES from '../const/config'
39 | import {
40 | RE_CALC_FUNCTION,
41 | RE_CSS_FUNCTION,
42 | RE_LINEAR_GRADIENT,
43 | RE_MEDIA_QUERY_STRING,
44 | RE_MODIFIED_MQ,
45 | RE_PERCENT_NUMBER,
46 | RE_RADIAL_GRADIENT,
47 | RE_RHYTHM_UNITS,
48 | RE_TOKEN,
49 | RE_TRANSFORM_TRANSLATE_FUNCTION,
50 | RE_UNNESTED_COMMA,
51 | RE_UNNESTED_WHITESPACE,
52 | RE_URL,
53 | } from '../const/regexp'
54 | import { LENGTH_UNITS } from '../const/units'
55 | import { propModifier } from '../objects/rangeItem'
56 | import { joinWithPipe } from './formatting'
57 |
58 | const { SCOPES } = CONFIG_FIELD_NAMES
59 |
60 | // -----------------------------------------------------------------------------
61 | // List
62 | // -----------------------------------------------------------------------------
63 |
64 | export const isContained = flip(contains)
65 |
66 | export const isLengthGt = curry((l, v) => compose(gt(__, l), length)(v))
67 |
68 | export const isLengthEqualTo = curry((l, v) => compose(equals(l), length)(v))
69 |
70 | // -----------------------------------------------------------------------------
71 | // Misc
72 | // -----------------------------------------------------------------------------
73 |
74 | export const isSingletonArray = both(isArray, isLengthEqualTo(1))
75 |
76 | export const isDefaultBreakpoint = equals(DEFAULT_BREAKPOINT_NAME)
77 |
78 | export const isNotDefaultBreakpoint = complement(isDefaultBreakpoint)
79 |
80 | export const isValidMqValue = anyPass([
81 | isSingletonArray,
82 | isString,
83 | isValidNumber,
84 | ])
85 |
86 | // -----------------------------------------------------------------------------
87 | // Numeric
88 | // -----------------------------------------------------------------------------
89 |
90 | export const isZero = equals(0)
91 | export const isNotZero = complement(isZero)
92 |
93 | // -----------------------------------------------------------------------------
94 | // Types
95 | // -----------------------------------------------------------------------------
96 |
97 | export const isNumberWithUnit = curry((units, value) => {
98 | const possibleUnits = compose(joinWithPipe, ensureArray)(units)
99 | const regExp = new RegExp(`^-?\\d+(?:.\\d+)?(?:${possibleUnits})$`)
100 | return test(regExp, value)
101 | })
102 |
103 | export const isEmString = isNumberWithUnit([LENGTH_UNITS.EM])
104 |
105 | /* eslint-disable-next-line no-restricted-globals */
106 | export const isNumberString = both(isString, complement(isNaN))
107 |
108 | export const isNotZeroString = complement(equals)(`0`)
109 |
110 | export const isPercentString = pipe(
111 | unless(isString, String),
112 | test(RE_PERCENT_NUMBER)
113 | )
114 |
115 | export const isValidNonZeroNumber = pipe(
116 | unless(isString, String),
117 | both(isNumberString, isNotZeroString)
118 | )
119 |
120 | export const isValidPositiveNumber = both(isValidNumber, isPositive)
121 |
122 | export const isLength = isNumberWithUnit(values(LENGTH_UNITS))
123 |
124 | export const isRhythmUnit = test(RE_RHYTHM_UNITS)
125 |
126 | export const isNotStringOrArray = both(isNotString, isNotArray)
127 |
128 | export const isUrl = test(RE_URL)
129 |
130 | export const isLinearGradient = test(RE_LINEAR_GRADIENT)
131 |
132 | export const isRadialGradient = test(RE_RADIAL_GRADIENT)
133 |
134 | export const isGradient = either(isLinearGradient, isRadialGradient)
135 |
136 | export const isToken = test(RE_TOKEN)
137 |
138 | export const isCSSFunction = test(RE_CSS_FUNCTION)
139 |
140 | export const isTransformTranslateFunction = test(
141 | RE_TRANSFORM_TRANSLATE_FUNCTION
142 | )
143 |
144 | export const isCalcFunction = test(RE_CALC_FUNCTION)
145 |
146 | export const tokenMatches = names => value => {
147 | const possibleNames = compose(joinWithPipe, ensureArray)(names)
148 | const regExp = new RegExp(`^(${possibleNames}):(.*)$`)
149 | return test(regExp, value)
150 | }
151 |
152 | export const isMediaQueryString = test(RE_MEDIA_QUERY_STRING)
153 |
154 | export const isGroup = test(RE_UNNESTED_COMMA)
155 |
156 | export const isUnitRemOrEm = isContained([LENGTH_UNITS.EM, LENGTH_UNITS.REM])
157 |
158 | export const hasScopes = has(SCOPES)
159 |
160 | export const hasNoScopes = complement(hasScopes)
161 |
162 | export const hasUnnestedWhitespace = test(RE_UNNESTED_WHITESPACE)
163 |
164 | export const isValidModifiedMq = test(RE_MODIFIED_MQ)
165 |
166 | export const modifierIs = regExp => pipe(propModifier, equals(regExp))
167 |
168 | export const modifierIsLtModifier = modifierIs(LT_MODIFIER)
169 |
170 | export const modifierIsGtModifier = modifierIs(GT_MODIFIER)
171 |
172 | export const modifierIsAtModifier = modifierIs(AT_MODIFIER)
173 |
174 | export const hasPositiveOffset = contains(POSITIVE_OFFSET)
175 |
176 | export const hasNegativeOffset = contains(NEGATIVE_OFFSET)
177 |
178 | export const isGroupString = both(isString, isGroup)
179 |
180 | export const isRange = value => indexOf(LT_MODIFIER, value) > 1
181 |
--------------------------------------------------------------------------------
/src/utils/queryDescriptor.js:
--------------------------------------------------------------------------------
1 | import { head } from 'ramda'
2 | import createRangedQueryDescriptor from '../breakpoints/descriptors/createRangedQueryDescriptor'
3 | import createUnrangedQueryDescrptor from '../breakpoints/descriptors/createUnrangedQueryDescrptor'
4 | import { propQuery } from '../objects/breakpointMapping'
5 | import createQueryDescriptor from '../objects/queryDescriptor'
6 | import { lengthEq1 } from '../utils/list'
7 | import { applyOffsetToBreakpointValue } from '../utils/range'
8 |
9 | // -----------------------------------------------------------------------------
10 | // Query Header
11 | // -----------------------------------------------------------------------------
12 |
13 | // Use an array of mappings to decide which header to render
14 | export const calculateQueryDescriptor = (idx, mappedValues) => {
15 | const queryValue = propQuery(mappedValues[idx])
16 | let nextQueryValue
17 | if (idx < mappedValues.length - 1) {
18 | nextQueryValue = propQuery(mappedValues[idx + 1])
19 | }
20 | return createQueryDescriptor({ from: queryValue, to: nextQueryValue })
21 | }
22 |
23 | export const calculateQueryDescriptorForRange = breakpointMap => range => {
24 | const firstRangeItem = head(range)
25 | const firstItemValue = applyOffsetToBreakpointValue(firstRangeItem)
26 | const renderQuery = lengthEq1(range)
27 | ? createUnrangedQueryDescrptor(firstRangeItem)
28 | : createRangedQueryDescriptor
29 | return renderQuery(breakpointMap, firstRangeItem, firstItemValue, range)
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/range.js:
--------------------------------------------------------------------------------
1 | import {
2 | any,
3 | cond,
4 | equals,
5 | head,
6 | isNil,
7 | of,
8 | pipe,
9 | reject,
10 | split,
11 | T,
12 | tail,
13 | unless,
14 | } from 'ramda'
15 | import {
16 | MODIFIERS,
17 | NEGATIVE_OFFSET,
18 | POSITIVE_OFFSET,
19 | } from '../const/breakpoints'
20 | import { propValue } from '../objects/breakpointMapping'
21 | import { propOffset } from '../objects/rangeItem'
22 | import rootPxToEmTransformer from '../transformers/rootPxToEmTransformer'
23 | import { addEmValues } from './parse'
24 | import { hasNegativeOffset, hasPositiveOffset, isEmString } from './predicate'
25 |
26 | const extractPositiveOffset = split(POSITIVE_OFFSET)
27 |
28 | const extractNegativeOffset = v => {
29 | const [name, offset] = split(NEGATIVE_OFFSET, v)
30 | return [name, `-${offset}`]
31 | }
32 |
33 | const extractOffset = cond([
34 | [hasPositiveOffset, extractPositiveOffset],
35 | [hasNegativeOffset, extractNegativeOffset],
36 | [T, of],
37 | ])
38 |
39 | const firstCharIsModifier = value => any(equals(value[0]), MODIFIERS)
40 |
41 | // -----------------------------------------------------------------------------
42 | // Exports
43 | // -----------------------------------------------------------------------------
44 |
45 | export const createRangeItem = value => {
46 | const modifier = firstCharIsModifier(value) ? head(value) : null
47 | if (modifier) value = tail(value)
48 |
49 | const [name, offset] = extractOffset(value)
50 |
51 | const result = {
52 | name,
53 | offset,
54 | modifier,
55 | }
56 |
57 | return reject(isNil, result)
58 | }
59 |
60 | export const hasNoOffset = pipe(propOffset, isNil)
61 |
62 | export const applyOffset = rangeItem =>
63 | pipe(
64 | propOffset,
65 | unless(isEmString, rootPxToEmTransformer),
66 | addEmValues(rangeItem.value)
67 | )
68 |
69 | export const applyOffsetToBreakpointValue = rangeItem =>
70 | cond([[hasNoOffset, propValue], [T, applyOffset(rangeItem)]])(rangeItem)
71 |
--------------------------------------------------------------------------------
/src/utils/scope.js:
--------------------------------------------------------------------------------
1 | import { append, flatten, last, pipe, zip } from 'ramda'
2 | import { createScope } from '../objects/scope'
3 | import { joinWithNoSpace } from './formatting'
4 |
5 | // Note: When called as a template-literal, the argument to a function will be
6 | // wrapped in an array, so we need to extract it using head.
7 | // eslint-disable-next-line import/prefer-default-export
8 | export const scope = (strings, ...values) =>
9 | pipe(zip, append(last(strings)), flatten, joinWithNoSpace, createScope)(
10 | strings,
11 | values
12 | )
13 |
--------------------------------------------------------------------------------
/src/utils/templates.js:
--------------------------------------------------------------------------------
1 | import { curry, flip, pipe, subtract } from 'ramda'
2 | import {
3 | CSS_FUNCTION_TEMPLATE,
4 | DECLARATION_TEMPLATE,
5 | QUERY_MAX_TEMPLATE,
6 | QUERY_MIN_MAX_TEMPLATE,
7 | QUERY_MIN_TEMPLATE,
8 | QUERY_TEMPLATE,
9 | } from '../const/templates'
10 | import { adjustNumberWithUnit } from './converters'
11 | import { replaceToken, replaceTokens } from './formatting'
12 |
13 | const subtractMinimumEm = flip(subtract)(0.01)
14 |
15 | const reduceMaxWidthValue = adjustNumberWithUnit(subtractMinimumEm)
16 |
17 | export const createQueryMinHeaderFromTemplate = replaceToken(
18 | QUERY_MIN_TEMPLATE,
19 | `minWidth`
20 | )
21 |
22 | export const createQueryMaxHeaderFromTemplate = pipe(
23 | reduceMaxWidthValue,
24 | replaceToken(QUERY_MAX_TEMPLATE, `maxWidth`)
25 | )
26 |
27 | export const createQueryMinMaxHeaderFromTemplate = curry((maxWidth, minWidth) =>
28 | replaceTokens(QUERY_MIN_MAX_TEMPLATE, {
29 | minWidth,
30 | maxWidth: reduceMaxWidthValue(maxWidth),
31 | })
32 | )
33 |
34 | export const createQueryFromTemplate = replaceTokens(QUERY_TEMPLATE)
35 |
36 | export const createDeclarationFromTemplate = replaceTokens(DECLARATION_TEMPLATE)
37 |
38 | export const createCSSFunctionFromTemplate = typeOfFunction => value =>
39 | replaceTokens(CSS_FUNCTION_TEMPLATE, {
40 | typeOfFunction,
41 | value,
42 | })
43 |
--------------------------------------------------------------------------------
/src/utils/transformers.js:
--------------------------------------------------------------------------------
1 | import {
2 | adjust,
3 | curry,
4 | flatten,
5 | ifElse,
6 | map,
7 | match,
8 | nth,
9 | pipe,
10 | reduce,
11 | replace,
12 | trim,
13 | when,
14 | } from 'ramda'
15 | import { ensureArray, isString } from 'ramda-adjunct'
16 | import { RE_CALC_VALUES, RE_CSS_FUNCTION_NAME } from '../const/regexp'
17 | import {
18 | extractFunctionArguments,
19 | joinWithCommaSpace,
20 | joinWithSpace,
21 | splitOnUnnestedComma,
22 | splitOnUnnestedWhitespace,
23 | splitOnWhitespace,
24 | trimAll,
25 | } from './formatting'
26 | import { isGroupString, isNotStringOrArray } from './predicate'
27 | import { createCSSFunctionFromTemplate } from './templates'
28 |
29 | export const prepareForTransform = pipe(
30 | when(isNotStringOrArray, String),
31 | when(isString, splitOnUnnestedWhitespace)
32 | )
33 |
34 | // -----------------------------------------------------------------------------
35 | // CSS Function name()
36 | // -----------------------------------------------------------------------------
37 |
38 | const transformArgumentPart = transformers =>
39 | pipe(
40 | trim,
41 | splitOnWhitespace,
42 | map(pipe(ensureArray, transformers)),
43 | joinWithSpace
44 | )
45 |
46 | const transformArgumentParts = transformers =>
47 | map(transformArgumentPart(transformers))
48 |
49 | const transformFunctionArguments = transformers =>
50 | pipe(
51 | splitOnUnnestedComma,
52 | transformArgumentParts(transformers),
53 | joinWithCommaSpace
54 | )
55 |
56 | const typeOfFunction = pipe(match(RE_CSS_FUNCTION_NAME), nth(1))
57 |
58 | export const transformFunctionElements = curry((transformers, value) =>
59 | pipe(
60 | trim,
61 | extractFunctionArguments,
62 | transformFunctionArguments(transformers),
63 | createCSSFunctionFromTemplate(typeOfFunction(value))
64 | )(value)
65 | )
66 |
67 | export const transformCalcElements = transformers =>
68 | pipe(
69 | trim,
70 | extractFunctionArguments,
71 | replace(RE_CALC_VALUES, transformers),
72 | createCSSFunctionFromTemplate(`calc`)
73 | )
74 |
75 | // -----------------------------------------------------------------------------
76 | // Transform Basic Value - after we have parsed to smallest possible value
77 | // -----------------------------------------------------------------------------
78 |
79 | const prepareTransformers = pipe(ensureArray, flatten)
80 |
81 | const applyTransformers = (value, data, breakpointName) =>
82 | reduce(
83 | (currentValue, transformer) =>
84 | transformer(currentValue, data, breakpointName),
85 | value
86 | )
87 |
88 | export const transformValue = curry(
89 | (transformers, data, breakpointName, value) =>
90 | pipe(prepareTransformers, applyTransformers(value, data, breakpointName))(
91 | transformers
92 | )
93 | )
94 |
95 | export const transformValues = curry(
96 | (transformers, data, breakpointName, values) =>
97 | pipe(
98 | prepareForTransform,
99 | map(transformValue(transformers, data, breakpointName))
100 | )(values)
101 | )
102 |
103 | export const transformValueAt = curry(
104 | (transformers, data, breakpointName, position, value) =>
105 | pipe(
106 | prepareForTransform,
107 | adjust(transformValues(transformers, data, breakpointName), position)
108 | )(value)
109 | )
110 |
111 | // -----------------------------------------------------------------------------
112 | // Transform Group (values, separated, with, comma)
113 | // -----------------------------------------------------------------------------
114 |
115 | const transformGroupMember = (transformers, data, breakpointName) =>
116 | pipe(
117 | splitOnUnnestedWhitespace,
118 | transformValues(transformers, data, breakpointName),
119 | joinWithSpace
120 | )
121 |
122 | const transformGroupMembers = (transformers, data, breakpointName) =>
123 | map(transformGroupMember(transformers, data, breakpointName))
124 |
125 | export const transformGroup = (transformers, data, breakpointName) =>
126 | pipe(
127 | splitOnUnnestedComma,
128 | trimAll,
129 | transformGroupMembers(transformers, data, breakpointName),
130 | joinWithCommaSpace
131 | )
132 |
133 | export const transformDeclarationValue = (transformers, breakpointName, data) =>
134 | ifElse(
135 | isGroupString,
136 | transformGroup(transformers, data, breakpointName),
137 | transformValue(transformers, data, breakpointName)
138 | )
139 |
--------------------------------------------------------------------------------