A simple JavaScript utility for generating classnames following the ABEM principles.
import abem from "bera";
abem("button", "label", ["visible", "active"]);
// => 'button__label -visible -active'
The library can also be used directly on the page just including the index.js
in a standalone <script>
tag; or by RequireJS.
Bera is a variant of bero, a utility created by Matteo Ferretti. What bero is to BEM,
bera is to ABEM: It follows the same non-invasive approach inspired by
classnames
to generate classnames for the ABEM naming convention.
In line with Matteo's inspiration for naming bero, it's a character from Yōkai Ningen Bem (妖怪人間ベム Yōkai Ningen Bemu, translated officially as Humanoid Monster Bem).
The abem
function is a curried function, takes up to three arguments.
The simplest usage is the basic signature with two arguments, identifier
and modifiers
:
abem(identifier: String, modifiers: Array|Object) : 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:
abem("button__label", ["visible", "active"]);
// => 'button__label -visible -active'
Since abem
is a curried function, it also possible write the code above as:
const label = abem("button__label");
label(["visible", "active"]);
// => 'button__label -visible -active'
This form would be rarely used for elements; it's more common having block functions, when the signature with three arguments is used:
abem(block: String, elem: String, modifiers: Array|Object) : String
In this form, the equivalent of the code above would be:
abem("button", "label", ["visible", "active"]);
// => 'button__label -visible -active'
But it would be more common used as curried function for block functions:
const button = abem("button");
button("label", ["visible", "active"]);
// => 'button__label -visible -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.
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:
abem("button__label", [false, "visible", 0, , "", undefined, "active"]);
// => button__label -visible -active'
However, modifiers
really shines when an Object
is given:
abem("button__label", {
visible: isVisible,
active: isActive
});
// with `isVisible`: true, `isActive`: true
// => button__label -visible -active'
// with `isVisible`: true, `isActive`: false
// => button__label -visible'
// with `isVisible`: false, `isActive`: true
// => button__label -active'
// with `isVisible`: false, `isActive`: false
// => button__label
With computed property names you can also have modifiers as such:
abem("button__label", {
[`text-${color}`]: !!color
});
// with `color`: undefined:
// => button__label
// with `color`: "red"
// => button__label -textRed
All modifiers are automatically converted to camel case:
abem("button__label", {
'has-focus': true
});
// with `has-focus`: true
// => button__label -hasFocus
// with "text-red": red
abem("button__label", ["ColorRed"]);
// => button__label -colorRed
Likely cases of abbreviation will be retained:
abem("button__label", ["DOMLoaded"]);
// => button__label -DOMLoaded
Any number of hyphens at the beginning of the string will be removed:
abem("button__label", ["-foo", "--bar", "---baz"]);
// => button__label -foo -bar -baz
And any numbers of hyphen inside the string will be removed and the first letter of the suffix properly cased:
abem("button__label", ["foo----bar"]);
// => button__label -fooBar
So that even in those edge cases the ABEM naming convention is kept.
bera
comes with an utility function that helps to concatenate several truthy
values in one string. That's useful when the generated ABEM classname needs to
be concatenate by external strings, such a className
passed by props in React. See below for a real-world example.
import abem, { join } from "bero";
const button = abem("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>
);
}
}
MIT. Copyright (c) 2018 Nuey San Waldman