From 8278e5f202b30be7163d01b14895f3c53f61712c Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Tue, 27 Oct 2026 11:44:57 +0100 Subject: [PATCH] :recycle: Use the DS select for the gradient type in the colorpicker The Linear / Radial select of the gradient options used the legacy select. It now uses select* with string ids, converted back to the gradient type keyword in the handler. Visible changes: the DS select look (label 1px higher, DS list with taller rows and the DS check mark, opening 2px lower). The list still opens inside the colorpicker, so it needs no portal. The "Create a RADIAL gradient" e2e test picks the type by role instead of a DOM position that only matched the legacy markup. New ds visual test "Gradient type select": closed, hover, open and changed. AI-assisted-by: claude-opus-5-5 --- .../playwright/ui/specs/colorpicker.spec.js | 15 +++--- .../ui/visual-specs/workspace.spec.js | 51 +++++++++++++++++++ .../ui/workspace/colorpicker/gradients.cljs | 20 ++++---- 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/frontend/playwright/ui/specs/colorpicker.spec.js b/frontend/playwright/ui/specs/colorpicker.spec.js index ee9dbcd770..6625cb4b8a 100644 --- a/frontend/playwright/ui/specs/colorpicker.spec.js +++ b/frontend/playwright/ui/specs/colorpicker.spec.js @@ -144,15 +144,14 @@ test("Create a RADIAL gradient", async ({ page }) => { const gradOption = workspacePage.page.getByText("Gradient"); await gradOption.click(); - const gradTypeOptions = workspacePage.colorpicker - .locator("div") - .filter({ hasText: "Linear" }) - .nth(3); - await gradTypeOptions.click(); + const gradTypeSelect = workspacePage.colorpicker + .getByRole("combobox") + .filter({ hasText: "Linear" }); + await gradTypeSelect.click(); - const gradRadialOption = workspacePage.page - .locator("li") - .filter({ hasText: "Radial" }); + const gradRadialOption = workspacePage.page.getByRole("option", { + name: "Radial", + }); await gradRadialOption.click(); const addStopBtn = workspacePage.page.getByRole("button", { diff --git a/frontend/playwright/ui/visual-specs/workspace.spec.js b/frontend/playwright/ui/visual-specs/workspace.spec.js index a9a4ef2bf3..2db7816b16 100644 --- a/frontend/playwright/ui/visual-specs/workspace.spec.js +++ b/frontend/playwright/ui/visual-specs/workspace.spec.js @@ -1161,3 +1161,54 @@ test.describe("Assets tab controls", () => { await expect(modal).toHaveScreenshot(shot); }); }); + +test.describe("Gradient type select", () => { + test("Shows the gradient type select closed, open and changed", async ({ + page, + }) => { + const shot = { maxDiffPixelRatio: 0, threshold: 0 }; + const workspace = new WorkspacePage(page); + await workspace.setupEmptyFile(); + await workspace.mockRPC(/get\-file\?/, "workspace/get-file-not-empty.json"); + await workspace.goToWorkspace({ + fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374", + pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375", + }); + await workspace.clickLeafLayer("Rectangle"); + await page.getByRole("button", { name: "#B1B2B5" }).click(); + await page.getByText("Solid").click(); + await page.getByText("Gradient").click(); + + const typeSelect = workspace.colorpicker + .getByRole("combobox") + .filter({ hasText: "Linear" }); + await expect(typeSelect).toBeVisible(); + const pickerBox = await workspace.colorpicker.boundingBox(); + const selectBox = await typeSelect.boundingBox(); + // The gradient bar above the select and room below it for the open list. + const clip = { + x: Math.floor(pickerBox.x), + y: Math.floor(selectBox.y) - 60, + width: Math.ceil(pickerBox.width), + height: 200, + }; + await page.mouse.move(0, 0); + await expect(page).toHaveScreenshot({ clip, ...shot }); + + await typeSelect.hover(); + await expect(page).toHaveScreenshot({ clip, ...shot }); + + await typeSelect.click(); + const radial = page.getByRole("option", { name: "Radial" }); + await expect(radial).toBeVisible(); + await page.mouse.move(0, 0); + await expect(page).toHaveScreenshot({ clip, ...shot }); + + await radial.click(); + await expect( + workspace.colorpicker.getByRole("combobox").filter({ hasText: "Radial" }), + ).toBeVisible(); + await page.mouse.move(0, 0); + await expect(page).toHaveScreenshot({ clip, ...shot }); + }); +}); diff --git a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs index 8338544f73..e1817621c6 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs @@ -15,8 +15,8 @@ [app.main.features :as features] [app.main.ui.components.numeric-input :as deprecated-input] [app.main.ui.components.reorder-handler :refer [reorder-handler*]] - [app.main.ui.components.select :refer [select]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.controls.select :refer [select*]] [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.formats :as fmt] [app.main.ui.hooks :as h] @@ -167,6 +167,10 @@ :on-focus handle-focus-stop-color :on-blur handle-blur-stop-color}]])) +(def ^:private gradient-type-options + [{:id "linear-gradient" :label "Linear"} + {:id "radial-gradient" :label "Radial"}]) + (mf/defc gradients* [{:keys [type stops @@ -200,9 +204,9 @@ handle-change-type (mf/use-callback (mf/deps on-change-type) - (fn [event] + (fn [type-id] (when on-change-type - (on-change-type event)))) + (on-change-type (keyword type-id))))) handle-add-stop (mf/use-callback @@ -336,12 +340,10 @@ :style {"--position" (dm/str (* offset 100) "%")}}]])]] [:div {:class (stl/css :gradient-options)} - [:& select - {:default-value type - :options [{:value :linear-gradient :label "Linear"} - {:value :radial-gradient :label "Radial"}] - :on-change handle-change-type - :class (stl/css :gradient-options-select)}] + [:> select* {:default-selected (d/name (d/nilv type :linear-gradient)) + :options gradient-type-options + :on-change handle-change-type + :wrapper-class (stl/css :gradient-options-select)}] [:div {:class (stl/css :gradient-options-buttons)} [:> icon-button* {:variant "ghost"