Compare commits

...

2 Commits

Author SHA1 Message Date
Eva Marco
ff79374648 🐛 Fix multiselection on multiple token applied 2026-01-30 09:32:28 +01:00
Eva Marco
db7ab5105d 🐛 Allow detach broken token from input 2026-01-30 09:32:28 +01:00
12 changed files with 192 additions and 46 deletions

View File

@@ -48,6 +48,11 @@
applied-tokens)
(into {}))))
(defn remove-attribute-for-detached-token
"Removes applied tokens when token-id is nil for the given `attributes` set from `applied-tokens`."
[attributes applied-tokens]
(apply dissoc applied-tokens attributes))
(defn token-attribute-applied?
"Test if `token` is applied to a `shape` on single `token-attribute`."
[token shape token-attribute]

View File

@@ -831,15 +831,102 @@ test.describe("Tokens: Apply token", () => {
});
await detachButton.click();
await expect(marginPillXL).not.toBeVisible();
const horizontalMarginInput = layoutItemSectionSidebar.getByText('Horizontal marginOpen token');
const horizontalMarginInput = layoutItemSectionSidebar.getByText(
"Horizontal marginOpen token",
);
await expect(horizontalMarginInput).toBeVisible();
const tokenDropdown = horizontalMarginInput.getByRole('button', { name: 'Open token list' });
const tokenDropdown = horizontalMarginInput.getByRole("button", {
name: "Open token list",
});
await tokenDropdown.click();
await expect(dimensionTokenOptionXl).toBeVisible();
await dimensionTokenOptionXl.click();
await expect(marginPillXL).toBeVisible();
});
});
test.describe("Tokens: Detach token", () => {
test("User applies border-radius token to a shape from sidebar", async ({
page,
}) => {
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
// Open tokens sections on left sidebar
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
// Unfold border radius tokens
await page.getByRole("button", { name: "Border Radius 3" }).click();
await expect(
tokensSidebar.getByRole("button", { name: "borderRadius" }),
).toBeVisible();
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
await expect(
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
).toBeVisible();
// Apply border radius token from token panels
await tokensSidebar
.getByRole("button", { name: "borderRadius.sm" })
.click();
// Check if border radius sections is visible on right sidebar
const borderRadiusSection = page.getByRole("region", {
name: "border-radius-section",
});
await expect(borderRadiusSection).toBeVisible();
// Check if token pill is visible on design tab on right sidebar
const brTokenPillSM = borderRadiusSection.getByRole("button", {
name: "borderRadius.sm",
});
await expect(brTokenPillSM).toBeVisible();
await brTokenPillSM.click();
// Rename token
await tokensSidebar
.getByRole("button", { name: "borderRadius.sm" })
.click({ button: "right" });
await expect(page.getByText("Edit token")).toBeVisible();
await page.getByText("Edit token").click();
const editModal = page.getByTestId("token-update-create-modal");
await expect(editModal).toBeVisible();
await expect(
editModal.getByRole("textbox", { name: "Name" }),
).toBeVisible();
await editModal
.getByRole("textbox", { name: "Name" })
.fill("BorderRadius.smBis");
const submitButton = editModal.getByRole("button", { name: "Save" });
await expect(submitButton).toBeEnabled();
await submitButton.click();
await expect(page.getByText("Don't remap")).toBeVisible();
await page.getByText("Don't remap").click();
const brokenPill = borderRadiusSection.getByRole("button", {
name: "This token is not in any",
});
await expect(brokenPill).toBeVisible();
// Detach broken token
const detachButton = borderRadiusSection.getByRole("button", {
name: "Detach token",
});
await detachButton.click();
await expect(brokenPill).not.toBeVisible();
//De-select and select shape again to double check token is detached
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers.getByTestId("layer-row").nth(0).click();
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
await expect(brokenPill).not.toBeVisible();
});
});

View File

