mirror of
https://github.com/penpot/penpot.git
synced 2025-08-01 07:39:07 +02:00
✨ Implement new token-type :font-families
This commit is contained in:
parent
2cddc6fb5b
commit
d788a4d252
16 changed files with 544 additions and 196 deletions
|
@ -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))
|
||||||
|
|
|
@ -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)))))
|
||||||
|
|
|
@ -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)
|
||||||
{}))
|
{}))
|
||||||
|
|
||||||
|
|
|
@ -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?)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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])))
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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))}
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue