├── .gitignore ├── .travis.yml ├── test ├── setup.js ├── index.test.js └── __snapshots__ │ └── index.test.js.snap ├── .babelrc ├── src └── index.js ├── rollup.config.js ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | dist/ 4 | lib/ 5 | node_modules/ 6 | *.log 7 | .idea 8 | packages/site/build 9 | package-lock.json 10 | .DS_Store 11 | .cache 12 | public/ 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | 6 | script: yarn test 7 | 8 | install: yarn 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov/bin/codecov 12 | 13 | cache: 14 | yarn: true 15 | directories: 16 | - node_modules 17 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import serializer from 'jest-glamor-react' 3 | import 'jest-styled-components' 4 | import { sheet } from 'emotion' 5 | import { parse, stringify } from 'css' 6 | 7 | expect.addSnapshotSerializer(serializer(sheet)) 8 | 9 | expect.addSnapshotSerializer({ 10 | test: val => val === sheet, 11 | print(val, printer) { 12 | return printer( 13 | stringify(parse(sheet.tags.map(tag => tag.textContent || '').join(''))) 14 | ) 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "loose": true, 7 | "exclude": ["transform-es2015-typeof-symbol"] 8 | } 9 | ] 10 | ], 11 | "env": { 12 | "test": { 13 | "presets": [ 14 | [ 15 | "env", 16 | { 17 | "loose": true, 18 | "exclude": ["transform-es2015-typeof-symbol"] 19 | } 20 | ], 21 | "react" 22 | ], 23 | "plugins": ["emotion"] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { css as magic } from 'emotion' 2 | 3 | export default function createResponsiveCss(breakpoints) { 4 | const mq = ['&'].concat(breakpoints) 5 | 6 | return function css() { 7 | const applied = [magic.apply(this, arguments)] 8 | 9 | function dynamicCss() { 10 | let cls = magic.apply(this, arguments) 11 | applied.push(cls) 12 | return dynamicCss 13 | } 14 | 15 | Object.defineProperty(dynamicCss, 'toString', { 16 | value() { 17 | // should return the sum of the styles to this point 18 | return magic.apply( 19 | this, 20 | applied.reduce((accum, cls, i) => { 21 | accum.push({ [mq[i]]: cls }) 22 | return accum 23 | }, []) 24 | ) 25 | } 26 | }) 27 | 28 | return dynamicCss 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import uglify from 'rollup-plugin-uglify' 2 | import babel from 'rollup-plugin-babel' 3 | import path from 'path' 4 | 5 | const pkg = require(path.resolve(process.cwd(), './package.json')) 6 | 7 | const config = { 8 | entry: './src/index.js', 9 | sourceMap: true, 10 | plugins: [ 11 | babel({ 12 | presets: [ 13 | [ 14 | 'env', 15 | { 16 | loose: true, 17 | modules: false, 18 | exclude: ['transform-es2015-typeof-symbol'] 19 | } 20 | ] 21 | ], 22 | babelrc: false 23 | }) 24 | ], 25 | targets: [ 26 | { dest: pkg.main, format: 'cjs' }, 27 | { dest: pkg.module, format: 'es' } 28 | ] 29 | } 30 | 31 | if (process.env.UMD) { 32 | config.plugins.push(uglify()) 33 | config.targets = [ 34 | { 35 | dest: './dist/facepaint.umd.min.js', 36 | format: 'umd', 37 | moduleName: pkg.name 38 | } 39 | ] 40 | } 41 | 42 | export default config 43 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-sparse-arrays */ 2 | import React from 'react' 3 | import renderer from 'react-test-renderer' 4 | import { sheet, flush } from 'emotion' 5 | 6 | import createResponsiveCss from '../src/index' 7 | 8 | const css = createResponsiveCss([ 9 | '@media(min-width: 420px)', 10 | '@media(min-width: 920px)', 11 | '@media(min-width: 1120px)', 12 | '@media(min-width: 11200px)' 13 | ]) 14 | 15 | describe('heptapod', () => { 16 | afterEach(() => flush()) 17 | test('basic', () => { 18 | const cls3 = css` 19 | font-size: 16px; 20 | background: rgba(45, 213, 47, 0.11); 21 | color: aquamarine; 22 | `` 23 | background-color: hotpink; 24 | `` 25 | font-size: 16px; 26 | background: rgba(0, 0, 0, 0.11); 27 | ` 28 | 29 | const tree = renderer 30 | .create(
Basic
) 31 | .toJSON() 32 | expect(tree).toMatchSnapshot() 33 | expect(sheet).toMatchSnapshot() 34 | }) 35 | 36 | test('object styles', () => { 37 | const cls3 = css({ 38 | fontSize: 16, 39 | background: 'rgba(45, 213, 47, 0.11)', 40 | color: 'aquamarine' 41 | })({ backgroundColor: 'hotpink' })({ 42 | fontSize: 16, 43 | background: 'rgba(0, 0, 0, 0.11)' 44 | }) 45 | 46 | const tree = renderer 47 | .create(
Basic
) 48 | .toJSON() 49 | expect(tree).toMatchSnapshot() 50 | expect(sheet).toMatchSnapshot() 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Heptapod 2 | 3 | #### Experiments with tagged template literals and custom `css` functions for [emotion](https://emotion.sh) 4 | 5 | ## Install 6 | 7 | ```bash 8 | npm i heptapod -S 9 | ``` 10 | 11 | **or** 12 | 13 | ```bash 14 | yarn add heptapod 15 | ``` 16 | 17 | --- 18 | 19 | ```javascript 20 | import createResponsiveCss from 'heptapod' 21 | 22 | const css = createResponsiveCss([ 23 | '@media(min-width: 420px)', 24 | '@media(min-width: 920px)', 25 | ]) 26 | 27 | const cls3 = css` 28 | font-size: 16px; 29 | background: rgba(45, 213, 47, 0.11); 30 | color: aquamarine; 31 | `` 32 | background-color: hotpink; 33 | `` 34 | font-size: 16px; 35 | background: rgba(0, 0, 0, 0.11); 36 | ` 37 | 38 |
Basic
39 | 40 | ``` 41 | 42 | This will insert the following styles into the current Stylesheet emotion is using. 43 | 44 | ```css 45 | .emotion-0 { 46 | font-size: 16px; 47 | background: rgba(45,213,47,0.11); 48 | color: aquamarine; 49 | } 50 | 51 | @media (min-width:420px) { 52 | .emotion-0 { 53 | background-color: hotpink; 54 | } 55 | } 56 | 57 | @media (min-width:920px) { 58 | .emotion-0 { 59 | font-size: 16px; 60 | background: rgba(0,0,0,0.11); 61 | } 62 | } 63 | ``` 64 | 65 | It works for both string and object based styles. The following object styles will output the same styles as the string variant above. 66 | 67 | ```javascript 68 | const cls3 = css({ 69 | fontSize: 16, 70 | background: 'rgba(45, 213, 47, 0.11)', 71 | color: 'aquamarine' 72 | })({ backgroundColor: 'hotpink' })({ 73 | fontSize: 16, 74 | background: 'rgba(0, 0, 0, 0.11)' 75 | }) 76 | 77 |
Basic
78 | ``` 79 | 80 | 81 | 82 | 83 | ## API 84 | 85 | #### createResponsiveCss `function` 86 | 87 | ```javascript 88 | import createResponsiveCss from 'heptapod' 89 | 90 | createResponsiveCss(selectors: Array) : DynamicStyleFunction 91 | ``` 92 | 93 | **Arguments** 94 | * *breakpoints* 95 | ```javascript 96 | const customCssFunction = createResponsiveCss([ 97 | '@media(min-width: 420px)', 98 | '@media(min-width: 920px)', 99 | '@media(min-width: 1120px)' 100 | ]) 101 | ``` 102 | 103 | **Returns** 104 | 105 | `heptapod` returns a function that can be used in place of emotion`s `css` function. This function can be partially applied to add further media query styles. 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /test/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`heptapod basic 1`] = ` 4 | .glamor-0 { 5 | font-size: 16px; 6 | background: rgba(45,213,47,0.11); 7 | color: aquamarine; 8 | } 9 | 10 | @media (min-width:420px) { 11 | .glamor-0 { 12 | background-color: hotpink; 13 | } 14 | } 15 | 16 | @media (min-width:920px) { 17 | .glamor-0 { 18 | font-size: 16px; 19 | background: rgba(0,0,0,0.11); 20 | } 21 | } 22 | 23 |
26 | Basic 27 |
28 | `; 29 | 30 | exports[`heptapod basic 2`] = ` 31 | ".css-zkome7 { 32 | font-size: 16px; 33 | background: rgba(45,213,47,0.11); 34 | color: aquamarine; 35 | } 36 | 37 | .css-txi5oi { 38 | background-color: hotpink; 39 | } 40 | 41 | .css-126my4u { 42 | font-size: 16px; 43 | background: rgba(0,0,0,0.11); 44 | } 45 | 46 | .css-1u159q8 { 47 | font-size: 16px; 48 | background: rgba(45,213,47,0.11); 49 | color: aquamarine; 50 | } 51 | 52 | @media (min-width:420px) { 53 | .css-1u159q8 { 54 | background-color: hotpink; 55 | } 56 | } 57 | 58 | @media (min-width:920px) { 59 | .css-1u159q8 { 60 | font-size: 16px; 61 | background: rgba(0,0,0,0.11); 62 | } 63 | }" 64 | `; 65 | 66 | exports[`heptapod object styles 1`] = ` 67 | .glamor-0 { 68 | font-size: 16px; 69 | background: rgba(45,213,47,0.11); 70 | color: aquamarine; 71 | } 72 | 73 | @media (min-width:420px) { 74 | .glamor-0 { 75 | background-color: hotpink; 76 | } 77 | } 78 | 79 | @media (min-width:920px) { 80 | .glamor-0 { 81 | font-size: 16px; 82 | background: rgba(0,0,0,0.11); 83 | } 84 | } 85 | 86 |
89 | Basic 90 |
91 | `; 92 | 93 | exports[`heptapod object styles 2`] = ` 94 | ".css-4r6ctt { 95 | font-size: 16px; 96 | background: rgba(45,213,47,0.11); 97 | color: aquamarine; 98 | } 99 | 100 | .css-a2d05i { 101 | background-color: hotpink; 102 | } 103 | 104 | .css-aop3sx { 105 | font-size: 16px; 106 | background: rgba(0,0,0,0.11); 107 | } 108 | 109 | .css-1v9evw0 { 110 | font-size: 16px; 111 | background: rgba(45,213,47,0.11); 112 | color: aquamarine; 113 | } 114 | 115 | @media (min-width:420px) { 116 | .css-1v9evw0 { 117 | background-color: hotpink; 118 | } 119 | } 120 | 121 | @media (min-width:920px) { 122 | .css-1v9evw0 { 123 | font-size: 16px; 124 | background: rgba(0,0,0,0.11); 125 | } 126 | }" 127 | `; 128 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heptapod", 3 | "version": "1.2.0", 4 | "description": "Responsive style values for css-in-js.", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "files": [ 8 | "src", 9 | "dist" 10 | ], 11 | "scripts": { 12 | "build": "npm-run-all clean rollup rollup:umd", 13 | "clean": "rimraf dist", 14 | "test": "jest --coverage --no-cache --ci --runInBand", 15 | "rollup": "rollup -c rollup.config.js", 16 | "watch": "rollup -c rollup.config.js --watch", 17 | "rollup:umd": "cross-env UMD=true rollup -c rollup.config.js" 18 | }, 19 | "devDependencies": { 20 | "babel-cli": "^6.24.1", 21 | "babel-eslint": "^7.2.3", 22 | "babel-jest": "^20.0.3", 23 | "babel-plugin-emotion": "^8.0.6", 24 | "babel-plugin-transform-define": "^1.3.0", 25 | "babel-preset-env": "^1.5.1", 26 | "babel-preset-react": "^6.24.1", 27 | "codecov": "^2.3.1", 28 | "cross-env": "^5.0.5", 29 | "css": "^2.2.1", 30 | "emotion": "^8.0.8", 31 | "eslint": "^4.5.0", 32 | "eslint-config-prettier": "^2.3.0", 33 | "eslint-config-react": "^1.1.7", 34 | "eslint-config-standard": "^10.2.1", 35 | "eslint-config-standard-react": "^5.0.0", 36 | "eslint-plugin-import": "^2.7.0", 37 | "eslint-plugin-node": "^5.1.1", 38 | "eslint-plugin-prettier": "^2.2.0", 39 | "eslint-plugin-promise": "^3.5.0", 40 | "eslint-plugin-react": "^7.3.0", 41 | "eslint-plugin-standard": "^3.0.1", 42 | "jest": "^20.0.4", 43 | "jest-cli": "^20.0.4", 44 | "jest-glamor-react": "^3.1.0", 45 | "jest-styled-components": "^4.9.0", 46 | "npm-run-all": "^4.0.2", 47 | "prettier": "^1.7.4", 48 | "prettier-eslint-cli": "^4.0.3", 49 | "react": "^16.0.0", 50 | "react-dom": "^16.0.0", 51 | "react-test-renderer": "^16.0.0", 52 | "rimraf": "^2.6.1", 53 | "rollup": "^0.43.0", 54 | "rollup-plugin-babel": "^2.7.1", 55 | "rollup-plugin-uglify": "^2.0.1", 56 | "rollup-watch": "^4.3.1", 57 | "styled-components": "^2.2.1" 58 | }, 59 | "author": "Kye Hohenberger", 60 | "homepage": "https://github.com/emotion-js/facepaint", 61 | "license": "MIT", 62 | "repository": "https://github.com/emotion-js/facepaint", 63 | "keywords": [ 64 | "styles", 65 | "emotion", 66 | "react", 67 | "css", 68 | "css-in-js" 69 | ], 70 | "bugs": { 71 | "url": "https://github.com/emotion-js/facepaint/issues" 72 | }, 73 | "eslintConfig": { 74 | "extends": [ 75 | "standard", 76 | "standard-react", 77 | "prettier", 78 | "prettier/react" 79 | ], 80 | "plugins": [ 81 | "prettier" 82 | ], 83 | "parser": "babel-eslint", 84 | "rules": { 85 | "prettier/prettier": [ 86 | "error", 87 | { 88 | "singleQuote": true, 89 | "semi": false 90 | } 91 | ], 92 | "react/prop-types": 0, 93 | "react/no-unused-prop-types": 0, 94 | "standard/computed-property-even-spacing": 0, 95 | "no-template-curly-in-string": 0 96 | }, 97 | "overrides": [ 98 | { 99 | "files": [ 100 | "*.test.js" 101 | ], 102 | "env": { 103 | "jest": true 104 | } 105 | } 106 | ] 107 | }, 108 | "jest": { 109 | "transform": { 110 | "^.+\\.js?$": "babel-jest" 111 | }, 112 | "moduleNameMapper": { 113 | "^emotion-theming$": "/packages/facepaint/src" 114 | }, 115 | "setupTestFrameworkScriptFile": "/test/setup.js" 116 | } 117 | } 118 | --------------------------------------------------------------------------------