Skip to content

Instantly share code, notes, and snippets.

@danielneal
Last active August 13, 2018 09:43
Show Gist options
  • Save danielneal/6c5b7b5bdad63742574035d4f97a07ef to your computer and use it in GitHub Desktop.
Save danielneal/6c5b7b5bdad63742574035d4f97a07ef to your computer and use it in GitHub Desktop.
Clojure tachyons for react native
(ns onions.core
"Clojure tachyons for react native.
Provides a shorthand to a constrained set of atomic css properties i.e.
(style [:flx-row :aifs :jcsb :bg-ui0 :br1]) ; =>
{:flexDirection "row",
:alignItems "flex-start",
:justifyContent "space-between",
:backgroundColor "#2a2a2a",
:borderRadius 2}
Rationale (from https://tachyons.io/)
Design Systems break as they scale (either scaling org or scaling product)
because new components/variants of a component are introduced. Those variants
sometimes (read: often) go undocumented, leading to duplication when that
component/variant is needed (and created) again. Even when the component is
documented, documenting effectively often means dozens/hundreds of instances
to capture all states/variants. Systems like Tachyons et al. approach this
problem by instead documenting and limiting *properties* of components. (I
like to think of this as “subatomic” design.)
-- Daniel Eden (Facebook)"
(:require [clojure.test :refer [is]]))
(def rem 18)
(def font-rem 18)
(defn kwd
"Forms a single keyword by concatenating strings or keywords"
[& strs]
(keyword (apply str (map #(if (keyword? %) (name %) %) strs))))
(def flex
"Flex properties"
{:flx-1 {:flex 1}
:flx-row {:flexDirection "row"}
:flx-row-reverse {:flexDirection "row-reverse"}
:flx-col-reverse {:flexDirection "column-reverse"}
:flx-wrap {:flexWrap "wrap"}
:aifs {:alignItems "flex-start"}
:aic {:alignItems "center"}
:aife {:alignItems "flex-end"}
:jcc {:justifyContent "center"}
:jcfe {:justifyContent "flex-end"}
:jcsb {:justifyContent "space-between"}
:jcsa {:justifyContent "space-around"}
:asfs {:alignSelf "flex-start"}
:asfe {:alignSelf "flex-end"}
:asc {:alignSelf "center"}
:ass {:alignSelf "stretch"}})
(def spacing
"Margin and padding properties
ma0 ... ma8 margin: 0|0.25|0.5|1|2|4|8|16|32 rem
ml|mr|mb|mt [0-8] marginLeft, marginRight, marginBottom, marginTop
mh [0-8] marginHorizontal
mv [0-8] marginVertical"
(let [scale [["0" 0]
["1" 0.25]
["2" 0.5]
["3" 1]
["4" 2]
["5" 4]
["6" 8]
["7" 16]
["8" 32]]]
(into {}
(for [[pre k] [[:ma :margin]
[:ml :marginLeft]
[:mr :marginRight]
[:mt :marginTop]
[:mb :marginBottom]
[:mh :marginHorizontal]
[:mv :marginVertical]
[:p :padding]
[:pl :paddingLeft]
[:pr :paddingRight]
[:pt :paddingTop]
[:pb :paddingBottom]
[:ph :paddingHorizontal]
[:ph :paddingVertical]]
[s fac] scale]
[(kwd pre s) {k (int (* fac rem))}]))))
(def heights
"Heights and widths
h1 ... h6 height: 1|2|4|8|16|32 rem
w1 ... w6 width: 1|2|4|8|16|32 rem
min-h1 ... min-h6 minHeight: 1|2|4|8|16|32 rem
max-h1 ... max-h6 maxHeight: 1|2|4|8|16|32 rem"
(let [scale [["1" 1]
["2" 2]
["3" 4]
["4" 8]
["5" 16]
["6" 32]]]
(into {}
(for [[pre k] [[:h :height]
[:w :width]
[:min-h :minHeight]
[:min-w :minWidth]]
[s fac] scale]
[(kwd pre s) {k (int (* fac rem))}]))))
(def absolute
"Absolute positioning and offsets
absolute position: absolute
top|right|bottom|left-0 top|right|bottom|left: 0 rem
... 1 ... 1 rem
... 2 ... 2 rem
absolute-fill position: absolute, top/left/right/bottom: 0 "
(let [scale [["0" 0]
["1" 1]
["2" 2]]]
(into {:absolute {:position "absolute"}
:absolute-fill {:position "absolute"
:top 0
:left 0
:right 0
:bottom 0}}
(for [[pre k] [[:top- :top]
[:right- :right]
[:left- :left]
[:bottom- :bottom]]
[s fac] scale]
[(kwd pre s) {k (int (* fac rem))}]))))
(def border-width
"Border width properties
ba borderWidth: 1
bl|br|bt|bb borderLeftWidth: 1 | borderRightWidth: 1 ..."
(into {}
(for [[k m] [[:ba {:borderWidth 1}]
[:bl {:borderLeftWidth 1}]
[:br {:borderRightWidth 1}]
[:bt {:borderTopWidth 1}]
[:bb {:borderBottomWidth 1}]]]
[k m])))
(def border-radius
"Border radius properties
br0 ... br5 borderRadius: 0|0.125|0.25|0.5|1]2 rem"
(let [scale [["0" 0]
["1" 0.125]
["2" 0.25]
["3" 0.5]
["4" 1]
["5" 2]]]
(into {}
(for [[s fac] scale]
[(kwd :br s) {:borderRadius (int (* fac rem))}]))))
(def font-size
"Font size properties
f5 fontSize: 1 rem
f1 ... f6 fontSize: 3|2.25|1.5|1.25|1|0.875 rem
f-headline fontSize: 6 rem
f-subheadline fontSize: 5 rem"
(let [scale [["1" 3]
["2" 2.25]
["3" 1.5]
["4" 1.25]
["5" 1]
["6" 0.875]]]
(into {:f-headline {:fontSize (int (* 6 font-rem))}
:f-subheadline {:fontSize (int (* 6 font-rem))}}
(for [[s fac] scale]
[(kwd :f s) {:fontSize (int (* fac font-rem))}]))))
(def text-align
"Text align properties
tl|tc|tr|tj textAlign: left|center|right|justify"
{:tl {:textAlign "left"}
:tc {:textAlign "center"}
:tr {:textAlign "right"}
:tj {:textAlign "justify"}})
(def font-family
"Font family & weight properties"
{:ffsb {:fontFamily "WorkSans-SemiBold"}
:ffm {:fontFamily "WorkSans-Medium"}
:ffr {:fontFamily "WorkSans-Regular"}
:ffl {:fontFamily "WorkSans-Light"}
:ff-title {:fontFamily "RiverfordTitle-Regular"}})
(def opacity
"Opacity properties
o10|20|...|100 opacity: 0.1|0.2|...|1
o05 opacity: 0.05
o025 opacity: 0.025"
(let [scale [["025" 0.025]
["05" 0.05]
["10" 0.1]
["20" 0.2]
["30" 0.3]
["40" 0.4]
["50" 0.5]
["60" 0.6]
["70" 0.7]
["80" 0.8]
["90" 0.9]
["100" 1]]]
(into {}
(for [[s o] scale]
[(kwd :o- s) {:opacity o}]))))
(def colors
"Font family & weight properties"
(let [palette [[:plum0 "#962d5d"]
[:plum1 "#ad2871"]
[:moss0 "#488b45"]
[:teal0 "#149998"]
[:ui0 "#2a2a2a"]
[:ui1 "#4a4a4a"]
[:ui2 "#6969696"]
[:ui3 "#e6e6e6"]
[:ui4 "#fbfbfb"]
[:ui5 "#ffffff"]
[:status0 "#5b9851"]
[:status1 "#f5a623"]
[:status2 "#b5071c"]]]
(into {}
(for [[c hex] palette
[pre prop] [[nil :color]
[:b-- :borderColor]
[:bg- :backgroundColor]]]
[(kwd pre c) {prop hex}]))))
(def line-heights
{:lh-solid {:lineHeight 1}
:lh-title {:lineHeight 1.25}
:lh-copy {:lineHeight 1.5}})
(def tachyons
(merge flex
spacing
heights
absolute
border-width
border-radius
font-size
text-align
font-family
opacity
colors
line-heights))
(defn scale-line-height
"Postprocess step to scale lineheight according to
provided font size."
[m]
(let [line-heights {:lh-solid 1
:lh-title 1.25
:lh-copy 1.5}
{:keys [fontSize lineHeight]} m]
(cond
(and lineHeight fontSize) (assoc m :lineHeight (int (* fontSize lineHeight)))
(and lineHeight (not fontSize)) (throw (ex-info "Font size must be provided if lineheight is required" m))
:else m)))
(defn style
"Constructs a style map from a collection of keywords"
[coll]
(let [f (fn [x] (or (when (map? x) x)
(when-let [m (get tachyons x)] m)
(throw (ex-info (str "Style " x " not found") {:style coll}))))]
(-> (reduce merge (map f coll))
(scale-line-height))))
(is (= {:backgroundColor "#fbfbfb",
:borderColor "#2a2a2a",
:borderRadius 9,
:textAlign "center"}
(style [:bg-ui4 :b--ui0 :br3 :tc])))
(is (= {:position "absolute",
:top 0,
:left 0,
:right 0,
:bottom 0,
:justifyContent "center",
:alignItems "center"}
(style [:absolute-fill :jcc :aic])))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment