Implement new token-type :font-families

This commit is contained in:
Florian Schroedl (aider) 2025-07-01 09:34:40 +02:00 committed by Andrés Moya
parent 2cddc6fb5b
commit d788a4d252
16 changed files with 544 additions and 196 deletions

View file

@ -10,6 +10,7 @@
[app.common.schema :as sm] [app.common.schema :as sm]
[clojure.data :as data] [clojure.data :as data]
[clojure.set :as set] [clojure.set :as set]
[cuerdas.core :as str]
[malli.util :as mu])) [malli.util :as mu]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -29,20 +30,21 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def token-type->dtcg-token-type (def token-type->dtcg-token-type
{:boolean "boolean" {:boolean "boolean"
:border-radius "borderRadius" :border-radius "borderRadius"
:color "color" :color "color"
:dimensions "dimension" :dimensions "dimension"
:font-size "fontSizes" :font-family "fontFamilies"
:font-size "fontSizes"
:letter-spacing "letterSpacing" :letter-spacing "letterSpacing"
:number "number" :number "number"
:opacity "opacity" :opacity "opacity"
:other "other" :other "other"
:rotation "rotation" :rotation "rotation"
:sizing "sizing" :sizing "sizing"
:spacing "spacing" :spacing "spacing"
:string "string" :string "string"
:stroke-width "strokeWidth"}) :stroke-width "strokeWidth"})
(def dtcg-token-type->token-type (def dtcg-token-type->token-type
(set/map-invert token-type->dtcg-token-type)) (set/map-invert token-type->dtcg-token-type))
@ -133,7 +135,13 @@
(def letter-spacing-keys (schema-keys schema:letter-spacing)) (def letter-spacing-keys (schema-keys schema:letter-spacing))
(def typography-keys (set/union font-size-keys letter-spacing-keys)) (def ^:private schema:font-family
[:map
[:font-family {:optional true} token-name-ref]])
(def font-family-keys (schema-keys schema:font-family))
(def typography-keys (set/union font-size-keys letter-spacing-keys font-family-keys))
;; TODO: Created to extract the font-size feature from the typography feature flag. ;; TODO: Created to extract the font-size feature from the typography feature flag.
;; Delete this once the typography feature flag is removed. ;; Delete this once the typography feature flag is removed.
@ -169,6 +177,7 @@
schema:number schema:number
schema:font-size schema:font-size
schema:letter-spacing schema:letter-spacing
schema:font-family
schema:dimensions]) schema:dimensions])
(defn shape-attr->token-attrs (defn shape-attr->token-attrs
@ -198,6 +207,7 @@
(font-size-keys shape-attr) #{shape-attr} (font-size-keys shape-attr) #{shape-attr}
(letter-spacing-keys shape-attr) #{shape-attr} (letter-spacing-keys shape-attr) #{shape-attr}
(font-family-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}
@ -291,3 +301,23 @@
(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))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TYPOGRAPHY
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn split-font-family
"Splits font family `value` string from into vector of font families.
Doesn't handle possible edge-case of font-families with `,` in their font family name."
[font-value]
(let [families (str/split font-value ",")
xform (comp
(map str/trim)
(remove str/empty?))]
(into [] xform families)))
(defn join-font-family
"Joins font family `value` into a string to be edited with a single input."
[font-families]
(str/join ", " font-families))

View file

