mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
♻️ Replace shadow form
This commit is contained in:
@@ -1239,8 +1239,12 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
// Fill in the shadow values
|
// Fill in the shadow values
|
||||||
const offsetXInput = firstShadowFields.getByLabel("X");
|
const offsetXInput = firstShadowFields.getByLabel("X");
|
||||||
const offsetYInput = firstShadowFields.getByLabel("Y");
|
const offsetYInput = firstShadowFields.getByLabel("Y");
|
||||||
const blurInput = firstShadowFields.getByLabel("Blur");
|
const blurInput = firstShadowFields.getByRole("textbox", {
|
||||||
const spreadInput = firstShadowFields.getByLabel("Spread");
|
name: "Blur",
|
||||||
|
});
|
||||||
|
const spreadInput = firstShadowFields.getByRole("textbox", {
|
||||||
|
name: "Spread",
|
||||||
|
});
|
||||||
|
|
||||||
await offsetXInput.fill("2");
|
await offsetXInput.fill("2");
|
||||||
await offsetYInput.fill("2");
|
await offsetYInput.fill("2");
|
||||||
@@ -1261,9 +1265,10 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
|
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
|
||||||
|
|
||||||
// Verify that a color value was set
|
// Verify that a color value was set
|
||||||
const colorInput = firstShadowFields.getByLabel("Color");
|
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||||
const firstColorValue = await colorInput.inputValue();
|
name: "Color",
|
||||||
await expect(firstColorValue).toMatch(/^rgb(.*)$/);
|
});
|
||||||
|
await expect(colorInput).toHaveValue(/^rgb(.*)$/);
|
||||||
|
|
||||||
// Wait for validation to complete
|
// Wait for validation to complete
|
||||||
await expect(
|
await expect(
|
||||||
@@ -1281,11 +1286,15 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||||
"shadow-input-fields-0",
|
"shadow-input-fields-0",
|
||||||
);
|
);
|
||||||
const colorInput = firstShadowFields.getByLabel("Color");
|
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||||
|
name: "Color",
|
||||||
|
});
|
||||||
const firstColorValue = await colorInput.inputValue();
|
const firstColorValue = await colorInput.inputValue();
|
||||||
|
|
||||||
// User adds a second shadow
|
// User adds a second shadow
|
||||||
const addButton = firstShadowFields.getByTestId("shadow-add-button-0");
|
const addButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
|
name: "Add Shadow",
|
||||||
|
});
|
||||||
await addButton.click();
|
await addButton.click();
|
||||||
|
|
||||||
const secondShadowFields = tokensUpdateCreateModal.getByTestId(
|
const secondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||||
@@ -1294,8 +1303,7 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
await expect(secondShadowFields).toBeVisible();
|
await expect(secondShadowFields).toBeVisible();
|
||||||
|
|
||||||
// User adds a third shadow
|
// User adds a third shadow
|
||||||
const addButton2 = secondShadowFields.getByTestId("shadow-add-button-1");
|
await addButton.click();
|
||||||
await addButton2.click();
|
|
||||||
|
|
||||||
const thirdShadowFields = tokensUpdateCreateModal.getByTestId(
|
const thirdShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||||
"shadow-input-fields-2",
|
"shadow-input-fields-2",
|
||||||
@@ -1305,9 +1313,15 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
// User adds values for the third shadow
|
// User adds values for the third shadow
|
||||||
const thirdOffsetXInput = thirdShadowFields.getByLabel("X");
|
const thirdOffsetXInput = thirdShadowFields.getByLabel("X");
|
||||||
const thirdOffsetYInput = thirdShadowFields.getByLabel("Y");
|
const thirdOffsetYInput = thirdShadowFields.getByLabel("Y");
|
||||||
const thirdBlurInput = thirdShadowFields.getByLabel("Blur");
|
const thirdBlurInput = thirdShadowFields.getByRole("textbox", {
|
||||||
const thirdSpreadInput = thirdShadowFields.getByLabel("Spread");
|
name: "Blur",
|
||||||
const thirdColorInput = thirdShadowFields.getByLabel("Color");
|
});
|
||||||
|
const thirdSpreadInput = thirdShadowFields.getByRole("textbox", {
|
||||||
|
name: "Spread",
|
||||||
|
});
|
||||||
|
const thirdColorInput = thirdShadowFields.getByRole("textbox", {
|
||||||
|
name: "Color",
|
||||||
|
});
|
||||||
|
|
||||||
await thirdOffsetXInput.fill("10");
|
await thirdOffsetXInput.fill("10");
|
||||||
await thirdOffsetYInput.fill("10");
|
await thirdOffsetYInput.fill("10");
|
||||||
@@ -1316,15 +1330,13 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
await thirdColorInput.fill("#FF0000");
|
await thirdColorInput.fill("#FF0000");
|
||||||
|
|
||||||
// User removes the 2nd shadow
|
// User removes the 2nd shadow
|
||||||
const removeButton2 = secondShadowFields.getByTestId(
|
const removeButton2 = secondShadowFields.getByRole("button", {
|
||||||
"shadow-remove-button-1",
|
name: "Remove Shadow",
|
||||||
);
|
});
|
||||||
await removeButton2.click();
|
await removeButton2.click();
|
||||||
|
|
||||||
// Verify second shadow is removed
|
// Verify that we have only two shadow fields
|
||||||
await expect(
|
await expect(thirdShadowFields).not.toBeVisible();
|
||||||
secondShadowFields.getByTestId("shadow-add-button-3"),
|
|
||||||
).not.toBeVisible();
|
|
||||||
|
|
||||||
// Verify that the first shadow kept its values
|
// Verify that the first shadow kept its values
|
||||||
const firstOffsetXValue = await firstShadowFields
|
const firstOffsetXValue = await firstShadowFields
|
||||||
@@ -1334,13 +1346,13 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
.getByLabel("Y")
|
.getByLabel("Y")
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const firstBlurValue = await firstShadowFields
|
const firstBlurValue = await firstShadowFields
|
||||||
.getByLabel("Blur")
|
.getByRole("textbox", { name: "Blur" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const firstSpreadValue = await firstShadowFields
|
const firstSpreadValue = await firstShadowFields
|
||||||
.getByLabel("Spread")
|
.getByRole("textbox", { name: "Spread" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const firstColorValueAfter = await firstShadowFields
|
const firstColorValueAfter = await firstShadowFields
|
||||||
.getByLabel("Color")
|
.getByRole("textbox", { name: "Color" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
|
|
||||||
await expect(firstOffsetXValue).toBe("2");
|
await expect(firstOffsetXValue).toBe("2");
|
||||||
@@ -1349,7 +1361,7 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
await expect(firstSpreadValue).toBe("0");
|
await expect(firstSpreadValue).toBe("0");
|
||||||
await expect(firstColorValueAfter).toBe(firstColorValue);
|
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
|
// After removing index 1, the third shadow becomes the second shadow at index 1
|
||||||
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||||
"shadow-input-fields-1",
|
"shadow-input-fields-1",
|
||||||
@@ -1363,13 +1375,13 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
.getByLabel("Y")
|
.getByLabel("Y")
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const secondBlurValue = await newSecondShadowFields
|
const secondBlurValue = await newSecondShadowFields
|
||||||
.getByLabel("Blur")
|
.getByRole("textbox", { name: "Blur" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const secondSpreadValue = await newSecondShadowFields
|
const secondSpreadValue = await newSecondShadowFields
|
||||||
.getByLabel("Spread")
|
.getByRole("textbox", { name: "Spread" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const secondColorValue = await newSecondShadowFields
|
const secondColorValue = await newSecondShadowFields
|
||||||
.getByLabel("Color")
|
.getByRole("textbox", { name: "Color" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
|
|
||||||
await expect(secondOffsetXValue).toBe("10");
|
await expect(secondOffsetXValue).toBe("10");
|
||||||
@@ -1386,7 +1398,9 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||||
"shadow-input-fields-1",
|
"shadow-input-fields-1",
|
||||||
);
|
);
|
||||||
const colorInput = firstShadowFields.getByLabel("Color");
|
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||||
|
name: "Color",
|
||||||
|
});
|
||||||
const firstColorValue = await colorInput.inputValue();
|
const firstColorValue = await colorInput.inputValue();
|
||||||
|
|
||||||
// Switch to reference tab
|
// Switch to reference tab
|
||||||
@@ -1414,13 +1428,13 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
.getByLabel("Y")
|
.getByLabel("Y")
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const restoredFirstBlur = await firstShadowFields
|
const restoredFirstBlur = await firstShadowFields
|
||||||
.getByLabel("Blur")
|
.getByRole("textbox", { name: "Blur" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const restoredFirstSpread = await firstShadowFields
|
const restoredFirstSpread = await firstShadowFields
|
||||||
.getByLabel("Spread")
|
.getByRole("textbox", { name: "Spread" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const restoredFirstColor = await firstShadowFields
|
const restoredFirstColor = await firstShadowFields
|
||||||
.getByLabel("Color")
|
.getByRole("textbox", { name: "Color" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
|
|
||||||
await expect(restoredFirstOffsetX).toBe("2");
|
await expect(restoredFirstOffsetX).toBe("2");
|
||||||
@@ -1437,13 +1451,13 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
.getByLabel("Y")
|
.getByLabel("Y")
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const restoredSecondBlur = await newSecondShadowFields
|
const restoredSecondBlur = await newSecondShadowFields
|
||||||
.getByLabel("Blur")
|
.getByRole("textbox", { name: "Blur" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const restoredSecondSpread = await newSecondShadowFields
|
const restoredSecondSpread = await newSecondShadowFields
|
||||||
.getByLabel("Spread")
|
.getByRole("textbox", { name: "Spread" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
const restoredSecondColor = await newSecondShadowFields
|
const restoredSecondColor = await newSecondShadowFields
|
||||||
.getByLabel("Color")
|
.getByRole("textbox", { name: "Color" })
|
||||||
.inputValue();
|
.inputValue();
|
||||||
|
|
||||||
await expect(restoredSecondOffsetX).toBe("10");
|
await expect(restoredSecondOffsetX).toBe("10");
|
||||||
|
|||||||
@@ -389,9 +389,16 @@
|
|||||||
(defn- parse-sd-token-shadow-value
|
(defn- parse-sd-token-shadow-value
|
||||||
"Parses shadow value and validates it."
|
"Parses shadow value and validates it."
|
||||||
[value]
|
[value]
|
||||||
|
(let [missing-references
|
||||||
|
(when (string? value)
|
||||||
|
(seq (cto/find-token-value-references value)))]
|
||||||
(cond
|
(cond
|
||||||
;; Reference value (string)
|
missing-references
|
||||||
(string? value) {:value value}
|
{: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
|
;; Empty value
|
||||||
(nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]}
|
(nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]}
|
||||||
@@ -417,7 +424,7 @@
|
|||||||
(if (seq all-errors)
|
(if (seq all-errors)
|
||||||
{:errors all-errors
|
{:errors all-errors
|
||||||
:value all-values}
|
:value all-values}
|
||||||
{:value all-values}))))
|
{:value all-values})))))
|
||||||
|
|
||||||
(defn collect-shadow-errors [token shadow-index]
|
(defn collect-shadow-errors [token shadow-index]
|
||||||
(group-by :shadow-key
|
(group-by :shadow-key
|
||||||
|
|||||||
@@ -108,6 +108,10 @@
|
|||||||
{:error/code :error.style-dictionary/invalid-token-value-shadow-spread
|
{:error/code :error.style-dictionary/invalid-token-value-shadow-spread
|
||||||
:error/fn #(tr "workspace.tokens.shadow-spread-range")}
|
: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/unknown
|
||||||
{:error/code :error/unknown
|
{:error/code :error/unknown
|
||||||
:error/fn #(tr "labels.unknown-error")}})
|
:error/fn #(tr "labels.unknown-error")}})
|
||||||
|
|||||||
@@ -118,6 +118,7 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps disabled)
|
(mf/deps disabled)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
(dom/prevent-default event)
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(when-not disabled
|
(when-not disabled
|
||||||
(swap! is-open* not))))
|
(swap! is-open* not))))
|
||||||
|
|||||||
@@ -53,10 +53,15 @@
|
|||||||
"true")
|
"true")
|
||||||
:aria-describedby (when has-hint
|
:aria-describedby (when has-hint
|
||||||
(str id "-hint"))
|
(str id "-hint"))
|
||||||
:aria-labelledby tooltip-id
|
|
||||||
:type (d/nilv type "text")
|
:type (d/nilv type "text")
|
||||||
:id id
|
:id id
|
||||||
:max-length (d/nilv max-length max-input-length)})
|
: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
|
inside-class (stl/css-case :input-wrapper true
|
||||||
:has-hint has-hint
|
:has-hint has-hint
|
||||||
:hint-type-hint (= hint-type "hint")
|
:hint-type-hint (= hint-type "hint")
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.swatch {
|
.swatch {
|
||||||
--border-color: var(--color-background-quaternary);
|
--border-color: var(--color-accent-primary-muted);
|
||||||
--border-radius: #{$br-4};
|
--border-radius: #{$br-4};
|
||||||
--border-color-active: var(--color-foreground-primary);
|
--border-color-active: var(--color-foreground-primary);
|
||||||
--border-color-active-inset: var(--color-background-primary);
|
--border-color-active-inset: var(--color-background-primary);
|
||||||
|
|||||||
@@ -168,7 +168,9 @@
|
|||||||
[:div {:class (stl/css :token-rows)}
|
[:div {:class (stl/css :token-rows)}
|
||||||
|
|
||||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
[:> 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)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> fc/form-input* {:id "token-name"
|
[:> fc/form-input* {:id "token-name"
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
#(not (cft/token-name-path-exists? % tokens-tree))]]]
|
#(not (cft/token-name-path-exists? % tokens-tree))]]]
|
||||||
|
|
||||||
[:value [::sm/text {:error/fn token-value-error-fn}]]
|
[:value [::sm/text {:error/fn token-value-error-fn}]]
|
||||||
|
[:color-result {:optional true} ::sm/any]
|
||||||
|
|
||||||
[:description {:optional true}
|
[:description {:optional true}
|
||||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||||
@@ -168,7 +169,9 @@
|
|||||||
[:div {:class (stl/css :token-rows)}
|
[:div {:class (stl/css :token-rows)}
|
||||||
|
|
||||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
[:> 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)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> fc/form-input* {:id "token-name"
|
[:> fc/form-input* {:id "token-name"
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
|
||||||
(defn- resolve-value
|
(defn- resolve-value
|
||||||
[tokens prev-token value]
|
[tokens prev-token value]
|
||||||
(let [token
|
(let [token
|
||||||
@@ -117,7 +116,6 @@
|
|||||||
|
|
||||||
props
|
props
|
||||||
(mf/spread-props props {:on-change on-change
|
(mf/spread-props props {:on-change on-change
|
||||||
;; TODO: Review this value vs default-value
|
|
||||||
:value (or value "")
|
:value (or value "")
|
||||||
:hint-message (:message hint)
|
:hint-message (:message hint)
|
||||||
:slot-end font-selector-button
|
:slot-end font-selector-button
|
||||||
@@ -242,7 +240,6 @@
|
|||||||
|
|
||||||
props
|
props
|
||||||
(mf/spread-props props {:on-change on-change
|
(mf/spread-props props {:on-change on-change
|
||||||
;; TODO: Review this value vs default-value
|
|
||||||
:value (or value "")
|
:value (or value "")
|
||||||
:hint-message (:message hint)
|
:hint-message (:message hint)
|
||||||
:slot-end font-selector-button
|
:slot-end font-selector-button
|
||||||
|
|||||||
@@ -167,7 +167,9 @@
|
|||||||
:on-submit on-submit}
|
:on-submit on-submit}
|
||||||
[:div {:class (stl/css :token-rows)}
|
[:div {:class (stl/css :token-rows)}
|
||||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
[:> 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)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> fc/form-input* {:id "token-name"
|
[:> fc/form-input* {:id "token-name"
|
||||||
|
|||||||
@@ -165,7 +165,9 @@
|
|||||||
[:div {:class (stl/css :token-rows)}
|
[:div {:class (stl/css :token-rows)}
|
||||||
|
|
||||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
[:> 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)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> fc/form-input* {:id "token-name"
|
[:> fc/form-input* {:id "token-name"
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
[app.main.ui.workspace.tokens.management.create.font-family :as font-family]
|
[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-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.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.text-case :as text-case]
|
||||||
[app.main.ui.workspace.tokens.management.create.typography :as typography]
|
[app.main.ui.workspace.tokens.management.create.typography :as typography]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
@@ -133,8 +134,9 @@
|
|||||||
[check-token-empty-value check-self-reference])
|
[check-token-empty-value check-self-reference])
|
||||||
|
|
||||||
(defn- default-validate-token
|
(defn- default-validate-token
|
||||||
"Validates a token by confirming a list of `validator` predicates and resolving the token using `tokens` with StyleDictionary.
|
"Validates a token by confirming a list of `validator` predicates and
|
||||||
Returns rx stream of either a valid resolved token or an errors map.
|
resolving the token using `tokens` with StyleDictionary. Returns rx
|
||||||
|
stream of either a valid resolved token or an errors map.
|
||||||
|
|
||||||
Props:
|
Props:
|
||||||
token-name, token-value, token-description: Values from the form inputs
|
token-name, token-value, token-description: Values from the form inputs
|
||||||
@@ -232,15 +234,15 @@
|
|||||||
(default-validate-token))))
|
(default-validate-token))))
|
||||||
|
|
||||||
(defn- validate-shadow-token
|
(defn- validate-shadow-token
|
||||||
[{:keys [token-value] :as props}]
|
[{:keys [token-value] :as params}]
|
||||||
(cond
|
(cond
|
||||||
;; Entering form without a value - show no error just resolve nil
|
;; Entering form without a value - show no error just resolve nil
|
||||||
(nil? token-value) (rx/of nil)
|
(nil? token-value) (rx/of nil)
|
||||||
;; Validate refrence string
|
;; 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
|
;; Validate composite token
|
||||||
:else
|
:else
|
||||||
(-> props
|
(let [params (-> params
|
||||||
(update :token-value (fn [value]
|
(update :token-value (fn [value]
|
||||||
(->> (or value [])
|
(->> (or value [])
|
||||||
(mapv (fn [shadow]
|
(mapv (fn [shadow]
|
||||||
@@ -249,8 +251,9 @@
|
|||||||
(= "true" %) true
|
(= "true" %) true
|
||||||
:else false)))))))
|
:else false)))))))
|
||||||
(assoc :validators [check-empty-shadow-token
|
(assoc :validators [check-empty-shadow-token
|
||||||
check-shadow-token-self-reference])
|
check-shadow-token-self-reference]))]
|
||||||
(default-validate-token))))
|
|
||||||
|
(default-validate-token params))))
|
||||||
|
|
||||||
(defn- use-debonced-resolve-callback
|
(defn- use-debonced-resolve-callback
|
||||||
"Resolves a token values using `StyleDictionary`.
|
"Resolves a token values using `StyleDictionary`.
|
||||||
@@ -1440,12 +1443,13 @@
|
|||||||
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
||||||
:token token})
|
:token token})
|
||||||
font-family-props (mf/spread-props props {:validate-token validate-font-family-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
|
(case token-type
|
||||||
:color [:> color/form* props]
|
:color [:> color/form* props]
|
||||||
:typography [:> typography/form* typography-props]
|
:typography [:> typography/form* typography-props]
|
||||||
:shadow [:> shadow-form* props]
|
:shadow [:> shadow/form* shadow-props]
|
||||||
:font-family [:> font-family/form* font-family-props]
|
:font-family [:> font-family/form* font-family-props]
|
||||||
:text-case [:> text-case/form* props]
|
:text-case [:> text-case/form* props]
|
||||||
:text-decoration [:> text-decoration-form* props]
|
:text-decoration [:> text-decoration-form* props]
|
||||||
|
|||||||
@@ -7,12 +7,15 @@
|
|||||||
(ns app.main.ui.workspace.tokens.management.create.form-color-input-token
|
(ns app.main.ui.workspace.tokens.management.create.form-color-input-token
|
||||||
(:require-macros [app.main.style :as stl])
|
(:require-macros [app.main.style :as stl])
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.colors :as color]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.types.color :as cl]
|
[app.common.types.color :as cl]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
[app.main.data.style-dictionary :as sd]
|
[app.main.data.style-dictionary :as sd]
|
||||||
[app.main.data.tinycolor :as tinycolor]
|
[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.controls.input :refer [input*]]
|
||||||
[app.main.ui.ds.utilities.swatch :refer [swatch*]]
|
[app.main.ui.ds.utilities.swatch :refer [swatch*]]
|
||||||
[app.main.ui.forms :as fc]
|
[app.main.ui.forms :as fc]
|
||||||
@@ -22,6 +25,7 @@
|
|||||||
[app.util.forms :as fm]
|
[app.util.forms :as fm]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(defn- resolve-value
|
(defn- resolve-value
|
||||||
@@ -115,12 +119,28 @@
|
|||||||
value
|
value
|
||||||
(get-in @form [:data input-name] "")
|
(get-in @form [:data input-name] "")
|
||||||
|
|
||||||
hex (if (tinycolor/valid-color value)
|
color-resolved
|
||||||
(tinycolor/->hex-string (tinycolor/valid-color value))
|
(get-in @form [:data :color-result] "")
|
||||||
"#8f9da3")
|
|
||||||
|
|
||||||
alpha (if (tinycolor/valid-color value)
|
|
||||||
(tinycolor/alpha (tinycolor/valid-color value))
|
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)
|
1)
|
||||||
|
|
||||||
resolve-stream
|
resolve-stream
|
||||||
@@ -222,9 +242,11 @@
|
|||||||
(if error
|
(if error
|
||||||
(do
|
(do
|
||||||
(swap! form assoc-in [:extra-errors input-name] {:message error})
|
(swap! form assoc-in [:extra-errors input-name] {:message error})
|
||||||
|
(swap! form assoc-in [:data :color-result] "")
|
||||||
(reset! hint* {:message error :type "error"}))
|
(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 update :extra-errors dissoc input-name)
|
||||||
|
(swap! form assoc-in [:data :color-result] value)
|
||||||
(reset! hint* {:message message :type "hint"}))))))))]
|
(reset! hint* {:message message :type "hint"}))))))))]
|
||||||
|
|
||||||
(fn []
|
(fn []
|
||||||
@@ -235,3 +257,179 @@
|
|||||||
|
|
||||||
(when color-ramp-open?
|
(when color-ramp-open?
|
||||||
[:> ramp* {:color value :on-change on-change-value}])]))
|
[:> 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}])]))
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
props)]
|
props)]
|
||||||
|
|
||||||
(mf/with-effect [resolve-stream tokens token input-name]
|
(mf/with-effect [resolve-stream tokens token input-name]
|
||||||
|
|
||||||
(let [subs (->> resolve-stream
|
(let [subs (->> resolve-stream
|
||||||
(rx/debounce 300)
|
(rx/debounce 300)
|
||||||
(rx/mapcat (partial resolve-value tokens token))
|
(rx/mapcat (partial resolve-value tokens token))
|
||||||
@@ -125,7 +126,7 @@
|
|||||||
(update :errors clean-errors)
|
(update :errors clean-errors)
|
||||||
(update :extra-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}]
|
[{:keys [name tokens token] :rest props}]
|
||||||
|
|
||||||
(let [form (mf/use-ctx fc/context)
|
(let [form (mf/use-ctx fc/context)
|
||||||
@@ -202,6 +203,108 @@
|
|||||||
input-value (get-in @form [:data :value input-name] "")]
|
input-value (get-in @form [:data :value input-name] "")]
|
||||||
(swap! form update :errors dissoc :value)
|
(swap! form update :errors dissoc :value)
|
||||||
(swap! form update :extra-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))
|
(if (= input-value (str value))
|
||||||
(reset! hint* {})
|
(reset! hint* {})
|
||||||
(reset! hint* {:message message :type "hint"})))))))]
|
(reset! hint* {:message message :type "hint"})))))))]
|
||||||
|
|||||||
@@ -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]))
|
||||||
@@ -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")]]]]))
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -168,7 +168,9 @@
|
|||||||
[:div {:class (stl/css :token-rows)}
|
[:div {:class (stl/css :token-rows)}
|
||||||
|
|
||||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
[:> 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)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> fc/form-input* {:id "token-name"
|
[:> fc/form-input* {:id "token-name"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||||
[app.main.ui.forms :as forms]
|
[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.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.dom :as dom]
|
||||||
[app.util.forms :as fm]
|
[app.util.forms :as fm]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
:token font-family-sub-token
|
:token font-family-sub-token
|
||||||
:tokens tokens}]]
|
:tokens tokens}]]
|
||||||
[:div {:class (stl/css :input-row)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> token-composite-value-input*
|
[:> form-input-token-composite*
|
||||||
{:aria-label "Font Size"
|
{:aria-label "Font Size"
|
||||||
:icon i/text-font-size
|
:icon i/text-font-size
|
||||||
:placeholder (tr "workspace.tokens.font-size-value-enter")
|
:placeholder (tr "workspace.tokens.font-size-value-enter")
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
:token font-size-sub-token
|
:token font-size-sub-token
|
||||||
:tokens tokens}]]
|
:tokens tokens}]]
|
||||||
[:div {:class (stl/css :input-row)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> token-composite-value-input*
|
[:> form-input-token-composite*
|
||||||
{:aria-label "Font Weight"
|
{:aria-label "Font Weight"
|
||||||
:icon i/text-font-weight
|
:icon i/text-font-weight
|
||||||
:placeholder (tr "workspace.tokens.font-weight-value-enter")
|
:placeholder (tr "workspace.tokens.font-weight-value-enter")
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
:token font-weight-sub-token
|
:token font-weight-sub-token
|
||||||
:tokens tokens}]]
|
:tokens tokens}]]
|
||||||
[:div {:class (stl/css :input-row)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> token-composite-value-input*
|
[:> form-input-token-composite*
|
||||||
{:aria-label "Line Height"
|
{:aria-label "Line Height"
|
||||||
:icon i/text-lineheight
|
:icon i/text-lineheight
|
||||||
:placeholder (tr "workspace.tokens.line-height-value-enter")
|
:placeholder (tr "workspace.tokens.line-height-value-enter")
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
:token line-height-sub-token
|
:token line-height-sub-token
|
||||||
:tokens tokens}]]
|
:tokens tokens}]]
|
||||||
[:div {:class (stl/css :input-row)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> token-composite-value-input*
|
[:> form-input-token-composite*
|
||||||
{:aria-label "Letter Spacing"
|
{:aria-label "Letter Spacing"
|
||||||
:icon i/text-letterspacing
|
:icon i/text-letterspacing
|
||||||
:placeholder (tr "workspace.tokens.letter-spacing-value-enter-composite")
|
:placeholder (tr "workspace.tokens.letter-spacing-value-enter-composite")
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
:token letter-spacing-sub-token
|
:token letter-spacing-sub-token
|
||||||
:tokens tokens}]]
|
:tokens tokens}]]
|
||||||
[:div {:class (stl/css :input-row)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> token-composite-value-input*
|
[:> form-input-token-composite*
|
||||||
{:aria-label "Text Case"
|
{:aria-label "Text Case"
|
||||||
:icon i/text-mixed
|
:icon i/text-mixed
|
||||||
:placeholder (tr "workspace.tokens.text-case-value-enter")
|
:placeholder (tr "workspace.tokens.text-case-value-enter")
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
:token text-case-sub-token
|
:token text-case-sub-token
|
||||||
:tokens tokens}]]
|
:tokens tokens}]]
|
||||||
[:div {:class (stl/css :input-row)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> token-composite-value-input*
|
[:> form-input-token-composite*
|
||||||
{:aria-label "Text Decoration"
|
{:aria-label "Text Decoration"
|
||||||
:icon i/text-underlined
|
:icon i/text-underlined
|
||||||
:placeholder (tr "workspace.tokens.text-decoration-value-enter")
|
:placeholder (tr "workspace.tokens.text-decoration-value-enter")
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
(mf/defc reference-form*
|
(mf/defc reference-form*
|
||||||
[{:keys [token tokens] :as props}]
|
[{:keys [token tokens] :as props}]
|
||||||
[:div {:class (stl/css :input-row)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> token-composite-value-input*
|
[:> form-input-token-composite*
|
||||||
{:placeholder (tr "workspace.tokens.reference-composite")
|
{:placeholder (tr "workspace.tokens.reference-composite")
|
||||||
:aria-label (tr "labels.reference")
|
:aria-label (tr "labels.reference")
|
||||||
:icon i/text-typography
|
:icon i/text-typography
|
||||||
@@ -357,7 +357,9 @@
|
|||||||
[:div {:class (stl/css :token-rows)}
|
[:div {:class (stl/css :token-rows)}
|
||||||
|
|
||||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
[:> 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)}
|
[:div {:class (stl/css :input-row)}
|
||||||
[:> forms/form-input* {:id "token-name"
|
[:> forms/form-input* {:id "token-name"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
:stroke-width "stroke-size"
|
:stroke-width "stroke-size"
|
||||||
:dimensions "expand"
|
:dimensions "expand"
|
||||||
:sizing "expand"
|
:sizing "expand"
|
||||||
|
:shadow "drop-shadow"
|
||||||
"add"))
|
"add"))
|
||||||
|
|
||||||
(mf/defc token-group*
|
(mf/defc token-group*
|
||||||
|
|||||||
@@ -7753,6 +7753,10 @@ msgstr "Invalid token value: only none, underline and strike-through are accepte
|
|||||||
msgid "workspace.tokens.invalid-token-value-typography"
|
msgid "workspace.tokens.invalid-token-value-typography"
|
||||||
msgstr "Invalid value: must reference a composite typography token."
|
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
|
#: 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"
|
msgid "workspace.tokens.invalid-value"
|
||||||
msgstr "Invalid token value: %s"
|
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"
|
msgid "workspace.tokens.reference-composite"
|
||||||
msgstr "Enter a token typography alias"
|
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
|
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
|
||||||
#, unused
|
#, unused
|
||||||
msgid "workspace.tokens.reference-error"
|
msgid "workspace.tokens.reference-error"
|
||||||
|
|||||||
@@ -7674,6 +7674,10 @@ msgstr "Tipo de sombra no válida: solo se aceptan 'innerShadow' o 'dropShadow'"
|
|||||||
msgid "workspace.tokens.invalid-token-value-typography"
|
msgid "workspace.tokens.invalid-token-value-typography"
|
||||||
msgstr "Valor no válido: debe hacer referencia a un token tipográfico compuesto."
|
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
|
#: 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"
|
msgid "workspace.tokens.invalid-value"
|
||||||
msgstr "Valor de token no válido: %s"
|
msgstr "Valor de token no válido: %s"
|
||||||
|
|||||||
Reference in New Issue
Block a user