♻️ Refactor composite token UI (#7287)

* ♻️ Refactor composite token UI

* 🐛 Fix comments
This commit is contained in:
Eva Marco
2025-09-10 12:16:39 +02:00
committed by GitHub
parent e1935fb3fb
commit a503f8ae93
10 changed files with 158 additions and 60 deletions

View File

@@ -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");

View File

@@ -38,6 +38,7 @@
[:label {:html-for id
:data-testid id
:title title
:class (stl/css-case
:radio-icon true

View File

@@ -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);

View File

@@ -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)]))

View File

@@ -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))

View File

@@ -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")]
[: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])]))
[:> typography-value-inputs* input-props])]]))
(mf/defc typography-form*
[{:keys [token] :rest props}]

View File

@@ -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);
}

View File

@@ -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}])]))

View File

@@ -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"

View File

@@ -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"