Skip to content

Commit 939a2b8

Browse files
authored
feat: add shift/option modifiers to spacing and position input popovers (#3924)
Part of #3914 ## Description To allow entering a value from keyboard for all or opposite sides in spacing and position controls, we now support same modifiers as we use for drag guesture ## Steps for reproduction 1. open spacing 2. click on padding top number to open input 3. enter value, hold option or shift modifiers, then press enter 4. option places the value into opposite values, top/bottom, left/right, shift changes all sides ## Code Review - [ ] 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 - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
1 parent 1c7cfe1 commit 939a2b8

File tree

7 files changed

+128
-102
lines changed

7 files changed

+128
-102
lines changed

apps/builder/app/builder/features/style-panel/sections/position/position-control.stories.tsx

+6-8
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,18 @@ const bigValue = {
9393
} as const;
9494

9595
export const PositionControlComponent = () => {
96-
const { styleInfo, setProperty, deleteProperty, createBatchUpdate } =
97-
useStyleInfo({
98-
left: defaultValue,
99-
right: bigValue,
100-
top: defaultValue,
101-
bottom: defaultValue,
102-
});
96+
const { styleInfo, deleteProperty, createBatchUpdate } = useStyleInfo({
97+
left: defaultValue,
98+
right: bigValue,
99+
top: defaultValue,
100+
bottom: defaultValue,
101+
});
103102

104103
return (
105104
<Box css={{ marginLeft: 100 }}>
106105
<PositionControl
107106
createBatchUpdate={createBatchUpdate}
108107
currentStyle={styleInfo}
109-
setProperty={setProperty}
110108
deleteProperty={deleteProperty}
111109
/>
112110
</Box>

apps/builder/app/builder/features/style-panel/sections/position/position-control.tsx

+2-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Grid, theme } from "@webstudio-is/design-system";
22
import { PositionLayout, type PositionProperty } from "./position-layout";
33
import { movementMapPosition, useKeyboardNavigation } from "../shared/keyboard";
4-
import { useRef, useState, type ComponentProps } from "react";
4+
import { useRef, useState } from "react";
55
import type {
66
CreateBatchUpdate,
77
DeleteProperty,
8-
SetProperty,
98
} from "../../shared/use-style-data";
109
import { getStyleSource, type StyleInfo } from "../../shared/style-info";
1110
import { getPositionModifiersGroup, useScrub } from "../shared/scrub";
@@ -21,12 +20,10 @@ const Cell = ({
2120
onHover,
2221
isPopoverOpen,
2322
onPopoverClose,
24-
onChange,
2523
createBatchUpdate,
2624
}: {
2725
isPopoverOpen: boolean;
2826
onPopoverClose: () => void;
29-
onChange: ComponentProps<typeof InputPopover>["onChange"];
3027
scrubStatus: ReturnType<typeof useScrub>;
3128
currentStyle: StyleInfo;
3229
property: PositionProperty;
@@ -50,8 +47,8 @@ const Cell = ({
5047
value={finalValue}
5148
isOpen={isPopoverOpen}
5249
property={property}
53-
onChange={onChange}
5450
onClose={onPopoverClose}
51+
createBatchUpdate={createBatchUpdate}
5552
/>
5653
<PositionTooltip
5754
property={property}
@@ -87,7 +84,6 @@ type HoverTarget = {
8784
};
8885

8986
type PositionControlProps = {
90-
setProperty: SetProperty;
9187
deleteProperty: DeleteProperty;
9288
createBatchUpdate: CreateBatchUpdate;
9389
currentStyle: StyleInfo;
@@ -97,7 +93,6 @@ export const PositionControl = ({
9793
createBatchUpdate,
9894
currentStyle,
9995
deleteProperty,
100-
setProperty,
10196
}: PositionControlProps) => {
10297
const [hoverTarget, setHoverTarget] = useState<HoverTarget>();
10398

@@ -192,13 +187,6 @@ export const PositionControl = ({
192187
layoutRef.current?.focus();
193188
}
194189
}}
195-
onChange={(update, options) => {
196-
if (update.operation === "set") {
197-
setProperty(update.property)(update.value, options);
198-
} else {
199-
deleteProperty(update.property, options);
200-
}
201-
}}
202190
createBatchUpdate={createBatchUpdate}
203191
/>
204192
)}

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

-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ export const Section = ({
101101
<Grid gap={3} columns={2}>
102102
<PositionControl
103103
currentStyle={currentStyle}
104-
setProperty={setProperty}
105104
deleteProperty={deleteProperty}
106105
createBatchUpdate={createBatchUpdate}
107106
/>

apps/builder/app/builder/features/style-panel/sections/shared/input-popover.tsx

+58-25
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,65 @@ import {
66
PopoverTrigger,
77
PopoverContent,
88
} from "@webstudio-is/design-system";
9-
import type { StyleProperty, StyleValue } from "@webstudio-is/css-engine";
9+
import type { StyleValue } from "@webstudio-is/css-engine";
1010
import {
1111
CssValueInput,
1212
type IntermediateStyleValue,
1313
} from "../../shared/css-value-input";
1414
import type { StyleSource } from "../../shared/style-info";
1515
import type {
16+
CreateBatchUpdate,
1617
StyleUpdate,
1718
StyleUpdateOptions,
1819
} from "../../shared/use-style-data";
1920
import { theme } from "@webstudio-is/design-system";
21+
import { getPositionModifiersGroup, getSpaceModifiersGroup } from "./scrub";
22+
import type { SpaceStyleProperty } from "../space/types";
23+
import type { PositionProperty } from "../position/position-layout";
2024

2125
const slideUpAndFade = keyframes({
2226
"0%": { opacity: 0, transform: "scale(0.8)" },
2327
"100%": { opacity: 1, transform: "scale(1)" },
2428
});
2529

30+
// We need to differentiate between marginTop and top for example.
31+
const isSpace = (property: string) => {
32+
return property.startsWith("margin") || property.startsWith("padding");
33+
};
34+
2635
const Input = ({
2736
styleSource,
2837
value,
2938
property,
30-
onChange,
3139
onClosePopover,
40+
createBatchUpdate,
3241
}: {
3342
styleSource: StyleSource;
34-
property: StyleProperty;
43+
property: SpaceStyleProperty | PositionProperty;
3544
value: StyleValue;
36-
onChange: (update: StyleUpdate, options: StyleUpdateOptions) => void;
3745
onClosePopover: () => void;
46+
createBatchUpdate: CreateBatchUpdate;
3847
}) => {
3948
const [intermediateValue, setIntermediateValue] = useState<
4049
StyleValue | IntermediateStyleValue
4150
>();
4251

52+
const onChange = (
53+
updates: Array<StyleUpdate>,
54+
options: StyleUpdateOptions
55+
) => {
56+
const batch = createBatchUpdate();
57+
for (const update of updates) {
58+
if (update.operation === "set") {
59+
batch.setProperty(update.property)(update.value);
60+
}
61+
if (update.operation === "delete") {
62+
batch.deleteProperty(update.property);
63+
}
64+
}
65+
batch.publish(options);
66+
};
67+
4368
return (
4469
<CssValueInput
4570
styleSource={styleSource}
@@ -50,38 +75,47 @@ const Input = ({
5075
setIntermediateValue(styleValue);
5176

5277
if (styleValue === undefined) {
53-
onChange({ operation: "delete", property }, { isEphemeral: true });
78+
onChange([{ operation: "delete", property }], { isEphemeral: true });
5479
return;
5580
}
5681

5782
if (styleValue.type !== "intermediate") {
58-
onChange(
59-
{ operation: "set", property, value: styleValue },
60-
{ isEphemeral: true }
61-
);
83+
onChange([{ operation: "set", property, value: styleValue }], {
84+
isEphemeral: true,
85+
});
6286
}
6387
}}
6488
onHighlight={(styleValue) => {
6589
if (styleValue === undefined) {
66-
onChange({ operation: "delete", property }, { isEphemeral: true });
90+
onChange([{ operation: "delete", property }], { isEphemeral: true });
6791
return;
6892
}
6993

70-
onChange(
71-
{ operation: "set", property, value: styleValue },
72-
{ isEphemeral: true }
73-
);
94+
onChange([{ operation: "set", property, value: styleValue }], {
95+
isEphemeral: true,
96+
});
7497
}}
75-
onChangeComplete={({ value, reason }) => {
98+
onChangeComplete={({ value, type, altKey, shiftKey }) => {
99+
const updates: Array<StyleUpdate> = [];
100+
const options = { isEphemeral: false };
101+
const modifiers = { shiftKey, altKey };
102+
const properties = isSpace(property)
103+
? getSpaceModifiersGroup(property as SpaceStyleProperty, modifiers)
104+
: getPositionModifiersGroup(property as PositionProperty, modifiers);
105+
76106
setIntermediateValue(undefined);
77-
onChange({ operation: "set", property, value }, { isEphemeral: false });
78107

79-
if (reason === "blur" || reason === "enter") {
108+
properties.forEach((property) => {
109+
updates.push({ operation: "set", property, value });
110+
});
111+
onChange(updates, options);
112+
113+
if (type === "blur" || type === "enter") {
80114
onClosePopover();
81115
}
82116
}}
83117
onAbort={() => {
84-
onChange({ operation: "delete", property }, { isEphemeral: true });
118+
onChange([{ operation: "delete", property }], { isEphemeral: true });
85119
}}
86120
/>
87121
);
@@ -108,14 +142,13 @@ export const InputPopover = ({
108142
styleSource,
109143
property,
110144
value,
111-
onChange,
112145
isOpen,
113146
onClose,
114-
}: {
115-
styleSource: StyleSource;
116-
property: StyleProperty;
117-
value: StyleValue;
118-
onChange: ComponentProps<typeof Input>["onChange"];
147+
createBatchUpdate,
148+
}: Pick<
149+
ComponentProps<typeof Input>,
150+
"styleSource" | "property" | "value" | "createBatchUpdate"
151+
> & {
119152
isOpen: boolean;
120153
onClose: () => void;
121154
}) => {
@@ -136,7 +169,7 @@ export const InputPopover = ({
136169
styleSource={styleSource}
137170
value={value}
138171
property={property}
139-
onChange={onChange}
172+
createBatchUpdate={createBatchUpdate}
140173
onClosePopover={onClose}
141174
/>
142175
</PopoverContentStyled>

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

+3-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type ComponentProps, useState, useRef } from "react";
1+
import { useState, useRef } from "react";
22
import type { SectionProps } from "../shared/section";
33
import { SpaceLayout } from "./layout";
44
import { ValueText } from "../shared/value-text";
@@ -10,13 +10,11 @@ import { SpaceTooltip } from "./tooltip";
1010
import { getStyleSource } from "../../shared/style-info";
1111
import { CollapsibleSection } from "../../shared/collapsible-section";
1212
import { movementMapSpace, useKeyboardNavigation } from "../shared/keyboard";
13-
1413
import type { CreateBatchUpdate } from "../../shared/use-style-data";
1514

1615
const Cell = ({
1716
isPopoverOpen,
1817
onPopoverClose,
19-
onChange,
2018
onHover,
2119
property,
2220
scrubStatus,
@@ -25,7 +23,6 @@ const Cell = ({
2523
}: {
2624
isPopoverOpen: boolean;
2725
onPopoverClose: () => void;
28-
onChange: ComponentProps<typeof InputPopover>["onChange"];
2926
onHover: (target: HoverTarget | undefined) => void;
3027
property: SpaceStyleProperty;
3128
scrubStatus: ReturnType<typeof useScrub>;
@@ -39,7 +36,7 @@ const Cell = ({
3936

4037
// for TypeScript
4138
if (finalValue === undefined) {
42-
return null;
39+
return;
4340
}
4441

4542
return (
@@ -49,8 +46,8 @@ const Cell = ({
4946
value={finalValue}
5047
isOpen={isPopoverOpen}
5148
property={property}
52-
onChange={onChange}
5349
onClose={onPopoverClose}
50+
createBatchUpdate={createBatchUpdate}
5451
/>
5552
<SpaceTooltip
5653
property={property}
@@ -83,7 +80,6 @@ const Cell = ({
8380
export { spaceProperties as properties };
8481

8582
export const Section = ({
86-
setProperty,
8783
deleteProperty,
8884
createBatchUpdate,
8985
currentStyle,
@@ -168,13 +164,6 @@ export const Section = ({
168164
layoutRef.current?.focus();
169165
}
170166
}}
171-
onChange={(update, options) => {
172-
if (update.operation === "set") {
173-
setProperty(update.property)(update.value, options);
174-
} else {
175-
deleteProperty(update.property, options);
176-
}
177-
}}
178167
onHover={handleHover}
179168
property={property}
180169
scrubStatus={scrubStatus}

0 commit comments

Comments
 (0)