209 lines
9.5 KiB
Clojure
209 lines
9.5 KiB
Clojure
(ns auto-ap.ssr.components.inputs
|
|
(:require
|
|
[hiccup2.core :as hiccup]
|
|
[auto-ap.ssr.hiccup-helper :as hh]
|
|
[clojure.string :as str]
|
|
[auto-ap.ssr.svg :as svg]
|
|
[auto-ap.ssr.hx :as hx]))
|
|
|
|
|
|
(def default-input-classes
|
|
["bg-gray-50" "border" "text-sm" "rounded-lg" "" "block"
|
|
"p-2.5" "border-gray-300" "text-gray-900" "focus:ring-blue-500" "focus:border-blue-500"
|
|
"dark:bg-gray-700" "dark:border-gray-600" "dark:placeholder-gray-400" "dark:text-white"
|
|
"dark:focus:ring-blue-500" "dark:focus:border-blue-500"
|
|
"group-[.has-error]:bg-red-50" "group-[.has-error]:border-red-500" "group-[.has-error]:text-red-900"
|
|
"group-[.has-error]:placeholder-red-700" "group-[.has-error]:focus:ring-red-500"
|
|
"group-[.has-error]:dark:bg-gray-700" "group-[.has-error]:focus:border-red-500"
|
|
"group-[.has-error]:dark:text-red-500" "group-[.has-error]:dark:placeholder-red-500"
|
|
|
|
"group-[.has-error]:dark:border-red-500"])
|
|
|
|
(def default-checkbox-classes
|
|
"w-4 h-4 bg-gray-100 border-gray-300 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600")
|
|
|
|
(defn select- [params & children]
|
|
(into
|
|
[:select (-> params
|
|
(dissoc :allow-blank? :value :options)
|
|
(update
|
|
:class (fnil hh/add-class "") default-input-classes))
|
|
(cond->>
|
|
(map (fn [[k v]]
|
|
[:option {:value k :selected (= k (:value params))} v])
|
|
(:options params))
|
|
(:allow-blank? params) (conj [:option {:value "" :selected (not (:value params))} ""]))]
|
|
children))
|
|
|
|
|
|
(defn typeahead- [params]
|
|
[:div {:x-data (hx/json {:open false
|
|
:baseUrl (if (str/includes? (:url params) "?")
|
|
(str (:url params) "&q=")
|
|
(str (:url params) "?q="))
|
|
:value {:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))}
|
|
:search ""
|
|
:active -1
|
|
:elements (if ((:value-fn params identity) (:value params))
|
|
[{:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))}]
|
|
[])
|
|
:popper nil})
|
|
:x-modelable "value.value"
|
|
:x-model (:x-model params)
|
|
:x-init "popper = Popper.createPopper($refs.input, $refs.dropdown, {placement: 'bottom-start', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [0, 10]}}})"
|
|
}
|
|
[:a {:class (-> (hh/add-class (or (:class params) "") default-input-classes)
|
|
(hh/add-class "cursor-pointer"))
|
|
"@click.prevent" "open = !open; popper.update()"
|
|
"@keydown.enter.prevent.stop" "open = !open; popper.update()"
|
|
"@keydown.down.prevent.stop" "open = true; popper.update()"
|
|
"@keydown.backspace" "value = {value: '', label: '' }"
|
|
:tabindex 0
|
|
:x-init (:x-init params)
|
|
:x-ref "input"
|
|
}
|
|
[:input (-> params
|
|
(dissoc :class)
|
|
(dissoc :value-fn)
|
|
(dissoc :content-fn)
|
|
|
|
(dissoc :placeholder)
|
|
(dissoc :x-model)
|
|
(assoc
|
|
"x-ref" "hidden"
|
|
:type "hidden"
|
|
":value" "value.value"
|
|
:x-init (hiccup/raw (str "$watch('value', v => $dispatch('change')); "))))]
|
|
[:div.flex.w-full.justify-items-stretch
|
|
[:span.flex-grow.text-left {"x-text" "value.label"}]
|
|
[:div {:class "w-3 h-3 m-1 inline ml-1 justify-self-end text-gray-500 self-center"}
|
|
svg/drop-down]]]
|
|
|
|
[:ul.dropdown-contents {:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 ring-1"
|
|
"x-ref" "dropdown"
|
|
"@keydown.escape" "open = false; value = {value: '', label: '' }"
|
|
"x-transition:enter" "ease-[cubic-bezier(.3,2.3,.6,1)] duration-200"
|
|
"x-transition:enter-start" "!opacity-0"
|
|
"x-transition:enter-end" "!opacity-1"
|
|
"x-transition:leave" "ease-out duration-200"
|
|
"x-transition:leave-start" "!opacity-1"
|
|
"x-transition:leave-end" "!opacity-0"
|
|
"x-show " "open"
|
|
"x-trap" "open"
|
|
"@click.outside" "open=false;"}
|
|
|
|
[:input {:type "text"
|
|
:class (-> (:class params)
|
|
(or "")
|
|
(hh/add-class default-input-classes)
|
|
(hh/replace-wildcard ["rounded" "border"] "border-bottom bg-gray-100 rounded-t-lg w-full"))
|
|
"x-model" "search"
|
|
"placeholder" (:placeholder params)
|
|
"@keydown.down.prevent" "active ++; active = active >= elements.length - 1 ? elements.length - 1 : active"
|
|
"@keydown.up.prevent" "active --; active = active < 0 ? 0 : active"
|
|
"@keydown.enter.prevent" "open = false; value = elements.length > 0 ? $data.elements[active] : {'value': '', label: ''};"
|
|
"x-init" "$watch('search', s => { if($el.value.length > 2) {fetch(baseUrl + s).then(data=>data.json()).then(data => {elements = data; active=-1}) }})"}]
|
|
[:div.dropdown-options {:class "rounded-b-lg overflow-hidden"}
|
|
[:template {:x-for "(element, index) in elements"}
|
|
[:li [:a {:class "px-4 py-2 flex gap-2 items-center outline-0 focus:bg-neutral-100 hover:bg-neutral-100 whitespace-nowrap [&.active]:bg-primary-300 [&.active]:dark:bg-primary-700 text-gray-800 dark:text-gray-100"
|
|
:href "#"
|
|
":class" "active == index ? 'active' : ''"
|
|
"@mouseover" "active = index"
|
|
"@mouseout" "active = -1"
|
|
"@click.prevent" "value = element; open=false; "
|
|
"x-html" "element.label"}]]]
|
|
[:template {:x-if "elements.length == 0"}
|
|
[:li {:class "px-4 py-2 flex gap-2 items-center outline-0 focus:bg-neutral-100 hover:bg-neutral-100 whitespace-nowrap [&.active]:bg-primary-500 text-gray-800 dark:text-gray-100 text-xs "}
|
|
"No results found"]]]
|
|
]])
|
|
|
|
|
|
(defn use-size [size]
|
|
(if (= :small size)
|
|
(str " " "text-xs p-2")
|
|
(str " " "text-sm p-2.25")))
|
|
|
|
|
|
|
|
(defn text-input- [{:keys [size error?] :as params}]
|
|
[:input
|
|
(-> params
|
|
(dissoc :error?)
|
|
(assoc :type "text")
|
|
(update
|
|
:class (fnil hh/add-class "") default-input-classes)
|
|
(update :class #(str % (use-size size))))])
|
|
|
|
(defn money-input- [{:keys [size] :as params}]
|
|
[:input
|
|
(-> params
|
|
(update :class (fnil hh/add-class "") default-input-classes)
|
|
(update :class hh/add-class "appearance-none text-right")
|
|
(update :class #(str % (use-size size)))
|
|
(assoc :type "number"
|
|
:step "0.01")
|
|
(dissoc :size))])
|
|
|
|
(defn int-input- [{:keys [size] :as params}]
|
|
[:input
|
|
(-> params
|
|
(update :class (fnil hh/add-class "") default-input-classes)
|
|
(update :class hh/add-class "appearance-none text-right")
|
|
(update :class #(str % (use-size size)))
|
|
(assoc :type "number"
|
|
:step "1")
|
|
(dissoc :size))])
|
|
|
|
(defn date-input- [{:keys [size] :as params}]
|
|
[:div
|
|
[:input
|
|
(-> params
|
|
(update :class (fnil hh/add-class "") default-input-classes)
|
|
(assoc :type "text")
|
|
(assoc "_" (hiccup/raw "init initDatepicker(me)"))
|
|
(assoc "hx-on" (hiccup/raw "changeDate: htmx.trigger(this, \"change\")
|
|
htmx:beforeCleanupElement: this.dp.destroy()"))
|
|
(update :class #(str % (use-size size)))
|
|
(dissoc :size))]])
|
|
|
|
|
|
|
|
(defn field-errors- [{:keys [source key]} & rest]
|
|
(let [errors (:errors (cond-> (meta source)
|
|
key (get key)))]
|
|
[:p.mt-2.text-xs.text-red-600.dark:text-red-500.h-4 (str/join ", " errors)]))
|
|
|
|
(defn field- [params & rest]
|
|
[:div (-> params
|
|
(update :class #(hh/add-class (or % "") "group" )))
|
|
(when (:label params)
|
|
[:label {:class "block mb-2 text-sm font-medium text-gray-900 dark:text-white"} (:label params)])
|
|
rest
|
|
(when (:error-source params)
|
|
(field-errors- {:source (:error-source params)
|
|
:key (:error-key params)}))])
|
|
|
|
(defn errors- [{:keys [errors]}]
|
|
[:p.mt-2.text-xs.text-red-600.dark:text-red-500.h-4
|
|
(when (sequential? errors)
|
|
(str/join ", " (filter string? errors)))])
|
|
|
|
(defn form-errors- [{:keys [errors]}]
|
|
[:div#form-errors (when errors
|
|
[:span.error-content
|
|
(errors- {:errors errors})])])
|
|
|
|
(defn validated-field- [params & rest]
|
|
(field- (cond-> params
|
|
true (dissoc :errors)
|
|
(sequential? (:errors params)) (update :class #(hh/add-class (or % "") "has-error")))
|
|
rest
|
|
(errors- {:errors (:errors params)})))
|
|
|
|
(defn hidden- [{:keys [name value]}]
|
|
[:input {:type "hidden" :value value :name name}])
|
|
|
|
(defn checkbox- [params & rest]
|
|
[:input (merge params {:type "checkbox" :class (hh/add-class default-checkbox-classes (:class params ""))})
|
|
rest])
|