♻️ Improve tokens implementation

This commit is contained in:
Luis de Dios 2026-09-18 09:30:39 +02:00
parent 3a146c1c09
commit 2b96a220bd
4 changed files with 118 additions and 14 deletions

View File

@ -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))))

View File

@ -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}]}}

View File

@ -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

View File

@ -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