Skip to content

Instantly share code, notes, and snippets.

@cjies
Last active Jul 27, 2016
Embed
What would you like to do?
BEM.js helper
// -------------------------------------
// BEM Helper
// Inspired by 14islands/bem-helper-js
// @ref https://github.com/14islands/bem-helper-js
//
// Usage:
// const bem = BEM('header');
// const bem2 = bem.element('title');
// const bem3 = bem2.modifier('highlight');
//
// bem.toString(); // => 'cj-header'
// bem2.toString(); // => 'cj-header__title'
// bem3.toString(); // => 'cj-header__title cj-header__title--highlight'
//
// or using in React:
// <header className={bem} />
// -------------------------------------
import classNamePrefix from './classname_prefix';
const BEM_ELEMENT_SUFFIX = '__';
const BEM_MODIFIER_SUFFIX = '--';
function stringNotEmpty(string) {
return (typeof string === 'string' && string.length);
}
export class BEMFactory {
constructor({ block, element, modifiers = [], nonBemClasses = [] }) {
if (!block) {
throw new Error('block is required.');
}
this._block = block;
this._element = element;
this._modifiers = modifiers;
this._nonBemClasses = nonBemClasses;
return this;
}
/**
* Set element scope
* @param {String}
* @return {BEMFactory}
*/
element(elementIdentifier) {
if (stringNotEmpty(elementIdentifier)) {
return new BEMFactory({
...this.toHash(),
element: elementIdentifier
});
}
return this;
}
/**
* Add BEM modifier
* @param {String}
* @return {BEMFactory}
*/
modifier(modifierIdentifier, isOn = true) {
if (isOn && stringNotEmpty(modifierIdentifier)) {
return new BEMFactory({
...this.toHash(),
modifiers: [...this._modifiers, modifierIdentifier]
});
}
return this;
}
/**
* Add other non-BEM classname to the mix
* @param {String}
* @return {BEMFactory}
*/
add(className) {
if (stringNotEmpty(className)) {
return new BEMFactory({
...this.toHash(),
nonBemClasses: [...this._nonBemClasses, className]
});
}
return this;
}
/**
* Render BEM chain as full class name string
*
* @return {String}
*/
toString() {
const prefix = typeof this._element !== 'undefined' ?
classNamePrefix(this._block) + BEM_ELEMENT_SUFFIX + this._element :
classNamePrefix(this._block);
const classes = [
prefix,
...this._modifiers.map(modifier => prefix + BEM_MODIFIER_SUFFIX + modifier),
...this._nonBemClasses
];
return classes.join(' ');
}
/**
* Export internal properties to a new Hash
*
* @return {Hash}
*/
toHash() {
return {
block: this._block,
element: this._element,
modifiers: this._modifiers.slice(0),
nonBemClasses: this._nonBemClasses.slice(0)
};
}
}
// Creates BEM chain based on context type
function BEM(blockName) {
if (typeof blockName === 'string') {
return new BEMFactory({ block: blockName });
}
if (blockName instanceof BEMFactory) {
return blockName;
}
throw new Error('blockName should be a valid String or a BEMFactory instance.');
}
export default BEM;
const CLASSNAME_PREFIX = 'cj';
/**
* ClassName Prefix
*
* @param {String} className - Original ClassName
* @return {String} - ClassName within prefix
*/
function classNamePrefix(className) {
if (!className) {
throw new Error('classNamePrefix: empty className');
}
return `${CLASSNAME_PREFIX}-${className}`;
}
export default classNamePrefix;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment