From c0db7eb76387432b3647283e31995412068294e4 Mon Sep 17 00:00:00 2001 From: Bryce Date: Fri, 20 Oct 2023 11:38:29 -0700 Subject: [PATCH] Lots of less customization when creating and editing forms --- resources/input.css | 2 +- resources/public/output.css | 115 +++++++++++++++- .../auto_ap/ssr/admin/transaction_rules.clj | 130 +++++++++--------- src/clj/auto_ap/ssr/components/inputs.clj | 54 +++++--- src/clj/auto_ap/ssr/form_cursor.clj | 8 ++ src/clj/auto_ap/ssr/hiccup_helper.clj | 98 +++++++++++++ src/clj/auto_ap/ssr/tailwind_util.clj | 2 + 7 files changed, 318 insertions(+), 91 deletions(-) create mode 100644 src/clj/auto_ap/ssr/hiccup_helper.clj create mode 100644 src/clj/auto_ap/ssr/tailwind_util.clj diff --git a/resources/input.css b/resources/input.css index b3a7437d..921b1ff9 100644 --- a/resources/input.css +++ b/resources/input.css @@ -109,7 +109,7 @@ .choices__list--multiple { } .choices__inner { - @apply bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 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 dark:bg-gray-700 p-1 !important; + @apply bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 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 dark:bg-gray-700 p-1 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 !important; } .choices:focus-within .choices__inner { diff --git a/resources/public/output.css b/resources/public/output.css index 63b80bcc..fd7ea057 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -2571,6 +2571,32 @@ input:checked + .toggle-bg { --tw-ring-color: rgb(0 156 234 / var(--tw-ring-opacity)) !important; } +.group.has-error .choices__inner { + --tw-border-opacity: 1 !important; + border-color: rgb(255 3 3 / var(--tw-border-opacity)) !important; + --tw-bg-opacity: 1 !important; + background-color: rgb(255 230 230 / var(--tw-bg-opacity)) !important; + --tw-text-opacity: 1 !important; + color: rgb(51 1 1 / var(--tw-text-opacity)) !important; +} + +.group.has-error .choices__inner::-moz-placeholder { + --tw-placeholder-opacity: 1 !important; + color: rgb(153 2 2 / var(--tw-placeholder-opacity)) !important; +} + +.group.has-error .choices__inner::placeholder { + --tw-placeholder-opacity: 1 !important; + color: rgb(153 2 2 / var(--tw-placeholder-opacity)) !important; +} + +.group.has-error .choices__inner:focus { + --tw-border-opacity: 1 !important; + border-color: rgb(255 3 3 / var(--tw-border-opacity)) !important; + --tw-ring-opacity: 1 !important; + --tw-ring-color: rgb(255 3 3 / var(--tw-ring-opacity)) !important; +} + :is(.dark .choices__inner) { --tw-border-opacity: 1 !important; border-color: rgb(75 85 99 / var(--tw-border-opacity)) !important; @@ -2597,6 +2623,25 @@ input:checked + .toggle-bg { --tw-ring-color: rgb(0 156 234 / var(--tw-ring-opacity)) !important; } +.group.has-error :is(.dark .choices__inner) { + --tw-border-opacity: 1 !important; + border-color: rgb(255 3 3 / var(--tw-border-opacity)) !important; + --tw-bg-opacity: 1 !important; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important; + --tw-text-opacity: 1 !important; + color: rgb(255 3 3 / var(--tw-text-opacity)) !important; +} + +.group.has-error :is(.dark .choices__inner)::-moz-placeholder { + --tw-placeholder-opacity: 1 !important; + color: rgb(255 3 3 / var(--tw-placeholder-opacity)) !important; +} + +.group.has-error :is(.dark .choices__inner)::placeholder { + --tw-placeholder-opacity: 1 !important; + color: rgb(255 3 3 / var(--tw-placeholder-opacity)) !important; +} + .choices:focus-within .choices__inner { --tw-border-opacity: 1 !important; border-color: rgb(0 156 234 / var(--tw-border-opacity)) !important; @@ -2945,6 +2990,41 @@ input:checked + .toggle-bg { color: rgb(17 24 39 / var(--tw-text-opacity)); } +.group.has-error .group-\[\.has-error\]\:border-red-500 { + --tw-border-opacity: 1; + border-color: rgb(255 3 3 / var(--tw-border-opacity)); +} + +.group.has-error .group-\[\.has-error\]\:bg-red-50 { + --tw-bg-opacity: 1; + background-color: rgb(255 230 230 / var(--tw-bg-opacity)); +} + +.group.has-error .group-\[\.has-error\]\:text-red-900 { + --tw-text-opacity: 1; + color: rgb(51 1 1 / var(--tw-text-opacity)); +} + +.group.has-error .group-\[\.has-error\]\:placeholder-red-700::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(153 2 2 / var(--tw-placeholder-opacity)); +} + +.group.has-error .group-\[\.has-error\]\:placeholder-red-700::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(153 2 2 / var(--tw-placeholder-opacity)); +} + +.group.has-error .group-\[\.has-error\]\:focus\:border-red-500:focus { + --tw-border-opacity: 1; + border-color: rgb(255 3 3 / var(--tw-border-opacity)); +} + +.group.has-error .group-\[\.has-error\]\:focus\:ring-red-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(255 3 3 / var(--tw-ring-opacity)); +} + :is(.dark .dark\:block) { display: block; } @@ -3125,6 +3205,11 @@ input:checked + .toggle-bg { color: rgb(255 53 53 / var(--tw-text-opacity)); } +:is(.dark .dark\:text-red-500) { + --tw-text-opacity: 1; + color: rgb(255 3 3 / var(--tw-text-opacity)); +} + :is(.dark .dark\:text-white) { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); @@ -3135,11 +3220,6 @@ input:checked + .toggle-bg { color: rgb(250 202 21 / var(--tw-text-opacity)); } -:is(.dark .dark\:text-red-500) { - --tw-text-opacity: 1; - color: rgb(255 3 3 / var(--tw-text-opacity)); -} - :is(.dark .dark\:placeholder-gray-400)::-moz-placeholder { --tw-placeholder-opacity: 1; color: rgb(156 163 175 / var(--tw-placeholder-opacity)); @@ -3282,6 +3362,31 @@ input:checked + .toggle-bg { color: rgb(255 255 255 / var(--tw-text-opacity)); } +.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:border-red-500) { + --tw-border-opacity: 1; + border-color: rgb(255 3 3 / var(--tw-border-opacity)); +} + +.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:bg-gray-700) { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + +.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:text-red-500) { + --tw-text-opacity: 1; + color: rgb(255 3 3 / var(--tw-text-opacity)); +} + +.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:placeholder-red-500)::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(255 3 3 / var(--tw-placeholder-opacity)); +} + +.group.has-error :is(.dark .group-\[\.has-error\]\:dark\:placeholder-red-500)::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(255 3 3 / var(--tw-placeholder-opacity)); +} + @media (min-width: 640px) { .sm\:block { display: block; diff --git a/src/clj/auto_ap/ssr/admin/transaction_rules.clj b/src/clj/auto_ap/ssr/admin/transaction_rules.clj index ed4a4622..17b95695 100644 --- a/src/clj/auto_ap/ssr/admin/transaction_rules.clj +++ b/src/clj/auto_ap/ssr/admin/transaction_rules.clj @@ -341,8 +341,6 @@ (nat-int? value) (dc/pull (dc/db conn) d-accounts/default-read)) client-id)))})]) - - (defn- transaction-rule-account-row* [transaction-rule account] (com/data-grid-row {} @@ -352,56 +350,58 @@ (com/hidden {:name (fc/field-name) :value (fc/field-value)})) (fc/with-field :transaction-rule-account/account - (com/data-grid-cell {} - [:div {:hx-trigger (hx/trigger-field-change :name "transaction-rule/client" - :from "#edit-form") - :hx-include "#edit-form" - :hx-vals (hx/vals {:name account-name}) - :hx-ext "rename-params" - :hx-rename-params-ex (hx/json {:transaction-rule/client "client-id" - :name "name" - account-name "value"}) - :hx-get (str (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-account-typeahead)) - :hx-swap "innerHTML"} - (account-typeahead* {:value (fc/field-value) - :client-id (:db/id (:transaction-rule/client transaction-rule)) - :name (fc/field-name)}) - (println "HERE" (cursor/path fc/*current*)) - (println "DATA "fc/*form-data*) - (println "ERR" fc/*form-errors*) - (println (fc/field-errors)) - (com/errors {:errors (fc/field-errors)})])) + (com/data-grid-cell + {} + (com/validated-field + {:errors (fc/field-errors)} + [:div {:hx-trigger (hx/trigger-field-change :name "transaction-rule/client" + :from "#edit-form") + :hx-include "#edit-form" + :hx-vals (hx/vals {:name account-name}) + :hx-ext "rename-params" + :hx-rename-params-ex (hx/json {:transaction-rule/client "client-id" + :name "name" + account-name "value"}) + :hx-get (str (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-account-typeahead)) + :hx-swap "innerHTML"} + (account-typeahead* {:value (fc/field-value) + :client-id (:db/id (:transaction-rule/client transaction-rule)) + :name (fc/field-name)})]))) (fc/with-field :transaction-rule-account/location - (com/data-grid-cell {} - [:div [:div {:hx-trigger (hx/triggers - (hx/trigger-field-change :name "transaction-rule/client" - :from "#edit-form") - (hx/trigger-field-change :name account-name - :from "#edit-form")) - :hx-include "#edit-form" - :hx-vals (hx/vals {:name (fc/field-name)}) - :hx-ext "rename-params" - :hx-rename-params-ex (hx/json {"transaction-rule/client" "client-id" - account-name "account-id" - "name" "name" - (fc/field-name) "value"}) - :hx-get (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-location-select) - :hx-swap "innerHTML"} - (location-select* {:name (fc/field-name) - :account-location (:account/location (cond->> (:transaction-rule-account/account @account) - (nat-int? (:transaction-rule-account/account @account)) (dc/pull (dc/db conn) - '[:account/location]))) - :client-locations (:client/locations (:transaction-rule/client transaction-rule)) - :value (fc/field-value)})] - (com/errors {:errors (fc/field-errors)})] - )) + (com/data-grid-cell + {} + (com/validated-field + {:errors (fc/field-errors)} + [:div [:div {:hx-trigger (hx/triggers + (hx/trigger-field-change :name "transaction-rule/client" + :from "#edit-form") + (hx/trigger-field-change :name account-name + :from "#edit-form")) + :hx-include "#edit-form" + :hx-vals (hx/vals {:name (fc/field-name)}) + :hx-ext "rename-params" + :hx-rename-params-ex (hx/json {"transaction-rule/client" "client-id" + account-name "account-id" + "name" "name" + (fc/field-name) "value"}) + :hx-get (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-location-select) + :hx-swap "innerHTML"} + (location-select* {:name (fc/field-name) + :account-location (:account/location (cond->> (:transaction-rule-account/account @account) + (nat-int? (:transaction-rule-account/account @account)) (dc/pull (dc/db conn) + '[:account/location]))) + :client-locations (:client/locations (:transaction-rule/client transaction-rule)) + :value (fc/field-value)})]]))) (fc/with-field :transaction-rule-account/percentage - (com/data-grid-cell (com/money-input {:name (fc/field-name) - :class "w-16" - :value (some-> (fc/field-value) - (* 100 ) - (long ))}) - (com/errors {:errors (fc/field-errors)}))))) + (com/data-grid-cell + {} + (com/validated-field + {:errors (fc/field-errors)} + (com/money-input {:name (fc/field-name) + :class "w-16" + :value (some-> (fc/field-value) + (* 100 ) + (long ))})))))) (com/data-grid-cell (com/a-icon-button {"_" (hiccup/raw "on click halt the event then transition the closest 's opacity to 0 then remove closest ") @@ -434,6 +434,7 @@ :errors (fc/field-errors)} [:div.w-96 (com/typeahead {:name (fc/field-name) + :error? (fc/error?) :class "w-96" :placeholder "Search..." :url (bidi/path-for ssr-routes/only-routes :company-search) @@ -465,6 +466,7 @@ (com/validated-field {:label "Description" :errors (fc/field-errors)} (com/text-input {:name (fc/field-name) + :error? (fc/error?) :placeholder "HOME DEPOT" :class "w-96" :value (fc/field-value)}))) @@ -489,19 +491,19 @@ (com/field {:label "Day of month"} [:div.flex.gap-2 (fc/with-field :transaction-rule/dom-gte - [:div.flex.flex-col - (com/int-input {:name (fc/field-name) - :placeholder ">=" - :class "w-24" - :value (fc/field-value)}) - (com/errors {:errors (fc/field-errors)})]) + (com/validated-field + {:errors (fc/field-errors)} + (com/int-input {:name (fc/field-name) + :placeholder ">=" + :class "w-24" + :value (fc/field-value)}))) (fc/with-field :transaction-rule/dom-lte - [:div.flex.flex-col - (com/int-input {:name (fc/field-name) - :placeholder ">=" - :class "w-24" - :value (fc/field-value)}) - (com/errors {:errors (fc/field-errors)})])]) + (com/validated-field + {:errors (fc/field-errors)} + (com/int-input {:name (fc/field-name) + :placeholder ">=" + :class "w-24" + :value (fc/field-value)})))]) [:h2.text-lg "Outcomes"] (fc/with-field :transaction-rule/vendor @@ -625,7 +627,7 @@ (def transaction-rule-schema (mc/schema [:map [:db/id {:optional true} [:maybe entity-id]] - [:transaction-rule/client entity-id] + [:transaction-rule/client {:optional true} [:maybe entity-id]] [:transaction-rule/description [:and regex [:string {:min 3}]]] [:transaction-rule/bank-account [:maybe entity-id]] @@ -639,7 +641,7 @@ (many-entity {:min 1} [:db/id [:or entity-id temp-id]] [:transaction-rule-account/account entity-id] - [:transaction-rule-account/location :string] + [:transaction-rule-account/location [:string {:min 1 :error/message "required"}]] [:transaction-rule-account/percentage percentage])]])) (def key->handler diff --git a/src/clj/auto_ap/ssr/components/inputs.clj b/src/clj/auto_ap/ssr/components/inputs.clj index 069d14ea..02678df7 100644 --- a/src/clj/auto_ap/ssr/components/inputs.clj +++ b/src/clj/auto_ap/ssr/components/inputs.clj @@ -1,17 +1,28 @@ (ns auto-ap.ssr.components.inputs (:require [hiccup2.core :as hiccup] + [auto-ap.ssr.hiccup-helper :as hh] [clojure.string :as str])) +(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 str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 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") - - ) + :class (fnil hh/add-class "") default-input-classes)) (cond->> (map (fn [[k v]] [:option {:value k :selected (= k (:value params))} v]) @@ -62,21 +73,22 @@ c.clearChoices(); (if (= :small size) (str " " "text-xs p-2") (str " " "text-sm p-2.25"))) -(defn text-input- [{:keys [size] :as params}] + + + +(defn text-input- [{:keys [size error?] :as params}] [:input (-> params + (dissoc :error?) (update - :class str " bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 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") - (update :class #(str % (use-size size))) - ) - ]) + :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 str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block 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 text-right appearance-none" - ) + (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") @@ -85,21 +97,18 @@ c.clearChoices(); (defn int-input- [{:keys [size] :as params}] [:input (-> params - (update - :class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block 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 text-right appearance-none" - ) + (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)) - ]) + (dissoc :size))]) (defn date-input- [{:keys [size] :as params}] [:div [:input (-> params - (update - :class str " bg-gray-50 border border-gray-300 text-gray-900 rounded-lg focus:ring-blue-500 focus:border-blue-500 block 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") + (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\") @@ -115,8 +124,9 @@ c.clearChoices(); [:p.mt-2.text-xs.text-red-600.dark:text-red-500.h-4 (str/join ", " errors)])) (defn field- [params & rest] - [:div {:id (:id params)} - [:label {:class "block mb-2 text-sm font-medium text-gray-900 dark:text-white"} (:label params)] + [:div {:id (:id params) :class (hh/add-class "group" (:class params))} + (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) @@ -128,7 +138,9 @@ c.clearChoices(); (str/join ", " (filter string? errors)))]) (defn validated-field- [params & rest] - (field- (dissoc params :errors) + (field- (cond-> params + true (dissoc :errors) + (sequential? (:errors params)) (update :class #(hh/add-class (or % "") "has-error"))) rest (errors- {:errors (:errors params)}))) diff --git a/src/clj/auto_ap/ssr/form_cursor.clj b/src/clj/auto_ap/ssr/form_cursor.clj index 537dcc78..1170afc0 100644 --- a/src/clj/auto_ap/ssr/form_cursor.clj +++ b/src/clj/auto_ap/ssr/form_cursor.clj @@ -35,4 +35,12 @@ ([cursor] (get-in *form-errors* (cursor/path cursor)))) +(defn error? + ([] + (error? *current*)) + ([cursor] + (let [errors (get-in *form-errors* (cursor/path cursor))] + (and (sequential? errors) + (every? string? errors))))) + diff --git a/src/clj/auto_ap/ssr/hiccup_helper.clj b/src/clj/auto_ap/ssr/hiccup_helper.clj new file mode 100644 index 00000000..617b4a80 --- /dev/null +++ b/src/clj/auto_ap/ssr/hiccup_helper.clj @@ -0,0 +1,98 @@ +(ns auto-ap.ssr.hiccup-helper + (:require [clojure.string :as str] + [hiccup2.core :as hiccup] + [hiccup.util :as hu] + [clojure.set :as set])) + + +(defprotocol ClassHelper + (add-class [this add]) + (remove-class [this remove]) + (replace-class [this remove add]) + (remove-wildcard [this wildcard]) + (replace-wildcard [this wildcard add]) + (replace-tw [this add])) + +(defn string->class-list [n] + (let [class-set (atom (set (str/split (or n "") #" ")))] + (reify + ClassHelper + (add-class [this add] + (if (sequential? add) + (swap! class-set into add) + (swap! class-set into (str/split (str (or add "")) #" "))) + this) + (remove-class [this remove] + (if (sequential? remove) + (swap! class-set set/difference (set remove)) + (swap! class-set disj remove)) + this) + (replace-class [this remove add] + (remove-class this remove) + (add-class this add) + this) + (remove-wildcard [this wildcard] + (if (sequential? wildcard) + (reduce + remove-wildcard + this + wildcard) + (reduce + remove-class + this + (filter (fn [c] + (str/starts-with? c wildcard) + ) @class-set))) + this) + (replace-wildcard [this wildcard add] + (remove-wildcard this wildcard) + (add-class this add) + this) + (replace-tw [this add] + ;; TODO + ) + Object + (toString [this] + (str/join " " @class-set))))) + +(extend-protocol ClassHelper + String + (add-class [this add] + (add-class (string->class-list this) add)) + (remove-class [this remove] + (remove-class (string->class-list this) remove) + ) + (replace-class [this remove add] + (replace-class (string->class-list this) remove add)) + (remove-wildcard [this wildcard] + (remove-wildcard (string->class-list this) wildcard)) + (replace-wildcard [this wildcard add] + (replace-wildcard (string->class-list this) wildcard add)) + (add-tw [this tw] + (replace-tw (string->class-list this) + tw) + )) + + +(str (hiccup/html [:div {:class (-> "hello bryce hello-1 hello-2" + (replace-wildcard ["hello-" "b"] ["hi" "there"]))}])) + + + + + + + + + + + + + + + + + + + + diff --git a/src/clj/auto_ap/ssr/tailwind_util.clj b/src/clj/auto_ap/ssr/tailwind_util.clj new file mode 100644 index 00000000..f8d5a4fe --- /dev/null +++ b/src/clj/auto_ap/ssr/tailwind_util.clj @@ -0,0 +1,2 @@ +(ns auto-ap.ssr.tailwind-util) +