Created
July 7, 2024 11:03
-
-
Save stefanpearson/4d2a3f394a8751e862014a755d837605 to your computer and use it in GitHub Desktop.
BEM factory helper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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