mirror of
https://github.com/penpot/penpot.git
synced 2025-05-24 21:46:12 +02:00
💄 Fix errors UI on input token for value
This commit is contained in:
parent
dbb9971482
commit
c0eaa75232
10 changed files with 61 additions and 53 deletions
|
@ -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]+$/);
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -40,7 +40,7 @@ export default {
|
|||
control: { type: "select" },
|
||||
},
|
||||
variant: {
|
||||
options: ["dense", "comfortable"],
|
||||
options: ["dense", "comfortable", "seamless"],
|
||||
control: { type: "select" },
|
||||
},
|
||||
disabled: {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]]]))
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
position: relative;
|
||||
|
||||
& .input {
|
||||
padding-inline-start: var(--sp-xxxl);
|
||||
--input-text-indent: var(--sp-xxl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue