Skip to content

Instantly share code, notes, and snippets.

@justgage
Created April 21, 2019 03:04
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justgage/0f4b5af9b40d39724579ffae9d5d76e7 to your computer and use it in GitHub Desktop.
Save justgage/0f4b5af9b40d39724579ffae9d5d76e7 to your computer and use it in GitHub Desktop.
BEM.re
let whitespaceRegex = Js.Re.fromString("\\s+");
/**
* This is a little helper to make sure that classNames are BEM style
*
* Example:
* module B =
* BEM.Block({
* let block = "Legal";
* });
*
* B.block
* ->B.mobileModifier
* ->(B.modf("isActive", active))
*
* will generate: "Legal Legal--mobile Legal--isActive" if it's both active and on a mobile phone
* but just "Legal" otherwise
*
* NOTE: See tests for more advanced usages.
*
* This is implemented using a "Functor". Basically they are
* a function that takes in a module and returns a module as
* it's return type. This is similar to an Elixir macro
* that generates a module.
*
* Read more about them here: https://reasonml.github.io/docs/en/module#module-functions-functors
*/
module Block = (BlockDef: {let block: string;}) => {
open BlockDef;
/**
this is the block (the B in BEM) It's usually the component name.
If you're in LocationSelector it will be named "LocationSelector"
*/
let block = block;
/* The line above doesn't look like it does anything,
but this is how you re-export something from another
module, in this case BlockDef above. It's very much
like defdelegate in Elixir */
/**
* A sub element: `B.el("Account") == "Legal__Account"`
*/
let el = element => block ++ "__" ++ element;
/**
* A helper function to add a non-standard classname to a BEM style thing.
*/
let addClass = (base, extraClass) => base ++ " " ++ extraClass;
exception UnableToModifyBlankClassName;
/**
* Adds a modifier to a classname.
*
* This will turn a BEM modifier on and off based on the "cond"
*
* Example:
*
* <div className=B.block->(B.modif("isActive", active))> ... </div>
*
* will generate: "Legal Legal--isActive" if it's both active but just "Legal" otherwise
*/
let modif = (existing: string, modifier: string, cond: bool) =>
if (cond) {
let splitOnWhitespace = Js.String.splitByRe(whitespaceRegex);
let classNames = existing |> splitOnWhitespace |> Array.to_list;
let (root: string, rest: string) =
switch (classNames) {
| [Some(root), ...rest] => (
root,
Js.String.concatMany(
rest
->Belt.List.map(x => Belt.Option.getWithDefault(x, ""))
->Belt.List.toArray,
"",
),
)
| _ => raise(UnableToModifyBlankClassName)
};
let prefix = rest == "" ? root : {j|$root $rest|j};
{j|$prefix $root--$modifier|j};
} else {
existing;
};
let mobileModifier = existing =>
modif(existing, "isMobile", MobileHelper.isMobile());
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment