├── .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 | 
2 | 
3 | 
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