Skip to content

Instantly share code, notes, and snippets.

@ambienthack
Last active August 13, 2023 00:27
Show Gist options
  • Select an option

  • Save ambienthack/83468c267dd7b9b049a0740d73164218 to your computer and use it in GitHub Desktop.

Select an option

Save ambienthack/83468c267dd7b9b049a0740d73164218 to your computer and use it in GitHub Desktop.
BEM class name builder

About

If you use BEM for CSS class naming, this utility will help to construct CSS class names in your TypeScript code.

It allows you to specify Block class name once, and then use template string to costruct derivative class names and selectors in a more concise way. It also makes relations of BEM classes and selectors in TS code more obvious and maintainable.

Without bem-builder:

$( '.page__menu-btn' ).on( 'click', () => {
  $( '.page' ).toggleClass( 'page--menu-visible' );
});

With bem-builder:

import { bem } from './bem-builder';

const p = bem( 'page' );

$( p`.&__menu-btn` ).on( 'click', () => {
  $( p`.&` ).toggleClass( p`--menu-visible` );
});

Usage

import { bem } from './bem-builder';

let m = bem('menu');

m`__item`; // 'menu__item'
m`--opened`; // 'menu--opened'
m`__item--active`; // 'menu__item--active'

// If you need to add a dot (for selectors), use '&':
m`.&__item`; // '.menu__item'

// '&' can be used multiple times:
m`.page .&--top .&__item:first-child`; // '.page .menu--top .menu__item:first-child'

// To refer to the block itself:
m``; // 'menu'
m`&`; // 'menu'
m`.&`; // '.menu'
function buildBemString( parent, strs: TemplateStringsArray, vals: any[] ) {
let result = strs.map( (str, i) => i < vals.length ? str + vals[i] : str ).join('');
if ( result.startsWith('__') || result.startsWith('--') || result == '') result = '&' + result;
return result.replace(/\&/g, parent);
}
/**
* Usage:
*
* let m = bem('menu');
*
* m`__item` == 'menu__item'
* m`--opened` == 'menu--opened'
* m`__item--active` == 'menu__item--active'
*
* If you need to add a dot (for selectors), use '&':
* m`.&__item` == '.menu__item'
*
* '&' can be used multiple times:
* m`.page .&--top .&__item:first-child` == '.page .menu--top .menu__item:first-child'
*
* To refer to the block itself:
* m`` == 'menu'
* m`&` == 'menu'
* m`.&` == '.menu'
*
* @param parent Block class name
* @returns Constructed class name or selector
*/
export const bem = ( parent: string ) => {
function tag(strs: TemplateStringsArray, ...vals: any[]) {
return buildBemString( parent, strs, vals )
}
return tag;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment