From f22633d18b32e4fc12c22df5504a5d976e48400a Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 21 Oct 2025 13:43:05 +0200 Subject: [PATCH] :construction: Add dropdown to component --- .../ds/controls/shared/options_dropdown.cljs | 3 +- .../ui/ds/controls/shared/token_option.cljs | 13 +- .../app/main/ui/ds/controls/token_input.cljs | 50 ++--- .../src/app/main/ui/workspace/sidebar.cljs | 96 ++++---- .../main/ui/workspace/tokens/management.cljs | 9 +- .../tokens/management/create/form.cljs | 39 +++- .../management/create/input_tokens_value.cljs | 210 +++++++++++++++++- .../tokens/management/create/modals.cljs | 5 +- .../ui/workspace/tokens/management/group.cljs | 3 +- .../tokens/management/token_pill.cljs | 35 +-- 10 files changed, 346 insertions(+), 117 deletions(-) diff --git a/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.cljs b/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.cljs index d604b60319..0845765e16 100644 --- a/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.cljs +++ b/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.cljs @@ -100,7 +100,8 @@ (mf/defc options-dropdown* - {::mf/schema schema:options-dropdown} + ;; TODO: Review schema + ;; {::mf/schema schema:options-dropdown} [{:keys [ref on-click options selected focused empty-to-end align] :rest props}] (let [align (d/nilv align :left) diff --git a/frontend/src/app/main/ui/ds/controls/shared/token_option.cljs b/frontend/src/app/main/ui/ds/controls/shared/token_option.cljs index f60e5350ae..285f3ca19f 100644 --- a/frontend/src/app/main/ui/ds/controls/shared/token_option.cljs +++ b/frontend/src/app/main/ui/ds/controls/shared/token_option.cljs @@ -25,7 +25,7 @@ [:focused {:optional true} :boolean]]) (mf/defc token-option* - {::mf/schema schema:token-option} + ;; {::mf/schema schema:token-option} [{:keys [id name on-click selected ref focused resolved] :rest props}] (let [internal-id (mf/use-id) id (d/nilv id internal-id)] @@ -56,5 +56,12 @@ [:span {:aria-labelledby (dm/str id "-name")} name]] (when resolved - [:> :span {:class (stl/css :option-pill)} - resolved])])) + (cond + (map? resolved) + (for [[k v] resolved] + [:div {:key (str k)} + [:span (dm/str (d/name k) ": ")] + [:strong (str v)]]) + :else + [:span {:class (stl/css :option-pill)} + resolved]))])) diff --git a/frontend/src/app/main/ui/ds/controls/token_input.cljs b/frontend/src/app/main/ui/ds/controls/token_input.cljs index e200172db0..58f8442e08 100644 --- a/frontend/src/app/main/ui/ds/controls/token_input.cljs +++ b/frontend/src/app/main/ui/ds/controls/token_input.cljs @@ -120,35 +120,33 @@ sorted-tokens) no-sets? (nil? sorted-tokens)] (generate-dropdown-options options no-sets?)))) - on-option-click + on-option-click (mf/use-fn - (mf/deps ) - (fn [event] - )) - focused-id* (mf/use-state nil) + (mf/deps) + (fn [event])) + focused-id* (mf/use-state nil) focused-id (deref focused-id*) - selected-id* - (mf/use-state (fn [] - )) - empty-to-end (d/nilv empty-to-end false) - selected-id + selected-id* + (mf/use-state (fn [])) + empty-to-end (d/nilv empty-to-end false) + selected-id (deref selected-id*) - listbox-id (mf/use-id) - nodes-ref (mf/use-ref nil) - set-option-ref - (mf/use-fn - (fn [node] - (let [state (mf/ref-val nodes-ref) - state (d/nilv state #js {}) - id (dom/get-data node "id") - state (obj/set! state id node)] - (mf/set-ref-val! nodes-ref state) - (fn [] - (let [state (mf/ref-val nodes-ref) - state (d/nilv state #js {}) - id (dom/get-data node "id") - state (obj/unset! state id)] - (mf/set-ref-val! nodes-ref state)))))) + listbox-id (mf/use-id) + nodes-ref (mf/use-ref nil) + set-option-ref + (mf/use-fn + (fn [node] + (let [state (mf/ref-val nodes-ref) + state (d/nilv state #js {}) + id (dom/get-data node "id") + state (obj/set! state id node)] + (mf/set-ref-val! nodes-ref state) + (fn [] + (let [state (mf/ref-val nodes-ref) + state (d/nilv state #js {}) + id (dom/get-data node "id") + state (obj/unset! state id)] + (mf/set-ref-val! nodes-ref state)))))) props (mf/spread-props props {:type type :id id :has-hint has-hint diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index a9733567d7..dd494fe903 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -155,61 +155,65 @@ tabs-action-button (mf/with-memo [] - (mf/html [:> collapse-button* {}]))] + (mf/html [:> collapse-button* {}])) - [:> (mf/provider muc/sidebar) {:value :left} - [:aside {:ref parent-ref - :id "left-sidebar-aside" - :data-testid "left-sidebar" - :data-left-sidebar-width (str width) - :class aside-class - :style {:--left-sidebar-width (dm/str width "px")}} + active-tokens-by-type + (mf/with-memo [resolved-active-tokens] + (delay (ctob/group-by-type resolved-active-tokens)))] - [:> left-header* - {:file file - :layout layout - :project project - :page-id page-id - :class (stl/css :left-header)}] + [:> (mf/provider muc/active-tokens-by-type) {:value active-tokens-by-type} + [:> (mf/provider muc/sidebar) {:value :left} + [:aside {:ref parent-ref + :id "left-sidebar-aside" + :data-testid "left-sidebar" + :data-left-sidebar-width (str width) + :class aside-class + :style {:--left-sidebar-width (dm/str width "px")}} - [:div {:on-pointer-down on-pointer-down - :on-lost-pointer-capture on-lost-pointer-capture - :on-pointer-move on-pointer-move - :class (stl/css :resize-area)}] + [:> left-header* + {:file file + :layout layout + :project project + :page-id page-id + :class (stl/css :left-header)}] - (cond - (true? shortcuts?) - [:> shortcuts-container* {:class (stl/css :settings-bar-content)}] + [:div {:on-pointer-down on-pointer-down + :on-lost-pointer-capture on-lost-pointer-capture + :on-pointer-move on-pointer-move + :class (stl/css :resize-area)}] - (true? show-debug?) - [:> debug-panel* {:class (stl/css :settings-bar-content)}] + (cond + (true? shortcuts?) + [:> shortcuts-container* {:class (stl/css :settings-bar-content)}] - :else - [:div {:class (stl/css :settings-bar-content)} - [:> tab-switcher* {:tabs tabs - :default "layers" - :selected (name section) - :on-change on-tab-change - :class (stl/css :left-sidebar-tabs) - :action-button-position "start" - :action-button tabs-action-button} + (true? show-debug?) + [:> debug-panel* {:class (stl/css :settings-bar-content)}] - (case section - :assets - [:> assets-toolbox* - {:size (- width 58) - :file-id file-id}] + :else + [:div {:class (stl/css :settings-bar-content)} + [:> tab-switcher* {:tabs tabs + :default "layers" + :selected (name section) + :on-change on-tab-change + :class (stl/css :left-sidebar-tabs) + :action-button-position "start" + :action-button tabs-action-button} - :tokens - [:> tokens-sidebar-tab* - {:tokens-lib tokens-lib - :active-tokens active-tokens - :resolved-active-tokens resolved-active-tokens}] + (case section + :assets + [:> assets-toolbox* + {:size (- width 58) + :file-id file-id}] - :layers - [:> layers-content* - {:layout layout - :width width}])]])]])) + :tokens + [:> tokens-sidebar-tab* + {:tokens-lib tokens-lib + :active-tokens active-tokens}] + + :layers + [:> layers-content* + {:layout layout + :width width}])]])]]])) ;; --- Right Sidebar (Component) diff --git a/frontend/src/app/main/ui/workspace/tokens/management.cljs b/frontend/src/app/main/ui/workspace/tokens/management.cljs index 91a6bc768b..942288d5ee 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management.cljs @@ -11,6 +11,7 @@ [app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.context :as muc] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.typography.text :refer [text*]] [app.main.ui.workspace.tokens.management.context-menu :refer [token-context-menu]] @@ -115,7 +116,9 @@ [empty-group filled-group] (mf/with-memo [tokens-by-type] - (get-sorted-token-groups tokens-by-type))] + (get-sorted-token-groups tokens-by-type)) + + active-theme-tokens (mf/use-ctx muc/active-tokens-by-type)] (mf/with-effect [tokens-lib selected-token-set-id] (when (and tokens-lib @@ -150,7 +153,7 @@ :selected-ids selected :selected-shapes selected-shapes :is-selected-inside-layout is-selected-inside-layout - :active-theme-tokens resolved-active-tokens + :active-theme-tokens active-theme-tokens :tokens tokens}])) (for [type empty-group] @@ -158,5 +161,5 @@ :type type :selected-shapes selected-shapes :is-selected-inside-layout :is-selected-inside-layout - :active-theme-tokens resolved-active-tokens + :active-theme-tokens active-theme-tokens :tokens []}])])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs index 2652c35395..e3ed6a6ad9 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs @@ -294,7 +294,7 @@ selected-token-set-id action input-value-placeholder - + tokens ;; Callbacks validate-token on-value-resolve @@ -622,6 +622,8 @@ :label label :default-value default-value :ref ref + :tokens tokens + :type token-type :on-blur on-update-value :on-change on-update-value :token-resolve-result token-resolve-result}]))] @@ -662,13 +664,15 @@ ;; Tabs Component -------------------------------------------------------------- (mf/defc composite-reference-input* - [{:keys [default-value on-blur on-update-value token-resolve-result reference-label reference-icon is-reference-fn]}] + [{:keys [default-value on-blur on-update-value token-resolve-result reference-label reference-icon is-reference-fn tokens]}] [:> input-token* {:aria-label (tr "labels.reference") :placeholder reference-label :icon reference-icon :default-value (when (is-reference-fn default-value) default-value) :on-blur on-blur + :tokens tokens + :type "composite-reference" :on-change on-update-value :token-resolve-result (when (or (:errors token-resolve-result) @@ -681,6 +685,7 @@ on-external-update-value on-value-resolve clear-resolve-value + tokens custom-input-token-value-props] :rest props}] (let [;; Active Tab State @@ -751,6 +756,7 @@ :on-update-value on-update-value' :reference-icon reference-icon :reference-label reference-label + :tokens tokens :is-reference-fn is-reference-fn})] [:> composite-tab (mf/spread-props props {:default-value default-value @@ -760,7 +766,7 @@ (mf/defc composite-form* "Wrapper around form* that manages composite/reference tab state. Takes the same props as form* plus a function to determine if a token value is a reference." - [{:keys [token is-reference-fn composite-tab reference-icon title update-composite-backup-value] :rest props}] + [{:keys [token is-reference-fn composite-tab reference-icon title update-composite-backup-value tokens] :rest props}] (let [active-tab* (mf/use-state (if (is-reference-fn (:value token)) :reference :composite)) active-tab (deref active-tab*) @@ -772,6 +778,7 @@ :set-active-tab #(reset! active-tab* %) :composite-tab composite-tab :reference-icon reference-icon + :tokens tokens :reference-label (tr "workspace.tokens.reference-composite") :title title :update-composite-backup-value update-composite-backup-value @@ -848,8 +855,8 @@ :on-change on-change'}]])) (mf/defc color-picker* - [{:keys [placeholder label default-value input-ref on-blur on-update-value on-external-update-value custom-input-token-value-props token-resolve-result]}] - (let [{:keys [color on-display-colorpicker]} custom-input-token-value-props + [{:keys [ placeholder label default-value input-ref on-blur on-update-value on-external-update-value custom-input-token-value-props token-resolve-result]}] + (let [{:keys [color on-display-colorpicker tokens]} custom-input-token-value-props color-ramp-open* (mf/use-state false) color-ramp-open? (deref color-ramp-open*) @@ -903,6 +910,8 @@ :default-value default-value :ref input-ref :on-blur on-blur + :tokens tokens + :type "color" :on-change on-update-value :slot-start swatch}] (when color-ramp-open? @@ -912,7 +921,7 @@ [:> token-value-hint* {:result token-resolve-result}]])) (mf/defc color-form* - [{:keys [token on-display-colorpicker] :rest props}] + [{:keys [token on-display-colorpicker tokens] :rest props}] (let [color* (mf/use-state (:value token)) color (deref color*) on-value-resolve (mf/use-fn @@ -926,6 +935,7 @@ (mf/deps color on-display-colorpicker) (fn [] {:color color + :tokens tokens :on-display-colorpicker on-display-colorpicker})) on-get-token-value @@ -1233,7 +1243,7 @@ :full-size true}]])) (mf/defc font-picker-combobox* - [{:keys [default-value label aria-label input-ref on-blur on-update-value on-external-update-value token-resolve-result placeholder]}] + [{:keys [tokens default-value label aria-label input-ref on-blur on-update-value on-external-update-value token-resolve-result placeholder]}] (let [font* (mf/use-state (fonts/find-font-family default-value)) font (deref font*) set-font (mf/use-fn @@ -1287,6 +1297,8 @@ :ref input-ref :on-blur on-blur :on-change on-update-value' + :tokens tokens + :type "font-family" :icon i/text-font-family :slot-end font-selector-button :token-resolve-result token-resolve-result}] @@ -1359,7 +1371,7 @@ :placeholder (tr "workspace.tokens.text-decoration-value-enter")})) (mf/defc typography-value-inputs* - [{:keys [default-value on-blur on-update-value token-resolve-result]}] + [{:keys [default-value on-blur on-update-value token-resolve-result tokens]}] (let [composite-token? (not (cto/typography-composite-token-reference? (:value token-resolve-result))) typography-inputs (mf/use-memo typography-inputs) errors-by-key (sd/collect-typography-errors token-resolve-result)] @@ -1407,6 +1419,7 @@ :input-ref input-ref :default-value (when value (cto/join-font-family value)) :on-blur on-blur + :tokens tokens :on-update-value on-change :on-external-update-value on-external-update-value :token-resolve-result token-prop}] @@ -1415,12 +1428,14 @@ :placeholder placeholder :default-value value :on-blur on-blur + :tokens tokens :icon icon + :type "typography-subvalue" :on-change on-change :token-resolve-result token-prop}])]))])) (mf/defc typography-form* - [{:keys [token] :rest props}] + [{:keys [token tokens] :rest props}] (let [on-get-token-value (mf/use-fn (fn [e prev-composite-value] @@ -1451,13 +1466,15 @@ :is-reference-fn cto/typography-composite-token-reference? :title (tr "labels.typography") :validate-token validate-typography-token + :tokens tokens :on-get-token-value on-get-token-value :update-composite-backup-value update-composite-backup-value})])) (mf/defc form-wrapper* - [{:keys [token token-type] :rest props}] + [{:keys [token token-type tokens] :rest props}] (let [token-type' (or (:type token) token-type) - props (mf/spread-props props {:token-type token-type' + props (mf/spread-props props {:token-type token-type' + :tokens tokens :token token})] (case token-type' :color [:> color-form* props] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs index 9ab8d6f4ab..e8846804e2 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs @@ -7,18 +7,117 @@ (ns app.main.ui.workspace.tokens.management.create.input-tokens-value (:require-macros [app.main.style :as stl]) (:require + + [app.common.data :as d] [app.common.data.macros :as dm] [app.main.data.workspace.tokens.errors :as wte] [app.main.data.workspace.tokens.format :as dwtf] [app.main.data.workspace.tokens.warnings :as wtw] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown*]] [app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]] [app.main.ui.ds.controls.utilities.input-field :refer [input-field*]] [app.main.ui.ds.controls.utilities.label :refer [label*]] - [app.main.ui.ds.foundations.assets.icon :refer [icon-list]] + [app.main.ui.ds.foundations.assets.icon :refer [icon-list] :as i] + [app.util.dom :as dom] [app.util.i18n :refer [tr]] + [app.util.object :as obj] [cuerdas.core :as str] [rumext.v2 :as mf])) +(def token-type->reference-types + {:color #{:color} + :dimensions #{:dimensions} + :spacing #{:spacing :dimensions} + :border-radius #{:border-radius :dimensions :sizing} + :font-family #{:font-family} + :font-size #{:font-size :sizing :dimension} + :opacity #{:opacity :number} + :rotation #{:rotation :number} + :stroke-width #{:stroke-width :dimension :sizing} + :sizing #{:sizing :dimensions} + :number #{:number} + :letter-spacing #{:letter-spacing} + :typography #{:typography} + :text-case #{:text-case} + :text-decoration #{:text-decoration} + :line-height #{:number}}) + +;; TODO; duplucated code with numeric-input.cljs, consider refactoring +(defn- sort-groups-and-tokens + "Sorts both the groups and the tokens inside them alphabetically. + + Input: + A map where: + - keys are groups (keywords or strings, e.g. :dimensions, :colors) + - values are vectors of token maps, each containing at least a :name key + + Example input: + {:dimensions [{:name \"tres\"} {:name \"quini\"}] + :colors [{:name \"azul\"} {:name \"rojo\"}]} + + Output: + A sorted map where: + - groups are ordered alphabetically by key + - tokens inside each group are sorted alphabetically by :name + + Example output: + {:colors [{:name \"azul\"} {:name \"rojo\"}] + :dimensions [{:name \"quini\"} {:name \"tres\"}]}" + + [groups->tokens] + (into (sorted-map) ;; ensure groups are ordered alphabetically by their key + (for [[group tokens] groups->tokens] + [group (sort-by :name tokens)]))) + +(defn- extract-partial-brace-text + [string] + (when-let [start (str/last-index-of string "{")] + (subs string (inc start)))) + +(defn- filter-token-groups-by-name + [tokens filter-text] + (let [lc-filter (str/lower filter-text)] + (into {} + (keep (fn [[group tokens]] + (let [filtered (filter #(str/includes? (str/lower (:name %)) lc-filter) tokens)] + (when (seq filtered) + [group filtered])))) + tokens))) + +(defn- token->dropdown-option + [token] + {:id (str (get token :id)) + :type :token + :resolved-value (get token :resolved-value) + :name (get token :name)}) + +(defn- generate-dropdown-options + [tokens no-sets] + (if (empty? tokens) + [{:type :empty + :label (if no-sets + (tr "ds.inputs.numeric-input.no-applicable-tokens") + (tr "ds.inputs.numeric-input.no-matches"))}] + (->> tokens + (map (fn [[type items]] + (cons {:group true + :type :group + :id (dm/str "group-" (name type)) + :name (name type)} + (map token->dropdown-option items)))) + (interpose [{:separator true + :id "separator" + :type :separator}]) + (apply concat) + (vec) + (not-empty)))) +(defn get-option + [options id] + (let [options (if (delay? options) @options options)] + (or (d/seek #(= id (get % :id)) options) + (nth options 0)))) + (def ^:private schema::input-token [:map [:label {:optional true} [:maybe :string]] @@ -58,21 +157,118 @@ (mf/defc input-token* {::mf/forward-ref true ::mf/schema schema::input-token} - [{:keys [class label token-resolve-result] :rest props} ref] + [{:keys [class label token-resolve-result tokens empty-to-end type] :rest props} ref] (let [error (not (nil? (:errors token-resolve-result))) id (mf/use-id) - input-ref (mf/use-ref) + + is-open* (mf/use-state false) + is-open (deref is-open*) + + filter-term* (mf/use-state "") + filter-term (deref filter-term*) + + listbox-id (mf/use-id) + + focused-id* (mf/use-state nil) + focused-id (deref focused-id*) + + selected-id* (mf/use-state (fn [])) + selected-id (deref selected-id*) + + empty-to-end (d/nilv empty-to-end false) + + internal-ref (mf/use-ref nil) + ref (or ref internal-ref) + nodes-ref (mf/use-ref nil) + open-dropdown-ref (mf/use-ref nil) + options-ref (mf/use-ref nil) + set-option-ref + (mf/use-fn + (fn [node] + (let [state (mf/ref-val nodes-ref) + state (d/nilv state #js {}) + id (dom/get-data node "id") + state (obj/set! state id node)] + (mf/set-ref-val! nodes-ref state) + (fn [] + (let [state (mf/ref-val nodes-ref) + state (d/nilv state #js {}) + id (dom/get-data node "id") + state (obj/unset! state id)] + (mf/set-ref-val! nodes-ref state)))))) + + dropdown-options + (mf/with-memo [tokens filter-term type] + (delay + (let [tokens (if (delay? tokens) @tokens tokens) + allowed (get token-type->reference-types (keyword type) #{}) + tokens (select-keys tokens allowed) + sorted-tokens (sort-groups-and-tokens tokens) + partial (extract-partial-brace-text filter-term) + + options (if (seq partial) + (filter-token-groups-by-name sorted-tokens partial) + sorted-tokens) + no-sets? (nil? sorted-tokens)] + (generate-dropdown-options options no-sets?)))) + + update-input + (mf/use-fn + (mf/deps ref) + (fn [new-value] + (when-let [node (mf/ref-val ref)] + (dom/set-value! node new-value) + (reset! is-open* false)))) + + on-option-click + (mf/use-fn + (mf/deps options-ref update-input) + (fn [event] + (let [node (dom/get-current-target event) + id (dom/get-data node "id") + options (mf/ref-val options-ref) + options (if (delay? options) @options options) + option (get-option options id) + name (get option :name) + new-value (str "{" name "}")] + (update-input new-value)))) + + open-dropdown + (mf/use-fn + (fn [_] + (swap! is-open* not))) + props (mf/spread-props props {:id id :type "text" :class (stl/css :input) :variant "comfortable" :hint-type (when error "error") - :ref (or ref input-ref)})] + :slot-end (when (some? tokens) + (mf/html [:> icon-button* {:variant "action" + :icon i/tokens + :class (stl/css :invisible-button) + :aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown") + :ref open-dropdown-ref + :on-click open-dropdown}])) + :ref ref})] + + (mf/with-effect [dropdown-options] + (mf/set-ref-val! options-ref dropdown-options)) [:* - [:div {:class (dm/str class " " (stl/css-case :wrapper true - :input-error error))} + [:div {:class [class (stl/css-case :wrapper true + :input-error error)]} (when label [:> label* {:for id} label]) [:> input-field* props]] (when token-resolve-result - [:> token-value-hint* {:result token-resolve-result}])])) + [:> token-value-hint* {:result token-resolve-result}]) + (when ^boolean is-open + (let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)] + [:> options-dropdown* {:on-click on-option-click + :id listbox-id + :options options + :selected selected-id + :focused focused-id + :align :left + :empty-to-end empty-to-end + :ref set-option-ref}]))])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/modals.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/modals.cljs index 7a706976ed..d7f97f4063 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/modals.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/modals.cljs @@ -68,8 +68,8 @@ (clj->js)))) (mf/defc token-update-create-modal - {::mf/wrap-props false} - [{:keys [x y position token token-type action selected-token-set-id] :as _args}] + { ::mf/props :obj} + [{:keys [x y position token token-type action selected-token-set-id tokens] :as _args}] (let [wrapper-style (use-viewport-position-style x y position (= token-type :color)) modal-size-large* (mf/use-state (= token-type :typography)) modal-size-large? (deref modal-size-large*) @@ -94,6 +94,7 @@ :action action :selected-token-set-id selected-token-set-id :token-type token-type + :tokens tokens :on-display-colorpicker update-modal-size}]])) ;; Modals ---------------------------------------------------------------------- diff --git a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs index d496e88f52..3e8e77e0e3 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs @@ -77,7 +77,7 @@ on-popover-open-click (mf/use-fn - (mf/deps type title modal) + (mf/deps type title modal active-theme-tokens) (fn [event] (dom/stop-propagation event) (st/emit! (dwtl/set-token-type-section-open type true) @@ -86,6 +86,7 @@ {:x (:x pos) :y (:y pos) :position :right + :tokens active-theme-tokens :fields (:fields modal) :title title :action "create" diff --git a/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs b/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs index e93a69c2aa..ce153d1cd8 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs @@ -16,7 +16,7 @@ [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.color :as dwtc] [app.main.data.workspace.tokens.format :as dwtf] - [app.main.refs :as refs] + [app.main.ui.context :as ctx] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon*]] [app.main.ui.ds.utilities.swatch :refer [swatch*]] @@ -27,7 +27,6 @@ [rumext.v2 :as mf])) ;; Translation dictionaries - (def ^:private attribute-dictionary {:rotation "Rotation" :opacity "Opacity" @@ -77,8 +76,7 @@ :y :y}) ;; Helper functions - -(defn partially-applied-attr +(defn- partially-applied-attr "Translates partially applied attributes based on the dictionary." [app-token-keys is-applied {:keys [attributes all-attributes]}] (let [filtered-keys (if all-attributes @@ -87,7 +85,7 @@ (when is-applied (str/join ", " (map attribute-dictionary filtered-keys))))) -(defn translate-and-format +(defn- translate-and-format "Translates and formats grouped values by category." [grouped-values] (str/join "\n" @@ -98,6 +96,18 @@ (str/join ", " (map attribute-dictionary values)) "."))) grouped-values))) +(defn- token-exists? + "Returns true if any token in the grouped token map has a name matching `token-name`." + [tokens-by-type token-name] + (let [clean-name (-> token-name + (str/trim) + (str/replace #"^\{" "") + (str/replace #"\}$" "") + (str/lower))] + (some (fn [[_ tokens]] + (some #(= clean-name (:name %)) tokens)) + tokens-by-type))) + (defn- generate-tooltip "Generates a tooltip for a given token" [is-viewer shape theme-token token half-applied no-valid-value ref-not-in-active-set] @@ -142,14 +152,6 @@ ;; Otherwise only show the base title :else base-title))) -;; FIXME: the token thould already have precalculated references, so -;; we don't need to perform this regex operation on each rerender -(defn contains-reference-value? - "Extracts the value between `{}` in a string and checks if it's in the provided vector." - [text active-tokens] - (let [match (second (re-find #"\{([^}]+)\}" text))] - (contains? active-tokens match))) - (def ^:private xf:map-id (map :id)) @@ -176,7 +178,6 @@ {::mf/wrap [mf/memo]} [{:keys [on-click token on-context-menu selected-shapes is-selected-inside-layout active-theme-tokens]}] (let [{:keys [name value errors type]} token - has-selected? (pos? (count selected-shapes)) is-reference? (cft/is-reference? token) contains-path? (str/includes? name ".") @@ -203,14 +204,14 @@ (not half-applied?) (not (attributes-match-selection? selected-shapes attributes {:selected-inside-layout? is-selected-inside-layout}))) - ;; FIXME: move to context or props - can-edit? (:can-edit (deref refs/permissions)) + can-edit? + (mf/use-ctx ctx/can-edit?) is-viewer? (not can-edit?) ref-not-in-active-set (and is-reference? - (not (contains-reference-value? value active-theme-tokens))) + (not (token-exists? @active-theme-tokens value))) no-valid-value (seq errors)