diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 71fff30bb7..241b2f084a 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -277,6 +277,10 @@ (def stroke-width-keys (schema-keys schema:stroke-width)) +(def per-side-stroke-width-keys + "Per-side stroke width attribute keys." + #{:stroke-width-top :stroke-width-right :stroke-width-bottom :stroke-width-left}) + (def ^:private schema:dimensions (-> (reduce mu/union [schema:sizing schema:spacing @@ -438,13 +442,13 @@ #{:fill} (and (= :strokes shape-attr) (nil? changed-sub-attr)) - (set/union stroke-width-keys #{:stroke-color}) + (set/union stroke-width-keys #{:stroke-color}) (= :strokes shape-attr) (let [sub-attrs (set changed-sub-attr)] (cond (sub-attrs :stroke-color) #{:stroke-color} - (sub-attrs :stroke-width) stroke-width-keys + (sub-attrs :stroke-width) stroke-width-keys :else (let [per-side (set/intersection sub-attrs stroke-width-keys)] (when (seq per-side) per-side)))) diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index 562f8a2c8d..cdec09ca5a 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -655,9 +655,9 @@ {ctt/border-radius-keys update-shape-radius-for-corners ctt/color-keys update-fill-stroke ctt/stroke-width-keys update-stroke-width-side - ctt/sizing-keys apply-dimensions-token - ctt/opacity-keys update-opacity - ctt/rotation-keys update-rotation + ctt/sizing-keys apply-dimensions-token + ctt/opacity-keys update-opacity + ctt/rotation-keys update-rotation ;; Typography ctt/font-family-keys update-font-family @@ -1043,7 +1043,6 @@ {:title "Stroke Width" :attributes ctt/stroke-width-keys :on-update-shape update-stroke-width - :on-update-shape-side update-stroke-width-side :modal {:key :tokens/stroke-width :fields [{:label "Stroke Width" :key :stroke-width}]}} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs index dba0e0c226..b148e2977d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs @@ -9,6 +9,7 @@ (:require [app.common.data :as d] [app.common.types.color :as ctc] + [app.common.types.token :as ctt] [app.main.data.workspace.colors :as dc] [app.main.data.workspace.tokens.application :as dwta] [app.main.features :as features] @@ -198,8 +199,7 @@ :stroke-width-left v} index))) ids - #{:stroke-width-top :stroke-width-right - :stroke-width-bottom :stroke-width-left}) + ctt/per-side-stroke-width-keys) (st/emit! (dc/change-stroke-attrs ids {:stroke-width value @@ -211,9 +211,8 @@ (soc/emit-value-or-token value #(on-stroke-width-change index %) - ids - #{:stroke-width-top :stroke-width-right - :stroke-width-bottom :stroke-width-left})))) + ids + ctt/per-side-stroke-width-keys)))) ;; The SVG renderer defaults dash and gap to `stroke-width + 10` when ;; unset. Showing that value as placeholder makes the override obvious. @@ -303,8 +302,7 @@ (mf/use-fn (mf/deps on-detach-token) (fn [token] - (on-detach-token token #{:stroke-width-top :stroke-width-right - :stroke-width-bottom :stroke-width-left}))) + (on-detach-token token ctt/per-side-stroke-width-keys))) on-detach-token-width-top (mf/use-fn diff --git a/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs b/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs index 6d02988933..78b43236f3 100644 --- a/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs +++ b/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs @@ -11,6 +11,7 @@ [app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.shapes :as cths] [app.common.types.text :as txt] + [app.common.types.token :as ctt] [app.common.types.tokens-lib :as ctob] [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.library-edit :as dwtl] @@ -508,7 +509,7 @@ rect-with-stroke (cths/get-shape file :rect-1) rect-without-stroke (cths/get-shape file :rect-2) events [(dwta/apply-token {:shape-ids [(:id rect-with-stroke) (:id rect-without-stroke)] - :attributes #{:stroke-width-top :stroke-width-right :stroke-width-bottom :stroke-width-left} + :attributes ctt/per-side-stroke-width-keys :token (toht/get-token file "stroke-width.sm") :on-update-shape dwta/update-stroke-width-side})]] (tohs/run-store-async @@ -531,6 +532,108 @@ (t/is (= (:stroke-width-left (:applied-tokens rect-without-stroke')) (:name token-target'))) (t/is (= (get-in rect-without-stroke' [:strokes 0 :stroke-width]) 10)))))))))) +(t/deftest test-apply-stroke-width-overwrites-per-side-token + (t/testing "applying token to all sides overwrites per-side token" + (t/async + done + (let [token-sm {:name "stroke-width.sm" + :value "6" + :type :stroke-width} + token-lg {:name "stroke-width.lg" + :value "12" + :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] + (fn [lib] + (-> lib + (ctob/add-token (cthi/id :set-a) (ctob/make-token token-sm)) + (ctob/add-token (cthi/id :set-a) (ctob/make-token token-lg)))))) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + step2 (fn [state1] + (let [file1 (ths/get-file-from-state state1) + rect1 (cths/get-shape file1 :rect-1)] + ;; Verify token-sm applied to top only + (t/is (= (:stroke-width-top (:applied-tokens rect1)) "stroke-width.sm")) + (t/is (nil? (:stroke-width-right (:applied-tokens rect1)))) + ;; Second: apply token-lg to all 4 sides + (let [events2 [(dwta/apply-token {:shape-ids [(:id rect1)] + :attributes ctt/per-side-stroke-width-keys + :token (toht/get-token file1 "stroke-width.lg") + :on-update-shape dwta/update-stroke-width})]] + (tohs/run-store-async + (ths/setup-store file1) done events2 + (fn [state2] + (let [file2 (ths/get-file-from-state state2) + rect2 (cths/get-shape file2 :rect-1)] + (t/testing "token-lg overwrites all sides" + (t/is (= (:stroke-width-top (:applied-tokens rect2)) "stroke-width.lg")) + (t/is (= (:stroke-width-right (:applied-tokens rect2)) "stroke-width.lg")) + (t/is (= (:stroke-width-bottom (:applied-tokens rect2)) "stroke-width.lg")) + (t/is (= (:stroke-width-left (:applied-tokens rect2)) "stroke-width.lg")) + (t/is (= (get-in rect2 [:strokes 0 :stroke-width]) 12)))))))))] + ;; First: apply token-sm to top side only + (tohs/run-store-async + store (constantly nil) + [(dwta/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:stroke-width-top} + :token (toht/get-token file "stroke-width.sm") + :on-update-shape dwta/update-stroke-width-side})] + step2))))) + +(t/deftest test-detach-token-from-single-side + (t/testing "detaching token from one side leaves other sides intact" + (t/async + done + (let [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 % (cthi/id :set-a) + (ctob/make-token token)))) + store (ths/setup-store file) + rect-1 (cths/get-shape file :rect-1) + step2 (fn [state1] + (let [file1 (ths/get-file-from-state state1) + rect1 (cths/get-shape file1 :rect-1)] + ;; Verify all sides have the token + (t/is (= (:stroke-width-top (:applied-tokens rect1)) "stroke-width.sm")) + (t/is (= (:stroke-width-right (:applied-tokens rect1)) "stroke-width.sm")) + (t/is (= (:stroke-width-bottom (:applied-tokens rect1)) "stroke-width.sm")) + (t/is (= (:stroke-width-left (:applied-tokens rect1)) "stroke-width.sm")) + ;; Second: unapply token from top side only + (let [events2 [(dwta/unapply-token {:token-name "stroke-width.sm" + :attributes #{:stroke-width-top} + :shape-ids [(:id rect1)]})]] + (tohs/run-store-async + (ths/setup-store file1) done events2 + (fn [state2] + (let [file2 (ths/get-file-from-state state2) + rect2 (cths/get-shape file2 :rect-1)] + (t/testing "top side token detached" + (t/is (nil? (:stroke-width-top (:applied-tokens rect2))))) + (t/testing "other sides retain the token" + (t/is (= (:stroke-width-right (:applied-tokens rect2)) "stroke-width.sm")) + (t/is (= (:stroke-width-bottom (:applied-tokens rect2)) "stroke-width.sm")) + (t/is (= (:stroke-width-left (:applied-tokens rect2)) "stroke-width.sm")))))))))] + ;; First: apply token to all 4 sides + (tohs/run-store-async + store (constantly nil) + [(dwta/apply-token {:shape-ids [(:id rect-1)] + :attributes ctt/per-side-stroke-width-keys + :token (toht/get-token file "stroke-width.sm") + :on-update-shape dwta/update-stroke-width})] + step2))))) + (t/deftest test-apply-shadow (t/testing "applies shadow token and updates the shapes with shadow" (t/async