@@ -710,6 +710,21 @@
(fn [shape]
(update shape :applied-tokens remove-token))))))))
(defn detach-token
"Removes `attributes` when token-id is nil.
Doesn't update shape attributes."
[{:keys [attributes shape-ids] :as _props}]
(ptk/reify ::detach-token
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(let [remove-token #(when % (cft/remove-attribute-for-detached-token attributes %))]
(dwsh/update-shapes
shape-ids
(fn [shape]
(update shape :applied-tokens remove-token))))))))
(defn toggle-token
[{:keys [token attrs shape-ids expand-with-children]}]
(ptk/reify ::on-toggle-token

View File

@@ -61,9 +61,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
on-detach-all
(mf/use-fn

View File

@@ -166,9 +166,16 @@
(d/without-nils))]
(mf/set-ref-val! prev-colors-ref
(conj prev-colors color))
(st/emit! (dwta/unapply-token {:attributes attr
:token token
:shape-ids [(:shape-id op)]})))))))
(prn token)
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes attr
:shape-ids [(:shape-id op)]}))
(st/emit! (dwta/detach-token {:attributes attr
:shape-ids [(:shape-id op)]})))
#_(st/emit! (dwta/unapply-token {:attributes attr
:token token
:shape-ids [(:shape-id op)]})))))))
select-only
(mf/use-fn

View File

@@ -74,7 +74,6 @@
render-wasm? (feat/use-feature "render-wasm/v1")
^boolean
multiple? (= :multiple fills)
@@ -184,9 +183,13 @@
(mf/use-fn
(mf/deps ids)
(fn [token]
(st/emit! (dwta/unapply-token {:attributes #{:fill}
:token token
:shape-ids ids}))))]
(prn "on-detach-token" token)
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{:fill}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{:fill}
:shape-ids ids})))))]
(mf/with-layout-effect [hide-on-export]
(when-let [checkbox (mf/ref-val checkbox-ref)]
@@ -215,7 +218,8 @@
(when open?
[:div {:class (stl/css :fill-content)}
(cond
(= :multiple fills)
(or (= :multiple fills)
(= :multiple fill-token-applied))
[:div {:class (stl/css :fill-multiple)}
[:div {:class (stl/css :fill-multiple-label)}
(tr "settings.multiple")]

View File

@@ -73,9 +73,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
current-blend-mode (or (get values :blend-mode) :normal)
current-opacity (opacity->string (:opacity values))

View File

@@ -340,9 +340,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
on-focus
(mf/use-fn
@@ -475,9 +478,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
on-p1-change
(mf/use-fn (mf/deps on-change') #(on-change' % :p1))
@@ -722,9 +728,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
on-row-gap-change
(mf/use-fn (mf/deps on-change') #(on-change' %1 %2 :row-gap))

View File

@@ -98,9 +98,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
on-detach-horizontal
(mf/use-fn
@@ -221,9 +224,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
on-focus
(mf/use-fn
@@ -551,9 +557,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
on-size-change
(mf/use-fn

View File

@@ -320,9 +320,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attr]
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes #{attr}
:shape-ids ids})))))
;; CLIP CONTENT AND SHOW IN VIEWER
on-change-clip-content

View File

@@ -172,9 +172,12 @@
(mf/use-fn
(mf/deps ids)
(fn [token attrs]
(st/emit! (dwta/unapply-token {:attributes attrs
:token token
:shape-ids ids}))))]
(if (seq token)
(st/emit! (dwta/unapply-token {:token (first token)
:attributes attrs
:shape-ids ids}))
(st/emit! (dwta/detach-token {:attributes attrs
:shape-ids ids})))))]
[:section {:class (stl/css :stroke-section)
:aria-label "stroke-section"}

View File

@@ -85,14 +85,13 @@
(mf/use-fn
(mf/deps detach-token token applied-token-name)
(fn []
(let [token (or token applied-token-name)]
(detach-token token))))
(detach-token token)))
has-errors (some? (:errors token))
token-name (:name token)
resolved (:resolved-value token)
not-active (and (empty? active-tokens)
(nil? token))
not-active (or (empty? active-tokens)
(nil? token))
id (dm/str (:id token) "-name")
swatch-tooltip-content (cond
not-active
@@ -344,7 +343,6 @@
(mf/with-effect [color prev-color disable-picker]
(when (and (not disable-picker) (not= prev-color color))
(modal/update-props! :colorpicker {:data (parse-color color)})))
[:div {:class [class row-class]}
;; Drag handler
(when (some? on-reorder)