diff --git a/frontend/playwright/ui/specs/tokens/apply.spec.js b/frontend/playwright/ui/specs/tokens/apply.spec.js index d4b48a54d4..a0e91b7c60 100644 --- a/frontend/playwright/ui/specs/tokens/apply.spec.js +++ b/frontend/playwright/ui/specs/tokens/apply.spec.js @@ -66,6 +66,74 @@ test.describe("Tokens: Apply token", () => { await expect(canvasSection.getByText("black")).toBeVisible(); }); + test("Canvas background follows its color token when the active set changes", async ({ + page, + }) => { + const { tokenThemesSetsSidebar, tokensUpdateCreateModal } = + await setupTokensFileRender(page); + + const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); + + // Creates a set holding a "canvas-bg" color token with `value`. + const createSetWithCanvasToken = async (setName, value) => { + await createSet(tokenThemesSetsSidebar, setName); + const setButton = tokenThemesSetsSidebar.getByRole("button", { + name: setName, + }); + await setButton.click(); + + await tokensTabPanel + .getByRole("button", { name: "Add Token: Color" }) + .click(); + await expect(tokensUpdateCreateModal).toBeVisible(); + await tokensUpdateCreateModal.getByLabel("Name").fill("canvas-bg"); + await tokensUpdateCreateModal.getByLabel("Value").fill(value); + + const submitButton = tokensUpdateCreateModal.getByRole("button", { + name: "Save", + }); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + await expect(tokensUpdateCreateModal).not.toBeVisible(); + + return setButton; + }; + + const firstSet = await createSetWithCanvasToken("first-bg", "#ff0000"); + const secondSet = await createSetWithCanvasToken("second-bg", "#00ff00"); + + await firstSet.getByRole("checkbox").click(); + + // No shape is selected, so the right sidebar shows the canvas options. + const canvasSection = page.getByRole("region", { + name: "Canvas background section", + }); + await canvasSection.getByRole("button").first().click(); + + const colorPicker = page.getByTestId("colorpicker"); + await expect(colorPicker).toBeVisible(); + await colorPicker.getByTestId("opt-token-color").click(); + await colorPicker.getByRole("button", { name: "canvas-bg" }).click(); + await page.keyboard.press("Escape"); + await expect(colorPicker).not.toBeVisible(); + + const canvas = page.getByTestId("canvas-wasm-shapes"); + await expect(canvas).toHaveCSS("background-color", "rgb(255, 0, 0)"); + + await firstSet.getByRole("checkbox").click(); + await secondSet.getByRole("checkbox").click(); + + await expect(canvas).toHaveCSS("background-color", "rgb(0, 255, 0)"); + await expect(canvasSection.getByText("canvas-bg")).toBeVisible(); + + await firstSet.getByRole("checkbox").click(); + await expect(canvas).toHaveCSS("background-color", "rgb(0, 255, 0)"); + + await secondSet.getByRole("checkbox").click(); + await expect(canvas).toHaveCSS("background-color", "rgb(255, 0, 0)"); + await expect(canvasSection.getByText("canvas-bg")).toBeVisible(); + }); + test("User applies border-radius token to a shape from sidebar", async ({ page, }) => { diff --git a/frontend/src/app/main/data/workspace/tokens/propagation.cljs b/frontend/src/app/main/data/workspace/tokens/propagation.cljs index aa8a4b3733..04472fd046 100644 --- a/frontend/src/app/main/data/workspace/tokens/propagation.cljs +++ b/frontend/src/app/main/data/workspace/tokens/propagation.cljs @@ -7,12 +7,14 @@ (ns app.main.data.workspace.tokens.propagation (:require [app.common.data :as d] + [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] [app.common.files.tokens :as cfo] [app.common.logging :as l] [app.common.time :as ct] [app.common.types.token :as ctt] [app.config :as cf] + [app.main.data.changes :as dch] [app.main.data.helpers :as dsh] [app.main.data.style-dictionary :as sd] [app.main.data.tokenscript :as ts] @@ -131,8 +133,27 @@ update-infos))) shapes-update-info)) +(defn- update-canvas-background + "Set the background of `page-id` to the resolved value of its linked + color token, when that value differs from the current background." + [page-id resolved-tokens] + (ptk/reify ::update-canvas-background + ptk/WatchEvent + (watch [it state _] + (let [page (dsh/lookup-page state page-id) + token-name (:background-token page) + color (some-> (get-in resolved-tokens [token-name :resolved-value]) + (dwta/value->color) + (:color))] + (when (and (some? color) (not= color (:background page))) + (rx/of (dch/commit-changes + (-> (pcb/empty-changes it) + (pcb/with-page page) + (pcb/mod-page {:background color}))))))))) + (defn propagate-tokens - "Propagate tokens values to all shapes where they are applied" + "Propagate tokens values to all shapes and page backgrounds where they + are applied" [state resolved-tokens] (let [file-id (get state :current-file-id) current-page-id (get state :current-page-id) @@ -168,6 +189,9 @@ (when (seq observable) (apply rx/merge observable)) (when (seq normal) (rx/concat-all (rx/of normal))) + (when (:background-token page) + (rx/of (update-canvas-background page-id resolved-tokens))) + (->> (rx/from frame-ids) (rx/mapcat (fn [frame-id] (rx/of (dwt/clear-thumbnail file-id page-id frame-id "frame") diff --git a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs index 62eb174c02..358d611703 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs @@ -260,10 +260,10 @@ on-drag (mf/use-fn - (mf/deps path) + (mf/deps id is-selected on-select) (fn [_] (when-not is-selected - (on-select path)))) + (on-select id)))) on-drop (mf/use-fn diff --git a/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs b/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs index 7ba6ac9632..fdd14413f2 100644 --- a/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs +++ b/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs @@ -308,6 +308,40 @@ (t/is (= (get-in rect-2' [:strokes 0 :stroke-color]) "#ff0000")) (t/is (= (get-in rect-2' [:strokes 0 :stroke-opacity]) 0.5)))))))))) +(t/deftest test-switch-set-updates-canvas-color + (t/testing "switching the active set updates a canvas background linked to a color token" + (t/async + done + (let [file (ctht/sample-file-with-tokens + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a) + :name "Set A")) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b) + :name "Set B")) + (ctob/add-token (cthi/id :set-a) + (ctob/make-token {:name "canvas.bg" + :value "#ff0000" + :type :color})) + (ctob/add-token (cthi/id :set-b) + (ctob/make-token {:name "canvas.bg" + :value "#00ff00" + :type :color}))) + ;; Both sets active: "Set B" comes last, so its value wins. + :status-fn #(ctos/set-tokens-status % #{} #{(cthi/id :set-a) + (cthi/id :set-b)})) + page-id (cthf/current-page-id file) + file (update-in file [:data :pages-index page-id] + assoc :background "#00ff00" :background-token "canvas.bg") + store (ths/setup-store file) + events [(dwtl/set-enabled-token-set (cthi/id :set-b) false)]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + page' (get-in file' [:data :pages-index page-id])] + (t/is (= "#ff0000" (:background page'))) + (t/is (= "canvas.bg" (:background-token page')))))))))) + (t/deftest test-apply-dimensions (t/testing "applies dimensions token and updates the shapes width and height" (t/async