A ClojureScript wrapper for the css-in-js library fela.
“This is the nature of love." Vashet said. "To attempt to describe it will drive a woman mad. This is what keeps poets scribbling endlessly away. If one could pin it to paper all complete, the others would lay down their pens. But it cannot be done.”
The primary objective of this project is provide the ability to leverage fela.js from ClojureScript without having to worry about interop and JavaScript object types. Most of the methods are just kebab-case versions of fela's api that accept ClojureScript data types. However fela's 'Renderer' class has been abstracted away and there are some additional utility methods.
(ns application.core
(:require [vashet.core :as styles]))
;; create a style renderer
(styles/create-renderer)
;; style rules are just a function of props that return a css map
;; new styles will be generated when props change
(defn rule
[props]
{:color (:color props)
:padding (case (:padding props)
:large "20px"
:small "5px"
0)
:font-size "16px"
":hover" {:color "black"
:transform "translateY(-" (:shiftY props) ")"}
"@media (max-width 410px)" {:margin "10px 0"}})
;; rendering a rule returns a class name that can be applied to any element
(def class (styles/render-rule
rule
{:color "red"
:padding :medium
:shiftY "10%"}))
;; it uses atomic css design to reuse styles on declaration base and to keep the markup minimal
(println class) ;; => "a b c d e"
;; renders all styles into the DOM
(styles/init-styles (.getElementById js/document "style-node"))
This wrapper was made with Reagent in mind, but the class names generated can be passed anywhere and to any object you'd like. Styling as a function of state is fela's goal and with the inclusion of media queries, pseudo classes, child selectors, and keyframes etc. it becomes a viable substitute for css. Vashet attempts to embrace these features and allow for the creation of rich, dynamic, styled components for web applications written in ClojureScript.
(ns reagent-app.core
(:require [reagent.core :as r]
[vashet.core :as styles]))
(defn toggle-button-style
[props]
(let [toggled? (:toggled? props)]
(merge
{:background-color (:bg-color props)
:font-size (str (:font-size props) "px")
:opacity 0.8
":hover" {:opacity 1
:transform "translateY(-5%)"}}
(if toggled? {:opacity 1} {}))))
(defn toggle-button-media-query
[props]
{"@media (max-width 410px)" {:height "42px" :width (if (:wide? props) "90%" 92px)}
"@media (max-width 920px)" {:height "64px" :width (if (:wide? props) "85%" 180px)}})
(def toggle-button-rule (styles/combine-rules
toggle-button-style
toggle-button-media-query))
(defn toggle-button
[text]
(let [state (r/atom {:toggled? false})]
(fn [text]
[:div
{:class (styles/render-rule toggle-button-rule (merge {:bg-color "blue" :font-size 12} @state))
:on-click #(swap! update-in state [:toggled?] not)}
text])))
(defn application-container
[]
(r/create-class
{:component-did-mount (fn []
(create-renderer)
(init-styles (.getElementById js/document "my-styles-node")))
:reagent-render (fn []
[:div
[toggle-button "toggle vashet styles"]])}))
(r/render [application-container] (.getElementById js/document "my-reagent-app"))
Below is a brief description of the available methods. For further clarification I recommend visiting fela's website. Discounting plugins, all of fela's API is encapsulated here, but expects EDN arguments. The only plugin current available in vashet is fela-plugin-prefixer which is activated within vashet's default configuration. I intend to expose more fela plugins in the future.
instantiate the fela renderer with an optional config object
type: map
renders all styles into specified node
type: DOM-node
invokes the rule function with props, applies the resulting styles to the style node, and returns corresponding class names
type: fn
type: map
invokes the keyframe function with props, places the keyframe definition the style node, and returns corresponding keyframe name
(defn my-keyframe
[props]
{:0% {:opacity (:start props)}
:100% {:opacity (:end props)}})
(println (render-keyframe my-keyframe {:start 0 :end 1})) ;; => "k1"
type: fn
type: map
Adds a font-face to the style node
(def files ["./fonts/Lato.ttf" "./fonts/Lato.woff"])
(render-font "Lato" files)
(render-font "Lato-bold" files {:font-weight "bold"})
type: string
type: collection
type: map
applies static styles for the provided selectors
(def header-global
[]
{:font-weight "bold"
:font-family "Open Sans"
:color "blue"})
(def container-global
[]
{"@media (max-width 410px)" {:margin "10px 20px"}
"@media (max-width 660px)" {:margin "10px 30px"}
"@media (max-width 980px)" {:margin "10px 50px"}})
(render-static header-global [:h1 "h2" :h3])
(render-static container-global [".container" :#special-container])
type: map
type: collection
accepts a collection of rule functions and merges their result; functions appearing later in the collection overwrite existing properties
(defn rule-one
[props]
{:color (:color props)
:font-weight (:weight props)
:font-size "16px"})
(defn rule-two
[props]
{:font-family (:font props)
:font-size "14px"})
(def combined-rule (combine-rules [rule-one rule-two]))
(println (combined-rule {:color "red" :weight "bold" :font "monospace"}))
;; => {:color "red" :font-weight "bold" :font-size "14px" :font-family "monospace"}
(println (render-rule combined-rule {:color "red" :weight "bold" :font "monospace"}))
;; => "a b c d"
type: collection
accepts optional css animation key-value pairs, a 'keyframe' key-value pair, and 'props' key-value pair then applies the keyframe to the style node and returns the animation string
(defn pulse
[props]
{:0% {:opacity 1
:transform "scale(1,1)"}
:100% {:opacity 0.4
:transform (str "scale(" (:scaleX props) "," (:scaleY props) ")")}})
(def pulse-animation (build-animation
:duration "3s"
:timing-fn "ease-in"
:count "infinite"
:direction "alternate"
:keyframe pulse
:props {:scaleX 2 :scaleY 2}))
(println pulse-animation) ;; => "3s ease-in infinite alternate k1"
(def rule
[props]
{:background-color "black"
":hover" {:background-color "gray"}
:animation (:animation props)})
(render-rule rule {:animation pulse-animation})
type: string
type: string
type: string
type: string/number
type: string
type: fn
type: map
works like render-rule but accepts key-value pairs and allows for optionally passing in a collection of regular css classes
(defn rule
[props]
{:color (:color props)
:font-family "Gill Sans Light"})
(def hybrid-class-string (render-styles
:rule rule
:props {:color "green"}
:add-class [:regular-class "another-class"]))
(println hybrid-class-string) ;; => "a b regular-class another-class"
type: fn
type: map
type: collection
returns all styles in a css string
(render-rule #(merge {:color nil} %) {:color "yellow"})
(println (render-to-string)) ;; => ".a{color:yellow}"
listens to style changes and invokes the callback function with an info map when new styles are added
type: fn
removes all styles from the targeted style node
Copyright © 2017 Christopher Howard
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.