mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ 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:
@@ -117,6 +117,7 @@
|
||||
;; Only for developtment.
|
||||
:tiered-file-data-storage
|
||||
:token-units
|
||||
:token-typography-types
|
||||
:transit-readable-response
|
||||
:user-feedback
|
||||
;; TODO: remove this flag.
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
:border-radius "borderRadius"
|
||||
:color "color"
|
||||
:dimensions "dimension"
|
||||
:font-size "fontSize"
|
||||
:number "number"
|
||||
:opacity "opacity"
|
||||
:other "other"
|
||||
@@ -122,6 +123,12 @@
|
||||
|
||||
(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
|
||||
[:map
|
||||
[:rotation {:optional true} token-name-ref]
|
||||
@@ -137,6 +144,7 @@
|
||||
spacing-keys
|
||||
dimensions-keys
|
||||
rotation-keys
|
||||
font-size-keys
|
||||
number-keys))
|
||||
|
||||
(def ^:private schema:tokens
|
||||
@@ -150,6 +158,7 @@
|
||||
schema:spacing
|
||||
schema:rotation
|
||||
schema:number
|
||||
schema:font-size
|
||||
schema:dimensions])
|
||||
|
||||
(defn shape-attr->token-attrs
|
||||
@@ -177,6 +186,7 @@
|
||||
changed-sub-attr
|
||||
#{:m1 :m2 :m3 :m4})
|
||||
|
||||
(font-size-keys shape-attr) #{shape-attr}
|
||||
(border-radius-keys shape-attr) #{shape-attr}
|
||||
(sizing-keys shape-attr) #{shape-attr}
|
||||
(opacity-keys shape-attr) #{shape-attr}
|
||||
|
||||
@@ -54,9 +54,13 @@
|
||||
(ctob/add-token-in-set "test-token-set"
|
||||
(ctob/make-token :name "token-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-text :text1 "Hello World")))
|
||||
(tho/add-text :text1 "Hello World!")))
|
||||
|
||||
(defn- apply-all-tokens
|
||||
[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" [:fill] [:fill] "#00ff00")
|
||||
(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
|
||||
(let [;; ==== Setup
|
||||
file (setup-file)
|
||||
page (thf/current-page file)
|
||||
frame1 (ths/get-shape file :frame1)
|
||||
text1 (ths/get-shape file :text1)
|
||||
token-radius (tht/get-token file "test-token-set" "token-radius")
|
||||
token-rotation (tht/get-token file "test-token-set" "token-rotation")
|
||||
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-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")
|
||||
|
||||
;; ==== Action
|
||||
changes (-> (-> (pcb/empty-changes nil)
|
||||
@@ -114,13 +120,23 @@
|
||||
:shape $
|
||||
:attributes [:width :height]})))
|
||||
(: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)
|
||||
|
||||
;; ==== Get
|
||||
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
|
||||
(t/is (= (count applied-tokens') 11))
|
||||
@@ -134,7 +150,9 @@
|
||||
(t/is (= (:stroke-color applied-tokens') "token-color"))
|
||||
(t/is (= (:fill applied-tokens') "token-color"))
|
||||
(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
|
||||
(let [;; ==== Setup
|
||||
@@ -142,6 +160,7 @@
|
||||
(apply-all-tokens))
|
||||
page (thf/current-page file)
|
||||
frame1 (ths/get-shape file :frame1)
|
||||
text1 (ths/get-shape file :text1)
|
||||
|
||||
;; ==== Action
|
||||
changes (-> (-> (pcb/empty-changes nil)
|
||||
@@ -158,16 +177,25 @@
|
||||
(cto/unapply-token-id [:fill])
|
||||
(cto/unapply-token-id [:width :height])))
|
||||
(: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)
|
||||
|
||||
;; ==== Get
|
||||
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
|
||||
(t/is (= (count applied-tokens') 0))))
|
||||
(t/is (= (count applied-tokens') 0))
|
||||
(t/is (= (count text1-applied-tokens) 0))))
|
||||
|
||||
(t/deftest unapply-tokens-automatic
|
||||
(let [;; ==== Setup
|
||||
@@ -202,7 +230,8 @@
|
||||
shape
|
||||
txt/is-content-node?
|
||||
d/txt-merge
|
||||
{:fills (ths/sample-fills-color :fill-color "#fabada")}))
|
||||
{:fills (ths/sample-fills-color :fill-color "#fabada")
|
||||
:font-size "1"}))
|
||||
(:objects page)
|
||||
{}))
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[app.main.data.workspace.colors :as wdc]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[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.store :as st]
|
||||
[beicon.v2.core :as rx]
|
||||
@@ -238,8 +238,8 @@
|
||||
(watch [_ _ _]
|
||||
(when (number? value)
|
||||
(rx/of
|
||||
(when (:width attributes) (dwt/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 (:width attributes) (dwtr/update-dimensions shape-ids :width 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]
|
||||
(let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap})
|
||||
@@ -311,7 +311,7 @@
|
||||
(when (number? value)
|
||||
(let [page-id (or page-id (get state :current-page-id))]
|
||||
(->> (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
|
||||
:page-id page-id})))))))))
|
||||
|
||||
@@ -346,6 +346,18 @@
|
||||
{: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]
|
||||
(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 ---------------------------------------------
|
||||
|
||||
;; FIXME: the values should be lazy evaluated, probably a function,
|
||||
@@ -371,6 +383,14 @@
|
||||
:modal {:key :tokens/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
|
||||
{:title "Stroke Width"
|
||||
:attributes ctt/stroke-width-keys
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
ctt/sizing-keys dwta/update-shape-dimensions
|
||||
ctt/opacity-keys dwta/update-opacity
|
||||
#{:line-height} dwta/update-line-height
|
||||
#{:font-size} dwta/update-font-size
|
||||
#{:x :y} dwta/update-shape-position
|
||||
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding
|
||||
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin
|
||||
|
||||
@@ -233,7 +233,8 @@
|
||||
(dwta/update-shape-radius-for-corners value shape-ids attributes)))
|
||||
|
||||
(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"
|
||||
:r2 "Top Right"
|
||||
:r4 "Bottom Left"
|
||||
@@ -252,6 +253,7 @@
|
||||
[(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))])
|
||||
:stroke-width stroke-width
|
||||
:font-size font-size
|
||||
:dimensions (fn [context-data]
|
||||
(concat
|
||||
[{:title "Sizing" :submenu :sizing}
|
||||
|
||||
@@ -179,3 +179,9 @@
|
||||
::mf/register-as :tokens/typography}
|
||||
[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])
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
:border-radius "corner-radius"
|
||||
:color "drop"
|
||||
:boolean "boolean-difference"
|
||||
:font-size "text-font-size"
|
||||
:opacity "percentage"
|
||||
:number "number"
|
||||
:rotation "rotation"
|
||||
@@ -145,14 +146,15 @@
|
||||
tokens exist for that type. Sort each group alphabetically (by their type).
|
||||
If `:token-units` is not in cf/flags, number tokens are excluded."
|
||||
[tokens-by-type]
|
||||
(let [all-types (-> dwta/token-properties keys seq)
|
||||
token-units? (contains? cf/flags :token-units)
|
||||
filtered-types (if token-units?
|
||||
all-types
|
||||
(remove #(= % :number) all-types))]
|
||||
(let [token-units? (contains? cf/flags :token-units)
|
||||
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))
|
||||
all-types (-> all-types keys seq)]
|
||||
(loop [empty #js []
|
||||
filled #js []
|
||||
types filtered-types]
|
||||
types all-types]
|
||||
(if-let [type (first types)]
|
||||
(if (not-empty (get tokens-by-type type))
|
||||
(recur empty
|
||||
|
||||
@@ -162,6 +162,9 @@
|
||||
shape-ids (into #{} xf:map-id selected-shapes)]
|
||||
(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/wrap [mf/memo]}
|
||||
[{:keys [on-click token on-context-menu selected-shapes active-theme-tokens]}]
|
||||
@@ -208,7 +211,7 @@
|
||||
(or (dwtc/resolved-token-bullet-color theme-token)
|
||||
(dwtc/resolved-token-bullet-color token))))
|
||||
|
||||
number-token (= type :number)
|
||||
status-icon? (contains? token-types-with-status-icon type)
|
||||
|
||||
on-click
|
||||
(mf/use-fn
|
||||
@@ -255,7 +258,7 @@
|
||||
|
||||
[:button {:class (stl/css-case
|
||||
: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-applied (and can-edit? has-selected? (or half-applied? full-applied?))
|
||||
:token-pill-invalid (and can-edit? errors?)
|
||||
@@ -280,12 +283,13 @@
|
||||
{:icon-id "broken-link"
|
||||
:class (stl/css :token-pill-icon)}]
|
||||
|
||||
(not number-token)
|
||||
(if color
|
||||
color
|
||||
[:& color-bullet {:color color :mini true}]
|
||||
|
||||
status-icon?
|
||||
[:> token-status-icon*
|
||||
{:icon-id token-status-id
|
||||
:class (stl/css :token-pill-icon)}]))
|
||||
:class (stl/css :token-pill-icon)}])
|
||||
|
||||
(if contains-path?
|
||||
(let [[first-part last-part] (cfh/split-by-last-period name)]
|
||||
|
||||
@@ -446,6 +446,40 @@
|
||||
(t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target')))
|
||||
(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/testing "applies line-height token and updates the text line-height"
|
||||
(t/async
|
||||
|
||||
Reference in New Issue
Block a user