mirror of
https://github.com/penpot/penpot.git
synced 2025-06-24 15:26:58 +02:00
✨ Manage empty property values in the combobox in design tab (#6574)
* ✨ Manage empty property values in the combobox in design tab * 📎 PR changes
This commit is contained in:
parent
878952f7b5
commit
46b0e4f0e7
9 changed files with 168 additions and 101 deletions
|
@ -8,8 +8,7 @@
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.types.component :as ctc]
|
[app.common.types.component :as ctc]
|
||||||
[app.common.types.components-list :as ctcl]
|
[app.common.types.components-list :as ctcl]
|
||||||
[app.common.types.variant :as ctv]
|
[app.common.types.variant :as ctv]))
|
||||||
[cuerdas.core :as str]))
|
|
||||||
|
|
||||||
|
|
||||||
(defn find-variant-components
|
(defn find-variant-components
|
||||||
|
@ -21,11 +20,6 @@
|
||||||
(map #(ctcl/get-component data % true))
|
(map #(ctcl/get-component data % true))
|
||||||
reverse))
|
reverse))
|
||||||
|
|
||||||
(defn- dashes-to-end
|
|
||||||
[property-values]
|
|
||||||
(let [dashes (if (some #(= % "--") property-values) ["--"] [])]
|
|
||||||
(concat (remove #(= % "--") property-values) dashes)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn extract-properties-names
|
(defn extract-properties-names
|
||||||
[shape data]
|
[shape data]
|
||||||
|
@ -42,10 +36,7 @@
|
||||||
(group-by :name)
|
(group-by :name)
|
||||||
(map (fn [[k v]]
|
(map (fn [[k v]]
|
||||||
{:name k
|
{:name k
|
||||||
:value (->> v
|
:value (->> v (map :value) distinct)}))))
|
||||||
(map #(if (str/empty? (:value %)) "--" (:value %)))
|
|
||||||
distinct
|
|
||||||
dashes-to-end)}))))
|
|
||||||
|
|
||||||
(defn get-variant-mains
|
(defn get-variant-mains
|
||||||
[component data]
|
[component data]
|
||||||
|
|
|
@ -59,11 +59,12 @@
|
||||||
[:disabled {:optional true} :boolean]
|
[:disabled {:optional true} :boolean]
|
||||||
[:default-selected {:optional true} :string]
|
[:default-selected {:optional true} :string]
|
||||||
[:on-change {:optional true} fn?]
|
[:on-change {:optional true} fn?]
|
||||||
|
[:empty-to-end {:optional true} :boolean]
|
||||||
[:has-error {:optional true} :boolean]])
|
[:has-error {:optional true} :boolean]])
|
||||||
|
|
||||||
(mf/defc combobox*
|
(mf/defc combobox*
|
||||||
{::mf/schema schema:combobox}
|
{::mf/schema schema:combobox}
|
||||||
[{:keys [id options class placeholder disabled has-error default-selected max-length on-change] :rest props}]
|
[{:keys [id options class placeholder disabled has-error default-selected max-length empty-to-end on-change] :rest props}]
|
||||||
(let [is-open* (mf/use-state false)
|
(let [is-open* (mf/use-state false)
|
||||||
is-open (deref is-open*)
|
is-open (deref is-open*)
|
||||||
|
|
||||||
|
@ -187,7 +188,6 @@
|
||||||
|
|
||||||
(kbd/enter? event)
|
(kbd/enter? event)
|
||||||
(do
|
(do
|
||||||
#_(handle-selection focused-value* selected-value* is-open*)
|
|
||||||
(reset! selected-value* focused-value)
|
(reset! selected-value* focused-value)
|
||||||
(reset! is-open* false)
|
(reset! is-open* false)
|
||||||
(reset! focused-value* nil)
|
(reset! focused-value* nil)
|
||||||
|
@ -286,4 +286,5 @@
|
||||||
:focused focused-value
|
:focused focused-value
|
||||||
:set-ref set-option-ref
|
:set-ref set-option-ref
|
||||||
:id listbox-id
|
:id listbox-id
|
||||||
|
:empty-to-end empty-to-end
|
||||||
:data-testid "combobox-options"}])]))
|
:data-testid "combobox-options"}])]))
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
[clojure.string :as str]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(def listbox-id-index (atom 0))
|
(def listbox-id-index (atom 0))
|
||||||
|
@ -66,13 +67,23 @@
|
||||||
[:class {:optional true} :string]
|
[:class {:optional true} :string]
|
||||||
[:disabled {:optional true} :boolean]
|
[:disabled {:optional true} :boolean]
|
||||||
[:default-selected {:optional true} :string]
|
[:default-selected {:optional true} :string]
|
||||||
|
[:empty-to-end {:optional true} :boolean]
|
||||||
[:on-change {:optional true} fn?]])
|
[:on-change {:optional true} fn?]])
|
||||||
|
|
||||||
(mf/defc select*
|
(mf/defc select*
|
||||||
{::mf/schema schema:select}
|
{::mf/schema schema:select}
|
||||||
[{:keys [options class disabled default-selected on-change] :rest props}]
|
[{:keys [options class disabled default-selected empty-to-end on-change] :rest props}]
|
||||||
(let [open* (mf/use-state false)
|
(let [is-open* (mf/use-state false)
|
||||||
open (deref open*)
|
is-open (deref is-open*)
|
||||||
|
|
||||||
|
selected-value* (mf/use-state #(get-selected-option-id options default-selected))
|
||||||
|
selected-value (deref selected-value*)
|
||||||
|
|
||||||
|
focused-value* (mf/use-state nil)
|
||||||
|
focused-value (deref focused-value*)
|
||||||
|
|
||||||
|
has-focus* (mf/use-state false)
|
||||||
|
has-focus (deref has-focus*)
|
||||||
|
|
||||||
listbox-id-ref (mf/use-ref (dm/str "select-listbox-" (swap! listbox-id-index inc)))
|
listbox-id-ref (mf/use-ref (dm/str "select-listbox-" (swap! listbox-id-index inc)))
|
||||||
options-nodes-refs (mf/use-ref nil)
|
options-nodes-refs (mf/use-ref nil)
|
||||||
|
@ -80,38 +91,11 @@
|
||||||
select-ref (mf/use-ref nil)
|
select-ref (mf/use-ref nil)
|
||||||
listbox-id (mf/ref-val listbox-id-ref)
|
listbox-id (mf/ref-val listbox-id-ref)
|
||||||
|
|
||||||
selected* (mf/use-state #(get-selected-option-id options default-selected))
|
empty-selected-value? (str/blank? selected-value)
|
||||||
selected (deref selected*)
|
|
||||||
|
|
||||||
focused* (mf/use-state nil)
|
set-option-ref
|
||||||
focused (deref focused*)
|
|
||||||
|
|
||||||
has-focus* (mf/use-state false)
|
|
||||||
has-focus (deref has-focus*)
|
|
||||||
|
|
||||||
on-click
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps disabled)
|
|
||||||
(fn [event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(reset! has-focus* true)
|
|
||||||
(when-not disabled
|
|
||||||
(swap! open* not))))
|
|
||||||
|
|
||||||
on-option-click
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps on-change)
|
|
||||||
(fn [event]
|
|
||||||
(let [node (dom/get-current-target event)
|
|
||||||
id (dom/get-data node "id")]
|
|
||||||
(reset! selected* id)
|
|
||||||
(reset! focused* nil)
|
|
||||||
(reset! open* false)
|
|
||||||
(when (fn? on-change)
|
|
||||||
(on-change id)))))
|
|
||||||
|
|
||||||
set-ref
|
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
(mf/deps options-nodes-refs)
|
||||||
(fn [node id]
|
(fn [node id]
|
||||||
(let [refs (or (mf/ref-val options-nodes-refs) #js {})
|
(let [refs (or (mf/ref-val options-nodes-refs) #js {})
|
||||||
refs (if node
|
refs (if node
|
||||||
|
@ -119,47 +103,69 @@
|
||||||
(obj/unset! refs id))]
|
(obj/unset! refs id))]
|
||||||
(mf/set-ref-val! options-nodes-refs refs))))
|
(mf/set-ref-val! options-nodes-refs refs))))
|
||||||
|
|
||||||
on-blur
|
on-option-click
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-change)
|
||||||
|
(fn [event]
|
||||||
|
(let [node (dom/get-current-target event)
|
||||||
|
id (dom/get-data node "id")]
|
||||||
|
(reset! selected-value* id)
|
||||||
|
(reset! focused-value* nil)
|
||||||
|
(reset! is-open* false)
|
||||||
|
(when (fn? on-change)
|
||||||
|
(on-change id)))))
|
||||||
|
|
||||||
|
on-component-click
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps disabled)
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(reset! has-focus* true)
|
||||||
|
(when-not disabled
|
||||||
|
(swap! is-open* not))))
|
||||||
|
|
||||||
|
on-component-blur
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [target (.-relatedTarget event)
|
(let [target (.-relatedTarget event)
|
||||||
outside? (not (.contains (mf/ref-val select-ref) target))]
|
outside? (not (.contains (mf/ref-val select-ref) target))]
|
||||||
(when outside?
|
(when outside?
|
||||||
(reset! focused* nil)
|
(reset! focused-value* nil)
|
||||||
(reset! open* false)
|
(reset! is-open* false)
|
||||||
(reset! has-focus* false)))))
|
(reset! has-focus* false)))))
|
||||||
|
|
||||||
on-key-down
|
on-component-focus
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps focused disabled)
|
(fn [_]
|
||||||
|
(reset! has-focus* true)))
|
||||||
|
|
||||||
|
on-button-key-down
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps focused-value disabled)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
(when-not disabled
|
(when-not disabled
|
||||||
(let [options (mf/ref-val options-ref)
|
(let [options (mf/ref-val options-ref)
|
||||||
len (alength options)
|
len (alength options)
|
||||||
index (array/find-index #(= (deref focused*) (obj/get % "id")) options)]
|
index (array/find-index #(= (deref focused-value*) (obj/get % "id")) options)]
|
||||||
(dom/stop-propagation event)
|
|
||||||
(cond
|
(cond
|
||||||
(kbd/home? event)
|
(kbd/home? event)
|
||||||
(handle-focus-change options focused* 0 options-nodes-refs)
|
(handle-focus-change options focused-value* 0 options-nodes-refs)
|
||||||
|
|
||||||
(kbd/up-arrow? event)
|
(kbd/up-arrow? event)
|
||||||
(handle-focus-change options focused* (mod (- index 1) len) options-nodes-refs)
|
(handle-focus-change options focused-value* (mod (- index 1) len) options-nodes-refs)
|
||||||
|
|
||||||
(kbd/down-arrow? event)
|
(kbd/down-arrow? event)
|
||||||
(handle-focus-change options focused* (mod (+ index 1) len) options-nodes-refs)
|
(handle-focus-change options focused-value* (mod (+ index 1) len) options-nodes-refs)
|
||||||
|
|
||||||
(or (kbd/space? event) (kbd/enter? event))
|
(or (kbd/space? event) (kbd/enter? event))
|
||||||
(when (deref open*)
|
(when (deref is-open*)
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(handle-selection focused* selected* open*))
|
(handle-selection focused-value* selected-value* is-open*))
|
||||||
|
|
||||||
(kbd/esc? event)
|
(kbd/esc? event)
|
||||||
(do (reset! open* false)
|
(do (reset! is-open* false)
|
||||||
(reset! focused* nil)))))))
|
(reset! focused-value* nil)))))))
|
||||||
|
|
||||||
on-focus
|
|
||||||
(mf/use-fn
|
|
||||||
(fn [_] (reset! has-focus* true)))
|
|
||||||
|
|
||||||
class (dm/str class " " (stl/css-case :select true
|
class (dm/str class " " (stl/css-case :select true
|
||||||
:focused has-focus))
|
:focused has-focus))
|
||||||
|
@ -168,13 +174,13 @@
|
||||||
:role "combobox"
|
:role "combobox"
|
||||||
:aria-controls listbox-id
|
:aria-controls listbox-id
|
||||||
:aria-haspopup "listbox"
|
:aria-haspopup "listbox"
|
||||||
:aria-activedescendant focused
|
:aria-activedescendant focused-value
|
||||||
:aria-expanded open
|
:aria-expanded is-open
|
||||||
:on-key-down on-key-down
|
:on-key-down on-button-key-down
|
||||||
:disabled disabled
|
:disabled disabled
|
||||||
:on-click on-click})
|
:on-click on-component-click})
|
||||||
|
|
||||||
selected-option (get-option options selected)
|
selected-option (get-option options selected-value)
|
||||||
label (obj/get selected-option "label")
|
label (obj/get selected-option "label")
|
||||||
icon (obj/get selected-option "icon")]
|
icon (obj/get selected-option "icon")]
|
||||||
|
|
||||||
|
@ -182,10 +188,11 @@
|
||||||
(mf/set-ref-val! options-ref options))
|
(mf/set-ref-val! options-ref options))
|
||||||
|
|
||||||
[:div {:class (stl/css :select-wrapper)
|
[:div {:class (stl/css :select-wrapper)
|
||||||
:on-click on-click
|
:on-click on-component-click
|
||||||
:on-focus on-focus
|
:on-focus on-component-focus
|
||||||
:ref select-ref
|
:ref select-ref
|
||||||
:on-blur on-blur}
|
:on-blur on-component-blur}
|
||||||
|
|
||||||
[:> :button props
|
[:> :button props
|
||||||
[:span {:class (stl/css-case :select-header true
|
[:span {:class (stl/css-case :select-header true
|
||||||
:header-icon (some? icon))}
|
:header-icon (some? icon))}
|
||||||
|
@ -193,16 +200,19 @@
|
||||||
[:> icon* {:icon-id icon
|
[:> icon* {:icon-id icon
|
||||||
:size "s"
|
:size "s"
|
||||||
:aria-hidden true}])
|
:aria-hidden true}])
|
||||||
[:span {:class (stl/css :header-label)}
|
[:span {:class (stl/css-case :header-label true
|
||||||
label]]
|
:header-label-dimmed empty-selected-value?)}
|
||||||
|
(if empty-selected-value? "--" label)]]
|
||||||
[:> icon* {:icon-id i/arrow
|
[:> icon* {:icon-id i/arrow
|
||||||
:class (stl/css :arrow)
|
:class (stl/css :arrow)
|
||||||
:size "m"
|
:size "m"
|
||||||
:aria-hidden true}]]
|
:aria-hidden true}]]
|
||||||
(when open
|
|
||||||
|
(when is-open
|
||||||
[:> options-dropdown* {:on-click on-option-click
|
[:> options-dropdown* {:on-click on-option-click
|
||||||
:id listbox-id
|
:id listbox-id
|
||||||
:options options
|
:options options
|
||||||
:selected selected
|
:selected selected-value
|
||||||
:focused focused
|
:focused focused-value
|
||||||
:set-ref set-ref}])]))
|
:empty-to-end empty-to-end
|
||||||
|
:set-ref set-option-ref}])]))
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
.select-wrapper {
|
.select-wrapper {
|
||||||
--select-icon-fg-color: var(--color-foreground-secondary);
|
--select-icon-fg-color: var(--color-foreground-secondary);
|
||||||
--select-fg-color: var(--color-foreground-primary);
|
--select-fg-color: var(--color-foreground-primary);
|
||||||
|
--select-fg-color-dimmed: var(--color-foreground-secondary);
|
||||||
--select-bg-color: var(--color-background-tertiary);
|
--select-bg-color: var(--color-background-tertiary);
|
||||||
--select-outline-color: none;
|
--select-outline-color: none;
|
||||||
--select-border-color: none;
|
--select-border-color: none;
|
||||||
|
@ -80,6 +81,10 @@
|
||||||
color: var(--select-fg-color);
|
color: var(--select-fg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-label-dimmed {
|
||||||
|
color: var(--select-fg-color-dimmed);
|
||||||
|
}
|
||||||
|
|
||||||
.header-icon {
|
.header-icon {
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
color: var(--select-icon-fg-color);
|
color: var(--select-icon-fg-color);
|
||||||
|
|
|
@ -9,12 +9,14 @@
|
||||||
[app.main.style :as stl])
|
[app.main.style :as stl])
|
||||||
(:require
|
(:require
|
||||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||||
|
[app.util.array :as array]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(mf/defc option*
|
(mf/defc option*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [id label icon aria-label on-click selected set-ref focused] :rest props}]
|
[{:keys [id label icon aria-label on-click selected set-ref focused dimmed] :rest props}]
|
||||||
|
|
||||||
[:> :li {:value id
|
[:> :li {:value id
|
||||||
:class (stl/css-case :option true
|
:class (stl/css-case :option true
|
||||||
|
@ -38,7 +40,8 @@
|
||||||
:aria-hidden (when label true)
|
:aria-hidden (when label true)
|
||||||
:aria-label (when (not label) aria-label)}])
|
:aria-label (when (not label) aria-label)}])
|
||||||
|
|
||||||
[:span {:class (stl/css :option-text)} label]
|
[:span {:class (stl/css-case :option-text true
|
||||||
|
:option-text-dimmed dimmed)} label]
|
||||||
(when selected
|
(when selected
|
||||||
[:> icon*
|
[:> icon*
|
||||||
{:icon-id i/tick
|
{:icon-id i/tick
|
||||||
|
@ -48,11 +51,18 @@
|
||||||
|
|
||||||
(mf/defc options-dropdown*
|
(mf/defc options-dropdown*
|
||||||
{::mf/props :obj}
|
{::mf/props :obj}
|
||||||
[{:keys [set-ref on-click options selected focused] :rest props}]
|
[{:keys [set-ref on-click options selected focused empty-to-end] :rest props}]
|
||||||
(let [props (mf/spread-props props
|
(let [props (mf/spread-props props
|
||||||
{:class (stl/css :option-list)
|
{:class (stl/css :option-list)
|
||||||
:tab-index "-1"
|
:tab-index "-1"
|
||||||
:role "listbox"})]
|
:role "listbox"})
|
||||||
|
|
||||||
|
options-blank (when empty-to-end
|
||||||
|
(array/filter #(str/blank? (obj/get % "id")) options))
|
||||||
|
options (if empty-to-end
|
||||||
|
(array/filter #((complement str/blank?) (obj/get % "id")) options)
|
||||||
|
options)]
|
||||||
|
|
||||||
[:> "ul" props
|
[:> "ul" props
|
||||||
(for [option ^js options]
|
(for [option ^js options]
|
||||||
(let [id (obj/get option "id")
|
(let [id (obj/get option "id")
|
||||||
|
@ -67,4 +77,26 @@
|
||||||
:aria-label aria-label
|
:aria-label aria-label
|
||||||
:set-ref set-ref
|
:set-ref set-ref
|
||||||
:focused (= id focused)
|
:focused (= id focused)
|
||||||
:on-click on-click}]))]))
|
:dimmed false
|
||||||
|
:on-click on-click}]))
|
||||||
|
|
||||||
|
(when (seq options-blank)
|
||||||
|
[:*
|
||||||
|
(when (seq options)
|
||||||
|
[:hr {:class (stl/css :option-separator)}])
|
||||||
|
|
||||||
|
(for [option ^js options-blank]
|
||||||
|
(let [id (obj/get option "id")
|
||||||
|
label (obj/get option "label")
|
||||||
|
aria-label (obj/get option "aria-label")
|
||||||
|
icon (obj/get option "icon")]
|
||||||
|
[:> option* {:selected (= id selected)
|
||||||
|
:key id
|
||||||
|
:id id
|
||||||
|
:label label
|
||||||
|
:icon icon
|
||||||
|
:aria-label aria-label
|
||||||
|
:set-ref set-ref
|
||||||
|
:focused (= id focused)
|
||||||
|
:dimmed true
|
||||||
|
:on-click on-click}]))])]))
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
--options-dropdown-bg-color: var(--color-background-tertiary);
|
--options-dropdown-bg-color: var(--color-background-tertiary);
|
||||||
--options-dropdown-outline-color: none;
|
--options-dropdown-outline-color: none;
|
||||||
--options-dropdown-border-color: var(--color-background-quaternary);
|
--options-dropdown-border-color: var(--color-background-quaternary);
|
||||||
|
--options-dropdown-empty: var(--color-canvas);
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -66,6 +67,10 @@
|
||||||
padding-inline-start: var(--sp-xxs);
|
padding-inline-start: var(--sp-xxs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.option-text-dimmed {
|
||||||
|
color: var(--options-dropdown-empty);
|
||||||
|
}
|
||||||
|
|
||||||
.option-icon {
|
.option-icon {
|
||||||
color: var(--options-dropdown-icon-fg-color);
|
color: var(--options-dropdown-icon-fg-color);
|
||||||
}
|
}
|
||||||
|
@ -79,3 +84,9 @@
|
||||||
--options-dropdown-fg-color: var(--color-accent-primary);
|
--options-dropdown-fg-color: var(--color-accent-primary);
|
||||||
--options-dropdown-icon-fg-color: var(--color-accent-primary);
|
--options-dropdown-icon-fg-color: var(--color-accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.option-separator {
|
||||||
|
border: $b-1 solid var(--options-dropdown-border-color);
|
||||||
|
margin-top: var(--sp-xs);
|
||||||
|
margin-bottom: var(--sp-xs);
|
||||||
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||||
[app.main.ui.ds.controls.combobox :refer [combobox*]]
|
[app.main.ui.ds.controls.combobox :refer [combobox*]]
|
||||||
|
[app.main.ui.ds.controls.select :refer [select*]]
|
||||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||||
[app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]]
|
[app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]]
|
||||||
[app.main.ui.hooks :as h]
|
[app.main.ui.hooks :as h]
|
||||||
|
@ -236,7 +237,9 @@
|
||||||
[:div {:class (stl/css :counter)} (str size "/300")])]])))
|
[:div {:class (stl/css :counter)} (str size "/300")])]])))
|
||||||
|
|
||||||
|
|
||||||
(defn- get-variant-error-message [errors]
|
(defn- get-variant-error-message
|
||||||
|
"Generate error message depending on the selected variants"
|
||||||
|
[errors]
|
||||||
(cond
|
(cond
|
||||||
(and (= (count errors) 1) (some? (first errors)))
|
(and (= (count errors) 1) (some? (first errors)))
|
||||||
(tr "workspace.options.component.variant.malformed.single.one")
|
(tr "workspace.options.component.variant.malformed.single.one")
|
||||||
|
@ -250,6 +253,16 @@
|
||||||
:else nil))
|
:else nil))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- get-variant-options
|
||||||
|
"Get variant options for a given property name"
|
||||||
|
[prop-name prop-vals]
|
||||||
|
(->> (filter #(= (:name %) prop-name) prop-vals)
|
||||||
|
first
|
||||||
|
:value
|
||||||
|
(map (fn [val] {:id val
|
||||||
|
:label (if (str/blank? val) (str "(" (tr "labels.empty") ")") val)}))))
|
||||||
|
|
||||||
|
|
||||||
(mf/defc component-variant-main-instance*
|
(mf/defc component-variant-main-instance*
|
||||||
[{:keys [components shapes data]}]
|
[{:keys [components shapes data]}]
|
||||||
(let [component (first components)
|
(let [component (first components)
|
||||||
|
@ -271,23 +284,17 @@
|
||||||
variant-errors (mapv :variant-error shapes)
|
variant-errors (mapv :variant-error shapes)
|
||||||
variant-error-msg (get-variant-error-message variant-errors)
|
variant-error-msg (get-variant-error-message variant-errors)
|
||||||
|
|
||||||
empty-indicator "--"
|
|
||||||
|
|
||||||
get-options
|
get-options
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps prop-vals)
|
(mf/deps prop-vals)
|
||||||
(fn [prop-name]
|
(fn [prop-name]
|
||||||
(->> (filter #(= (:name %) prop-name) prop-vals)
|
(get-variant-options prop-name prop-vals)))
|
||||||
first
|
|
||||||
:value
|
|
||||||
(map (fn [val] {:label val :id val})))))
|
|
||||||
|
|
||||||
update-property-value
|
update-property-value
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps component-ids)
|
(mf/deps component-ids)
|
||||||
(fn [pos value]
|
(fn [pos value]
|
||||||
(let [value (str/trim value)
|
(let [value (d/nilv (str/trim value) "")]
|
||||||
value (if (= value empty-indicator) "" value)]
|
|
||||||
(doseq [id component-ids]
|
(doseq [id component-ids]
|
||||||
(st/emit! (dwv/update-property-value id pos value))
|
(st/emit! (dwv/update-property-value id pos value))
|
||||||
(st/emit! (dwv/update-error id nil))))))
|
(st/emit! (dwv/update-error id nil))))))
|
||||||
|
@ -315,9 +322,10 @@
|
||||||
|
|
||||||
(let [mixed-value? (= (:value prop) false)]
|
(let [mixed-value? (= (:value prop) false)]
|
||||||
[:> combobox* {:id (str "variant-prop-" variant-id "-" pos)
|
[:> combobox* {:id (str "variant-prop-" variant-id "-" pos)
|
||||||
:placeholder (if mixed-value? (tr "settings.multiple") empty-indicator)
|
:placeholder (if mixed-value? (tr "settings.multiple") "--")
|
||||||
:default-selected (if mixed-value? "" (:value prop))
|
:default-selected (if mixed-value? "" (:value prop))
|
||||||
:options (clj->js (get-options (:name prop)))
|
:options (clj->js (get-options (:name prop)))
|
||||||
|
:empty-to-end true
|
||||||
:on-change (partial update-property-value pos)}])]])]
|
:on-change (partial update-property-value pos)}])]])]
|
||||||
|
|
||||||
(when variant-error-msg
|
(when variant-error-msg
|
||||||
|
@ -329,6 +337,7 @@
|
||||||
[:div {:class (stl/css :variant-error-darken)}
|
[:div {:class (stl/css :variant-error-darken)}
|
||||||
(tr "workspace.options.component.variant.malformed.structure.example")]])]))
|
(tr "workspace.options.component.variant.malformed.structure.example")]])]))
|
||||||
|
|
||||||
|
|
||||||
(mf/defc component-variant*
|
(mf/defc component-variant*
|
||||||
[{:keys [component shape data]}]
|
[{:keys [component shape data]}]
|
||||||
(let [component-id (:id component)
|
(let [component-id (:id component)
|
||||||
|
@ -342,13 +351,11 @@
|
||||||
prop-vals (mf/with-memo [data objects variant-id]
|
prop-vals (mf/with-memo [data objects variant-id]
|
||||||
(cfv/extract-properties-values data objects variant-id))
|
(cfv/extract-properties-values data objects variant-id))
|
||||||
|
|
||||||
get-options-vals
|
get-options
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps prop-vals)
|
(mf/deps prop-vals)
|
||||||
(fn [prop-name]
|
(fn [prop-name]
|
||||||
(->> (filter #(= (:name %) prop-name) prop-vals)
|
(get-variant-options prop-name prop-vals)))
|
||||||
first
|
|
||||||
:value)))
|
|
||||||
|
|
||||||
switch-component
|
switch-component
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
@ -360,7 +367,7 @@
|
||||||
valid-comps (->> variant-components
|
valid-comps (->> variant-components
|
||||||
(remove #(= (:id %) component-id))
|
(remove #(= (:id %) component-id))
|
||||||
(filter #(= (dm/get-in % [:variant-properties pos :value]) val)))
|
(filter #(= (dm/get-in % [:variant-properties pos :value]) val)))
|
||||||
nearest-comp (apply min-key #(ctv/distance target-props (:variant-properties %)) valid-comps)]
|
nearest-comp (apply min-key #(ctv/distance target-props (:variant-properties %)) valid-comps)]
|
||||||
(when nearest-comp
|
(when nearest-comp
|
||||||
(st/emit! (dwl/component-swap shape (:component-file shape) (:id nearest-comp) true)))))))]
|
(st/emit! (dwl/component-swap shape (:component-file shape) (:id nearest-comp) true)))))))]
|
||||||
|
|
||||||
|
@ -370,9 +377,11 @@
|
||||||
[:*
|
[:*
|
||||||
[:span {:class (stl/css :variant-property-name)}
|
[:span {:class (stl/css :variant-property-name)}
|
||||||
(:name prop)]
|
(:name prop)]
|
||||||
[:& select {:default-value (if (str/empty? (:value prop)) "--" (:value prop))
|
[:> select* {:default-selected (:value prop)
|
||||||
:options (clj->js (get-options-vals (:name prop)))
|
:options (clj->js (get-options (:name prop)))
|
||||||
:on-change #(switch-component pos %)}]]])]))
|
:empty-to-end true
|
||||||
|
:on-change (partial switch-component pos)}]]])]))
|
||||||
|
|
||||||
|
|
||||||
(mf/defc component-swap-item
|
(mf/defc component-swap-item
|
||||||
{::mf/props :obj}
|
{::mf/props :obj}
|
||||||
|
|
|
@ -2051,6 +2051,10 @@ msgstr "Edit file"
|
||||||
msgid "labels.editor"
|
msgid "labels.editor"
|
||||||
msgstr "Editor"
|
msgstr "Editor"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:263
|
||||||
|
msgid "labels.empty"
|
||||||
|
msgstr "Empty"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/import.cljs:294
|
#: src/app/main/ui/dashboard/import.cljs:294
|
||||||
msgid "labels.error"
|
msgid "labels.error"
|
||||||
msgstr "Error"
|
msgstr "Error"
|
||||||
|
|
|
@ -2088,6 +2088,10 @@ msgstr "Editar archivo"
|
||||||
msgid "labels.editor"
|
msgid "labels.editor"
|
||||||
msgstr "Edición"
|
msgstr "Edición"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:263
|
||||||
|
msgid "labels.empty"
|
||||||
|
msgstr "Vacío"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/import.cljs:294
|
#: src/app/main/ui/dashboard/import.cljs:294
|
||||||
msgid "labels.error"
|
msgid "labels.error"
|
||||||
msgstr "Error"
|
msgstr "Error"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue