diff --git a/CHANGES.md b/CHANGES.md index 459417162e..86e915656e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ - Duplicate token sets [Taiga #10694](https://tree.taiga.io/project/penpot/issue/10694) - Add set selection in create Token themes flow [Taiga #10746](https://tree.taiga.io/project/penpot/issue/10746) - Display indicator on not active sets [Taiga #10668](https://tree.taiga.io/project/penpot/issue/10668) +- Create `input*` wrapper component, and `label*`, `input-field*` and `hint-message*` components [Taiga #10713](https://tree.taiga.io/project/penpot/us/10713) ### :bug: Bugs fixed diff --git a/frontend/src/app/main/ui/ds.cljs b/frontend/src/app/main/ui/ds.cljs index abd34f5c13..6df0ee299a 100644 --- a/frontend/src/app/main/ui/ds.cljs +++ b/frontend/src/app/main/ui/ds.cljs @@ -18,7 +18,8 @@ [app.main.ui.ds.foundations.typography :refer [typography-list]] [app.main.ui.ds.foundations.typography.heading :refer [heading*]] [app.main.ui.ds.foundations.typography.text :refer [text*]] - [app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon* token-status-list]] + [app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon* + token-status-list]] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] [app.main.ui.ds.notifications.actionable :refer [actionable*]] [app.main.ui.ds.notifications.context-notification :refer [context-notification*]] diff --git a/frontend/src/app/main/ui/ds/controls/input.cljs b/frontend/src/app/main/ui/ds/controls/input.cljs index 93674b4da5..4e31d87dc5 100644 --- a/frontend/src/app/main/ui/ds/controls/input.cljs +++ b/frontend/src/app/main/ui/ds/controls/input.cljs @@ -5,50 +5,62 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.ds.controls.input - (:require-macros - [app.common.data.macros :as dm] - [app.main.style :as stl]) + (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.main.constants :refer [max-input-length]] - [app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]] - [app.util.dom :as dom] + [app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]] + [app.main.ui.ds.controls.utilities.input-field :refer [input-field*]] + [app.main.ui.ds.controls.utilities.label :refer [label*]] + [app.main.ui.ds.foundations.assets.icon :refer [icon-list]] + [cuerdas.core :as str] [rumext.v2 :as mf])) (def ^:private schema:input [:map + [:id {:optional true} :string] + [:label {:optional true} :string] [:class {:optional true} :string] + [:is-optional {:optional true} :boolean] + [:placeholder {:optional true} :string] [:icon {:optional true} [:and :string [:fn #(contains? icon-list %)]]] [:type {:optional true} :string] [:max-length {:optional true} :int] - [:variant {:optional true} :string]]) + [:variant {:optional true} [:maybe [:enum "seamless" "dense" "comfortable"]]] + [:hint-message {:optional true} [:maybe :string]] + [:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]]) (mf/defc input* {::mf/props :obj ::mf/forward-ref true ::mf/schema schema:input} - [{:keys [icon class type max-length variant] :rest props} ref] - (let [ref (or ref (mf/use-ref)) - type (d/nilv type "text") - props (mf/spread-props props - {:class (stl/css-case - :input true - :input-with-icon (some? icon)) - :ref ref - :maxlength (d/nilv max-length (str max-input-length)) - :type type}) + [{:keys [id class label is-optional type max-length variant hint-message hint-type children] :rest props} ref] + (let [id (or id (mf/use-id)) + variant (d/nilv variant "dense") + is-optional (d/nilv is-optional false) + type (d/nilv type "text") + max-length (d/nilv max-length max-input-length) + has-hint (and (some? hint-message) (not (str/blank? hint-message))) + has-label (not (str/blank? label)) + ref (or ref (mf/use-ref)) + props (mf/spread-props props {:ref ref + :type type + :id id + :hint-type hint-type + :max-length max-length + :has-hint has-hint + :variant variant})] + [:div {:class (dm/str class " " (stl/css-case :input-wrapper true + :variant-dense (= variant "dense") + :variant-comfortable (= variant "comfortable") + :has-hint has-hint))} + (when has-label + [:> label* {:for id :is-optional is-optional} label]) + [:> input-field* props children] + (when has-hint + [:> hint-message* {:id id + :message hint-message + :type hint-type}])])) - on-icon-click - (mf/use-fn - (mf/deps ref) - (fn [_event] - (let [input-node (mf/ref-val ref)] - (dom/select-node input-node) - (dom/focus! input-node))))] - - [:> :span {:class (dm/str class " " (stl/css-case :container true - :input-seamless (= variant "seamless")))} - (when (some? icon) - [:> icon* {:icon-id icon :class (stl/css :icon) :on-click on-icon-click}]) - [:> :input props]])) diff --git a/frontend/src/app/main/ui/ds/controls/input.mdx b/frontend/src/app/main/ui/ds/controls/input.mdx index 1ecb0e937e..47e6b303c2 100644 --- a/frontend/src/app/main/ui/ds/controls/input.mdx +++ b/frontend/src/app/main/ui/ds/controls/input.mdx @@ -5,13 +5,10 @@ import * as InputStories from "./input.stories"; # Input -The `input*` component is a wrapper to the HTML `` element with custom styling -and additional elements that adds context and, in some cases, adds extra -functionality. +The `input*` component is a wrapper composed of `label*`, `input-field*`, and `hint-message*` components, functioning as a form element that adapts its UI based on configuration, making it suitable for different areas of the interface. - ## Technical notes ### Icons @@ -29,8 +26,6 @@ These are available in the `app.main.ds.foundations.assets.icon` namespace. [:> input* {:icon i/effects}] ``` - - ## Usage guidelines (design) ### Where to use diff --git a/frontend/src/app/main/ui/ds/controls/input.scss b/frontend/src/app/main/ui/ds/controls/input.scss index 0eeb49359b..a78df29a1b 100644 --- a/frontend/src/app/main/ui/ds/controls/input.scss +++ b/frontend/src/app/main/ui/ds/controls/input.scss @@ -4,98 +4,11 @@ // // Copyright (c) KALEIDOS INC -@use "../_borders.scss" as *; -@use "../_sizes.scss" as *; -@use "../typography.scss" as *; +@use "../spacing.scss" as *; -.container { - --input-bg-color: var(--color-background-tertiary); - --input-fg-color: var(--color-foreground-primary); - --input-icon-color: var(--color-foreground-secondary); - --input-outline-color: none; - - display: inline-flex; - column-gap: var(--sp-xs); - align-items: center; - position: relative; +.input-wrapper { + display: flex; + flex-direction: column; + gap: var(--sp-xs); inline-size: 100%; - - background: var(--input-bg-color); - border-radius: $br-8; - padding: 0 var(--sp-s); - outline-offset: #{$b-1}; - outline: $b-1 solid var(--input-outline-color); - - &:hover { - --input-bg-color: var(--color-background-quaternary); - } - - &:has(*:focus-visible) { - --input-bg-color: var(--color-background-primary); - --input-outline-color: var(--color-accent-primary); - } - - &:has(*:disabled) { - --input-bg-color: var(--color-background-primary); - --input-outline-color: var(--color-background-quaternary); - } -} - -.input-seamless { - --input-bg-color: none; - --input-outline-color: none; - --input-height: auto; - --input-margin: 0; - - padding: 0; - border: none; - - &:hover { - --input-bg-color: none; - --input-outline-color: none; - } - - &:has(*:focus-visible) { - --input-bg-color: none; - --input-outline-color: none; - } -} - -.input { - margin: var(--input-margin, unset); // remove settings from global css - padding: 0; - appearance: none; - height: var(--input-height, #{$sz-32}); - border: none; - background: none; - inline-size: 100%; - - @include use-typography("body-small"); - color: var(--input-fg-color); - - &:focus-visible { - outline: none; - } - - &::selection { - background: var(--color-accent-select); - } - - &::placeholder { - --input-fg-color: var(--color-foreground-secondary); - } - - &:is(:autofill, :autofill:hover, :autofill:focus, :autofill:active) { - -webkit-text-fill-color: var(--input-fg-color); - -webkit-background-clip: text; - caret-color: var(--input-bg-color); - } -} - -.input-with-icon { - margin-inline-start: var(--sp-xxs); -} - -.icon { - color: var(--color-foreground-secondary); } diff --git a/frontend/src/app/main/ui/ds/controls/input.stories.jsx b/frontend/src/app/main/ui/ds/controls/input.stories.jsx index 0e23bffe73..f80a8a97bc 100644 --- a/frontend/src/app/main/ui/ds/controls/input.stories.jsx +++ b/frontend/src/app/main/ui/ds/controls/input.stories.jsx @@ -14,34 +14,74 @@ export default { title: "Controls/Input", component: Components.Input, argTypes: { + defaultValue: { + control: { type: "text" }, + }, + label: { + control: { type: "text" }, + }, + placeholder: { + control: { type: "text" }, + }, + isOptional: { control: { type: "boolean" } }, icon: { options: icons, control: { type: "select" }, }, - value: { + type: { + options: ["text", "email", "password"], + control: { type: "select" }, + }, + hintMessage: { control: { type: "text" }, }, - disabled: { control: "boolean" }, + hintType: { + options: ["hint", "error", "warning"], + control: { type: "select" }, + }, + variant: { + options: ["dense", "comfortable"], + control: { type: "select" }, + }, + disabled: { + control: { type: "boolean" }, + }, }, args: { + id: "input", + label: "Label", + isOptional: false, + defaultValue: "Value", + placeholder: "Placeholder", + type: "text", + icon: "search", + hintMessage: "This is a hint text to help user.", + hintType: "hint", + variant: "dense", disabled: false, - value: "Lorem ipsum", + }, + parameters: { + controls: { exclude: ["id"] }, }, render: ({ ...args }) => , }; export const Default = {}; -export const WithIcon = { +export const Dense = { args: { - icon: "effects", + variant: "dense", }, }; -export const WithPlaceholder = { +export const Comfortable = { args: { - icon: "effects", - value: undefined, - placeholder: "Mixed", + variant: "comfortable", + }, +}; + +export const Seamless = { + args: { + variant: "seamless", }, }; diff --git a/frontend/src/app/main/ui/ds/controls/utilities/hint_message.cljs b/frontend/src/app/main/ui/ds/controls/utilities/hint_message.cljs new file mode 100644 index 0000000000..cff5571ebf --- /dev/null +++ b/frontend/src/app/main/ui/ds/controls/utilities/hint_message.cljs @@ -0,0 +1,35 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.ds.controls.utilities.hint-message + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [rumext.v2 :as mf])) + +(def ^:private schema::hint-message + [:map + [:message :string] + [:id :string] + [:type {:optional true} [:enum "hint" "error" "warning"]] + [:class {:optional true} :string]]) + +(mf/defc hint-message* + {::mf/props :obj + ::mf/schema schema::hint-message} + [{:keys [id class message type] :rest props}] + (let [type (d/nilv type :hint)] + [:> "div" {:class (dm/str class " " (stl/css-case + :hint-message true + :type-hint (= type "hint") + :type-warning (= type "warning") + :type-error (= type "error"))) + :aria-live (when (or (= type "warning") (= type "error")) "polite")} + (when (some? message) + [:span {:class (stl/css :hint-message-text) + :id (str id "-hint")} + message])])) diff --git a/frontend/src/app/main/ui/ds/controls/utilities/hint_message.scss b/frontend/src/app/main/ui/ds/controls/utilities/hint_message.scss new file mode 100644 index 0000000000..c80cf0e1a5 --- /dev/null +++ b/frontend/src/app/main/ui/ds/controls/utilities/hint_message.scss @@ -0,0 +1,27 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) KALEIDOS INC + +@use "../../typography.scss" as *; +@use "../../colors.scss" as *; + +.hint-message { + --hint-color: var(--color-foreground-secondary); + + @include use-typography("body-small"); + color: var(--hint-color); +} + +.type-hint { + --hint-color: var(--color-foreground-secondary); +} + +.type-warning { + --hint-color: var(--color-accent-warning); +} + +.type-error { + --hint-color: var(--color-foreground-error); +} diff --git a/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs b/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs new file mode 100644 index 0000000000..3266f15cbd --- /dev/null +++ b/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs @@ -0,0 +1,72 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.ds.controls.utilities.input-field + (:require-macros + [app.common.data.macros :as dm] + [app.main.style :as stl]) + (:require + [app.common.data :as d] + [app.main.constants :refer [max-input-length]] + [app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]] + [app.util.dom :as dom] + [rumext.v2 :as mf])) + +(def ^:private schema:input-field + [:map + [:class {:optional true} :string] + [:id :string] + [:icon {:optional true} + [:and :string [:fn #(contains? icon-list %)]]] + [:has-hint {:optional true} :boolean] + [:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]] + [:type {:optional true} :string] + [:max-length {:optional true} :int] + [:variant {:optional true} [:enum "seamless" "dense" "comfortable"]]]) + +(mf/defc input-field* + {::mf/props :obj + ::mf/forward-ref true + ::mf/schema schema:input-field} + [{:keys [id icon has-hint hint-type class type max-length variant children] :rest props} ref] + (let [input-ref (mf/use-ref) + type (d/nilv type "text") + variant (d/nilv variant "dense") + props (mf/spread-props props + {:class (stl/css-case + :input true + :input-with-icon (some? icon)) + :ref (or ref input-ref) + :aria-invalid (when (and has-hint + (= hint-type "error")) + "true") + :aria-describedby (when has-hint + (str id "-hint")) + :type (d/nilv type "text") + :id id + :max-length (d/nilv max-length max-input-length)}) + + on-icon-click + (mf/use-fn + (mf/deps ref) + (fn [_event] + (let [input-node (mf/ref-val ref)] + (dom/select-node input-node) + (dom/focus! input-node))))] + + [:div {:class (dm/str class " " (stl/css-case :input-wrapper true + :has-hint has-hint + :hint-type-hint (= hint-type "hint") + :hint-type-warning (= hint-type "warning") + :hint-type-error (= hint-type "error") + :variant-seamless (= variant "seamless") + :variant-dense (= variant "dense") + :variant-comfortable (= variant "comfortable")))} + (when (some? icon) + [:> icon* {:icon-id icon :class (stl/css :icon) :on-click on-icon-click}]) + [:> "input" props] + (when (some? children) + [:div {:class (stl/css :input-actions)} children])])) diff --git a/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss b/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss new file mode 100644 index 0000000000..4094006125 --- /dev/null +++ b/frontend/src/app/main/ui/ds/controls/utilities/input_field.scss @@ -0,0 +1,121 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) KALEIDOS INC + +@use "../../_borders.scss" as *; +@use "../../_sizes.scss" as *; +@use "../../typography.scss" as *; +@use "../../colors.scss" as *; + +.input-wrapper { + --input-bg-color: var(--color-background-tertiary); + --input-fg-color: var(--color-foreground-primary); + --input-icon-color: var(--color-foreground-secondary); + --input-outline-color: none; + --input-height: #{$sz-32}; + --input-margin: unset; + + display: inline-flex; + column-gap: var(--sp-xs); + align-items: center; + position: relative; + inline-size: 100%; + + background: var(--input-bg-color); + border-radius: $br-8; + padding: 0 var(--sp-s); + outline-offset: #{$b-1}; + outline: $b-1 solid var(--input-outline-color); + + &:hover { + --input-bg-color: var(--color-background-quaternary); + } + + &:has(*:focus-visible) { + --input-bg-color: var(--color-background-primary); + --input-outline-color: var(--color-accent-primary); + } + + &:has(*:disabled) { + --input-bg-color: var(--color-background-primary); + --input-outline-color: var(--color-background-quaternary); + } +} + +.variant-dense { + @include use-typography("body-small"); +} + +.variant-comfortable { + @include use-typography("body-medium"); +} + +.variant-seamless { + --input-bg-color: none; + --input-outline-color: none; + --input-height: auto; + --input-margin: 0; + + padding: 0; + border: none; + + &:hover { + --input-bg-color: none; + --input-outline-color: none; + } + + &:has(*:focus-visible) { + --input-bg-color: none; + --input-outline-color: none; + } +} + +.input { + margin: var(--input-margin); // remove settings from global css + padding: 0; + appearance: none; + height: var(--input-height); + border: none; + background: none; + inline-size: 100%; + + font-family: inherit; + font-size: inherit; + font-weight: inherit; + line-height: inherit; + + color: var(--input-fg-color); + + &:focus-visible { + outline: none; + } + + &::selection { + background: var(--color-accent-select); + } + + &::placeholder { + --input-fg-color: var(--color-foreground-secondary); + } + + &:is(:autofill, :autofill:hover, :autofill:focus, :autofill:active) { + -webkit-text-fill-color: var(--input-fg-color); + -webkit-background-clip: text; + background-clip: text; + caret-color: var(--input-bg-color); + } +} + +.input-with-icon { + margin-inline-start: var(--sp-xxs); +} + +.hint-type-error:has(.has-hint) { + --input-outline-color: var(--color-foreground-error); +} + +.icon { + color: var(--color-foreground-secondary); +} diff --git a/frontend/src/app/main/ui/ds/controls/utilities/label.cljs b/frontend/src/app/main/ui/ds/controls/utilities/label.cljs new file mode 100644 index 0000000000..1b5e06cedb --- /dev/null +++ b/frontend/src/app/main/ui/ds/controls/utilities/label.cljs @@ -0,0 +1,31 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.ds.controls.utilities.label + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data.macros :as dm] + [rumext.v2 :as mf])) + +(def ^:private schema::label + [:map + [:for :string] + [:is-optional {:optional true} :boolean] + [:class {:optional true} :string]]) + +(mf/defc label* + {::mf/props :obj + ::mf/schema schema::label} + [{:keys [class for is-optional children] :rest props}] + (let [is-optional (or is-optional false) + props (mf/spread-props props {:class (dm/str class " " (stl/css :label)) + :for for})] + [:> "label" props + [:* + (when (some? children) + [:span {:class (stl/css :label-text)} children]) + (when is-optional + [:span {:class (stl/css :label-optional)} "(Optional)"])]])) diff --git a/frontend/src/app/main/ui/ds/controls/utilities/label.scss b/frontend/src/app/main/ui/ds/controls/utilities/label.scss new file mode 100644 index 0000000000..572a1e3bb2 --- /dev/null +++ b/frontend/src/app/main/ui/ds/controls/utilities/label.scss @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) KALEIDOS INC + +@use "../../typography.scss" as *; +@use "../../colors.scss" as *; +@use "../../spacing.scss" as *; + +.label { + --label-color: var(--color-foreground-primary); + --label-optional-color: var(--color-foreground-secondary); + + @include use-typography("body-small"); + color: var(--label-color); + display: flex; + gap: var(--sp-xs); + align-items: center; +} + +.label-text { + color: var(--label-color); +} + +.label-optional { + color: var(--label-optional-color); +} diff --git a/frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens.cljs b/frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens_value.cljs similarity index 83% rename from frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens.cljs rename to frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens_value.cljs index 3746e32175..44e9379589 100644 --- a/frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens_value.cljs @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.main.ui.workspace.tokens.components.controls.input-tokens +(ns app.main.ui.workspace.tokens.components.controls.input-tokens-value (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] @@ -13,9 +13,8 @@ [app.main.ui.ds.controls.input :refer [input*]] [rumext.v2 :as mf])) -(def ^:private schema::input-tokens +(def ^:private schema::input-tokens-value [:map - [:id :string] [:label :string] [:placeholder {:optional true} :string] [:default-value {:optional true} [:maybe :string]] @@ -24,19 +23,20 @@ [:error {:optional true} :boolean] [:value {:optional true} :string]]) -(mf/defc input-tokens* +(mf/defc input-tokens-value* {::mf/props :obj ::mf/forward-ref true - ::mf/schema schema::input-tokens} - [{:keys [class label id max-length error value children] :rest props} ref] - (let [ref (or ref (mf/use-ref)) + ::mf/schema schema::input-tokens-value} + [{:keys [class label max-length 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 - :ref ref})] + :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] diff --git a/frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens.scss b/frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens_value.scss similarity index 97% rename from frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens.scss rename to frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens_value.scss index 71d0c00252..ea4ad5106f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens.scss +++ b/frontend/src/app/main/ui/workspace/tokens/components/controls/input_tokens_value.scss @@ -34,6 +34,7 @@ .input-wrapper { display: flex; + flex: 1; align-items: center; &:has(.input-swatch) { @@ -51,3 +52,7 @@ inset-inline-start: var(--sp-s); z-index: 2; } + +.input { + flex: 1; +} diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 606b070936..9339563e11 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -12,6 +12,7 @@ [app.common.data.macros :as dm] [app.common.files.tokens :as cft] [app.common.types.tokens-lib :as ctob] + [app.main.constants :refer [max-input-length]] [app.main.data.modal :as modal] [app.main.data.style-dictionary :as sd] [app.main.data.tinycolor :as tinycolor] @@ -23,6 +24,8 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.ds.buttons.button :refer [button*]] + [app.main.ui.ds.controls.input :refer [input*]] + [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*]] @@ -30,7 +33,7 @@ [app.main.ui.workspace.colorpicker :as colorpicker] [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]] [app.main.ui.workspace.tokens.components.controls.input-token-color-bullet :refer [input-token-color-bullet*]] - [app.main.ui.workspace.tokens.components.controls.input-tokens :refer [input-tokens*]] + [app.main.ui.workspace.tokens.components.controls.input-tokens-value :refer [input-tokens-value*]] [app.util.dom :as dom] [app.util.functions :as uf] [app.util.i18n :refer [tr]] @@ -277,6 +280,7 @@ token-name-ref (mf/use-var (:name token)) name-ref (mf/use-ref nil) name-errors (mf/use-state nil) + validate-name (mf/use-fn (mf/deps selected-set-tokens-tree) @@ -405,20 +409,22 @@ ;; Description description-ref (mf/use-var (:description token)) - description-errors (mf/use-state nil) + description-errors* (mf/use-state nil) + description-errors (deref description-errors*) + validate-descripion (mf/use-fn #(m/explain token-description-schema %)) on-update-description-debounced (mf/use-fn (uf/debounce (fn [e] (let [value (dom/get-target-val e) errors (validate-descripion value)] - (reset! description-errors errors))))) + (reset! description-errors* errors))))) on-update-description (mf/use-fn (mf/deps on-update-description-debounced) (fn [e] (reset! description-ref (dom/get-target-val e)) (on-update-description-debounced e))) - valid-description-field? (not @description-errors) + valid-description-field? (not description-errors) ;; Form disabled? (or (not valid-name-field?) @@ -523,26 +529,26 @@ [:div {:class (stl/css :input-row)} (let [token-title (str/lower (:title token-properties))] - [:> input-tokens* - {:id "token-name" - :placeholder (tr "workspace.token.enter-token-name", token-title) - :error (boolean @name-errors) - :auto-focus true - :label (tr "workspace.token.token-name") - :default-value @token-name-ref - :ref name-ref - :max-length 256 - :on-blur on-blur-name - :on-change on-update-name}]) + [:> input* {:id "token-name" + :label (tr "workspace.token.token-name") + :placeholder (tr "workspace.token.enter-token-name", token-title) + :max-length max-input-length + :variant "comfortable" + :auto-focus true + :default-value @token-name-ref + :ref name-ref + :on-blur on-blur-name + :on-change on-update-name}]) (for [error (->> (:errors @name-errors) (map #(-> (assoc @name-errors :errors [%]) - (me/humanize))))] - [:> text* {:as "p" - :key error - :typography "body-small" - :class (stl/css :error)} - error]) + (me/humanize))) + (map first))] + + [:> hint-message* {:key error + :message error + :type "error" + :id "token-name-hint"}]) (when (and warning-name-change? (= action "edit")) [:div {:class (stl/css :warning-name-change-notification-wrapper)} @@ -550,7 +556,7 @@ {:level :warning :appearance :ghost} (tr "workspace.token.warning-name-change")]])] [:div {:class (stl/css :input-row)} - [:> input-tokens* + [:> input-tokens-value* {:id "token-value" :placeholder (tr "workspace.token.token-value-enter") :label (tr "workspace.token.token-value") @@ -567,21 +573,14 @@ [:> ramp* {:color (some-> color (tinycolor/valid-color)) :on-change on-update-color}]) [:& token-value-or-errors {:result-or-errors token-resolve-result}]] - [:div {:class (stl/css :input-row)} - [:> input-tokens* - {:id "token-description" - :placeholder (tr "workspace.token.enter-token-description") - :label (tr "workspace.token.token-description") - :max-length 256 - :default-value @description-ref - :on-blur on-update-description - :on-change on-update-description}] - (when @description-errors - [:> text* {:as "p" - :typography "body-small" - :class (stl/css :error)} - (me/humanize @description-errors)])] + [:> input* {:label (tr "workspace.token.token-description") + :placeholder (tr "workspace.token.enter-token-description") + :max-length max-input-length + :variant "comfortable" + :default-value @description-ref + :on-blur on-update-description + :on-change on-update-description}]] [:div {:class (stl/css-case :button-row true :with-delete (= action "edit"))} diff --git a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs index 0b8c751e70..f6abbdbc21 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.logic.tokens :as clt] [app.common.types.tokens-lib :as ctob] + [app.main.constants :refer [max-input-length]] [app.main.data.event :as ev] [app.main.data.modal :as modal] [app.main.data.workspace.tokens.library-edit :as dwtl] @@ -19,11 +20,11 @@ [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.controls.combobox :refer [combobox*]] + [app.main.ui.ds.controls.input :refer [input*]] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as ic] [app.main.ui.ds.foundations.typography.heading :refer [heading*]] [app.main.ui.ds.foundations.typography.text :refer [text*]] [app.main.ui.icons :as i] - [app.main.ui.workspace.tokens.components.controls.input-tokens :refer [input-tokens*]] [app.main.ui.workspace.tokens.sets :as wts] [app.util.dom :as dom] [app.util.i18n :refer [tr]] @@ -202,14 +203,13 @@ :on-change on-update-group}]] [:div {:class (stl/css :group-input-wrapper)} - [:> input-tokens* {:id "theme-input" - :label (tr "workspace.token.label.theme") - :type "text" - :max-length 256 - :placeholder (tr "workspace.token.label.theme-placeholder") - :on-change on-update-name - :value (mf/ref-val theme-name-ref) - :auto-focus true}]]])) + [:> input* {:label (tr "workspace.token.label.theme") + :placeholder (tr "workspace.token.label.theme-placeholder") + :max-length max-input-length + :variant "comfortable" + :default-value (mf/ref-val theme-name-ref) + :auto-focus true + :on-change on-update-name}]]])) (mf/defc theme-modal-buttons* [{:keys [close-modal on-save-form disabled?] :as props}]