@ -1333,13 +1333,25 @@ Will return a value that matches this schema:
(walk/postwalk (walk/postwalk
(fn [node] (fn [node]
(cond-> node (cond-> node
;; Handle sequential values that are objects with type
(and (map? node) (and (map? node)
(contains? node "value") (contains? node "value")
(sequential? (get node "value"))) (sequential? (get node "value"))
(map? (first (get node "value"))))
(update "value" (update "value"
(fn [seq-value] (fn [seq-value]
(map #(set/rename-keys % {"type" "$type"}) seq-value))) (map #(set/rename-keys % {"type" "$type"}) seq-value)))
;; Keep array of font families
(and (map? node)
(contains? node "type")
(= "fontFamilies" (get node "type"))
(contains? node "value")
(sequential? (get node "value"))
(not (map? (first (get node "value")))))
identity
;; Rename keys for all token nodes
(and (map? node) (and (map? node)
(and (contains? node "type") (and (contains? node "type")
(contains? node "value"))) (contains? node "value")))
@ -1371,7 +1383,16 @@ Will return a value that matches this schema:
(assoc tokens child-path (make-token (assoc tokens child-path (make-token
:name child-path :name child-path
:type token-type :type token-type
:value (get v "$value") :value (cond-> (get v "$value")
;; Split string of font-families
(and (= :font-family token-type)
(string? (get v "$value")))
cto/split-font-family
;; Keep array of font-families
(and (= :font-family token-type)
(sequential? (get v "$value")))
identity)
:description (get v "$description"))) :description (get v "$description")))
;; Discard unknown type tokens ;; Discard unknown type tokens
tokens))))) tokens)))))

View file

@ -62,7 +62,11 @@
(ctob/add-token-in-set "test-token-set" (ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-letter-spacing" (ctob/make-token :name "token-letter-spacing"
:type :letter-spacing :type :letter-spacing
:value 2)))) :value 2))
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-font-family"
:type :font-family
:value ["Helvetica" "Arial" "sans-serif"]))))
(tho/add-frame :frame1) (tho/add-frame :frame1)
(tho/add-text :text1 "Hello World!"))) (tho/add-text :text1 "Hello World!")))
@ -77,7 +81,8 @@
(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))) (tht/apply-token-to-shape :text1 "token-letter-spacing" [:letter-spacing] [:letter-spacing] 2)
(tht/apply-token-to-shape :text1 "token-font-family" [:font-family] [:font-family] ["Helvetica" "Arial" "sans-serif"])))
(t/deftest apply-tokens-to-shape (t/deftest apply-tokens-to-shape
(let [;; ==== Setup (let [;; ==== Setup
@ -93,6 +98,7 @@
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") token-letter-spacing (tht/get-token file "test-token-set" "token-letter-spacing")
token-font-family (tht/get-token file "test-token-set" "token-font-family")
;; ==== Action ;; ==== Action
changes (-> (-> (pcb/empty-changes nil) changes (-> (-> (pcb/empty-changes nil)
@ -132,7 +138,10 @@
:attributes [:font-size]}) :attributes [:font-size]})
(cto/apply-token-to-shape {:token token-letter-spacing (cto/apply-token-to-shape {:token token-letter-spacing
:shape $ :shape $
:attributes [:letter-spacing]}))) :attributes [:letter-spacing]})
(cto/apply-token-to-shape {:token token-font-family
:shape $
:attributes [:font-family]})))
(:objects page) (:objects page)
{})) {}))
@ -157,9 +166,10 @@
(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) 2)) (t/is (= (count text1-applied-tokens) 3))
(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/is (= (:letter-spacing text1-applied-tokens) "token-letter-spacing"))
(t/is (= (:font-family text1-applied-tokens) "token-font-family"))))
(t/deftest unapply-tokens-from-shape (t/deftest unapply-tokens-from-shape
(let [;; ==== Setup (let [;; ==== Setup
@ -189,7 +199,8 @@
(fn [shape] (fn [shape]
(-> shape (-> shape
(cto/unapply-token-id [:font-size]) (cto/unapply-token-id [:font-size])
(cto/unapply-token-id [:letter-spacing]))) (cto/unapply-token-id [:letter-spacing])
(cto/unapply-token-id [:font-family])))
(:objects page) (:objects page)
{})) {}))
@ -240,7 +251,8 @@
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"})) :letter-spacing "0"
:font-family "Arial"}))
(:objects page) (:objects page)
{})) {}))

View file

