Skip to content

Commit 7ff72c3

Browse files
authored
feat: Make complex values in spacing UI look good (#5020)
<img width="247" alt="image" src="https://github.com/user-attachments/assets/ed89dd8c-c45e-4bf6-9e28-b6fb8c812bbd" /> ## Description 1. Cuts off the long values and avoids badly looking UI 2. Adds scroll on pointer over effect to see the value quickly ## Steps for reproduction 1. click button 3. expect xyz ## 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: 0000) - [ ] 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 910a7a2 commit 7ff72c3

File tree

7 files changed

+149
-149
lines changed

7 files changed

+149
-149
lines changed

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,6 @@ const Cell = ({
5050
/>
5151
<InsetTooltip property={property} preventOpen={scrubStatus.isActive}>
5252
<ValueText
53-
css={{
54-
// We want value to have `default` cursor to indicate that it's clickable,
55-
// unlike the rest of the value area that has cursor that indicates scrubbing.
56-
// Click and scrub works everywhere anyway, but we want cursors to be different.
57-
//
58-
// In order to have control over cursor we're setting pointerEvents to "all" here
59-
// because SpaceLayout sets it to "none" for cells' content.
60-
pointerEvents: "all",
61-
}}
6253
value={finalValue}
6354
source={styleDecl.source.name}
6455
onMouseEnter={(event) =>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export const InsetTooltip = ({
141141
}
142142
>
143143
{/* @todo show tooltip on focus */}
144-
<div>{children}</div>
144+
<div style={{ maxWidth: "100%" }}>{children}</div>
145145
</Tooltip>
146146
);
147147
};

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

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
import { styled, Text } from "@webstudio-is/design-system";
22
import type { StyleValue } from "@webstudio-is/css-engine";
3-
import { useMemo, type ComponentProps } from "react";
3+
import { useEffect, useMemo, type ComponentProps } from "react";
44
import { theme } from "@webstudio-is/design-system";
55
import { toValue } from "@webstudio-is/css-engine";
6+
import { scrollByPointer } from "../../shared/scroll-by-pointer";
67

78
const Container = styled("button", {
89
// fit-content is not needed for the "button" element,
910
// leave it here in case of tag change
1011
width: "fit-content",
12+
maxWidth: "100%",
1113
display: "flex",
12-
flexWrap: "wrap",
14+
flexWrap: "nowrap",
1315
alignItems: "baseline",
14-
justifyContent: "center",
16+
justifyContent: "start",
1517
border: "none",
1618
borderRadius: theme.borderRadius[3],
17-
padding: `${theme.spacing[2]}`,
19+
paddingBlock: theme.spacing[2],
20+
paddingInline: 0,
21+
overflow: "hidden",
22+
whiteSpace: "nowrap",
23+
// We want value to have `default` cursor to indicate that it's clickable,
24+
// unlike the rest of the value area that has cursor that indicates scrubbing.
25+
// Click and scrub works everywhere anyway, but we want cursors to be different.
26+
//
27+
// In order to have control over cursor we're setting pointerEvents to "all" here
28+
// because SpaceLayout sets it to "none" for cells' content.
29+
pointerEvents: "all",
1830

1931
"&:focus-visible": {
2032
outline: "none",
@@ -52,21 +64,13 @@ const Container = styled("button", {
5264
export const ValueText = ({
5365
value,
5466
source,
55-
truncate = false,
5667
...rest
57-
}: { value: StyleValue; truncate?: boolean } & Omit<
58-
ComponentProps<typeof Container>,
59-
"value"
60-
>) => {
68+
}: { value: StyleValue } & Omit<ComponentProps<typeof Container>, "value">) => {
6169
const children = useMemo(() => {
6270
if (value.type === "unit") {
6371
// we want to show "0" rather than "0px" for default values for cleaner UI
6472
if (source === "default" && value.unit === "px" && value.value === 0) {
65-
return (
66-
<Text truncate={truncate} variant="spaceSectionValueText">
67-
{value.value}
68-
</Text>
69-
);
73+
return <Text variant="spaceSectionValueText">{value.value}</Text>;
7074
}
7175

7276
/**
@@ -76,7 +80,7 @@ export const ValueText = ({
7680

7781
return (
7882
<>
79-
<Text truncate={truncate} variant="spaceSectionValueText">
83+
<Text variant="spaceSectionValueText">
8084
{value.value}
8185
<Text
8286
variant="spaceSectionValueText"
@@ -93,22 +97,20 @@ export const ValueText = ({
9397
}
9498

9599
if (value.type === "var") {
96-
return (
97-
<Text truncate={truncate} variant="spaceSectionValueText">
98-
--{value.value}
99-
</Text>
100-
);
100+
return <Text variant="spaceSectionValueText">{value.value}</Text>;
101101
}
102102

103-
return (
104-
<Text truncate={truncate} variant="spaceSectionValueText">
105-
{toValue(value)}
106-
</Text>
107-
);
108-
}, [value, source, truncate]);
103+
return <Text variant="spaceSectionValueText">{toValue(value)}</Text>;
104+
}, [value, source]);
105+
106+
const { abort, ...autoScrollProps } = useMemo(scrollByPointer, []);
107+
108+
useEffect(() => {
109+
return () => abort("unmount");
110+
}, [abort]);
109111

110112
return (
111-
<Container source={source} {...rest} tabIndex={-1}>
113+
<Container source={source} {...rest} {...autoScrollProps} tabIndex={-1}>
112114
{children}
113115
</Container>
114116
);

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ const Cell = styled("div", {
173173
alignItems: "center",
174174
justifyContent: "center",
175175
maxWidth: "100%",
176-
padding: theme.spacing[2],
177176
variants: {
178177
property: {
179178
"margin-top": { gridColumn: "2 / 5", gridRow: "1" },

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

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useState, useRef } from "react";
2-
import { theme } from "@webstudio-is/design-system";
32
import type { CssProperty } from "@webstudio-is/css-engine";
43
import { SpaceLayout } from "./layout";
54
import { ValueText } from "../shared/value-text";
@@ -10,7 +9,6 @@ import {
109
type SpaceStyleProperty,
1110
} from "./properties";
1211
import { InputPopover } from "../shared/input-popover";
13-
import { SpaceTooltip } from "./tooltip";
1412
import { StyleSection } from "../../shared/style-section";
1513
import { useKeyboardNavigation } from "../shared/keyboard";
1614
import { useComputedStyleDecl, useComputedStyles } from "../../shared/model";
@@ -120,27 +118,14 @@ const Cell = ({
120118
getActiveProperties={getActiveProperties}
121119
onClose={onPopoverClose}
122120
/>
123-
<SpaceTooltip property={property} preventOpen={scrubStatus.isActive}>
124-
<ValueText
125-
truncate
126-
css={{
127-
// We want value to have `default` cursor to indicate that it's clickable,
128-
// unlike the rest of the value area that has cursor that indicates scrubbing.
129-
// Click and scrub works everywhere anyway, but we want cursors to be different.
130-
//
131-
// In order to have control over cursor we're setting pointerEvents to "all" here
132-
// because SpaceLayout sets it to "none" for cells' content.
133-
pointerEvents: "all",
134-
maxWidth: theme.spacing[18],
135-
}}
136-
value={finalValue}
137-
source={styleDecl.source.name}
138-
onMouseEnter={(event) =>
139-
onHover({ property, element: event.currentTarget })
140-
}
141-
onMouseLeave={() => onHover(undefined)}
142-
/>
143-
</SpaceTooltip>
121+
<ValueText
122+
value={finalValue}
123+
source={styleDecl.source.name}
124+
onMouseEnter={(event) =>
125+
onHover({ property, element: event.currentTarget })
126+
}
127+
onMouseLeave={() => onHover(undefined)}
128+
/>
144129
</>
145130
);
146131
};

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

Lines changed: 2 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
ValueEditorDialog,
5959
} from "./value-editor-dialog";
6060
import { useEffectEvent } from "~/shared/hook-utils/effect-event";
61+
import { scrollByPointer } from "../scroll-by-pointer";
6162

6263
// We need to enable scrub on properties that can have numeric value.
6364
const canBeNumber = (property: CssProperty, value: CssValueInputValue) => {
@@ -345,93 +346,6 @@ const itemToString = (item: CssValueInputValue | null) => {
345346
return toValue(item);
346347
};
347348

348-
const scrollAhead = ({ target, clientX }: MouseEvent) => {
349-
const element = target as HTMLInputElement;
350-
351-
if (element.scrollWidth === element.clientWidth) {
352-
// Nothing to scroll.
353-
return false;
354-
}
355-
const inputRect = element.getBoundingClientRect();
356-
357-
// Calculate the relative x position of the mouse within the input element
358-
const relativeMouseX = clientX - inputRect.x;
359-
360-
// Calculate the percentage position (0% at the beginning, 100% at the end)
361-
const inputWidth = inputRect.width;
362-
const mousePercentageX = Math.ceil((relativeMouseX / inputWidth) * 100);
363-
364-
// Apply acceleration based on the relative position of the mouse
365-
// Closer to the beginning (-20%), closer to the end (+20%)
366-
const accelerationFactor = (mousePercentageX - 50) / 50;
367-
const adjustedMousePercentageX = Math.min(
368-
Math.max(mousePercentageX + accelerationFactor * 20, 0),
369-
100
370-
);
371-
372-
// Calculate the scroll position corresponding to the adjusted percentage
373-
const scrollPosition =
374-
(adjustedMousePercentageX / 100) *
375-
(element.scrollWidth - element.clientWidth);
376-
377-
// Scroll the input element
378-
element.scroll({ left: scrollPosition });
379-
return true;
380-
};
381-
382-
const getAutoScrollProps = () => {
383-
let abortController = new AbortController();
384-
385-
const abort = (reason: string) => {
386-
abortController.abort(reason);
387-
};
388-
389-
return {
390-
abort,
391-
onMouseOver(event: MouseEvent) {
392-
if (event.target === document.activeElement) {
393-
abort("focused");
394-
return;
395-
}
396-
if (scrollAhead(event) === false) {
397-
return;
398-
}
399-
400-
abortController = new AbortController();
401-
event.target?.addEventListener(
402-
"mousemove",
403-
(event) => {
404-
if (event.target === document.activeElement) {
405-
abort("focused");
406-
return;
407-
}
408-
requestAnimationFrame(() => {
409-
scrollAhead(event as MouseEvent);
410-
});
411-
},
412-
{
413-
signal: abortController.signal,
414-
passive: true,
415-
}
416-
);
417-
},
418-
onMouseOut(event: MouseEvent) {
419-
if (event.target === document.activeElement) {
420-
abort("focused");
421-
return;
422-
}
423-
(event.target as HTMLInputElement).scroll({
424-
left: 0,
425-
behavior: "smooth",
426-
});
427-
abort("mouseout");
428-
},
429-
onFocus() {
430-
abort("focus");
431-
},
432-
};
433-
};
434-
435349
const Description = styled(Box, { width: theme.spacing[27] });
436350

437351
/**
@@ -880,7 +794,7 @@ export const CssValueInput = ({
880794
};
881795

882796
const { abort, ...autoScrollProps } = useMemo(() => {
883-
return getAutoScrollProps();
797+
return scrollByPointer();
884798
}, []);
885799

886800
useEffect(() => {

0 commit comments

Comments
 (0)