From 77ba6c135ec32269581a87236e9e13dee3a3bbaa Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 21 Oct 2024 10:08:03 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=90=9B=20Keep=20selection=20when=20re?= =?UTF-8?q?naming=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/main/data/tokens.cljs | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index fa47005019..b2f8c3bff7 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -169,6 +169,7 @@ changes (-> (pcb/empty-changes it) (pcb/update-token-set token-set prev-token-set))] (rx/of + (set-selected-token-set-id (:name token-set)) (dch/commit-changes changes)))))) (defn toggle-token-set [{:keys [token-set-name]}] From 31b5f5cefa957921524ada7857e2fdd2ee09545d Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Mon, 21 Oct 2024 16:36:47 +0200 Subject: [PATCH 2/3] :recycle: Format code --- frontend/src/app/main/data/tokens.cljs | 5 +- frontend/src/app/main/refs.cljs | 2 +- frontend/src/app/main/ui.cljs | 2 +- .../sidebar/options/menus/measures.cljs | 14 +- .../app/main/ui/workspace/tokens/changes.cljs | 34 +- .../ui/workspace/tokens/context_menu.cljs | 6 +- .../ui/workspace/tokens/modals/themes.cljs | 12 +- .../app/main/ui/workspace/tokens/sets.cljs | 2 +- .../workspace/tokens/sets_context_menu.cljs | 2 +- .../app/main/ui/workspace/tokens/token.cljs | 2 +- .../app/main/ui/workspace/tokens/update.cljs | 14 +- frontend/test/token_tests/helpers/tokens.cljs | 4 +- .../token_tests/logic/token_actions_test.cljs | 414 +++++++++--------- .../token_tests/style_dictionary_test.cljs | 6 +- frontend/test/token_tests/token_test.cljs | 10 +- 15 files changed, 266 insertions(+), 263 deletions(-) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index b2f8c3bff7..b657d0c66f 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -259,8 +259,8 @@ (pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{})) add-to-hidden-theme? (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)] - (-> base-changes - (pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme))) + (-> base-changes + (pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme))) :else base-changes)) ;; Either update or add token to existing set @@ -279,6 +279,7 @@ ptk/WatchEvent (watch [it state _] (let [data (get state :workspace-data) + _ (prn "paso por aquí") changes (-> (pcb/empty-changes it) (pcb/with-library-data data) (pcb/delete-token set-name token-name))] diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index e44e920e4f..83aed63edf 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -501,7 +501,7 @@ (l/derived #(some-> (wtts/get-selected-token-set %) (ctob/get-token token-name)) - st/state))) + st/state))) (def workspace-selected-token-set-tokens (l/derived #(or (wtts/get-selected-token-set-tokens %) {}) st/state)) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 0bc7aa70a7..81ebd5fbb6 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -6,7 +6,6 @@ (ns app.main.ui (:require - [app.main.ui.workspace.tokens.modals.themes :as wtmt] [app.config :as cf] [app.main.refs :as refs] [app.main.ui.context :as ctx] @@ -20,6 +19,7 @@ [app.main.ui.onboarding.team-choice :refer [onboarding-team-modal]] [app.main.ui.releases :refer [release-notes-modal]] [app.main.ui.static :as static] + [app.main.ui.workspace.tokens.modals.themes :as wtmt] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 1aed9f1734..0dc52d062e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -12,6 +12,7 @@ [app.common.logic.shapes :as cls] [app.common.types.shape.layout :as ctl] [app.common.types.shape.radius :as ctsr] + [app.common.types.tokens-lib :as ctob] [app.main.constants :refer [size-presets]] [app.main.data.tokens :as dt] [app.main.data.workspace :as udw] @@ -32,8 +33,7 @@ [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [clojure.set :refer [rename-keys union]] - [rumext.v2 :as mf] - [app.common.types.tokens-lib :as ctob])) + [rumext.v2 :as mf])) (def measure-attrs [:proportion-lock @@ -248,11 +248,11 @@ (st/emit! (udw/trigger-bounding-box-cloaking ids) (dwu/start-undo-transaction undo-id) (dwsh/update-shapes ids - (if token-value - #(assoc-in % [:applied-tokens attr] (:id value)) - #(d/dissoc-in % [:applied-tokens attr])) - {:reg-objects? true - :attrs [:applied-tokens]}) + (if token-value + #(assoc-in % [:applied-tokens attr] (:id value)) + #(d/dissoc-in % [:applied-tokens attr])) + {:reg-objects? true + :attrs [:applied-tokens]}) (udw/update-dimensions ids attr (or token-value value)) (dwu/commit-undo-transaction undo-id))))) diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index 0ddd8c8306..5be2d4e734 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -91,11 +91,11 @@ (defn update-shape-radius-all [value shape-ids] (dwsh/update-shapes shape-ids - (fn [shape] - (when (ctsr/has-radius? shape) - (ctsr/set-radius-1 shape value))) - {:reg-objects? true - :attrs ctt/border-radius-keys})) + (fn [shape] + (when (ctsr/has-radius? shape) + (ctsr/set-radius-1 shape value))) + {:reg-objects? true + :attrs ctt/border-radius-keys})) (defn update-opacity [value shape-ids] (when (<= 0 value 1) @@ -111,22 +111,22 @@ (defn update-shape-radius-single-corner [value shape-ids attributes] (dwsh/update-shapes shape-ids - (fn [shape] - (when (ctsr/has-radius? shape) - (cond-> shape - (:rx shape) (ctsr/switch-to-radius-4) - :always (ctsr/set-radius-4 (first attributes) value)))) - {:reg-objects? true - :attrs [:rx :ry :r1 :r2 :r3 :r4]})) + (fn [shape] + (when (ctsr/has-radius? shape) + (cond-> shape + (:rx shape) (ctsr/switch-to-radius-4) + :always (ctsr/set-radius-4 (first attributes) value)))) + {:reg-objects? true + :attrs [:rx :ry :r1 :r2 :r3 :r4]})) (defn update-stroke-width [value shape-ids] (dwsh/update-shapes shape-ids - (fn [shape] - (when (seq (:strokes shape)) - (assoc-in shape [:strokes 0 :stroke-width] value))) - {:reg-objects? true - :attrs [:strokes]})) + (fn [shape] + (when (seq (:strokes shape)) + (assoc-in shape [:strokes 0 :stroke-width] value))) + {:reg-objects? true + :attrs [:strokes]})) (defn update-color [value shape-ids] diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs index a50218df7d..bedd6c56f8 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -47,8 +47,8 @@ :selected? selected? :action (fn [] (if selected? - (st/emit! (wtch/unapply-token props)) - (st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-fn)))))})) + (st/emit! (wtch/unapply-token props)) + (st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-fn)))))})) attributes))) (defn all-or-sepearate-actions [{:keys [attribute-labels on-update-shape-all on-update-shape]} @@ -216,6 +216,8 @@ :y (.-clientY ^js event) :position :right :fields fields + :action "edit" + :selected-token-set-id selected-token-set-id :token token})))}])) (defn selection-actions [{:keys [type token] :as context-data}] diff --git a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs index f75bb4ba8f..c70105df35 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs @@ -165,12 +165,12 @@ :on-change (comp on-update-group dom/get-target-val)} :render-right (when (seq theme-groups) (mf/fnc [] - [:button {:class (stl/css :group-drop-down-button) - :type "button" - :on-click (fn [e] - (dom/stop-propagation e) - (on-toggle-dropdown))} - i/arrow]))}]] + [:button {:class (stl/css :group-drop-down-button) + :type "button" + :on-click (fn [e] + (dom/stop-propagation e) + (on-toggle-dropdown))} + i/arrow]))}]] [:& labeled-input {:label "Theme" :input-props {:default-value (:name theme) :on-change (comp on-update-name dom/get-target-val)}}]] diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 6ba57882ba..7b7fd163b7 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -7,10 +7,10 @@ (ns app.main.ui.workspace.tokens.sets (:require-macros [app.main.style :as stl]) (:require + [app.main.data.notifications :as ntf] [app.main.data.tokens :as wdt] [app.main.refs :as refs] [app.main.store :as st] - [app.main.data.notifications :as ntf] [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.main.ui.workspace.tokens.sets-context :as sets-context] diff --git a/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs index e82ba44c46..d034fe210d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs @@ -28,7 +28,7 @@ [:span {:class (stl/css :title)} title]]) (mf/defc menu - [{:keys [token-set-name]}] + [{:keys [token-set-name]}] (let [{:keys [on-edit]} (sets-context/use-context)] [:ul {:class (stl/css :context-list)} [:& menu-entry {:title "Rename" :on-click #(on-edit token-set-name)}] diff --git a/frontend/src/app/main/ui/workspace/tokens/token.cljs b/frontend/src/app/main/ui/workspace/tokens/token.cljs index 8783f47328..215f9ca512 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token.cljs @@ -38,7 +38,7 @@ (and (attr? k) (= v (token-identifier token)))) applied-tokens) - (into {})))) + (into {})))) (defn token-attribute-applied? "Test if `token` is applied to a `shape` on single `token-attribute`." diff --git a/frontend/src/app/main/ui/workspace/tokens/update.cljs b/frontend/src/app/main/ui/workspace/tokens/update.cljs index 5f0b581a97..b4cfc047d6 100644 --- a/frontend/src/app/main/ui/workspace/tokens/update.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/update.cljs @@ -32,10 +32,10 @@ ctt/rotation-keys wtch/update-rotation}) (def attribute-actions-map - (reduce - (fn [acc [ks action]] - (into acc (map (fn [k] [k action]) ks))) - {} attributes->shape-update)) + (reduce + (fn [acc [ks action]] + (into acc (map (fn [k] [k action]) ks))) + {} attributes->shape-update)) ;; Helpers --------------------------------------------------------------------- @@ -110,7 +110,7 @@ (fn [[v shape-ids]] (action v shape-ids attrs)) update-infos))) - shapes-update-info)) + shapes-update-info)) (defn update-tokens [resolved-tokens] (->> @refs/workspace-page-objects @@ -124,8 +124,8 @@ (->> (rx/from (-> - (wtts/get-active-theme-sets-tokens-names-map state) - (wtsd/resolve-tokens+ {:names-map? true}))) + (wtts/get-active-theme-sets-tokens-names-map state) + (wtsd/resolve-tokens+ {:names-map? true}))) (rx/mapcat (fn [sd-tokens] (let [undo-id (js/Symbol)] diff --git a/frontend/test/token_tests/helpers/tokens.cljs b/frontend/test/token_tests/helpers/tokens.cljs index 73cf124fca..4f510c002a 100644 --- a/frontend/test/token_tests/helpers/tokens.cljs +++ b/frontend/test/token_tests/helpers/tokens.cljs @@ -1,8 +1,8 @@ (ns token-tests.helpers.tokens (:require [app.common.test-helpers.ids-map :as thi] - [app.main.ui.workspace.tokens.token :as wtt] - [app.common.types.tokens-lib :as ctob])) + [app.common.types.tokens-lib :as ctob] + [app.main.ui.workspace.tokens.token :as wtt])) (defn add-token [state label params] (let [id (thi/new-id! label) diff --git a/frontend/test/token_tests/logic/token_actions_test.cljs b/frontend/test/token_tests/logic/token_actions_test.cljs index 9c79dcb90c..e3f215c030 100644 --- a/frontend/test/token_tests/logic/token_actions_test.cljs +++ b/frontend/test/token_tests/logic/token_actions_test.cljs @@ -49,205 +49,205 @@ (t/deftest test-apply-token (t/testing "applies token to shape and updates shape attributes to resolved value" (t/async - done - (let [file (setup-file-with-tokens) - store (ths/setup-store file) - rect-1 (cths/get-shape file :rect-1) - events [(wtch/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:rx :ry} - :token (toht/get-token file "borderRadius.md") - :on-update-shape wtch/update-shape-radius-all})]] - (tohs/run-store-async - store done events - (fn [new-state] - (let [file' (ths/get-file-from-store new-state) - token (toht/get-token file' "borderRadius.md") - rect-1' (cths/get-shape file' :rect-1)] - (t/testing "shape `:applied-tokens` got updated" - (t/is (some? (:applied-tokens rect-1'))) - (t/is (= (:rx (:applied-tokens rect-1')) (:name token))) - (t/is (= (:ry (:applied-tokens rect-1')) (:name token)))) - (t/testing "shape radius got update to the resolved token value." - (t/is (= (:rx rect-1') 24)) - (t/is (= (:ry rect-1') 24)))))))))) + done + (let [file (setup-file-with-tokens) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:rx :ry} + :token (toht/get-token file "borderRadius.md") + :on-update-shape wtch/update-shape-radius-all})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state) + token (toht/get-token file' "borderRadius.md") + rect-1' (cths/get-shape file' :rect-1)] + (t/testing "shape `:applied-tokens` got updated" + (t/is (some? (:applied-tokens rect-1'))) + (t/is (= (:rx (:applied-tokens rect-1')) (:name token))) + (t/is (= (:ry (:applied-tokens rect-1')) (:name token)))) + (t/testing "shape radius got update to the resolved token value." + (t/is (= (:rx rect-1') 24)) + (t/is (= (:ry rect-1') 24)))))))))) (t/deftest test-apply-multiple-tokens (t/testing "applying a token twice with the same attributes will override the previously applied tokens values" (t/async - done - (let [file (setup-file-with-tokens) - store (ths/setup-store file) - rect-1 (cths/get-shape file :rect-1) - events [(wtch/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:rx :ry} - :token (toht/get-token file "borderRadius.sm") - :on-update-shape wtch/update-shape-radius-all}) - (wtch/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:rx :ry} - :token (toht/get-token file "borderRadius.md") - :on-update-shape wtch/update-shape-radius-all})]] - (tohs/run-store-async - store done events - (fn [new-state] - (let [file' (ths/get-file-from-store new-state) - token (toht/get-token file' "borderRadius.md") - rect-1' (cths/get-shape file' :rect-1)] - (t/testing "shape `:applied-tokens` got updated" - (t/is (some? (:applied-tokens rect-1'))) - (t/is (= (:rx (:applied-tokens rect-1')) (:name token))) - (t/is (= (:ry (:applied-tokens rect-1')) (:name token)))) - (t/testing "shape radius got update to the resolved token value." - (t/is (= (:rx rect-1') 24)) - (t/is (= (:ry rect-1') 24)))))))))) + done + (let [file (setup-file-with-tokens) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:rx :ry} + :token (toht/get-token file "borderRadius.sm") + :on-update-shape wtch/update-shape-radius-all}) + (wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:rx :ry} + :token (toht/get-token file "borderRadius.md") + :on-update-shape wtch/update-shape-radius-all})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state) + token (toht/get-token file' "borderRadius.md") + rect-1' (cths/get-shape file' :rect-1)] + (t/testing "shape `:applied-tokens` got updated" + (t/is (some? (:applied-tokens rect-1'))) + (t/is (= (:rx (:applied-tokens rect-1')) (:name token))) + (t/is (= (:ry (:applied-tokens rect-1')) (:name token)))) + (t/testing "shape radius got update to the resolved token value." + (t/is (= (:rx rect-1') 24)) + (t/is (= (:ry rect-1') 24)))))))))) (t/deftest test-apply-token-overwrite (t/testing "removes old token attributes and applies only single attribute" (t/async - done - (let [file (setup-file-with-tokens) - store (ths/setup-store file) - rect-1 (cths/get-shape file :rect-1) - events [;; Apply "borderRadius.sm" to all border radius attributes - (wtch/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4} - :token (toht/get-token file "borderRadius.sm") - :shape-ids [(:id rect-1)] - :on-update-shape wtch/update-shape-radius-all}) + done + (let [file (setup-file-with-tokens) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + events [;; Apply "borderRadius.sm" to all border radius attributes + (wtch/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4} + :token (toht/get-token file "borderRadius.sm") + :shape-ids [(:id rect-1)] + :on-update-shape wtch/update-shape-radius-all}) ;; Apply single `:r1` attribute to same shape ;; while removing other attributes from the border-radius set ;; but keep `:r4` for testing purposes - (wtch/apply-token {:attributes #{:r1} - :attributes-to-remove #{:rx :ry :r1 :r2 :r3} - :token (toht/get-token file "borderRadius.md") - :shape-ids [(:id rect-1)] - :on-update-shape wtch/update-shape-radius-all})]] - (tohs/run-store-async - store done events - (fn [new-state] - (let [file' (ths/get-file-from-store new-state) - token-sm (toht/get-token file' "borderRadius.sm") - token-md (toht/get-token file' "borderRadius.md") - rect-1' (cths/get-shape file' :rect-1)] - (t/testing "other border-radius attributes got removed" - (t/is (nil? (:rx (:applied-tokens rect-1'))))) - (t/testing "r1 got applied with borderRadius.md" - (t/is (= (:r1 (:applied-tokens rect-1')) (:name token-md)))) - (t/testing "while :r4 was kept with borderRadius.sm" - (t/is (= (:r4 (:applied-tokens rect-1')) (:name token-sm))))))))))) + (wtch/apply-token {:attributes #{:r1} + :attributes-to-remove #{:rx :ry :r1 :r2 :r3} + :token (toht/get-token file "borderRadius.md") + :shape-ids [(:id rect-1)] + :on-update-shape wtch/update-shape-radius-all})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state) + token-sm (toht/get-token file' "borderRadius.sm") + token-md (toht/get-token file' "borderRadius.md") + rect-1' (cths/get-shape file' :rect-1)] + (t/testing "other border-radius attributes got removed" + (t/is (nil? (:rx (:applied-tokens rect-1'))))) + (t/testing "r1 got applied with borderRadius.md" + (t/is (= (:r1 (:applied-tokens rect-1')) (:name token-md)))) + (t/testing "while :r4 was kept with borderRadius.sm" + (t/is (= (:r4 (:applied-tokens rect-1')) (:name token-sm))))))))))) (t/deftest test-apply-dimensions (t/testing "applies dimensions token and updates the shapes width and height" (t/async - done - (let [dimensions-token {:name "dimensions.sm" - :value "100" - :type :dimensions} - file (-> (setup-file-with-tokens) - (update-in [:data :tokens-lib] - #(ctob/add-token-in-set % "Set A" (ctob/make-token dimensions-token)))) - store (ths/setup-store file) - rect-1 (cths/get-shape file :rect-1) - events [(wtch/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:width :height} - :token (toht/get-token file "dimensions.sm") - :on-update-shape wtch/update-shape-dimensions})]] - (tohs/run-store-async - store done events - (fn [new-state] - (let [file' (ths/get-file-from-store new-state) - token-target' (toht/get-token file' "dimensions.sm") - rect-1' (cths/get-shape file' :rect-1)] - (t/testing "shape `:applied-tokens` got updated" - (t/is (some? (:applied-tokens rect-1'))) - (t/is (= (:width (:applied-tokens rect-1')) (:name token-target'))) - (t/is (= (:height (:applied-tokens rect-1')) (:name token-target')))) - (t/testing "shapes width and height got updated" - (t/is (= (:width rect-1') 100)) - (t/is (= (:height rect-1') 100)))))))))) + done + (let [dimensions-token {:name "dimensions.sm" + :value "100" + :type :dimensions} + file (-> (setup-file-with-tokens) + (update-in [:data :tokens-lib] + #(ctob/add-token-in-set % "Set A" (ctob/make-token dimensions-token)))) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:width :height} + :token (toht/get-token file "dimensions.sm") + :on-update-shape wtch/update-shape-dimensions})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state) + token-target' (toht/get-token file' "dimensions.sm") + rect-1' (cths/get-shape file' :rect-1)] + (t/testing "shape `:applied-tokens` got updated" + (t/is (some? (:applied-tokens rect-1'))) + (t/is (= (:width (:applied-tokens rect-1')) (:name token-target'))) + (t/is (= (:height (:applied-tokens rect-1')) (:name token-target')))) + (t/testing "shapes width and height got updated" + (t/is (= (:width rect-1') 100)) + (t/is (= (:height rect-1') 100)))))))))) (t/deftest test-apply-sizing (t/testing "applies sizing token and updates the shapes width and height" (t/async - done - (let [sizing-token {:name "sizing.sm" - :value "100" - :type :sizing} - file (-> (setup-file-with-tokens) - (update-in [:data :tokens-lib] - #(ctob/add-token-in-set % "Set A" (ctob/make-token sizing-token)))) - store (ths/setup-store file) - rect-1 (cths/get-shape file :rect-1) - events [(wtch/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:width :height} - :token (toht/get-token file "sizing.sm") - :on-update-shape wtch/update-shape-dimensions})]] - (tohs/run-store-async - store done events - (fn [new-state] - (let [file' (ths/get-file-from-store new-state) - token-target' (toht/get-token file' "sizing.sm") - rect-1' (cths/get-shape file' :rect-1)] - (t/testing "shape `:applied-tokens` got updated" - (t/is (some? (:applied-tokens rect-1'))) - (t/is (= (:width (:applied-tokens rect-1')) (:name token-target'))) - (t/is (= (:height (:applied-tokens rect-1')) (:name token-target')))) - (t/testing "shapes width and height got updated" - (t/is (= (:width rect-1') 100)) - (t/is (= (:height rect-1') 100)))))))))) + done + (let [sizing-token {:name "sizing.sm" + :value "100" + :type :sizing} + file (-> (setup-file-with-tokens) + (update-in [:data :tokens-lib] + #(ctob/add-token-in-set % "Set A" (ctob/make-token sizing-token)))) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:width :height} + :token (toht/get-token file "sizing.sm") + :on-update-shape wtch/update-shape-dimensions})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state) + token-target' (toht/get-token file' "sizing.sm") + rect-1' (cths/get-shape file' :rect-1)] + (t/testing "shape `:applied-tokens` got updated" + (t/is (some? (:applied-tokens rect-1'))) + (t/is (= (:width (:applied-tokens rect-1')) (:name token-target'))) + (t/is (= (:height (:applied-tokens rect-1')) (:name token-target')))) + (t/testing "shapes width and height got updated" + (t/is (= (:width rect-1') 100)) + (t/is (= (:height rect-1') 100)))))))))) (t/deftest test-apply-opacity (t/testing "applies opacity token and updates the shapes opacity" (t/async - done - (let [opacity-float {:name "opacity.float" - :value "0.3" - :type :opacity} - opacity-percent {:name "opacity.percent" - :value "40%" - :type :opacity} - opacity-invalid {:name "opacity.invalid" - :value "100" - :type :opacity} - file (-> (setup-file-with-tokens) - (update-in [:data :tokens-lib] - #(-> % - (ctob/add-token-in-set "Set A" (ctob/make-token opacity-float)) - (ctob/add-token-in-set "Set A" (ctob/make-token opacity-percent)) - (ctob/add-token-in-set "Set A" (ctob/make-token opacity-invalid))))) - store (ths/setup-store file) - rect-1 (cths/get-shape file :rect-1) - rect-2 (cths/get-shape file :rect-2) - rect-3 (cths/get-shape file :rect-3) - events [(wtch/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:opacity} - :token (toht/get-token file "opacity.float") - :on-update-shape wtch/update-opacity}) - (wtch/apply-token {:shape-ids [(:id rect-2)] - :attributes #{:opacity} - :token (toht/get-token file "opacity.percent") - :on-update-shape wtch/update-opacity}) - (wtch/apply-token {:shape-ids [(:id rect-3)] - :attributes #{:opacity} - :token (toht/get-token file "opacity.invalid") - :on-update-shape wtch/update-opacity})]] - (tohs/run-store-async - store done events - (fn [new-state] - (let [file' (ths/get-file-from-store new-state) - rect-1' (cths/get-shape file' :rect-1) - rect-2' (cths/get-shape file' :rect-2) - rect-3' (cths/get-shape file' :rect-3) - token-opacity-float (toht/get-token file' "opacity.float") - token-opacity-percent (toht/get-token file' "opacity.percent") - token-opacity-invalid (toht/get-token file' "opacity.invalid")] - (t/testing "float value got translated to float and applied to opacity" - (t/is (= (:opacity (:applied-tokens rect-1')) (:name token-opacity-float))) - (t/is (= (:opacity rect-1') 0.3))) - (t/testing "percentage value got translated to float and applied to opacity" - (t/is (= (:opacity (:applied-tokens rect-2')) (:name token-opacity-percent))) - (t/is (= (:opacity rect-2') 0.4))) - (t/testing "invalid opacity value got applied but did not change shape" - (t/is (= (:opacity (:applied-tokens rect-3')) (:name token-opacity-invalid))) - (t/is (nil? (:opacity rect-3'))))))))))) + done + (let [opacity-float {:name "opacity.float" + :value "0.3" + :type :opacity} + opacity-percent {:name "opacity.percent" + :value "40%" + :type :opacity} + opacity-invalid {:name "opacity.invalid" + :value "100" + :type :opacity} + file (-> (setup-file-with-tokens) + (update-in [:data :tokens-lib] + #(-> % + (ctob/add-token-in-set "Set A" (ctob/make-token opacity-float)) + (ctob/add-token-in-set "Set A" (ctob/make-token opacity-percent)) + (ctob/add-token-in-set "Set A" (ctob/make-token opacity-invalid))))) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + rect-2 (cths/get-shape file :rect-2) + rect-3 (cths/get-shape file :rect-3) + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:opacity} + :token (toht/get-token file "opacity.float") + :on-update-shape wtch/update-opacity}) + (wtch/apply-token {:shape-ids [(:id rect-2)] + :attributes #{:opacity} + :token (toht/get-token file "opacity.percent") + :on-update-shape wtch/update-opacity}) + (wtch/apply-token {:shape-ids [(:id rect-3)] + :attributes #{:opacity} + :token (toht/get-token file "opacity.invalid") + :on-update-shape wtch/update-opacity})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state) + rect-1' (cths/get-shape file' :rect-1) + rect-2' (cths/get-shape file' :rect-2) + rect-3' (cths/get-shape file' :rect-3) + token-opacity-float (toht/get-token file' "opacity.float") + token-opacity-percent (toht/get-token file' "opacity.percent") + token-opacity-invalid (toht/get-token file' "opacity.invalid")] + (t/testing "float value got translated to float and applied to opacity" + (t/is (= (:opacity (:applied-tokens rect-1')) (:name token-opacity-float))) + (t/is (= (:opacity rect-1') 0.3))) + (t/testing "percentage value got translated to float and applied to opacity" + (t/is (= (:opacity (:applied-tokens rect-2')) (:name token-opacity-percent))) + (t/is (= (:opacity rect-2') 0.4))) + (t/testing "invalid opacity value got applied but did not change shape" + (t/is (= (:opacity (:applied-tokens rect-3')) (:name token-opacity-invalid))) + (t/is (nil? (:opacity rect-3'))))))))))) (t/deftest test-apply-rotation (t/testing "applies rotation token and updates the shapes rotation" @@ -278,37 +278,37 @@ (t/deftest test-apply-stroke-width (t/testing "applies stroke-width token and updates the shapes with stroke" (t/async - done - (let [stroke-width-token {:name "stroke-width.sm" - :value "10" - :type :stroke-width} - file (-> (setup-file-with-tokens {:rect-1 {:strokes [{:stroke-alignment :inner, - :stroke-style :solid, - :stroke-color "#000000", - :stroke-opacity 1, - :stroke-width 5}]}}) - (update-in [:data :tokens-lib] - #(ctob/add-token-in-set % "Set A" (ctob/make-token stroke-width-token)))) - store (ths/setup-store file) - rect-with-stroke (cths/get-shape file :rect-1) - rect-without-stroke (cths/get-shape file :rect-2) - events [(wtch/apply-token {:shape-ids [(:id rect-with-stroke) (:id rect-without-stroke)] - :attributes #{:stroke-width} - :token (toht/get-token file "stroke-width.sm") - :on-update-shape wtch/update-stroke-width})]] - (tohs/run-store-async - store done events - (fn [new-state] - (let [file' (ths/get-file-from-store new-state) - token-target' (toht/get-token file' "stroke-width.sm") - rect-with-stroke' (cths/get-shape file' :rect-1) - rect-without-stroke' (cths/get-shape file' :rect-2)] - (t/testing "token got applied to rect with stroke and shape stroke got updated" - (t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (:name token-target'))) - (t/is (= (get-in rect-with-stroke' [:strokes 0 :stroke-width]) 10))) - (t/testing "token got applied to rect without stroke but shape didnt get updated" - (t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target'))) - (t/is (empty? (:strokes rect-without-stroke'))))))))))) + done + (let [stroke-width-token {:name "stroke-width.sm" + :value "10" + :type :stroke-width} + file (-> (setup-file-with-tokens {:rect-1 {:strokes [{:stroke-alignment :inner, + :stroke-style :solid, + :stroke-color "#000000", + :stroke-opacity 1, + :stroke-width 5}]}}) + (update-in [:data :tokens-lib] + #(ctob/add-token-in-set % "Set A" (ctob/make-token stroke-width-token)))) + store (ths/setup-store file) + rect-with-stroke (cths/get-shape file :rect-1) + rect-without-stroke (cths/get-shape file :rect-2) + events [(wtch/apply-token {:shape-ids [(:id rect-with-stroke) (:id rect-without-stroke)] + :attributes #{:stroke-width} + :token (toht/get-token file "stroke-width.sm") + :on-update-shape wtch/update-stroke-width})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-store new-state) + token-target' (toht/get-token file' "stroke-width.sm") + rect-with-stroke' (cths/get-shape file' :rect-1) + rect-without-stroke' (cths/get-shape file' :rect-2)] + (t/testing "token got applied to rect with stroke and shape stroke got updated" + (t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (:name token-target'))) + (t/is (= (get-in rect-with-stroke' [:strokes 0 :stroke-width]) 10))) + (t/testing "token got applied to rect without stroke but shape didnt get updated" + (t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target'))) + (t/is (empty? (:strokes rect-without-stroke'))))))))))) (t/deftest test-toggle-token-none (t/testing "should apply token to all selected items, where no item has the token applied" diff --git a/frontend/test/token_tests/style_dictionary_test.cljs b/frontend/test/token_tests/style_dictionary_test.cljs index 6ec0971381..fa0093f47d 100644 --- a/frontend/test/token_tests/style_dictionary_test.cljs +++ b/frontend/test/token_tests/style_dictionary_test.cljs @@ -1,10 +1,10 @@ (ns token-tests.style-dictionary-test (:require + [app.common.data :as d] [app.main.ui.workspace.tokens.style-dictionary :as sd] - [cljs.test :as t :include-macros true] - [promesa.core :as p] [app.main.ui.workspace.tokens.token :as wtt] - [app.common.data :as d])) + [cljs.test :as t :include-macros true] + [promesa.core :as p])) (def border-radius-token {:value "12px" diff --git a/frontend/test/token_tests/token_test.cljs b/frontend/test/token_tests/token_test.cljs index 2aca8fda2f..309fed6e29 100644 --- a/frontend/test/token_tests/token_test.cljs +++ b/frontend/test/token_tests/token_test.cljs @@ -64,8 +64,8 @@ shape-applied-x-y shape-applied-all))) (t/is (= (:y expected) (shape-ids shape-applied-y - shape-applied-x-y - shape-applied-all))) + shape-applied-x-y + shape-applied-all))) (t/is (= (:z expected) (shape-ids shape-applied-all))) (t/is (true? (wtt/shapes-applied-all? expected (shape-ids shape-applied-all) attributes))) (t/is (false? (wtt/shapes-applied-all? expected (apply shape-ids shapes) attributes))) @@ -77,14 +77,14 @@ (t/testing "is true when single shape matches the token and attributes" (t/is (true? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}} {:applied-tokens {:x "b"}}] - #{:x})))) + #{:x})))) (t/testing "is false when no shape matches the token or attributes" (t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "b"}} {:applied-tokens {:x "b"}}] - #{:x}))) + #{:x}))) (t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}} {:applied-tokens {:x "a"}}] - #{:y}))))) + #{:y}))))) (t/deftest name->path-test (t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo.bar.baz"))) From 03ea5414bebb0e2c587934922028bc6660751184 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Mon, 21 Oct 2024 17:14:17 +0200 Subject: [PATCH 3/3] :recycle: Review create and edit modal --- frontend/src/app/main/data/tokens.cljs | 1 - .../src/app/main/ui/ds/buttons/_buttons.scss | 18 ++ .../app/main/ui/ds/buttons/icon_button.cljs | 6 +- .../app/main/ui/ds/buttons/icon_button.scss | 4 + .../ui/ds/buttons/icon_button.stories.jsx | 8 +- .../app/main/ui/workspace/tokens/form.cljs | 262 +++++++++++------- .../app/main/ui/workspace/tokens/form.scss | 47 ++-- .../app/main/ui/workspace/tokens/modals.cljs | 24 +- .../app/main/ui/workspace/tokens/modals.scss | 13 +- .../app/main/ui/workspace/tokens/sidebar.cljs | 24 +- frontend/translations/en.po | 25 ++ frontend/translations/es.po | 25 ++ 12 files changed, 310 insertions(+), 147 deletions(-) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index b657d0c66f..e7ae337f5f 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -279,7 +279,6 @@ ptk/WatchEvent (watch [it state _] (let [data (get state :workspace-data) - _ (prn "paso por aquí") changes (-> (pcb/empty-changes it) (pcb/with-library-data data) (pcb/delete-token set-name token-name))] diff --git a/frontend/src/app/main/ui/ds/buttons/_buttons.scss b/frontend/src/app/main/ui/ds/buttons/_buttons.scss index b489b4df97..07038c8239 100644 --- a/frontend/src/app/main/ui/ds/buttons/_buttons.scss +++ b/frontend/src/app/main/ui/ds/buttons/_buttons.scss @@ -130,3 +130,21 @@ box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2); } } + +%base-button-action { + --button-bg-color: transparent; + --button-fg-color: var(--color-foreground-secondary); + + --button-hover-bg-color: transparent; + --button-hover-fg-color: var(--color-accent-primary); + + --button-active-bg-color: var(--color-background-quaternary); + + --button-disabled-bg-color: transparent; + --button-disabled-fg-color: var(--color-accent-primary-muted); + + --button-focus-bg-color: transparent; + --button-focus-fg-color: var(--color-accent-primary); + --button-focus-inner-ring-color: transparent; + --button-focus-outer-ring-color: var(--color-accent-primary); +} diff --git a/frontend/src/app/main/ui/ds/buttons/icon_button.cljs b/frontend/src/app/main/ui/ds/buttons/icon_button.cljs index dadb285afd..630c520165 100644 --- a/frontend/src/app/main/ui/ds/buttons/icon_button.cljs +++ b/frontend/src/app/main/ui/ds/buttons/icon_button.cljs @@ -12,9 +12,6 @@ [app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]] [rumext.v2 :as mf])) -(def button-variants (set '("primary" "secondary" "ghost" "destructive"))) - - (def ^:private schema:icon-button [:map [:class {:optional true} :string] @@ -22,7 +19,7 @@ [:and :string [:fn #(contains? icon-list %)]]] [:aria-label :string] [:variant {:optional true} - [:maybe [:enum "primary" "secondary" "ghost" "destructive"]]]]) + [:maybe [:enum "primary" "secondary" "ghost" "destructive" "action"]]]]) (mf/defc icon-button* {::mf/props :obj @@ -33,6 +30,7 @@ :icon-button-primary (= variant "primary") :icon-button-secondary (= variant "secondary") :icon-button-ghost (= variant "ghost") + :icon-button-action (= variant "action") :icon-button-destructive (= variant "destructive"))) props (mf/spread-props props {:class class :title aria-label})] [:> "button" props [:> icon* {:id icon :aria-label aria-label}]])) \ No newline at end of file diff --git a/frontend/src/app/main/ui/ds/buttons/icon_button.scss b/frontend/src/app/main/ui/ds/buttons/icon_button.scss index 1a10c3775b..eed4d8f5b3 100644 --- a/frontend/src/app/main/ui/ds/buttons/icon_button.scss +++ b/frontend/src/app/main/ui/ds/buttons/icon_button.scss @@ -31,3 +31,7 @@ .icon-button-destructive { @extend %base-button-destructive; } + +.icon-button-action { + @extend %base-button-action; +} diff --git a/frontend/src/app/main/ui/ds/buttons/icon_button.stories.jsx b/frontend/src/app/main/ui/ds/buttons/icon_button.stories.jsx index 17cb4b2fbb..321aa7b7a1 100644 --- a/frontend/src/app/main/ui/ds/buttons/icon_button.stories.jsx +++ b/frontend/src/app/main/ui/ds/buttons/icon_button.stories.jsx @@ -26,7 +26,7 @@ export default { }, disabled: { control: "boolean" }, variant: { - options: ["primary", "secondary", "ghost", "destructive"], + options: ["primary", "secondary", "ghost", "destructive", "action"], control: { type: "select" }, }, }, @@ -59,6 +59,12 @@ export const Ghost = { }, }; +export const Action = { + args: { + variant: "action", + }, +}; + export const Destructive = { args: { variant: "destructive", diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index dee3b0215b..677439a049 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -10,12 +10,16 @@ ["lodash.debounce" :as debounce] [app.common.colors :as c] [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.types.tokens-lib :as ctob] [app.main.data.modal :as modal] [app.main.data.tokens :as dt] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.color-bullet :refer [color-bullet]] + [app.main.ui.ds.buttons.button :refer [button*]] + [app.main.ui.ds.foundations.assets.icon :as i] + [app.main.ui.ds.foundations.typography.text :refer [text*]] [app.main.ui.workspace.colorpicker :as colorpicker] [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]] [app.main.ui.workspace.tokens.common :as tokens.common] @@ -25,6 +29,7 @@ [app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.update :as wtu] [app.util.dom :as dom] + [app.util.i18n :refer [tr]] [cuerdas.core :as str] [malli.core :as m] [malli.error :as me] @@ -124,7 +129,7 @@ Token names should only contain letters and digits separated by . characters.")} [name-ref token tokens callback & {:keys [timeout] :or {timeout 160}}] (let [timeout-id-ref (mf/use-ref nil) debounced-resolver-callback - (mf/use-callback + (mf/use-fn (mf/deps token callback tokens) (fn [value] (let [timeout-id (js/Symbol) @@ -178,19 +183,22 @@ Token names should only contain letters and digits separated by . characters.")} [{:keys [result-or-errors]}] (let [{:keys [errors]} result-or-errors empty-message? (or (nil? result-or-errors) - (wte/has-error-code? :error/empty-input errors))] - [:div {:class (stl/css-case :resolved-value true - :resolved-value-placeholder empty-message? - :resolved-value-error (seq errors))} - (cond - empty-message? "Enter token value" - errors (->> (wte/humanize-errors errors) - (str/join "\n")) - :else [:p result-or-errors])])) + (wte/has-error-code? :error/empty-input errors)) + message (cond + empty-message? (dm/str (tr "workspace.token.resolved-value") "-") + errors (->> (wte/humanize-errors errors) + (str/join "\n")) + :else (dm/str (tr "workspace.token.resolved-value") result-or-errors))] + [:> text* {:as "p" + :typography "body-small" + :class (stl/css-case :resolved-value true + :resolved-value-placeholder empty-message? + :resolved-value-error (seq errors))} + message])) (mf/defc form {::mf/wrap-props false} - [{:keys [token token-type]}] + [{:keys [token token-type action selected-token-set-id]}] (let [validate-name? (mf/use-state (not (:id token))) token (or token {:type token-type}) color? (wtt/color-token? token) @@ -212,25 +220,31 @@ Token names should only contain letters and digits separated by . characters.")} ;; Name name-ref (mf/use-var (:name token)) name-errors (mf/use-state nil) - validate-name (mf/use-callback - (mf/deps selected-set-tokens-tree) - (fn [value] - (let [schema (token-name-schema {:token token - :tokens-tree selected-set-tokens-tree})] - (m/explain schema (finalize-name value))))) - on-update-name-debounced (mf/use-callback - (debounce (fn [e] - (let [value (dom/get-target-val e) - errors (validate-name value)] + validate-name + (mf/use-fn + (mf/deps selected-set-tokens-tree) + (fn [value] + (let [schema (token-name-schema {:token token + :tokens-tree selected-set-tokens-tree})] + (m/explain schema (finalize-name value))))) + + on-update-name-debounced + (mf/use-fn + (debounce (fn [e] + (let [value (dom/get-target-val e) + errors (validate-name value)] ;; Prevent showing error when just going to another field on a new token - (when-not (and validate-name? (str/empty? value)) - (reset! validate-name? false) - (reset! name-errors errors)))))) - on-update-name (mf/use-callback - (mf/deps on-update-name-debounced) - (fn [e] - (reset! name-ref (dom/get-target-val e)) - (on-update-name-debounced e))) + (when-not (and validate-name? (str/empty? value)) + (reset! validate-name? false) + (reset! name-errors errors)))))) + + on-update-name + (mf/use-fn + (mf/deps on-update-name-debounced) + (fn [e] + (reset! name-ref (dom/get-target-val e)) + (on-update-name-debounced e))) + valid-name-field? (and (not @name-errors) (valid-name? @name-ref)) @@ -241,27 +255,29 @@ Token names should only contain letters and digits separated by . characters.")} value-input-ref (mf/use-ref nil) value-ref (mf/use-var (:value token)) token-resolve-result (mf/use-state (get-in resolved-tokens [(wtt/token-identifier token) :resolved-value])) - set-resolve-value (mf/use-callback - (fn [token-or-err] - (let [error? (:errors token-or-err) - v (if error? - token-or-err - (:resolved-value token-or-err))] - (when color? (reset! color (if error? nil v))) - (reset! token-resolve-result v)))) + set-resolve-value + (mf/use-fn + (fn [token-or-err] + (let [error? (:errors token-or-err) + v (if error? + token-or-err + (:resolved-value token-or-err))] + (when color? (reset! color (if error? nil v))) + (reset! token-resolve-result v)))) on-update-value-debounced (use-debonced-resolve-callback name-ref token active-theme-tokens set-resolve-value) - on-update-value (mf/use-callback + on-update-value (mf/use-fn (mf/deps on-update-value-debounced) (fn [e] (let [value (dom/get-target-val e)] (reset! value-ref value) (on-update-value-debounced value)))) - on-update-color (mf/use-callback + on-update-color (mf/use-fn (mf/deps on-update-value-debounced) (fn [hex-value] (reset! value-ref hex-value) (set! (.-value (mf/ref-val value-input-ref)) hex-value) (on-update-value-debounced hex-value))) + value-error? (seq (:errors @token-resolve-result)) valid-value-field? (and (not value-error?) @@ -270,17 +286,18 @@ Token names should only contain letters and digits separated by . characters.")} ;; Description description-ref (mf/use-var (:description token)) description-errors (mf/use-state nil) - validate-descripion (mf/use-callback #(m/explain token-description-schema %)) - on-update-description-debounced (mf/use-callback + validate-descripion (mf/use-fn #(m/explain token-description-schema %)) + on-update-description-debounced (mf/use-fn (debounce (fn [e] (let [value (dom/get-target-val e) errors (validate-descripion value)] (reset! description-errors errors))))) - on-update-description (mf/use-callback - (mf/deps on-update-description-debounced) - (fn [e] - (reset! description-ref (dom/get-target-val e)) - (on-update-description-debounced e))) + on-update-description + (mf/use-fn + (mf/deps on-update-description-debounced) + (fn [e] + (reset! description-ref (dom/get-target-val e)) + (on-update-description-debounced e))) valid-description-field? (not @description-errors) ;; Form @@ -288,41 +305,62 @@ Token names should only contain letters and digits separated by . characters.")} (not valid-value-field?) (not valid-description-field?)) - on-submit (mf/use-callback - (mf/deps validate-name validate-descripion token resolved-tokens) - (fn [e] - (dom/prevent-default e) + on-submit + (mf/use-fn + (mf/deps validate-name validate-descripion token resolved-tokens) + (fn [e] + (dom/prevent-default e) ;; We have to re-validate the current form values before submitting ;; because the validation is asynchronous/debounced ;; and the user might have edited a valid form to make it invalid, ;; and press enter before the next validations could return. - (let [final-name (finalize-name @name-ref) - valid-name?+ (-> (validate-name final-name) schema-validation->promise) - final-value (finalize-value @value-ref) - final-description @description-ref - valid-description?+ (some-> final-description validate-descripion schema-validation->promise)] - (-> (p/all [valid-name?+ - valid-description?+ - (validate-token-value+ {:value final-value - :name-value final-name - :token token - :tokens resolved-tokens})]) - (p/finally (fn [result err] + (let [final-name (finalize-name @name-ref) + valid-name?+ (-> (validate-name final-name) schema-validation->promise) + final-value (finalize-value @value-ref) + final-description @description-ref + valid-description?+ (some-> final-description validate-descripion schema-validation->promise)] + (-> (p/all [valid-name?+ + valid-description?+ + (validate-token-value+ {:value final-value + :name-value final-name + :token token + :tokens resolved-tokens})]) + (p/finally (fn [result err] ;; The result should be a vector of all resolved validations ;; We do not handle the error case as it will be handled by the components validations - (when (and (seq result) (not err)) - (st/emit! (dt/update-create-token {:token (ctob/make-token :name final-name - :type (or (:type token) token-type) - :value final-value - :description final-description) - :prev-token-name (:name token)})) - (st/emit! (wtu/update-workspace-tokens)) - (modal/hide!))))))))] - [:form - {:class (stl/css :form-wrapper) - :on-submit on-submit} + (when (and (seq result) (not err)) + (st/emit! (dt/update-create-token {:token (ctob/make-token :name final-name + :type (or (:type token) token-type) + :value final-value + :description final-description) + :prev-token-name (:name token)})) + (st/emit! (wtu/update-workspace-tokens)) + (modal/hide!)))))))) + on-delete-token + (mf/use-fn + (mf/deps selected-token-set-id) + (fn [e] + (dom/prevent-default e) + (modal/hide!) + (st/emit! (dt/delete-token selected-token-set-id (:name token))))) + + on-cancel + (mf/use-fn + (fn [e] + (dom/prevent-default e) + (modal/hide!)))] + + [:form {:class (stl/css :form-wrapper) + :on-submit on-submit} [:div {:class (stl/css :token-rows)} - [:div + [:> text* {:as "span" :typography "headline-medium"} + (if (= action "edit") + (tr "workspace.token.edit-token") + (tr "workspace.token.create-token" token-type))] + + [:div {:class (stl/css :input-row)} + ;; This should be remove when labeled-imput is modified + [:span "Name"] [:& tokens.common/labeled-input {:label "Name" :error? @name-errors :input-props {:default-value @name-ref @@ -332,37 +370,59 @@ Token names should only contain letters and digits separated by . characters.")} (for [error (->> (:errors @name-errors) (map #(-> (assoc @name-errors :errors [%]) (me/humanize))))] - [:p {:key error - :class (stl/css :error)} + [:> text* {:as "p" + :key error + :typography "body-small" + :class (stl/css :error)} error])] - [:& tokens.common/labeled-input {:label "Value" - :input-props {:default-value @value-ref - :on-blur on-update-value - :on-change on-update-value - :ref value-input-ref} - :render-right (when color? - (mf/fnc [] - [:div {:class (stl/css :color-bullet) - :on-click #(swap! color-ramp-open? not)} - (if-let [hex (some-> @color tinycolor/valid-color tinycolor/->hex)] - [:& color-bullet {:color hex - :mini? true}] - [:div {:class (stl/css :color-bullet-placeholder)}])]))}] - (when @color-ramp-open? - [:& ramp {:color (some-> (or @token-resolve-result (:value token)) - (tinycolor/valid-color)) - :on-change on-update-color}]) - [:& token-value-or-errors {:result-or-errors @token-resolve-result}] - [:div + [:div {:class (stl/css :input-row)} + ;; This should be remove when labeled-imput is modified + [:span "value"] + [:& tokens.common/labeled-input {:label "Value" + :input-props {:default-value @value-ref + :on-blur on-update-value + :on-change on-update-value + :ref value-input-ref} + :render-right (when color? + (mf/fnc [] + [:div {:class (stl/css :color-bullet) + :on-click #(swap! color-ramp-open? not)} + (if-let [hex (some-> @color tinycolor/valid-color tinycolor/->hex)] + [:& color-bullet {:color hex + :mini? true}] + [:div {:class (stl/css :color-bullet-placeholder)}])]))}] + (when @color-ramp-open? + [:& ramp {:color (some-> (or @token-resolve-result (:value token)) + (tinycolor/valid-color)) + :on-change on-update-color}]) + [:& token-value-or-errors {:result-or-errors @token-resolve-result}]] + + + [:div {:class (stl/css :input-row)} + ;; This should be remove when labeled-imput is modified + [:span "Description"] [:& tokens.common/labeled-input {:label "Description" :input-props {:default-value @description-ref :on-change on-update-description}}] (when @description-errors - [:p {:class (stl/css :error)} + [:> text* {:as "p" + :typography "body-small" + :class (stl/css :error)} (me/humanize @description-errors)])] - [:div {:class (stl/css :button-row)} - [:button {:class (stl/css :button) - :type "submit" - :disabled disabled?} - "Save"]]]])) + + [:div {:class (stl/css-case :button-row true + :with-delete (= action "edit"))} + (when (= action "edit") + [:> button* {:on-click on-delete-token + :class (stl/css :delete-btn) + :icon i/delete + :variant "secondary"} + (tr "labels.delete")]) + [:> button* {:on-click on-cancel + :variant "secondary"} + (tr "labels.cancel")] + [:> button* {:type "submit" + :variant "primary" + :disabled disabled?} + (tr "labels.save")]]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.scss b/frontend/src/app/main/ui/workspace/tokens/form.scss index 4d1b37c86b..dd35014acd 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.scss +++ b/frontend/src/app/main/ui/workspace/tokens/form.scss @@ -8,49 +8,56 @@ @import "./common.scss"; .form-wrapper { - width: $s-260; + width: $s-384; } .button-row { - display: flex; - flex-direction: column; - margin-top: $s-16; + display: grid; + grid-template-columns: auto auto; + justify-content: end; + gap: $s-12; + padding-block-start: $s-8; +} + +.with-delete { + grid-template-columns: 1fr auto auto; +} + +.delete-btn { + justify-self: start; } .token-rows { display: flex; flex-direction: column; - gap: $s-8; + gap: $s-16; +} + +.input-row { + display: flex; + flex-direction: column; + gap: $s-4; } .error { - @include bodySmallTypography; - margin-top: $s-6; + padding: $s-4 $s-6; margin-bottom: 0; color: var(--status-color-error-500); } .resolved-value { - @include bodySmallTypography; + --input-hint-color: var(--color-foreground-primary); + margin-bottom: 0; padding: $s-4 $s-6; - font-weight: medium; - min-height: 1lh; - - color: var(--color-foreground-primary); - border: 1px solid color-mix(in hsl, var(--color-foreground-secondary) 30%, transparent); - - p { - font-size: $fs-12; - margin: 0; - } + color: var(--input-hint-color); } .resolved-value-placeholder { - color: var(--color-foreground-secondary); + --input-hint-color: var(--color-foreground-secondary); } .resolved-value-error { - color: var(--status-color-error-500); + --input-hint-color: var(--status-color-error-500); } .color-bullet { diff --git a/frontend/src/app/main/ui/workspace/tokens/modals.cljs b/frontend/src/app/main/ui/workspace/tokens/modals.cljs index 5e4358d341..06ef35af92 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals.cljs @@ -8,9 +8,12 @@ (:require-macros [app.main.style :as stl]) (:require [app.main.data.modal :as modal] - [app.main.ui.workspace.tokens.modals.themes :as wtmt] [app.main.refs :as refs] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.workspace.tokens.form :refer [form]] + [app.main.ui.workspace.tokens.modals.themes :as wtmt] + [app.util.i18n :refer [tr]] [okulary.core :as l] [rumext.v2 :as mf])) @@ -40,12 +43,21 @@ (mf/defc token-update-create-modal {::mf/wrap-props false} - [{:keys [x y position token token-type] :as _args}] - (let [wrapper-style (use-viewport-position-style x y position)] - [:div - {:class (stl/css :shadow) - :style wrapper-style} + [{:keys [x y position token token-type action selected-token-set-id] :as _args}] + (let [wrapper-style (use-viewport-position-style x y position) + close-modal (mf/use-fn + (fn [] + (modal/hide!)))] + [:div {:class (stl/css :token-modal-wrapper) + :style wrapper-style} + [:> icon-button* {:on-click close-modal + :class (stl/css :close-btn) + :icon i/close + :variant "action" + :aria-label (tr "labels.close")}] [:& form {:token token + :action action + :selected-token-set-id selected-token-set-id :token-type token-type}]])) (mf/defc token-themes-modal diff --git a/frontend/src/app/main/ui/workspace/tokens/modals.scss b/frontend/src/app/main/ui/workspace/tokens/modals.scss index cb16e1ec21..b1e924616d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals.scss +++ b/frontend/src/app/main/ui/workspace/tokens/modals.scss @@ -6,14 +6,19 @@ @import "refactor/common-refactor.scss"; -.shadow { +.token-modal-wrapper { @extend .modal-container-base; - width: auto; - max-width: auto; - min-width: auto; @include menuShadow; position: absolute; + width: auto; + min-width: auto; z-index: 11; overflow-y: auto; overflow-x: hidden; } + +.close-btn { + position: absolute; + top: $s-6; + right: $s-6; +} diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index d36b561996..e2c522f4cd 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -10,8 +10,8 @@ [app.common.data :as d] [app.common.transit :as t] [app.common.types.tokens-lib :as ctob] - [app.main.data.notifications :as ntf] [app.main.data.modal :as modal] + [app.main.data.notifications :as ntf] [app.main.data.tokens :as dt] [app.main.refs :as refs] [app.main.store :as st] @@ -33,6 +33,7 @@ [app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token-types :as wtty] [app.util.dom :as dom] + [app.util.i18n :refer [tr]] [app.util.webapi :as wapi] [beicon.v2.core :as rx] [cuerdas.core :as str] @@ -60,8 +61,8 @@ :title (cond errors? (sd/humanize-errors token) :else (->> [(str "Token: " name) - (str "Original value: " value) - (str "Resolved value: " resolved-value)] + (str (tr "workspace.token.original-value") value) + (str (tr "workspace.token.resolved-value") resolved-value)] (str/join "\n"))) :on-click on-click :on-context-menu on-context-menu @@ -111,6 +112,7 @@ #(st/emit! (dt/set-token-type-section-open type (not open?)))) on-popover-open-click (mf/use-fn (fn [event] + (mf/deps type title) (let [{:keys [key fields]} modal] (dom/stop-propagation event) (st/emit! (dt/set-token-type-section-open type true)) @@ -118,6 +120,8 @@ :y (.-clientY ^js event) :position :right :fields fields + :title title + :action "create" :token-type type})))) on-token-pill-click (mf/use-fn @@ -175,16 +179,16 @@ :on-click (fn [e] (dom/stop-propagation e) (modal/show! :tokens/themes {}))} - (if create? "Create" "Edit")]) + (if create? (tr "labels.create") (tr "labels.edit"))]) (mf/defc themes-header [_props] (let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden)] - [:div {:class (stl/css :themes-wrapper)} - [:span {:class (stl/css :themes-header)} "Themes"] - [:div {:class (stl/css :theme-select-wrapper)} - [:& theme-select] - [:& edit-button {:create? (empty? ordered-themes)}]]])) + [:div {:class (stl/css :themes-wrapper)} + [:span {:class (stl/css :themes-header)} (tr "labels.themes")] + [:div {:class (stl/css :theme-select-wrapper)} + [:& theme-select] + [:& edit-button {:create? (empty? ordered-themes)}]]])) (mf/defc add-set-button [{:keys [on-open]}] @@ -207,7 +211,7 @@ [:& title-bar {:collapsable true :collapsed (not @open?) :all-clickable true - :title "SETS" + :title (tr "labels.sets") :on-collapsed #(swap! open? not)} [:& add-set-button {:on-open on-open}]]] (when @open? diff --git a/frontend/translations/en.po b/frontend/translations/en.po index b0b931a058..bdec8767d1 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1993,6 +1993,14 @@ msgstr "Team Leader" msgid "labels.team-member" msgstr "Team member" +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "labels.themes" +msgstr "Themes" + +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "labels.sets" +msgstr "Sets" + #: src/app/main/ui/dashboard/sidebar.cljs:992, src/app/main/ui/workspace/main_menu.cljs:118 msgid "labels.tutorials" msgstr "Tutorials" @@ -6139,3 +6147,20 @@ msgstr "Update" #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Click to close the path" + + +#: src/app/main/ui/workspace/tokens/form.cljs +msgid "workspace.token.create-token" +msgstr "Create new %s token" + +#: src/app/main/ui/workspace/tokens/form.cljs +msgid "workspace.token.edit-token" +msgstr "Edit token" + +#: src/app/main/ui/workspace/tokens/form.cljs +msgid "workspace.token.resolved-value" +msgstr "Resolved value: " + +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "workspace.token.original-value" +msgstr "Original value: " \ No newline at end of file diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 7400fd0b9c..02b19d5848 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1991,6 +1991,14 @@ msgstr "Líder de equipo" msgid "labels.team-member" msgstr "Miembro de equipo" +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "labels.themes" +msgstr "Temas" + +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "labels.sets" +msgstr "Sets" + #: src/app/main/ui/dashboard/sidebar.cljs:992, src/app/main/ui/workspace/main_menu.cljs:118 msgid "labels.tutorials" msgstr "Tutoriales" @@ -6126,3 +6134,20 @@ msgstr "Pulsar para cerrar la ruta" msgid "errors.maximum-invitations-by-request-reached" msgstr "Se ha alcanzado el número máximo (%s) de correos electrónicos que se pueden invitar en una sola solicitud" + + +#: src/app/main/ui/workspace/tokens/form.cljs +msgid "workspace.token.create-token" +msgstr "Crear un token de %s" + +#: src/app/main/ui/workspace/tokens/form.cljs +msgid "workspace.token.edit-token" +msgstr "Editar token" + +#: src/app/main/ui/workspace/tokens/form.cljs +msgid "workspace.token.resolved-value" +msgstr "Valor resuelto: " + +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "workspace.token.original-value" +msgstr "Valor original: " \ No newline at end of file