From 6692f8dce26f86a9d8a9f3de979efdcad234f91d Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Mon, 10 Feb 2025 15:04:22 +0100 Subject: [PATCH] :sparkles: Add validation to token opacity (#5802) --- .../app/main/ui/workspace/tokens/errors.cljs | 5 +++ .../app/main/ui/workspace/tokens/form.cljs | 20 +++++++-- .../app/main/ui/workspace/tokens/form.scss | 5 +++ .../ui/workspace/tokens/style_dictionary.cljs | 44 ++++++++++++++++--- .../main/ui/workspace/tokens/warnings.cljs | 27 ++++++++++++ 5 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 frontend/src/app/main/ui/workspace/tokens/warnings.cljs diff --git a/frontend/src/app/main/ui/workspace/tokens/errors.cljs b/frontend/src/app/main/ui/workspace/tokens/errors.cljs index 9d3b94b78..e232455a7 100644 --- a/frontend/src/app/main/ui/workspace/tokens/errors.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/errors.cljs @@ -35,6 +35,11 @@ {:error/code :error.style-dictionary/invalid-token-value :error/fn #(str "Invalid token value: " %)} + :error.style-dictionary/invalid-token-value-opacity + {:error/code :error.style-dictionary/invalid-token-value-opacity + :error/fn #(str/join "\n" [(str "Invalid token value: " % ".") "Opacity must be between 0 and 100% or 0 and 1 (e.g. 50% or 0.5)"])} + + :error/unknown {:error/code :error/unknown :error/message "Unknown error"}}) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index b6d2cb5b2..c2cd6b8d8 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -29,6 +29,7 @@ [app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token-types :as wtty] [app.main.ui.workspace.tokens.update :as wtu] + [app.main.ui.workspace.tokens.warnings :as wtw] [app.util.dom :as dom] [app.util.functions :as uf] [app.util.i18n :refer [tr]] @@ -198,18 +199,21 @@ (mf/defc token-value-or-errors [{:keys [result-or-errors]}] - (let [{:keys [errors]} result-or-errors + (let [{:keys [errors warnings resolved-value]} result-or-errors empty-message? (or (nil? result-or-errors) (wte/has-error-code? :error/empty-input errors)) + message (cond empty-message? (tr "workspace.token.resolved-value" "-") + warnings (wtw/humanize-warnings warnings) errors (->> (wte/humanize-errors errors) (str/join "\n")) - :else (tr "workspace.token.resolved-value" result-or-errors))] + :else (tr "workspace.token.resolved-value" (or resolved-value result-or-errors)))] [:> text* {:as "p" :typography "body-small" :class (stl/css-case :resolved-value true :resolved-value-placeholder empty-message? + :resolved-value-warning (seq warnings) :resolved-value-error (seq errors))} message])) @@ -227,6 +231,7 @@ token-path (mf/use-memo (mf/deps (:name token)) #(wtt/token-name->path (:name token))) + selected-set-tokens-tree (mf/use-memo (mf/deps token-path selected-set-tokens) (fn [] @@ -294,14 +299,20 @@ color-ramp-open? (deref color-ramp-open*) value-input-ref (mf/use-ref nil) value-ref (mf/use-var (:value token)) - token-resolve-result* (mf/use-state (get-in resolved-tokens [(wtt/token-identifier token) :resolved-value])) + + token-resolve-result* (mf/use-state (get resolved-tokens (wtt/token-identifier token))) token-resolve-result (deref token-resolve-result*) set-resolve-value (mf/use-fn (fn [token-or-err] (let [error? (:errors token-or-err) - v (if error? + warnings? (:warnings token-or-err) + v (cond + error? token-or-err + warnings? + (:warnings {:warnings token-or-err}) + :else (:resolved-value token-or-err))] (when color? (reset! color (if error? nil v))) (reset! token-resolve-result* v)))) @@ -333,6 +344,7 @@ (on-display-colorpicker open?)))) value-error? (seq (:errors token-resolve-result)) + valid-value-field? (and (not value-error?) (valid-value? token-resolve-result)) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.scss b/frontend/src/app/main/ui/workspace/tokens/form.scss index 171d8c1ac..53dc452f9 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.scss +++ b/frontend/src/app/main/ui/workspace/tokens/form.scss @@ -53,6 +53,7 @@ margin-bottom: 0; padding: $s-4 $s-6; color: var(--input-hint-color); + white-space: pre-wrap; } .resolved-value-placeholder { @@ -63,6 +64,10 @@ --input-hint-color: var(--status-color-error-500); } +.resolved-value-warning { + --input-hint-color: var(--status-color-warning-500); +} + .form-modal-title { color: var(--color-foreground-primary); } diff --git a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs index 005e7ee65..60953fec9 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -9,6 +9,7 @@ [app.main.ui.workspace.tokens.errors :as wte] [app.main.ui.workspace.tokens.tinycolor :as tinycolor] [app.main.ui.workspace.tokens.token :as wtt] + [app.main.ui.workspace.tokens.warnings :as wtw] [beicon.v2.core :as rx] [cuerdas.core :as str] [promesa.core :as p] @@ -59,6 +60,31 @@ :references references} {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}))) +(defn parse-sd-token-opacity-value + "Parses `value` of a dimensions `sd-token` into a map like `{:value 1 :unit \"px\"}`. + If the `value` is not parseable and/or has missing references returns a map with `:errors`. + If the `value` is parseable but is out of range returns a map with `warnings`." + [value has-references?] + + (let [parsed-value (wtt/parse-token-value value) + out-of-scope (< 100 (:value parsed-value)) + references (seq (ctob/find-token-value-references value))] + (cond + (and parsed-value (not out-of-scope)) + parsed-value + + references + {:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)] + :references references} + + (and (not has-references?) out-of-scope) + {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-opacity value)]} + + (and has-references? out-of-scope) + (assoc parsed-value :warnings [(wtw/warning-with-value :warning.style-dictionary/invalid-referenced-token-value value)]) + + :else {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}))) + (defn process-sd-tokens "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 @@ -95,14 +121,22 @@ (fn [acc ^js sd-token] (let [origin-token (get-origin-token sd-token) value (.-value sd-token) + has-references? (str/includes? (:value origin-token) "{") parsed-token-value (case (:type origin-token) :color (parse-sd-token-color-value value) + :opacity (parse-sd-token-opacity-value value has-references?) (parse-sd-token-dimensions-value value)) - output-token (if (:errors parsed-token-value) - (merge origin-token parsed-token-value) - (assoc origin-token - :resolved-value (:value parsed-token-value) - :unit (:unit parsed-token-value)))] + output-token (cond (:errors parsed-token-value) + (merge origin-token parsed-token-value) + (:warnings parsed-token-value) + (assoc origin-token + :resolved-value (:value parsed-token-value) + :warnings (:warnings parsed-token-value) + :unit (:unit parsed-token-value)) + :else + (assoc origin-token + :resolved-value (:value parsed-token-value) + :unit (:unit parsed-token-value)))] (assoc acc (:name output-token) output-token))) {} sd-tokens)) diff --git a/frontend/src/app/main/ui/workspace/tokens/warnings.cljs b/frontend/src/app/main/ui/workspace/tokens/warnings.cljs new file mode 100644 index 000000000..7b84e4a21 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/warnings.cljs @@ -0,0 +1,27 @@ +(ns app.main.ui.workspace.tokens.warnings + (:require + [cuerdas.core :as str])) + +(def warning-codes + {:warning.style-dictionary/invalid-referenced-token-value + {:warning/code :warning.style-dictionary/invalid-referenced-token-value + :warning/fn (fn [value] (str/join "\n" [(str "Resolved value " value ".") "Opacity must be between 0 and 100% or 0 and 1 (e.g. 50% or 0.5)"]))} + + :warning/unknown + {:warning/code :warning/unknown + :warning/message "Unknown warning"}}) + +(defn get-warning-code [warning-key] + (get warning-codes warning-key (:warning/unknown warning-codes))) + +(defn warning-with-value [warning-key warning-value] + (-> (get-warning-code warning-key) + (assoc :warning/value warning-value))) + +(defn humanize-warnings [warnings] + (->> warnings + (map (fn [warn] + (cond + (:warning/fn warn) ((:warning/fn warn) (:warning/value warn)) + (:warning/message warn) (:warning/message warn) + :else warn))))) \ No newline at end of file