Add letter spacing token (#6814)

* 🐛 Fix merge schema not working with key generation

*  Add letter-spacing token

* ♻️ Remove comments

* ♻️ Inline line-height for now
This commit is contained in:
Florian Schrödl 2025-07-03 16:00:58 +02:00 committed by GitHub
parent 3165761bac
commit 21746144b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 103 additions and 22 deletions

View file

@ -34,6 +34,7 @@
:color "color"
:dimensions "dimension"
:font-size "fontSizes"
:letter-spacing "letterSpacing"
:number "number"
:opacity "opacity"
:other "other"
@ -107,11 +108,10 @@
(def spacing-keys (schema-keys schema:spacing))
(def ^:private schema:dimensions
[:merge
schema:sizing
(reduce mu/union [schema:sizing
schema:spacing
schema:stroke-width
schema:border-radius])
schema:border-radius]))
(def dimensions-keys (schema-keys schema:dimensions))
@ -127,12 +127,17 @@
(def font-size-keys (schema-keys schema:font-size))
(def typography-keys (set/union font-size-keys))
(def ^:private schema:letter-spacing
[:map
[:letter-spacing {:optional true} token-name-ref]])
(def letter-spacing-keys (schema-keys schema:letter-spacing))
(def typography-keys (set/union font-size-keys letter-spacing-keys))
(def ^:private schema:number
[:map
[:rotation {:optional true} token-name-ref]
[:line-height {:optional true} token-name-ref]])
(reduce mu/union [[:map [:line-height {:optional true} token-name-ref]]
schema:rotation]))
(def number-keys (schema-keys schema:number))
@ -159,6 +164,7 @@
schema:rotation
schema:number
schema:font-size
schema:letter-spacing
schema:dimensions])
(defn shape-attr->token-attrs
@ -187,6 +193,7 @@
#{:m1 :m2 :m3 :m4})
(font-size-keys shape-attr) #{shape-attr}
(letter-spacing-keys shape-attr) #{shape-attr}
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}
@ -280,4 +287,3 @@
(defn unapply-token-id [shape attributes]
(update shape :applied-tokens d/without-keys attributes))

View file

@ -58,7 +58,11 @@
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-font-size"
:type :font-size
:value 24))))
:value 24))
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-letter-spacing"
:type :letter-spacing
:value 2))))
(tho/add-frame :frame1)
(tho/add-text :text1 "Hello World!")))
@ -72,7 +76,8 @@
(tht/apply-token-to-shape :frame1 "token-color" [:stroke-color] [:stroke-color] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100)
(tht/apply-token-to-shape :text1 "token-font-size" [:font-size] [:font-size] 24)))
(tht/apply-token-to-shape :text1 "token-font-size" [:font-size] [:font-size] 24)
(tht/apply-token-to-shape :text1 "token-letter-spacing" [:letter-spacing] [:letter-spacing] 2)))
(t/deftest apply-tokens-to-shape
(let [;; ==== Setup
@ -87,6 +92,7 @@
token-color (tht/get-token file "test-token-set" "token-color")
token-dimensions (tht/get-token file "test-token-set" "token-dimensions")
token-font-size (tht/get-token file "test-token-set" "token-font-size")
token-letter-spacing (tht/get-token file "test-token-set" "token-letter-spacing")
;; ==== Action
changes (-> (-> (pcb/empty-changes nil)
@ -123,7 +129,10 @@
(as-> shape $
(cto/apply-token-to-shape {:token token-font-size
:shape $
:attributes [:font-size]})))
:attributes [:font-size]})
(cto/apply-token-to-shape {:token token-letter-spacing
:shape $
:attributes [:letter-spacing]})))
(:objects page)
{}))
@ -148,8 +157,9 @@
(t/is (= (:fill applied-tokens') "token-color"))
(t/is (= (:width applied-tokens') "token-dimensions"))
(t/is (= (:height applied-tokens') "token-dimensions"))
(t/is (= (count text1-applied-tokens) 1))
(t/is (= (:font-size text1-applied-tokens) "token-font-size"))))
(t/is (= (count text1-applied-tokens) 2))
(t/is (= (:font-size text1-applied-tokens) "token-font-size"))
(t/is (= (:letter-spacing text1-applied-tokens) "token-letter-spacing"))))
(t/deftest unapply-tokens-from-shape
(let [;; ==== Setup
@ -178,7 +188,8 @@
(cls/generate-update-shapes [(:id text1)]
(fn [shape]
(-> shape
(cto/unapply-token-id [:font-size])))
(cto/unapply-token-id [:font-size])
(cto/unapply-token-id [:letter-spacing])))
(:objects page)
{}))
@ -228,7 +239,8 @@
txt/is-content-node?
d/txt-merge
{:fills (ths/sample-fills-color :fill-color "#fabada")
:font-size "1"}))
:font-size "1"
:letter-spacing "0"}))
(:objects page)
{}))

