diff --git a/apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx b/apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx
index 2bed364d9d..8d58370877 100644
--- a/apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx
+++ b/apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx
@@ -1,12 +1,18 @@
-import { Tooltip, Text } from "@webstudio-is/design-system";
-import { InfoCircleIcon } from "@webstudio-is/icons";
-import type { LayersValue, StyleProperty } from "@webstudio-is/css-engine";
+import { colord, type RgbaColor } from "colord";
+import {
+ toValue,
+ type StyleProperty,
+ type StyleValue,
+} from "@webstudio-is/css-engine";
import { RepeatedStyleSection } from "../../shared/style-section";
-import type { SectionProps } from "../shared/section";
-import { LayersList } from "../../style-layers-list";
-import { addLayer } from "../../style-layer-utils";
-import { parseCssValue } from "@webstudio-is/css-data";
import { ShadowContent } from "../../shared/shadow-content";
+import { useComputedStyleDecl } from "../../shared/model";
+import {
+ addRepeatedStyleItem,
+ editRepeatedStyleItem,
+ RepeatedStyle,
+} from "../../shared/repeated-style";
+import { parseCssFragment } from "../../shared/parse-css-fragment";
export const properties = ["boxShadow"] satisfies [
StyleProperty,
@@ -17,9 +23,41 @@ const property: StyleProperty = properties[0];
const label = "Box Shadows";
const initialBoxShadow = "0px 2px 5px 0px rgba(0, 0, 0, 0.2)";
-export const Section = (props: SectionProps) => {
- const { currentStyle, deleteProperty } = props;
- const value = currentStyle[property]?.value;
+const getItemProps = (_index: number, layer: StyleValue) => {
+ const values = layer.type === "tuple" ? layer.value : [];
+ const labels = [];
+ let color: RgbaColor | undefined;
+ let isInset = false;
+
+ for (const item of values) {
+ if (item.type === "rgb") {
+ color = colord(toValue(item)).toRgb();
+ continue;
+ }
+ if (item.type === "keyword") {
+ if (item.value === "inset") {
+ isInset = true;
+ continue;
+ }
+ if (colord(item.value).isValid()) {
+ color = colord(item.value).toRgb();
+ continue;
+ }
+ }
+ labels.push(toValue(item));
+ }
+
+ if (isInset) {
+ labels.unshift("Inner Shadow:");
+ } else {
+ labels.unshift("Outer Shadow:");
+ }
+
+ return { label: labels.join(" "), color };
+};
+
+export const Section = () => {
+ const styleDecl = useComputedStyleDecl("boxShadow");
return (
{
description="Adds shadow effects around an element's frame."
properties={properties}
onAdd={() => {
- addLayer(
- property,
- parseCssValue(property, initialBoxShadow) as LayersValue,
- currentStyle,
- props.createBatchUpdate
+ addRepeatedStyleItem(
+ [styleDecl],
+ parseCssFragment(initialBoxShadow, "boxShadow")
);
}}
>
- {value?.type === "layers" && value.value.length > 0 && (
- {
- if (layerProps.layer.type !== "tuple") {
- return <>>;
- }
-
- return (
-
- Paste a box-shadow CSS code without the property name,
- for example:
-
- {initialBoxShadow}
-
- }
- >
-
-
- }
- />
- );
- }}
- />
- )}
+ (
+ {
+ editRepeatedStyleItem(
+ [styleDecl],
+ index,
+ new Map([["boxShadow", value]]),
+ options
+ );
+ }}
+ />
+ )}
+ />
);
};
diff --git a/apps/builder/app/builder/features/style-panel/sections/text-shadows/text-shadows.tsx b/apps/builder/app/builder/features/style-panel/sections/text-shadows/text-shadows.tsx
index 1b502a9706..f12f252dcf 100644
--- a/apps/builder/app/builder/features/style-panel/sections/text-shadows/text-shadows.tsx
+++ b/apps/builder/app/builder/features/style-panel/sections/text-shadows/text-shadows.tsx
@@ -1,12 +1,18 @@
-import type { SectionProps } from "../shared/section";
-import type { LayersValue, StyleProperty } from "@webstudio-is/css-engine";
-import { Tooltip, Text } from "@webstudio-is/design-system";
-import { InfoCircleIcon } from "@webstudio-is/icons";
-import { addLayer } from "../../style-layer-utils";
-import { parseCssValue } from "@webstudio-is/css-data";
+import { colord, type RgbaColor } from "colord";
+import {
+ toValue,
+ type StyleProperty,
+ type StyleValue,
+} from "@webstudio-is/css-engine";
import { RepeatedStyleSection } from "../../shared/style-section";
-import { LayersList } from "../../style-layers-list";
import { ShadowContent } from "../../shared/shadow-content";
+import {
+ addRepeatedStyleItem,
+ editRepeatedStyleItem,
+ RepeatedStyle,
+} from "../../shared/repeated-style";
+import { parseCssFragment } from "../../shared/parse-css-fragment";
+import { useComputedStyleDecl } from "../../shared/model";
export const properties = ["textShadow"] satisfies [
StyleProperty,
@@ -17,9 +23,28 @@ const property: StyleProperty = properties[0];
const label = "Text Shadows";
const initialTextShadow = "0px 2px 5px rgba(0, 0, 0, 0.2)";
-export const Section = (props: SectionProps) => {
- const { currentStyle, createBatchUpdate, deleteProperty } = props;
- const value = currentStyle[property]?.value;
+const getItemProps = (_index: number, layer: StyleValue) => {
+ const values = layer.type === "tuple" ? layer.value : [];
+ const labels = ["Text Shadow:"];
+ let color: RgbaColor | undefined;
+
+ for (const item of values) {
+ if (item.type === "rgb") {
+ color = colord(toValue(item)).toRgb();
+ continue;
+ }
+ if (item.type === "keyword" && colord(item.value).isValid()) {
+ color = colord(item.value).toRgb();
+ continue;
+ }
+ labels.push(toValue(item));
+ }
+
+ return { label: labels.join(" "), color };
+};
+
+export const Section = () => {
+ const styleDecl = useComputedStyleDecl("textShadow");
return (
{
description="Adds shadow effects around a text."
properties={properties}
onAdd={() => {
- addLayer(
- property,
- parseCssValue(property, initialTextShadow) as LayersValue,
- currentStyle,
- createBatchUpdate
+ addRepeatedStyleItem(
+ [styleDecl],
+ parseCssFragment(initialTextShadow, "textShadow")
);
}}
>
- {value?.type === "layers" && value.value.length > 0 && (
- {
- if (layerProps.layer.type !== "tuple") {
- return <>>;
- }
-
- return (
-
- Paste a text-shadow CSS code without the property name,
- for example:
-
- {initialTextShadow}
-
- }
- >
-
-
- }
- />
- );
- }}
- />
- )}
+ (
+ {
+ editRepeatedStyleItem(
+ [styleDecl],
+ index,
+ new Map([["textShadow", value]]),
+ options
+ );
+ }}
+ />
+ )}
+ />
);
};
diff --git a/apps/builder/app/builder/features/style-panel/shared/filter-content.tsx b/apps/builder/app/builder/features/style-panel/shared/filter-content.tsx
index c5c2357fd9..f9f4c15ade 100644
--- a/apps/builder/app/builder/features/style-panel/shared/filter-content.tsx
+++ b/apps/builder/app/builder/features/style-panel/shared/filter-content.tsx
@@ -228,7 +228,6 @@ export const FilterSectionContent = ({
index={index}
property="dropShadow"
layer={layer.args}
- tooltip={<>>}
propertyValue={toValue(layer.args)}
onEditLayer={(_, dropShadowLayers, options) => {
handleComplete(
@@ -236,7 +235,6 @@ export const FilterSectionContent = ({
options
);
}}
- deleteProperty={() => {}}
hideCodeEditor={true}
/>
) : undefined}
diff --git a/apps/builder/app/builder/features/style-panel/shared/repeated-style.tsx b/apps/builder/app/builder/features/style-panel/shared/repeated-style.tsx
index 3cb442c8e7..058bd3bf39 100644
--- a/apps/builder/app/builder/features/style-panel/shared/repeated-style.tsx
+++ b/apps/builder/app/builder/features/style-panel/shared/repeated-style.tsx
@@ -141,7 +141,7 @@ export const RepeatedStyle = (props: {
index: number,
primaryValue: StyleValue
) => { label: string; color?: RgbaColor };
- renderItemContent: (index: number) => JSX.Element;
+ renderItemContent: (index: number, primaryValue: StyleValue) => JSX.Element;
}) => {
const transformLayers = createLayersTransformer(props.styles);
const { label, styles, getItemProps, renderItemContent } = props;
@@ -183,7 +183,7 @@ export const RepeatedStyle = (props: {
void;
- deleteProperty: DeleteProperty;
hideCodeEditor?: boolean;
};
@@ -122,18 +123,20 @@ export const ShadowContent = ({
index,
property,
propertyValue,
- tooltip,
hideCodeEditor = false,
onEditLayer,
- deleteProperty,
}: ShadowContentProps) => {
const [intermediateValue, setIntermediateValue] = useState<
IntermediateStyleValue | InvalidValue | undefined
- >();
- const layerValues = useMemo(() => {
+ >({ type: "intermediate", value: propertyValue });
+ useEffect(() => {
setIntermediateValue({ type: "intermediate", value: propertyValue });
- return extractShadowProperties(layer);
- }, [layer, propertyValue]);
+ }, [propertyValue]);
+ const layerValues = useMemo(() => {
+ return extractShadowProperties(
+ layer.type === "tuple" ? layer : { type: "tuple", value: [] }
+ );
+ }, [layer]);
const { offsetX, offsetY, blur, spread, color, inset } = layerValues;
const colorControlProp = color ?? {
@@ -379,7 +382,21 @@ export const ShadowContent = ({
diff --git a/apps/builder/app/builder/features/style-panel/style-layer-utils.test.ts b/apps/builder/app/builder/features/style-panel/style-layer-utils.test.ts
index f575e3ee07..5376e7b948 100644
--- a/apps/builder/app/builder/features/style-panel/style-layer-utils.test.ts
+++ b/apps/builder/app/builder/features/style-panel/style-layer-utils.test.ts
@@ -363,69 +363,6 @@ describe("boxShadowUtils", () => {
});
});
-test("Generates humane layer names for shadow style layer", () => {
- expect(
- getHumanizedTextFromLayer(
- "boxShadow",
- (
- parseCssValue(
- "boxShadow",
- "inset 2px 2px 5px rgba(0, 0, 0, 0.5)"
- ) as LayersValue
- ).value[0] as TupleValue
- )
- ).toMatchInlineSnapshot(`
-{
- "color": {
- "a": 0.5,
- "b": 0,
- "g": 0,
- "r": 0,
- },
- "name": "Inner Shadow: 2px 2px 5px",
- "value": "inset 2px 2px 5px rgba(0, 0, 0, 0.5)",
-}
-`);
-
- expect(
- getHumanizedTextFromLayer(
- "boxShadow",
- (
- parseCssValue(
- "boxShadow",
- "3px 3px 10px rgba(255, 0, 0, 0.7)"
- ) as LayersValue
- ).value[0] as TupleValue
- )
- ).toMatchInlineSnapshot(`
-{
- "color": {
- "a": 0.7,
- "b": 0,
- "g": 0,
- "r": 255,
- },
- "name": "Outer Shadow: 3px 3px 10px",
- "value": "3px 3px 10px rgba(255, 0, 0, 0.7)",
-}
-`);
-
- // colors is returning isValid as false in node env for 'red'
- expect(
- getHumanizedTextFromLayer(
- "textShadow",
- (parseCssValue("textShadow", "1px 1px 2px red") as LayersValue)
- .value[0] as TupleValue
- )
- ).toMatchInlineSnapshot(`
-{
- "color": undefined,
- "name": "Text Shadow: 1px 1px 2px red",
- "value": "1px 1px 2px red",
-}
-`);
-});
-
test("Generates humane layer names for filter style layer", () => {
expect(
getHumanizedTextFromLayer(
diff --git a/apps/builder/app/builder/features/style-panel/style-layer-utils.ts b/apps/builder/app/builder/features/style-panel/style-layer-utils.ts
index 52f8c16f85..6bbfe6ee19 100644
--- a/apps/builder/app/builder/features/style-panel/style-layer-utils.ts
+++ b/apps/builder/app/builder/features/style-panel/style-layer-utils.ts
@@ -12,7 +12,6 @@ import type {
CreateBatchUpdate,
StyleUpdateOptions,
} from "./shared/use-style-data";
-import { colord, type RgbaColor } from "colord";
import { humanizeString } from "~/shared/string-utils";
import type { LayerListProperty } from "./style-layers-list";
@@ -192,60 +191,6 @@ export const getHumanizedTextFromLayer = (
layer: StyleValue
) => {
switch (property) {
- case "textShadow":
- case "boxShadow":
- if (layer.type === "tuple") {
- const name = [];
- let color: RgbaColor | undefined;
- const properties = [...layer.value];
-
- if (property === "boxShadow") {
- const insetKeyword = layer.value.find(
- (item) => item.type === "keyword" && item.value === "inset"
- );
- if (insetKeyword !== undefined) {
- name.push("Inner Shadow: ");
- properties.splice(properties.indexOf(insetKeyword), 1);
- } else {
- name.push("Outer Shadow: ");
- }
- }
-
- if (property === "textShadow") {
- name.push("Text Shadow: ");
- }
-
- for (const item of properties) {
- if (item.type === "unit") {
- const value = toValue(item);
- name.push(value);
- }
-
- if (item.type === "rgb") {
- color = colord(toValue(item)).toRgb();
- }
-
- if (item.type === "keyword") {
- if (colord(item.value).isValid() === false) {
- name.push(item.value);
- } else {
- color = colord(item.value).toRgb();
- }
- }
-
- if (item.type === "unparsed") {
- name.push(item.value);
- }
-
- if (item.type === "function") {
- const value = `${item.name}(${toValue(item.args)})`;
- name.push(value);
- }
- }
-
- return { name: name.join(" "), value: toValue(layer), color };
- }
- break;
case "filter":
case "backdropFilter":
if (layer.type === "function") {