Hi, I’m making a web app with ClojureScript, react, and re-frame. I’m trying to write code to handle this dropdown menu:

(defn quote-actions [sales-contractor? _ id {{:keys [status sales-order-file-url editable] :as quote} :raw-data idx :idx}]
(ra/with-let [approved-quote-exists? @(subscribe [:quote.job/approved-quotes-exist?])
branding-theme-set? @(subscribe [:organisation/branding-themes])
approve-disabled? @(subscribe [:quote.job/approve-button-disabled? idx])
reject-disabled? @(subscribe [:quote.job/reject-button-disabled? idx])]
[new/dropdown-button-menu
{:id “quote-actions”
:label “Quote Actions”
:dropdown-toggle
{:color (dropdown-color-logic status approved-quote-exists?)}
:items [{:label [:div.d-flex
“Approve”
(when (and (not (= status :approved)) approved-quote-exists?)
[ui/with-popover
[:i.fas.fa-question-circle.fa-md.ml-2 {:id (str “approved-existing-” id)}]
“An approved quote already exists. If you wish to approve this quote, reject that one first.”])]
:disabled approve-disabled?
:on-click #(dispatch [:quote.job/open-comment-modal id :approved])}
{:label “Reject”
:disabled reject-disabled?
:on-click #(dispatch [:quote.job/open-comment-modal id :rejected])}
{:label [:div.d-flex
(if sales-order-file-url
“Recreate Sales Order”
“Create Sales Order”)
[ui/with-popover
[:i.fas.fa-question-circle.fa-md.ml-2 {:id (str “sales-order-comment-” id)}]
(sales-order-popover-logic status editable)]]
:disabled (or (not (= status :approved)) ;; Only enable sales order button for approved quotes, and only if the branding theme is set up.
(not (:sales-orders branding-theme-set?))
;; Old quotes which don’t store quote data cannot make sales orders
(not editable))
:on-click #(dispatch [:quote.job/api-create-sales-order id])}
;; Sales contractors cannot delete quotes (GOJ 2105)
(when-not sales-contractor? [:hr]) ;; processed as separate line items
(when-not sales-contractor? {:label “Delete”
:on-click #(dispatch [::events/api-delete-quote id])})]}]))

I got comments that it shouldn’t be able to pass through raw Hiccup formatted like, but I need this feature to directly insert the horizontal rule and for other use cases as well.

Here’s the specifics of how that code is handled in the new/ namespace.

(defn dropdown-item-interpreter [config]
(let [label (:label config)]
(cond
(not (nil? label)) [components/dropdown-item config label] ;; if it’s properly formatted, we expect it to be a dropdown item
:else config)))

(def default-styles-dropdown-button
{:label “Actions”
:dropdown-button {:id (str “dropdown-button”)
;; If there are multiple dropdowns with the same ID this leads to problems.
;; You should set this manually.
:direction “down”}
:dropdown-toggle {:key “dropdown-toggle”
:style {:width “”}
:color “primary”
:caret true}
:dropdown-menu {:key “dropdown-menu”
:class “dropdown-menu-right”}
;; These items are here to show examples of the structure.
;; :items [{:disabled false
;; :label “Clickable item”}
;; {:disabled true
;; :label [:div.d-flex
;; "Disabled item "
;; [:i.fas.fa-question-circle.fa-md.ml-2]]}]
})

(defn dropdown-button-menu
“Standard dropdown menu button for Gojee. Takes a config map.

Required:
:id
:items [{:label
:disabled (boolean)
:on-click (usually dispatch)}
[]] (will pass through raw hiccup into the menu)
Recommended:
:label (string)
Optional:
:dropdown-button {:direction (up/down, string)
:class
:style {:height}}
:dropdown-toggle {:style {:width}
:color
:caret}
:dropdown-menu {:class}”
[{:keys [id label dropdown-button dropdown-toggle dropdown-menu items]
:or {label (:label default-styles-dropdown-button)}}]
(ra/with-let [is-open (ra/atom false)
default default-styles-dropdown-button]
(fn [] [components/dropdown-button
(merge (:dropdown-button default)
dropdown-button
{:is-open @is-open
:key (str id “-button”)
:toggle #(reset! is-open (not @is-open))})
[components/dropdown-toggle
(merge (:dropdown-toggle default)
dropdown-toggle
{:key (str id “-toggle”)})
label]
[components/dropdown-menu
(merge (:dropdown-menu default)
dropdown-menu)
(map dropdown-item-interpreter items)]]))) ;; TODO: make separate keys generate

I think to be accepted upstream, I should be reformatting hiccup code that gets passed through as nested maps and changing it back so it's interpreted as regular hiccup from within the function. For example, maybe the item to be interpreted could be {:direct :hr} or {:passthrough {:p “test”}}, or another better keyword.

How should I go about doing this?