Skip to content

Commit edceea2

Browse files
authored
experimental: Link animation range names (webstudio-is#5056)
## Description <img width="523" alt="image" src="https://github.com/user-attachments/assets/81a860c5-fcd3-4f0c-af60-9073216a8da6" /> ## Steps for reproduction 1. click button 2. 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 1d9ae2a commit edceea2

File tree

1 file changed

+118
-58
lines changed

1 file changed

+118
-58
lines changed

apps/builder/app/builder/features/settings-panel/props-section/animation/animation-panel-content.tsx

Lines changed: 118 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { useState } from "react";
22
import {
33
Box,
4+
EnhancedTooltip,
45
Flex,
56
Grid,
67
InputField,
78
Label,
89
Select,
10+
SmallToggleButton,
911
theme,
1012
toast,
1113
} from "@webstudio-is/design-system";
@@ -34,6 +36,7 @@ import {
3436
} from "@webstudio-is/css-engine";
3537
import { Keyframes } from "./animation-keyframes";
3638
import { humanizeString } from "~/shared/string-utils";
39+
import { Link2Icon, Link2UnlinkedIcon } from "@webstudio-is/icons";
3740

3841
const fillModeDescriptions: Record<
3942
NonNullable<ViewAnimation["timing"]["fill"]>,
@@ -215,11 +218,27 @@ type AnimationPanelContentProps = {
215218
((value: undefined, isEphemeral: true) => void);
216219
};
217220

221+
const defaultRangeStart = {
222+
type: "unit",
223+
value: 0,
224+
unit: "%",
225+
};
226+
227+
const defaultRangeEnd = {
228+
type: "unit",
229+
value: 100,
230+
unit: "%",
231+
};
232+
218233
export const AnimationPanelContent = ({
219234
onChange,
220235
value,
221236
type,
222237
}: AnimationPanelContentProps) => {
238+
const [isLinked, setIsLinked] = useState(
239+
value.timing.rangeStart?.[0] === value.timing.rangeEnd?.[0]
240+
);
241+
223242
const fieldIds = useIds([
224243
"rangeStartName",
225244
"rangeStartValue",
@@ -302,12 +321,13 @@ export const AnimationPanelContent = ({
302321
gap={1}
303322
align={"center"}
304323
css={{
305-
gridTemplateColumns: "1fr 1fr",
324+
gridTemplateColumns: "1fr 16px 1fr",
306325
paddingInline: theme.panel.paddingInline,
307326
flexShrink: 0,
308327
}}
309328
>
310329
<Label htmlFor={fieldIds.fill}>Fill Mode</Label>
330+
<div />
311331
<Label htmlFor={fieldIds.easing}>Easing</Label>
312332

313333
<Select
@@ -358,6 +378,7 @@ export const AnimationPanelContent = ({
358378
);
359379
}}
360380
/>
381+
<div />
361382
<EasingInput
362383
id={fieldIds.easing}
363384
value={value.timing.easing}
@@ -384,13 +405,14 @@ export const AnimationPanelContent = ({
384405
gap={1}
385406
align={"center"}
386407
css={{
387-
gridTemplateColumns: "1fr 1fr",
408+
gridTemplateColumns: "1fr 16px 1fr",
388409
paddingInline: theme.panel.paddingInline,
389410
flexShrink: 0,
390411
}}
391412
>
392413
<Label htmlFor={fieldIds.rangeStartName}>Range Start</Label>
393-
<Label htmlFor={fieldIds.rangeStartValue}>Value</Label>
414+
<div />
415+
<Label htmlFor={fieldIds.rangeEndName}>Range End</Label>
394416

395417
<Select
396418
id={fieldIds.rangeStartName}
@@ -423,12 +445,14 @@ export const AnimationPanelContent = ({
423445
...value.timing,
424446
rangeStart: [
425447
timelineRangeName,
426-
value.timing.rangeStart?.[1] ?? {
427-
type: "unit",
428-
value: 0,
429-
unit: "%",
430-
},
448+
value.timing.rangeStart?.[1] ?? defaultRangeStart,
431449
],
450+
rangeEnd: isLinked
451+
? [
452+
timelineRangeName,
453+
value.timing.rangeEnd?.[1] ?? defaultRangeEnd,
454+
]
455+
: value.timing.rangeEnd,
432456
},
433457
},
434458
true
@@ -442,53 +466,51 @@ export const AnimationPanelContent = ({
442466
...value.timing,
443467
rangeStart: [
444468
timelineRangeName,
445-
value.timing.rangeStart?.[1] ?? {
446-
type: "unit",
447-
value: 0,
448-
unit: "%",
449-
},
469+
value.timing.rangeStart?.[1] ?? defaultRangeStart,
450470
],
471+
rangeEnd: isLinked
472+
? [
473+
timelineRangeName,
474+
value.timing.rangeEnd?.[1] ?? defaultRangeEnd,
475+
]
476+
: value.timing.rangeEnd,
451477
},
452478
},
453479
false
454480
);
455481
}}
456482
/>
457-
<RangeValueInput
458-
id={fieldIds.rangeStartValue}
459-
value={
460-
value.timing.rangeStart?.[1] ?? {
461-
type: "unit",
462-
value: 0,
463-
unit: "%",
464-
}
465-
}
466-
onChange={(rangeStart, isEphemeral) => {
467-
if (rangeStart === undefined && isEphemeral) {
468-
handleChange(undefined, true);
469-
return;
470-
}
471-
472-
const defaultTimelineRangeName = timelineRangeNames[0]!;
473-
474-
handleChange(
475-
{
476-
...value,
477-
timing: {
478-
...value.timing,
479-
rangeStart: [
480-
value.timing.rangeStart?.[0] ?? defaultTimelineRangeName,
481-
rangeStart,
482-
],
483-
},
484-
},
485-
isEphemeral
486-
);
487-
}}
488-
/>
489-
490-
<Label htmlFor={fieldIds.rangeEndName}>Range End</Label>
491-
<Label htmlFor={fieldIds.rangeEndValue}>Value</Label>
483+
<Grid>
484+
<EnhancedTooltip
485+
content={isLinked ? "Unlink range names" : "Link range names"}
486+
>
487+
<SmallToggleButton
488+
pressed={isLinked}
489+
onPressedChange={(pressed) => {
490+
setIsLinked(pressed);
491+
if (pressed) {
492+
handleChange(
493+
{
494+
...value,
495+
timing: {
496+
...value.timing,
497+
rangeEnd: pressed
498+
? [
499+
value.timing.rangeStart?.[0] ?? "entry",
500+
value.timing.rangeEnd?.[1] ?? defaultRangeEnd,
501+
]
502+
: value.timing.rangeEnd,
503+
},
504+
},
505+
false
506+
);
507+
}
508+
}}
509+
variant="normal"
510+
icon={isLinked ? <Link2Icon /> : <Link2UnlinkedIcon />}
511+
/>
512+
</EnhancedTooltip>
513+
</Grid>
492514
<Select
493515
id={fieldIds.rangeEndName}
494516
options={timelineRangeNames}
@@ -519,12 +541,14 @@ export const AnimationPanelContent = ({
519541
...value.timing,
520542
rangeEnd: [
521543
timelineRangeName,
522-
value.timing.rangeEnd?.[1] ?? {
523-
type: "unit",
524-
value: 0,
525-
unit: "%",
526-
},
544+
value.timing.rangeEnd?.[1] ?? defaultRangeEnd,
527545
],
546+
rangeStart: isLinked
547+
? [
548+
timelineRangeName,
549+
value.timing.rangeStart?.[1] ?? defaultRangeStart,
550+
]
551+
: value.timing.rangeStart,
528552
},
529553
},
530554
true
@@ -538,18 +562,54 @@ export const AnimationPanelContent = ({
538562
...value.timing,
539563
rangeEnd: [
540564
timelineRangeName,
541-
value.timing.rangeEnd?.[1] ?? {
542-
type: "unit",
543-
value: 0,
544-
unit: "%",
545-
},
565+
value.timing.rangeEnd?.[1] ?? defaultRangeEnd,
546566
],
567+
rangeStart: isLinked
568+
? [
569+
timelineRangeName,
570+
value.timing.rangeStart?.[1] ?? defaultRangeStart,
571+
]
572+
: value.timing.rangeStart,
547573
},
548574
},
549575
false
550576
);
551577
}}
552578
/>
579+
580+
<RangeValueInput
581+
id={fieldIds.rangeStartValue}
582+
value={
583+
value.timing.rangeStart?.[1] ?? {
584+
type: "unit",
585+
value: 0,
586+
unit: "%",
587+
}
588+
}
589+
onChange={(rangeStart, isEphemeral) => {
590+
if (rangeStart === undefined && isEphemeral) {
591+
handleChange(undefined, true);
592+
return;
593+
}
594+
595+
const defaultTimelineRangeName = timelineRangeNames[0]!;
596+
597+
handleChange(
598+
{
599+
...value,
600+
timing: {
601+
...value.timing,
602+
rangeStart: [
603+
value.timing.rangeStart?.[0] ?? defaultTimelineRangeName,
604+
rangeStart,
605+
],
606+
},
607+
},
608+
isEphemeral
609+
);
610+
}}
611+
/>
612+
<div />
553613
<RangeValueInput
554614
id={fieldIds.rangeEndValue}
555615
value={

0 commit comments

Comments
 (0)