Implement font-size token type (#6708)

*  Implement font-size token type

*  Hide typography tokens behind FF

* 💄 Update icon

* 🔧 Add font-size to unapply check

* ♻️ Generalize status-icon logic and remove icon for font-size
This commit is contained in:
Florian Schrödl
2025-06-23 12:12:40 +02:00
committed by GitHub
parent 9ea0875e65
commit 580bb46a05
10 changed files with 141 additions and 32 deletions

View File

@@ -117,6 +117,7 @@
;; Only for developtment. ;; Only for developtment.
:tiered-file-data-storage :tiered-file-data-storage
:token-units :token-units
:token-typography-types
:transit-readable-response :transit-readable-response
:user-feedback :user-feedback
;; TODO: remove this flag. ;; TODO: remove this flag.

View File

@@ -33,6 +33,7 @@
:border-radius "borderRadius" :border-radius "borderRadius"
:color "color" :color "color"
:dimensions "dimension" :dimensions "dimension"
:font-size "fontSize"
:number "number" :number "number"
:opacity "opacity" :opacity "opacity"
:other "other" :other "other"
@@ -122,6 +123,12 @@
(def rotation-keys (schema-keys schema:rotation)) (def rotation-keys (schema-keys schema:rotation))
(def ^:private schema:font-size
[:map
[:font-size {:optional true} token-name-ref]])
(def font-size-keys (schema-keys schema:font-size))
(def ^:private schema:number (def ^:private schema:number
[:map [:map
[:rotation {:optional true} token-name-ref] [:rotation {:optional true} token-name-ref]
@@ -137,6 +144,7 @@
spacing-keys spacing-keys
dimensions-keys dimensions-keys
rotation-keys rotation-keys
font-size-keys
number-keys)) number-keys))
(def ^:private schema:tokens (def ^:private schema:tokens
@@ -150,6 +158,7 @@
schema:spacing schema:spacing
schema:rotation schema:rotation
schema:number schema:number
schema:font-size
schema:dimensions]) schema:dimensions])
(defn shape-attr->token-attrs (defn shape-attr->token-attrs
@@ -177,6 +186,7 @@
changed-sub-attr changed-sub-attr
#{:m1 :m2 :m3 :m4}) #{:m1 :m2 :m3 :m4})
(font-size-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}

View File

@@ -54,9 +54,13 @@
(ctob/add-token-in-set "test-token-set" (ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-dimensions" (ctob/make-token :name "token-dimensions"
:type :dimensions :type :dimensions
:value 100)))) :value 100))
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-font-size"
:type :font-size
:value 24))))
(tho/add-frame :frame1) (tho/add-frame :frame1)
(tho/add-text :text1 "Hello World"))) (tho/add-text :text1 "Hello World!")))
(defn- apply-all-tokens (defn- apply-all-tokens
[file] [file]
@@ -68,19 +72,21 @@
(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-color" [:fill] [:fill] "#00ff00"))) (tht/apply-token-to-shape :text1 "token-font-size" [:font-size] [:font-size] 24)))
(t/deftest apply-tokens-to-shape (t/deftest apply-tokens-to-shape
(let [;; ==== Setup (let [;; ==== Setup
file (setup-file) file (setup-file)
page (thf/current-page file) page (thf/current-page file)
frame1 (ths/get-shape file :frame1) frame1 (ths/get-shape file :frame1)
text1 (ths/get-shape file :text1)
token-radius (tht/get-token file "test-token-set" "token-radius") token-radius (tht/get-token file "test-token-set" "token-radius")
token-rotation (tht/get-token file "test-token-set" "token-rotation") token-rotation (tht/get-token file "test-token-set" "token-rotation")
token-opacity (tht/get-token file "test-token-set" "token-opacity") token-opacity (tht/get-token file "test-token-set" "token-opacity")
token-stroke-width (tht/get-token file "test-token-set" "token-stroke-width") token-stroke-width (tht/get-token file "test-token-set" "token-stroke-width")
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")
;; ==== Action ;; ==== Action
changes (-> (-> (pcb/empty-changes nil) changes (-> (-> (pcb/empty-changes nil)
@@ -114,13 +120,23 @@
:shape $ :shape $
:attributes [:width :height]}))) :attributes [:width :height]})))
(:objects page) (:objects page)
{})
(cls/generate-update-shapes [(:id text1)]
(fn [shape]
(as-> shape $
(cto/maybe-apply-token-to-shape {:token token-font-size
:shape $
:attributes [:font-size]})))
(:objects page)
{})) {}))
file' (thf/apply-changes file changes) file' (thf/apply-changes file changes)
;; ==== Get ;; ==== Get
frame1' (ths/get-shape file' :frame1) frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')] applied-tokens' (:applied-tokens frame1')
text1' (ths/get-shape file' :text1)
text1-applied-tokens (:applied-tokens text1')]
;; ==== Check ;; ==== Check
(t/is (= (count applied-tokens') 11)) (t/is (= (count applied-tokens') 11))
@@ -134,7 +150,9 @@
(t/is (= (:stroke-color applied-tokens') "token-color")) (t/is (= (:stroke-color applied-tokens') "token-color"))
(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 (= (:font-size text1-applied-tokens) "token-font-size"))))
(t/deftest unapply-tokens-from-shape (t/deftest unapply-tokens-from-shape
(let [;; ==== Setup (let [;; ==== Setup
@@ -142,6 +160,7 @@
(apply-all-tokens)) (apply-all-tokens))
page (thf/current-page file) page (thf/current-page file)
frame1 (ths/get-shape file :frame1) frame1 (ths/get-shape file :frame1)
text1 (ths/get-shape file :text1)
;; ==== Action ;; ==== Action
changes (-> (-> (pcb/empty-changes nil) changes (-> (-> (pcb/empty-changes nil)
@@ -158,16 +177,25 @@
(cto/unapply-token-id [:fill]) (cto/unapply-token-id [:fill])
(cto/unapply-token-id [:width :height]))) (cto/unapply-token-id [:width :height])))
(:objects page) (:objects page)
{})
(cls/generate-update-shapes [(:id text1)]
(fn [shape]
(-> shape
(cto/unapply-token-id [:font-size])))
(:objects page)
{})) {}))
file' (thf/apply-changes file changes) file' (thf/apply-changes file changes)
;; ==== Get ;; ==== Get
frame1' (ths/get-shape file' :frame1) frame1' (ths/get-shape file' :frame1)
applied-tokens' (:applied-tokens frame1')] applied-tokens' (:applied-tokens frame1')
text1' (ths/get-shape file' :text1)
text1-applied-tokens (:applied-tokens text1')]
;; ==== Check ;; ==== Check
(t/is (= (count applied-tokens') 0)))) (t/is (= (count applied-tokens') 0))
(t/is (= (count text1-applied-tokens) 0))))
(t/deftest unapply-tokens-automatic (t/deftest unapply-tokens-automatic
(let [;; ==== Setup (let [;; ==== Setup
@@ -202,7 +230,8 @@
shape shape
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"}))
(:objects page) (:objects page)
{})) {}))

View File

@@ -22,7 +22,7 @@
[app.main.data.workspace.colors :as wdc] [app.main.data.workspace.colors :as wdc]
[app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.transforms :as dwtr]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.store :as st] [app.main.store :as st]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
@@ -238,8 +238,8 @@
(watch [_ _ _] (watch [_ _ _]
(when (number? value) (when (number? value)
(rx/of (rx/of
(when (:width attributes) (dwt/update-dimensions shape-ids :width value {:ignore-touched true :page-id page-id})) (when (:width attributes) (dwtr/update-dimensions shape-ids :width value {:ignore-touched true :page-id page-id}))
(when (:height attributes) (dwt/update-dimensions shape-ids :height value {:ignore-touched true :page-id page-id})))))))) (when (:height attributes) (dwtr/update-dimensions shape-ids :height value {:ignore-touched true :page-id page-id}))))))))
(defn- attributes->layout-gap [attributes value] (defn- attributes->layout-gap [attributes value]
(let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap}) (let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap})
@@ -311,7 +311,7 @@
(when (number? value) (when (number? value)
(let [page-id (or page-id (get state :current-page-id))] (let [page-id (or page-id (get state :current-page-id))]
(->> (rx/from shape-ids) (->> (rx/from shape-ids)
(rx/map #(dwt/update-position % (zipmap attributes (repeat value)) (rx/map #(dwtr/update-position % (zipmap attributes (repeat value))
{:ignore-touched true {:ignore-touched true
:page-id page-id}))))))))) :page-id page-id})))))))))
@@ -346,6 +346,18 @@
{:ignore-touched true {:ignore-touched true
:page-id page-id}))))) :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]
(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 {:font-size (str value)})
{:ignore-touched true
:page-id page-id})))))
;; Map token types to different properties used along the cokde --------------------------------------------- ;; Map token types to different properties used along the cokde ---------------------------------------------
;; FIXME: the values should be lazy evaluated, probably a function, ;; FIXME: the values should be lazy evaluated, probably a function,
@@ -371,6 +383,14 @@
:modal {:key :tokens/color :modal {:key :tokens/color
:fields [{:label "Color" :key :color}]}} :fields [{:label "Color" :key :color}]}}
:font-size
{:title "Font Size"
:attributes ctt/font-size-keys
:on-update-shape update-font-size
:modal {:key :tokens/font-size
:fields [{:label "Font Size"
:key :font-size}]}}
:stroke-width :stroke-width
{:title "Stroke Width" {:title "Stroke Width"
:attributes ctt/stroke-width-keys :attributes ctt/stroke-width-keys

View File

@@ -33,6 +33,7 @@
ctt/sizing-keys dwta/update-shape-dimensions ctt/sizing-keys dwta/update-shape-dimensions
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
#{: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

@@ -233,7 +233,8 @@
(dwta/update-shape-radius-for-corners value shape-ids attributes))) (dwta/update-shape-radius-for-corners value shape-ids attributes)))
(def shape-attribute-actions-map (def shape-attribute-actions-map
(let [stroke-width (partial generic-attribute-actions #{:stroke-width} "Stroke Width")] (let [stroke-width (partial generic-attribute-actions #{:stroke-width} "Stroke Width")
font-size (partial generic-attribute-actions #{:font-size} "Font Size")]
{:border-radius (partial all-or-separate-actions {:attribute-labels {:r1 "Top Left" {:border-radius (partial all-or-separate-actions {:attribute-labels {:r1 "Top Left"
:r2 "Top Right" :r2 "Top Right"
:r4 "Bottom Left" :r4 "Bottom Left"
@@ -252,6 +253,7 @@
[(generic-attribute-actions #{:rotation} "Rotation" (assoc context-data :on-update-shape dwta/update-rotation)) [(generic-attribute-actions #{:rotation} "Rotation" (assoc context-data :on-update-shape dwta/update-rotation))
(generic-attribute-actions #{:line-height} "Line Height" (assoc context-data :on-update-shape dwta/update-line-height))]) (generic-attribute-actions #{:line-height} "Line Height" (assoc context-data :on-update-shape dwta/update-line-height))])
:stroke-width stroke-width :stroke-width stroke-width
:font-size font-size
:dimensions (fn [context-data] :dimensions (fn [context-data]
(concat (concat
[{:title "Sizing" :submenu :sizing} [{:title "Sizing" :submenu :sizing}

View File

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

View File

@@ -50,6 +50,7 @@
:border-radius "corner-radius" :border-radius "corner-radius"
:color "drop" :color "drop"
:boolean "boolean-difference" :boolean "boolean-difference"
:font-size "text-font-size"
:opacity "percentage" :opacity "percentage"
:number "number" :number "number"
:rotation "rotation" :rotation "rotation"
@@ -145,14 +146,15 @@
tokens exist for that type. Sort each group alphabetically (by their type). tokens exist for that type. Sort each group alphabetically (by their type).
If `:token-units` is not in cf/flags, number tokens are excluded." If `:token-units` is not in cf/flags, number tokens are excluded."
[tokens-by-type] [tokens-by-type]
(let [all-types (-> dwta/token-properties keys seq) (let [token-units? (contains? cf/flags :token-units)
token-units? (contains? cf/flags :token-units) token-typography-types? (contains? cf/flags :token-typography-types)
filtered-types (if token-units? all-types (cond-> dwta/token-properties
all-types (not token-units?) (dissoc :number)
(remove #(= % :number) all-types))] (not token-typography-types?) (dissoc :font-size))
all-types (-> all-types keys seq)]
(loop [empty #js [] (loop [empty #js []
filled #js [] filled #js []
types filtered-types] types all-types]
(if-let [type (first types)] (if-let [type (first types)]
(if (not-empty (get tokens-by-type type)) (if (not-empty (get tokens-by-type type))
(recur empty (recur empty

View File

@@ -162,6 +162,9 @@
shape-ids (into #{} xf:map-id selected-shapes)] shape-ids (into #{} xf:map-id selected-shapes)]
(cft/shapes-applied-all? ids-by-attributes shape-ids attributes))) (cft/shapes-applied-all? ids-by-attributes shape-ids attributes)))
(def token-types-with-status-icon
#{:color :border-radius :rotation :sizing :dimensions :opacity :spacing :stroke-width})
(mf/defc token-pill* (mf/defc token-pill*
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [on-click token on-context-menu selected-shapes active-theme-tokens]}] [{:keys [on-click token on-context-menu selected-shapes active-theme-tokens]}]
@@ -208,7 +211,7 @@
(or (dwtc/resolved-token-bullet-color theme-token) (or (dwtc/resolved-token-bullet-color theme-token)
(dwtc/resolved-token-bullet-color token)))) (dwtc/resolved-token-bullet-color token))))
number-token (= type :number) status-icon? (contains? token-types-with-status-icon type)
on-click on-click
(mf/use-fn (mf/use-fn
@@ -255,7 +258,7 @@
[:button {:class (stl/css-case [:button {:class (stl/css-case
:token-pill true :token-pill true
:token-pill-no-icon (and number-token (not errors?)) :token-pill-no-icon (and (not status-icon?) (not errors?))
:token-pill-default can-edit? :token-pill-default can-edit?
:token-pill-applied (and can-edit? has-selected? (or half-applied? full-applied?)) :token-pill-applied (and can-edit? has-selected? (or half-applied? full-applied?))
:token-pill-invalid (and can-edit? errors?) :token-pill-invalid (and can-edit? errors?)
@@ -280,12 +283,13 @@
{:icon-id "broken-link" {:icon-id "broken-link"
:class (stl/css :token-pill-icon)}] :class (stl/css :token-pill-icon)}]
(not number-token) color
(if color
[:& color-bullet {:color color :mini true}] [:& color-bullet {:color color :mini true}]
status-icon?
[:> token-status-icon* [:> token-status-icon*
{:icon-id token-status-id {:icon-id token-status-id
:class (stl/css :token-pill-icon)}])) :class (stl/css :token-pill-icon)}])
(if contains-path? (if contains-path?
(let [[first-part last-part] (cfh/split-by-last-period name)] (let [[first-part last-part] (cfh/split-by-last-period name)]

View File

@@ -446,6 +446,40 @@
(t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target'))) (t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target')))
(t/is (empty? (:strokes rect-without-stroke'))))))))))) (t/is (empty? (:strokes rect-without-stroke')))))))))))
(t/deftest test-apply-font-size
(t/testing "applies font-size token and updates the text font-size"
(t/async
done
(let [font-size-token {:name "heading-size"
:value "24"
:type :font-size}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token font-size-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-size}
:token (toht/get-token file "heading-size")
:on-update-shape dwta/update-font-size})]]
(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' "heading-size")
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-size (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:font-size style-text-blocks) "24")))))))))
(t/deftest test-apply-line-height (t/deftest test-apply-line-height
(t/testing "applies line-height token and updates the text line-height" (t/testing "applies line-height token and updates the text line-height"
(t/async (t/async