From bc9651284eb7048dff9c88ebe69a416c3ab67913 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Thu, 9 Jul 2026 01:29:57 -0700 Subject: [PATCH] feat(studio): add flat Layer blur and Backdrop sliders to the Style group --- .../propertyPanelFlatStyleSections.test.tsx | 57 ++++++++++++++++++ .../editor/propertyPanelFlatStyleSections.tsx | 59 ++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx index 6b47c7ddb..936473245 100644 --- a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx +++ b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.test.tsx @@ -277,3 +277,60 @@ describe("FlatStyleSection — Shadow and Blend", () => { act(() => root.unmount()); }); }); + +describe("FlatStyleSection — blur sliders", () => { + it("renders Layer blur and Backdrop sliders and commits through onSetStyle", () => { + const onSetStyle = vi.fn(); + const host = document.createElement("div"); + document.body.append(host); + const root = createRoot(host); + act(() => { + root.render( + , + ); + }); + expect(host.textContent).toContain("Layer blur"); + expect(host.textContent).toContain("Backdrop"); + expect(host.textContent).toContain("4px"); + const track = host.querySelectorAll('[data-flat-slider-track="true"]')[0]; + Object.defineProperty(track, "getBoundingClientRect", { + value: () => ({ left: 0, width: 100, top: 0, height: 2, right: 100, bottom: 2 }), + }); + act(() => { + track.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, clientX: 50 })); + }); + // filterBlurValue=4 -> max=Math.max(40, 4)=40; clientX=50 of width 100 -> ratio 0.5 -> 20px. + expect(onSetStyle).toHaveBeenCalledWith("filter", "blur(20px)"); + act(() => root.unmount()); + }); + + it("renders the Backdrop slider from backdrop-filter and commits a new blur value on track click", () => { + const { host, root, onSetStyle } = renderSection({ "backdrop-filter": "blur(6px)" }); + expect(host.textContent).toContain("Backdrop"); + expect(host.textContent).toContain("6px"); + const tracks = host.querySelectorAll('[data-flat-slider-track="true"]'); + const backdropTrack = tracks[tracks.length - 1]; + Object.defineProperty(backdropTrack, "getBoundingClientRect", { + value: () => ({ left: 0, width: 100, top: 0, height: 2, right: 100, bottom: 2 }), + }); + act(() => { + backdropTrack.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true, clientX: 50 })); + }); + // backdropBlurValue=6 -> max=Math.max(60, 6)=60; clientX=50 of width 100 -> ratio 0.5 -> 30px. + expect(onSetStyle).toHaveBeenCalledWith("backdrop-filter", "blur(30px)"); + act(() => root.unmount()); + }); + + it("does not render a fill/knob highlight for a zero-value blur (default tier)", () => { + const { host, root } = renderSection({}); + expect(host.querySelectorAll('[data-flat-slider-fill="true"]')).toHaveLength(0); + act(() => root.unmount()); + }); +}); diff --git a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx index b2f45548d..ca9241cba 100644 --- a/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx +++ b/packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx @@ -12,13 +12,20 @@ import { extractBackgroundImageUrl, formatNumericValue, formatPxMetricValue, + getCssFilterFunctionPx, inferBoxShadowPreset, normalizePanelPxValue, parseNumericValue, parsePxMetricValue, + setCssFilterFunctionPx, type BoxShadowPreset, } from "./propertyPanelHelpers"; -import { FlatRow, FlatSegmentedRow, FlatSelectRow } from "./propertyPanelFlatPrimitives"; +import { + FlatRow, + FlatSegmentedRow, + FlatSelectRow, + FlatSlider, +} from "./propertyPanelFlatPrimitives"; import { resolveValueTier } from "./propertyPanelValueTier"; import { ColorField } from "./propertyPanelColor"; import { GradientField, ImageFillField } from "./propertyPanelFill"; @@ -317,6 +324,55 @@ function FlatShadowBlendRows({ ); } +/* ------------------------------------------------------------------ */ +/* Flat Layer blur + Backdrop sliders */ +/* ------------------------------------------------------------------ */ + +function FlatBlurSliders({ + styles, + disabled, + onSetStyle, +}: { + styles: Record; + disabled: boolean; + onSetStyle: (prop: string, value: string) => void | Promise; +}) { + const filterBlurValue = getCssFilterFunctionPx(styles.filter, "blur"); + const backdropBlurValue = getCssFilterFunctionPx(styles["backdrop-filter"], "blur"); + + return ( + <> + 0 ? "explicitCustom" : "default"} + displayValue={`${formatNumericValue(filterBlurValue)}px`} + disabled={disabled} + onCommit={(next) => + void onSetStyle("filter", setCssFilterFunctionPx(styles.filter, "blur", next)) + } + /> + 0 ? "explicitCustom" : "default"} + displayValue={`${formatNumericValue(backdropBlurValue)}px`} + disabled={disabled} + onCommit={(next) => + void onSetStyle( + "backdrop-filter", + setCssFilterFunctionPx(styles["backdrop-filter"], "blur", next), + ) + } + /> + + ); +} + export function FlatStyleSection({ projectId, element, @@ -357,6 +413,7 @@ export function FlatStyleSection({ disabled={styleEditingDisabled} onSetStyle={onSetStyle} /> + ); }