Compare commits

...

3 Commits

Author SHA1 Message Date
Eva Marco
bdcbde37c3 ♻️ Replace offsetx and offsety names 2025-12-03 10:43:00 +01:00
Eva Marco
81c6cb52ca ♻️ Rename files and components 2025-12-03 09:49:34 +01:00
Eva Marco
1dc274e6e2 ♻️ Replace shadow form 2025-12-02 14:30:53 +01:00
31 changed files with 1341 additions and 370 deletions

View File

@@ -1575,10 +1575,10 @@ Will return a value that matches this schema:
(if (map? shadow)
(let [legacy-shadow-type (get "type" shadow)]
(-> shadow
(set/rename-keys {"x" :offsetX
"offsetX" :offsetX
"y" :offsetY
"offsetY" :offsetY
(set/rename-keys {"x" :offset-x
"offsetX" :offset-x
"y" :offset-y
"offsetY" :offset-y
"blur" :blur
"spread" :spread
"color" :color
@@ -1589,7 +1589,7 @@ Will return a value that matches this schema:
(= "false" %) false
(= legacy-shadow-type "innerShadow") true
:else false))
(select-keys [:offsetX :offsetY :blur :spread :color :inset])))
(select-keys [:offset-x :offset-y :blur :spread :color :inset])))
shadow))]
(cond
;; Reference value - keep as string
@@ -1860,8 +1860,8 @@ Will return a value that matches this schema:
(mapv (fn [shadow]
(if (map? shadow)
(-> shadow
(set/rename-keys {:offsetX "offsetX"
:offsetY "offsetY"
(set/rename-keys {:offset-x "offsetX"
:offset-y "offsetY"
:blur "blur"
:spread "spread"
:color "color"

View File

@@ -1897,15 +1897,15 @@
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-single")]
(t/is (some? token))
(t/is (= :shadow (:type token)))
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset false}]
(t/is (= [{:offset-x "0", :offset-y "2px", :blur "4px", :spread "0", :color "#000", :inset false}]
(:value token)))))
(t/testing "multiple shadow token"
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-multiple")]
(t/is (some? token))
(t/is (= :shadow (:type token)))
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset true}
{:offsetX "0", :offsetY "8px", :blur "16px", :spread "0", :color "#000", :inset true}]
(t/is (= [{:offset-x "0", :offset-y "2px", :blur "4px", :spread "0", :color "#000", :inset true}
{:offset-x "0", :offset-y "8px", :blur "16px", :spread "0", :color "#000", :inset true}]
(:value token)))))
(t/testing "shadow token with reference"
@@ -1918,7 +1918,7 @@
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-with-type")]
(t/is (some? token))
(t/is (= :shadow (:type token)))
(t/is (= [{:offsetX "0", :offsetY "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}]
(t/is (= [{:offset-x "0", :offset-y "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}]
(:value token)))))
(t/testing "shadow token with description"
@@ -1937,14 +1937,14 @@
(ctob/make-token
{:name "shadow.single"
:type :shadow
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}]
:value [{:offset-x "0" :offset-y "2px" :blur "4px" :spread "0" :color "#0000001A"}]
:description "A single shadow"})
"shadow.multiple"
(ctob/make-token
{:name "shadow.multiple"
:type :shadow
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}
{:offsetX "0" :offsetY "8px" :blur "16px" :spread "0" :color "#0000001A"}]})
:value [{:offset-x "0" :offset-y "2px" :blur "4px" :spread "0" :color "#0000001A"}
{:offset-x "0" :offset-y "8px" :blur "16px" :spread "0" :color "#0000001A"}]})
"shadow.ref"
(ctob/make-token
{:name "shadow.ref"
@@ -1991,7 +1991,7 @@
(ctob/make-token
{:name "shadow.test"
:type :shadow
:value [{:offsetX "1" :offsetY "1" :blur "1" :spread "1" :color "red" :inset true}]
:value [{:offset-x "1" :offset-y "1" :blur "1" :spread "1" :color "red" :inset true}]
:description "Round trip test"})
"shadow.ref"
(ctob/make-token

View File

@@ -5947,8 +5947,8 @@
"~:spread": "10",
"~:color": "rgb(160, 73, 73)",
"~:inset": true,
"~:offsetX": "10",
"~:offsetY": "10"
"~:offset-x": "10",
"~:offset-y": "10"
}
],
"~:description": "",

View File

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

View File

@@ -368,8 +368,8 @@
(let [add-keyed-errors (fn [shadow-result k errors]
(update shadow-result :errors concat
(map #(assoc % :shadow-key k :shadow-index shadow-index) errors)))
parsers {:offsetX parse-sd-token-general-value
:offsetY parse-sd-token-general-value
parsers {:offset-x parse-sd-token-general-value
:offset-y parse-sd-token-general-value
:blur parse-sd-token-shadow-blur
:spread parse-sd-token-shadow-spread
:color parse-sd-token-color-value
@@ -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

View File

@@ -153,11 +153,11 @@
(defn value->shadow
"Transform a token shadow value into penpot shadow data structure"
[value]
(mapv (fn [{:keys [offsetX offsetY blur spread color inset]}]
(mapv (fn [{:keys [offset-x offset-y blur spread color inset]}]
{:id (random-uuid)
:hidden false
:offset-x offsetX
:offset-y offsetY
:offset-x offset-x
:offset-y offset-y
:blur blur
:color (value->color color)
:spread spread

View File

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

View File

@@ -16,8 +16,8 @@
:letter-spacing "Letter Spacing"
:text-case "Text Case"
:text-decoration "Text Decoration"
:offsetX "X"
:offsetY "Y"
:offset-x "X"
:offset-y "Y"
:blur "Blur"
:spread "Spread"
:color "Color"

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.forms :as fc]
[app.main.ui.workspace.tokens.management.create.form-input-token :refer [form-input-token*]]
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token*]]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
@@ -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"
@@ -185,7 +187,7 @@
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
[:div {:class (stl/css :input-row)}
[:> form-input-token*
[:> input-token*
{:placeholder (tr "workspace.tokens.token-value-enter")
:label (tr "workspace.tokens.token-value")
:name :value

View File

@@ -23,7 +23,7 @@
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.forms :as fc]
[app.main.ui.workspace.tokens.management.create.form-color-input-token :refer [form-color-input-token*]]
[app.main.ui.workspace.tokens.management.create.color-input-token :refer [color-input-token*]]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
@@ -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"
@@ -185,7 +188,7 @@
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
[:div {:class (stl/css :input-row)}
[:> form-color-input-token*
[:> color-input-token*
{:placeholder (tr "workspace.tokens.token-value-enter")
:label (tr "workspace.tokens.token-value")
:name :value

View File

@@ -0,0 +1,435 @@
;; 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.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]
[app.main.ui.workspace.colorpicker :as colorpicker]
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
[app.util.dom :as dom]
[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
[tokens prev-token value]
(let [token
{:value value
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
tokens
(-> tokens
;; Remove previous token when renaming a token
(dissoc (:name prev-token))
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
(->> tokens
(sd/resolve-tokens-interactive)
(rx/mapcat
(fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
(if resolved-value
(rx/of {:value resolved-value})
(rx/of {:error (first errors)}))))))))
(defn- hex->color-obj
[hex]
(when-let [tc (tinycolor/valid-color hex)]
(let [hex (tinycolor/->hex-string tc)
alpha (tinycolor/alpha tc)
[r g b] (cl/hex->rgb hex)
[h s v] (cl/hex->hsv hex)]
{:hex hex
:r r :g g :b b
:h h :s s :v v
:alpha alpha})))
(mf/defc ramp*
[{:keys [color on-change]}]
(let [wrapper-node-ref (mf/use-ref nil)
dragging-ref (mf/use-ref false)
on-start-drag
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
on-finish-drag
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
internal-color*
(mf/use-state #(hex->color-obj color))
internal-color
(deref internal-color*)
on-change'
(mf/use-fn
(mf/deps on-change)
(fn [{:keys [hex alpha] :as selector-color}]
(let [dragging? (mf/ref-val dragging-ref)]
(when-not (and dragging? hex)
(reset! internal-color* selector-color)
(on-change hex alpha)))))]
(mf/use-effect
(mf/deps color)
(fn []
;; Update internal color when user changes input value
(when-let [color (tinycolor/valid-color color)]
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
(reset! internal-color* (hex->color-obj color))))))
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
[:div {:ref wrapper-node-ref}
[:> ramp-selector*
{:color internal-color
:on-start-drag on-start-drag
:on-finish-drag on-finish-drag
:on-change on-change'}]]))
(mf/defc color-input-token*
[{:keys [name tokens token] :rest props}]
(let [form (mf/use-ctx fc/context)
input-name name
touched?
(and (contains? (:data @form) input-name)
(get-in @form [:touched input-name]))
error
(get-in @form [:errors input-name])
value
(get-in @form [:data input-name] "")
color-resolved
(get-in @form [:data :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 (:value token)]
(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)
(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)
(fm/on-input-change form input-name color-value true)
(rx/push! resolve-stream color-value)))))
on-change
(mf/use-fn
(mf/deps resolve-stream input-name)
(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)]
(fm/on-input-change form input-name value true)
(rx/push! resolve-stream value))))
props
(mf/spread-props props {:on-change on-change
;; TODO: Review this value vs default-value
:value (or value "")
:hint-message (:message hint)
:variant "comfortable"
:slot-start swatch
:hint-type (:type hint)})
props
(if (and error touched?)
(mf/spread-props props {:hint-type "error"
:hint-message (:message error)})
props)]
(mf/with-effect [resolve-stream tokens token input-name]
(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]
((:error/fn error) (:error/value error))))))
(rx/subs! (fn [{:keys [error value]}]
(let [touched? (get-in @form [:touched input-name])]
(when touched?
(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" (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 []
(rx/dispose! subs))))
[:*
[:> input* props]
(when color-ramp-open?
[:> ramp* {:color value :on-change on-change-value}])]))
(defn- on-composite-indexed-input-token-change
([form field index value composite-type]
(on-composite-indexed-input-token-change form field index value composite-type false))
([form field index value composite-type trim?]
(letfn [(clean-errors [errors]
(-> errors
(dissoc field)
(not-empty)))]
(swap! form (fn [state]
(-> state
(assoc-in [:data :value composite-type index field] (if trim? (str/trim value) value))
(update :errors clean-errors)
(update :extra-errors clean-errors)))))))
(mf/defc color-input-token-indexed*
[{:keys [name tokens token index composite-type] :rest props}]
(let [form (mf/use-ctx fc/context)
input-name name
error
(get-in @form [:errors :value composite-type index input-name])
value
(get-in @form [:data :value composite-type index input-name] "")
color-resolved
(get-in @form [:data :value composite-type 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 composite-type 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 composite-type 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 composite-type 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 composite-type]
(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 composite-type index] dissoc input-name)
(swap! form update-in [:data :value composite-type index] dissoc input-name)
(swap! form assoc-in [:data :value composite-type 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 composite-type index input-name] {:message error'})
(swap! form assoc-in [:data :value composite-type 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 composite-type index input-name] "")]
(swap! form update :errors dissoc :value)
(swap! form update :extra-errors dissoc :value)
(swap! form assoc-in [:data :value composite-type 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}])]))

View File

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

View File

@@ -23,7 +23,7 @@
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.forms :as fc]
[app.main.ui.workspace.tokens.management.create.form-input-token :refer [form-input-token*]]
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token*]]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
@@ -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"
@@ -184,7 +186,7 @@
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
[:div {:class (stl/css :input-row)}
[:> form-input-token*
[:> input-token*
{:placeholder (tr "workspace.tokens.token-value-enter")
:label (tr "workspace.tokens.token-value")
:name :value

View File

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

View File

@@ -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
@@ -209,7 +211,7 @@
(defn- check-empty-shadow-token [token]
(when (or (empty? (:value token))
(some (fn [shadow] (not-every? #(contains? shadow %) [:offsetX :offsetY :blur :spread :color]))
(some (fn [shadow] (not-every? #(contains? shadow %) [:offset-x :offset-y :blur :spread :color]))
(:value token)))
(wte/get-error-code :error.token/empty-input)))
@@ -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`.
@@ -937,10 +940,10 @@
(def ^:private shadow-inputs
#(d/ordered-map
:offsetX
:offset-x
{:label (tr "workspace.tokens.shadow-x")
:placeholder (tr "workspace.tokens.shadow-x")}
:offsetY
:offset-y
{:label (tr "workspace.tokens.shadow-y")
:placeholder (tr "workspace.tokens.shadow-y")}
:blur
@@ -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]

View File

@@ -1,237 +0,0 @@
;; 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-color-input-token
(:require-macros [app.main.style :as stl])
(:require
[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.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.utilities.swatch :refer [swatch*]]
[app.main.ui.forms :as fc]
[app.main.ui.workspace.colorpicker :as colorpicker]
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
[beicon.v2.core :as rx]
[rumext.v2 :as mf]))
(defn- resolve-value
[tokens prev-token value]
(let [token
{:value value
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
tokens
(-> tokens
;; Remove previous token when renaming a token
(dissoc (:name prev-token))
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
(->> tokens
(sd/resolve-tokens-interactive)
(rx/mapcat
(fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
(if resolved-value
(rx/of {:value resolved-value})
(rx/of {:error (first errors)}))))))))
(defn- hex->color-obj
[hex]
(when-let [tc (tinycolor/valid-color hex)]
(let [hex (tinycolor/->hex-string tc)
alpha (tinycolor/alpha tc)
[r g b] (cl/hex->rgb hex)
[h s v] (cl/hex->hsv hex)]
{:hex hex
:r r :g g :b b
:h h :s s :v v
:alpha alpha})))
(mf/defc ramp*
[{:keys [color on-change]}]
(let [wrapper-node-ref (mf/use-ref nil)
dragging-ref (mf/use-ref false)
on-start-drag
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
on-finish-drag
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
internal-color*
(mf/use-state #(hex->color-obj color))
internal-color
(deref internal-color*)
on-change'
(mf/use-fn
(mf/deps on-change)
(fn [{:keys [hex alpha] :as selector-color}]
(let [dragging? (mf/ref-val dragging-ref)]
(when-not (and dragging? hex)
(reset! internal-color* selector-color)
(on-change hex alpha)))))]
(mf/use-effect
(mf/deps color)
(fn []
;; Update internal color when user changes input value
(when-let [color (tinycolor/valid-color color)]
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
(reset! internal-color* (hex->color-obj color))))))
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
[:div {:ref wrapper-node-ref}
[:> ramp-selector*
{:color internal-color
:on-start-drag on-start-drag
:on-finish-drag on-finish-drag
:on-change on-change'}]]))
(mf/defc form-color-input-token*
[{:keys [name tokens token] :rest props}]
(let [form (mf/use-ctx fc/context)
input-name name
touched?
(and (contains? (:data @form) input-name)
(get-in @form [:touched input-name]))
error
(get-in @form [:errors input-name])
value
(get-in @form [:data input-name] "")
hex (if (tinycolor/valid-color value)
(tinycolor/->hex-string (tinycolor/valid-color value))
"#8f9da3")
alpha (if (tinycolor/valid-color value)
(tinycolor/alpha (tinycolor/valid-color value))
1)
resolve-stream
(mf/with-memo [token]
(if-let [value (:value token)]
(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)
(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)
(fm/on-input-change form input-name color-value true)
(rx/push! resolve-stream color-value)))))
on-change
(mf/use-fn
(mf/deps resolve-stream input-name)
(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)]
(fm/on-input-change form input-name value true)
(rx/push! resolve-stream value))))
props
(mf/spread-props props {:on-change on-change
;; TODO: Review this value vs default-value
:value (or value "")
:hint-message (:message hint)
:variant "comfortable"
:slot-start swatch
:hint-type (:type hint)})
props
(if (and error touched?)
(mf/spread-props props {:hint-type "error"
:hint-message (:message error)})
props)]
(mf/with-effect [resolve-stream tokens token input-name]
(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]
((:error/fn error) (:error/value error))))))
(rx/subs! (fn [{:keys [error value]}]
(let [touched? (get-in @form [:touched input-name])]
(when touched?
(if error
(do
(swap! form assoc-in [:extra-errors input-name] {:message error})
(reset! hint* {:message error :type "error"}))
(let [message (tr "workspace.tokens.resolved-value" value)]
(swap! form update :extra-errors dissoc input-name)
(reset! hint* {:message message :type "hint"}))))))))]
(fn []
(rx/dispose! subs))))
[:*
[:> input* props]
(when color-ramp-open?
[:> ramp* {:color value :on-change on-change-value}])]))

View File

@@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace.tokens.management.create.form-input-token
(ns app.main.ui.workspace.tokens.management.create.input-token
(:require
[app.common.data :as d]
[app.common.types.tokens-lib :as ctob]
@@ -39,7 +39,7 @@
(rx/of {:value resolved-value})
(rx/of {:error (first errors)}))))))))
(mf/defc form-input-token*
(mf/defc input-token*
[{:keys [name tokens token] :rest props}]
(let [form (mf/use-ctx fc/context)
@@ -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 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 composite-type]
(on-composite-indexed-input-token-change form field index value composite-type false))
([form field index value composite-type trim?]
(letfn [(clean-errors [errors]
(-> errors
(dissoc field)
(not-empty)))]
(swap! form (fn [state]
(-> state
(assoc-in [:data :value composite-type index field] (if trim? (str/trim value) value))
(update :errors clean-errors)
(update :extra-errors clean-errors)))))))
(mf/defc input-token-indexed*
[{:keys [name tokens token index composite-type] :rest props}]
(let [form (mf/use-ctx fc/context)
input-name name
error
(get-in @form [:errors :value composite-type index input-name])
value-from-form
(get-in @form [:data :value composite-type index input-name] "")
resolve-stream
(mf/with-memo [token index input-name]
(if-let [value (get-in token [:value composite-type 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 composite-type 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 composite-type]
(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 composite-type index] dissoc input-name)
(swap! form update-in [:data :value composite-type 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 composite-type 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 composite-type 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"})))))))]

View File

@@ -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.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 composite-type] :rest props}]
(let [form (mf/use-ctx fc/context)
input-name name
value
(get-in @form [:data :value composite-type 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 composite-type index input-name] is-inner?))))
props (mf/spread-props props {:default-selected (if value "inner" "drop")
:variant "ghost"
:on-change on-change})]
[:> select* props]))

View File

@@ -0,0 +1,486 @@
;; 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.color-input-token :refer [color-input-token-indexed*]]
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token-composite* input-token-indexed*]]
[app.main.ui.workspace.tokens.management.create.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]))
(def ^:private default-token-shadow
{:offset-x "4"
:offset-y "4"
:blur "4"
:spread "0"})
(defn get-subtoken
[token index prop composite-type]
(let [value (get-in token [:value composite-type 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 composite-type] :as props}]
(let [inset-token (get-subtoken token index :inset composite-type)
inset-token (hooks/use-equal-memo inset-token)
color-token (get-subtoken token index :color composite-type)
color-token (hooks/use-equal-memo color-token)
offset-x-token (get-subtoken token index :offset-x composite-type)
offset-x-token (hooks/use-equal-memo offset-x-token)
offset-y-token (get-subtoken token index :offset-y composite-type)
offset-y-token (hooks/use-equal-memo offset-y-token)
blur-token (get-subtoken token index :blur composite-type)
blur-token (hooks/use-equal-memo blur-token)
spread-token (get-subtoken token index :spreadX composite-type)
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
:composite-type composite-type
: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-token-indexed*
{:placeholder (tr "workspace.tokens.token-value-enter")
:aria-label (tr "workspace.tokens.color")
:name :color
:token color-token
:composite-type composite-type
:index index
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> input-token-indexed*
{:aria-label (tr "workspace.tokens.shadow-x")
:icon i/character-x
:placeholder (tr "workspace.tokens.shadow-x")
:name :offset-x
:token offset-x-token
:index index
:composite-type composite-type
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> input-token-indexed*
{:aria-label (tr "workspace.tokens.shadow-y")
:icon i/character-y
:placeholder (tr "workspace.tokens.shadow-y")
:name :offset-y
:token offset-y-token
:index index
:composite-type composite-type
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> 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)}
(str (tr "workspace.tokens.shadow-blur") ":")])
:token blur-token
:index index
:composite-type composite-type
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> 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)}
(str (tr "workspace.tokens.shadow-spread") ":")])
:token spread-token
:composite-type composite-type
:index index
:tokens tokens}]]]]))
(mf/defc composite-form*
[{:keys [token tokens remove-shadow-block composite-type] :as props}]
(let [form
(mf/use-ctx forms/context)
length
(-> form deref :data :value composite-type count)]
(for [index (range length)]
[:> shadow-formset* {:key index
:index index
:token token
:tokens tokens
:composite-type composite-type
: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)}
[:> 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
[:shadow {:optinal true}
[:vector
[:map
[:offset-x {:optional true} [:maybe :string]]
[:offset-y {: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 :shadow)
;; To be a valid shadow it must contain one on each valid values
valid-composite-shadow?
(and (seq shadows)
(every?
(fn [{:keys [offset-x offset-y blur spread color]}]
(and (not (str/blank? offset-x))
(not (str/blank? offset-y))
(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*)
composite-type :shadow
token
(mf/with-memo [token]
(or token
(if-let [value (get token :value)]
(cond
(string? value)
{:value {:reference value
:shadow []}
:type :shadow}
(vector? value)
{:value {:reference nil
:shadow value}
:type :shadow})
{:type :shadow
:value {:reference nil
:shadow [default-token-shadow]}})))
token-type
(get token :type)
token-properties
(dwta/get-token-properties token)
token-title (str/lower (:title token-properties))
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
:shadow []}
(vector? raw-value)
{:reference nil
:shadow raw-value}
:else
{:reference nil
:shadow [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
(mf/deps composite-type)
(fn []
(swap! form update-in [:data :value composite-type] conj default-token-shadow)))
remove-shadow-block
(mf/use-fn
(mf/deps composite-type)
(fn [index event]
(dom/prevent-default event)
(swap! form update-in [:data :value composite-type] #(d/remove-at-index % index))))
on-submit
(mf/use-fn
(mf/deps validate-token token tokens token-type active-tab composite-type)
(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)
(composite-type 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
:composite-type composite-type}]
[:> 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")]]]]))

View File

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

View File

@@ -23,7 +23,7 @@
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.forms :as fc]
[app.main.ui.workspace.tokens.management.create.form-input-token :refer [form-input-token*]]
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token*]]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
@@ -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"
@@ -185,7 +187,7 @@
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
[:div {:class (stl/css :input-row)}
[:> form-input-token*
[:> input-token*
{:placeholder (tr "workspace.tokens.text-case-value-enter")
:label (tr "workspace.tokens.token-value")
:name :value

View File

@@ -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.input-token :refer [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*
[:> 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*
[:> 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*
[:> 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*
[:> 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*
[:> 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*
[:> 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*
[:> 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"

View File

@@ -43,6 +43,7 @@
:stroke-width "stroke-size"
:dimensions "expand"
:sizing "expand"
:shadow "drop-shadow"
"add"))
(mf/defc token-group*

View File

@@ -151,8 +151,8 @@
;; export interface Shadow {
;; id?: string;
;; style?: 'drop-shadow' | 'inner-shadow';
;; offsetX?: number;
;; offsetY?: number;
;; offset-x?: number;
;; offset-y?: number;
;; blur?: number;
;; spread?: number;
;; hidden?: boolean;
@@ -164,8 +164,8 @@
(obj/without-empty
#js {:id (-> id format-id)
:style (-> style format-key)
:offsetX offset-x
:offsetY offset-y
:offset-x offset-x
:offset-y offset-y
:blur blur
:spread spread
:hidden hidden

View File

@@ -147,7 +147,7 @@
;; export interface Shadow {
;; id?: string;
;; style?: 'drop-shadow' | 'inner-shadow';
;; offsetX?: number;
;; offset--y?: number;
;; offsetY?: number;
;; blur?: number;
;; spread?: number;
@@ -160,8 +160,8 @@
(d/without-nils
{:id (-> (obj/get shadow "id") parse-id)
:style (-> (obj/get shadow "style") parse-keyword)
:offset-x (obj/get shadow "offsetX")
:offset-y (obj/get shadow "offsetY")
:offset-x (obj/get shadow "offset-x")
:offset-y (obj/get shadow "offset-y")
:blur (obj/get shadow "blur")
:spread (obj/get shadow "spread")
:hidden (obj/get shadow "hidden")

View File

@@ -474,8 +474,8 @@
(t/async
done
(let [shadow-token {:name "shadow.sm"
:value [{:offsetX 10
:offsetY 10
:value [{:offset-x 10
:offset-y 10
:blur 10
:spread 10
:color "rgba(0,0,0,0.5)"

View File

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

View File

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