Skip to content

Commit

Permalink
feat: add transform section under style-panel with ui controls (#3718)
Browse files Browse the repository at this point in the history
## Description

Adds the primary `translate`, `scale`, `rotate`, skew` properties under
transform section. Related to #3411

## Steps for reproduction
- Add multiple properties under the transform section.
- Update values of each property from the UI.
- Hide/Delete each individual property

## Todo
- [x] Need to discuss if the `isExperiemental` flag need to be removed
before merging the branch.

## Code Review

- [x] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [x] made a self-review
- [x] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [x] tested locally and on preview environment (preview dev login:
5de6)
- [x] added tests
- [ ] if any new env variables are added, added them to `.env` file

---------

Co-authored-by: Oleg Isonen <[email protected]>
  • Loading branch information
JayaKrishnaNamburu and kof authored Jul 27, 2024
1 parent 31bdec9 commit 5b46f79
Show file tree
Hide file tree
Showing 17 changed files with 1,650 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as outline from "./outline/outline";
import * as advanced from "./advanced/advanced";
import * as textShadows from "./text-shadows/text-shadows";
import * as backdropFilter from "./backdrop-filter/backdrop-filter";
import * as transforms from "./transforms/transforms";
import type { StyleProperty } from "@webstudio-is/css-engine";
import type { SectionProps } from "./shared/section";

Expand All @@ -39,6 +40,7 @@ export const sections = new Map<
["filter", filter],
["backdropFilters", backdropFilter],
["transitions", transitions],
["transfrom", transforms],
["outline", outline],
["advanced", advanced],
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Flex, Grid, Label } from "@webstudio-is/design-system";
import {
updateRotateOrSkewPropertyValue,
type TransformPanelProps,
} from "./transform-utils";
import {
XAxisRotateIcon,
YAxisRotateIcon,
ZAxisRotateIcon,
} from "@webstudio-is/icons";
import { CssValueInputContainer } from "../../shared/css-value-input";
import {
toValue,
UnitValue,
type FunctionValue,
type StyleValue,
} from "@webstudio-is/css-engine";
import type { StyleUpdateOptions } from "../../shared/use-style-data";
import { parseCssValue } from "@webstudio-is/css-data";
import { extractRotatePropertiesFromTransform } from "./transform-utils";

export const RotatePanelContent = (props: TransformPanelProps) => {
const { propertyValue, setProperty, currentStyle } = props;
const { rotateX, rotateY, rotateZ } =
extractRotatePropertiesFromTransform(propertyValue);

const handlePropertyUpdate = (
index: number,
prop: string,
value: StyleValue,
options?: StyleUpdateOptions
) => {
let newValue: UnitValue = { type: "unit", value: 0, unit: "deg" };

if (value.type === "unit") {
newValue = value;
}

if (value.type === "tuple" && value.value[0].type === "unit") {
newValue = value.value[0];
}

const newFunctionValue: FunctionValue = {
type: "function",
name: prop,
args: { type: "layers", value: [newValue] },
};

const newPropertyValue = updateRotateOrSkewPropertyValue({
panel: "rotate",
index,
currentStyle,
value: newFunctionValue,
propertyValue,
});

const rotate = parseCssValue("transform", toValue(newPropertyValue));
if (rotate.type === "invalid") {
return;
}

setProperty("transform")(rotate, options);
};

return (
<Flex direction="column" gap={2}>
<Grid
gap={1}
css={{ alignItems: "center", gridTemplateColumns: "auto 1fr 1fr" }}
>
<XAxisRotateIcon />
<Label> Rotate X</Label>
<CssValueInputContainer
key="rotateX"
styleSource="local"
property="rotate"
value={
rotateX?.type === "function" && rotateX.args.type === "layers"
? rotateX.args.value[0]
: { type: "unit", value: 0, unit: "deg" }
}
keywords={[]}
setValue={(value, options) => {
handlePropertyUpdate(0, "rotateX", value, options);
}}
deleteProperty={() => {}}
/>
</Grid>
<Grid
gap={1}
css={{ alignItems: "center", gridTemplateColumns: "auto 1fr 1fr" }}
>
<YAxisRotateIcon />
<Label> Rotate Y</Label>
<CssValueInputContainer
key="rotateY"
styleSource="local"
property="rotate"
value={
rotateY?.type === "function" && rotateY.args.type === "layers"
? rotateY.args.value[0]
: { type: "unit", value: 0, unit: "deg" }
}
keywords={[]}
setValue={(value, options) => {
handlePropertyUpdate(1, "rotateY", value, options);
}}
deleteProperty={() => {}}
/>
</Grid>
<Grid
gap={1}
css={{ alignItems: "center", gridTemplateColumns: "auto 1fr 1fr" }}
>
<ZAxisRotateIcon />
<Label> Rotate Z</Label>
<CssValueInputContainer
key="rotateZ"
styleSource="local"
property="rotate"
value={
rotateZ?.type === "function" && rotateZ.args.type === "layers"
? rotateZ.args.value[0]
: { type: "unit", value: 0, unit: "deg" }
}
keywords={[]}
setValue={(value, options) => {
handlePropertyUpdate(2, "rotateZ", value, options);
}}
deleteProperty={() => {}}
/>
</Grid>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Flex, Grid, Label } from "@webstudio-is/design-system";
import {
StyleValue,
toValue,
type StyleProperty,
} from "@webstudio-is/css-engine";
import { CssValueInputContainer } from "../../shared/css-value-input";
import type { StyleUpdateOptions } from "../../shared/use-style-data";
import {
updateTransformTuplePropertyValue,
type TransformPanelProps,
} from "./transform-utils";
import { XAxisIcon, YAxisIcon, ZAxisIcon } from "@webstudio-is/icons";
import { parseCssValue } from "@webstudio-is/css-data";

// We use fakeProperty to pass for the CssValueInputContainer.
// As we know during parsing, the syntax for scale is wrong in the css-data package.
// https://github.com/mdn/data/pull/746
// https://developer.mozilla.org/en-US/docs/Web/CSS/opacity#syntax
// number | percentage
const fakeProperty = "opacity";
const property: StyleProperty = "scale";

export const ScalePanelContent = (props: TransformPanelProps) => {
const { propertyValue, setProperty } = props;
const [scaleX, scaleY, scaleZ] = propertyValue.value;

const handlePropertyUpdate = (
index: number,
value: StyleValue,
options?: StyleUpdateOptions
) => {
if (value.type !== "unit") {
return;
}

const newValue = updateTransformTuplePropertyValue(
index,
value,
propertyValue
);

const scale = parseCssValue(property, toValue(newValue));
if (scale.type === "invalid") {
return;
}

setProperty(property)(scale, options);
};

return (
<Flex direction="column" gap={2}>
<Grid
gap={1}
css={{ alignItems: "center", gridTemplateColumns: "auto 1fr 1fr" }}
>
<XAxisIcon />
<Label> Scale X</Label>
<CssValueInputContainer
key="scaleX"
styleSource="local"
property={fakeProperty}
value={scaleX}
keywords={[]}
setValue={(newValue, options) => {
handlePropertyUpdate(0, newValue, options);
}}
deleteProperty={() => {}}
/>
</Grid>
<Grid
gap={1}
css={{ alignItems: "center", gridTemplateColumns: "auto 1fr 1fr" }}
>
<YAxisIcon />
<Label> Scale Y</Label>
<CssValueInputContainer
key="scaleY"
styleSource="local"
property={fakeProperty}
value={scaleY}
keywords={[]}
setValue={(newValue, options) => {
handlePropertyUpdate(1, newValue, options);
}}
deleteProperty={() => {}}
/>
</Grid>
<Grid
gap={1}
css={{ alignItems: "center", gridTemplateColumns: "auto 1fr 1fr" }}
>
<ZAxisIcon />
<Label> Scale Z</Label>
<CssValueInputContainer
key="scaleZ"
styleSource="local"
property={fakeProperty}
value={scaleZ}
keywords={[]}
setValue={(newValue, options) => {
handlePropertyUpdate(2, newValue, options);
}}
deleteProperty={() => {}}
/>
</Grid>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Flex, Label, Grid } from "@webstudio-is/design-system";
import {
updateRotateOrSkewPropertyValue,
extractSkewPropertiesFromTransform,
type TransformPanelProps,
} from "./transform-utils";
import { XAxisIcon, YAxisIcon } from "@webstudio-is/icons";
import { CssValueInputContainer } from "../../shared/css-value-input";
import type { StyleUpdateOptions } from "../../shared/use-style-data";
import {
StyleValue,
toValue,
UnitValue,
type FunctionValue,
} from "@webstudio-is/css-engine";
import { parseCssValue } from "@webstudio-is/css-data";

// We use fakeProperty to pass for the CssValueInputContainer.
// https://developer.mozilla.org/en-US/docs/Web/CSS/rotate#formal_syntax
// angle
const fakeProperty = "rotate";

export const SkewPanelContent = (props: TransformPanelProps) => {
const { propertyValue, setProperty, currentStyle } = props;
const { skewX, skewY } = extractSkewPropertiesFromTransform(propertyValue);

const handlePropertyUpdate = (
index: number,
prop: string,
value: StyleValue,
options?: StyleUpdateOptions
) => {
let newValue: UnitValue = { type: "unit", value: 0, unit: "deg" };

if (value.type === "unit") {
newValue = value;
}

if (value.type === "tuple" && value.value[0].type === "unit") {
newValue = value.value[0];
}

const newFunctionValue: FunctionValue = {
type: "function",
name: prop,
args: { type: "layers", value: [newValue] },
};

const newPropertyValue = updateRotateOrSkewPropertyValue({
panel: "skew",
index,
currentStyle,
value: newFunctionValue,
propertyValue,
});

const skew = parseCssValue("transform", toValue(newPropertyValue));
if (skew.type === "invalid") {
return;
}

setProperty("transform")(skew, options);
};

return (
<Flex direction="column" gap={2}>
<Grid
gap={1}
css={{ alignItems: "center", gridTemplateColumns: "auto 1fr 1fr" }}
>
<XAxisIcon />
<Label> Skew X</Label>
<CssValueInputContainer
key="skewX"
styleSource="local"
property={fakeProperty}
value={
skewX?.type === "function" && skewX.args.type === "layers"
? skewX.args.value[0]
: { type: "unit", value: 0, unit: "deg" }
}
keywords={[]}
setValue={(value, options) => {
handlePropertyUpdate(0, "skewX", value, options);
}}
deleteProperty={() => {}}
/>
</Grid>
<Grid
gap={1}
css={{ alignItems: "center", gridTemplateColumns: "auto 1fr 1fr" }}
>
<YAxisIcon />
<Label> Skew Y</Label>
<CssValueInputContainer
key="skewY"
styleSource="local"
property={fakeProperty}
value={
skewY?.type === "function" && skewY.args.type === "layers"
? skewY.args.value[0]
: { type: "unit", value: 0, unit: "deg" }
}
keywords={[]}
setValue={(value, options) => {
handlePropertyUpdate(1, "skewY", value, options);
}}
deleteProperty={() => {}}
/>
</Grid>
</Flex>
);
};
Loading

0 comments on commit 5b46f79

Please sign in to comment.