Skip to content

Instantly share code, notes, and snippets.

@inscapist
Last active November 15, 2022 00:36
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inscapist/afb78a1c3f96ea9cc7707ed2a04cf03c to your computer and use it in GitHub Desktop.
Save inscapist/afb78a1c3f96ea9cc7707ed2a04cf03c to your computer and use it in GitHub Desktop.
ChakraUI inspired tailwind *layouts*, in reagent/hiccup
(ns myapp.tailwind
(:require
[clojure.spec.alpha :as s]
[clojure.string :as str]
[vl.config :as config]))
(s/def ::class (s/or :string string? :vector vector?))
(s/def ::cs sequential?) ;; shorthand for classes
(s/def ::dim #{:screen :fill :h :vh :w :vw})
(s/def ::position #{:x-center :y-center :center}) ;; position utils for relative elements
(s/def ::bs map?) ;; shorthand for breakpoints
(s/def ::layout #{:cols :stacks})
(s/def ::flex #{:fixed :fill :shrinkable :expandable :no-grow :no-shrink})
(s/def ::magnet #{:start :end :center})
(s/def ::spread #{:max :equal :min})
(s/def ::align #{:stretch :start :end :center :baseline})
(s/def ::tailwind-opts
(s/keys :opt-un [::class ::cs ::dim ::bs
::layout ::magnet ::spread ::align]))
(def ^:private debug-classes config/debug?)
(defn- opts->twclasses
[{:keys [class cs dim position layout flex magnet spread align bs]
:as tailwind-opts}]
(if-not (s/valid? ::tailwind-opts tailwind-opts)
(throw (ex-info "Invalid tailwind opts"
(s/explain-data ::tailwind-opts tailwind-opts)))
(letfn [(breakpoint->class [[breakpoint breakpoint-opts]]
(->> breakpoint-opts
opts->twclasses
(map #(str/join ":" [(name breakpoint) %]))))]
(->> (lazy-cat
;; tokenize class string
(when class
(if (vector? class) class
(-> class (str/split #" "))))
;; turn class keywords to strings
(->> cs
(remove nil?)
(map name))
;; preset width/height
(when dim
(case dim
:screen ["h-screen" "w-screen"]
:fill ["h-full" "w-full"]
:h ["h-full"]
:vh ["h-screen"]
:w ["w-full"]
:vw ["w-screen"]))
;; relative positions
(when position
(case position
:x-center ["left-1/2" "-translate-x-1/2"]
:y-center ["top-1/2" "-translate-y-1/2"]
:center ["left-1/2" "-translate-x-1/2"
"top-1/2" "-translate-y-1/2"]))
;; layouts
(when layout
(case layout
:cols ["flex" "flex-row"]
:stacks ["flex" "flex-col"]))
[;; flex variants
(when flex
(case flex
:fixed "flex-none"
:fill "flex-1" ;; fill regardless of existing size. Used for layouting
:shrinkable "flex-initial"
:expandable "flex-auto" ;; expand proportionate to existing size
:no-grow "flex-grow-0"
:no-shrink "flex-shrink-0"))
;; https://tailwindcss.com/docs/justify-content
(when magnet
(case magnet
:start "justify-start"
:end "justify-end"
:center "justify-center"))
;; https://tailwindcss.com/docs/justify-content
(when spread
(case spread
:max "justify-between"
:equal "justify-around"
:min "justify-evenly"))
;; https://tailwindcss.com/docs/align-items
(when align
(case align
:stretch "items-stretch"
:start "items-start"
:end "items-end"
:center "items-center"
:baseline "items-baseline"))]
;; responsive classes
(mapcat breakpoint->class bs))
(remove nil?)))))
(defn- render [opts children]
[:div (as-> opts o'
(assoc o' :class (->> o' opts->twclasses (str/join " ")))
(let [dsl [:cs :dims :flex :layout :bs :magnet :spread :align]]
(apply dissoc o' (if debug-classes [:cs] dsl))))
(into [:<>] children)])
(defn- unpack-props [& [opts & rest :as props]]
(if (map? opts)
[opts rest]
[{} props]))
(defn tailwind-element
"Coerce hiccup children into opts and children, while allowing some extensions"
[{classes :cs :or {classes []}} & props]
(let [[opts children] (apply unpack-props props)]
(render
(update opts :cs concat classes)
children)))
const PREFIXES = ["tab:"]
function cljsExtractor(content) {
let matches = (content.match(/(["']).*?\1/g) || [])
.map((m) => m.replace(/"/g, ""))
.flatMap((m) => m.split(/[\s]+/))
.filter((s) => s.search(/[^$?+<>=]/) !== -1);
matches = matches.concat(matches.flatMap((m) => m.split(/[.]/)))
matches = matches.concat(
matches.flatMap((s) => PREFIXES.map((p) => p + s))
);
return matches;
}
module.exports = {
mode: "jit", // https://tailwindcss.com/docs/just-in-time-mode
purge: {
// in prod look at shadow-cljs output file in dev look at runtime, which will change files that are actually compiled; postcss watch should be a whole lot faster
content:
process.env.NODE_ENV == "production"
? ["./resources/public/js/main.js"]
: ["./resources/public/js/cljs-runtime/*.js"],
extract: cljsExtractor,
},
darkMode: false, // or 'media' or 'class'
theme: {
fontFamily: {
title: ["Nova Oval", "cursive"],
body: ["Montserrat", "sans-serif"],
},
extend: {
screens: {
// tablet
tab: { max: "960px" },
},
},
},
variants: {
extend: {
textDecoration: ["focus-visible"],
},
},
plugins: [],
};
(defn participants []
[ui/<stacks> {:magnet :end
:dims #{:w}}
[:div
(let [participants (<sub :room-participants)]
[ui/<cols> {:dims #{:w}
:spread :max
:cs [:tab:hidden :justify-between :h-27 :p-3
:bg-gradient-to-t :from-grey-ultradarc]}
[ui/<cols> {:cs [:pointer-events-auto]}
(for [{:keys [username fullname]} participants]
^{:key username} [user-image {:src (h/gen-avatar fullname)
:username username
:fullname fullname}])]
[sign-out]])]])
(defn overlays []
[:<>
[ui/<overlay> :z-10 [lane]]
[ui/<overlay> :z-10 [participants]]
(let [[desktop tablet] [:bottom-14 :bottom-0]
responsive {:tab {:layout :stacks
:cs [:bg-grey-ultradarc :pt-9 :pb-7 tablet]}}]
[ui/<relative> {:cs [desktop]
:bs responsive}
[song-info]
[actions]])])
(defn room-ui []
[:div.font-body
[ui/<cols> {:dims #{:vh}}
[ui/<relative> {:flex :fill
:cs [:overflow-hidden :bg-black]
:bs {:tab {:layout :stacks}}}
[player-view]
[overlays]]]])
@inscapist
Copy link
Author

inscapist commented Jul 30, 2021

This is my setup for tailwind components in clojurescript.

The key mapping is found in the function opts->twclasses. I am not a frontend dev by training so I rely on my own naming scheme.
Refer to the Clojure spec for the usage.

Tailwind uses purgecss underneath, hence we need to augment the extractor to return responsive classes, or they won't be included in the CSS build. Note that purge.extract option is only available in the latest tailwindcss version

@inscapist
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment