├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── bower.json
├── index.js
├── package.json
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Hayato Mizuno
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 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bem-classnames
2 |
3 | [](https://circleci.com/gh/pocotan001/bem-classnames)
4 | [](https://npmjs.org/package/bem-classnames)
5 | [](https://github.com/pocotan001/bem-classnames)
6 |
7 |
8 | bem-classnames is a simple utility to manage BEM class names on React.
9 |
10 | Inspired by [classnames](https://github.com/JedWatson/classnames).
11 |
12 | ``` sh
13 | npm install bem-classnames
14 | ```
15 |
16 | ## Usage
17 |
18 | ``` js
19 | var cx = require('bem-classnames');
20 |
21 | cx(/* classes, [...props|className] */);
22 | ```
23 |
24 | **Simple**
25 |
26 | ``` js
27 | var classes = {
28 | name: 'button',
29 | modifiers: ['color', 'block'],
30 | states: ['disabled']
31 | };
32 |
33 | cx(classes, { color: 'green', block: true }); // "button button--green button--block"
34 | cx(classes, { disabled: true }); // "button is-disabled"
35 | cx(classes, 'a b', ['c', 'd']); // "button a b c d"
36 | ```
37 |
38 | **Custom prefix**
39 |
40 | ``` js
41 | // Default prefixes:
42 | //
43 | // cx.prefixes = {
44 | // modifiers: '{name}--',
45 | // states: 'is-'
46 | // };
47 |
48 | cx.prefixes.modifiers = '-';
49 | cx(classes, { color: 'green' }); // "button -green"
50 |
51 | // You can add the prefixes
52 | cx.prefixes.foo = 'foo-';
53 | classes.foo = ['a', 'b'];
54 | cx(classes, { a: true, b: true }); // "button foo-a foo-b"
55 | ```
56 |
57 | **with React and ES6**
58 |
59 | ``` js
60 | import React from 'react';
61 | import cx from 'bem-classnames';
62 |
63 | class Button extends React.Component {
64 | render() {
65 | let classes = {
66 | name: 'button',
67 | modifiers: ['color', 'size'],
68 | states: ['disabled']
69 | };
70 |
71 | return (
72 |
75 | );
76 | }
77 | }
78 |
79 | React.render(
80 | ,
81 | document.getElementById('example')
82 | );
83 |
84 | // "button button--green button--xl a b is-disabled"
85 | ```
86 |
87 | **for manage the elements of BEM**
88 |
89 | ``` js
90 | class Button extends React.Component {
91 | render() {
92 | let classes = {
93 | button: {
94 | name: 'button',
95 | modifiers: ['color', 'size'],
96 | states: ['disabled']
97 | },
98 | button__inner: {
99 | name: 'button__inner',
100 | modifiers: ['align']
101 | }
102 | };
103 |
104 | return (
105 |
110 | );
111 | }
112 | }
113 |
114 | React.render(
115 | ,
116 | document.getElementById('example')
117 | );
118 |
119 | // button -> "button button--green"
120 | // span -> "button__inner button__inner--center"
121 | ```
122 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bem-classnames",
3 | "main": "index.js",
4 | "version": "1.0.6",
5 | "homepage": "https://github.com/pocotan001/bem-classnames",
6 | "authors": [
7 | "pocotan001"
8 | ],
9 | "description": "A simple utility to manage BEM class names on React",
10 | "keywords": [
11 | "bem",
12 | "classname",
13 | "component",
14 | "css",
15 | "react",
16 | "utility"
17 | ],
18 | "license": "MIT",
19 | "ignore": [
20 | ".editorconfig",
21 | ".gitignore",
22 | "node_modules",
23 | "package.json",
24 | "test.js"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | (function(global, factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | define([], factory); // AMD
4 | } else if (typeof exports === 'object') {
5 | module.exports = factory(); // CommonJS
6 | } else {
7 | global.cx = factory(); // Globals
8 | }
9 | }(this, function() {
10 | 'use strict';
11 |
12 | var prefixes = {
13 | modifiers: '{name}--',
14 | states: 'is-'
15 | };
16 |
17 | var push = Array.prototype.push;
18 | var slice = Array.prototype.slice;
19 | var toString = Object.prototype.toString;
20 |
21 | /**
22 | * toType([]) -> 'array'
23 | *
24 | * @param {*} object
25 | * @return {string}
26 | */
27 | function toType(object) {
28 | return toString.call(object).slice(8, -1).toLowerCase();
29 | }
30 |
31 | /**
32 | * is.array([]) -> true
33 | *
34 | * @param {*} object
35 | * @return {string}
36 | */
37 | var is = {};
38 | ['string', 'boolean', 'array', 'object'].forEach(function(type) {
39 | is[type] = function(object) {
40 | return toType(object) === type;
41 | };
42 | });
43 |
44 | /**
45 | * uniq(['a', 'b', 'a', 'b']) -> ['a', 'b']
46 | *
47 | * @param {Array} array
48 | * @return {Array}
49 | */
50 | function uniq(array) {
51 | return array.filter(function(el, i) {
52 | return array.indexOf(el) === i;
53 | });
54 | }
55 |
56 | /**
57 | * exclude([null, undefined, 1, 0, true, false, '', 'a', ' b ']) -> ['a', 'b']
58 | *
59 | * @param {Array} array
60 | * @return {string[]}
61 | */
62 | function exclude(array) {
63 | return array
64 | .filter(function(el) {
65 | return is.string(el) && el.trim() !== '';
66 | })
67 | .map(function(className) {
68 | return className.trim();
69 | });
70 | }
71 |
72 | /**
73 | * split(' a b ') -> ['a', 'b']
74 | *
75 | * @param {string} className
76 | * @return {string[]}
77 | */
78 | function split(className) {
79 | return className.trim().split(/ +/);
80 | }
81 |
82 | /**
83 | * toClassName(['a', 'b']) -> 'a b'
84 | *
85 | * @param {string[]} names
86 | * @return {string}
87 | */
88 | function toClassName(names) {
89 | return names.join(' ').trim();
90 | }
91 |
92 | /**
93 | * detectPrefix('modifiers', { name: 'foo' }) -> 'foo--'
94 | *
95 | * @param {string} prefixName
96 | * @param {Object} classes
97 | * @return {string}
98 | */
99 | function detectPrefix(prefixName, classes) {
100 | return (prefixes[prefixName] || '').replace(/\{([\w-]*?)\}/g, function (match, p1) {
101 | return classes[p1] || '';
102 | });
103 | }
104 |
105 | /**
106 | * getClassNamesByProps(['a'], { a: 'foo' }, '-') -> [ '-foo' ]
107 | *
108 | * @param {string[]} propNames
109 | * @param {Object} props
110 | * @param {string} [prefix]
111 | * @return {string[]}
112 | */
113 | function getClassNamesByProps(propNames, props, prefix) {
114 | prefix = prefix || '';
115 |
116 | return propNames
117 | .filter(function(name) {
118 | return !!props[name];
119 | })
120 | .map(function(name) {
121 | return prefix + (is.boolean(props[name]) ? name : props[name]);
122 | });
123 | }
124 |
125 | /**
126 | * @param {Object} classes
127 | * @param {...Object|string} [props|className]
128 | * @return {string}
129 | */
130 | function cx(classes/* , [...props|className] */) {
131 | if (!classes) {
132 | return '';
133 | }
134 |
135 | var args = slice.call(arguments).slice(1);
136 | var classNames = [];
137 |
138 | Object.keys(classes).forEach(function(name) {
139 | switch (toType(classes[name])) {
140 | case 'string':
141 | push.apply(classNames, split(classes[name]));
142 | break;
143 | case 'array':
144 | args.forEach(function (arg) {
145 | if (is.object(arg)) {
146 | var names = getClassNamesByProps(classes[name], arg, detectPrefix(name, classes));
147 | push.apply(classNames, names);
148 | }
149 | });
150 | break;
151 | default:
152 | }
153 | });
154 |
155 | args.forEach(function (arg) {
156 | switch (toType(arg)) {
157 | case 'string':
158 | push.apply(classNames, split(arg));
159 | break;
160 | case 'array':
161 | push.apply(classNames, arg);
162 | break;
163 | default:
164 | }
165 | });
166 |
167 | return toClassName(exclude(uniq(classNames)));
168 | }
169 |
170 | cx.prefixes = prefixes;
171 |
172 | return cx;
173 | }));
174 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bem-classnames",
3 | "version": "1.0.6",
4 | "description": "A simple utility to manage BEM class names on React",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha test.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/pocotan001/bem-classnames.git"
12 | },
13 | "keywords": [
14 | "bem",
15 | "classname",
16 | "component",
17 | "css",
18 | "react",
19 | "utility"
20 | ],
21 | "author": "pocotan001",
22 | "license": "MIT",
23 | "devDependencies": {
24 | "mocha": "^2.2.5"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var cx = require('./');
3 |
4 | describe('cx', function() {
5 |
6 | var classes = {
7 | name: 'button',
8 | modifiers: ['color', 'block'],
9 | states: ['loading', 'disabled']
10 | };
11 |
12 | it('should return empty string', function() {
13 | assert.equal(cx(), '');
14 | });
15 |
16 | it('should return block name', function() {
17 | assert.equal(cx(classes), 'button');
18 | });
19 |
20 | it('should return modifiers', function() {
21 | assert.equal(cx(classes, { color: 'green' }), 'button button--green');
22 | assert.equal(cx(classes, { color: 'green', block: true }), 'button button--green button--block');
23 | assert.equal(cx(classes, { color: 'green' }, { block: true }), 'button button--green button--block');
24 | });
25 |
26 | it('should return states', function() {
27 | assert.equal(cx(classes, { loading: true }), 'button is-loading');
28 | assert.equal(cx(classes, { loading: true, disabled: true }), 'button is-loading is-disabled');
29 | assert.equal(cx(classes, { loading: true }, { disabled: true }), 'button is-loading is-disabled');
30 | });
31 |
32 | it('supports a string of class names', function() {
33 | assert.equal(cx({ name: 'button' }, 'a'), 'button a');
34 | assert.equal(cx(classes, 'a'), 'button a');
35 | assert.equal(cx(classes, 'a', 'b c'), 'button a b c');
36 | });
37 |
38 | it('supports an array of class names', function() {
39 | assert.equal(cx(classes, ['a']), 'button a');
40 | assert.equal(cx(classes, ['a'], ['b', 'c']), 'button a b c');
41 | });
42 |
43 | it('should ignore, except for valid objects', function() {
44 | assert.equal(cx(classes, null, undefined, 1, 0, true, false, '', { color: 'green' }, 'a', ['b', 'c']), 'button button--green a b c');
45 | });
46 |
47 | it('should be trimmed', function() {
48 | assert.equal(cx(classes, '', ' b ', [' ']), 'button b');
49 | });
50 |
51 | it('should dedupe', function() {
52 | assert.equal(cx(classes, 'foo', 'bar', 'foo', 'bar'), 'button foo bar');
53 | });
54 |
55 | it('should be custom prefixes', function() {
56 | cx.prefixes.modifiers = '-';
57 | assert.equal(cx(classes, { color: 'green', block: true }), 'button -green -block');
58 | });
59 |
60 | it('should be custom rules', function() {
61 | cx.prefixes.foo = 'foo-';
62 | classes.foo = ['a', 'b'];
63 | assert.equal(cx(classes, { a: true, b: true }), 'button foo-a foo-b');
64 | });
65 |
66 | });
67 |
--------------------------------------------------------------------------------