♻️ 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
This commit is contained in:
Eva Marco 2026-10-27 11:44:57 +01:00
parent 29e79edc66
commit 8278e5f202
3 changed files with 69 additions and 17 deletions

View File

@ -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", {

View File

@ -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 });
});
});

View File

@ -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"