mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Allow references to other typography tokens (#7251)
This commit is contained in:
@@ -442,3 +442,8 @@
|
||||
(when (font-weight-values weight)
|
||||
(cond-> {:weight weight}
|
||||
italic? (assoc :style "italic")))))
|
||||
|
||||
(defn typography-composite-token-reference?
|
||||
"Predicate if a typography composite token is a reference value - a string pointing to another reference token."
|
||||
[token-value]
|
||||
(string? token-value))
|
||||
|
||||
@@ -972,10 +972,141 @@ test.describe("Tokens: Themes modal", () => {
|
||||
await fontSizeField.fill("");
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
// Fill in values for all fields and verify they persist when switching tabs
|
||||
await fontSizeField.fill("16");
|
||||
|
||||
const fontFamilyField = tokensUpdateCreateModal
|
||||
.getByLabel(/Font Family/i)
|
||||
.first();
|
||||
const fontWeightField =
|
||||
tokensUpdateCreateModal.getByLabel(/Font Weight/i);
|
||||
const letterSpacingField =
|
||||
tokensUpdateCreateModal.getByLabel(/Letter Spacing/i);
|
||||
const textCaseField = tokensUpdateCreateModal.getByLabel(/Text Case/i);
|
||||
const textDecorationField =
|
||||
tokensUpdateCreateModal.getByLabel(/Text Decoration/i);
|
||||
|
||||
// Capture all values before switching tabs
|
||||
const originalValues = {
|
||||
fontSize: await fontSizeField.inputValue(),
|
||||
fontFamily: await fontFamilyField.inputValue(),
|
||||
fontWeight: await fontWeightField.inputValue(),
|
||||
letterSpacing: await letterSpacingField.inputValue(),
|
||||
textCase: await textCaseField.inputValue(),
|
||||
textDecoration: await textDecorationField.inputValue(),
|
||||
};
|
||||
|
||||
// Switch to reference tab and back to composite tab
|
||||
const referenceTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceTabButton.click();
|
||||
const compositeTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Composite",
|
||||
});
|
||||
await compositeTabButton.click();
|
||||
|
||||
// Verify all values are preserved after switching tabs
|
||||
await expect(fontSizeField).toHaveValue(originalValues.fontSize);
|
||||
await expect(fontFamilyField).toHaveValue(originalValues.fontFamily);
|
||||
await expect(fontWeightField).toHaveValue(originalValues.fontWeight);
|
||||
await expect(letterSpacingField).toHaveValue(
|
||||
originalValues.letterSpacing,
|
||||
);
|
||||
await expect(textCaseField).toHaveValue(originalValues.textCase);
|
||||
await expect(textDecorationField).toHaveValue(
|
||||
originalValues.textDecoration,
|
||||
);
|
||||
|
||||
await saveButton.click();
|
||||
|
||||
// Modal should close, token should be visible (with new name) in sidebar
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User cant submit empty typography token or reference", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("typography.empty");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Font Size");
|
||||
|
||||
// Insert a value and then delete it
|
||||
await valueField.fill("1");
|
||||
await valueField.fill("");
|
||||
|
||||
// Submit button should be disabled when field is empty
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Switch to reference tab, should not be submittable either
|
||||
const referenceTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceTabButton.click();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test("User adds typography token with reference", async ({ page }) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const newTokenTitle = "NewReference";
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill(newTokenTitle);
|
||||
|
||||
const referenceTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Reference",
|
||||
});
|
||||
referenceTabButton.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByLabel("Reference");
|
||||
await referenceField.fill("{Full}");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
const resolvedValue =
|
||||
await tokensUpdateCreateModal.getByText("Resolved value:");
|
||||
await expect(resolvedValue).toBeVisible();
|
||||
await expect(resolvedValue).toContainText("Font Family: 42dot Sans");
|
||||
await expect(resolvedValue).toContainText("Font Size: 100");
|
||||
await expect(resolvedValue).toContainText("Font Weight: 300");
|
||||
await expect(resolvedValue).toContainText("Letter Spacing: 2");
|
||||
await expect(resolvedValue).toContainText("Text Case: uppercase");
|
||||
await expect(resolvedValue).toContainText("Text Decoration: underline");
|
||||
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
const newToken = tokensSidebar.getByRole("button", {
|
||||
name: newTokenTitle,
|
||||
});
|
||||
await expect(newToken).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,6 +88,10 @@
|
||||
{:error/code :error.style-dictionary/invalid-token-value-font-weight
|
||||
:error/fn #(tr "workspace.tokens.invalid-font-weight-token-value" %)}
|
||||
|
||||
:error.style-dictionary/invalid-token-value-typography
|
||||
{:error/code :error.style-dictionary/invalid-token-value-typography
|
||||
:error/fn #(tr "workspace.tokens.invalid-token-value-typography" %)}
|
||||
|
||||
:error/unknown
|
||||
{:error/code :error/unknown
|
||||
:error/fn #(tr "labels.unknown-error")}})
|
||||
|
||||
32
frontend/src/app/main/data/workspace/tokens/format.cljs
Normal file
32
frontend/src/app/main/data/workspace/tokens/format.cljs
Normal file
@@ -0,0 +1,32 @@
|
||||
(ns app.main.data.workspace.tokens.format
|
||||
(:require
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def category-dictionary
|
||||
{:stroke-width "Stroke Width"
|
||||
:spacing "Spacing"
|
||||
:sizing "Sizing"
|
||||
:border-radius "Border Radius"
|
||||
:x "X"
|
||||
:y "Y"
|
||||
:font-size "Font Size"
|
||||
:font-family "Font Family"
|
||||
:font-weight "Font Weight"
|
||||
:letter-spacing "Letter Spacing"
|
||||
:text-case "Text Case"
|
||||
:text-decoration "Text Decoration"})
|
||||
|
||||
(defn format-token-value
|
||||
"Converts token value of any shape to a string."
|
||||
[token-value]
|
||||
(cond
|
||||
(map? token-value)
|
||||
(->> (map (fn [[k v]] (str "- " (category-dictionary k) ": " (format-token-value v))) token-value)
|
||||
(str/join "\n")
|
||||
(str "\n"))
|
||||
|
||||
(sequential? token-value)
|
||||
(str/join ", " token-value)
|
||||
|
||||
:else
|
||||
(str token-value)))
|
||||
@@ -102,19 +102,19 @@
|
||||
|
||||
;; Validation ------------------------------------------------------------------
|
||||
|
||||
(defn invalidate-empty-value [token-value]
|
||||
(defn check-empty-value [token-value]
|
||||
(when (empty? (str/trim token-value))
|
||||
(wte/get-error-code :error.token/empty-input)))
|
||||
|
||||
(defn invalidate-token-empty-value [token]
|
||||
(invalidate-empty-value (:value token)))
|
||||
(defn check-token-empty-value [token]
|
||||
(check-empty-value (:value token)))
|
||||
|
||||
(defn invalidate-self-reference [token-name token-value]
|
||||
(defn check-self-reference [token-name token-value]
|
||||
(when (ctob/token-value-self-reference? token-name token-value)
|
||||
(wte/get-error-code :error.token/direct-self-reference)))
|
||||
|
||||
(defn invalidate-token-self-reference [token]
|
||||
(invalidate-self-reference (:name token) (:value token)))
|
||||
(defn check-token-self-reference [token]
|
||||
(check-self-reference (:name token) (:value token)))
|
||||
|
||||
(defn validate-resolve-token
|
||||
[token prev-token tokens]
|
||||
@@ -146,7 +146,7 @@
|
||||
(rx/of token)))
|
||||
|
||||
(def default-validators
|
||||
[invalidate-token-empty-value invalidate-token-self-reference])
|
||||
[check-token-empty-value check-self-reference])
|
||||
|
||||
(defn default-validate-token
|
||||
"Validates a token by confirming a list of `validator` predicates and resolving the token using `tokens` with StyleDictionary.
|
||||
@@ -176,14 +176,14 @@
|
||||
;; Resolving token via StyleDictionary
|
||||
(rx/mapcat #(validate-resolve-token % prev-token tokens)))))
|
||||
|
||||
(defn invalidate-coll-self-reference
|
||||
(defn check-coll-self-reference
|
||||
"Invalidate a collection of `token-vals` for a self-refernce against `token-name`.,"
|
||||
[token-name token-vals]
|
||||
(when (some #(ctob/token-value-self-reference? token-name %) token-vals)
|
||||
(wte/get-error-code :error.token/direct-self-reference)))
|
||||
|
||||
(defn invalidate-font-family-token-self-reference [token]
|
||||
(invalidate-coll-self-reference (:name token) (:value token)))
|
||||
(defn check-font-family-token-self-reference [token]
|
||||
(check-coll-self-reference (:name token) (:value token)))
|
||||
|
||||
(defn validate-font-family-token
|
||||
[props]
|
||||
@@ -192,30 +192,42 @@
|
||||
(assoc :validators [(fn [token]
|
||||
(when (empty? (:value token))
|
||||
(wte/get-error-code :error.token/empty-input)))
|
||||
invalidate-font-family-token-self-reference])
|
||||
check-font-family-token-self-reference])
|
||||
(default-validate-token)))
|
||||
|
||||
(defn invalidate-typography-token-self-reference
|
||||
"Invalidate token when any of the attributes in token value have a self refernce."
|
||||
(defn check-typography-token-self-reference
|
||||
"Check token when any of the attributes in token value have a self-reference."
|
||||
[token]
|
||||
(let [token-name (:name token)
|
||||
token-values (:value token)]
|
||||
(some (fn [[k v]]
|
||||
(when-let [err (case k
|
||||
:font-family (invalidate-coll-self-reference token-name v)
|
||||
(invalidate-self-reference token-name v))]
|
||||
:font-family (check-coll-self-reference token-name v)
|
||||
(check-self-reference token-name v))]
|
||||
(assoc err :typography-key k)))
|
||||
token-values)))
|
||||
|
||||
(defn check-empty-typography-token [token]
|
||||
(when (empty? (:value token))
|
||||
(wte/get-error-code :error.token/empty-input)))
|
||||
|
||||
(defn validate-typography-token
|
||||
[props]
|
||||
(-> props
|
||||
(update :token-value
|
||||
(fn [v]
|
||||
(-> (or v {})
|
||||
(d/update-when :font-family #(if (string? %) (ctt/split-font-family %) %)))))
|
||||
(assoc :validators [invalidate-typography-token-self-reference])
|
||||
(default-validate-token)))
|
||||
[{:keys [token-value] :as props}]
|
||||
(cond
|
||||
;; Entering form without a value - show no error just resolve nil
|
||||
(nil? token-value) (rx/of nil)
|
||||
;; Validate refrence string
|
||||
(ctt/typography-composite-token-reference? token-value) (default-validate-token props)
|
||||
;; Validate composite token
|
||||
:else
|
||||
(-> props
|
||||
(update :token-value
|
||||
(fn [v]
|
||||
(-> (or v {})
|
||||
(d/update-when :font-family #(if (string? %) (ctt/split-font-family %) %)))))
|
||||
(assoc :validators [check-empty-typography-token
|
||||
check-typography-token-self-reference])
|
||||
(default-validate-token))))
|
||||
|
||||
(defn use-debonced-resolve-callback
|
||||
"Resolves a token values using `StyleDictionary`.
|
||||
@@ -365,6 +377,11 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
token-resolve-result* (mf/use-state (get resolved-tokens (cft/token-identifier token)))
|
||||
token-resolve-result (deref token-resolve-result*)
|
||||
|
||||
clear-resolve-value
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! token-resolve-result* nil)))
|
||||
|
||||
set-resolve-value
|
||||
(mf/use-fn
|
||||
(mf/deps on-value-resolve)
|
||||
@@ -399,7 +416,6 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
(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)))
|
||||
|
||||
@@ -576,7 +592,8 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
:on-update-value on-update-value
|
||||
:on-external-update-value on-external-update-value
|
||||
:custom-input-token-value-props custom-input-token-value-props
|
||||
:token-resolve-result token-resolve-result}]
|
||||
:token-resolve-result token-resolve-result
|
||||
:clear-resolve-value clear-resolve-value}]
|
||||
[:> input-tokens-value*
|
||||
{:placeholder placeholder
|
||||
:label label
|
||||
@@ -716,6 +733,7 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
color-value (-> (tinycolor/valid-color hex-value)
|
||||
(tinycolor/set-alpha (or alpha 1))
|
||||
(tinycolor/->string format))]
|
||||
(dom/set-value! (mf/ref-val input-ref) color-value)
|
||||
(on-external-update-value color-value))))]
|
||||
|
||||
[:*
|
||||
@@ -897,7 +915,7 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
{:label "Text Decoration"
|
||||
:placeholder (tr "workspace.tokens.text-decoration-value-enter")}))
|
||||
|
||||
(mf/defc typography-inputs*
|
||||
(mf/defc typography-value-inputs*
|
||||
[{:keys [default-value on-blur on-update-value token-resolve-result]}]
|
||||
(let [typography-inputs (mf/use-memo typography-inputs)
|
||||
errors-by-key (sd/collect-typography-errors token-resolve-result)]
|
||||
@@ -928,12 +946,12 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
(-> (obj/set! e "tokenType" k)
|
||||
(on-update-value))))]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:div {:key (str k)
|
||||
:class (stl/css :input-row)}
|
||||
(case k
|
||||
:font-family
|
||||
[:> font-picker*
|
||||
{:key (str k)
|
||||
:label label
|
||||
{:label label
|
||||
:placeholder placeholder
|
||||
:input-ref input-ref
|
||||
:default-value (when value (ctt/join-font-family value))
|
||||
@@ -942,24 +960,84 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
:on-external-update-value on-external-update-value
|
||||
:token-resolve-result (when (seq token-resolve-result) token-resolve-result)}]
|
||||
[:> input-tokens-value*
|
||||
{:key (str k)
|
||||
:label label
|
||||
{:label label
|
||||
:placeholder placeholder
|
||||
:default-value value
|
||||
:on-blur on-blur
|
||||
:on-change on-change
|
||||
:token-resolve-result (when (seq token-resolve-result) token-resolve-result)}])]))]))
|
||||
|
||||
(mf/defc typography-reference-input*
|
||||
[{:keys [default-value on-blur on-update-value token-resolve-result]}]
|
||||
[:> input-tokens-value*
|
||||
{:label "Reference"
|
||||
:placeholder "Reference"
|
||||
:default-value (when (ctt/typography-composite-token-reference? default-value) default-value)
|
||||
:on-blur on-blur
|
||||
:on-change on-update-value
|
||||
:token-resolve-result (when (or
|
||||
(:errors token-resolve-result)
|
||||
(string? (:value token-resolve-result)))
|
||||
token-resolve-result)}])
|
||||
|
||||
(mf/defc typography-inputs*
|
||||
[{:keys [default-value on-update-value on-external-update-value on-value-resolve clear-resolve-value] :rest props}]
|
||||
(let [;; Active Tab State
|
||||
active-tab* (mf/use-state (if (ctt/typography-composite-token-reference? default-value) :reference :composite))
|
||||
active-tab (deref active-tab*)
|
||||
reference-tab-active? (= :reference active-tab)
|
||||
;; Backup value ref
|
||||
;; Used to restore the previously entered value when switching tabs
|
||||
;; Uses ref to not trigger state updates during update
|
||||
backup-state-ref (mf/use-var
|
||||
(if reference-tab-active?
|
||||
{:reference default-value}
|
||||
{:composite default-value}))
|
||||
default-value (get @backup-state-ref active-tab)
|
||||
|
||||
on-toggle-tab
|
||||
(mf/use-fn
|
||||
(mf/deps active-tab on-external-update-value on-value-resolve clear-resolve-value)
|
||||
(fn []
|
||||
(let [next-tab (if (= active-tab :composite) :reference :composite)]
|
||||
;; Clear the resolved value so it wont show up before the next-tab value has resolved
|
||||
(clear-resolve-value)
|
||||
;; Restore the internal value from backup
|
||||
(on-external-update-value (get @backup-state-ref next-tab))
|
||||
(reset! active-tab* next-tab))))
|
||||
|
||||
;; Store token value in the backup-state-ref
|
||||
on-update-reference-value
|
||||
(mf/use-fn
|
||||
(mf/deps on-update-value active-tab)
|
||||
(fn [e]
|
||||
(if reference-tab-active?
|
||||
(swap! backup-state-ref assoc :reference (dom/get-target-val e))
|
||||
(swap! backup-state-ref assoc-in [:composite (obj/get e "tokenType")] (dom/get-target-val e)))
|
||||
(on-update-value e)))
|
||||
|
||||
input-props (mf/spread-props props {:default-value default-value
|
||||
:on-update-value on-update-reference-value})]
|
||||
[:div {:class (stl/css :nested-input-row)}
|
||||
[:button {:on-click on-toggle-tab :type "button"}
|
||||
(if reference-tab-active? "Composite" "Reference")]
|
||||
(if reference-tab-active?
|
||||
[:> typography-reference-input* input-props]
|
||||
[:> typography-value-inputs* input-props])]))
|
||||
|
||||
(mf/defc typography-form*
|
||||
[{:keys [token] :rest props}]
|
||||
(let [on-get-token-value
|
||||
(mf/use-callback
|
||||
(fn [e prev-value]
|
||||
(let [token-type (obj/get e "tokenType")
|
||||
input-value (dom/get-target-val e)]
|
||||
(if (empty? input-value)
|
||||
(dissoc prev-value token-type)
|
||||
(assoc prev-value token-type input-value)))))]
|
||||
input-value (dom/get-target-val e)
|
||||
reference-value-input? (not token-type)]
|
||||
(cond
|
||||
reference-value-input? input-value
|
||||
|
||||
(empty? input-value) (dissoc prev-value token-type)
|
||||
:else (assoc prev-value token-type input-value)))))]
|
||||
[:> form*
|
||||
(mf/spread-props props {:token token
|
||||
:custom-input-token-value typography-inputs*
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.data.workspace.tokens.warnings :as wtw]
|
||||
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
|
||||
[app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
|
||||
@@ -41,7 +42,7 @@
|
||||
(str/join "\n"))
|
||||
errors (->> (wte/humanize-errors errors)
|
||||
(str/join "\n"))
|
||||
:else (tr "workspace.tokens.resolved-value" (or resolved-value result)))
|
||||
:else (tr "workspace.tokens.resolved-value" (dwtf/format-token-value (or resolved-value result))))
|
||||
type (cond
|
||||
empty-message? "hint"
|
||||
errors "error"
|
||||
|
||||
@@ -31,3 +31,7 @@
|
||||
@include textEllipsis;
|
||||
color: var(--label-color);
|
||||
}
|
||||
|
||||
.resolved-value {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
[app.common.types.token :as ctt]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.color :as dwtc]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.components.color-bullet :refer [color-bullet]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||
@@ -26,6 +27,7 @@
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; Translation dictionaries
|
||||
|
||||
(def ^:private attribute-dictionary
|
||||
{:rotation "Rotation"
|
||||
:opacity "Opacity"
|
||||
@@ -74,20 +76,6 @@
|
||||
:x :x
|
||||
:y :y})
|
||||
|
||||
(def ^:private category-dictionary
|
||||
{:stroke-width "Stroke Width"
|
||||
:spacing "Spacing"
|
||||
:sizing "Sizing"
|
||||
:border-radius "Border Radius"
|
||||
:x "X"
|
||||
:y "Y"
|
||||
:font-size "Font Size"
|
||||
:font-family "Font Family"
|
||||
:font-weight "Font Weight"
|
||||
:letter-spacing "Letter Spacing"
|
||||
:text-case "Text Case"
|
||||
:text-decoration "Text Decoration"})
|
||||
|
||||
;; Helper functions
|
||||
|
||||
(defn partially-applied-attr
|
||||
@@ -105,24 +93,11 @@
|
||||
(str/join "\n"
|
||||
(map (fn [[category values]]
|
||||
(if (#{:x :y} category)
|
||||
(dm/str "- " (category-dictionary category))
|
||||
(dm/str "- " (category-dictionary category) ": "
|
||||
(dm/str "- " (dwtf/category-dictionary category))
|
||||
(dm/str "- " (dwtf/category-dictionary category) ": "
|
||||
(str/join ", " (map attribute-dictionary values)) ".")))
|
||||
grouped-values)))
|
||||
|
||||
(defn format-token-value [token-value]
|
||||
(cond
|
||||
(map? token-value)
|
||||
(->> (map (fn [[k v]] (str "- " (category-dictionary k) ": " (format-token-value v))) token-value)
|
||||
(str/join "\n")
|
||||
(str "\n"))
|
||||
|
||||
(sequential? token-value)
|
||||
(str/join "," token-value)
|
||||
|
||||
:else
|
||||
(str token-value)))
|
||||
|
||||
(defn- generate-tooltip
|
||||
"Generates a tooltip for a given token"
|
||||
[is-viewer shape theme-token token half-applied no-valid-value ref-not-in-active-set]
|
||||
@@ -142,8 +117,8 @@
|
||||
grouped-values (group-by dimensions-dictionary app-token-keys)
|
||||
|
||||
base-title (dm/str "Token: " name "\n"
|
||||
(tr "workspace.tokens.original-value" (format-token-value value)) "\n"
|
||||
(tr "workspace.tokens.resolved-value" (format-token-value resolved-value))
|
||||
(tr "workspace.tokens.original-value" (dwtf/format-token-value value)) "\n"
|
||||
(tr "workspace.tokens.resolved-value" (dwtf/format-token-value resolved-value))
|
||||
(when (= (:type token) :number)
|
||||
(dm/str "\n" (tr "workspace.tokens.more-options"))))]
|
||||
|
||||
|
||||
@@ -7531,6 +7531,10 @@ msgstr ""
|
||||
"Invalid font weight value: use numeric values (100-950) or standard names "
|
||||
"(thin, light, regular, bold, etc.) optionally followed by 'Italic'"
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:91
|
||||
msgid "workspace.tokens.invalid-token-value-typography"
|
||||
msgstr "Invalid value: must reference a composite typography token."
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:23
|
||||
msgid "workspace.tokens.invalid-json"
|
||||
msgstr "Import Error: Invalid token data in JSON."
|
||||
|
||||
@@ -7483,6 +7483,10 @@ msgstr ""
|
||||
"estándar (thin, light, regular, bold, etc.) opcionalmente seguidos de "
|
||||
"'Italic'"
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:91
|
||||
msgid "workspace.tokens.invalid-token-value-typography"
|
||||
msgstr "Valor no válido: debe hacer referencia a un token tipográfico compuesto."
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:23
|
||||
msgid "workspace.tokens.invalid-json"
|
||||
msgstr "Error al importar: Datos de token no válidos en JSON."
|
||||
|
||||
Reference in New Issue
Block a user