├── .gitignore ├── .npmignore ├── tests ├── example.html └── stylenames.test.js ├── .github └── workflows │ └── nodejs.yml ├── LICENSE ├── package.json ├── src └── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | lib -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ### Node template 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Dependency directories 8 | node_modules 9 | 10 | .idea 11 | .babelrc 12 | .eslintrc 13 | .gitignore 14 | webpack.production.babel.js 15 | 16 | tests 17 | .dependabot 18 | .github 19 | src 20 | -------------------------------------------------------------------------------- /tests/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [10.x, 12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: yarn install, lint, and test 21 | run: | 22 | yarn 23 | yarn lint 24 | yarn test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Kevin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@shat/stylenames", 3 | "version": "1.3.3", 4 | "description": "A simple JavaScript utility for conditionally joining inline styles together", 5 | "source": "src/index.js", 6 | "jsnext:main": "lib/index.module.js", 7 | "main": "lib/index.js", 8 | "module": "lib/index.module.js", 9 | "unpkg": "lib/index.umd.js", 10 | "scripts": { 11 | "pretest": "microbundle", 12 | "test": "jest", 13 | "watch": "microbundle watch", 14 | "lint": "xo src tests", 15 | "format": "xo src tests --fix", 16 | "build": "npm run clean && microbundle", 17 | "clean": "rimraf lib", 18 | "prerelease": "npm run build", 19 | "release": "np" 20 | }, 21 | "keywords": [ 22 | "stylenames", 23 | "inline-styles", 24 | "styles", 25 | "css", 26 | "classname", 27 | "classnames", 28 | "util", 29 | "alpinejs", 30 | "alpinejs-styles" 31 | ], 32 | "devDependencies": { 33 | "jest": "^26.0.1", 34 | "microbundle": "^0.14.0", 35 | "np": "^7.0.0", 36 | "rimraf": "^3.0.2", 37 | "xo": "^0.38.2" 38 | }, 39 | "xo": { 40 | "prettier": true, 41 | "space": true, 42 | "globals": [ 43 | "window", 44 | "expect", 45 | "test" 46 | ] 47 | }, 48 | "author": "Kevin Mathmann", 49 | "license": "MIT", 50 | "publishConfig": { 51 | "registry": "https://registry.npmjs.org", 52 | "access": "public" 53 | }, 54 | "repository": { 55 | "type": "git", 56 | "url": "git+https://github.com/shatstack/stylenames.git" 57 | }, 58 | "bugs": { 59 | "url": "https://github.com/shatstack/stylenames/issues" 60 | }, 61 | "homepage": "https://github.com/shatstack/stylenames#readme" 62 | } 63 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert Object to CSS style string. 3 | * 4 | * Example: 5 | * { 6 | * height: "20px", 7 | * width: { 8 | * "20px": false, 9 | * "30px": true, 10 | * "40px": true 11 | * }, 12 | * } 13 | * returns: "height:20px;width:30px;" 14 | * 15 | * @param {object|Array} styles - style rules 16 | * @returns {string} 17 | */ 18 | export default function styleNames(styles) { 19 | if (!styles || typeof styles !== 'object') { 20 | return ''; 21 | } 22 | 23 | if (Array.isArray(styles)) { 24 | return styles.join(';') + ';'; 25 | } 26 | 27 | let styleNames = ''; 28 | for (const key of Object.keys(styles)) { 29 | const value = styles[key]; 30 | const cssPropertyName = kebabCase(key); 31 | if (typeof value === 'string') { 32 | styleNames += `${cssPropertyName}:${value};`; 33 | continue; 34 | } 35 | 36 | if (typeof value === 'boolean') { 37 | styleNames += cssPropertyName; 38 | continue; 39 | } 40 | 41 | if (typeof value !== 'object' || value.length === 0) { 42 | continue; 43 | } 44 | 45 | const conditions = styles[key]; 46 | 47 | for (const value of Object.keys(conditions)) { 48 | if ( 49 | (typeof conditions[value] !== 'function' && conditions[value]) || 50 | (typeof conditions[value] === 'function' && conditions[value]()) 51 | ) { 52 | styleNames += `${cssPropertyName}:${value};`; 53 | break; 54 | } 55 | } 56 | } 57 | 58 | return styleNames; 59 | } 60 | 61 | /** 62 | * Convert string from camel/snake case to kebab case. Borrowed from Alpine.js utils. 63 | * 64 | * @param {string} subject - subject to kebab 65 | * @returns {string} 66 | */ 67 | function kebabCase(subject) { 68 | return subject 69 | .replace(/([a-z])([A-Z])/g, '$1-$2') 70 | .replace(/[_\s]/, '-') 71 | .toLowerCase(); 72 | } 73 | 74 | if (window) { 75 | window.styleNames = styleNames; 76 | } 77 | -------------------------------------------------------------------------------- /tests/stylenames.test.js: -------------------------------------------------------------------------------- 1 | const styleNames = require('../lib/index.umd'); 2 | 3 | test('quickstart example', () => { 4 | expect( 5 | styleNames({ 6 | backgroundColor: 'red', 7 | width: '120px', 8 | 9 | height: { 10 | // If the condition is false the style does not becomes used. 11 | '200px': false, 12 | // Only the first value with true condition becomes used. 13 | '300px': true, 14 | '400px': true 15 | } 16 | }) 17 | ).toBe('background-color:red;width:120px;height:300px;'); 18 | }); 19 | 20 | test('returns empty string when called with nothing', () => { 21 | expect(styleNames()).toBe(''); 22 | }); 23 | 24 | test('returns empty string when not called with an object', () => { 25 | expect(styleNames(123)).toBe(''); 26 | }); 27 | 28 | test('works with JS stylenames', () => { 29 | expect(styleNames({backgroundColor: 'red'})).toBe('background-color:red;'); 30 | }); 31 | 32 | test('works with string values', () => { 33 | expect( 34 | styleNames({ 35 | height: '120px', 36 | width: '100px' 37 | }) 38 | ).toBe('height:120px;width:100px;'); 39 | }); 40 | 41 | test('works with multiple rules under 1 toggle', () => { 42 | expect( 43 | styleNames({ 44 | 'height:120px;width:100px;': true 45 | }) 46 | ).toBe('height:120px;width:100px;'); 47 | }); 48 | 49 | test('works with arrays', () => { 50 | expect(styleNames(['height:120px', 'width:100px'])).toBe( 51 | 'height:120px;width:100px;' 52 | ); 53 | }); 54 | 55 | test('works with a single true condition', () => { 56 | expect( 57 | styleNames({ 58 | height: '120px', 59 | width: { 60 | '200px': false 61 | } 62 | }) 63 | ).toBe('height:120px;'); 64 | }); 65 | 66 | test('works with a single false condition', () => { 67 | expect( 68 | styleNames({ 69 | height: '120px', 70 | width: { 71 | '200px': true 72 | } 73 | }) 74 | ).toBe('height:120px;width:200px;'); 75 | }); 76 | 77 | test('works with more than one condition & with function conditionals', () => { 78 | let itemCount = 0; 79 | 80 | const styleNamesConfig = { 81 | display: { 82 | none: () => itemCount === 0 83 | }, 84 | height: '120px', 85 | width: { 86 | '100px': () => itemCount <= 1, 87 | '200px': () => itemCount <= 2, 88 | '400px': () => itemCount <= 4, 89 | '100%': () => itemCount > 4 90 | } 91 | }; 92 | 93 | expect(styleNames(styleNamesConfig)).toBe( 94 | 'display:none;height:120px;width:100px;' 95 | ); 96 | itemCount++; // 1 97 | expect(styleNames(styleNamesConfig)).toBe('height:120px;width:100px;'); 98 | itemCount++; // 2 99 | expect(styleNames(styleNamesConfig)).toBe('height:120px;width:200px;'); 100 | itemCount++; // 3 101 | expect(styleNames(styleNamesConfig)).toBe('height:120px;width:400px;'); 102 | itemCount += 12; // 15 103 | expect(styleNames(styleNamesConfig)).toBe('height:120px;width:100%;'); 104 | }); 105 | 106 | test('respects whitespace in value', () => { 107 | expect(styleNames({height: `calc( 100% - 90px )`})).toBe( 108 | 'height:calc( 100% - 90px );' 109 | ); 110 | }); 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://github.com/shatstack/stylenames/workflows/build/badge.svg) 2 | ![npm version](https://img.shields.io/npm/v/@shat/stylenames) 3 | ![npm bundle size (scoped)](https://img.shields.io/bundlephobia/min/@shat/stylenames) 4 | 5 | # @shat/stylenames 6 | 7 | A simple JavaScript utility for conditionally joining inline styles together. 8 | 9 | > This is a fork of the unmaintained [stylenames](https://github.com/kmathmann/stylenames) package 10 | 11 | ## What does stylenames do? 12 | 13 | In one short sentence: "stylenames converts an object to a css style string." 14 | 15 | Think [classNames](https://www.npmjs.com/package/classnames) but for inline styles. 16 | 17 | ## Install 18 | 19 | **From CDN:** Add the following script to the end of your `` section. 20 | ```html 21 | 22 | ``` 23 | 24 | That's it. It will initialize itself as `styleNames()`. 25 | 26 | **From NPM:** Install the package from NPM. 27 | ```js 28 | npm install @shat/stylenames 29 | ``` 30 | 31 | Include it in your script. 32 | 33 | ```javascript 34 | import styleNames from '@shat/stylenames'; 35 | ``` 36 | 37 | 38 | ## Quick Start 39 | 40 | Standalone: 41 | 42 | ```js 43 | styleNames({ 44 | backgroundColor: 'red', 45 | width: '120px', 46 | 47 | 'height':{ 48 | // If the condition is false the style does not get used. 49 | '200px': false, 50 | // Only the first value with true condition is used. 51 | '300px': true, 52 | '400px': true 53 | }, 54 | }); 55 | ``` 56 | 57 | With [Alpine.js](https://github.com/alpinejs/alpine): 58 | 59 | ```html 60 | 61 | 62 |
63 | 66 |
67 | 68 | 69 | ``` 70 | 71 | ## Examples 72 | 73 | ### Without conditions 74 | 75 | ```javascript 76 | let styles1 = styleNames({ 77 | height: '120px', 78 | width: '100px' 79 | }); 80 | console.log(styles1); //--> 'height:120px;width:100px;' 81 | ``` 82 | 83 | ### With one condition using a boolean toggle 84 | 85 | ```javascript 86 | let styles1 = styleNames({ 87 | height: '120px', 88 | width: { 89 | '200px': false 90 | } 91 | }); 92 | console.log(styles1); //--> 'height:120px ' 93 | 94 | let styles2 = styleNames({ 95 | height: '120px', 96 | width: { 97 | '200px': true 98 | } 99 | }); 100 | console.log(styles2); //--> 'height:120px;width:200px;' 101 | ``` 102 | 103 | ### With multiple rules with 1 boolean toggle 104 | 105 | ```js 106 | const styles = styleNames({ 107 | 'height:120px;width:100px;': true 108 | }); 109 | console.log(styles); //--> 'height:120px;width:100px;' 110 | ``` 111 | 112 | ### With camelcased rules 113 | 114 | ```js 115 | const styles = styleNames({backgroundColor: 'red'}); 116 | console.log(styles); //--> 'background-color:red;' 117 | ``` 118 | 119 | ### With array input 120 | 121 | ```js 122 | const styles = styleNames(['height:120px', 'width:100px']); 123 | console.log(styles); //--> 'height:120px;width:100px;' 124 | ``` 125 | 126 | ### With more than one condition using a function toggle 127 | 128 | ```javascript 129 | let itemCount = 0; 130 | 131 | let styleNamesConfig = { 132 | display: { 133 | 'none': () => itemCount === 0 134 | }, 135 | height: '120px', 136 | width: { 137 | '100px': () => itemCount <= 1, 138 | '200px': () => itemCount <= 2, 139 | '400px': () => itemCount <= 4, 140 | '100%': () => itemCount > 4 141 | } 142 | }; 143 | 144 | console.log(styleNames(styleNamesConfig)); //--> 'display:none;height:120px;width:100px;' 145 | 146 | itemCount++; //1 147 | console.log(styleNames(styleNamesConfig)); //--> 'height:120px;width:100px;' 148 | 149 | itemCount++; //2 150 | console.log(styleNames(styleNamesConfig)); //--> 'height:120px;width:200px;' 151 | 152 | itemCount++; //3 153 | console.log(styleNames(styleNamesConfig)); //--> 'height:120px;width:400px;' 154 | 155 | itemCount += 12; //15 156 | console.log(styleNames(styleNamesConfig)); //--> 'height:120px;width:100%;' 157 | ``` 158 | 159 | ## Contributing 160 | 161 | ### Requirements 162 | 163 | - Node 10 164 | - Yarn 1.x or npm 165 | 166 | ### Setup 167 | 168 | 1. Clone the repository 169 | 2. Run `yarn` or `npm install` installs all required dependencies. 170 | 171 | ### npm scripts 172 | 173 | > Equivalent `npm run