diff --git a/frontend/playwright/ui/specs/tokens.spec.js b/frontend/playwright/ui/specs/tokens.spec.js index c2d2796caf..941e901f71 100644 --- a/frontend/playwright/ui/specs/tokens.spec.js +++ b/frontend/playwright/ui/specs/tokens.spec.js @@ -1239,8 +1239,12 @@ test.describe("Tokens: Apply token", () => { // Fill in the shadow values const offsetXInput = firstShadowFields.getByLabel("X"); const offsetYInput = firstShadowFields.getByLabel("Y"); - const blurInput = firstShadowFields.getByLabel("Blur"); - const spreadInput = firstShadowFields.getByLabel("Spread"); + const blurInput = firstShadowFields.getByRole("textbox", { + name: "Blur", + }); + const spreadInput = firstShadowFields.getByRole("textbox", { + name: "Spread", + }); await offsetXInput.fill("2"); await offsetYInput.fill("2"); @@ -1261,9 +1265,10 @@ test.describe("Tokens: Apply token", () => { await valueSaturationSelector.click({ position: { x: 50, y: 50 } }); // Verify that a color value was set - const colorInput = firstShadowFields.getByLabel("Color"); - const firstColorValue = await colorInput.inputValue(); - await expect(firstColorValue).toMatch(/^rgb(.*)$/); + const colorInput = firstShadowFields.getByRole("textbox", { + name: "Color", + }); + await expect(colorInput).toHaveValue(/^rgb(.*)$/); // Wait for validation to complete await expect( @@ -1281,11 +1286,15 @@ test.describe("Tokens: Apply token", () => { const firstShadowFields = tokensUpdateCreateModal.getByTestId( "shadow-input-fields-0", ); - const colorInput = firstShadowFields.getByLabel("Color"); + const colorInput = firstShadowFields.getByRole("textbox", { + name: "Color", + }); const firstColorValue = await colorInput.inputValue(); // User adds a second shadow - const addButton = firstShadowFields.getByTestId("shadow-add-button-0"); + const addButton = tokensUpdateCreateModal.getByRole("button", { + name: "Add Shadow", + }); await addButton.click(); const secondShadowFields = tokensUpdateCreateModal.getByTestId( @@ -1294,8 +1303,7 @@ test.describe("Tokens: Apply token", () => { await expect(secondShadowFields).toBeVisible(); // User adds a third shadow - const addButton2 = secondShadowFields.getByTestId("shadow-add-button-1"); - await addButton2.click(); + await addButton.click(); const thirdShadowFields = tokensUpdateCreateModal.getByTestId( "shadow-input-fields-2", @@ -1305,9 +1313,15 @@ test.describe("Tokens: Apply token", () => { // User adds values for the third shadow const thirdOffsetXInput = thirdShadowFields.getByLabel("X"); const thirdOffsetYInput = thirdShadowFields.getByLabel("Y"); - const thirdBlurInput = thirdShadowFields.getByLabel("Blur"); - const thirdSpreadInput = thirdShadowFields.getByLabel("Spread"); - const thirdColorInput = thirdShadowFields.getByLabel("Color"); + const thirdBlurInput = thirdShadowFields.getByRole("textbox", { + name: "Blur", + }); + const thirdSpreadInput = thirdShadowFields.getByRole("textbox", { + name: "Spread", + }); + const thirdColorInput = thirdShadowFields.getByRole("textbox", { + name: "Color", + }); await thirdOffsetXInput.fill("10"); await thirdOffsetYInput.fill("10"); @@ -1316,15 +1330,13 @@ test.describe("Tokens: Apply token", () => { await thirdColorInput.fill("#FF0000"); // User removes the 2nd shadow - const removeButton2 = secondShadowFields.getByTestId( - "shadow-remove-button-1", - ); + const removeButton2 = secondShadowFields.getByRole("button", { + name: "Remove Shadow", + }); await removeButton2.click(); - // Verify second shadow is removed - await expect( - secondShadowFields.getByTestId("shadow-add-button-3"), - ).not.toBeVisible(); + // Verify that we have only two shadow fields + await expect(thirdShadowFields).not.toBeVisible(); // Verify that the first shadow kept its values const firstOffsetXValue = await firstShadowFields @@ -1334,13 +1346,13 @@ test.describe("Tokens: Apply token", () => { .getByLabel("Y") .inputValue(); const firstBlurValue = await firstShadowFields - .getByLabel("Blur") + .getByRole("textbox", { name: "Blur" }) .inputValue(); const firstSpreadValue = await firstShadowFields - .getByLabel("Spread") + .getByRole("textbox", { name: "Spread" }) .inputValue(); const firstColorValueAfter = await firstShadowFields - .getByLabel("Color") + .getByRole("textbox", { name: "Color" }) .inputValue(); await expect(firstOffsetXValue).toBe("2"); @@ -1349,7 +1361,7 @@ test.describe("Tokens: Apply token", () => { await expect(firstSpreadValue).toBe("0"); await expect(firstColorValueAfter).toBe(firstColorValue); - // Verify that the third shadow (now second) kept its values + // Verify that the second kept its values (after shadow 3) // After removing index 1, the third shadow becomes the second shadow at index 1 const newSecondShadowFields = tokensUpdateCreateModal.getByTestId( "shadow-input-fields-1", @@ -1363,13 +1375,13 @@ test.describe("Tokens: Apply token", () => { .getByLabel("Y") .inputValue(); const secondBlurValue = await newSecondShadowFields - .getByLabel("Blur") + .getByRole("textbox", { name: "Blur" }) .inputValue(); const secondSpreadValue = await newSecondShadowFields - .getByLabel("Spread") + .getByRole("textbox", { name: "Spread" }) .inputValue(); const secondColorValue = await newSecondShadowFields - .getByLabel("Color") + .getByRole("textbox", { name: "Color" }) .inputValue(); await expect(secondOffsetXValue).toBe("10"); @@ -1386,7 +1398,9 @@ test.describe("Tokens: Apply token", () => { const newSecondShadowFields = tokensUpdateCreateModal.getByTestId( "shadow-input-fields-1", ); - const colorInput = firstShadowFields.getByLabel("Color"); + const colorInput = firstShadowFields.getByRole("textbox", { + name: "Color", + }); const firstColorValue = await colorInput.inputValue(); // Switch to reference tab @@ -1414,13 +1428,13 @@ test.describe("Tokens: Apply token", () => { .getByLabel("Y") .inputValue(); const restoredFirstBlur = await firstShadowFields - .getByLabel("Blur") + .getByRole("textbox", { name: "Blur" }) .inputValue(); const restoredFirstSpread = await firstShadowFields - .getByLabel("Spread") + .getByRole("textbox", { name: "Spread" }) .inputValue(); const restoredFirstColor = await firstShadowFields - .getByLabel("Color") + .getByRole("textbox", { name: "Color" }) .inputValue(); await expect(restoredFirstOffsetX).toBe("2"); @@ -1437,13 +1451,13 @@ test.describe("Tokens: Apply token", () => { .getByLabel("Y") .inputValue(); const restoredSecondBlur = await newSecondShadowFields - .getByLabel("Blur") + .getByRole("textbox", { name: "Blur" }) .inputValue(); const restoredSecondSpread = await newSecondShadowFields - .getByLabel("Spread") + .getByRole("textbox", { name: "Spread" }) .inputValue(); const restoredSecondColor = await newSecondShadowFields - .getByLabel("Color") + .getByRole("textbox", { name: "Color" }) .inputValue(); await expect(restoredSecondOffsetX).toBe("10"); diff --git a/frontend/src/app/main/data/style_dictionary.cljs b/frontend/src/app/main/data/style_dictionary.cljs index 5005dedeed..e1fd76978e 100644 --- a/frontend/src/app/main/data/style_dictionary.cljs +++ b/frontend/src/app/main/data/style_dictionary.cljs @@ -389,35 +389,42 @@ (defn- parse-sd-token-shadow-value "Parses shadow value and validates it." [value] - (cond - ;; Reference value (string) - (string? value) {:value value} + (let [missing-references + (when (string? value) + (seq (cto/find-token-value-references value)))] + (cond + missing-references + {:errors [(wte/error-with-value :error.style-dictionary/missing-reference missing-references)] + :references missing-references} + + (string? value) + {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-shadow value)]} ;; Empty value - (nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]} + (nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]} ;; Invalid value - (not (js/Array.isArray value)) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]} + (not (js/Array.isArray value)) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]} ;; Array of shadows - :else - (let [converted (js->clj value :keywordize-keys true) + :else + (let [converted (js->clj value :keywordize-keys true) ;; Parse each shadow with its index - parsed-shadows (map-indexed - (fn [idx shadow-map] - (parse-single-shadow shadow-map idx)) - converted) + parsed-shadows (map-indexed + (fn [idx shadow-map] + (parse-single-shadow shadow-map idx)) + converted) ;; Collect all errors from all shadows - all-errors (mapcat :errors parsed-shadows) + all-errors (mapcat :errors parsed-shadows) ;; Collect all values from shadows that have values - all-values (into [] (keep :value parsed-shadows))] + all-values (into [] (keep :value parsed-shadows))] - (if (seq all-errors) - {:errors all-errors - :value all-values} - {:value all-values})))) + (if (seq all-errors) + {:errors all-errors + :value all-values} + {:value all-values}))))) (defn collect-shadow-errors [token shadow-index] (group-by :shadow-key diff --git a/frontend/src/app/main/data/workspace/tokens/errors.cljs b/frontend/src/app/main/data/workspace/tokens/errors.cljs index 0be73b58a8..ac733c52ac 100644 --- a/frontend/src/app/main/data/workspace/tokens/errors.cljs +++ b/frontend/src/app/main/data/workspace/tokens/errors.cljs @@ -108,6 +108,10 @@ {:error/code :error.style-dictionary/invalid-token-value-shadow-spread :error/fn #(tr "workspace.tokens.shadow-spread-range")} + :error.style-dictionary/invalid-token-value-shadow + {:error/code :error.style-dictionary/invalid-token-value-shadow + :error/fn #(tr "workspace.tokens.invalid-token-value-shadow" %)} + :error/unknown {:error/code :error/unknown :error/fn #(tr "labels.unknown-error")}}) diff --git a/frontend/src/app/main/ui/ds/controls/select.cljs b/frontend/src/app/main/ui/ds/controls/select.cljs index a8572dd8b9..32676bcd36 100644 --- a/frontend/src/app/main/ui/ds/controls/select.cljs +++ b/frontend/src/app/main/ui/ds/controls/select.cljs @@ -118,6 +118,7 @@ (mf/use-fn (mf/deps disabled) (fn [event] + (dom/prevent-default event) (dom/stop-propagation event) (when-not disabled (swap! is-open* not)))) 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 4a8bcb1554..1b3c9514c7 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 @@ -53,10 +53,15 @@ "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)}) + + props (if (and aria-label (not (some? icon))) + (mf/spread-props props + {:aria-label aria-label}) + (mf/spread-props props + {:aria-labelledby tooltip-id})) inside-class (stl/css-case :input-wrapper true :has-hint has-hint :hint-type-hint (= hint-type "hint") diff --git a/frontend/src/app/main/ui/ds/utilities/swatch.scss b/frontend/src/app/main/ui/ds/utilities/swatch.scss index 4743517165..a9eb3b6936 100644 --- a/frontend/src/app/main/ui/ds/utilities/swatch.scss +++ b/frontend/src/app/main/ui/ds/utilities/swatch.scss @@ -15,7 +15,7 @@ } .swatch { - --border-color: var(--color-background-quaternary); + --border-color: var(--color-accent-primary-muted); --border-radius: #{$br-4}; --border-color-active: var(--color-foreground-primary); --border-color-active-inset: var(--color-background-primary); diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/border_radius.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/border_radius.cljs index d3f5842390..2f7351062e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/border_radius.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/border_radius.cljs @@ -168,7 +168,9 @@ [:div {:class (stl/css :token-rows)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)} - (tr "workspace.tokens.create-token" token-type)] + (if (= action "edit") + (tr "workspace.tokens.edit-token" token-type) + (tr "workspace.tokens.create-token" token-type))] [:div {:class (stl/css :input-row)} [:> fc/form-input* {:id "token-name" diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/color.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/color.cljs index e953d8206e..6b4bcbe8d6 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/color.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/color.cljs @@ -51,6 +51,7 @@ #(not (cft/token-name-path-exists? % tokens-tree))]]] [:value [::sm/text {:error/fn token-value-error-fn}]] + [:color-result {:optional true} ::sm/any] [:description {:optional true} [:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]] @@ -168,7 +169,9 @@ [:div {:class (stl/css :token-rows)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)} - (tr "workspace.tokens.create-token" token-type)] + (if (= action "edit") + (tr "workspace.tokens.edit-token" token-type) + (tr "workspace.tokens.create-token" token-type))] [:div {:class (stl/css :input-row)} [:> fc/form-input* {:id "token-name" diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/combobox_token_fonts.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/combobox_token_fonts.cljs index 712b244ed3..b49fe4437b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/combobox_token_fonts.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/combobox_token_fonts.cljs @@ -24,7 +24,6 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) - (defn- resolve-value [tokens prev-token value] (let [token @@ -117,7 +116,6 @@ props (mf/spread-props props {:on-change on-change - ;; TODO: Review this value vs default-value :value (or value "") :hint-message (:message hint) :slot-end font-selector-button @@ -242,7 +240,6 @@ props (mf/spread-props props {:on-change on-change - ;; TODO: Review this value vs default-value :value (or value "") :hint-message (:message hint) :slot-end font-selector-button diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/dimensions.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/dimensions.cljs index fa0c64b501..ff5f2b23b3 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/dimensions.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/dimensions.cljs @@ -167,7 +167,9 @@ :on-submit on-submit} [:div {:class (stl/css :token-rows)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)} - (tr "workspace.tokens.create-token" token-type)] + (if (= action "edit") + (tr "workspace.tokens.edit-token" token-type) + (tr "workspace.tokens.create-token" token-type))] [:div {:class (stl/css :input-row)} [:> fc/form-input* {:id "token-name" diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/font_family.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/font_family.cljs index 7e104e8f0f..014905a77d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/font_family.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/font_family.cljs @@ -165,7 +165,9 @@ [:div {:class (stl/css :token-rows)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)} - (tr "workspace.tokens.create-token" token-type)] + (if (= action "edit") + (tr "workspace.tokens.edit-token" token-type) + (tr "workspace.tokens.create-token" token-type))] [:div {:class (stl/css :input-row)} [:> fc/form-input* {:id "token-name" 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 49e9c1e202..45f27baa45 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 @@ -40,6 +40,7 @@ [app.main.ui.workspace.tokens.management.create.font-family :as font-family] [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-token* token-value-hint*]] + [app.main.ui.workspace.tokens.management.create.shadow :as shadow] [app.main.ui.workspace.tokens.management.create.text-case :as text-case] [app.main.ui.workspace.tokens.management.create.typography :as typography] [app.util.dom :as dom] @@ -133,8 +134,9 @@ [check-token-empty-value check-self-reference]) (defn- default-validate-token - "Validates a token by confirming a list of `validator` predicates and resolving the token using `tokens` with StyleDictionary. - Returns rx stream of either a valid resolved token or an errors map. + "Validates a token by confirming a list of `validator` predicates and + resolving the token using `tokens` with StyleDictionary. Returns rx + stream of either a valid resolved token or an errors map. Props: token-name, token-value, token-description: Values from the form inputs @@ -232,25 +234,26 @@ (default-validate-token)))) (defn- validate-shadow-token - [{:keys [token-value] :as props}] + [{:keys [token-value] :as params}] (cond ;; Entering form without a value - show no error just resolve nil (nil? token-value) (rx/of nil) ;; Validate refrence string - (cto/shadow-composite-token-reference? token-value) (default-validate-token props) + (cto/shadow-composite-token-reference? token-value) (default-validate-token params) ;; Validate composite token :else - (-> props - (update :token-value (fn [value] - (->> (or value []) - (mapv (fn [shadow] - (d/update-when shadow :inset #(cond - (boolean? %) % - (= "true" %) true - :else false))))))) - (assoc :validators [check-empty-shadow-token - check-shadow-token-self-reference]) - (default-validate-token)))) + (let [params (-> params + (update :token-value (fn [value] + (->> (or value []) + (mapv (fn [shadow] + (d/update-when shadow :inset #(cond + (boolean? %) % + (= "true" %) true + :else false))))))) + (assoc :validators [check-empty-shadow-token + check-shadow-token-self-reference]))] + + (default-validate-token params)))) (defn- use-debonced-resolve-callback "Resolves a token values using `StyleDictionary`. @@ -1440,12 +1443,13 @@ :tokens-tree-in-selected-set tokens-tree-in-selected-set :token token}) font-family-props (mf/spread-props props {:validate-token validate-font-family-token}) - typography-props (mf/spread-props props {:validate-token validate-typography-token})] + typography-props (mf/spread-props props {:validate-token validate-typography-token}) + shadow-props (mf/spread-props props {:validate-token validate-shadow-token})] (case token-type :color [:> color/form* props] :typography [:> typography/form* typography-props] - :shadow [:> shadow-form* props] + :shadow [:> shadow/form* shadow-props] :font-family [:> font-family/form* font-family-props] :text-case [:> text-case/form* props] :text-decoration [:> text-decoration-form* props] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/form_color_input_token.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/form_color_input_token.cljs index c6c667281e..d232a27de9 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/form_color_input_token.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/form_color_input_token.cljs @@ -7,12 +7,15 @@ (ns app.main.ui.workspace.tokens.management.create.form-color-input-token (:require-macros [app.main.style :as stl]) (:require + [app.common.colors :as color] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.types.color :as cl] [app.common.types.tokens-lib :as ctob] [app.main.data.style-dictionary :as sd] [app.main.data.tinycolor :as tinycolor] + [app.main.data.workspace.tokens.format :as dwtf] + [app.main.refs :as refs] [app.main.ui.ds.controls.input :refer [input*]] [app.main.ui.ds.utilities.swatch :refer [swatch*]] [app.main.ui.forms :as fc] @@ -22,6 +25,7 @@ [app.util.forms :as fm] [app.util.i18n :refer [tr]] [beicon.v2.core :as rx] + [cuerdas.core :as str] [rumext.v2 :as mf])) (defn- resolve-value @@ -115,13 +119,29 @@ value (get-in @form [:data input-name] "") - hex (if (tinycolor/valid-color value) - (tinycolor/->hex-string (tinycolor/valid-color value)) - "#8f9da3") + color-resolved + (get-in @form [:data :color-result] "") - alpha (if (tinycolor/valid-color value) - (tinycolor/alpha (tinycolor/valid-color value)) - 1) + + valid-color (or (tinycolor/valid-color value) + (tinycolor/valid-color color-resolved)) + + profile (mf/deref refs/profile) + + default-bullet-color + (case (:theme profile) + "light" + color/background-quaternary-light + color/background-quaternary) + hex + (if valid-color + (tinycolor/->hex-string (tinycolor/valid-color valid-color)) + default-bullet-color) + + alpha + (if (tinycolor/valid-color valid-color) + (tinycolor/alpha (tinycolor/valid-color valid-color)) + 1) resolve-stream (mf/with-memo [token] @@ -222,9 +242,11 @@ (if error (do (swap! form assoc-in [:extra-errors input-name] {:message error}) + (swap! form assoc-in [:data :color-result] "") (reset! hint* {:message error :type "error"})) - (let [message (tr "workspace.tokens.resolved-value" value)] + (let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))] (swap! form update :extra-errors dissoc input-name) + (swap! form assoc-in [:data :color-result] value) (reset! hint* {:message message :type "hint"}))))))))] (fn [] @@ -234,4 +256,180 @@ [:> input* props] (when color-ramp-open? - [:> ramp* {:color value :on-change on-change-value}])])) \ No newline at end of file + [:> ramp* {:color value :on-change on-change-value}])])) + +(defn- on-composite-indexed-input-token-change + ([form field index value] + (on-composite-indexed-input-token-change form field index value false)) + ([form field index value trim?] + (letfn [(clean-errors [errors] + (-> errors + (dissoc field) + (not-empty)))] + (swap! form (fn [state] + (-> state + (assoc-in [:data :value :shadows index field] (if trim? (str/trim value) value)) + (update :errors clean-errors) + (update :extra-errors clean-errors))))))) + +(mf/defc color-input-indexed* + [{:keys [name tokens token index] :rest props}] + + (let [form (mf/use-ctx fc/context) + input-name name + + error + (get-in @form [:errors :value :shadows index input-name]) + + value + (get-in @form [:data :value :shadows index input-name] "") + + color-resolved + (get-in @form [:data :value :shadows index :color-result] "") + + valid-color (or (tinycolor/valid-color value) + (tinycolor/valid-color color-resolved)) + profile (mf/deref refs/profile) + + default-bullet-color + (case (:theme profile) + "light" + color/background-quaternary-light + color/background-quaternary) + + hex + (if valid-color + (tinycolor/->hex-string (tinycolor/valid-color valid-color)) + default-bullet-color) + + alpha + (if (tinycolor/valid-color valid-color) + (tinycolor/alpha (tinycolor/valid-color valid-color)) + 1) + + resolve-stream + (mf/with-memo [token] + (if-let [value (get-in token [:value :shadows index input-name])] + (rx/behavior-subject value) + (rx/subject))) + + hint* + (mf/use-state {}) + + hint + (deref hint*) + + color-ramp-open* (mf/use-state false) + color-ramp-open? (deref color-ramp-open*) + + on-click-swatch + (mf/use-fn + (mf/deps color-ramp-open?) + (fn [] + (let [open? (not color-ramp-open?)] + (reset! color-ramp-open* open?)))) + + swatch + (mf/html + [:> swatch* + {:background {:color hex :opacity alpha} + :show-tooltip false + :data-testid "token-form-color-bullet" + :class (stl/css :slot-start) + :on-click on-click-swatch}]) + + on-change-value + (mf/use-fn + (mf/deps resolve-stream input-name value index) + (fn [hex alpha] + (let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field + prev-input-color (some-> value + (tinycolor/valid-color)) + ;; If the input is a reference we will take the format from the computed value + prev-computed-color (when-not prev-input-color + (some-> value (tinycolor/valid-color))) + prev-format (some-> (or prev-input-color prev-computed-color) + (tinycolor/color-format)) + to-rgba? (and + (< alpha 1) + (or (= prev-format "hex") (not prev-format))) + to-hex? (and (not prev-format) (= alpha 1)) + format (cond + to-rgba? "rgba" + to-hex? "hex" + prev-format prev-format + :else "hex") + color-value (-> (tinycolor/valid-color hex) + (tinycolor/set-alpha (or alpha 1)) + (tinycolor/->string format))] + (when (not= value color-value) + (on-composite-indexed-input-token-change form input-name index color-value true) + (rx/push! resolve-stream color-value))))) + + on-change + (mf/use-fn + (mf/deps resolve-stream input-name index) + (fn [event] + (let [raw-value (-> event dom/get-target dom/get-input-value) + value (if (tinycolor/hex-without-hash-prefix? raw-value) + (dm/str "#" raw-value) + raw-value)] + (on-composite-indexed-input-token-change form input-name index value true) + (rx/push! resolve-stream value)))) + + props + (mf/spread-props props {:on-change on-change + :value (or value "") + :hint-message (:message hint) + :slot-start swatch + :hint-type (:type hint)}) + + props + (if error + (mf/spread-props props {:hint-type "error" + :hint-message (:message error)}) + props)] + + (mf/with-effect [resolve-stream tokens token input-name index] + (let [subs (->> resolve-stream + (rx/debounce 300) + (rx/mapcat (partial resolve-value tokens token)) + (rx/map (fn [result] + (d/update-when result :error + (fn [error] + (assoc error :message ((:error/fn error) (:error/value error))))))) + + (rx/subs! + (fn [{:keys [error value]}] + (cond + (and error (str/empty? (:error/value error))) + (do + (swap! form update-in [:errors :value :shadows index] dissoc input-name) + (swap! form update-in [:data :value :shadows index] dissoc input-name) + (swap! form assoc-in [:data :value :shadows index :color-result] "") + (swap! form update :extra-errors dissoc :value) + (reset! hint* {})) + + (some? error) + (let [error' (:message error)] + (swap! form assoc-in [:extra-errors :value :shadows index input-name] {:message error'}) + (swap! form assoc-in [:data :value :shadows index :color-result] "") + (reset! hint* {:message error' :type "error"})) + + :else + (let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value)) + input-value (get-in @form [:data :value :shadows index input-name] "")] + (swap! form update :errors dissoc :value) + (swap! form update :extra-errors dissoc :value) + (swap! form assoc-in [:data :value :shadows index :color-result] (dwtf/format-token-value value)) + (if (= input-value (str value)) + (reset! hint* {}) + (reset! hint* {:message message :type "hint"})))))))] + (fn [] + (rx/dispose! subs)))) + + [:* + [:> input* props] + + (when color-ramp-open? + [:> ramp* {:color value :on-change on-change-value}])])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/form_input_token.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/form_input_token.cljs index 90c681beb6..d2e9314881 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/form_input_token.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/form_input_token.cljs @@ -88,6 +88,7 @@ props)] (mf/with-effect [resolve-stream tokens token input-name] + (let [subs (->> resolve-stream (rx/debounce 300) (rx/mapcat (partial resolve-value tokens token)) @@ -125,7 +126,7 @@ (update :errors clean-errors) (update :extra-errors clean-errors))))))) -(mf/defc token-composite-value-input* +(mf/defc form-input-token-composite* [{:keys [name tokens token] :rest props}] (let [form (mf/use-ctx fc/context) @@ -202,6 +203,108 @@ input-value (get-in @form [:data :value input-name] "")] (swap! form update :errors dissoc :value) (swap! form update :extra-errors dissoc :value) + (if (= input-value (str value)) + (reset! hint* {}) + (reset! hint* {:message message :type "hint"}))))) + (fn [cause] + (js/console.log "MUU" cause))))] + (fn [] + (rx/dispose! subs)))) + + [:> input* props])) + +(defn- on-composite-indexed-input-token-change + ([form field index value] + (on-composite-indexed-input-token-change form field index value false)) + ([form field index value trim?] + (letfn [(clean-errors [errors] + (-> errors + (dissoc field) + (not-empty)))] + (swap! form (fn [state] + (-> state + (assoc-in [:data :value :shadows index field] (if trim? (str/trim value) value)) + (update :errors clean-errors) + (update :extra-errors clean-errors))))))) + +(mf/defc form-input-token-indexed* + [{:keys [name tokens token index] :rest props}] + + (let [form (mf/use-ctx fc/context) + input-name name + + error + (get-in @form [:errors :value :shadows index input-name]) + + value-from-form + (get-in @form [:data :value :shadows index input-name] "") + + resolve-stream + (mf/with-memo [token index input-name] + (if-let [value (get-in token [:value :shadows index input-name])] + (rx/behavior-subject value) + (rx/subject))) + + hint* + (mf/use-state {}) + + hint + (deref hint*) + + on-change + (mf/use-fn + (mf/deps resolve-stream input-name index) + (fn [event] + (let [value (-> event dom/get-target dom/get-input-value)] + (on-composite-indexed-input-token-change form input-name index value true) + (rx/push! resolve-stream value)))) + + props + (mf/spread-props props {:on-change on-change + :value value-from-form + :variant "comfortable" + :hint-message (:message hint) + :hint-type (:type hint)}) + props + (if error + (mf/spread-props props {:hint-type "error" + :hint-message (:message error)}) + props) + + props + (if (and (not error) (= input-name :reference)) + (mf/spread-props props {:hint-formated true}) + props)] + + (mf/with-effect [resolve-stream tokens token input-name index] + (let [subs (->> resolve-stream + (rx/debounce 300) + (rx/mapcat (partial resolve-value tokens token)) + (rx/map (fn [result] + (d/update-when result :error + (fn [error] + (assoc error :message ((:error/fn error) (:error/value error))))))) + + (rx/subs! + (fn [{:keys [error value]}] + (cond + (and error (str/empty? (:error/value error))) + (do + (swap! form update-in [:errors :value :shadows index] dissoc input-name) + (swap! form update-in [:data :value :shadows index] dissoc input-name) + (swap! form update :extra-errors dissoc :value) + (reset! hint* {})) + + (some? error) + (let [error' (:message error)] + (swap! form assoc-in [:extra-errors :value :shadows index input-name] {:message error'}) + (reset! hint* {:message error' :type "error"})) + + :else + (let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value)) + input-value (get-in @form [:data :value :shadows index input-name] "")] + (swap! form update :errors dissoc :value) + (swap! form update :extra-errors dissoc :value) (if (= input-value (str value)) (reset! hint* {}) (reset! hint* {:message message :type "hint"})))))))] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/form_select_token.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/form_select_token.cljs new file mode 100644 index 0000000000..23565accc0 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/form_select_token.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.workspace.tokens.management.create.form-select-token + (:require + [app.main.ui.ds.controls.select :refer [select*]] + [app.main.ui.forms :as fc] + [rumext.v2 :as mf])) + +(mf/defc select-composite* + [{:keys [name index] :rest props}] + (let [form (mf/use-ctx fc/context) + input-name name + + value + (get-in @form [:data :value :shadows index input-name] false) + + on-change + (mf/use-fn + (mf/deps input-name) + (fn [type] + (let [is-inner? (= type "inner")] + (swap! form assoc-in [:data :value :shadows index input-name] is-inner?)))) + + props (mf/spread-props props {:default-selected (if value "inner" "drop") + :variant "ghost" + :on-change on-change})] + [:> select* props])) \ No newline at end of file diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/shadow.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/shadow.cljs new file mode 100644 index 0000000000..b4958a517d --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/shadow.cljs @@ -0,0 +1,483 @@ +;; 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.workspace.tokens.management.create.shadow + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data :as d] + [app.common.files.tokens :as cft] + [app.common.schema :as sm] + [app.common.types.token :as cto] + [app.common.types.tokens-lib :as ctob] + [app.main.constants :refer [max-input-length]] + [app.main.data.modal :as modal] + [app.main.data.workspace.tokens.application :as dwta] + [app.main.data.workspace.tokens.library-edit :as dwtl] + [app.main.data.workspace.tokens.propagation :as dwtp] + [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.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.forms :as forms] + [app.main.ui.hooks :as hooks] + [app.main.ui.workspace.tokens.management.create.form-color-input-token :refer [color-input-indexed*]] + [app.main.ui.workspace.tokens.management.create.form-input-token :refer [form-input-token-composite* form-input-token-indexed*]] + [app.main.ui.workspace.tokens.management.create.form-select-token :refer [select-composite*]] + [app.util.dom :as dom] + [app.util.forms :as fm] + [app.util.i18n :refer [tr]] + [app.util.keyboard :as k] + [beicon.v2.core :as rx] + [cuerdas.core :as str] + [rumext.v2 :as mf])) + +;; TODO: Review if code has this implementation +(defn vec-remove + "remove elem in coll" + [pos coll] + (into (subvec coll 0 pos) (subvec coll (inc pos)))) + +;; TODO: Put this in a common file, and use ir for shadow option menu as well +(def ^:private default-token-shadow + {:offsetX "4" + :offsetY "4" + :blur "4" + :spread "0"}) + +(defn get-subtoken + [token index prop] + (let [value (get-in token [:value :shadows index prop])] + (d/without-nils + {:type (if (= prop :color) :color :number) + :value value}))) + +(mf/defc shadow-formset* + [{:keys [index token tokens remove-shadow-block show-button] :as props}] + (let [inset-token (get-subtoken token index :inset) + inset-token (hooks/use-equal-memo inset-token) + + color-token (get-subtoken token index :color) + color-token (hooks/use-equal-memo color-token) + + offset-x-token (get-subtoken token index :offsetX) + offset-x-token (hooks/use-equal-memo offset-x-token) + + offset-y-token (get-subtoken token index :offsetY) + offset-y-token (hooks/use-equal-memo offset-y-token) + + blur-token (get-subtoken token index :blur) + blur-token (hooks/use-equal-memo blur-token) + + spread-token (get-subtoken token index :spreadX) + spread-token (hooks/use-equal-memo spread-token) + + on-button-click + (mf/use-fn + (mf/deps index) + (fn [event] + (remove-shadow-block index event)))] + + [:div {:class (stl/css :shadow-block) + :data-testid (str "shadow-input-fields-" index)} + [:div {:class (stl/css :select-wrapper)} + [:> select-composite* {:options [{:id "drop" :label "drop shadow" :icon i/drop-shadow} + {:id "inner" :label "inner shadow" :icon i/inner-shadow}] + :aria-label (tr "workspace.tokens.shadow-inset") + :token inset-token + :tokens tokens + :index index + :name :inset}] + (when show-button + [:> icon-button* {:variant "ghost" + :type "button" + :aria-label (tr "workspace.tokens.shadow-remove-shadow") + :on-click on-button-click + :icon i/remove}])] + [:div {:class (stl/css :inputs-wrapper)} + [:div {:class (stl/css :input-row)} + [:> color-input-indexed* + {:placeholder (tr "workspace.tokens.token-value-enter") + :aria-label (tr "workspace.tokens.color") + :name :color + :token color-token + :index index + :tokens tokens}]] + + [:div {:class (stl/css :input-row)} + [:> form-input-token-indexed* + {:aria-label (tr "workspace.tokens.shadow-x") + :icon i/character-x + :placeholder (tr "workspace.tokens.shadow-x") + :name :offsetX + :token offset-x-token + :index index + :tokens tokens}]] + + [:div {:class (stl/css :input-row)} + [:> form-input-token-indexed* + {:aria-label (tr "workspace.tokens.shadow-y") + :icon i/character-y + :placeholder (tr "workspace.tokens.shadow-y") + :name :offsetY + :token offset-y-token + :index index + :tokens tokens}]] + + [:div {:class (stl/css :input-row)} + [:> form-input-token-indexed* + {:aria-label (tr "workspace.tokens.shadow-blur") + :placeholder (tr "workspace.tokens.shadow-blur") + :name :blur + :slot-start (mf/html [:span {:class (stl/css :visible-label)} "Blur:"]) + :token blur-token + :index index + :tokens tokens}]] + + [:div {:class (stl/css :input-row)} + [:> form-input-token-indexed* + {:aria-label (tr "workspace.tokens.shadow-spread") + :placeholder (tr "workspace.tokens.shadow-spread") + :name :spread + :slot-start (mf/html [:span {:class (stl/css :visible-label)} "Spread:"]) + :token spread-token + :index index + :tokens tokens}]]]])) + +(mf/defc composite-form* + [{:keys [token tokens remove-shadow-block] :as props}] + (let [form + (mf/use-ctx forms/context) + + length + (-> form deref :data :value :shadows count)] + + (for [index (range length)] + [:> shadow-formset* {:key index + :index index + :token token + :tokens tokens + :remove-shadow-block remove-shadow-block + :show-button (> length 1)}]))) + +(mf/defc reference-form* + [{:keys [token tokens] :as props}] + [:div {:class (stl/css :input-row-reference)} + [:> form-input-token-composite* + {:placeholder (tr "workspace.tokens.reference-composite-shadow") + :aria-label (tr "labels.reference") + :icon i/drop-shadow + :name :reference + :token token + :tokens tokens}]]) + +(defn- make-schema + [tokens-tree active-tab] + (sm/schema + [:and + [:map + [:name + [:and + [:string {:min 1 :max 255 + :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}] + (sm/update-properties cto/token-name-ref assoc + :error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error"))) + [:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))} + #(not (cft/token-name-path-exists? % tokens-tree))]]] + + [:value + [:map + [:shadows {:optinal true} + [:vector + [:map + ;; TODO: cambiar offsetX por offset-x + [:offsetX {:optional true} [:maybe :string]] + [:offsetY {:optional true} [:maybe :string]] + [:blur {:optional true} [:maybe :string]] + [:spread {:optional true} [:maybe :string]] + [:color {:optional true} [:maybe :string]] + [:color-result {:optional true} ::sm/any] + [:inset {:optional true} [:maybe :boolean]]]]] + (if (= active-tab :reference) + [:reference {:optional false} ::sm/text] + [:reference {:optional true} [:maybe :string]])]] + + [:description {:optional true} + [:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]] + + [:fn {:error/field [:value :reference] + :error/fn #(tr "workspace.tokens.self-reference")} + (fn [{:keys [name value]}] + (let [reference (get value :reference)] + (if (and reference name) + (not (cto/token-value-self-reference? name reference)) + true)))] + + [:fn {:error/fn (fn [_] "Must be a valid shadow or reference") + :error/field :value} + (fn [{:keys [value]}] + (let [reference (get value :reference) + ref-valid? (and reference (not (str/blank? reference))) + + shadows (get value :shadows) + ;; To be a valid shadow it must contain one on each valid values + valid-composite-shadow? + (and (seq shadows) + (every? + (fn [{:keys [offsetX offsetY blur spread color]}] + (and (not (str/blank? offsetX)) + (not (str/blank? offsetY)) + (not (str/blank? blur)) + (not (str/blank? spread)) + (not (str/blank? color)))) + shadows))] + + (or ref-valid? valid-composite-shadow?)))]])) + +(mf/defc form* + [{:keys [token validate-token action is-create selected-token-set-id tokens-tree-in-selected-set] :as props}] + + (let [active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite)) + active-tab (deref active-tab*) + token + (mf/with-memo [token] + (or token + (if-let [value (get token :value)] + (cond + (string? value) + {:value {:reference value + :shadows []} + :type :shadow} + + (vector? value) + {:value {:reference nil + :shadows value} + :type :shadow}) + {:type :shadow + :value {:reference nil + :shadows [default-token-shadow]}}))) + + token-type + (get token :type) + + token-properties + (dwta/get-token-properties token) + + token-title (str/lower (:title token-properties)) + + ;; TODO: review + show-button* (mf/use-state false) + show-button (deref show-button*) + + tokens + (mf/deref refs/workspace-active-theme-sets-tokens) + + tokens + (mf/with-memo [tokens token] + ;; Ensure that the resolved value uses the currently editing token + ;; even if the name has been overriden by a token with the same name + ;; in another set below. + (cond-> tokens + (and (:name token) (:value token)) + (assoc (:name token) token))) + + schema + (mf/with-memo [tokens-tree-in-selected-set active-tab] + (make-schema tokens-tree-in-selected-set active-tab)) + + initial + (mf/with-memo [token] + (let [raw-value (:value token) + + value + (cond + (string? raw-value) + {:reference raw-value + :shadows []} + + (vector? raw-value) + {:reference nil + :shadows raw-value} + + :else + {:reference nil + :shadows [default-token-shadow]})] + + {:name (:name token "") + :description (:description token "") + :value value})) + + form + (fm/use-form :schema schema + :initial initial) + + warning-name-change? + (not= (get-in @form [:data :name]) + (:name initial)) + + on-toggle-tab + (mf/use-fn + (mf/deps) + (fn [new-tab] + (let [new-tab (keyword new-tab)] + (reset! active-tab* new-tab)))) + + on-cancel + (mf/use-fn + (fn [e] + (dom/prevent-default e) + (modal/hide!))) + + on-delete-token + (mf/use-fn + (mf/deps selected-token-set-id token) + (fn [e] + (dom/prevent-default e) + (modal/hide!) + (st/emit! (dwtl/delete-token selected-token-set-id (:id token))))) + + handle-key-down-delete + (mf/use-fn + (mf/deps on-delete-token) + (fn [e] + (when (or (k/enter? e) (k/space? e)) + (on-delete-token e)))) + + handle-key-down-cancel + (mf/use-fn + (mf/deps on-cancel) + (fn [e] + (when (or (k/enter? e) (k/space? e)) + (on-cancel e)))) + + on-add-shadow-block + (mf/use-fn + (fn [] + (swap! form update-in [:data :value :shadows] conj default-token-shadow))) + + remove-shadow-block + (mf/use-fn + (fn [index event] + (dom/prevent-default event) + (swap! form update-in [:data :value :shadows] #(vec-remove index %)))) + + on-submit + (mf/use-fn + (mf/deps validate-token token tokens token-type active-tab) + (fn [form _event] + (let [name (get-in @form [:clean-data :name]) + description (get-in @form [:clean-data :description]) + value (get-in @form [:clean-data :value])] + + (->> (validate-token {:token-value (if (= active-tab :reference) + (:reference value) + (:shadows value)) + :token-name name + :token-description description + :prev-token token + :tokens tokens}) + (rx/subs! + (fn [valid-token] + (st/emit! + (if is-create + (dwtl/create-token (ctob/make-token {:name name + :type token-type + :value (:value valid-token) + :description description})) + + (dwtl/update-token (:id token) + {:name name + :value (:value valid-token) + :description description})) + (dwtp/propagate-workspace-tokens) + (modal/hide))))))))] + + [:> forms/form* {:class (stl/css :form-wrapper) + :form form + :on-submit on-submit} + [:div {:class (stl/css :token-rows)} + + [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)} + (if (= action "edit") + (tr "workspace.tokens.edit-token" token-type) + (tr "workspace.tokens.create-token" token-type))] + + [:div {:class (stl/css :input-row)} + [:> forms/form-input* {:id "token-name" + :name :name + :label (tr "workspace.tokens.token-name") + :placeholder (tr "workspace.tokens.enter-token-name" token-title) + :max-length max-input-length + :variant "comfortable" + :auto-focus true}] + + (when (and warning-name-change? (= action "edit")) + [:div {:class (stl/css :warning-name-change-notification-wrapper)} + [:> context-notification* + {:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])] + + [:div {:class (stl/css :title-bar)} + [:div {:class (stl/css :title)} (tr "labels.shadow")] + [:> icon-button* {:variant "ghost" + :type "button" + :aria-label (tr "workspace.tokens.shadow-add-shadow") + :on-click on-add-shadow-block + :icon i/add}] + [:& radio-buttons {:class (stl/css :listing-options) + :selected (d/name active-tab) + :on-change on-toggle-tab + :name "reference-composite-tab"} + [:& radio-button {:icon i/layers + :value "composite" + :title (tr "workspace.tokens.individual-tokens") + :id "composite-opt"}] + [:& radio-button {:icon i/tokens + :value "reference" + :title (tr "workspace.tokens.use-reference") + :id "reference-opt"}]]] + + (if (= active-tab :composite) + [:> composite-form* {:token token + :tokens tokens + :remove-shadow-block remove-shadow-block + :show-button show-button}] + + [:> reference-form* {:token token + :tokens tokens}]) + + [:div {:class (stl/css :input-row)} + [:> forms/form-input* {:id "token-description" + :name :description + :label (tr "workspace.tokens.token-description") + :placeholder (tr "workspace.tokens.token-description") + :max-length max-input-length + :variant "comfortable" + :is-optional true}]] + + [:div {:class (stl/css-case :button-row true + :with-delete (= action "edit"))} + (when (= action "edit") + [:> button* {:on-click on-delete-token + :on-key-down handle-key-down-delete + :class (stl/css :delete-btn) + :type "button" + :icon i/delete + :variant "secondary"} + (tr "labels.delete")]) + + [:> button* {:on-click on-cancel + :on-key-down handle-key-down-cancel + :type "button" + :id "token-modal-cancel" + :variant "secondary"} + (tr "labels.cancel")] + + [:> forms/form-submit* {:variant "primary" + :on-submit on-submit} + (tr "labels.save")]]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/shadow.scss b/frontend/src/app/main/ui/workspace/tokens/management/create/shadow.scss new file mode 100644 index 0000000000..f54695fcae --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/shadow.scss @@ -0,0 +1,95 @@ +// 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 "ds/typography.scss" as t; +@use "ds/_sizes.scss" as *; +@use "ds/_borders.scss" as *; + +.form-wrapper { + width: $sz-384; + position: relative; +} + +.token-rows { + display: flex; + flex-direction: column; + gap: var(--sp-l); +} + +.inputs-wrapper { + display: flex; + flex-direction: column; + gap: var(--sp-m); + border-inline-start: $b-1 solid var(--color-accent-primary-muted); + padding-inline-start: var(--sp-m); +} + +.input-row { + position: relative; + display: flex; + flex-direction: column; + gap: var(--sp-xs); +} + +.input-row-reference { + position: relative; + display: flex; + flex-direction: column; + gap: var(--sp-xs); + border-inline-start: $b-1 solid var(--color-accent-primary-muted); + padding-inline-start: var(--sp-m); +} + +.title-bar { + display: grid; + grid-template-columns: 1fr auto auto; + gap: var(--sp-xs); +} + +.title { + @include t.use-typography("body-small"); + color: var(--color-foreground-primary); + display: flex; + align-items: center; +} + +.form-modal-title { + @include t.use-typography("headline-medium"); + color: var(--color-foreground-primary); + display: flex; + align-items: center; +} + +.button-row { + display: grid; + grid-template-columns: auto auto; + justify-content: end; + gap: var(--sp-m); + padding-block-start: var(--sp-s); +} + +.with-delete { + grid-template-columns: 1fr auto auto; +} + +.warning-name-change-notification-wrapper { + margin-block-start: var(--sp-l); +} + +.delete-btn { + justify-self: start; +} + +.visible-label { + @include t.use-typography("headline-small"); + color: var(--color-foreground-secondary); + line-height: $sz-32; +} + +.select-wrapper { + display: grid; + grid-template-columns: 1fr auto; +} diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/text_case.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/text_case.cljs index 82a2d5ba2c..ed80b5b46f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/text_case.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/text_case.cljs @@ -168,7 +168,9 @@ [:div {:class (stl/css :token-rows)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)} - (tr "workspace.tokens.create-token" token-type)] + (if (= action "edit") + (tr "workspace.tokens.edit-token" token-type) + (tr "workspace.tokens.create-token" token-type))] [:div {:class (stl/css :input-row)} [:> fc/form-input* {:id "token-name" diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/typography.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/typography.cljs index ce39829c71..d94479d60a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/typography.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/typography.cljs @@ -26,7 +26,7 @@ [app.main.ui.ds.notifications.context-notification :refer [context-notification*]] [app.main.ui.forms :as forms] [app.main.ui.workspace.tokens.management.create.combobox-token-fonts :refer [font-picker-composite-combobox*]] - [app.main.ui.workspace.tokens.management.create.form-input-token :refer [token-composite-value-input*]] + [app.main.ui.workspace.tokens.management.create.form-input-token :refer [form-input-token-composite*]] [app.util.dom :as dom] [app.util.forms :as fm] [app.util.i18n :refer [tr]] @@ -97,7 +97,7 @@ :token font-family-sub-token :tokens tokens}]] [:div {:class (stl/css :input-row)} - [:> token-composite-value-input* + [:> form-input-token-composite* {:aria-label "Font Size" :icon i/text-font-size :placeholder (tr "workspace.tokens.font-size-value-enter") @@ -105,7 +105,7 @@ :token font-size-sub-token :tokens tokens}]] [:div {:class (stl/css :input-row)} - [:> token-composite-value-input* + [:> form-input-token-composite* {:aria-label "Font Weight" :icon i/text-font-weight :placeholder (tr "workspace.tokens.font-weight-value-enter") @@ -113,7 +113,7 @@ :token font-weight-sub-token :tokens tokens}]] [:div {:class (stl/css :input-row)} - [:> token-composite-value-input* + [:> form-input-token-composite* {:aria-label "Line Height" :icon i/text-lineheight :placeholder (tr "workspace.tokens.line-height-value-enter") @@ -121,7 +121,7 @@ :token line-height-sub-token :tokens tokens}]] [:div {:class (stl/css :input-row)} - [:> token-composite-value-input* + [:> form-input-token-composite* {:aria-label "Letter Spacing" :icon i/text-letterspacing :placeholder (tr "workspace.tokens.letter-spacing-value-enter-composite") @@ -129,7 +129,7 @@ :token letter-spacing-sub-token :tokens tokens}]] [:div {:class (stl/css :input-row)} - [:> token-composite-value-input* + [:> form-input-token-composite* {:aria-label "Text Case" :icon i/text-mixed :placeholder (tr "workspace.tokens.text-case-value-enter") @@ -137,7 +137,7 @@ :token text-case-sub-token :tokens tokens}]] [:div {:class (stl/css :input-row)} - [:> token-composite-value-input* + [:> form-input-token-composite* {:aria-label "Text Decoration" :icon i/text-underlined :placeholder (tr "workspace.tokens.text-decoration-value-enter") @@ -148,7 +148,7 @@ (mf/defc reference-form* [{:keys [token tokens] :as props}] [:div {:class (stl/css :input-row)} - [:> token-composite-value-input* + [:> form-input-token-composite* {:placeholder (tr "workspace.tokens.reference-composite") :aria-label (tr "labels.reference") :icon i/text-typography @@ -357,7 +357,9 @@ [:div {:class (stl/css :token-rows)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)} - (tr "workspace.tokens.create-token" token-type)] + (if (= action "edit") + (tr "workspace.tokens.edit-token" token-type) + (tr "workspace.tokens.create-token" token-type))] [:div {:class (stl/css :input-row)} [:> forms/form-input* {:id "token-name" diff --git a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs index d496e88f52..8dd73d5fce 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs @@ -43,6 +43,7 @@ :stroke-width "stroke-size" :dimensions "expand" :sizing "expand" + :shadow "drop-shadow" "add")) (mf/defc token-group* diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 0a5c4a727f..775deb2de8 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -7753,6 +7753,10 @@ msgstr "Invalid token value: only none, underline and strike-through are accepte msgid "workspace.tokens.invalid-token-value-typography" msgstr "Invalid value: must reference a composite typography token." +#: src/app/main/data/workspace/tokens/errors.cljs +msgid "workspace.tokens.invalid-token-value-shadow" +msgstr "Invalid value: must reference a composite shadow token." + #: src/app/main/data/workspace/tokens/errors.cljs:61, src/app/main/data/workspace/tokens/errors.cljs:73, src/app/main/data/workspace/tokens/errors.cljs:77 msgid "workspace.tokens.invalid-value" msgstr "Invalid token value: %s" @@ -7870,6 +7874,10 @@ msgstr "Reference is not valid or is not in any active set" msgid "workspace.tokens.reference-composite" msgstr "Enter a token typography alias" +#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775 +msgid "workspace.tokens.reference-composite-shadow" +msgstr "Enter a token shadow alias" + #: src/app/main/ui/workspace/tokens/style_dictionary.cljs #, unused msgid "workspace.tokens.reference-error" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index e58facf94b..c0ed714dad 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -7674,6 +7674,10 @@ msgstr "Tipo de sombra no válida: solo se aceptan 'innerShadow' o 'dropShadow'" msgid "workspace.tokens.invalid-token-value-typography" msgstr "Valor no válido: debe hacer referencia a un token tipográfico compuesto." +#: src/app/main/data/workspace/tokens/errors.cljs +msgid "workspace.tokens.invalid-token-value-shadow" +msgstr "Valor no válido: debe hacer referencia a un token de sombra compuesto." + #: src/app/main/data/workspace/tokens/errors.cljs:61, src/app/main/data/workspace/tokens/errors.cljs:73, src/app/main/data/workspace/tokens/errors.cljs:77 msgid "workspace.tokens.invalid-value" msgstr "Valor de token no válido: %s"