View file

@ -333,10 +333,7 @@
(defn update-line-height
([value shape-ids attributes] (update-line-height value shape-ids attributes nil))
([value shape-ids _attributes page-id] ; The attributes param is
; needed to have the same
; arity that other update
; functions
([value shape-ids _attributes page-id]
(let [update-node? (fn [node]
(or (txt/is-text-node? node)
(txt/is-paragraph-node? node)))]
@ -346,6 +343,18 @@
{:ignore-touched true
:page-id page-id})))))
(defn update-letter-spacing
([value shape-ids attributes] (update-letter-spacing value shape-ids attributes nil))
([value shape-ids _attributes page-id]
(let [update-node? (fn [node]
(or (txt/is-text-node? node)
(txt/is-paragraph-node? node)))]
(when (number? value)
(dwsh/update-shapes shape-ids
#(txt/update-text-content % update-node? d/txt-merge {:letter-spacing (str value)})
{:ignore-touched true
:page-id page-id})))))
(defn update-font-size
([value shape-ids attributes] (update-font-size value shape-ids attributes nil))
([value shape-ids _attributes page-id]
@ -391,6 +400,14 @@
:fields [{:label "Font Size"
:key :font-size}]}}
:letter-spacing
{:title "Letter Spacing"
:attributes ctt/letter-spacing-keys
:on-update-shape update-letter-spacing
:modal {:key :tokens/letter-spacing
:fields [{:label "Letter Spacing"
:key :letter-spacing}]}}
:stroke-width
{:title "Stroke Width"
:attributes ctt/stroke-width-keys

View file

@ -34,6 +34,7 @@
ctt/opacity-keys dwta/update-opacity
#{:line-height} dwta/update-line-height
#{:font-size} dwta/update-font-size
#{:letter-spacing} dwta/update-letter-spacing
#{:x :y} dwta/update-shape-position
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin

View file

@ -2,6 +2,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.types.token :as ctt]
[app.common.types.tokens-lib :as ctob]
[app.config :as cf]
[app.main.data.style-dictionary :as sd]
@ -21,6 +22,9 @@
(def ref:token-type-open-status
(l/derived (l/key :open-status-by-type) refs/workspace-tokens))
(defn- remove-keys [m ks]
(d/removem (comp ks key) m))
(defn- get-sorted-token-groups
"Separate token-types into groups of `empty` or `filled` depending if
tokens exist for that type. Sort each group alphabetically (by their type).
@ -30,7 +34,7 @@
token-typography-types? (contains? cf/flags :token-typography-types)
all-types (cond-> dwta/token-properties
(not token-units?) (dissoc :number)
(not token-typography-types?) (dissoc :font-size))
(not token-typography-types?) (remove-keys ctt/typography-keys))
all-types (-> all-types keys seq)]
(loop [empty #js []
filled #js []

View file

@ -185,3 +185,9 @@
::mf/register-as :tokens/font-size}
[properties]
[:& token-update-create-modal properties])
(mf/defc letter-spacing-modal
{::mf/register modal/components
::mf/register-as :tokens/letter-spacing}
[properties]
[:& token-update-create-modal properties])

View file

@ -28,6 +28,7 @@
:color "drop"
:boolean "boolean-difference"
:font-size "text-font-size"
:letter-spacing "text-letterspacing"
:opacity "percentage"
:number "number"
:rotation "rotation"

View file

@ -521,6 +521,40 @@
(t/is (= (:line-height (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:line-height style-text-blocks) 1.5)))))))))
(t/deftest test-apply-letter-spacing
(t/testing "applies letter-spacing token and updates the text letter-spacing"
(t/async
done
(let [letter-spacing-token {:name "wide-spacing"
:value "2"
:type :letter-spacing}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token letter-spacing-token))))
store (ths/setup-store file)
text-1 (cths/get-shape file :text-1)
events [(dwta/apply-token {:shape-ids [(:id text-1)]
:attributes #{:letter-spacing}
:token (toht/get-token file "wide-spacing")
:on-update-shape dwta/update-letter-spacing})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
token-target' (toht/get-token file' "wide-spacing")
text-1' (cths/get-shape file' :text-1)
style-text-blocks (->> (:content text-1')
(txt/content->text+styles)
(remove (fn [[_ text]] (str/empty? (str/trim text))))
(mapv (fn [[style text]]
{:styles (merge txt/default-text-attrs style)
:text-content text}))
(first)
(:styles))]
(t/is (some? (:applied-tokens text-1')))
(t/is (= (:letter-spacing (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:letter-spacing style-text-blocks) "2")))))))))
(t/deftest test-toggle-token-none
(t/testing "should apply token to all selected items, where no item has the token applied"
(t/async