💄 Fix errors UI on input token for value

This commit is contained in:
Xavier Julian 2025-05-08 10:34:36 +02:00 committed by Xaviju
parent dbb9971482
commit c0eaa75232
10 changed files with 61 additions and 53 deletions

View file

@ -409,7 +409,7 @@ test.describe("Tokens: Tokens Tab", () => {
// Clearing the input field should pick hex // Clearing the input field should pick hex
await valueField.fill(""); await valueField.fill("");
await expect( await expect(
tokensUpdateCreateModal.getByText("Resolved value: -"), tokensUpdateCreateModal.getByText("Token value cannot be empty"),
).toBeVisible(); ).toBeVisible();
await valueSaturationSelector.click({ position: { x: 50, y: 50 } }); await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
await expect(valueField).toHaveValue(/^#[A-Fa-f\d]+$/); await expect(valueField).toHaveValue(/^#[A-Fa-f\d]+$/);

View file

@ -32,6 +32,10 @@
{:error/code :error.import/style-dictionary-reference-errors {:error/code :error.import/style-dictionary-reference-errors
:error/fn #(tr "workspace.token.import-error")} :error/fn #(tr "workspace.token.import-error")}
:error.token/empty-input
{:error/code :error.token/empty-input
:error/fn #(tr "workspace.token.empty-input")}
:error.token/direct-self-reference :error.token/direct-self-reference
{:error/code :error.token/direct-self-reference {:error/code :error.token/direct-self-reference
:error/fn #(tr "workspace.token.self-reference")} :error/fn #(tr "workspace.token.self-reference")}

View file

@ -40,7 +40,7 @@ export default {
control: { type: "select" }, control: { type: "select" },
}, },
variant: { variant: {
options: ["dense", "comfortable"], options: ["dense", "comfortable", "seamless"],
control: { type: "select" }, control: { type: "select" },
}, },
disabled: { disabled: {

View file

@ -13,6 +13,7 @@
--input-bg-color: var(--color-background-tertiary); --input-bg-color: var(--color-background-tertiary);
--input-fg-color: var(--color-foreground-primary); --input-fg-color: var(--color-foreground-primary);
--input-icon-color: var(--color-foreground-secondary); --input-icon-color: var(--color-foreground-secondary);
--input-text-indent: 0;
--input-outline-color: none; --input-outline-color: none;
--input-height: #{$sz-32}; --input-height: #{$sz-32};
--input-margin: unset; --input-margin: unset;
@ -43,7 +44,8 @@
} }
} }
.variant-dense { .variant-dense,
.variant-seamless {
@include use-typography("body-small"); @include use-typography("body-small");
} }
@ -79,6 +81,7 @@
border: none; border: none;
background: none; background: none;
inline-size: 100%; inline-size: 100%;
text-indent: var(--input-text-indent, 0);
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;

View file

@ -9,38 +9,37 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.constants :refer [max-input-length]] [app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
[app.main.ui.ds.controls.input :refer [input*]] [app.main.ui.ds.controls.utilities.label :refer [label*]]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def ^:private schema::input-tokens-value (def ^:private schema::input-tokens-value
[:map [:map
[:label :string] [:label :string]
[:placeholder {:optional true} :string] [:placeholder {:optional true} :string]
[:default-value {:optional true} [:maybe :string]] [:value {:optional true} [:maybe :string]]
[:class {:optional true} :string] [:class {:optional true} :string]
[:max-length {:optional true} :int] [:error {:optional true} :boolean]])
[:error {:optional true} :boolean]
[:value {:optional true} :string]])
(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 max-length error value children] :rest props} ref] [{:keys [class label placeholder error value children] :rest props} ref]
(let [id (mf/use-id) (let [id (mf/use-id)
input-ref (mf/use-ref) input-ref (mf/use-ref)
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)
:aria-invalid error :placeholder placeholder
:max-length (d/nilv max-length max-input-length) :value (d/nilv value "")
:value value :variant "comfortable"
:hint-type (when error "error")
: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))}
[:label {:for id :class (stl/css :label)} label] [:> label* {:for id} label]
[:div {:class (stl/css :input-wrapper)} [:div {:class (stl/css :input-wrapper)}
(when (some? children) (when (some? children)
[:div {:class (stl/css :input-swatch)} children]) [:div {:class (stl/css :input-swatch)} children])
[:> input* props]]])) [:> input-field* props]]]))

View file

@ -41,7 +41,7 @@
position: relative; position: relative;
& .input { & .input {
padding-inline-start: var(--sp-xxxl); --input-text-indent: var(--sp-xxl);
} }
} }
} }

View file

