diff --git a/frontend/playwright/ui/specs/tokens.spec.js b/frontend/playwright/ui/specs/tokens.spec.js index 08f3b3f5a9..d9cd5ec584 100644 --- a/frontend/playwright/ui/specs/tokens.spec.js +++ b/frontend/playwright/ui/specs/tokens.spec.js @@ -997,13 +997,9 @@ test.describe("Tokens: Themes modal", () => { }; // Switch to reference tab and back to composite tab - const referenceTabButton = tokensUpdateCreateModal.getByRole("button", { - name: "Reference", - }); + const referenceTabButton = tokensUpdateCreateModal.getByTestId("reference-opt"); await referenceTabButton.click(); - const compositeTabButton = tokensUpdateCreateModal.getByRole("button", { - name: "Composite", - }); + const compositeTabButton = tokensUpdateCreateModal.getByTestId("composite-opt"); await compositeTabButton.click(); // Verify all values are preserved after switching tabs @@ -1053,9 +1049,7 @@ test.describe("Tokens: Themes modal", () => { await expect(submitButton).toBeDisabled(); // Switch to reference tab, should not be submittable either - const referenceTabButton = tokensUpdateCreateModal.getByRole("button", { - name: "Reference", - }); + const referenceTabButton = tokensUpdateCreateModal.getByTestId("reference-opt"); await referenceTabButton.click(); await expect(submitButton).toBeDisabled(); }); @@ -1076,9 +1070,7 @@ test.describe("Tokens: Themes modal", () => { const nameField = tokensUpdateCreateModal.getByLabel("Name"); await nameField.fill(newTokenTitle); - const referenceTabButton = tokensUpdateCreateModal.getByRole("button", { - name: "Reference", - }); + const referenceTabButton = tokensUpdateCreateModal.getByTestId("reference-opt"); referenceTabButton.click(); const referenceField = tokensUpdateCreateModal.getByLabel("Reference"); diff --git a/frontend/src/app/main/ui/components/radio_buttons.cljs b/frontend/src/app/main/ui/components/radio_buttons.cljs index 53e46834f5..85ec06a793 100644 --- a/frontend/src/app/main/ui/components/radio_buttons.cljs +++ b/frontend/src/app/main/ui/components/radio_buttons.cljs @@ -38,6 +38,7 @@ [:label {:html-for id + :data-testid id :title title :class (stl/css-case :radio-icon true diff --git a/frontend/src/app/main/ui/ds/_sizes.scss b/frontend/src/app/main/ui/ds/_sizes.scss index 22dc97b5ee..89db4c9700 100644 --- a/frontend/src/app/main/ui/ds/_sizes.scss +++ b/frontend/src/app/main/ui/ds/_sizes.scss @@ -24,6 +24,7 @@ $sz-252: px2rem(252); $sz-284: px2rem(284); $sz-318: px2rem(318); $sz-352: px2rem(352); +$sz-384: px2rem(384); $sz-400: px2rem(400); $sz-480: px2rem(480); $sz-500: px2rem(500); 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 index 4f546ade66..23ca7eab25 100644 --- a/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs +++ b/frontend/src/app/main/ui/ds/controls/utilities/input_field.cljs @@ -12,12 +12,14 @@ [app.common.data :as d] [app.main.constants :refer [max-input-length]] [app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]] + [app.main.ui.ds.tooltip :refer [tooltip*]] [app.util.dom :as dom] [rumext.v2 :as mf])) (def ^:private schema:input-field [:map [:class {:optional true} :string] + [:aria-label {:optional true} [:maybe :string]] [:id :string] [:icon {:optional true} [:maybe [:and :string [:fn #(contains? icon-list %)]]]] @@ -35,10 +37,13 @@ [{:keys [id icon class type has-hint hint-type max-length variant - slot-start slot-end] :rest props} ref] + slot-start slot-end + aria-label] :rest props} ref] (let [input-ref (mf/use-ref) type (d/nilv type "text") variant (d/nilv variant "dense") + tooltip-id (mf/use-id) + props (mf/spread-props props {:class (stl/css-case :input true @@ -49,10 +54,10 @@ "true") :aria-describedby (when has-hint (str id "-hint")) + :aria-labelledby tooltip-id :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) @@ -72,7 +77,11 @@ (when (some? slot-start) slot-start) (when (some? icon) - [:> icon* {:icon-id icon :class (stl/css :icon) :on-click on-icon-click}]) + (if aria-label + [:> tooltip* {:content aria-label + :id tooltip-id} + [:> icon* {:icon-id icon :class (stl/css :icon) :on-click on-icon-click}]] + [:> icon* {:icon-id icon :class (stl/css :icon) :on-click on-icon-click}])) [:> "input" props] (when (some? slot-end) slot-end)])) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 665f5b6eb1..5e3849adbd 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -260,6 +260,7 @@ (def ^:icon text-uppercase (icon-xref :text-uppercase)) (def ^:icon thumbnail (icon-xref :thumbnail)) (def ^:icon tick (icon-xref :tick)) +(def ^:icon tokens (icon-xref :tokens)) (def ^:icon to-corner (icon-xref :to-corner)) (def ^:icon to-curve (icon-xref :to-curve)) (def ^:icon tree (icon-xref :tree)) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs index e58cd31b3f..161b6cb3b8 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs @@ -24,6 +24,7 @@ [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] [app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.controls.input :refer [input*]] @@ -31,11 +32,12 @@ [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.foundations.typography.heading :refer [heading*]] [app.main.ui.ds.notifications.context-notification :refer [context-notification*]] + [app.main.ui.icons :as deprecated-icon] [app.main.ui.workspace.colorpicker :as colorpicker] [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]] [app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]] [app.main.ui.workspace.tokens.management.create.input-token-color-bullet :refer [input-token-color-bullet*]] - [app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-tokens-value* + [app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token* token-value-hint*]] [app.util.dom :as dom] [app.util.functions :as uf] @@ -594,7 +596,7 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va :custom-input-token-value-props custom-input-token-value-props :token-resolve-result token-resolve-result :clear-resolve-value clear-resolve-value}] - [:> input-tokens-value* + [:> input-token* {:placeholder placeholder :label label :default-value default-value @@ -737,7 +739,7 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va (on-external-update-value color-value))))] [:* - [:> input-tokens-value* + [:> input-token* {:placeholder placeholder :label label :default-value default-value @@ -800,8 +802,8 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va :on-close on-close-font-selector :full-size true}]])) -(mf/defc font-picker* - [{:keys [default-value input-ref on-blur on-update-value on-external-update-value token-resolve-result]}] +(mf/defc font-picker-combobox* + [{:keys [default-value label aria-label input-ref on-blur on-update-value on-external-update-value token-resolve-result placeholder]}] (let [font* (mf/use-state (fonts/find-font-family default-value)) font (deref font*) set-font (mf/use-fn @@ -847,9 +849,10 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va :variant "action" :type "button"}])] [:* - [:> input-tokens-value* - {:placeholder (tr "workspace.tokens.token-font-family-value-enter") - :label (tr "workspace.tokens.token-font-family-value") + [:> input-token* + {:placeholder (or placeholder (tr "workspace.tokens.token-font-family-value-enter")) + :label label + :aria-label aria-label :default-value default-value :ref input-ref :on-blur on-blur @@ -872,7 +875,7 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va (ctt/join-font-family value))))] [:> form* (mf/spread-props props {:token (when token (update token :value ctt/join-font-family)) - :custom-input-token-value font-picker* + :custom-input-token-value font-picker-combobox* :on-value-resolve on-value-resolve :validate-token validate-font-family-token})])) @@ -896,23 +899,29 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va (def ^:private typography-inputs #(d/ordered-map - :font-size - {:label "Font Size" - :placeholder (tr "workspace.tokens.token-value-enter")} :font-family {:label (tr "workspace.tokens.token-font-family-value") + :icon i/text-font-family :placeholder (tr "workspace.tokens.token-font-family-value-enter")} + :font-size + {:label "Font Size" + :icon i/text-font-size + :placeholder (tr "workspace.tokens.token-value-enter")} :font-weight {:label "Font Weight" + :icon i/text-font-weight :placeholder (tr "workspace.tokens.font-weight-value-enter")} :letter-spacing {:label "Letter Spacing" - :placeholder (tr "workspace.tokens.token-value-enter")} + :icon i/text-letterspacing + :placeholder (tr "workspace.tokens.letter-spacing-value-enter-composite")} :text-case {:label "Text Case" + :icon i/text-mixed :placeholder (tr "workspace.tokens.text-case-value-enter")} :text-decoration {:label "Text Decoration" + :icon i/text-underlined :placeholder (tr "workspace.tokens.text-decoration-value-enter")})) (mf/defc typography-value-inputs* @@ -920,7 +929,7 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va (let [typography-inputs (mf/use-memo typography-inputs) errors-by-key (sd/collect-typography-errors token-resolve-result)] [:div {:class (stl/css :nested-input-row)} - (for [[k {:keys [label placeholder]}] typography-inputs] + (for [[k {:keys [label placeholder icon]}] typography-inputs] (let [value (get default-value k) token-resolve-result (-> {:resolved-value (let [v (get-in token-resolve-result [:resolved-value k])] @@ -950,8 +959,8 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va :class (stl/css :input-row)} (case k :font-family - [:> font-picker* - {:label label + [:> font-picker-combobox* + {:aria-label label :placeholder placeholder :input-ref input-ref :default-value (when value (ctt/join-font-family value)) @@ -959,19 +968,21 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va :on-update-value on-change :on-external-update-value on-external-update-value :token-resolve-result (when (seq token-resolve-result) token-resolve-result)}] - [:> input-tokens-value* - {:label label + [:> input-token* + {:aria-label label :placeholder placeholder :default-value value :on-blur on-blur + :icon icon :on-change on-change :token-resolve-result (when (seq token-resolve-result) token-resolve-result)}])]))])) (mf/defc typography-reference-input* [{:keys [default-value on-blur on-update-value token-resolve-result]}] - [:> input-tokens-value* - {:label "Reference" - :placeholder "Reference" + [:> input-token* + {:aria-label (tr "labels.reference") + :placeholder (tr "workspace.tokens.reference-composite") + :icon i/text-typography :default-value (when (ctt/typography-composite-token-reference? default-value) default-value) :on-blur on-blur :on-change on-update-value @@ -1018,12 +1029,26 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va input-props (mf/spread-props props {:default-value default-value :on-update-value on-update-reference-value})] - [:div {:class (stl/css :nested-input-row)} - [:button {:on-click on-toggle-tab :type "button"} - (if reference-tab-active? "Composite" "Reference")] - (if reference-tab-active? - [:> typography-reference-input* input-props] - [:> typography-value-inputs* input-props])])) + [:div {:class (stl/css :typography-inputs-row)} + [:div {:class (stl/css :title-bar)} + [:div {:class (stl/css :title)} + (tr "labels.typography")] + [:& radio-buttons {:class (stl/css :listing-options) + :selected (if reference-tab-active? "reference" "composite") + :on-change on-toggle-tab + :name "reference-composite-tab"} + [:& radio-button {:icon deprecated-icon/layers + :value "composite" + :title (tr "workspace.tokens.individual-tokens") + :id "composite-opt"}] + [:& radio-button {:icon deprecated-icon/tokens + :value "reference" + :title (tr "workspace.tokens.use-reference") + :id "reference-opt"}]]] + [:div {:class (stl/css :typography-inputs)} + (if reference-tab-active? + [:> typography-reference-input* input-props] + [:> typography-value-inputs* input-props])]])) (mf/defc typography-form* [{:keys [token] :rest props}] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/form.scss b/frontend/src/app/main/ui/workspace/tokens/management/create/form.scss index 25d0d70198..56b6e8b105 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/form.scss +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/form.scss @@ -4,10 +4,12 @@ // // Copyright (c) KALEIDOS INC -@import "refactor/common-refactor.scss"; +@use "../../../../ds/typography.scss" as t; +@use "../../../../ds/_sizes.scss" as *; +@use "../../../../ds/_borders.scss" as *; .form-wrapper { - width: $s-384; + width: $sz-384; position: relative; } @@ -15,8 +17,8 @@ display: grid; grid-template-columns: auto auto; justify-content: end; - gap: $s-12; - padding-block-start: $s-8; + gap: var(--sp-m); + padding-block-start: var(--sp-s); } .with-delete { @@ -30,28 +32,50 @@ .token-rows { display: flex; flex-direction: column; - gap: $s-16; + gap: var(--sp-l); } .input-row { display: flex; flex-direction: column; - gap: $s-4; + gap: var(--sp-xs); } .nested-input-row { display: flex; flex-direction: column; - gap: $s-12; + gap: var(--sp-m); +} + +.typography-inputs-row { + display: flex; + flex-direction: column; + gap: var(--sp-m); +} + +.typography-inputs { + border-inline-start: $b-1 solid var(--color-accent-primary-muted); + padding-inline-start: var(--sp-l); +} + +.title-bar { + display: grid; + grid-template-columns: 1fr auto; +} +.title { + @include t.use-typography("body-small"); + color: var(--color-foreground-primary); + display: flex; + align-items: center; } .warning-name-change-notification-wrapper { - margin-block-start: $s-16; + margin-block-start: var(--sp-l); } .error { - padding: $s-4 $s-6; - margin-bottom: 0; + padding: var(--sp-xs) $sz-6; + margin-block-end: 0; color: var(--status-color-error-500); } @@ -77,5 +101,5 @@ inset: 0; // This padding from the modal should be shared as a variable // Need to set this or the font-select will cause scroll - bottom: $s-32; + bottom: var(--sp-xxxl); } diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs index c0de51d709..8502e981a9 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs @@ -19,9 +19,10 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) -(def ^:private schema::input-tokens-value +(def ^:private schema::input-token [:map - [:label :string] + [:label {:optional true} [:maybe :string]] + [:aria-label {:optional true} [:maybe :string]] [:placeholder {:optional true} :string] [:value {:optional true} [:maybe :string]] [:class {:optional true} :string] @@ -54,10 +55,9 @@ :class (stl/css-case :resolved-value (not (or empty-message? (seq warnings) (seq errors)))) :type type}])) -(mf/defc input-tokens-value* - {::mf/props :obj - ::mf/forward-ref true - ::mf/schema schema::input-tokens-value} +(mf/defc input-token* + {::mf/forward-ref true + ::mf/schema schema::input-token} [{:keys [class label placeholder value icon slot-start token-resolve-result] :rest props} ref] (let [error (not (nil? (:errors token-resolve-result))) id (mf/use-id) @@ -75,7 +75,8 @@ [:* [:div {:class (dm/str class " " (stl/css-case :wrapper true :input-error error))} - [:> label* {:for id} label] + (when label + [:> label* {:for id} label]) [:> input-field* props]] (when token-resolve-result [:> token-value-hint* {:result token-resolve-result}])])) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index b9bae08d47..f1e39655a9 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2378,6 +2378,10 @@ msgstr "Profile" msgid "labels.projects" msgstr "Projects" +#:src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "labels.reference" +msgstr "Reference" + #: src/app/main/data/common.cljs:83 msgid "labels.refresh" msgstr "Refresh" @@ -2554,6 +2558,10 @@ msgstr "Themes" msgid "labels.tutorials" msgstr "Tutorials" +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "labels.typography" +msgstr "Typography" + #: src/app/main/data/workspace/tokens/errors.cljs:93 msgid "labels.unknown-error" msgstr "Unknown error" @@ -7739,6 +7747,22 @@ msgstr "Enter: none | uppercase | lowercase | capitalize or {alias}" msgid "workspace.tokens.text-decoration-value-enter" msgstr "Enter text decoration: none | underline | strike-through" +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "workspace.tokens.letter-spacing-value-enter-composite" +msgstr "Add letter spacing or {alias}" + +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "workspace.tokens.individual-tokens" +msgstr "Use individual tokens" + +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "workspace.tokens.use-reference" +msgstr "Use a reference" + +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "workspace.tokens.reference-composite" +msgstr "Enter a token typography alias" + #: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:130 msgid "workspace.tokens.theme-name" msgstr "Theme %s" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 9ec2b74e40..bab450d86e 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -2380,6 +2380,10 @@ msgstr "Perfil" msgid "labels.projects" msgstr "Proyectos" +#:src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "labels.reference" +msgstr "Referencia" + #: src/app/main/ui/dashboard/sidebar.cljs:909, src/app/main/ui/settings/sidebar.cljs:129, src/app/main/ui/workspace/main_menu.cljs:132 msgid "labels.release-notes" msgstr "Notas de versión" @@ -2552,6 +2556,10 @@ msgstr "Temas" msgid "labels.tutorials" msgstr "Tutoriales" +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "labels.typography" +msgstr "Tipografía" + #: src/app/main/data/workspace/tokens/errors.cljs:93 msgid "labels.unknown-error" msgstr "Error desconocido" @@ -7655,6 +7663,18 @@ msgstr "Introduce: none | uppercase | lowercase | capitalize o {alias}" msgid "workspace.tokens.text-decoration-value-enter" msgstr "Introduce text decoration: none | underline | strike-through" +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "workspace.tokens.letter-spacing-value-enter-composite" +msgstr "Introduce letter spacing o {alias}" + +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "workspace.tokens.individual-tokens" +msgstr "Usa tokens individuales" + +#: src/app/main/ui/workspace/tokens/management/create/form.cljs +msgid "workspace.tokens.use-reference" +msgstr "Usa una referencia" + #: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:130 msgid "workspace.tokens.theme-name" msgstr "Tema %s"