Home

Awesome

vashet

A ClojureScript wrapper for the css-in-js library fela.

Clojars ProjectBuild Status

“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.”

Overview

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.

Usage

(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.

Reagent Example

(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"))

API

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.

(create-renderer [& [config]])

instantiate the fela renderer with an optional config object

config

type: map

(init-styles node)

renders all styles into specified node

node required

type: DOM-node

(render-rule rule props)

invokes the rule function with props, applies the resulting styles to the style node, and returns corresponding class names

rule required

type: fn

props

type: map

(render-keyframe keyframe props)

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"

keyframe required

type: fn

props

type: map

(render-font family files props)

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"})

family required

type: string

files required

type: collection

props

type: map

(render-static styles selectors)

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])

styles required

type: map

selectors required

type: collection

(combine-rules & rules)

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"

rules required

type: collection

(build-animation & {:keys [duration timing-fn delay count direction keyframe props]})

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})

duration required

type: string

timing-fn

type: string

delay

type: string

count

type: string/number

direction

type: string

keyframe required

type: fn

props

type: map

(render-styles & {:keys [rule props add-class]})

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"

rule required

type: fn

props

type: map

add-class

type: collection

(render-to-string)

returns all styles in a css string

(render-rule #(merge {:color nil} %) {:color "yellow"})

(println (render-to-string)) ;; => ".a{color:yellow}"

(subscribe-to-styles cbfn)

listens to style changes and invokes the callback function with an info map when new styles are added

cbfn required

type: fn

(clear-styles)

removes all styles from the targeted style node

License

Copyright © 2017 Christopher Howard

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.