228 lines
9.8 KiB
Clojure
228 lines
9.8 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"])
|
|
|
|
(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]
|
|
[:select (-> params
|
|
(dissoc :url)
|
|
(dissoc :value)
|
|
(dissoc :value-fn)
|
|
(dissoc :content-fn))
|
|
(for [value (if (:multiple params)
|
|
(:value params)
|
|
[(:value params)])
|
|
:when ((:value-fn params first) value)]
|
|
[:option {:value ((:value-fn params first) value) :selected true} ((:content-fn params second) value)])
|
|
|
|
[:script {:lang "javascript"}
|
|
(hiccup/raw (format "
|
|
(function () {
|
|
var element = document.getElementById('%s');
|
|
var c = new Choices(element, {removeItems: true, removeItemButton:true, searchFloor: 3, searchPlaceholderValue: '%s'});
|
|
let baseUrl = '%s';
|
|
|
|
element.addEventListener('search', function (e) {
|
|
let fullUrl = baseUrl + (baseUrl.includes(\"?\") ? \"&\" : \"?\") + \"q=\" + e.detail.value;
|
|
let data = fetch(fullUrl)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
c.setChoices(data, 'value', 'label', true)
|
|
});
|
|
});
|
|
element.addEventListener('choice', function (e) {
|
|
c.clearChoices();
|
|
})
|
|
})();
|
|
|
|
"
|
|
(:id params)
|
|
(:placeholder params)
|
|
(:url params)
|
|
))]])
|
|
|
|
(defn typeahead-2- [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 first) (:value params)) :label ((:content-fn params second) (:value params))}
|
|
:search ""
|
|
:active -1
|
|
:elements (if ((:value-fn params first) (:value params))
|
|
[{:value ((:value-fn params first) (:value params)) :label ((:content-fn params second) (:value params))}]
|
|
[])})
|
|
:x-modelable "value.value"
|
|
:x-model (:x-model params)
|
|
:class "relative"}
|
|
[:a {:class (-> (hh/add-class (or (:class params) "") default-input-classes)
|
|
(hh/add-class "cursor-pointer"))
|
|
"@click.prevent" "open = !open;"
|
|
"@keydown.enter.prevent.stop" "open = !open;"
|
|
"@keydown.down.prevent.stop" "open = true;"
|
|
"@keydown.backspace" "value = {value: '', label: '' }"
|
|
:tabindex 0
|
|
:x-init (:x-init params)}
|
|
[: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 "absolute bg-gray-50 dark:bg-gray-600 rounded-lg shadow-lg py-1 w-max z-10 mt-1"
|
|
"@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 !mt-0"
|
|
"x-transition:enter-end" "!opacity-1 !mt-1"
|
|
"x-transition:leave" "ease-out duration-200"
|
|
"x-transition:leave-start" "!opacity-1 !mt-1"
|
|
"x-transition:leave-end" "!opacity-0 !mt-0"
|
|
"x-show " "open"
|
|
"x-trap" "open"
|
|
"@click.outside" "open=false; console.log('is this ihid')"}
|
|
[:input {:type "text" :class (hh/add-class (or (:class params) "") default-input-classes)
|
|
"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: ''}; console.log('are we here')"
|
|
"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
|
|
[: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}])
|