Skip to content

Instantly share code, notes, and snippets.

@clojens
Last active September 15, 2020 11:56
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save clojens/5878804 to your computer and use it in GitHub Desktop.
Save clojens/5878804 to your computer and use it in GitHub Desktop.
Semantic grid in Clojure Garden DSL for CSS generation.
(defproject gridsystem "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.5.1"]
[compojure "1.1.5"]
[garden "1.0.0-SNAPSHOT"]
[ring/ring-jetty-adapter "0.2.5"]
[hiccup "1.0.4"]]
:plugins [[lein-ring "0.8.5"]]
:ring {:handler semantic-gs.handler/app}
:profiles
{:dev {:dependencies [[ring-mock "0.1.5"]]}})
(ns gridsystem.core
"Demonstration using Garden to recreate the Semantic Grid framework.
SEE: https://github.com/noprompt/garden
SEE: http://semantic.gs/
SEE: https://github.com/twigkit/semantic.gs/blob/master/stylesheets/scss/grid.scss"
(:use [compojure.core]
[ring.adapter.jetty])
(:refer-clojure :exclude [+ - * /])
(:require [compojure.handler :as handler]
[compojure.route :as route]
[garden.units :as gu :refer [px em]]
[garden.color :as gc :refer [hsl]]
[garden.arithmetic :refer [+ - * /]]
[garden.def :refer [defrule]]
[garden.core :refer [css]]
[hiccup.page :refer [html5]]))
(def ^{:doc "The famous \"micro\" clearfix."}
clearfix
["&"
{:*zoom 1}
["&:before" "&:after"
{:content "\"\""
:display "table"}]
["&:after"
{:clear "both"}]])
;; In Sass configuration is achieved through the use of mutable
;; variables. In Clojure we can define our default configuration as an
;; immutable *value*. Later we can refer to this value when building
;; out or grid system. Note how we can use Clojure's tagged literals
;; to make our stylesheet code look more like CSS.
;; SEE: https://github.com/twigkit/semantic.gs/blob/master/stylesheets/scss/grid.scss#L6-L8
(def grid-defaults
{:column-width (px 60)
:gutter-width (px 20)
:columns 12})
;; This function has been ported directly from the SCSS code.
;; SEE: https://github.com/twigkit/semantic.gs/blob/master/stylesheets/scss/grid.scss#L11-L13
(defn grid-system-width
[{:keys [column-width columns gutter-width]}]
(+ (* column-width columns)
(* gutter-width columns)))
;; Since we aren't using mutable values to configure our grid system
;; and it's behavior, we need to apply a functional approach. Instead
;; of functions which simply respond to mutable values in the
;; surrounding code, we use higher order functions to create an
;; "instance" of a grid system based upon it's input values.
;; There are four key functions which are commonly used when working
;; with grid systems: `row`, `column`, `push`, and `pull`. The
;; following higher order functions assist in creating them. Each one
;; accepts grid configuration via a map and returns an instance of one
;; of these key functions.
;; https://github.com/twigkit/semantic.gs/blob/master/stylesheets/scss/grid.scss#L46-L53
(defn row-fn
"Creates a row function based on a grid configuration."
[{:keys [total-width grid-width gutter-width]}]
(fn []
["&"
clearfix
{:display "block"
:width (* total-width
(/ (+ gutter-width grid-width)
grid-width))
:margin [0 (* total-width
(- (/ (* 0.5 gutter-width)
grid-width)))]}]))
;; https://github.com/twigkit/semantic.gs/blob/master/stylesheets/scss/grid.scss#L54-L61
(defn column-fn
"Creates a column function based on a grid configuration."
[{:keys [total-width grid-width column-width gutter-width]}]
(fn [n]
{:display "inline"
:float "left"
:width (* total-width
(/ (- (* (+ gutter-width
column-width)
n)
gutter-width)
grid-width))
:margin [0 (* total-width
(/ (* 0.5 gutter-width)
grid-width))]}))
(defn offset-margin
"Computes the offset amount for push and pull functions."
[{:keys [total-width grid-width column-width gutter-width]} amount]
(+ (* total-width
(/ (* (+ gutter-width
column-width)
amount)
grid-width))
(* total-width
(/ (* 0.5 gutter-width)
grid-width))))
;; Lastly, we need a function for tying everything together.
(defn create-grid
([]
;; Make use of the default grid settings.
(create-grid grid-defaults))
([grid-opts]
(let [;; Ensure we have everything we need to generate the key
;; functions mentioned above.
opts (merge grid-defaults grid-opts)
{:keys [column-width gutter-width total-width]} opts
;; Calculate the grid with.
grid-width (grid-system-width grid-opts)
opts (assoc opts
:grid-width grid-width
:total-width (or total-width grid-width))
;; Partially evaluate `offset-margin` with our grid options
;; for our custom `push` and `pull` functions.
offset (partial offset-margin opts)]
;; Return a map representing an instance of our grid system.
{:row (row-fn opts)
:column (column-fn opts)
:push (fn [amount]
{:margin-left (offset amount)})
:pull (fn [amount]
{:margin-right (offset amount)})})))
;; With our grid factory in place let's put together a demo. For
;; demonstration purposes we'll take the easy route and build the
;; "fixed" grid (http://semantic.gs/examples/fixed/fixed.html).
;; I like to create this alias because it better expresses the intent
;; in the context of gardening.
(def styles list)
;; Define a few rule functions to make the code more expressive and
;; readable.
(defrule center :div.center)
(defrule top :section#top)
(defrule main :section#main)
(defrule sidebar :section#sidebar)
(defrule headings :h1 :h2 :h3)
;; I like this approach as well.
(def center-text {:text-align "center"})
;; Define our "fixed" grid CSS.
(def fixed
;; Create a standard grid and bind the key values.
(let [{:keys [row column push pull]} (create-grid)]
(styles
["*" "*:after" "*:before"
{:box-sizing "border-box"}]
[:body
{:width (gu/percent 100)
:font-family [["Georgia" :sans-serif]]
:padding 0
:margin 0}
clearfix]
(headings
{:font-weight "normal"})
(center
{:width (px 960)
:margin [0 "auto"]
:overflow "hidden"})
(top
(column 12)
{:margin-bottom (em 1)
:color (hsl [0 0 100])
:background (hsl [0 0 0])
:padding (px 20)})
(main
center-text
(column 9)
{:color (hsl [0 0 40])
:background (hsl [0 0 80])
:padding (px 20)})
(sidebar
center-text
(column 3)
{:color (hsl [0 0 40])
:background (hsl [0 0 80])
:padding (px 20)}))))
(defroutes app-routes
(GET "/" []
;; Render the demo. Compare with the demo on the Semantic Grid
;; website.
;;
;; SEE: http://semantic.gs/examples/fixed/fixed.html
(html5
[:head
[:style (css fixed)]]
[:body
(center
(top [:h1 "The Semantic Grid System"])
(main [:h2 "Main"])
(sidebar [:h2 "Sidebar"]))]))
(route/resources "/")
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
(defonce server (run-jetty #'app {:port 8080 :join? false}))
;; (.start server) ;; (.stop server)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment