mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Add line-height to composite typography token
This commit is contained in:
committed by
Andrés Moya
parent
c1fd1a3b42
commit
c882e8347a
@@ -58,6 +58,19 @@
|
||||
"fontSize" :font-size
|
||||
"fontFamily" :font-family)))
|
||||
|
||||
(def composite-token-type->dtcg-token-type
|
||||
"Custom set of conversion keys for composite typography token with `:line-height` available.
|
||||
(Penpot doesn't support `:line-height` token)"
|
||||
(assoc token-type->dtcg-token-type
|
||||
:line-height "lineHeights"))
|
||||
|
||||
(def composite-dtcg-token-type->token-type
|
||||
"Custom set of conversion keys for composite typography token with `:line-height` available.
|
||||
(Penpot doesn't support `:line-height` token)"
|
||||
(assoc dtcg-token-type->token-type
|
||||
"lineHeights" :line-height
|
||||
"lineHeight" :line-height))
|
||||
|
||||
(def token-types
|
||||
(into #{} (keys token-type->dtcg-token-type)))
|
||||
|
||||
@@ -214,7 +227,8 @@
|
||||
text-case-keys
|
||||
text-decoration-keys
|
||||
font-weight-keys
|
||||
typography-token-keys))
|
||||
typography-token-keys
|
||||
#{:line-height}))
|
||||
|
||||
;; TODO: Created to extract the font-size feature from the typography feature flag.
|
||||
;; Delete this once the typography feature flag is removed.
|
||||
@@ -286,6 +300,7 @@
|
||||
(font-size-keys shape-attr) #{shape-attr :typography}
|
||||
(letter-spacing-keys shape-attr) #{shape-attr :typography}
|
||||
(font-family-keys shape-attr) #{shape-attr :typography}
|
||||
(= :line-height shape-attr) #{:line-height :typography}
|
||||
(= :text-transform shape-attr) #{:text-case :typography}
|
||||
(text-decoration-keys shape-attr) #{shape-attr :typography}
|
||||
(font-weight-keys shape-attr) #{shape-attr :typography}
|
||||
|
||||
@@ -1517,7 +1517,7 @@ Will return a value that matches this schema:
|
||||
[value]
|
||||
(if (map? value)
|
||||
(-> value
|
||||
(set/rename-keys cto/dtcg-token-type->token-type)
|
||||
(set/rename-keys cto/composite-dtcg-token-type->token-type)
|
||||
(select-keys cto/typography-keys)
|
||||
;; Convert font-family values within typography composite tokens
|
||||
(d/update-when :font-family convert-dtcg-font-family))
|
||||
@@ -1705,7 +1705,7 @@ Will return a value that matches this schema:
|
||||
(reduce-kv
|
||||
(fn [acc k v]
|
||||
(if (contains? cto/typography-keys k)
|
||||
(assoc acc (cto/token-type->dtcg-token-type k) v)
|
||||
(assoc acc (cto/composite-token-type->dtcg-token-type k) v)
|
||||
acc))
|
||||
{} value)
|
||||
value))
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"fontWeight": "bold",
|
||||
"fontSize": "24px",
|
||||
"letterSpacing": "0.05em",
|
||||
"lineHeights": "100%",
|
||||
"fontFamilies": ["Arial", "sans-serif"],
|
||||
"textCase": "uppercase"
|
||||
},
|
||||
|
||||
@@ -1625,6 +1625,7 @@
|
||||
(t/is (= (:value token) {:font-weight "bold"
|
||||
:font-size "24px"
|
||||
:letter-spacing "0.05em"
|
||||
:line-height "100%"
|
||||
:font-family ["Arial", "sans-serif"]
|
||||
:text-case "uppercase"}))
|
||||
(t/is (= (:description token) "A complex typography token"))))
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
"~:font-family": ["42dot Sans"],
|
||||
"~:font-size": "100",
|
||||
"~:font-weight": "300",
|
||||
"~:line-height": "2",
|
||||
"~:letter-spacing": "2",
|
||||
"~:text-case": "uppercase",
|
||||
"~:text-decoration": "underline"
|
||||
|
||||
@@ -974,9 +974,9 @@ test.describe("Tokens: Themes modal", () => {
|
||||
).toBeVisible();
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
// Allow empty fields
|
||||
// Show error with line-height depending on invalid font-size
|
||||
await fontSizeField.fill("");
|
||||
await expect(saveButton).toBeEnabled();
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
// Fill in values for all fields and verify they persist when switching tabs
|
||||
await fontSizeField.fill("16");
|
||||
@@ -985,6 +985,8 @@ test.describe("Tokens: Themes modal", () => {
|
||||
tokensUpdateCreateModal.getByLabel(/Font Weight/i);
|
||||
const letterSpacingField =
|
||||
tokensUpdateCreateModal.getByLabel(/Letter Spacing/i);
|
||||
const lineHeightField =
|
||||
tokensUpdateCreateModal.getByLabel(/Line Height/i);
|
||||
const textCaseField = tokensUpdateCreateModal.getByLabel(/Text Case/i);
|
||||
const textDecorationField =
|
||||
tokensUpdateCreateModal.getByLabel(/Text Decoration/i);
|
||||
@@ -995,6 +997,7 @@ test.describe("Tokens: Themes modal", () => {
|
||||
fontFamily: await fontFamilyField.inputValue(),
|
||||
fontWeight: await fontWeightField.inputValue(),
|
||||
letterSpacing: await letterSpacingField.inputValue(),
|
||||
lineHeight: await lineHeightField.inputValue(),
|
||||
textCase: await textCaseField.inputValue(),
|
||||
textDecoration: await textDecorationField.inputValue(),
|
||||
};
|
||||
@@ -1014,6 +1017,9 @@ test.describe("Tokens: Themes modal", () => {
|
||||
await expect(letterSpacingField).toHaveValue(
|
||||
originalValues.letterSpacing,
|
||||
);
|
||||
await expect(lineHeightField).toHaveValue(
|
||||
originalValues.lineHeight,
|
||||
);
|
||||
await expect(textCaseField).toHaveValue(originalValues.textCase);
|
||||
await expect(textDecorationField).toHaveValue(
|
||||
originalValues.textDecoration,
|
||||
|
||||
@@ -225,6 +225,34 @@
|
||||
:else
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-font-weight value)]})))
|
||||
|
||||
(defn- parse-sd-token-typography-line-height
|
||||
"Parses `line-height-value` of a composite typography token.
|
||||
Uses `font-size-value` to calculate the relative line-height value.
|
||||
Returns an error for an invalid font-size value."
|
||||
[line-height-value font-size-value font-size-errors]
|
||||
(let [missing-references (seq (some ctob/find-token-value-references line-height-value))
|
||||
error
|
||||
(cond
|
||||
missing-references
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference missing-references)]
|
||||
:references missing-references}
|
||||
|
||||
(or
|
||||
(not font-size-value)
|
||||
(seq font-size-errors))
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/composite-line-height-needs-font-size font-size-value)]
|
||||
:font-size-value font-size-value})]
|
||||
(or error
|
||||
(try
|
||||
(when-let [{:keys [unit value]} (cft/parse-token-value line-height-value)]
|
||||
(case unit
|
||||
"%" (/ value 100)
|
||||
"px" (/ value font-size-value)
|
||||
nil value
|
||||
nil))
|
||||
(catch :default _ nil))
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value line-height-value)]})))
|
||||
|
||||
(defn- parse-sd-token-font-family-value
|
||||
[value]
|
||||
(let [missing-references (seq (some ctob/find-token-value-references value))]
|
||||
@@ -247,6 +275,8 @@
|
||||
nil))
|
||||
|
||||
(defn- parse-composite-typography-value
|
||||
"Parses composite typography `value` map.
|
||||
Processes the `:line-height` based on the `:font-size` value in the map."
|
||||
[value]
|
||||
(let [missing-references
|
||||
(when (string? value)
|
||||
@@ -261,14 +291,34 @@
|
||||
|
||||
:else
|
||||
(let [converted (js->clj value :keywordize-keys true)
|
||||
add-keyed-errors (fn [typography-map k errors]
|
||||
(update typography-map :errors concat (map #(assoc % :typography-key k) errors)))
|
||||
;; Separate line-height to process in an extra step
|
||||
without-line-height (dissoc converted :line-height)
|
||||
valid-typography (reduce
|
||||
(fn [acc [k v]]
|
||||
(let [{:keys [errors value]} (parse-atomic-typography-value k v)]
|
||||
(if (seq errors)
|
||||
(update acc :errors concat (map #(assoc % :typography-key k) errors))
|
||||
(add-keyed-errors acc k errors)
|
||||
(assoc-in acc [:value k] (or value v)))))
|
||||
{:value {}}
|
||||
converted)]
|
||||
without-line-height)
|
||||
|
||||
;; Calculate line-height based on the resolved font-size and add it back to the map
|
||||
line-height (when-let [line-height (:line-height converted)]
|
||||
(-> (parse-sd-token-typography-line-height
|
||||
line-height
|
||||
(get-in valid-typography [:value :font-size])
|
||||
(get-in valid-typography [:errors :font-size]))))
|
||||
valid-typography (cond
|
||||
(:errors line-height)
|
||||
(add-keyed-errors valid-typography :line-height (:errors line-height))
|
||||
|
||||
line-height
|
||||
(assoc-in valid-typography [:value :line-height] line-height)
|
||||
|
||||
:else
|
||||
valid-typography)]
|
||||
valid-typography))))
|
||||
|
||||
(defn collect-typography-errors [token]
|
||||
|
||||
@@ -434,7 +434,8 @@
|
||||
:font-weight update-font-weight
|
||||
:letter-spacing update-letter-spacing
|
||||
:text-case update-text-case
|
||||
:text-decoration update-text-decoration}
|
||||
:text-decoration update-text-decoration
|
||||
:line-height update-line-height}
|
||||
value
|
||||
[shape-ids attributes page-id])))))
|
||||
|
||||
|
||||
@@ -92,6 +92,10 @@
|
||||
{:error/code :error.style-dictionary/invalid-token-value-typography
|
||||
:error/fn #(tr "workspace.tokens.invalid-token-value-typography" %)}
|
||||
|
||||
:error.style-dictionary/composite-line-height-needs-font-size
|
||||
{:error/code :error.style-dictionary/composite-line-height-needs-font-size
|
||||
:error/fn #(tr "workspace.tokens.composite-line-height-needs-font-size" %)}
|
||||
|
||||
:error/unknown
|
||||
{:error/code :error/unknown
|
||||
:error/fn #(tr "labels.unknown-error")}})
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
:font-size "Font Size"
|
||||
:font-family "Font Family"
|
||||
:font-weight "Font Weight"
|
||||
:line-height "Line Height"
|
||||
:letter-spacing "Letter Spacing"
|
||||
:text-case "Text Case"
|
||||
:text-decoration "Text Decoration"})
|
||||
|
||||
@@ -912,6 +912,10 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
{:label "Font Weight"
|
||||
:icon i/text-font-weight
|
||||
:placeholder (tr "workspace.tokens.font-weight-value-enter")}
|
||||
:line-height
|
||||
{:label "Line Height"
|
||||
:icon i/text-lineheight
|
||||
:placeholder (tr "workspace.tokens.line-height-value-enter")}
|
||||
:letter-spacing
|
||||
{:label "Letter Spacing"
|
||||
:icon i/text-letterspacing
|
||||
|
||||
@@ -874,6 +874,7 @@
|
||||
:value {:font-size "24px"
|
||||
:font-weight "bold"
|
||||
:font-family [(:font-id txt/default-text-attrs) "Arial" "sans-serif"]
|
||||
:line-height "24px"
|
||||
:letter-spacing "2"
|
||||
:text-case "uppercase"
|
||||
:text-decoration "underline"}
|
||||
@@ -895,7 +896,6 @@
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-state new-state)
|
||||
text-1' (cths/get-shape file' :text-1)
|
||||
text-1' (def text-1' text-1')
|
||||
style-text-blocks (->> (:content text-1')
|
||||
(txt/content->text+styles)
|
||||
(remove (fn [[_ text]] (str/empty? (str/trim text))))
|
||||
@@ -909,6 +909,7 @@
|
||||
|
||||
(t/is (= (:font-size style-text-blocks) "24"))
|
||||
(t/is (= (:font-weight style-text-blocks) "700"))
|
||||
(t/is (= (:line-height style-text-blocks) 1))
|
||||
(t/is (= (:font-family style-text-blocks) "sourcesanspro"))
|
||||
(t/is (= (:letter-spacing style-text-blocks) "2"))
|
||||
(t/is (= (:text-transform style-text-blocks) "uppercase"))
|
||||
|
||||
@@ -7466,6 +7466,10 @@ msgstr ""
|
||||
msgid "workspace.tokens.font-weight-value-enter"
|
||||
msgstr "Enter a value (300, Bold, Regular Italic...) or an {alias}"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:918
|
||||
msgid "workspace.tokens.line-height-value-enter"
|
||||
msgstr "Enter line height — multiplier, px, %, or {alias}"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:229
|
||||
msgid "workspace.tokens.gaps"
|
||||
msgstr "Gaps"
|
||||
@@ -7662,6 +7666,10 @@ msgstr "Invalid token value. The resolved value is too large: %s"
|
||||
msgid "workspace.tokens.opacity-range"
|
||||
msgstr "Opacity must be between 0 and 100% or 0 and 1 (e.g. 50% or 0.5)."
|
||||
|
||||
#: frontend/src/app/main/data/workspace/tokens/errors.cljs:99
|
||||
msgid "workspace.tokens.composite-line-height-needs-font-size"
|
||||
msgstr "Line Height depends on Font Size. Add a Font Size to get the resolved value."
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:145
|
||||
#, fuzzy
|
||||
msgid "workspace.tokens.original-value"
|
||||
|
||||
@@ -7445,6 +7445,10 @@ msgstr ""
|
||||
msgid "workspace.tokens.font-weight-value-enter"
|
||||
msgstr "Introduce un valor (300, Bold, Regular Italic...) o un {alias}"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:918
|
||||
msgid "workspace.tokens.line-height-value-enter"
|
||||
msgstr "Introduce line height — multiplicador, px o % — o {alias}"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
|
||||
#, unused
|
||||
msgid "workspace.tokens.generic-error"
|
||||
@@ -7596,6 +7600,10 @@ msgstr "Valor de token no valido. El valor resuelto es muy grande: %s"
|
||||
msgid "workspace.tokens.opacity-range"
|
||||
msgstr "La opacidad debe estar entre 0 y 100% o 0 y 1 (p.e. 50% o 0.5)."
|
||||
|
||||
#: frontend/src/app/main/data/workspace/tokens/errors.cljs:99
|
||||
msgid "workspace.tokens.composite-line-height-needs-font-size"
|
||||
msgstr "El Line Height depende del Font Size. Añade un Font Size para obtener el valor computado."
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:145
|
||||
#, fuzzy
|
||||
msgid "workspace.tokens.original-value"
|
||||
|
||||
Reference in New Issue
Block a user