Skip to content

Commit e0e12de

Browse files
authored
feat(layers): add Style panel button to layer cards (#1944)
* feat(layers): add Style panel button to layer cards Opening a layer's styling controls previously meant finding the entry inside the layer's overflow menu. A palette button on the card itself selects the layer and opens the Style panel in one click, and the action row now wraps so the extra button does not overflow on narrow panels. * Address review feedback - Return early from the layer card key handler when the event came from a nested control. The card is a role="button" wrapper that called preventDefault on Enter, which cancelled the native activation of every action button inside it, the new palette button included. - Add an e2e test that opens the Style panel from the card by Enter and by Space. It fails without the guard above. - Match the Style panel aside by exact accessible name, so the collapsed rail ("Layer style (collapsed)") cannot satisfy the default substring match and mask the initial toHaveCount(0) assertion. - Note in the user guide that the palette button appears only when the built-in Style panel is enabled, since onOpenStylePanel is optional.
1 parent d036067 commit e0e12de

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

apps/geolibre-desktop/src/components/panels/LayerPanel.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3210,6 +3210,10 @@ export function LayerPanel({
32103210
aria-pressed={selectedLayerIds.has(layer.id)}
32113211
onClick={(e) => handleLayerSelection(e, layer.id)}
32123212
onKeyDown={(e) => {
3213+
// Only act on the card itself: preventDefault here would
3214+
// otherwise cancel the Enter activation of the action
3215+
// buttons nested inside it.
3216+
if (e.target !== e.currentTarget) return;
32133217
if (e.key === "Enter" || e.key === " ") {
32143218
e.preventDefault();
32153219
setSelectedLayerIds(new Set([layer.id]));
@@ -3389,7 +3393,7 @@ export function LayerPanel({
33893393
onChange={(v) => setLayerOpacity(layer.id, v)}
33903394
/>
33913395
)}
3392-
<div className="mt-2 flex gap-1">
3396+
<div className="mt-2 flex flex-wrap gap-1">
33933397
<Button
33943398
variant="ghost"
33953399
size="icon"
@@ -3449,6 +3453,22 @@ export function LayerPanel({
34493453
>
34503454
<MousePointerClick className="h-3.5 w-3.5" />
34513455
</Button>
3456+
{onOpenStylePanel && (
3457+
<Button
3458+
variant="ghost"
3459+
size="icon"
3460+
className="h-7 w-7"
3461+
title={t("layers.openStylePanel")}
3462+
aria-label={t("layers.openStylePanel")}
3463+
onClick={(e) => {
3464+
e.stopPropagation();
3465+
selectLayer(layer.id);
3466+
onOpenStylePanel();
3467+
}}
3468+
>
3469+
<Palette className="h-3.5 w-3.5" />
3470+
</Button>
3471+
)}
34523472
<DropdownMenu>
34533473
<DropdownMenuTrigger asChild>
34543474
<Button

docs/user-guide/layers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The **Layers panel** on the left lists every layer in the project, from the topm
1414

1515
Each layer exposes a set of actions:
1616

17+
- **Open Style panel**: when the built-in Style panel is enabled, use the palette button on the layer card to select the layer and open its styling controls.
1718
- **Zoom to layer**: fit the map to the layer's extent (for layers whose bounds are known).
1819
- **Identify features**: click features on the map to see their attributes in a popup. On a raster layer this reads the pixel value instead, and on a multiband raster it also builds a [spectral profile](styling.md#spectral-profile).
1920
- **Labels**: toggle text labels for vector layers that have a label field.

e2e/layer-panel.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,46 @@ test("long layer names truncate without widening the layer panel", async ({ page
108108
.poll(() => name.evaluate((element) => element.scrollWidth > element.clientWidth))
109109
.toBe(true);
110110
});
111+
112+
test("opens the selected layer in the Style panel from its card", async ({ page }) => {
113+
await page.setViewportSize({ width: 768, height: 720 });
114+
await waitForMap(page);
115+
await dropGeoJson(page, "first", FIXTURE_TEXT);
116+
await expect(layerRow(page, "first")).toBeVisible();
117+
await dropGeoJson(page, "second", FIXTURE_TEXT);
118+
await expect(layerRow(page, "second")).toBeVisible();
119+
120+
// Exact, so the collapsed rail ("Layer style (collapsed)") cannot satisfy the
121+
// default substring match.
122+
const stylePanel = page.getByRole("complementary", { name: "Layer style", exact: true });
123+
await expect(stylePanel).toHaveCount(0);
124+
125+
await layerRow(page, "first").getByRole("button", { name: "Open Style panel" }).click();
126+
127+
await expect(stylePanel).toBeVisible();
128+
await expect(stylePanel.getByText("Style - first", { exact: true })).toBeVisible();
129+
});
130+
131+
test("opens the Style panel from the layer card by keyboard", async ({ page }) => {
132+
await page.setViewportSize({ width: 768, height: 720 });
133+
await waitForMap(page);
134+
await dropGeoJson(page, "first", FIXTURE_TEXT);
135+
await expect(layerRow(page, "first")).toBeVisible();
136+
await dropGeoJson(page, "second", FIXTURE_TEXT);
137+
await expect(layerRow(page, "second")).toBeVisible();
138+
139+
const stylePanel = page.getByRole("complementary", { name: "Layer style", exact: true });
140+
const styleButton = (name: string) =>
141+
layerRow(page, name).getByRole("button", { name: "Open Style panel" });
142+
143+
// The card is a role="button" wrapper; its key handler must not swallow the
144+
// activation of the action buttons nested inside it.
145+
await styleButton("first").focus();
146+
await page.keyboard.press("Enter");
147+
await expect(stylePanel).toBeVisible();
148+
await expect(stylePanel.getByText("Style - first", { exact: true })).toBeVisible();
149+
150+
await styleButton("second").focus();
151+
await page.keyboard.press("Space");
152+
await expect(stylePanel.getByText("Style - second", { exact: true })).toBeVisible();
153+
});

0 commit comments

Comments
 (0)