Surfs is a library for creating user interfaces in Slack applications. It aims to make creating Slack surfaces enjoyable.
Slack's Block Kit is great! Writing blocks as hash maps is less great! Writing vectors is better! The goal of Surfs is to be a high quality template library for defining the user interface of a Slack application. It should be:
(s/fdef)
(s/assert)
Surfs renders Slack blocks, elements, and composition objects from vectors. It also supports defining and using custom components (to organize and encapsulate Slack elements).
Surfs uses clojure.spec.alpha/assert on all rendered results. All component functions contain function specs. This can greatly improve the development experience at the repl.
See:
The thlack.surfs.repl
namespace contains some useful utilities for developing with Surfs.
(describe :tag)
Get render function metadata and fspec
.(doc :tag)
Print component signatures, docstrings, and examples right to the repl!(props :tag)
Get the prop spec for a component (if it has one).The heart of Surfs is the render
function.
(require '[thlack.surfs :as surfs])
(surfs/render [:button {:action_id "A123"} "Click Me!"])
({:action_id "A123",
:type :button,
:text {:type :plain_text, :text "Click Me!"}})
(surfs/render [:section {:block_id "B123"}
[:text "Important Text"]]
[:section {:block_id "B456"}
[:markdown "# More Text!"]])
({:block_id "B123",
:type :section,
:text {:type :plain_text, :text "Important Text"}}
{:block_id "B456",
:type :section,
:text {:type :mrkdwn, :text "# More Text!", :verbatim false}})
The results of render are typically attached to a Slack API payload in the blocks
attribute.
(require '[thlack.surfs :as surfs])
(require '[clojure.data.json :as json])
(let [blocks (surfs/render
[:section {:block_id "B123"}
[:text "Important Text"]]
[:section {:block_id "B456"}
[:markdown "# More Text!"]])]
(post-message some-token (json/write-string {:channel "C123"
:blocks blocks})))
Custom components can be defined two different ways:
defc
macro;;; Using a function
(defn greeting
[first-name]
[:text (str "Hello " first-name "!")])
;;; Using defc
(defc greeting
[first-name]
[:text (str "Hello " first-name "!")])
Both types of custom component can be used in the head position of a vector within a group of components.
(render [:section {:block_id "B123"}
[greeting "Brian"]])
Custom components defined via defc
can be called as render functions themselves:
(defc greeting
[first-name]
[:text (str "Hello " first-name "!")])
(greeting "Brian")
The result of rendering a component defined via defc is either a single result or a sequence of results. A single result is only returned in the event that thlack.surfs/render would return a sequence containing a single item. This is to support scenarios where a component would suffice as the render function for the entire payload to Slack - as is the case with publishing views like home tabs and modals.
Children are one or more nested components - such as :option
elements placed in a :static-select
.
[:static-select {:action_id "A123"}
[:placeholder "Choose a topping"]
[:option {:value "pepperoni"} "Pepperoni"]
[:option {:value "pineapple"} "Pineapple"]]
If a child is encountered as a sequence, it will be flattened. This is useful for things like generating options:
[:static-select {:action_id "A123"}
[:placeholder "Options"]
(map (fn [value label] [:option {:value value} label]) data)]
Or separating items:
[:home
(drop 1 (interleave (repeat [:divider]) [[:section {:block_id "1"}
[:text "One!"]]
[:section {:block_id "2"}
[:text "Two!"]]
[:section {:block_id "3"}
[:text "Three!"]]]))]
Concepts are borrowed from and inspired by the following libraries:
Many thanks to these authors and contributors.
Copyright © 2020 Brian Scaturro
Distributed under the Eclipse Public License (see LICENSE)
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close