mirror of
https://github.com/penpot/penpot.git
synced 2026-06-01 05:00:17 +00:00
🐛 Fix show error on name-input
This commit is contained in:
parent
05cceab768
commit
ba39600192
@ -6,7 +6,7 @@ import {
|
||||
setupTypographyTokensFileRender,
|
||||
unfoldTokenType,
|
||||
createToken,
|
||||
createSet
|
||||
createSet,
|
||||
} from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
|
||||
@ -1818,21 +1818,6 @@ test("User disables the current set but token still have resolved values shown i
|
||||
});
|
||||
|
||||
test.describe("User can't create groups that clash with token names", () => {
|
||||
const changeSetInput = async (sidebar, setName, finalKey = "Enter") => {
|
||||
const setInput = sidebar.locator("input:focus");
|
||||
await expect(setInput).toBeVisible();
|
||||
await setInput.fill(setName);
|
||||
await setInput.press(finalKey);
|
||||
};
|
||||
|
||||
const createSet = async (sidebar, setName, finalKey = "Enter") => {
|
||||
const tokensTabButton = sidebar
|
||||
.getByRole("button", { name: "Add set" })
|
||||
.click();
|
||||
|
||||
await changeSetInput(sidebar, setName, (finalKey = "Enter"));
|
||||
};
|
||||
|
||||
const createBadToken = async (page, type, name, textFieldName, value) => {
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
@ -2102,7 +2087,7 @@ test("User can't create Text Decoration token with group name that clashes with
|
||||
});
|
||||
|
||||
const errorNode = tokensUpdateCreateModal.getByText("Group name of typ1.bad conflicts with a token of the same name in another active set.");
|
||||
await expect(errorNode).toHaveCount(6);
|
||||
await expect(errorNode).toHaveCount(1);
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
@ -2160,7 +2145,7 @@ test("User can't create Text Decoration token with group name that clashes with
|
||||
});
|
||||
|
||||
const errorNode = tokensUpdateCreateModal.getByText("Group name of sha1.bad conflicts with a token of the same name in another active set.");
|
||||
await expect(errorNode).toHaveCount(5);
|
||||
await expect(errorNode).toHaveCount(1);
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -360,20 +360,20 @@ const createToken = async (page, type, name, textFieldName, value) => {
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
};
|
||||
|
||||
const changeSetInput = async (sidebar, setName, finalKey = "Enter") => {
|
||||
const setInput = sidebar.locator("input:focus");
|
||||
await expect(setInput).toBeVisible();
|
||||
await setInput.fill(setName);
|
||||
await setInput.press(finalKey);
|
||||
};
|
||||
const changeSetInput = async (sidebar, setName, finalKey = "Enter") => {
|
||||
const setInput = sidebar.locator("input:focus");
|
||||
await expect(setInput).toBeVisible();
|
||||
await setInput.fill(setName);
|
||||
await setInput.press(finalKey);
|
||||
};
|
||||
|
||||
const createSet = async (sidebar, setName, finalKey = "Enter") => {
|
||||
const tokensTabButton = sidebar
|
||||
.getByRole("button", { name: "Add set" })
|
||||
.click();
|
||||
const createSet = async (sidebar, setName, finalKey = "Enter") => {
|
||||
const tokensTabButton = sidebar
|
||||
.getByRole("button", { name: "Add set" })
|
||||
.click();
|
||||
|
||||
await changeSetInput(sidebar, setName, (finalKey = "Enter"));
|
||||
};
|
||||
await changeSetInput(sidebar, setName, (finalKey = "Enter"));
|
||||
};
|
||||
|
||||
export {
|
||||
setupEmptyTokensFile,
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
|
||||
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
||||
import { createToken, setupTokensFileRender, unfoldTokenType } from "./helpers";
|
||||
import {
|
||||
createToken,
|
||||
setupTokensFileRender,
|
||||
unfoldTokenType,
|
||||
createSet,
|
||||
} from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WasmWorkspacePage.init(page);
|
||||
@ -174,14 +179,14 @@ test.describe("Tokens - node tree", () => {
|
||||
// Rename to move it into the collapsed light group
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("light.base");
|
||||
await tokensUpdateCreateModal
|
||||
.getByRole("button", { name: "Save" })
|
||||
.click();
|
||||
await tokensUpdateCreateModal.getByRole("button", { name: "Save" }).click();
|
||||
|
||||
// After rename, light group should be auto-expanded and both tokens visible
|
||||
await expect(lightGroup).toBeVisible();
|
||||
await expect(lightAccentToken).toBeVisible();
|
||||
await expect(tokensSidebar.getByRole("button", { name: "base" })).toBeVisible();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "base" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User removes node and all child tokens", async ({ page }) => {
|
||||
@ -228,3 +233,64 @@ test.describe("Tokens - node tree", () => {
|
||||
await expect(tokenTypeButton).toHaveAttribute("aria-expanded", "false");
|
||||
});
|
||||
});
|
||||
|
||||
test("User can see an error on token pill and token modal form when token has an error", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensSidebar,
|
||||
tokensUpdateCreateModal,
|
||||
tokenContextMenuForToken,
|
||||
tokenThemesSetsSidebar,
|
||||
} = await setupTokensFileRender(page);
|
||||
|
||||
await createSet(tokenThemesSetsSidebar, "set/first");
|
||||
await tokenThemesSetsSidebar.getByRole("button", { name: "first" }).click();
|
||||
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "first" })
|
||||
.getByRole("checkbox")
|
||||
.click();
|
||||
|
||||
await createSet(tokenThemesSetsSidebar, "set/second");
|
||||
await tokenThemesSetsSidebar.getByRole("button", { name: "second" }).click();
|
||||
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "second" })
|
||||
.getByRole("checkbox")
|
||||
.click();
|
||||
|
||||
await createToken(page, "Border radius", "a.b", "Value", "23");
|
||||
await tokenThemesSetsSidebar.getByRole("button", { name: "first" }).click();
|
||||
await createToken(page, "Border radius", "a", "Value", "25");
|
||||
await tokenThemesSetsSidebar.getByRole("button", { name: "second" }).click();
|
||||
|
||||
const brokenTokenPill = tokensSidebar.getByRole("button", {
|
||||
name: "Group name of a.b conflicts",
|
||||
});
|
||||
await expect(brokenTokenPill).toBeVisible();
|
||||
|
||||
await brokenTokenPill.click({ button: "right" });
|
||||
|
||||
const editTokenButton = page
|
||||
.getByRole("listitem")
|
||||
.filter({ hasText: "Edit token" });
|
||||
await expect(editTokenButton).toBeVisible();
|
||||
await editTokenButton.click();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await expect(nameField).toBeVisible();
|
||||
await expect(nameField).toHaveValue("a.b");
|
||||
|
||||
const errorMessage = tokensUpdateCreateModal.getByText(
|
||||
"Group name of a.b conflicts",
|
||||
);
|
||||
await expect(errorMessage).toBeVisible();
|
||||
|
||||
await nameField.fill("new-name");
|
||||
await expect(errorMessage).not.toBeVisible();
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeEnabled();
|
||||
});
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error (get-in @form [:errors input-name])
|
||||
extra-error (get-in @form [:extra-errors input-name])
|
||||
|
||||
value (get-in @form [:data input-name] "")
|
||||
|
||||
@ -41,9 +42,9 @@
|
||||
:value value})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
(if (or extra-error (and error touched?))
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
:hint-message (:message (or error extra-error))})
|
||||
props)]
|
||||
|
||||
[:> input* props]))
|
||||
|
||||
@ -333,7 +333,7 @@
|
||||
(generic-attribute-actions #{:y} "Y" (assoc context-data :on-update-shape dwta/update-shape-position)))
|
||||
(clean-separators)))}))
|
||||
|
||||
(defn default-actions [{:keys [token selected-token-set-id on-delete-token]}]
|
||||
(defn default-actions [{:keys [token selected-token-set-id on-delete-token errors]}]
|
||||
(let [{:keys [modal]} (dwta/get-token-properties token)
|
||||
on-copy-name #(clipboard/to-clipboard (:name token))
|
||||
on-duplicate-token #(st/emit! (dwtl/duplicate-token (:id token)))]
|
||||
@ -347,6 +347,7 @@
|
||||
:y (.-clientY ^js event)
|
||||
:position :right
|
||||
:fields fields
|
||||
:initial-errors errors
|
||||
:action "edit"
|
||||
:selected-token-set-id selected-token-set-id
|
||||
:token token}))))}
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.colorpicker :as colorpicker]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
|
||||
[app.main.ui.workspace.tokens.management.forms.controls.utils :as csu]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@ -282,10 +283,13 @@
|
||||
(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"}))
|
||||
(if (csu/group-name-conflict-error? error token-name)
|
||||
(swap! form assoc-in [:extra-errors ""] {:message 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)
|
||||
@ -312,7 +316,8 @@
|
||||
(-> state
|
||||
(assoc-in [:data :value value-subfield index field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
(update :extra-errors clean-errors)
|
||||
(update :extra-errors dissoc "")))))))
|
||||
|
||||
(mf/defc indexed-color-input*
|
||||
[{:keys [name tokens token index value-subfield] :rest props}]
|
||||
@ -452,10 +457,12 @@
|
||||
|
||||
(some? error)
|
||||
(let [error' (:message error)]
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors :value value-subfield index input-name] {:message error'})
|
||||
(swap! form assoc-in [:data :value value-subfield index :color-result] "")
|
||||
(reset! hint* {:message error' :type "error"})))
|
||||
(if (csu/group-name-conflict-error? error token-name)
|
||||
(swap! form assoc-in [:extra-errors ""] {:message error})
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors :value value-subfield index input-name] {:message error'})
|
||||
(swap! form assoc-in [:data :value value-subfield index :color-result] "")
|
||||
(reset! hint* {:message error' :type "error"}))))
|
||||
|
||||
:else
|
||||
(let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))
|
||||
|
||||
@ -78,6 +78,9 @@
|
||||
error
|
||||
(get-in @form [:errors name])
|
||||
|
||||
extra-error
|
||||
(get-in @form [:extra-errors name])
|
||||
|
||||
value
|
||||
(get-in @form [:data name] "")
|
||||
|
||||
@ -264,12 +267,11 @@
|
||||
:on-mouse-down dom/prevent-default
|
||||
:on-click toggle-dropdown}]))})
|
||||
props
|
||||
(if (and error touched?)
|
||||
(if (or extra-error (and error touched?))
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
:hint-message (:message (or error extra-error))})
|
||||
props)
|
||||
|
||||
|
||||
{:keys [style ready?]} (use-floating-dropdown is-open input-wrapper-ref wrapper-ref dropdown-ref)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token name token-name]
|
||||
@ -284,9 +286,11 @@
|
||||
(let [touched? (get-in @form [:touched name])]
|
||||
(when touched?
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors name] {:message error})
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(if (csu/group-name-conflict-error? error token-name)
|
||||
(swap! form assoc-in [:extra-errors ""] {:message error})
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors name] {:message error})
|
||||
(reset! hint* {:message error :type "error"})))
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)]
|
||||
(swap! form update :extra-errors dissoc name)
|
||||
(reset! hint* {:message message :type "hint"}))))))))]
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]]
|
||||
[app.main.ui.workspace.tokens.management.forms.controls.utils :as csu]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@ -175,9 +176,11 @@
|
||||
(rx/subs! (fn [{:keys [error value]}]
|
||||
(when touched?
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors input-name] {:message error})
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(if (csu/group-name-conflict-error? error token-name)
|
||||
(swap! form assoc-in [:extra-errors ""] {:message 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"})))))))]
|
||||
@ -205,7 +208,8 @@
|
||||
(-> state
|
||||
(assoc-in [:data :value field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
(update :extra-errors clean-errors)
|
||||
(update :extra-errors dissoc "")))))))
|
||||
|
||||
(mf/defc composite-fonts-combobox*
|
||||
[{:keys [token tokens name] :rest props}]
|
||||
@ -306,8 +310,11 @@
|
||||
|
||||
(some? error)
|
||||
(let [error' (:message error)]
|
||||
(swap! form assoc-in [:extra-errors :value input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))
|
||||
(if (csu/group-name-conflict-error? error' token-name)
|
||||
(swap! form assoc-in [:extra-errors ""] {:message error'})
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors :value input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))))
|
||||
|
||||
:else
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.ui.ds.controls.input :as ds]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.tokens.management.forms.controls.utils :as csu]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@ -248,9 +249,11 @@
|
||||
(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"}))
|
||||
(if (csu/group-name-conflict-error? error token-name)
|
||||
(swap! form assoc-in [:extra-errors ""] {:message 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"}))))))))]
|
||||
@ -274,7 +277,8 @@
|
||||
(assoc-in [:data :value field] (if trim? (str/trim value) value))
|
||||
(assoc-in [:touched :value field] true)
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
(update :extra-errors clean-errors)
|
||||
(update :extra-errors dissoc "")))))))
|
||||
|
||||
(mf/defc input-composite*
|
||||
[{:keys [name tokens token] :rest props}]
|
||||
@ -286,6 +290,9 @@
|
||||
error
|
||||
(get-in @form [:errors :value input-name])
|
||||
|
||||
extra-error
|
||||
(get-in @form [:extra-errors :value input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data :value input-name] "")
|
||||
|
||||
@ -319,9 +326,9 @@
|
||||
:hint-message (:message hint)
|
||||
:hint-type (:type hint)})
|
||||
props
|
||||
(if (and touched? error)
|
||||
(if (or extra-error (and touched? error))
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
:hint-message (:message (or error extra-error))})
|
||||
props)
|
||||
|
||||
props (if (and (not error) (= input-name :reference))
|
||||
@ -347,8 +354,11 @@
|
||||
|
||||
(some? error)
|
||||
(let [error' (:message error)]
|
||||
(swap! form assoc-in [:extra-errors :value input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))
|
||||
(if (csu/group-name-conflict-error? error' token-name)
|
||||
(swap! form assoc-in [:extra-errors ""] {:message error'})
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors :value input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))))
|
||||
|
||||
:else
|
||||
(let [input-value (get-in @form [:data :value input-name] "")
|
||||
@ -386,7 +396,8 @@
|
||||
(-> state
|
||||
(assoc-in [:data :value value-subfield index field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
(update :extra-errors clean-errors)
|
||||
(update :extra-errors dissoc "")))))))
|
||||
|
||||
(mf/defc input-indexed*
|
||||
[{:keys [name tokens token index value-subfield] :rest props}]
|
||||
@ -398,6 +409,9 @@
|
||||
error
|
||||
(get-in @form [:errors :value value-subfield index input-name])
|
||||
|
||||
extra-error
|
||||
(get-in @form [:extra-errors :value value-subfield index input-name])
|
||||
|
||||
value-from-form
|
||||
(get-in @form [:data :value value-subfield index input-name] "")
|
||||
|
||||
@ -428,9 +442,9 @@
|
||||
:hint-message (:message hint)
|
||||
:hint-type (:type hint)})
|
||||
props
|
||||
(if error
|
||||
(if (or error extra-error)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
:hint-message (:message (or error extra-error))})
|
||||
props)
|
||||
|
||||
props
|
||||
@ -457,8 +471,11 @@
|
||||
|
||||
(some? error)
|
||||
(let [error' (:message error)]
|
||||
(swap! form assoc-in [:extra-errors :value value-subfield index input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))
|
||||
(if (csu/group-name-conflict-error? error' token-name)
|
||||
(swap! form assoc-in [:extra-errors ""] {:message error'})
|
||||
|
||||
(do (swap! form assoc-in [:extra-errors :value value-subfield index input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))))
|
||||
|
||||
:else
|
||||
(let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))
|
||||
|
||||
@ -37,7 +37,8 @@
|
||||
(mf/deps input-name)
|
||||
(fn [id]
|
||||
(let [is-inner? (= id "inner")]
|
||||
(swap! form assoc-in [:data :value indexed-type index input-name] is-inner?))))
|
||||
(swap! form assoc-in [:data :value indexed-type index input-name] is-inner?)
|
||||
(swap! form update :extra-errors dissoc ""))))
|
||||
|
||||
props (mf/spread-props props {:default-selected (if value "inner" "drop")
|
||||
:variant "ghost"
|
||||
|
||||
@ -119,4 +119,9 @@
|
||||
(not-empty)))))
|
||||
|
||||
(defn focusable-options [options]
|
||||
(filter #(= (:type %) :token) options))
|
||||
(filter #(= (:type %) :token) options))
|
||||
|
||||
(defn group-name-conflict-error?
|
||||
[error token-name]
|
||||
(let [translated-string (tr "errors.tokens.name-collision" token-name)]
|
||||
(= error translated-string)))
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc form-container*
|
||||
[{:keys [token token-type] :rest props}]
|
||||
[{:keys [token token-type initial-errors] :rest props}]
|
||||
(let [token-type
|
||||
(or (:type token) token-type)
|
||||
|
||||
@ -38,7 +38,8 @@
|
||||
props
|
||||
(mf/spread-props props {:token-type token-type
|
||||
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
||||
:token token})
|
||||
:token token
|
||||
:initial-errors initial-errors})
|
||||
text-case-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-case-value-enter")})
|
||||
text-decoration-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.text-decoration-value-enter")})
|
||||
font-weight-props (mf/spread-props props {:input-value-placeholder (tr "workspace.tokens.font-weight-value-enter")})
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
[app.main.ui.ds.buttons.button :refer [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 fc]
|
||||
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
||||
[app.main.ui.workspace.tokens.management.forms.validators :refer [default-validate-token]]
|
||||
@ -61,6 +62,7 @@
|
||||
make-schema
|
||||
input-component
|
||||
initial
|
||||
initial-errors
|
||||
value-type
|
||||
value-subfield
|
||||
input-value-placeholder] :as props}]
|
||||
@ -112,10 +114,21 @@
|
||||
:value (:value token "")
|
||||
:description (:description token "")}))
|
||||
|
||||
initial-general-errors (mf/with-memo [token initial initial-errors]
|
||||
(when initial-errors
|
||||
(if (= :error.style-dictionary/missing-reference (:error/code (first initial-errors)))
|
||||
(if (or (= value-type :composite)
|
||||
(= value-type :indexed))
|
||||
{:value {:reference {:message (wte/resolve-error-message (first initial-errors))}}}
|
||||
{:value {:message (wte/resolve-error-message (first initial-errors))}})
|
||||
{"" {:message (wte/resolve-error-message (first initial-errors))}})))
|
||||
form
|
||||
(fm/use-form :schema schema
|
||||
:initial-errors initial-general-errors
|
||||
:initial initial)
|
||||
|
||||
general-errors (get-in @form [:extra-errors ""])
|
||||
|
||||
on-toggle-tab
|
||||
(mf/use-fn
|
||||
(mf/deps form)
|
||||
@ -288,6 +301,10 @@
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:is-optional true}]]
|
||||
(when (some? general-errors)
|
||||
[:> context-notification* {:level :warning
|
||||
:appearance :ghost}
|
||||
(:message general-errors)])
|
||||
|
||||
[:div {:class (stl/css-case :button-row true
|
||||
:with-delete (= action "edit"))}
|
||||
|
||||
@ -75,7 +75,7 @@
|
||||
|
||||
(mf/defc token-update-create-modal
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [x y position token token-type action selected-token-set-id] :as _args}]
|
||||
[{:keys [x y position token token-type action selected-token-set-id initial-errors] :as _args}]
|
||||
(let [wrapper-style (use-viewport-position-style x y position token-type)
|
||||
modal-size-large* (mf/use-state (or (= token-type :typography)
|
||||
(= token-type :color)
|
||||
@ -102,6 +102,7 @@
|
||||
:action action
|
||||
:selected-token-set-id selected-token-set-id
|
||||
:token-type token-type
|
||||
:initial-errors initial-errors
|
||||
:on-display-colorpicker update-modal-size}]]))
|
||||
|
||||
;; Modals ----------------------------------------------------------------------
|
||||
|
||||
@ -100,18 +100,19 @@
|
||||
tokens
|
||||
(mf/with-memo [tokens]
|
||||
(vec (sort-by :name tokens)))
|
||||
|
||||
expandable? (d/nilv (seq tokens) false)
|
||||
|
||||
on-pill-context-menu
|
||||
(mf/use-fn
|
||||
(mf/deps active-theme-tokens)
|
||||
(fn [event token]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (dwtl/assign-token-context-menu
|
||||
{:type :token
|
||||
:position (dom/get-client-position event)
|
||||
:errors (:errors token)
|
||||
:token-id (:id token)}))))
|
||||
(let [resolved-token (get active-theme-tokens (:name token))]
|
||||
(st/emit! (dwtl/assign-token-context-menu
|
||||
{:type :token
|
||||
:position (dom/get-client-position event)
|
||||
:errors (:errors resolved-token)
|
||||
:token-id (:id token)})))))
|
||||
|
||||
on-node-context-menu
|
||||
(mf/use-fn
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
[app.config :as cf]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.color :as dwtc]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
@ -101,7 +102,7 @@
|
||||
|
||||
(defn- generate-tooltip
|
||||
"Generates a tooltip for a given token"
|
||||
[is-viewer shape theme-token token half-applied no-valid-value ref-not-in-active-set]
|
||||
[is-viewer shape theme-token token half-applied no-valid-value ref-not-in-active-set is-name-collision errors]
|
||||
(let [{:keys [name type resolved-value value]} token
|
||||
resolved-value-theme (:resolved-value theme-token)
|
||||
resolved-value (or resolved-value-theme resolved-value)
|
||||
@ -128,6 +129,9 @@
|
||||
ref-not-in-active-set
|
||||
(tr "workspace.tokens.ref-not-valid")
|
||||
|
||||
is-name-collision
|
||||
(wte/resolve-error-message (first errors))
|
||||
|
||||
no-valid-value
|
||||
(tr "workspace.tokens.value-not-valid")
|
||||
|
||||
@ -177,7 +181,6 @@
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [on-click token on-context-menu selected-shapes is-selected-inside-layout active-theme-tokens]}]
|
||||
(let [{:keys [name value type]} token
|
||||
|
||||
resolved-token (get active-theme-tokens (:name token))
|
||||
errors (:errors resolved-token)
|
||||
|
||||
@ -218,11 +221,17 @@
|
||||
(and is-reference?
|
||||
(not (contains-reference-value? value active-theme-tokens))))
|
||||
|
||||
name-collision (->> errors
|
||||
(first)
|
||||
(:error/code)
|
||||
(= :error.token/name-collision))
|
||||
|
||||
no-valid-value (seq errors)
|
||||
|
||||
errors?
|
||||
(or ref-not-in-active-set
|
||||
no-valid-value)
|
||||
no-valid-value
|
||||
name-collision)
|
||||
|
||||
color
|
||||
(when (cfo/color-token? token)
|
||||
@ -266,12 +275,12 @@
|
||||
|
||||
on-hover
|
||||
(mf/use-fn
|
||||
(mf/deps selected-shapes is-viewer? active-theme-tokens token half-applied? no-valid-value ref-not-in-active-set)
|
||||
(mf/deps selected-shapes is-viewer? active-theme-tokens token half-applied? no-valid-value ref-not-in-active-set name-collision errors)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
theme-token (get active-theme-tokens name)
|
||||
title (generate-tooltip is-viewer? (first selected-shapes) theme-token token
|
||||
half-applied? no-valid-value ref-not-in-active-set)]
|
||||
half-applied? no-valid-value ref-not-in-active-set name-collision errors)]
|
||||
(dom/set-attribute! node "title" title))))]
|
||||
|
||||
[:button {:class (stl/css-case
|
||||
@ -301,7 +310,9 @@
|
||||
[:> icon*
|
||||
{:icon-id i/broken-link
|
||||
:class (stl/css :token-pill-icon)
|
||||
:aria-label (tr "workspace.tokens.missing-reference")}]
|
||||
:aria-label (if name-collision
|
||||
(wte/resolve-error-message (first errors))
|
||||
(tr "workspace.tokens.missing-reference"))}]
|
||||
|
||||
color
|
||||
[:> swatch* {:background color
|
||||
|
||||
@ -45,11 +45,15 @@
|
||||
valid?)))))
|
||||
|
||||
(defn- make-initial-state
|
||||
[initial-data]
|
||||
[initial-data initial-errors]
|
||||
(let [initial (if (fn? initial-data) (initial-data) initial-data)
|
||||
initial (d/nilv initial {})]
|
||||
initial (d/nilv initial {})
|
||||
initial-errors (if (fn? initial-errors) (initial-errors) initial-errors)
|
||||
initial-errors (d/nilv initial-errors {})]
|
||||
{:initial initial
|
||||
:initial-errors initial-errors
|
||||
:data initial
|
||||
:extra-errors initial-errors
|
||||
:errors {}
|
||||
:touched {}}))
|
||||
|
||||
@ -64,9 +68,11 @@
|
||||
(-reset! [_ new-value]
|
||||
(if (nil? new-value)
|
||||
(let [initial (-> (mf/ref-val internal-state)
|
||||
(get :initial)
|
||||
(make-initial-state))]
|
||||
(mf/set-ref-val! internal-state initial))
|
||||
(get :initial))
|
||||
initial-errors (-> (mf/ref-val internal-state)
|
||||
(get :initial-errors))
|
||||
state (make-initial-state initial initial-errors)]
|
||||
(mf/set-ref-val! internal-state state))
|
||||
(mf/set-ref-val! internal-state new-value))
|
||||
(rerender-fn))
|
||||
|
||||
@ -92,12 +98,12 @@
|
||||
(rerender-fn)))))
|
||||
|
||||
(defn use-form
|
||||
[& {:keys [initial schema validators] :as opts}]
|
||||
[& {:keys [initial initial-errors schema validators] :as opts}]
|
||||
(let [rerender-fn (use-rerender-fn)
|
||||
|
||||
initial
|
||||
(mf/with-memo [initial]
|
||||
(make-initial-state initial))
|
||||
(mf/with-memo [initial initial-errors]
|
||||
(make-initial-state initial initial-errors))
|
||||
|
||||
internal-state
|
||||
(mf/use-ref initial)
|
||||
@ -131,14 +137,16 @@
|
||||
(assoc-in [:touched field] true)
|
||||
(assoc-in [:data field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
(update :extra-errors clean-errors)
|
||||
(update :extra-errors dissoc "")))))))
|
||||
|
||||
(defn update-input-value!
|
||||
[form field value]
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data field] value)
|
||||
(update :errors dissoc field)))))
|
||||
(update :errors dissoc field)
|
||||
(update :extra-errors dissoc "")))))
|
||||
|
||||
(defn on-input-blur
|
||||
[form field]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user