Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.
/ FkCSS Public archive

Powerful styling without leaving Clojure/ClojureScript - f**k CSS

License

Notifications You must be signed in to change notification settings

raystubbs/FkCSS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clojars Project

Warning

This project has been archived and will no longer be maintained. There are many alternatives on the market, including:

FkCSS

Powerful styling without leaving Clojure/ClojureScript - f**k CSS. FkCSS is a minimal CLJ->CSS library without the weight of CLJSS and other alternatives.

Features

  • Styles scoped by Clojure namespace
  • Fonts and animations via @font-face and @keyframes
  • Concise syntax, but more expressive property values where desired
  • Auto-prefixing
  • Custom property handlers
  • Only a few hundred lines of code

Usage

Most useful things are defined in fkcss.core, so require that in your module.

(ns ...
  (:require
    [fkcss.core :as ss]))

Styles are represented by maps of properties and nested style maps.The key determines how FkCSS interprets a value in the style map:

  • Keywords ending in > denote a nested tag style
  • Keywords ending in >> denote a nested pseudo-element style
  • Keywords ending in ? denote conditional properties
  • Strings denote some number of whitespace delimited classes

Here's an example:

{:div>
 {:hovered?
  {:color "red"}

  :before>>
  {:color "blue"}

  "foo bar"
  {:color "pink"}}}

Which yields:

div:hover {
  color: red;
}

div::before {
  color: blue;
}

div.foo.bar {
  color: pink;
}

Use a vector for more concise nesting.

{[:div> :before>>]
 {:color "blue"}}

Use a map for more concise sub-properties.

{:div>
 {:margin {:left "1rem" :right "1rem"}}}

Yields:

div {
  margin-left: 1rem;
  margin-right: 1rem;
}

defclass

Use defclass to define namespace scoped classes, it'll bind the given var name to the name of the generated class.

(ss/defclass my-class
  {:color "red"

   :hovered?
   {:color "blue"}})

(defn my-component []
  [:div {:class my-class}
    "Hello"])

Properties at the root of a defclass apply to elements with the defined class. Properties in a nested node within a defclass apply to elements within an element with the defined class.

defanimation, reg-animation!

Namespace scoped animations can be defined with defanimation, or animations with custom names can be registered with reg-animation!.

(ns example-ns)

(ss/defanimation example-1
 {:from {:opacity 0}
  :to {:opacity 1}})

(ss/reg-animation! "example-2"
 {0 {:opacity 0}
  1 {:opacity 1}})

(ss/reg-animation "example-3"
 {"0%" {:opacity 0}
  "100%" {:opacity 1}})

This yields.

@keyframes example-ns-example-1 {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes example-2 {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

@keyframes example-3 {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

Nested nodes aren't allowed in animation property maps.

reg-font!

Add fonts to the generated CSS with reg-font!.

(ss/reg-font! "Tangerine"
  [{:src "url(angerine-Regular.ttf) format('opentype')"
    :font-weight 400
    :font-style "normal"}
   {:src "url(Tangerine-Bold.ttf) format('opentype')"
    :font-weight 700
    :font-style "normal"}])

This yields.

@font-face {
  src: url(/fonts/Tangerine-Regular.ttf) format('opentype');
  font-weight: 400;
  font-style: normal;
  font-family: 'Tangerine';
}
@font-face {
  src: url(/fonts/Tangerine-Bold.ttf) format('opentype');
  font-weight: 700;
  font-style: normal;
  font-family: 'Tangerine';
}

A single map can be given instead of the vector when only one @font-face is needed.

reg-style!

Use reg-style! to register global styles. Properties at the root of such style maps apply to all elements. reg-style! requires a key in addition to the style map itself so it can do the right replacement/cleanup when namespaces are reloaded.

(ss/reg-style! ::global
 {:a>
  {:color "blue"
   :text-decoration "none"}})

gen-css

Use gen-css to generate CSS for all registered styles.

(def css (ss/gen-css))

fkcss.cljs/mount!

FkCSS can generate the CSS and add it to a style tag in the DOM in one go, if running in a browser.

(ns ...
  (:require
    [fkcss.cljs :as ss-cljs]))

(ss-cljs/mount!)

Use unmount! to remove it.

Property Handlers

Property handlers allow for custom translations from FkCSS properties to CSS properties. FkCSS comes with some builtin handlers in fkcss.render/default-property-handlers which handle vendor prefixing and allow for some conveniences like margin-x/margin-y properties. Custom handlers can be passed into gen-css, but be sure to merge them with the defaults if you want to keep the bultin ones.

(ss/gen-css
 {:property-handlers
  (merge
   fkcss.render/default-property-handlers
   {...custom handlers...})})

The map of property handlers should look like this:

{:property-name
 (fn [property-value]
  {:props
   {:property-name property-value
    :-webkit-property-name property-value
    :-ms-property-name property-value}})}

Where the :props map in the handlers result gives the final CSS properties.

Built-in Property Handlers

  • margin-x/margin-y shorthand
  • padding-x/padding-y shorthand
  • border-<edge>-radius shorthand (top/right/bottom/left)
  • box-shadow map value with explicit keys #{:offset-x :offset-y :inset? :blur-radius :spread-radius}
  • Vendor prefixes for appropriate properties

Example of more expressive box shadow syntax.

{:box-shadow {:inset? true :offset-x 0 :offset-y 2}}

Predicates

Predicates allow for conditional rules without depending on how the test is implemented. Predicates are keys ending in ? within a style map. FkCSS has builtin predicates for the most common cases, but custom predicates can also be given in gen-css.

(ss/gen-css
 {:predicates
  (merge
   fkcss.render/default-predicates
   {...custom predicates...})})

The predicates map should look like:

{:predicate-key?
 {:selector <css-selector>
  :exec <boolean-function>
  :query <css-query>}}

Any predicate field can be omitted, in which case it simply won't apply.

The :selector field should give a CSS selector to limit where the conditional rules will apply. For example :hover or .selected.

The :exec field should give a function to be executed when the CSS is being generated; if the function returns false then the conditional CSS simply won't be generated.

The :query field should give a @media or @supports query to predicate the rule on.

Built-in Predicates

For CLJ and CLJS: :hovered?, :active? :focused?, :focus-visible?, :enabled?, :disabled?, :visited?, :checked?, :expanded?, :current?, :screen-tiny?, :screen-small?, :screen-large?, :screen-huge?, :pointer-fine?, :pointer-coarse?, :pointer-none?, :hoverable?

For CLJS only: :touchable?

See fkcss.render/default-predicates for how these or implemented and as examples for custom predicates.

About

Powerful styling without leaving Clojure/ClojureScript - f**k CSS

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published