mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Add text decoration token
This commit is contained in:
committed by
Andrei Fëdorov
parent
44d626d578
commit
cdb4311d22
@@ -30,22 +30,23 @@
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(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-family "fontFamilies"
|
:font-family "fontFamilies"
|
||||||
:font-size "fontSizes"
|
:font-size "fontSizes"
|
||||||
:letter-spacing "letterSpacing"
|
:letter-spacing "letterSpacing"
|
||||||
:text-case "textCase"
|
:text-case "textCase"
|
||||||
:number "number"
|
:text-decoration "textDecoration"
|
||||||
:opacity "opacity"
|
:number "number"
|
||||||
:other "other"
|
:opacity "opacity"
|
||||||
:rotation "rotation"
|
:other "other"
|
||||||
:sizing "sizing"
|
:rotation "rotation"
|
||||||
:spacing "spacing"
|
:sizing "sizing"
|
||||||
:string "string"
|
:spacing "spacing"
|
||||||
:stroke-width "strokeWidth"})
|
:string "string"
|
||||||
|
: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))
|
||||||
@@ -170,10 +171,17 @@
|
|||||||
|
|
||||||
(def text-case-keys (schema-keys schema:text-case))
|
(def text-case-keys (schema-keys schema:text-case))
|
||||||
|
|
||||||
|
(def ^:private schema:text-decoration
|
||||||
|
[:map
|
||||||
|
[:text-decoration {:optional true} token-name-ref]])
|
||||||
|
|
||||||
|
(def text-decoration-keys (schema-keys schema:text-decoration))
|
||||||
|
|
||||||
(def typography-keys (set/union font-size-keys
|
(def typography-keys (set/union font-size-keys
|
||||||
letter-spacing-keys
|
letter-spacing-keys
|
||||||
font-family-keys
|
font-family-keys
|
||||||
text-case-keys))
|
text-case-keys
|
||||||
|
text-decoration-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.
|
||||||
@@ -212,6 +220,7 @@
|
|||||||
schema:letter-spacing
|
schema:letter-spacing
|
||||||
schema:font-family
|
schema:font-family
|
||||||
schema:text-case
|
schema:text-case
|
||||||
|
schema:text-decoration
|
||||||
schema:dimensions])
|
schema:dimensions])
|
||||||
|
|
||||||
(defn shape-attr->token-attrs
|
(defn shape-attr->token-attrs
|
||||||
@@ -243,6 +252,7 @@
|
|||||||
(letter-spacing-keys shape-attr) #{shape-attr}
|
(letter-spacing-keys shape-attr) #{shape-attr}
|
||||||
(font-family-keys shape-attr) #{shape-attr}
|
(font-family-keys shape-attr) #{shape-attr}
|
||||||
(text-case-keys shape-attr) #{shape-attr}
|
(text-case-keys shape-attr) #{shape-attr}
|
||||||
|
(text-decoration-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}
|
||||||
|
|||||||
@@ -175,6 +175,24 @@
|
|||||||
:else
|
:else
|
||||||
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-text-case value)]})))
|
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-text-case value)]})))
|
||||||
|
|
||||||
|
(defn- parse-sd-token-text-decoration-value
|
||||||
|
"Parses `value` of a text-decoration `sd-token` into a map like `{:value \"underline\"}`.
|
||||||
|
If the `value` is not parseable and/or has missing references returns a map with `:errors`."
|
||||||
|
[value]
|
||||||
|
(let [normalized-value (str/lower (str/trim value))
|
||||||
|
valid? (contains? #{"none" "underline" "strike-through"} normalized-value)
|
||||||
|
references (seq (ctob/find-token-value-references value))]
|
||||||
|
(cond
|
||||||
|
valid?
|
||||||
|
{:value normalized-value}
|
||||||
|
|
||||||
|
references
|
||||||
|
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)]
|
||||||
|
:references references}
|
||||||
|
|
||||||
|
:else
|
||||||
|
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-text-decoration value)]})))
|
||||||
|
|
||||||
(defn process-sd-tokens
|
(defn process-sd-tokens
|
||||||
"Converts a StyleDictionary dictionary with resolved tokens (aka `sd-tokens`) back to clojure.
|
"Converts a StyleDictionary dictionary with resolved tokens (aka `sd-tokens`) back to clojure.
|
||||||
The `get-origin-token` argument should be a function that takes an
|
The `get-origin-token` argument should be a function that takes an
|
||||||
@@ -218,6 +236,7 @@
|
|||||||
: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?)
|
||||||
:text-case (parse-sd-token-text-case-value value)
|
:text-case (parse-sd-token-text-case-value value)
|
||||||
|
:text-decoration (parse-sd-token-text-decoration-value value)
|
||||||
:number (parse-sd-token-number-value value)
|
:number (parse-sd-token-number-value value)
|
||||||
(parse-sd-token-general-value value))
|
(parse-sd-token-general-value value))
|
||||||
output-token (cond (:errors parsed-token-value)
|
output-token (cond (:errors parsed-token-value)
|
||||||
|
|||||||
@@ -307,6 +307,15 @@
|
|||||||
(when (string? value)
|
(when (string? value)
|
||||||
(generate-text-shape-update {:text-transform value} shape-ids page-id))))
|
(generate-text-shape-update {:text-transform value} shape-ids page-id))))
|
||||||
|
|
||||||
|
(defn update-text-decoration
|
||||||
|
([value shape-ids attributes] (update-text-decoration value shape-ids attributes nil))
|
||||||
|
([value shape-ids _attributes page-id]
|
||||||
|
(when (string? value)
|
||||||
|
(let [css-value (case value
|
||||||
|
"strike-through" "line-through"
|
||||||
|
value)]
|
||||||
|
(generate-text-shape-update {:text-decoration css-value} shape-ids page-id)))))
|
||||||
|
|
||||||
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
|
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
|
||||||
|
|
||||||
(defn apply-token
|
(defn apply-token
|
||||||
@@ -481,6 +490,14 @@
|
|||||||
:fields [{:label "Text Case"
|
:fields [{:label "Text Case"
|
||||||
:key :text-case}]}}
|
:key :text-case}]}}
|
||||||
|
|
||||||
|
:text-decoration
|
||||||
|
{:title "Text Decoration"
|
||||||
|
:attributes ctt/text-decoration-keys
|
||||||
|
:on-update-shape update-text-decoration
|
||||||
|
:modal {:key :tokens/text-decoration
|
||||||
|
:fields [{:label "Text Decoration"
|
||||||
|
:key :text-decoration}]}}
|
||||||
|
|
||||||
:stroke-width
|
:stroke-width
|
||||||
{:title "Stroke Width"
|
{:title "Stroke Width"
|
||||||
:attributes ctt/stroke-width-keys
|
:attributes ctt/stroke-width-keys
|
||||||
|
|||||||
@@ -76,6 +76,10 @@
|
|||||||
{:error/code :error.style-dictionary/invalid-token-value-text-case
|
{:error/code :error.style-dictionary/invalid-token-value-text-case
|
||||||
:error/fn #(tr "workspace.tokens.invalid-text-case-token-value" %)}
|
:error/fn #(tr "workspace.tokens.invalid-text-case-token-value" %)}
|
||||||
|
|
||||||
|
:error.style-dictionary/invalid-token-value-text-decoration
|
||||||
|
{:error/code :error.style-dictionary/invalid-token-value-text-decoration
|
||||||
|
:error/fn #(tr "workspace.tokens.invalid-text-decoration-token-value" %)}
|
||||||
|
|
||||||
:error/unknown
|
:error/unknown
|
||||||
{:error/code :error/unknown
|
{:error/code :error/unknown
|
||||||
:error/fn #(tr "labels.unknown-error")}})
|
:error/fn #(tr "labels.unknown-error")}})
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#{:letter-spacing} dwta/update-letter-spacing
|
#{:letter-spacing} dwta/update-letter-spacing
|
||||||
#{:font-family} dwta/update-font-family
|
#{:font-family} dwta/update-font-family
|
||||||
#{:text-case} dwta/update-text-case
|
#{:text-case} dwta/update-text-case
|
||||||
|
#{:text-decoration} dwta/update-text-decoration
|
||||||
#{: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
|
||||||
|
|||||||
@@ -271,6 +271,7 @@
|
|||||||
font-family (partial generic-attribute-actions #{:font-family} "Font Family")
|
font-family (partial generic-attribute-actions #{:font-family} "Font Family")
|
||||||
line-height #(generic-attribute-actions #{:line-height} "Line Height" (assoc % :on-update-shape dwta/update-line-height))
|
line-height #(generic-attribute-actions #{:line-height} "Line Height" (assoc % :on-update-shape dwta/update-line-height))
|
||||||
text-case (partial generic-attribute-actions #{:text-case} "Text Case")
|
text-case (partial generic-attribute-actions #{:text-case} "Text Case")
|
||||||
|
text-decoration (partial generic-attribute-actions #{:text-decoration} "Text Decoration")
|
||||||
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"
|
||||||
@@ -298,6 +299,7 @@
|
|||||||
:line-height line-height
|
:line-height line-height
|
||||||
:letter-spacing letter-spacing
|
:letter-spacing letter-spacing
|
||||||
:text-case text-case
|
:text-case text-case
|
||||||
|
:text-decoration text-decoration
|
||||||
:dimensions (fn [context-data]
|
:dimensions (fn [context-data]
|
||||||
(-> (concat
|
(-> (concat
|
||||||
(when (seq (sizing-attribute-actions context-data)) [{:title "Sizing" :submenu :sizing}])
|
(when (seq (sizing-attribute-actions context-data)) [{:title "Sizing" :submenu :sizing}])
|
||||||
|
|||||||
@@ -794,6 +794,13 @@
|
|||||||
(mf/spread-props props {:token token
|
(mf/spread-props props {:token token
|
||||||
:input-placeholder placeholder})]))
|
:input-placeholder placeholder})]))
|
||||||
|
|
||||||
|
(mf/defc text-decoration-form*
|
||||||
|
[{:keys [token] :rest props}]
|
||||||
|
(let [placeholder (tr "workspace.tokens.text-decoration-value-enter")]
|
||||||
|
[:> form*
|
||||||
|
(mf/spread-props props {:token token
|
||||||
|
:input-placeholder placeholder})]))
|
||||||
|
|
||||||
(mf/defc form-wrapper*
|
(mf/defc form-wrapper*
|
||||||
[{:keys [token token-type] :as props}]
|
[{:keys [token token-type] :as props}]
|
||||||
(let [token-type' (or (:type token) token-type)]
|
(let [token-type' (or (:type token) token-type)]
|
||||||
@@ -801,4 +808,5 @@
|
|||||||
:color [:> color-form* props]
|
:color [:> color-form* props]
|
||||||
:font-family [:> font-family-form* props]
|
:font-family [:> font-family-form* props]
|
||||||
:text-case [:> text-case-form* props]
|
:text-case [:> text-case-form* props]
|
||||||
|
:text-decoration [:> text-decoration-form* props]
|
||||||
[:> form* props])))
|
[:> form* props])))
|
||||||
|
|||||||
@@ -203,3 +203,9 @@
|
|||||||
::mf/register-as :tokens/text-case}
|
::mf/register-as :tokens/text-case}
|
||||||
[properties]
|
[properties]
|
||||||
[:& token-update-create-modal properties])
|
[:& token-update-create-modal properties])
|
||||||
|
|
||||||
|
(mf/defc text-decoration-modal
|
||||||
|
{::mf/register modal/components
|
||||||
|
::mf/register-as :tokens/text-decoration}
|
||||||
|
[properties]
|
||||||
|
[:& token-update-create-modal properties])
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
:font-size "text-font-size"
|
:font-size "text-font-size"
|
||||||
:letter-spacing "text-letterspacing"
|
:letter-spacing "text-letterspacing"
|
||||||
:text-case "text-mixed"
|
:text-case "text-mixed"
|
||||||
|
:text-decoration "text-underlined"
|
||||||
:opacity "percentage"
|
:opacity "percentage"
|
||||||
:number "number"
|
:number "number"
|
||||||
:rotation "rotation"
|
:rotation "rotation"
|
||||||
|
|||||||
@@ -176,7 +176,7 @@
|
|||||||
selected-shapes)))
|
selected-shapes)))
|
||||||
|
|
||||||
(def token-types-with-status-icon
|
(def token-types-with-status-icon
|
||||||
#{:color :border-radius :rotation :sizing :dimensions :opacity :spacing :stroke-width :text-case})
|
#{: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]}
|
||||||
|
|||||||
@@ -626,6 +626,40 @@
|
|||||||
(t/is (= (:text-case (:applied-tokens text-1')) (:name token-target')))
|
(t/is (= (:text-case (:applied-tokens text-1')) (:name token-target')))
|
||||||
(t/is (= (:text-transform style-text-blocks) "uppercase")))))))))
|
(t/is (= (:text-transform style-text-blocks) "uppercase")))))))))
|
||||||
|
|
||||||
|
(t/deftest test-apply-text-decoration
|
||||||
|
(t/testing "applies text-decoration token and updates the text decoration"
|
||||||
|
(t/async
|
||||||
|
done
|
||||||
|
(let [text-decoration-token {:name "underline-decoration"
|
||||||
|
:value "underline"
|
||||||
|
:type :text-decoration}
|
||||||
|
file (-> (setup-file-with-tokens)
|
||||||
|
(update-in [:data :tokens-lib]
|
||||||
|
#(ctob/add-token-in-set % "Set A" (ctob/make-token text-decoration-token))))
|
||||||
|
store (ths/setup-store file)
|
||||||
|
text-1 (cths/get-shape file :text-1)
|
||||||
|
events [(dwta/apply-token {:shape-ids [(:id text-1)]
|
||||||
|
:attributes #{:text-decoration}
|
||||||
|
:token (toht/get-token file "underline-decoration")
|
||||||
|
:on-update-shape dwta/update-text-decoration})]]
|
||||||
|
(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' "underline-decoration")
|
||||||
|
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 (= (:text-decoration (:applied-tokens text-1')) (:name token-target')))
|
||||||
|
(t/is (= (:text-decoration style-text-blocks) "underline")))))))))
|
||||||
|
|
||||||
(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
|
||||||
|
|||||||
@@ -7290,6 +7290,14 @@ msgstr "Enter text case: none | Uppercase | Lowercase | Capitalize"
|
|||||||
msgid "workspace.tokens.invalid-text-case-token-value"
|
msgid "workspace.tokens.invalid-text-case-token-value"
|
||||||
msgstr "Invalid token value: only none, Uppercase, Lowercase or Capitalize are accepted"
|
msgstr "Invalid token value: only none, Uppercase, Lowercase or Capitalize are accepted"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:799
|
||||||
|
msgid "workspace.tokens.text-decoration-value-enter"
|
||||||
|
msgstr "Enter text decoration: none | underline | strike-through"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:81
|
||||||
|
msgid "workspace.tokens.invalid-text-decoration-token-value"
|
||||||
|
msgstr "Invalid token value: only none, underline and strike-through are accepted"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/modals/export.cljs:33
|
#: src/app/main/ui/workspace/tokens/modals/export.cljs:33
|
||||||
msgid "workspace.tokens.export.preview"
|
msgid "workspace.tokens.export.preview"
|
||||||
msgstr "Preview:"
|
msgstr "Preview:"
|
||||||
|
|||||||
@@ -7236,6 +7236,10 @@ msgstr "No existen tokens, temas o sets para exportar."
|
|||||||
msgid "workspace.tokens.text-case-value-enter"
|
msgid "workspace.tokens.text-case-value-enter"
|
||||||
msgstr "Introduce una capitalización: none | Uppercase | Lowercase | Capitalize"
|
msgstr "Introduce una capitalización: none | Uppercase | Lowercase | Capitalize"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:799
|
||||||
|
msgid "workspace.tokens.text-decoration-value-enter"
|
||||||
|
msgstr "Introduce text decoration: none | underline | strike-through"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/tokens/modals/export.cljs:33
|
#: src/app/main/ui/workspace/tokens/modals/export.cljs:33
|
||||||
msgid "workspace.tokens.export.preview"
|
msgid "workspace.tokens.export.preview"
|
||||||
msgstr "Previsualizar:"
|
msgstr "Previsualizar:"
|
||||||
|
|||||||
Reference in New Issue
Block a user