bs-emotion
BuckleScript bindings to Emotion.
Installation
Get the package:
# yarn
yarn add @minima.app/re-css bs-emotion
# or npm
npm install --save @minima.app/re-css bs-emotion
Then add it to bsconfig.json
:
"bs-dependencies": [
"@minima.app/re-css",
"bs-emotion"
]
Usage
Defining styles
Example is in OCaml syntax, but you can use Reason too.
(* ComponentStyles.ml *)
open Emotion
let container = css [
(* label hashed classnames for readability *)
(* I prolly should do ppx to automate that *)
label "container";
display `flex;
flexFlow `column `nowrap;
alignItems `center;
]
let shape = css [
label "shape";
display `flex;
flexFlow `row `nowrap;
alignItems `center;
justifyContent `center;
transitionProperty "border-radius";
transitionDuration (`ms 100);
transitionTimingFunction `easeInOut;
width (`px 200);
height (`px 200);
borderRadius (`px 6);
backgroundColor (`hex "29d");
(* :hover selector, same as `select ":hover" [ ... ]` *)
hover [
borderRadius (`pct 50.);
important (cursor `grab);
];
]
(* Dynamic styling *)
let text ~size = css [
label "text";
color (`hex "fff");
fontSize (`px size);
fontWeight 700;
(* Transition takes property, duration, timing-function & delay *)
transition "font-size" (`ms 100) `easeInOut `zero;
(* You can define multiple transitions by packing them into list of tuples *)
transitions [
("font-size", `ms 100, `easeInOut, `ms 0);
];
(* Complex selector that uses .container class defined above *)
(* Rendered as: `.container:hover .text {...}` *)
select {j|.$container:hover &|j} [
fontSize Calc.(((`px size) + (`pct 150.)) * (`n 1.5));
];
(* @media quiery with nested selectors *)
media "(max-width: 900px)" [
color (`hex "ff69b4");
select ":hover" [
color (`hex "fff");
];
];
]
(* Define keyframes *)
let bounce = keyframes [
(0, [ transform (`translateY `zero); ]);
(50, [ transform (`translateY (`px (-20))); ]);
(100, [ transform (`translateY `zero); ]);
]
let animated = css [
label "animated";
(* Use generated animation name *)
animationName bounce;
animationDuration (`ms 300);
animationIterationCount (`i 7);
]
(* Compose things *)
let smallText = css [
label "smallText";
fontSize (`em 0.8);
]
let note = css ~extend: smallText [
label "note";
marginTop (`px 10);
]
Applying styles
/* Component.re */
module Css = ComponentStyles;
let component = ReasonReact.statelessComponent(__MODULE__);
let make = _ => {
...component,
render: _ => <div className=Css.container> ... </div>,
};
Composing classnames
Cx
This package provides Cx.merge
function which is a binding to Emotion's cx
. It merges 2 Emotion's CSS classes into single unique class. See the Caveats section for details.
Cn
Also, there is re-classnames
. You can use it to combine classnames together. It's not aware of Emotion and simply operates on strings.
Caveats
First, let's talk about the difference between Cn.make
and Cx.merge
:
Cn.make([Css.one, Css.two]) /* => "css-<HASH>-one css-<HASH>-two" */
Cx.merge([|Css.one, Css.two|]) /* => "css-<HASH>-one-two" */
If the former simply concatenates two classname strings into a single string (and as a result 2 CSS classes are applied) then the latter merges 2 Emotion classes into single unique class and applies it to a node (as a result 1 unique CSS class is applied).
Caveat #1
<div className={Cx.merge([|Css.foo, Css.bar|])}>
<button className=Css.button />
</div>
let foo = css [ ... ]
let bar = css [ ... ]
let button = css [
...
select {j|.$foo:hover &|j} [
(*
It won't work due to `.foo` class is being merged w/ `.bar`
into single unique classname inside component
*)
]
]
To make this css work you can use Cn.make
, e.g:
<div className={Cn.make([Css.foo, Css.bar])} />
Caveat #2
let make = (~className, children) => {
...component,
render: _ =>
<div className={Cn.make([Css.foo, className])}>
...children
</div>,
};
Oftentimes, UI abstractions accept className
prop to extend or override default CSS of abstraction. Since position of classname on application site doesn't guarantee precedence of className
prop (in CSS, precedence is determined by position of classname on definition site), it's not safe to use Cn.make
here. In this case, use Cx.merge
since Emotion
determines precedence on application site and guarantees that last classname has precedence over the preceding classes.
<div className={Cx.merge([|Css.foo, className|])} />
Thanks
glennsl/bs-typed-css-core
andSentiaAnalytics/bs-css
for examples and inspiration.