@ -28,7 +28,6 @@
[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]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]] [app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[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*]]
@ -112,7 +111,7 @@
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)) (empty? (str/trim value))
(p/rejected {:errors [{:error/code :error/empty-input}]}) (p/rejected {:errors [(wte/get-error-code :error.token/empty-input)]})
(ctob/token-value-self-reference? token-name value) (ctob/token-value-self-reference? token-name value)
(p/rejected {:errors [(wte/get-error-code :error.token/direct-self-reference)]}) (p/rejected {:errors [(wte/get-error-code :error.token/direct-self-reference)]})
@ -217,25 +216,27 @@
:on-finish-drag on-finish-drag :on-finish-drag on-finish-drag
:on-change on-change'}]])) :on-change on-change'}]]))
(mf/defc token-value-or-errors (mf/defc token-value-hint
[{:keys [result-or-errors]}] [{:keys [result]}]
(let [{:keys [errors warnings resolved-value]} result-or-errors (let [{:keys [errors warnings resolved-value]} result
empty-message? (or (nil? result-or-errors) empty-message? (nil? result)
(wte/has-error-code? :error/empty-input errors))
message (cond message (cond
empty-message? (tr "workspace.token.resolved-value" "-") empty-message? (tr "workspace.token.resolved-value" "-")
warnings (wtw/humanize-warnings warnings) warnings (wtw/humanize-warnings warnings)
errors (->> (wte/humanize-errors errors) errors (->> (wte/humanize-errors errors)
(str/join "\n")) (str/join "\n"))
:else (tr "workspace.token.resolved-value" (or resolved-value result-or-errors)))] :else (tr "workspace.token.resolved-value" (or resolved-value result)))
[:> text* {:as "p" type (cond
:typography "body-small" empty-message? "hint"
:class (stl/css-case :resolved-value true errors "error"
:resolved-value-placeholder empty-message? warnings "warning"
:resolved-value-warning (seq warnings) :else "hint")]
:resolved-value-error (seq errors))} [:> hint-message*
message])) {:id "token-value-hint"
:message message
:class (stl/css-case :resolved-value (not (or empty-message? (seq warnings) (seq errors))))
:type type}]))
(mf/defc form (mf/defc form
{::mf/wrap-props false} {::mf/wrap-props false}
@ -331,7 +332,7 @@
color-ramp-open* (mf/use-state false) color-ramp-open* (mf/use-state false)
color-ramp-open? (deref color-ramp-open*) 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-var (:value token)) value-ref (mf/use-ref (:value token))
token-resolve-result* (mf/use-state (get resolved-tokens (cft/token-identifier token))) token-resolve-result* (mf/use-state (get resolved-tokens (cft/token-identifier token)))
token-resolve-result (deref token-resolve-result*) token-resolve-result (deref token-resolve-result*)
@ -364,7 +365,7 @@
(dom/set-value! (mf/ref-val value-input-ref) hex) (dom/set-value! (mf/ref-val value-input-ref) hex)
hex) hex)
value)] value)]
(reset! value-ref value') (mf/set-ref-val! value-ref value')
(on-update-value-debounced value')))) (on-update-value-debounced value'))))
on-update-color (mf/use-fn on-update-color (mf/use-fn
(mf/deps color on-update-value-debounced) (mf/deps color on-update-value-debounced)
@ -389,7 +390,7 @@
color-value (-> (tinycolor/valid-color hex-value) color-value (-> (tinycolor/valid-color hex-value)
(tinycolor/set-alpha (or alpha 1)) (tinycolor/set-alpha (or alpha 1))
(tinycolor/->string format))] (tinycolor/->string format))]
(reset! value-ref color-value) (mf/set-ref-val! value-ref color-value)
(dom/set-value! (mf/ref-val value-input-ref) color-value) (dom/set-value! (mf/ref-val value-input-ref) color-value)
(on-update-value-debounced color-value)))) (on-update-value-debounced color-value))))
@ -443,7 +444,7 @@
;; and press enter before the next validations could return. ;; and press enter before the next validations could return.
(let [final-name (finalize-name @token-name-ref) (let [final-name (finalize-name @token-name-ref)
valid-name?+ (-> (validate-name final-name) schema-validation->promise) valid-name?+ (-> (validate-name final-name) schema-validation->promise)
final-value (finalize-value @value-ref) final-value (finalize-value (mf/ref-val value-ref))
final-description @description-ref final-description @description-ref
valid-description?+ (some-> final-description validate-descripion schema-validation->promise)] valid-description?+ (some-> final-description validate-descripion schema-validation->promise)]
(-> (p/all [valid-name?+ (-> (p/all [valid-name?+
@ -558,13 +559,12 @@
[:div {:class (stl/css :input-row)} [:div {:class (stl/css :input-row)}
[:> input-tokens-value* [:> input-tokens-value*
{:id "token-value" {:placeholder (tr "workspace.token.token-value-enter")
:placeholder (tr "workspace.token.token-value-enter")
:label (tr "workspace.token.token-value") :label (tr "workspace.token.token-value")
:max-length 256 :value (mf/ref-val value-ref)
:default-value @value-ref
:ref value-input-ref :ref value-input-ref
:on-change on-update-value :on-change on-update-value
:error (not (nil? (:errors token-resolve-result)))
:on-blur on-update-value} :on-blur on-update-value}
(when color? (when color?
[:> input-token-color-bullet* [:> input-token-color-bullet*
@ -573,10 +573,11 @@
(when color-ramp-open? (when color-ramp-open?
[:> ramp* {:color (some-> color (tinycolor/valid-color)) [:> ramp* {:color (some-> color (tinycolor/valid-color))
:on-change on-update-color}]) :on-change on-update-color}])
[:& token-value-or-errors {:result-or-errors 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.token.token-description") [:> input* {:label (tr "workspace.token.token-description")
:placeholder (tr "workspace.token.enter-token-description") :placeholder (tr "workspace.token.token-description")
:is-optional true
:max-length max-input-length :max-length max-input-length
:variant "comfortable" :variant "comfortable"
:default-value @description-ref :default-value @description-ref

View file

@ -50,14 +50,7 @@
.resolved-value { .resolved-value {
--input-hint-color: var(--color-foreground-primary); --input-hint-color: var(--color-foreground-primary);
margin-bottom: 0;
padding: $s-4 $s-6;
color: var(--input-hint-color); color: var(--input-hint-color);
white-space: pre-wrap;
}
.resolved-value-placeholder {
--input-hint-color: var(--color-foreground-secondary);
} }
.resolved-value-error { .resolved-value-error {

View file

@ -6832,8 +6832,8 @@ msgid "workspace.token.edit-token"
msgstr "Edit token" msgstr "Edit token"
#: src/app/main/ui/workspace/tokens/form.cljs:544 #: src/app/main/ui/workspace/tokens/form.cljs:544
msgid "workspace.token.enter-token-description" msgid "workspace.token.token-description"
msgstr "Add a description (optional)" msgstr "Add a description"
#: src/app/main/ui/workspace/tokens/form.cljs:498 #: src/app/main/ui/workspace/tokens/form.cljs:498
msgid "workspace.token.enter-token-name" msgid "workspace.token.enter-token-name"
@ -6869,6 +6869,10 @@ msgstr "Import Error:"
msgid "workspace.token.import-tooltip" msgid "workspace.token.import-tooltip"
msgstr "Importing a JSON file will override all your current tokens, sets and themes" msgstr "Importing a JSON file will override all your current tokens, sets and themes"
#: src/app/main/ui/workspace/tokens/errors.cljs:29
msgid "workspace.token.empty-input"
msgstr "Token value cannot be empty"
#: src/app/main/ui/workspace/tokens/errors.cljs:29 #: src/app/main/ui/workspace/tokens/errors.cljs:29
msgid "workspace.token.invalid-color" msgid "workspace.token.invalid-color"
msgstr "Invalid color value: %s" msgstr "Invalid color value: %s"

View file

@ -6854,8 +6854,8 @@ msgid "workspace.token.edit-token"
msgstr "Editar token" msgstr "Editar token"
#: src/app/main/ui/workspace/tokens/form.cljs:544 #: src/app/main/ui/workspace/tokens/form.cljs:544
msgid "workspace.token.enter-token-description" msgid "workspace.token.token-description"
msgstr "Añade una Descripción (opcional)" msgstr "Añade una descripción"
#: src/app/main/ui/workspace/tokens/form.cljs:498 #: src/app/main/ui/workspace/tokens/form.cljs:498
msgid "workspace.token.enter-token-name" msgid "workspace.token.enter-token-name"
@ -6887,9 +6887,13 @@ msgstr "Error al importar:"
msgid "workspace.token.import-tooltip" msgid "workspace.token.import-tooltip"
msgstr "Al importar un fichero JSON sobreescribirás todos tus tokens, sets y themes" msgstr "Al importar un fichero JSON sobreescribirás todos tus tokens, sets y themes"
#: src/app/main/ui/workspace/tokens/errors.cljs:29
msgid "workspace.token.empty-input"
msgstr "El valor del token no puede estar vacío"
#: src/app/main/ui/workspace/tokens/errors.cljs:29 #: src/app/main/ui/workspace/tokens/errors.cljs:29
msgid "workspace.token.invalid-color" msgid "workspace.token.invalid-color"
msgstr "Valor de color no válido: %s" msgstr "Valor de color inválido: %s"
#: src/app/main/ui/workspace/tokens/errors.cljs:13 #: src/app/main/ui/workspace/tokens/errors.cljs:13
msgid "workspace.token.invalid-json" msgid "workspace.token.invalid-json"