Skip to content

Instantly share code, notes, and snippets.

@stefanpearson
Created July 7, 2024 11:03
Show Gist options
  • Save stefanpearson/4d2a3f394a8751e862014a755d837605 to your computer and use it in GitHub Desktop.
Save stefanpearson/4d2a3f394a8751e862014a755d837605 to your computer and use it in GitHub Desktop.
BEM factory helper
type BlockName = string;
type ElementName = string | string[];
type ModifierMap = Record<string, unknown>;
const isElementName = (elementName?: ElementName | ModifierMap) =>
typeof elementName === "string" || Array.isArray(elementName);
/**
* Converts part to kebab case and trims white-space
*/
const toSanitizedPart = (part: string) =>
part
.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2")
.toLowerCase()
.trim();
const toArray = <T>(...values: T[]): T[] => ([] as T[]).concat(...values);
/**
* BEM classes (block, [[element], modifier])
*
* bem("card") => "card"
* bem("card", "header") => "card__header"
* bem("card", ["header", "author"]) => "card__header__author"
* bem("card", {featured: true}) => "card card--featured"
* bem("card", ["header", "author"], {anonymous: true}) => "card__header__author card__header__author--anonymous"
*/
export const bem = (
blockName: BlockName,
elementNameOrModifierMap?: ElementName | ModifierMap,
modifierMap?: ModifierMap
) => {
const sanitizedBlockName = toSanitizedPart(blockName);
const resolvedElementName = (
isElementName(elementNameOrModifierMap)
? toArray(elementNameOrModifierMap)
: []
) as string[];
const resolvedModifierMap = ((isElementName(elementNameOrModifierMap)
? modifierMap
: elementNameOrModifierMap) || {}) as ModifierMap;
const elementNamePath = resolvedElementName.reduce(
(memo, elementNamePart) => {
const sanitizedElementNamePart = toSanitizedPart(elementNamePart);
if (sanitizedElementNamePart) {
return memo.concat(sanitizedElementNamePart);
}
return memo;
},
[] as string[]
);
const modifiers = Object.entries(resolvedModifierMap).reduce(
(memo, [modifierName, shouldApply]) => {
const sanitizedModifierName = toSanitizedPart(modifierName);
if (sanitizedModifierName && !!shouldApply) {
return memo.concat(sanitizedModifierName);
}
return memo;
},
[] as string[]
);
const baseClassName = `${sanitizedBlockName}${
elementNamePath.length ? `__${elementNamePath.join("__")}` : ""
}`;
const modifierClassNames = modifiers.map(
(modifierName) => `${baseClassName}--${modifierName}`
);
return [baseClassName].concat(modifierClassNames).join(" ");
};
export const createBem =
(blockName: BlockName) =>
(
elementNameOrModifierMap?: ElementName | ModifierMap,
modifierMap?: ModifierMap
) =>
bem(blockName, elementNameOrModifierMap, modifierMap);
export const concatClasses = (
...bemResults: (ReturnType<typeof bem> | null | undefined)[]
) => bemResults.filter((result) => !!result).join(" ");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment