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

View file

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

View file

@ -333,10 +333,7 @@
(defn update-line-height (defn update-line-height
([value shape-ids attributes] (update-line-height value shape-ids attributes nil)) ([value shape-ids attributes] (update-line-height value shape-ids attributes nil))
([value shape-ids _attributes page-id] ; The attributes param is ([value shape-ids _attributes page-id]
; needed to have the same
; arity that other update
; functions
(let [update-node? (fn [node] (let [update-node? (fn [node]
(or (txt/is-text-node? node) (or (txt/is-text-node? node)
(txt/is-paragraph-node? node)))] (txt/is-paragraph-node? node)))]
@ -346,6 +343,18 @@
{:ignore-touched true {:ignore-touched true
:page-id page-id}))))) :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 (defn update-font-size
([value shape-ids attributes] (update-font-size value shape-ids attributes nil)) ([value shape-ids attributes] (update-font-size value shape-ids attributes nil))
([value shape-ids _attributes page-id] ([value shape-ids _attributes page-id]
@ -391,6 +400,14 @@
:fields [{:label "Font Size" :fields [{:label "Font Size"
:key :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 :stroke-width
{:title "Stroke Width" {:title "Stroke Width"
:attributes ctt/stroke-width-keys :attributes ctt/stroke-width-keys

View file

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

View file

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

View file

@ -185,3 +185,9 @@
::mf/register-as :tokens/font-size} ::mf/register-as :tokens/font-size}
[properties] [properties]
[:& token-update-create-modal 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" :color "drop"
:boolean "boolean-difference" :boolean "boolean-difference"
:font-size "text-font-size" :font-size "text-font-size"
:letter-spacing "text-letterspacing"
:opacity "percentage" :opacity "percentage"
:number "number" :number "number"
:rotation "rotation" :rotation "rotation"

View file

@ -521,6 +521,40 @@
(t/is (= (:line-height (:applied-tokens text-1')) (:name token-target'))) (t/is (= (:line-height (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:line-height style-text-blocks) 1.5))))))))) (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/deftest test-toggle-token-none
(t/testing "should apply token to all selected items, where no item has the token applied" (t/testing "should apply token to all selected items, where no item has the token applied"
(t/async (t/async