@ -195,6 +195,7 @@
value (.-value sd-token) value (.-value sd-token)
has-references? (str/includes? (:value origin-token) "{") has-references? (str/includes? (:value origin-token) "{")
parsed-token-value (case (:type origin-token) parsed-token-value (case (:type origin-token)
:font-family {:value (-> (js->clj value) (flatten))}
:color (parse-sd-token-color-value value) :color (parse-sd-token-color-value value)
:opacity (parse-sd-token-opacity-value value has-references?) :opacity (parse-sd-token-opacity-value value has-references?)
:stroke-width (parse-sd-token-stroke-width-value value has-references?) :stroke-width (parse-sd-token-stroke-width-value value has-references?)

View file

@ -14,6 +14,7 @@
[app.common.types.token :as ctt] [app.common.types.token :as ctt]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.uuid :as uuid]
[app.main.data.event :as ev] [app.main.data.event :as ev]
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.style-dictionary :as sd] [app.main.data.style-dictionary :as sd]
@ -24,6 +25,7 @@
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.transforms :as dwtr] [app.main.data.workspace.transforms :as dwtr]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.fonts :as fonts]
[app.main.store :as st] [app.main.store :as st]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[clojure.set :as set] [clojure.set :as set]
@ -364,6 +366,26 @@
{:ignore-touched true {:ignore-touched true
:page-id page-id}))))) :page-id page-id})))))
(defn update-font-family
([value shape-ids attributes] (update-font-family value shape-ids attributes nil))
([value shape-ids _attributes page-id]
(let [font-family (first value)
font (some-> font-family
(fonts/find-font-family))
text-attrs (if font
{:font-id (:id font)
:font-family (:family font)}
{:font-id (str uuid/zero)
:font-family font-family})
update-node? (fn [node]
(or (txt/is-text-node? node)
(txt/is-paragraph-node? node)))]
(when text-attrs
(dwsh/update-shapes shape-ids
#(txt/update-text-content % update-node? d/txt-merge text-attrs)
{: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]
@ -421,6 +443,14 @@
:fields [{:label "Letter Spacing" :fields [{:label "Letter Spacing"
:key :letter-spacing}]}} :key :letter-spacing}]}}
:font-family
{:title "Font Family"
:attributes ctt/font-family-keys
:on-update-shape update-font-family
:modal {:key :tokens/font-family
:fields [{:label "Font Family"
:key :font-family}]}}
:stroke-width :stroke-width
{:title "Stroke Width" {:title "Stroke Width"
:attributes ctt/stroke-width-keys :attributes ctt/stroke-width-keys

View file

@ -35,6 +35,7 @@
#{: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 #{:letter-spacing} dwta/update-letter-spacing
#{:font-family} dwta/update-font-family
#{: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

@ -78,6 +78,15 @@
data)) data))
(vals @fontsdb))) (vals @fontsdb)))
(defn find-font-family
"Case insensitive lookup of font-family."
[family]
(let [family' (str/lower family)]
(d/seek
(fn [{:keys [family]}]
(= family' (str/lower family)))
(vals @fontsdb))))
(defn resolve-variants (defn resolve-variants
[id] [id]
(get-in @fontsdb [id :variants])) (get-in @fontsdb [id :variants]))

View file

@ -20,7 +20,7 @@
[:class {:optional true} :string] [:class {:optional true} :string]
[:id :string] [:id :string]
[:icon {:optional true} [:icon {:optional true}
[:and :string [:fn #(contains? icon-list %)]]] [:maybe [:and :string [:fn #(contains? icon-list %)]]]]
[:has-hint {:optional true} :boolean] [:has-hint {:optional true} :boolean]
[:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]] [:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]
[:type {:optional true} :string] [:type {:optional true} :string]

View file

@ -11,6 +11,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cft] [app.common.files.tokens :as cft]
[app.common.types.color :as c] [app.common.types.color :as c]
[app.common.types.token :as ctt]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.main.constants :refer [max-input-length]] [app.main.constants :refer [max-input-length]]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
@ -21,9 +22,11 @@
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.tokens.propagation :as dwtp] [app.main.data.workspace.tokens.propagation :as dwtp]
[app.main.data.workspace.tokens.warnings :as wtw] [app.main.data.workspace.tokens.warnings :as wtw]
[app.main.fonts :as fonts]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.input :refer [input*]] [app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]] [app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
[app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.foundations.assets.icon :as i]
@ -31,6 +34,8 @@
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]] [app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.workspace.colorpicker :as colorpicker] [app.main.ui.workspace.colorpicker :as colorpicker]
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]] [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
[app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]]
[app.main.ui.workspace.tokens.management.create.input-token-color-bullet :refer [input-token-color-bullet*]]
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-tokens-value*]] [app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-tokens-value*]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.functions :as uf] [app.util.functions :as uf]
@ -94,7 +99,17 @@
(defn valid-value? [value] (defn valid-value? [value]
(seq (finalize-value value))) (seq (finalize-value value)))
;; Component ------------------------------------------------------------------- ;; Validation ------------------------------------------------------------------
(defn validate-empty-input [value]
(if (sequential? value)
(empty? value)
(empty? (str/trim value))))
(defn validate-self-reference? [token-name value]
(if (sequential? value)
(some #(ctob/token-value-self-reference? token-name %) value)
(ctob/token-value-self-reference? token-name value)))
(defn validate-token-value (defn validate-token-value
"Validates token value by resolving the value `input` using `StyleDictionary`. "Validates token value by resolving the value `input` using `StyleDictionary`.
@ -104,17 +119,19 @@
;; so we use a temporary token name that hopefully doesn't clash with any of the users token names ;; so we use a temporary token name that hopefully doesn't clash with any of the users token names
token-name (if (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value)] token-name (if (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value)]
(cond (cond
(empty? (str/trim value)) (validate-empty-input value)
(rx/throw {:errors [(wte/get-error-code :error.token/empty-input)]}) (rx/throw {:errors [(wte/get-error-code :error.token/empty-input)]})
(ctob/token-value-self-reference? token-name value) (validate-self-reference? token-name value)
(rx/throw {:errors [(wte/get-error-code :error.token/direct-self-reference)]}) (rx/throw {:errors [(wte/get-error-code :error.token/direct-self-reference)]})
:else :else
(let [tokens' (cond-> tokens (let [tokens' (cond-> tokens
;; Remove previous token when renaming a token ;; Remove previous token when renaming a token
(not= name-value (:name token)) (dissoc (:name token)) (not= name-value (:name token)) (dissoc (:name token))
:always (update token-name #(ctob/make-token (merge % {:value value :always (update token-name #(ctob/make-token (merge % {:value (cond
(= (:type token) :font-family) (ctt/split-font-family value)
:else value)
:name token-name :name token-name
:type (:type token)}))))] :type (:type token)}))))]
(->> tokens' (->> tokens'
@ -156,59 +173,7 @@
(defonce form-token-cache-atom (atom nil)) (defonce form-token-cache-atom (atom nil))
;; FIXME: this function has confusing name ;; Component -------------------------------------------------------------------
(defn- hex->value
[hex]
(when-let [tc (tinycolor/valid-color hex)]
(let [hex (tinycolor/->hex-string tc)
alpha (tinycolor/alpha tc)
[r g b] (c/hex->rgb hex)
[h s v] (c/hex->hsv hex)]
{:hex hex
:r r :g g :b b
:h h :s s :v v
:alpha alpha})))
(mf/defc ramp*
[{:keys [color on-change]}]
(let [wrapper-node-ref (mf/use-ref nil)
dragging-ref (mf/use-ref false)
on-start-drag
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
on-finish-drag
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
internal-color*
(mf/use-state #(hex->value color))
internal-color
(deref internal-color*)
on-change'
(mf/use-fn
(mf/deps on-change)
(fn [{:keys [hex alpha] :as selector-color}]
(let [dragging? (mf/ref-val dragging-ref)]
(when-not (and dragging? hex)
(reset! internal-color* selector-color)
(on-change hex alpha)))))]
(mf/use-effect
(mf/deps color)
(fn []
;; Update internal color when user changes input value
(when-let [color (tinycolor/valid-color color)]
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
(reset! internal-color* (hex->value color))))))
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
[:div {:ref wrapper-node-ref}
[:> ramp-selector*
{:color internal-color
:on-start-drag on-start-drag
:on-finish-drag on-finish-drag
:on-change on-change'}]]))
(mf/defc token-value-hint (mf/defc token-value-hint
[{:keys [result]}] [{:keys [result]}]
@ -232,13 +197,11 @@
:class (stl/css-case :resolved-value (not (or empty-message? (seq warnings) (seq errors)))) :class (stl/css-case :resolved-value (not (or empty-message? (seq warnings) (seq errors))))
:type type}])) :type type}]))
(mf/defc form (mf/defc form*
{::mf/wrap-props false} [{:keys [token token-type action selected-token-set-name transform-value on-value-resolve custom-input-token-value custom-input-token-value-props]}]
[{:keys [token token-type action selected-token-set-name on-display-colorpicker]}]
(let [create? (not (instance? ctob/Token token)) (let [create? (not (instance? ctob/Token token))
token (or token {:type token-type}) token (or token {:type token-type})
token-properties (dwta/get-token-properties token) token-properties (dwta/get-token-properties token)
is-color-token (cft/color-token? token)
tokens-in-selected-set (mf/deref refs/workspace-all-tokens-in-selected-set) tokens-in-selected-set (mf/deref refs/workspace-all-tokens-in-selected-set)
active-theme-tokens (cond-> (mf/deref refs/workspace-active-theme-sets-tokens) active-theme-tokens (cond-> (mf/deref refs/workspace-active-theme-sets-tokens)
@ -246,7 +209,11 @@
;; even if the name has been overriden by a token with the same name ;; even if the name has been overriden by a token with the same name
;; in another set below. ;; in another set below.
(and (:name token) (:value token)) (and (:name token) (:value token))
(assoc (:name token) token)) (assoc (:name token) token)
;; Style dictionary resolver needs font families to be an array of strings
(= :font-family (or (:type token) token-type))
(update-in [(:name token) :value] ctt/split-font-family))
resolved-tokens (sd/use-resolved-tokens active-theme-tokens {:cache-atom form-token-cache-atom resolved-tokens (sd/use-resolved-tokens active-theme-tokens {:cache-atom form-token-cache-atom
:interactive? true}) :interactive? true})
@ -260,13 +227,6 @@
(-> (ctob/tokens-tree tokens-in-selected-set) (-> (ctob/tokens-tree tokens-in-selected-set)
;; Allow setting editing token to it's own path ;; Allow setting editing token to it's own path
(d/dissoc-in token-path)))) (d/dissoc-in token-path))))
cancel-ref (mf/use-ref nil)
on-cancel-ref
(mf/use-fn
(fn [node]
(mf/set-ref-val! cancel-ref node)))
;; Name ;; Name
touched-name* (mf/use-state false) touched-name* (mf/use-state false)
touched-name? (deref touched-name*) touched-name? (deref touched-name*)
@ -286,16 +246,13 @@
on-blur-name on-blur-name
(mf/use-fn (mf/use-fn
(mf/deps cancel-ref touched-name? warning-name-change?) (mf/deps touched-name? warning-name-change?)
(fn [e] (fn [e]
(let [node (dom/get-related-target e) (let [value (dom/get-target-val e)
on-cancel-btn (= node (mf/ref-val cancel-ref))] errors (validate-name value)]
(when-not on-cancel-btn (when touched-name?
(let [value (dom/get-target-val e) (reset! warning-name-change* true))
errors (validate-name value)] (reset! name-errors errors))))
(when touched-name?
(reset! warning-name-change* true))
(reset! name-errors errors))))))
on-update-name-debounced on-update-name-debounced
(mf/use-fn (mf/use-fn
@ -321,10 +278,6 @@
(valid-name? @token-name-ref)) (valid-name? @token-name-ref))
;; Value ;; Value
color* (mf/use-state (when is-color-token (:value token)))
color (deref color*)
color-ramp-open* (mf/use-state false)
color-ramp-open? (deref color-ramp-open*)
value-input-ref (mf/use-ref nil) value-input-ref (mf/use-ref nil)
value-ref (mf/use-ref (:value token)) value-ref (mf/use-ref (:value token))
@ -333,68 +286,46 @@
set-resolve-value set-resolve-value
(mf/use-fn (mf/use-fn
(mf/deps on-value-resolve)
(fn [token-or-err] (fn [token-or-err]
(let [error? (:errors token-or-err) (let [error? (:errors token-or-err)
warnings? (:warnings token-or-err) warnings? (:warnings token-or-err)
v (cond v (cond
error? error?
token-or-err (do
(when on-value-resolve (on-value-resolve nil))
token-or-err)
warnings? warnings?
(:warnings {:warnings token-or-err}) (:warnings {:warnings token-or-err})
:else :else
(:resolved-value token-or-err))] (cond-> (:resolved-value token-or-err)
(when is-color-token (reset! color* (if error? nil v))) on-value-resolve on-value-resolve))]
(reset! token-resolve-result* v)))) (reset! token-resolve-result* v))))
on-update-value-debounced (use-debonced-resolve-callback token-name-ref token active-theme-tokens set-resolve-value) on-update-value-debounced (use-debonced-resolve-callback token-name-ref token active-theme-tokens set-resolve-value)
on-update-value (mf/use-fn on-update-value
(mf/deps on-update-value-debounced)
(fn [e]
(let [value (dom/get-target-val e)
;; Automatically add # for hex values
value' (if (and is-color-token (tinycolor/hex-without-hash-prefix? value))
(let [hex (dm/str "#" value)]
(dom/set-value! (mf/ref-val value-input-ref) hex)
hex)
value)]
(mf/set-ref-val! value-ref value')
(on-update-value-debounced value'))))
on-update-color (mf/use-fn
(mf/deps color on-update-value-debounced)
(fn [hex-value alpha]
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
prev-input-color (some-> (dom/get-value (mf/ref-val value-input-ref))
(tinycolor/valid-color))
;; If the input is a reference we will take the format from the computed value
prev-computed-color (when-not prev-input-color
(some-> color (tinycolor/valid-color)))
prev-format (some-> (or prev-input-color prev-computed-color)
(tinycolor/color-format))
to-rgba? (and
(< alpha 1)
(or (= prev-format "hex") (not prev-format)))
to-hex? (and (not prev-format) (= alpha 1))
format (cond
to-rgba? "rgba"
to-hex? "hex"
prev-format prev-format
:else "hex")
color-value (-> (tinycolor/valid-color hex-value)
(tinycolor/set-alpha (or alpha 1))
(tinycolor/->string format))]
(mf/set-ref-val! value-ref color-value)
(dom/set-value! (mf/ref-val value-input-ref) color-value)
(on-update-value-debounced color-value))))
on-display-colorpicker'
(mf/use-fn (mf/use-fn
(mf/deps color-ramp-open? on-display-colorpicker) (mf/deps on-update-value-debounced transform-value)
(fn [] (fn [e]
(let [open? (not color-ramp-open?)] (let [value (dom/get-target-val e)
(reset! color-ramp-open* open?) value' (if (fn? transform-value)
(on-display-colorpicker open?)))) (transform-value value)
value)]
;; Value got updated in transform, update the dom node
(when (not= value value')
(dom/set-value! (mf/ref-val value-input-ref) value'))
(mf/set-ref-val! value-ref value)
(on-update-value-debounced value))))
on-external-update-value
(mf/use-fn
(mf/deps on-update-value-debounced)
(fn [next-value]
(dom/set-value! (mf/ref-val value-input-ref) next-value)
(mf/set-ref-val! value-ref next-value)
(on-update-value-debounced next-value)))
value-error? (seq (:errors token-resolve-result)) value-error? (seq (:errors token-resolve-result))
@ -431,7 +362,6 @@
(mf/deps validate-name validate-descripion token active-theme-tokens) (mf/deps validate-name validate-descripion token active-theme-tokens)
(fn [e] (fn [e]
(dom/prevent-default e) (dom/prevent-default e)
(mf/set-ref-val! cancel-ref nil)
;; We have to re-validate the current form values before submitting ;; We have to re-validate the current form values before submitting
;; because the validation is asynchronous/debounced ;; because the validation is asynchronous/debounced
;; and the user might have edited a valid form to make it invalid, ;; and the user might have edited a valid form to make it invalid,
@ -440,7 +370,11 @@
valid-name? (try valid-name? (try
(not (:errors (validate-name final-name))) (not (:errors (validate-name final-name)))
(catch js/Error _ nil)) (catch js/Error _ nil))
final-value (finalize-value (mf/ref-val value-ref)) final-value (let [value (mf/ref-val value-ref)
font-family? (= :font-family (or (:type token) token-type))]
(if font-family?
(ctt/split-font-family value)
(finalize-value value)))
final-description @description-ref final-description @description-ref
valid-description? (if final-description valid-description? (if final-description
(try (try
@ -479,9 +413,9 @@
on-cancel on-cancel
(mf/use-fn (mf/use-fn
(fn [e] (fn [e]
(mf/set-ref-val! cancel-ref nil)
(dom/prevent-default e) (dom/prevent-default e)
(modal/hide!))) (modal/hide!)))
handle-key-down-delete handle-key-down-delete
(mf/use-fn (mf/use-fn
(mf/deps on-delete-token) (mf/deps on-delete-token)
@ -555,20 +489,31 @@
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])] {:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
[:div {:class (stl/css :input-row)} [:div {:class (stl/css :input-row)}
[:> input-tokens-value* (let [placeholder (tr "workspace.tokens.token-value-enter")
{:placeholder (tr "workspace.tokens.token-value-enter") label (tr "workspace.tokens.token-value")
:label (tr "workspace.tokens.token-value") default-value (mf/ref-val value-ref)
:default-value (mf/ref-val value-ref) ref value-input-ref
:ref value-input-ref error (not (nil? (:errors token-resolve-result)))
:is-color-token is-color-token on-blur on-update-value]
:color color (if (fn? custom-input-token-value)
:on-change on-update-value [:> custom-input-token-value
:error (not (nil? (:errors token-resolve-result))) {:placeholder placeholder
:display-colorpicker on-display-colorpicker' :label label
:on-blur on-update-value}] :default-value default-value
(when color-ramp-open? :input-ref ref
[:> ramp* {:color (some-> color (tinycolor/valid-color)) :error error
:on-change on-update-color}]) :on-blur on-blur
:on-update-value on-update-value
:on-external-update-value on-external-update-value
:custom-input-token-value-props custom-input-token-value-props}]
[:> input-tokens-value*
{:placeholder placeholder
:label label
:default-value default-value
:ref ref
:error error
:on-blur on-blur
:on-change on-update-value}]))
[:& token-value-hint {:result token-resolve-result}]] [:& token-value-hint {:result token-resolve-result}]]
[:div {:class (stl/css :input-row)} [:div {:class (stl/css :input-row)}
[:> input* {:label (tr "workspace.tokens.token-description") [:> input* {:label (tr "workspace.tokens.token-description")
@ -593,7 +538,6 @@
[:> button* {:on-click on-cancel [:> button* {:on-click on-cancel
:on-key-down handle-key-down-cancel :on-key-down handle-key-down-cancel
:type "button" :type "button"
:on-ref on-cancel-ref
:id "token-modal-cancel" :id "token-modal-cancel"
:variant "secondary"} :variant "secondary"}
(tr "labels.cancel")] (tr "labels.cancel")]
@ -602,3 +546,247 @@
:variant "primary" :variant "primary"
:disabled disabled?} :disabled disabled?}
(tr "labels.save")]]]])) (tr "labels.save")]]]]))
;; FIXME: this function has confusing name
(defn- hex->value
[hex]
(when-let [tc (tinycolor/valid-color hex)]
(let [hex (tinycolor/->hex-string tc)
alpha (tinycolor/alpha tc)
[r g b] (c/hex->rgb hex)
[h s v] (c/hex->hsv hex)]
{:hex hex
:r r :g g :b b
:h h :s s :v v
:alpha alpha})))
(mf/defc ramp*
[{:keys [color on-change]}]
(let [wrapper-node-ref (mf/use-ref nil)
dragging-ref (mf/use-ref false)
on-start-drag
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
on-finish-drag
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
internal-color*
(mf/use-state #(hex->value color))
internal-color
(deref internal-color*)
on-change'
(mf/use-fn
(mf/deps on-change)
(fn [{:keys [hex alpha] :as selector-color}]
(let [dragging? (mf/ref-val dragging-ref)]
(when-not (and dragging? hex)
(reset! internal-color* selector-color)
(on-change hex alpha)))))]
(mf/use-effect
(mf/deps color)
(fn []
;; Update internal color when user changes input value
(when-let [color (tinycolor/valid-color color)]
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
(reset! internal-color* (hex->value color))))))
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
[:div {:ref wrapper-node-ref}
[:> ramp-selector*
{:color internal-color
:on-start-drag on-start-drag
:on-finish-drag on-finish-drag
:on-change on-change'}]]))
(mf/defc color-picker*
[{:keys [placeholder label default-value input-ref error on-blur on-update-value on-external-update-value custom-input-token-value-props]}]
(let [{:keys [color on-display-colorpicker]} custom-input-token-value-props
color-ramp-open* (mf/use-state false)
color-ramp-open? (deref color-ramp-open*)
on-click-swatch
(mf/use-fn
(mf/deps color-ramp-open? on-display-colorpicker)
(fn []
(let [open? (not color-ramp-open?)]
(reset! color-ramp-open* open?)
(on-display-colorpicker open?))))
swatch
(mf/html
[:> input-token-color-bullet*
{:color color
:class (stl/css :slot-start)
:on-click on-click-swatch}])
on-change'
(mf/use-fn
(mf/deps color on-external-update-value)
(fn [hex-value alpha]
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
prev-input-color (some-> (dom/get-value (mf/ref-val input-ref))
(tinycolor/valid-color))
;; If the input is a reference we will take the format from the computed value
prev-computed-color (when-not prev-input-color
(some-> color (tinycolor/valid-color)))
prev-format (some-> (or prev-input-color prev-computed-color)
(tinycolor/color-format))
to-rgba? (and
(< alpha 1)
(or (= prev-format "hex") (not prev-format)))
to-hex? (and (not prev-format) (= alpha 1))
format (cond
to-rgba? "rgba"
to-hex? "hex"
prev-format prev-format
:else "hex")
color-value (-> (tinycolor/valid-color hex-value)
(tinycolor/set-alpha (or alpha 1))
(tinycolor/->string format))]
(on-external-update-value color-value))))]
[:*
[:> input-tokens-value*
{:placeholder placeholder
:label label
:default-value default-value
:ref input-ref
:error error
:on-blur on-blur
:on-change on-update-value
:slot-start swatch}]
(when color-ramp-open?
[:> ramp*
{:color (some-> color (tinycolor/valid-color))
:on-change on-change'}])]))
(mf/defc color-form*
[{:keys [token on-display-colorpicker] :rest props}]
(let [color* (mf/use-state (:value token))
color (deref color*)
on-value-resolve (mf/use-fn
(mf/deps color)
(fn [value]
(reset! color* value)
value))
custom-input-token-value-props
(mf/use-memo
(mf/deps color on-display-colorpicker)
(fn []
{:color color
:on-display-colorpicker on-display-colorpicker}))
transform-value
(mf/use-fn
(fn [value]
(if (tinycolor/hex-without-hash-prefix? value)
(dm/str "#" value)
value)))]
[:> form*
(mf/spread-props props {:token token
:transform-value transform-value
:on-value-resolve on-value-resolve
:custom-input-token-value color-picker*
:custom-input-token-value-props custom-input-token-value-props})]))
(mf/defc font-selector-wrapper*
[{:keys [font input-ref on-select-font on-close-font-selector]}]
(let [current-font* (mf/use-state (or font
(some-> (mf/ref-val input-ref)
(dom/get-value)
(ctt/split-font-family)
(first)
(fonts/find-font-family))))
current-font (deref current-font*)]
[:div {:class (stl/css :font-select-wrapper)}
[:> font-selector* {:current-font current-font
:on-select on-select-font
:on-close on-close-font-selector
:full-size true}]]))
(mf/defc font-picker*
[{:keys [default-value input-ref error on-blur on-update-value on-external-update-value]}]
(let [font* (mf/use-state (fonts/find-font-family default-value))
font (deref font*)
set-font (mf/use-fn
(mf/deps font)
#(reset! font* %))
font-selector-open* (mf/use-state false)
font-selector-open? (deref font-selector-open*)
on-close-font-selector
(mf/use-fn
(fn []
(reset! font-selector-open* false)))
on-click-dropdown-button
(mf/use-fn
(mf/deps font-selector-open?)
(fn [e]
(dom/prevent-default e)
(reset! font-selector-open* (not font-selector-open?))))
on-select-font
(mf/use-fn
(mf/deps on-external-update-value set-font)
(fn [{:keys [family] :as font}]
(when font
(set-font font)
(on-external-update-value family))))
on-update-value'
(mf/use-fn
(mf/deps on-update-value set-font)
(fn [value]
(set-font nil)
(on-update-value value)))
font-selector-button
(mf/html
[:> icon-button*
{:on-click on-click-dropdown-button
:aria-label (tr "workspace.tokens.token-font-family-select")
:icon "arrow-down"
:variant "action"
:type "button"}])]
[:*
[:> input-tokens-value*
{:placeholder (tr "workspace.tokens.token-font-family-value-enter")
:label (tr "workspace.tokens.token-font-family-value")
:default-value default-value
:ref input-ref
:error error
:on-blur on-blur
:on-change on-update-value'
:icon "text-font-family"
:slot-end font-selector-button}]
(when font-selector-open?
[:> font-selector-wrapper* {:font font
:input-ref input-ref
:on-select-font on-select-font
:on-close-font-selector on-close-font-selector}])]))
(mf/defc font-family-form*
[{:keys [token] :rest props}]
(let [on-value-resolve
(mf/use-fn
(fn [value]
(when value
(ctt/join-font-family value))))]
[:> form*
(mf/spread-props props {:token (when token (update token :value ctt/join-font-family))
:custom-input-token-value font-picker*
:on-value-resolve on-value-resolve})]))
(mf/defc form-wrapper*
[{:keys [token token-type] :as props}]
(let [token-type' (or (:type token) token-type)]
(case token-type'
:color [:> color-form* props]
:font-family [:> font-family-form* props]
[:> form* props])))

View file

@ -8,6 +8,7 @@
.form-wrapper { .form-wrapper {
width: $s-384; width: $s-384;
position: relative;
} }
.button-row { .button-row {
@ -64,3 +65,11 @@
.form-modal-title { .form-modal-title {
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
} }
.font-select-wrapper {
position: absolute;
inset: 0;
// This padding from the modal should be shared as a variable
// Need to set this or the font-select will cause scroll
bottom: $s-32;
}

View file

@ -7,11 +7,10 @@
(ns app.main.ui.workspace.tokens.management.create.input-tokens-value (ns app.main.ui.workspace.tokens.management.create.input-tokens-value
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.ui.ds.controls.utilities.input-field :refer [input-field*]] [app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
[app.main.ui.ds.controls.utilities.label :refer [label*]] [app.main.ui.ds.controls.utilities.label :refer [label*]]
[app.main.ui.workspace.tokens.management.create.input-token-color-bullet :refer [input-token-color-bullet*]] [app.main.ui.ds.foundations.assets.icon :refer [icon-list]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def ^:private schema::input-tokens-value (def ^:private schema::input-tokens-value
@ -20,26 +19,18 @@
[:placeholder {:optional true} :string] [:placeholder {:optional true} :string]
[:value {:optional true} [:maybe :string]] [:value {:optional true} [:maybe :string]]
[:class {:optional true} :string] [:class {:optional true} :string]
[:is-color-token {:optional true} :boolean] [:error {:optional true} :boolean]
[:color {:optional true} [:maybe :string]] [:slot-start {:optional true} [:maybe some?]]
[:display-colorpicker {:optional true} fn?] [:icon {:optional true}
[:error {:optional true} :boolean]]) [:maybe [:and :string [:fn #(contains? icon-list %)]]]]])
(mf/defc input-tokens-value* (mf/defc input-tokens-value*
{::mf/props :obj {::mf/props :obj
::mf/forward-ref true ::mf/forward-ref true
::mf/schema schema::input-tokens-value} ::mf/schema schema::input-tokens-value}
[{:keys [class label is-color-token placeholder error value color display-colorpicker] :rest props} ref] [{:keys [class label placeholder error value icon slot-start] :rest props} ref]
(let [id (mf/use-id) (let [id (mf/use-id)
input-ref (mf/use-ref) input-ref (mf/use-ref)
is-color-token (d/nilv is-color-token false)
swatch
(mf/html [:> input-token-color-bullet*
{:color color
:class (stl/css :slot-start)
:on-click display-colorpicker}])
props (mf/spread-props props {:id id props (mf/spread-props props {:id id
:type "text" :type "text"
:class (stl/css :input) :class (stl/css :input)
@ -47,7 +38,8 @@
:value value :value value
:variant "comfortable" :variant "comfortable"
:hint-type (when error "error") :hint-type (when error "error")
:slot-start (when is-color-token swatch) :slot-start slot-start
:icon icon
:ref (or ref input-ref)})] :ref (or ref input-ref)})]
[:div {:class (dm/str class " " (stl/css-case :wrapper true [:div {:class (dm/str class " " (stl/css-case :wrapper true
:input-error error))} :input-error error))}

View file

@ -12,7 +12,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.workspace.tokens.management.create.form :refer [form]] [app.main.ui.workspace.tokens.management.create.form :refer [form-wrapper*]]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[okulary.core :as l] [okulary.core :as l]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -88,11 +88,11 @@
:icon i/close :icon i/close
:variant "action" :variant "action"
:aria-label (tr "labels.close")}] :aria-label (tr "labels.close")}]
[:& form {:token token [:> form-wrapper* {:token token
:action action :action action
:selected-token-set-name selected-token-set-name :selected-token-set-name selected-token-set-name
:token-type token-type :token-type token-type
:on-display-colorpicker update-modal-size}]])) :on-display-colorpicker update-modal-size}]]))
;; Modals ---------------------------------------------------------------------- ;; Modals ----------------------------------------------------------------------
@ -191,3 +191,9 @@
::mf/register-as :tokens/letter-spacing} ::mf/register-as :tokens/letter-spacing}
[properties] [properties]
[:& token-update-create-modal properties]) [:& token-update-create-modal properties])
(mf/defc font-familiy-modal
{::mf/register modal/components
::mf/register-as :tokens/font-family}
[properties]
[:& token-update-create-modal properties])

View file

@ -27,6 +27,7 @@
:border-radius "corner-radius" :border-radius "corner-radius"
:color "drop" :color "drop"
:boolean "boolean-difference" :boolean "boolean-difference"
:font-family "text-font-family"
:font-size "text-font-size" :font-size "text-font-size"
:letter-spacing "text-letterspacing" :letter-spacing "text-letterspacing"
:opacity "percentage" :opacity "percentage"

View file

@ -106,7 +106,9 @@
(defn- generate-tooltip (defn- generate-tooltip
"Generates a tooltip for a given token" "Generates a tooltip for a given token"
[is-viewer shape theme-token token half-applied no-valid-value ref-not-in-active-set] [is-viewer shape theme-token token half-applied no-valid-value ref-not-in-active-set]
(let [{:keys [name value type resolved-value]} token (let [{:keys [name type resolved-value]} token
value (cond->> (:value token)
(= :font-family type) ctt/join-font-family)
resolved-value-theme (:resolved-value theme-token) resolved-value-theme (:resolved-value theme-token)
resolved-value (or resolved-value-theme resolved-value) resolved-value (or resolved-value-theme resolved-value)
{:keys [title] :as token-props} (dwta/get-token-properties theme-token) {:keys [title] :as token-props} (dwta/get-token-properties theme-token)

View file

@ -558,6 +558,40 @@
(t/is (= (:letter-spacing (:applied-tokens text-1')) (:name token-target'))) (t/is (= (:letter-spacing (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:letter-spacing style-text-blocks) "2"))))))))) (t/is (= (:letter-spacing style-text-blocks) "2")))))))))
(t/deftest test-apply-font-family
(t/testing "applies font-family token and updates the text font-family"
(t/async
done
(let [font-family-token {:name "primary-font"
:value "Arial"
:type :font-family}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token font-family-token))))
store (ths/setup-store file)
text-1 (cths/get-shape file :text-1)
events [(dwta/apply-token {:shape-ids [(:id text-1)]
:attributes #{:font-family}
:token (toht/get-token file "primary-font")
:on-update-shape dwta/update-font-family})]]
(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' "primary-font")
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 (= (:font-family (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:font-family style-text-blocks) (:font-id txt/default-text-attrs))))))))))
(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

View file

@ -7528,10 +7528,22 @@ msgstr "Could not resolve reference token with the name: %s"
msgid "workspace.tokens.token-value" msgid "workspace.tokens.token-value"
msgstr "Value" msgstr "Value"
#: src/app/main/ui/workspace/tokens/form.cljs:560
msgid "workspace.tokens.token-font-family-value"
msgstr "Font family"
#: src/app/main/ui/workspace/tokens/form.cljs:559 #: src/app/main/ui/workspace/tokens/form.cljs:559
msgid "workspace.tokens.token-value-enter" msgid "workspace.tokens.token-value-enter"
msgstr "Enter a value or alias with {alias}" msgstr "Enter a value or alias with {alias}"
#: src/app/main/ui/workspace/tokens/form.cljs:689
msgid "workspace.tokens.token-font-family-value-enter"
msgstr "Font family or list of fonts separated by comma (,)"
#: src/app/main/ui/workspace/tokens/form.cljs:688
msgid "workspace.tokens.token-font-family-select"
msgstr "Select font family"
#: src/app/main/ui/workspace/tokens/sidebar.cljs:336 #: src/app/main/ui/workspace/tokens/sidebar.cljs:336
msgid "workspace.tokens.tokens-section-title" msgid "workspace.tokens.tokens-section-title"
msgstr "TOKENS - %s" msgstr "TOKENS - %s"
@ -7900,4 +7912,4 @@ msgid "labels.sources"
msgstr "Sources" msgstr "Sources"
msgid "labels.pinned-projects" msgid "labels.pinned-projects"
msgstr "Pinned Projects" msgstr "Pinned Projects"