Skip to content

Commit 3e056f9

Browse files
authored
experimental: support css variables in shadows (#4283)
Ref #3399 ![Screenshot 2024-10-14 at 20 13 34](https://github.com/user-attachments/assets/6518141b-b0f4-40e8-89db-7fcf68093ff6)
1 parent 994813d commit 3e056f9

File tree

5 files changed

+66
-25
lines changed

5 files changed

+66
-25
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export const BackgroundGradient = ({ index }: { index: number }) => {
139139
</Label>
140140
<CssFragmentEditor
141141
invalid={intermediateValue?.type === "invalid"}
142+
autoFocus={styleValue.type === "var"}
142143
value={textAreaValue ?? ""}
143144
onChange={handleChange}
144145
onBlur={handleOnComplete}

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useComputedStyleDecl } from "../../shared/model";
1010
import {
1111
addRepeatedStyleItem,
1212
editRepeatedStyleItem,
13+
getComputedRepeatedItem,
1314
RepeatedStyle,
1415
} from "../../shared/repeated-style";
1516
import { parseCssFragment } from "../../shared/css-fragment";
@@ -23,12 +24,21 @@ const property: StyleProperty = properties[0];
2324
const label = "Box Shadows";
2425
const initialBoxShadow = "0px 2px 5px 0px rgba(0, 0, 0, 0.2)";
2526

26-
const getItemProps = (_index: number, layer: StyleValue) => {
27-
const values = layer.type === "tuple" ? layer.value : [];
27+
const getItemProps = (layer: StyleValue, computedLayer?: StyleValue) => {
28+
let values: StyleValue[] = [];
29+
if (layer.type === "tuple") {
30+
values = layer.value;
31+
}
32+
if (layer.type === "var" && computedLayer?.type === "tuple") {
33+
values = computedLayer.value;
34+
}
2835
const labels = [];
2936
let color: RgbaColor | undefined;
3037
let isInset = false;
3138

39+
if (layer.type === "var") {
40+
labels.push(`--${layer.value}`);
41+
}
3242
for (const item of values) {
3343
if (item.type === "rgb") {
3444
color = colord(toValue(item)).toRgb();
@@ -44,7 +54,9 @@ const getItemProps = (_index: number, layer: StyleValue) => {
4454
continue;
4555
}
4656
}
47-
labels.push(toValue(item));
57+
if (layer.type !== "var") {
58+
labels.push(toValue(item));
59+
}
4860
}
4961

5062
if (isInset) {
@@ -74,11 +86,14 @@ export const Section = () => {
7486
<RepeatedStyle
7587
label={label}
7688
styles={[styleDecl]}
77-
getItemProps={getItemProps}
89+
getItemProps={(index, layer) =>
90+
getItemProps(layer, getComputedRepeatedItem(styleDecl, index))
91+
}
7892
renderItemContent={(index, value) => (
7993
<ShadowContent
8094
index={index}
8195
layer={value}
96+
computedLayer={getComputedRepeatedItem(styleDecl, index)}
8297
property={property}
8398
propertyValue={toValue(value)}
8499
onEditLayer={(index, value, options) => {

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ShadowContent } from "../../shared/shadow-content";
99
import {
1010
addRepeatedStyleItem,
1111
editRepeatedStyleItem,
12+
getComputedRepeatedItem,
1213
RepeatedStyle,
1314
} from "../../shared/repeated-style";
1415
import { parseCssFragment } from "../../shared/css-fragment";
@@ -23,11 +24,20 @@ const property: StyleProperty = properties[0];
2324
const label = "Text Shadows";
2425
const initialTextShadow = "0px 2px 5px rgba(0, 0, 0, 0.2)";
2526

26-
const getItemProps = (_index: number, layer: StyleValue) => {
27-
const values = layer.type === "tuple" ? layer.value : [];
27+
const getItemProps = (layer: StyleValue, computedLayer?: StyleValue) => {
28+
let values: StyleValue[] = [];
29+
if (layer.type === "tuple") {
30+
values = layer.value;
31+
}
32+
if (layer.type === "var" && computedLayer?.type === "tuple") {
33+
values = computedLayer.value;
34+
}
2835
const labels = ["Text Shadow:"];
2936
let color: RgbaColor | undefined;
3037

38+
if (layer.type === "var") {
39+
labels.push(`--${layer.value}`);
40+
}
3141
for (const item of values) {
3242
if (item.type === "rgb") {
3343
color = colord(toValue(item)).toRgb();
@@ -37,7 +47,9 @@ const getItemProps = (_index: number, layer: StyleValue) => {
3747
color = colord(item.value).toRgb();
3848
continue;
3949
}
40-
labels.push(toValue(item));
50+
if (layer.type !== "var") {
51+
labels.push(toValue(item));
52+
}
4153
}
4254

4355
return { label: labels.join(" "), color };
@@ -61,11 +73,14 @@ export const Section = () => {
6173
<RepeatedStyle
6274
label={label}
6375
styles={[styleDecl]}
64-
getItemProps={getItemProps}
76+
getItemProps={(index, layer) =>
77+
getItemProps(layer, getComputedRepeatedItem(styleDecl, index))
78+
}
6579
renderItemContent={(index, value) => (
6680
<ShadowContent
6781
index={index}
6882
layer={value}
83+
computedLayer={getComputedRepeatedItem(styleDecl, index)}
6984
property={property}
7085
propertyValue={toValue(value)}
7186
onEditLayer={(index, value, options) => {

apps/builder/app/builder/features/style-panel/shared/css-fragment.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const compareVariables = (left: RankingInfo, right: RankingInfo) => {
4444
};
4545

4646
const scopeCompletionSource: CompletionSource = (context) => {
47-
const word = context.matchBefore(/[-\w]+/);
47+
const word = context.matchBefore(/[-\w()]+/);
4848
if (word === null || (word.from === word.to && false === context.explicit)) {
4949
return null;
5050
}
@@ -60,6 +60,7 @@ const scopeCompletionSource: CompletionSource = (context) => {
6060
});
6161
return {
6262
from: word.from,
63+
to: word.to,
6364
filter: false,
6465
options: matches,
6566
};
@@ -75,6 +76,7 @@ export const CssFragmentEditor = ({
7576
...props
7677
}: {
7778
invalid?: boolean;
79+
autoFocus?: boolean;
7880
value: string;
7981
onChange: (newValue: string) => void;
8082
onBlur?: (event: FocusEvent) => void;

apps/builder/app/builder/features/style-panel/shared/shadow-content.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
type StyleValue,
99
type TupleValue,
1010
type UnitValue,
11+
type VarValue,
1112
} from "@webstudio-is/css-engine";
1213
import {
1314
extractShadowProperties,
@@ -20,8 +21,6 @@ import {
2021
Label,
2122
Separator,
2223
Text,
23-
TextArea,
24-
textVariants,
2524
theme,
2625
ToggleGroup,
2726
ToggleGroupButton,
@@ -36,7 +35,7 @@ import type { IntermediateStyleValue } from "../shared/css-value-input";
3635
import { CssValueInputContainer } from "../shared/css-value-input";
3736
import { toPascalCase } from "../shared/keyword-utils";
3837
import type { StyleUpdateOptions } from "../shared/use-style-data";
39-
import { parseCssFragment } from "./css-fragment";
38+
import { CssFragmentEditor, parseCssFragment } from "./css-fragment";
4039
import { PropertyInlineLabel } from "../property-label";
4140
import { styleConfigByName } from "./configs";
4241
import { ColorPicker } from "./color-picker";
@@ -69,10 +68,11 @@ type ShadowContentProps = {
6968
index: number;
7069
property: "boxShadow" | "textShadow" | "dropShadow";
7170
layer: StyleValue;
71+
computedLayer?: StyleValue;
7272
propertyValue: string;
7373
onEditLayer: (
7474
index: number,
75-
layers: LayersValue,
75+
layers: LayersValue | VarValue,
7676
options: StyleUpdateOptions
7777
) => void;
7878
hideCodeEditor?: boolean;
@@ -120,6 +120,7 @@ const shadowPropertySyntaxes = {
120120

121121
export const ShadowContent = ({
122122
layer,
123+
computedLayer,
123124
index,
124125
property,
125126
propertyValue,
@@ -133,10 +134,15 @@ export const ShadowContent = ({
133134
setIntermediateValue({ type: "intermediate", value: propertyValue });
134135
}, [propertyValue]);
135136
const layerValues = useMemo<ExtractedShadowProperties>(() => {
136-
return extractShadowProperties(
137-
layer.type === "tuple" ? layer : { type: "tuple", value: [] }
138-
);
139-
}, [layer]);
137+
let value: TupleValue = { type: "tuple", value: [] };
138+
if (layer.type === "tuple") {
139+
value = layer;
140+
}
141+
if (layer.type === "var" && computedLayer?.type === "tuple") {
142+
value = computedLayer;
143+
}
144+
return extractShadowProperties(value);
145+
}, [layer, computedLayer]);
140146

141147
const { offsetX, offsetY, blur, spread, color, inset } = layerValues;
142148
const colorControlProp = color ?? {
@@ -169,7 +175,7 @@ export const ShadowContent = ({
169175
const parsedValue = parsed.get(
170176
property === "dropShadow" ? "textShadow" : property
171177
);
172-
if (parsedValue?.type === "layers") {
178+
if (parsedValue?.type === "layers" || parsedValue?.type === "var") {
173179
onEditLayer(index, parsedValue, { isEphemeral: false });
174180
return;
175181
}
@@ -213,6 +219,7 @@ export const ShadowContent = ({
213219
// outline-offset is a fake property for validating box-shadow's offsetX.
214220
property="outlineOffset"
215221
styleSource="local"
222+
disabled={layer.type === "var"}
216223
value={offsetX ?? { type: "unit", value: 0, unit: "px" }}
217224
setValue={(value, options) =>
218225
handlePropertyChange({ offsetX: value }, options)
@@ -236,6 +243,7 @@ export const ShadowContent = ({
236243
// outline-offset is a fake property for validating box-shadow's offsetY.
237244
property="outlineOffset"
238245
styleSource="local"
246+
disabled={layer.type === "var"}
239247
value={offsetY ?? { type: "unit", value: 0, unit: "px" }}
240248
setValue={(value, options) =>
241249
handlePropertyChange({ offsetY: value }, options)
@@ -259,6 +267,7 @@ export const ShadowContent = ({
259267
// border-top-width is a fake property for validating box-shadow's blur.
260268
property="borderTopWidth"
261269
styleSource="local"
270+
disabled={layer.type === "var"}
262271
value={blur ?? { type: "unit", value: 0, unit: "px" }}
263272
setValue={(value, options) =>
264273
handlePropertyChange({ blur: value }, options)
@@ -283,6 +292,7 @@ export const ShadowContent = ({
283292
// outline-offset is a fake property for validating box-shadow's spread.
284293
property="outlineOffset"
285294
styleSource="local"
295+
disabled={layer.type === "var"}
286296
value={spread ?? { type: "unit", value: 0, unit: "px" }}
287297
setValue={(value, options) =>
288298
handlePropertyChange({ spread: value }, options)
@@ -313,6 +323,7 @@ export const ShadowContent = ({
313323
/>
314324
<ColorPicker
315325
property="color"
326+
disabled={layer.type === "var"}
316327
value={colorControlProp}
317328
currentColor={colorControlProp}
318329
getOptions={() =>
@@ -339,6 +350,7 @@ export const ShadowContent = ({
339350
/>
340351
<ToggleGroup
341352
type="single"
353+
disabled={layer.type === "var"}
342354
value={inset?.value ?? "normal"}
343355
defaultValue="inset"
344356
onValueChange={(value) => {
@@ -396,14 +408,10 @@ export const ShadowContent = ({
396408
</Tooltip>
397409
</Flex>
398410
</Label>
399-
<TextArea
400-
rows={3}
401-
name="description"
411+
<CssFragmentEditor
412+
invalid={intermediateValue?.type === "invalid"}
413+
autoFocus={layer.type === "var"}
402414
value={intermediateValue?.value ?? propertyValue ?? ""}
403-
css={{ minHeight: theme.spacing[14], ...textVariants.mono }}
404-
color={
405-
intermediateValue?.type === "invalid" ? "error" : undefined
406-
}
407415
onChange={handleChange}
408416
onBlur={handleComplete}
409417
onKeyDown={(event) => {

0 commit comments

Comments
 (0)