💄 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
await valueField.fill("");
await expect(
tokensUpdateCreateModal.getByText("Resolved value: -"),
tokensUpdateCreateModal.getByText("Token value cannot be empty"),
).toBeVisible();
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
await expect(valueField).toHaveValue(/^#[A-Fa-f\d]+$/);

View file

@ -32,6 +32,10 @@
{:error/code :error.import/style-dictionary-reference-errors
: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/code :error.token/direct-self-reference
:error/fn #(tr "workspace.token.self-reference")}

View file

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

View file

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

View file

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

View file

@ -41,7 +41,7 @@
position: relative;
& .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.foundations.assets.icon :as i]
[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.workspace.colorpicker :as colorpicker]
[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)]
(cond
(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)
(p/rejected {:errors [(wte/get-error-code :error.token/direct-self-reference)]})
@ -217,25 +216,27 @@
:on-finish-drag on-finish-drag
:on-change on-change'}]]))
(mf/defc token-value-or-errors
[{:keys [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))
(mf/defc token-value-hint
[{:keys [result]}]
(let [{:keys [errors warnings resolved-value]} result
empty-message? (nil? result)
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" (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]))
:else (tr "workspace.token.resolved-value" (or resolved-value result)))
type (cond
empty-message? "hint"
errors "error"
warnings "warning"
:else "hint")]
[:> hint-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/wrap-props false}
@ -331,7 +332,7 @@
color-ramp-open* (mf/use-state false)
color-ramp-open? (deref color-ramp-open*)
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 (deref token-resolve-result*)
@ -364,7 +365,7 @@
(dom/set-value! (mf/ref-val value-input-ref) hex)
hex)
value)]
(reset! value-ref value')
(mf/set-ref-val! value-ref value')
(on-update-value-debounced value'))))
on-update-color (mf/use-fn
(mf/deps color on-update-value-debounced)
@ -389,7 +390,7 @@
color-value (-> (tinycolor/valid-color hex-value)
(tinycolor/set-alpha (or alpha 1))
(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)
(on-update-value-debounced color-value))))
@ -443,7 +444,7 @@
;; and press enter before the next validations could return.
(let [final-name (finalize-name @token-name-ref)
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
valid-description?+ (some-> final-description validate-descripion schema-validation->promise)]
(-> (p/all [valid-name?+
@ -558,13 +559,12 @@
[:div {:class (stl/css :input-row)}
[:> 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")
:max-length 256
:default-value @value-ref
:value (mf/ref-val value-ref)
:ref value-input-ref
:on-change on-update-value
:error (not (nil? (:errors token-resolve-result)))
:on-blur on-update-value}
(when color?
[:> input-token-color-bullet*
@ -573,10 +573,11 @@
(when color-ramp-open?
[:> ramp* {:color (some-> color (tinycolor/valid-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)}
[:> 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
:variant "comfortable"
:default-value @description-ref

View file

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

View file

@ -6832,8 +6832,8 @@ msgid "workspace.token.edit-token"
msgstr "Edit token"
#: src/app/main/ui/workspace/tokens/form.cljs:544
msgid "workspace.token.enter-token-description"
msgstr "Add a description (optional)"
msgid "workspace.token.token-description"
msgstr "Add a description"
#: src/app/main/ui/workspace/tokens/form.cljs:498
msgid "workspace.token.enter-token-name"
@ -6869,6 +6869,10 @@ msgstr "Import Error:"
msgid "workspace.token.import-tooltip"
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
msgid "workspace.token.invalid-color"
msgstr "Invalid color value: %s"

View file

@ -6854,8 +6854,8 @@ msgid "workspace.token.edit-token"
msgstr "Editar token"
#: src/app/main/ui/workspace/tokens/form.cljs:544
msgid "workspace.token.enter-token-description"
msgstr "Añade una Descripción (opcional)"
msgid "workspace.token.token-description"
msgstr "Añade una descripción"
#: src/app/main/ui/workspace/tokens/form.cljs:498
msgid "workspace.token.enter-token-name"
@ -6887,9 +6887,13 @@ msgstr "Error al importar:"
msgid "workspace.token.import-tooltip"
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
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
msgid "workspace.token.invalid-json"