Skip to content
/ bero Public

A simple utility function inspired by classnames to handle BEM naming

License

Notifications You must be signed in to change notification settings

ZER0/bero

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bero

Build Status

A simple JavaScript utility for generating classnames following the BEM principles.

Usage:

import bem from "bero";

bem("button", "label", ["visible", "active"]);
// => 'button__label button__label--visible button__label--active'

The library can also be used directly on the page just including the index.js in a standalone <script> tag; or by RequireJS.

Rationale

I found repeating myself a lot when I was working on a React project following the BEM structure and naming convention; even using library such classnames. I wanted a similar non-invasive approach as classnames lib, but following the BEM naming convention, and that's the reason behind the creation of bero.

Why "bero"?

It's a reference to Yōkai Ningen Bem (妖怪人間ベム Yōkai Ningen Bemu, translated officially as Humanoid Monster Bem), a 1968 Japanise anime that I used to watch when I was a kid. The main characters were Bem, Bera and Bero.

Usage

The bem function is a curried function, takes up to three arguments.

The simplest usage is the basic signature with two arguments, identifier and modifiers:

bem(identifier: String, modifiers: Array|Object) : String

identifier: String

The identifier can be either a block, or an element.

If an element is specified, the full identifier has to be written, in the form of block__elem. For example:

bem("button__label", ["visible", "active"]);
// => 'button__label button__label--visible button__label--active'

Since bem is a curried function, it also possible write the code above as:

const label = bem("button__label");

label(["visible", "active"]);
// => 'button__label button__label--visible button__label--active'

This form would be rarely used for elements; it's more common having block functions, when the signature with three arguments is used:

bem(block: String, elem: String, modifiers: Array|Object) : String

In this form, the equivalent of the code above would be:

bem("button", "label", ["visible", "active"]);
// => 'button__label button__label--visible button__label--active'

But it would be more common used as curried function for block functions:

const button = bem("button");

button("label", ["visible", "active"]);
// => 'button__label button__label--visible button__label--active'

This form is useful especially in components, where there is likely only one block per component, but multiple elements as children of that block.

modifiers: Array|Object

The modifiers arguments can be either an Array or an Object. The logic is the same of @JedWatson's classnames module.

If it's an Array, every element that is considered truthy, would be added as modifier in the resulting classname:

bem("button__label", [false, "visible", 0, , "", undefined, "active"]);
// =>  button__label button__label--visible button__label--active'

However, modifiers really shines when an Object is given:

bem("button__label", {
  visible: isVisible,
  active: isActive
});
// with `isVisible`: true, `isActive`: true
// => button__label button__label--visible button__label--active'

// with `isVisible`: true, `isActive`: false
// => button__label button__label--visible'

// with `isVisible`: false, `isActive`: true
// => button__label button__label--active'

// with `isVisible`: false, `isActive`: false
// => button__label

With computed property names you can also have modifiers as such:

bem("button__label", {
  [`text-${color}`]: !!color
});
// with `color`: undefined:
// => button__label

// with `color`: "red"
// => button__label button__label--text-red

from camel case to kebab case

All modifiers are automatically converted from camel case to kebab case:

bem("button__label", {
  hasFocus
});
// with `hasFocus`: true
// => button__label button__label--has-focus

bem("button__label", ["ColorRed"]);
// => button__label button__label--color-red

bem("button__label", ["DOMLoaded"]);
// => button__label button__label--dom-loaded

Any numbers of hyphen at the beginning of the string would be removed:

bem("button__label", ["-foo", "--bar", "---baz"]);
// => button__label button__label--foo button__label--bar button__label--baz

And any numbers of hyphen inside the string would be reduced to one:

bem("button__label", ["foo----bar"]);
// => button__label button__label--foo-bar

So that even in those edge cases the BEM naming convention is kept.

The join function

bero comes with an utility function that helps to concatenate several truthy values in one string. That's useful when the generated BEM classname needs to be concatenate by external strings, such a className passed by props in React. See below for a real-world example.

Usage with React Component.

import bem, { join } from "bero";

const button = bem("button");

export default class Button {
  // ...
  render() {
    const { pressed, hover } = this.state;
    const { className, label, onClick } = this.props;

    return (
      <button
        className={join(button({ pressed, hover }), className)}
        onClick={onClick}
      >
        <label className={button("label", ["strong"])}>{label}</label>
      </button>
    );
  }
}

License

MIT. Copyright (c) 2018 Matteo Ferretti

About

A simple utility function inspired by classnames to handle BEM naming

Resources

License

Stars

Watchers

Forks

Packages

No packages published