Skip to content

Commit fc14133

Browse files
authored
experimental: autocomplete variables in backgrounds code (#4281)
Ref #3399 Using dedicated inputs to autocomplete variables makes UI more complex. Though we already have "Code" fields which can be used for the same purpose. Here added css fragment editor to background gradient. ![Screenshot 2024-10-14 at 15 56 54](https://github.com/user-attachments/assets/2c41f3e7-9f79-46c3-92be-870f69ac515d)
1 parent 0e71255 commit fc14133

23 files changed

+437
-407
lines changed

apps/builder/app/builder/features/style-panel/sections/backdrop-filter/backdrop-filter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { InfoCircleIcon } from "@webstudio-is/icons";
88
import { humanizeString } from "~/shared/string-utils";
99
import { RepeatedStyleSection } from "../../shared/style-section";
1010
import { FilterSectionContent } from "../../shared/filter-content";
11-
import { parseCssFragment } from "../../shared/parse-css-fragment";
11+
import { parseCssFragment } from "../../shared/css-fragment";
1212
import {
1313
addRepeatedStyleItem,
1414
editRepeatedStyleItem,
@@ -44,7 +44,7 @@ export const Section = () => {
4444
onAdd={() => {
4545
addRepeatedStyleItem(
4646
[styleDecl],
47-
parseCssFragment(initialBackdropFilter, "backdropFilter")
47+
parseCssFragment(initialBackdropFilter, ["backdropFilter"])
4848
);
4949
}}
5050
>

apps/builder/app/builder/features/style-panel/sections/backgrounds/background-content.tsx

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ import {
1111
RepeatRowIcon,
1212
CrossSmallIcon,
1313
} from "@webstudio-is/icons";
14-
import {
15-
LayersValue,
16-
type StyleProperty,
17-
type StyleValue,
18-
toValue,
19-
UnparsedValue,
20-
} from "@webstudio-is/css-engine";
14+
import { type StyleValue, toValue } from "@webstudio-is/css-engine";
2115
import {
2216
theme,
2317
Flex,
@@ -35,17 +29,11 @@ import { BackgroundImage } from "./background-image";
3529
import { BackgroundPosition } from "./background-position";
3630
import { PropertyInlineLabel } from "../../property-label";
3731
import { ToggleGroupTooltip } from "../../controls/toggle-group/toggle-group-control";
38-
import { $availableVariables, useComputedStyleDecl } from "../../shared/model";
32+
import { useComputedStyleDecl } from "../../shared/model";
3933
import {
4034
getRepeatedStyleItem,
4135
setRepeatedStyleItem,
4236
} from "../../shared/repeated-style";
43-
import { CssValueInputContainer } from "../../shared/css-value-input";
44-
import {
45-
setProperty,
46-
type StyleUpdateOptions,
47-
} from "../../shared/use-style-data";
48-
import type { ComputedStyleDecl } from "~/shared/style-object-model";
4937

5038
const detectImageOrGradientToggle = (styleValue?: StyleValue) => {
5139
if (styleValue?.type === "image") {
@@ -73,54 +61,6 @@ const Spacer = styled("div", {
7361
height: theme.spacing[5],
7462
});
7563

76-
const setGradientProperty = (
77-
styleDecl: ComputedStyleDecl,
78-
index: number,
79-
newItem: StyleValue,
80-
options?: StyleUpdateOptions
81-
) => {
82-
const property = styleDecl.property as StyleProperty;
83-
let items: StyleValue[] = [];
84-
if (styleDecl.cascadedValue.type === "var") {
85-
items = [styleDecl.cascadedValue];
86-
}
87-
if (styleDecl.cascadedValue.type === "layers") {
88-
items = styleDecl.cascadedValue.value;
89-
}
90-
const unpackedItem = newItem.type === "layers" ? newItem.value[0] : newItem;
91-
if (items.length === 1 && unpackedItem.type === "var") {
92-
setProperty(property)(unpackedItem, options);
93-
} else {
94-
const newValue = { type: "layers", value: items } as LayersValue;
95-
newValue.value[index] = newItem as UnparsedValue;
96-
setProperty(property)(newValue, options);
97-
}
98-
};
99-
100-
const GradientControl = ({ index }: { index: number }) => {
101-
const styleDecl = useComputedStyleDecl("backgroundImage");
102-
const value =
103-
styleDecl.cascadedValue.type === "var"
104-
? styleDecl.cascadedValue
105-
: getRepeatedStyleItem(styleDecl, index);
106-
return (
107-
<CssValueInputContainer
108-
property="backgroundImage"
109-
styleSource="default"
110-
getOptions={() => $availableVariables.get()}
111-
value={value}
112-
setValue={(newValue, options) => {
113-
setGradientProperty(styleDecl, index, newValue, options);
114-
}}
115-
deleteProperty={() => {
116-
if (value) {
117-
setGradientProperty(styleDecl, index, value);
118-
}
119-
}}
120-
/>
121-
);
122-
};
123-
12464
const BackgroundRepeat = ({ index }: { index: number }) => {
12565
const styleDecl = useComputedStyleDecl("backgroundRepeat");
12666
const value = getRepeatedStyleItem(styleDecl, index);
@@ -275,16 +215,6 @@ export const BackgroundContent = ({ index }: { index: number }) => {
275215
</FloatingPanelProvider>
276216
</>
277217
)}
278-
{imageGradientToggle === "gradient" && (
279-
<>
280-
<PropertyInlineLabel
281-
label="Gradient"
282-
description={propertyDescriptions.backgroundImage}
283-
properties={["backgroundImage"]}
284-
/>
285-
<GradientControl index={index} />
286-
</>
287-
)}
288218

289219
<PropertyInlineLabel
290220
label="Clip"

apps/builder/app/builder/features/style-panel/sections/backgrounds/background-gradient.tsx

Lines changed: 22 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,19 @@
1-
import type {
2-
InvalidValue,
3-
LayersValue,
4-
StyleValue,
1+
import {
2+
toValue,
3+
type InvalidValue,
4+
type StyleValue,
55
} from "@webstudio-is/css-engine";
66
import { parseCssValue } from "@webstudio-is/css-data";
7-
import {
8-
Flex,
9-
Label,
10-
Text,
11-
TextArea,
12-
textVariants,
13-
theme,
14-
Tooltip,
15-
} from "@webstudio-is/design-system";
7+
import { Flex, Label, Text, theme, Tooltip } from "@webstudio-is/design-system";
168
import { useEffect, useRef, useState } from "react";
17-
import { parseCssFragment } from "../../shared/parse-css-fragment";
189
import { InfoCircleIcon } from "@webstudio-is/icons";
1910
import { setProperty } from "../../shared/use-style-data";
2011
import { useComputedStyleDecl } from "../../shared/model";
2112
import {
2213
editRepeatedStyleItem,
23-
getRepeatedStyleItem,
2414
setRepeatedStyleItem,
2515
} from "../../shared/repeated-style";
16+
import { parseCssFragment, CssFragmentEditor } from "../../shared/css-fragment";
2617

2718
type IntermediateValue = {
2819
type: "intermediate";
@@ -33,18 +24,17 @@ const isTransparent = (color: StyleValue) =>
3324
color.type === "keyword" && color.value === "transparent";
3425

3526
export const BackgroundGradient = ({ index }: { index: number }) => {
36-
const textAreaRef = useRef<HTMLTextAreaElement>(null);
37-
const property = "backgroundImage";
38-
const styleDecl = useComputedStyleDecl(property);
39-
const styleValue = getRepeatedStyleItem(styleDecl, index);
27+
const styleDecl = useComputedStyleDecl("backgroundImage");
28+
let styleValue = styleDecl.cascadedValue;
29+
if (styleValue.type === "layers") {
30+
styleValue = styleValue.value[index];
31+
}
4032

4133
const [intermediateValue, setIntermediateValue] = useState<
4234
IntermediateValue | InvalidValue | undefined
4335
>(undefined);
4436

45-
const textAreaValue =
46-
intermediateValue?.value ??
47-
(styleValue?.type === "unparsed" ? styleValue.value : undefined);
37+
const textAreaValue = intermediateValue?.value ?? toValue(styleValue);
4838

4939
const handleChange = (value: string) => {
5040
setIntermediateValue({
@@ -55,9 +45,9 @@ export const BackgroundGradient = ({ index }: { index: number }) => {
5545
// This doesn't have the same behavior as CssValueInput.
5646
// However, it's great to see the immediate results when making gradient changes,
5747
// especially until we have a better gradient tool.
58-
const newValue = parseCssValue(property, value);
48+
const newValue = parseCssValue("backgroundImage", value);
5949

60-
if (newValue.type === "unparsed") {
50+
if (newValue.type === "unparsed" || newValue.type === "var") {
6151
setRepeatedStyleItem(styleDecl, index, newValue, { isEphemeral: true });
6252
return;
6353
}
@@ -76,21 +66,15 @@ export const BackgroundGradient = ({ index }: { index: number }) => {
7666
return;
7767
}
7868

79-
const parsed = parseCssFragment(intermediateValue.value, "background");
69+
const parsed = parseCssFragment(intermediateValue.value, [
70+
"backgroundImage",
71+
"background",
72+
]);
8073
const backgroundImage = parsed.get("backgroundImage");
8174
const backgroundColor = parsed.get("backgroundColor");
82-
const layers: LayersValue =
83-
backgroundImage?.type === "layers"
84-
? backgroundImage
85-
: { type: "layers", value: [] };
86-
const [firstLayer] = layers.value;
8775

8876
// set invalid state
89-
if (
90-
backgroundColor?.type === "invalid" ||
91-
layers.value.length === 0 ||
92-
firstLayer.type === "invalid"
93-
) {
77+
if (backgroundColor?.type === "invalid" || backgroundImage === undefined) {
9478
setIntermediateValue({ type: "invalid", value: intermediateValue.value });
9579
if (styleValue) {
9680
setRepeatedStyleItem(styleDecl, index, styleValue, {
@@ -107,7 +91,7 @@ export const BackgroundGradient = ({ index }: { index: number }) => {
10791
editRepeatedStyleItem(
10892
[styleDecl],
10993
index,
110-
new Map([["backgroundImage", layers]])
94+
new Map([["backgroundImage", backgroundImage]])
11195
);
11296
};
11397

@@ -153,15 +137,9 @@ export const BackgroundGradient = ({ index }: { index: number }) => {
153137
</Tooltip>
154138
</Flex>
155139
</Label>
156-
<TextArea
157-
ref={textAreaRef}
158-
css={{ ...textVariants.mono }}
159-
rows={2}
160-
autoGrow
161-
maxRows={4}
162-
name="description"
140+
<CssFragmentEditor
141+
invalid={intermediateValue?.type === "invalid"}
163142
value={textAreaValue ?? ""}
164-
color={intermediateValue?.type === "invalid" ? "error" : undefined}
165143
onChange={handleChange}
166144
onBlur={handleOnComplete}
167145
onKeyDown={(event) => {

apps/builder/app/builder/features/style-panel/sections/backgrounds/background-image.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { useStore } from "@nanostores/react";
1515
import { $assets } from "~/shared/nano-states";
1616
import type { StyleUpdateOptions } from "../../shared/use-style-data";
1717
import { InfoCircleIcon } from "@webstudio-is/icons";
18-
import { parseCssFragment } from "../../shared/parse-css-fragment";
18+
import { parseCssFragment } from "../../shared/css-fragment";
1919
import { useComputedStyleDecl } from "../../shared/model";
2020
import {
2121
getRepeatedStyleItem,
@@ -90,7 +90,7 @@ export const BackgroundImage = ({ index }: { index: number }) => {
9090
value: value,
9191
});
9292

93-
const parsed = parseCssFragment(value, "background");
93+
const parsed = parseCssFragment(value, ["backgroundImage", "background"]);
9494
const newValue = parsed.get("backgroundImage");
9595

9696
if (newValue === undefined || newValue?.type === "invalid") {

apps/builder/app/builder/features/style-panel/sections/backgrounds/backgrounds.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
RepeatedStyle,
1212
} from "../../shared/repeated-style";
1313
import { useComputedStyles } from "../../shared/model";
14-
import { parseCssFragment } from "../../shared/parse-css-fragment";
14+
import { parseCssFragment } from "../../shared/css-fragment";
1515
import { BackgroundContent } from "./background-content";
1616
import {
1717
getBackgroundLabel,
@@ -34,7 +34,7 @@ export const Section = () => {
3434
description="Add one or more backgrounds to the instance such as a color, image, or gradient."
3535
properties={properties}
3636
onAdd={() => {
37-
addRepeatedStyleItem(styles, parseCssFragment("none", "background"));
37+
addRepeatedStyleItem(styles, parseCssFragment("none", ["background"]));
3838
}}
3939
>
4040
<Flex gap={1} direction="column">

apps/builder/app/builder/features/style-panel/sections/box-shadows/box-shadows.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
editRepeatedStyleItem,
1313
RepeatedStyle,
1414
} from "../../shared/repeated-style";
15-
import { parseCssFragment } from "../../shared/parse-css-fragment";
15+
import { parseCssFragment } from "../../shared/css-fragment";
1616

1717
export const properties = ["boxShadow"] satisfies [
1818
StyleProperty,
@@ -67,7 +67,7 @@ export const Section = () => {
6767
onAdd={() => {
6868
addRepeatedStyleItem(
6969
[styleDecl],
70-
parseCssFragment(initialBoxShadow, "boxShadow")
70+
parseCssFragment(initialBoxShadow, ["boxShadow"])
7171
);
7272
}}
7373
>

apps/builder/app/builder/features/style-panel/sections/filter/filter.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
editRepeatedStyleItem,
1313
RepeatedStyle,
1414
} from "../../shared/repeated-style";
15-
import { parseCssFragment } from "../../shared/parse-css-fragment";
15+
import { parseCssFragment } from "../../shared/css-fragment";
1616
import { useComputedStyleDecl } from "../../shared/model";
1717
import { humanizeString } from "~/shared/string-utils";
1818

@@ -44,7 +44,7 @@ export const Section = () => {
4444
onAdd={() => {
4545
addRepeatedStyleItem(
4646
[styleDecl],
47-
parseCssFragment(initialFilter, "filter")
47+
parseCssFragment(initialFilter, ["filter"])
4848
);
4949
}}
5050
>

apps/builder/app/builder/features/style-panel/sections/text-shadows/text-shadows.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
editRepeatedStyleItem,
1212
RepeatedStyle,
1313
} from "../../shared/repeated-style";
14-
import { parseCssFragment } from "../../shared/parse-css-fragment";
14+
import { parseCssFragment } from "../../shared/css-fragment";
1515
import { useComputedStyleDecl } from "../../shared/model";
1616

1717
export const properties = ["textShadow"] satisfies [
@@ -54,7 +54,7 @@ export const Section = () => {
5454
onAdd={() => {
5555
addRepeatedStyleItem(
5656
[styleDecl],
57-
parseCssFragment(initialTextShadow, "textShadow")
57+
parseCssFragment(initialTextShadow, ["textShadow"])
5858
);
5959
}}
6060
>

apps/builder/app/builder/features/style-panel/sections/transitions/transition-content.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { properties, propertyDescriptions } from "@webstudio-is/css-data";
2121
import type { StyleUpdateOptions } from "../../shared/use-style-data";
2222
import { type IntermediateStyleValue } from "../../shared/css-value-input";
2323
import { CssValueInputContainer } from "../../shared/css-value-input";
24-
import { parseCssFragment } from "../../shared/parse-css-fragment";
24+
import { parseCssFragment } from "../../shared/css-fragment";
2525
import { PropertyInlineLabel } from "../../property-label";
2626
import { TransitionProperty } from "./transition-property";
2727
import { TransitionTiming } from "./transition-timing";
@@ -82,7 +82,7 @@ export const TransitionContent = ({ index }: { index: number }) => {
8282
editRepeatedStyleItem(
8383
styles,
8484
index,
85-
parseCssFragment(intermediateValue.value, "transition")
85+
parseCssFragment(intermediateValue.value, ["transition"])
8686
);
8787
};
8888

@@ -111,7 +111,7 @@ export const TransitionContent = ({ index }: { index: number }) => {
111111
editRepeatedStyleItem(
112112
styles,
113113
index,
114-
parseCssFragment(shorthand, "transition"),
114+
parseCssFragment(shorthand, ["transition"]),
115115
options
116116
);
117117
};

apps/builder/app/builder/features/style-panel/sections/transitions/transitions.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
type TransitionProperty,
2929
} from "./transition-utils";
3030
import { TransitionContent } from "./transition-content";
31-
import { parseCssFragment } from "../../shared/parse-css-fragment";
31+
import { parseCssFragment } from "../../shared/css-fragment";
3232

3333
export { transitionLongHandProperties as properties };
3434

@@ -116,10 +116,9 @@ export const Section = () => {
116116
setIsOpen(true);
117117
addRepeatedStyleItem(
118118
styles,
119-
parseCssFragment(
120-
"opacity 200ms ease 0ms normal",
121-
"transition"
122-
)
119+
parseCssFragment("opacity 200ms ease 0ms normal", [
120+
"transition",
121+
])
123122
);
124123
}}
125124
/>

0 commit comments

Comments
 (0)