Liking cljdoc? Tell your friends :D

shared-ui

com.wagoe/wagoe-shared-ui holds the Hiccup building blocks that more than one library renders with. It was extracted so admin and user could stop depending on each other’s UI code — that mutual dependency was a genuine cycle until this library broke it.

What it is

A library of pure functions returning Hiccup. No I/O, no state, no ports.

That last part is deliberate and worth stating, because it makes shared-ui the one exception to Wagoe’s usual public-API rule. Elsewhere core/ is an implementation detail and you depend on ports.clj; here there is no port, because there is nothing to abstract over — a function that turns arguments into a vector has no adapter to swap. The functions under wagoe.shared.ui.core. *are the public surface, and the stability policy covers them as such.

Namespaces

NamespaceContains

wagoe.shared.ui.core.components

Form controls and inline feedback: text-input, email-input, password-input, textarea, select-field, checkbox, form-field, button, submit-button, link-button, badge, tone-badge, tabs, success-message, error-message, info-message, validation-errors

wagoe.shared.ui.core.layout

Page scaffolding: page-layout, pilot-page-layout, admin-pilot-page-layout, error-layout, main-navigation, sidebar, breadcrumbs, card, render-error-page

wagoe.shared.ui.core.shell, wagoe.shared.ui.core.user-menu

The admin shell (sidebar, topbar) and the signed-in user’s menu

wagoe.shared.ui.core.modal

modal on a native <dialog>, and confirm-modal with confirm-attrs. window.AdminUX.showConfirmModal still opens one, deprecated until 2.0.

wagoe.shared.ui.core.toast

toast and the toast-region every layout renders. window.AdminUX.showToast and .flash-messages banners still feed the region, deprecated until 2.0.

wagoe.shared.ui.core.combobox

Typeahead input that asks the server for options

wagoe.shared.ui.core.form

Form attributes, decoding and errors from a Malli map schema

wagoe.shared.ui.core.table

data-table (sortable headers, filter inputs, infinite scroll), filter-input, pagination, bulk-bar for actions on selected rows, and parse-table-params / list-url for the query string that holds the table’s state

wagoe.shared.ui.core.validation

Turning Malli explain output into per-field messages: explain→field-errors, has-errors?, field-error

wagoe.shared.ui.core.icons

Inline SVG icon set

wagoe.shared.ui.core.alpine

Alpine.js attribute helpers for HTMX-driven interactions

Relationship to ui-style

shared-ui produces markup; ui-style ships the CSS and JS that markup expects. The class names the components emit are the contract between them, which is why the two are versioned in lockstep like everything else.

Usage

(require '[wagoe.shared.ui.core.components :as ui]
         '[wagoe.shared.ui.core.layout :as layout])

(layout/page-layout
  "Products"
  [:div
   (ui/text-input :name "" {:placeholder "Product name"})
   (ui/submit-button "Save")])

Because every function is pure, they are straightforward to test — call one and assert on the Hiccup vector it returns, with no rendering step and no system to start.

Tables

The table’s controls request the list’s own URL and push it, so sort, filters and page live in the address bar. The handler answers an htmx request with the table and anything else with the whole page.

(require '[wagoe.shared.ui.core.table :as table])

(defn orders-table [orders tq filters total]
  [:div#orders
   (table/data-table
    {:url         "/orders"
     :hx-target   "#orders"
     :table-query tq
     :filters     filters
     :columns     [{:field :number :label "Number"}
                   {:field :status :label "Status" :filter {:options [["paid" "Paid"]]}}]
     :rows        (for [o orders] [:tr [:td (:number o)] [:td (:status o)]])})
   (table/pagination {:table-query tq :total-count total
                      :base-url "/orders" :hx-target "#orders" :extra-params filters})])

;; In the handler
(let [{:keys [filters] :as tq} (table/parse-table-params (:query-params request)
                                                         {:sortable #{:number :status}
                                                          :max-page-size 100})]
  ...)

With :infinite? true (and :total-count and :rows-url) the last row loads the next page once it scrolls into view; the handler at :rows-url answers with just the <tr> rows. Send Vary: HX-Request, HX-Target from a handler that answers one URL with both the page and the fragment.

Can you improve this documentation? These fine people already did:
thijscreemers & Thijs Creemers
Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close