Skip to content

Commit 73b275e

Browse files
committed
experimental: Ranges animation control
Add border animation Move vars Try ranges
1 parent 1c254ed commit 73b275e

File tree

6 files changed

+484
-110
lines changed

6 files changed

+484
-110
lines changed

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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { AnimationPanelContent } from "./animation-panel-content";
3-
import { theme } from "@webstudio-is/design-system";
3+
import { Box, theme } from "@webstudio-is/design-system";
44
import { useState } from "react";
55
import type { ScrollAnimation, ViewAnimation } from "@webstudio-is/sdk";
66

@@ -26,27 +26,38 @@ const ScrollAnimationTemplate: Story["render"] = ({ value: initialValue }) => {
2626
const [value, setValue] = useState(initialValue);
2727

2828
return (
29-
<AnimationPanelContent
30-
type="scroll"
31-
value={value}
32-
onChange={(newValue) => {
33-
setValue(newValue as ScrollAnimation);
34-
}}
35-
/>
29+
<Box css={{ width: 240 }}>
30+
<AnimationPanelContent
31+
type="scroll"
32+
value={value}
33+
onChange={(newValue, isEphemeral) => {
34+
if (isEphemeral) {
35+
return;
36+
}
37+
38+
setValue(newValue as ScrollAnimation);
39+
}}
40+
/>
41+
</Box>
3642
);
3743
};
3844

3945
const ViewAnimationTemplate: Story["render"] = ({ value: initialValue }) => {
4046
const [value, setValue] = useState(initialValue);
4147

4248
return (
43-
<AnimationPanelContent
44-
type="view"
45-
value={value}
46-
onChange={(newValue) => {
47-
setValue(newValue as ViewAnimation);
48-
}}
49-
/>
49+
<Box css={{ width: 240 }}>
50+
<AnimationPanelContent
51+
type="view"
52+
value={value}
53+
onChange={(newValue, isEphemeral) => {
54+
if (isEphemeral) {
55+
return;
56+
}
57+
setValue(newValue as ViewAnimation);
58+
}}
59+
/>
60+
</Box>
5061
);
5162
};
5263

@@ -82,7 +93,7 @@ export const ViewAnimationStory: Story = {
8293
name: "view-animation",
8394
timing: {
8495
rangeStart: ["entry", { type: "unit", value: 0, unit: "%" }],
85-
rangeEnd: ["exit", { type: "unit", value: 100, unit: "%" }],
96+
rangeEnd: ["entry", { type: "unit", value: 100, unit: "%" }],
8697
},
8798
keyframes: [
8899
{

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

Lines changed: 135 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { Keyframes } from "./animation-keyframes";
4040
import { humanizeString } from "~/shared/string-utils";
4141
import { Link2Icon, Link2UnlinkedIcon } from "@webstudio-is/icons";
4242
import { $availableUnitVariables } from "~/builder/features/style-panel/shared/model";
43+
import { AnimationRanges } from "./animation-ranges";
4344

4445
const fillModeDescriptions: Record<
4546
NonNullable<ViewAnimation["timing"]["fill"]>,
@@ -469,22 +470,46 @@ export const AnimationPanelContent = ({
469470
}}
470471
/>
471472
</Grid>
473+
472474
<Grid
473475
gap={1}
474476
align={"center"}
475477
css={{
476-
gridTemplateColumns: "1fr 16px 1fr",
478+
gridTemplateAreas: `
479+
"rangeEndLabel . . animation"
480+
"rangeEndName rangeEndPercentage connect animation"
481+
"rangeStartLabel . connect animation"
482+
"rangeStartName rangeStartPercentage connect animation"
483+
"durationLabel durationValue . animation"
484+
`,
485+
gridTemplateColumns: "1.5fr 1fr 16px 34px",
477486
paddingInline: theme.panel.paddingInline,
478487
flexShrink: 0,
479488
}}
480489
>
481-
<Label htmlFor={fieldIds.rangeStartName}>Range Start</Label>
482-
<div />
483-
<Label disabled={!isRangeEndEnabled} htmlFor={fieldIds.rangeEndName}>
490+
<Label
491+
css={{
492+
gridArea: "rangeStartLabel",
493+
}}
494+
htmlFor={fieldIds.rangeStartName}
495+
>
496+
Range Start
497+
</Label>
498+
499+
<Label
500+
css={{
501+
gridArea: "rangeEndLabel",
502+
}}
503+
disabled={!isRangeEndEnabled}
504+
htmlFor={fieldIds.rangeEndName}
505+
>
484506
Range End
485507
</Label>
486508

487509
<Select
510+
css={{
511+
gridArea: "rangeStartName",
512+
}}
488513
id={fieldIds.rangeStartName}
489514
options={timelineRangeNames}
490515
getLabel={humanizeString}
@@ -550,7 +575,11 @@ export const AnimationPanelContent = ({
550575
);
551576
}}
552577
/>
553-
<Grid>
578+
<Grid
579+
css={{
580+
gridArea: "connect",
581+
}}
582+
>
554583
<EnhancedTooltip
555584
content={isLinked ? "Unlink range names" : "Link range names"}
556585
>
@@ -582,6 +611,9 @@ export const AnimationPanelContent = ({
582611
</EnhancedTooltip>
583612
</Grid>
584613
<Select
614+
css={{
615+
gridArea: "rangeEndName",
616+
}}
585617
id={fieldIds.rangeEndName}
586618
disabled={!isRangeEndEnabled}
587619
options={timelineRangeNames}
@@ -648,105 +680,114 @@ export const AnimationPanelContent = ({
648680
}}
649681
/>
650682

651-
<RangeValueInput
652-
id={fieldIds.rangeStartValue}
653-
value={
654-
value.timing.rangeStart?.[1] ?? {
655-
type: "unit",
656-
value: 0,
657-
unit: "%",
658-
}
659-
}
660-
onChange={(rangeStart, isEphemeral) => {
661-
if (rangeStart === undefined && isEphemeral) {
662-
handleChange(undefined, true);
663-
return;
683+
<Box css={{ gridArea: "rangeStartPercentage" }}>
684+
<RangeValueInput
685+
id={fieldIds.rangeStartValue}
686+
value={
687+
value.timing.rangeStart?.[1] ?? {
688+
type: "unit",
689+
value: 0,
690+
unit: "%",
691+
}
664692
}
693+
onChange={(rangeStart, isEphemeral) => {
694+
if (rangeStart === undefined && isEphemeral) {
695+
handleChange(undefined, true);
696+
return;
697+
}
665698

666-
const defaultTimelineRangeName = timelineRangeNames[0]!;
667-
668-
handleChange(
669-
{
670-
...value,
671-
timing: {
672-
...value.timing,
673-
rangeStart: [
674-
value.timing.rangeStart?.[0] ?? defaultTimelineRangeName,
675-
rangeStart,
676-
],
699+
const defaultTimelineRangeName = timelineRangeNames[0]!;
700+
701+
handleChange(
702+
{
703+
...value,
704+
timing: {
705+
...value.timing,
706+
rangeStart: [
707+
value.timing.rangeStart?.[0] ?? defaultTimelineRangeName,
708+
rangeStart,
709+
],
710+
},
677711
},
678-
},
679-
isEphemeral
680-
);
681-
}}
682-
/>
683-
<div />
684-
<RangeValueInput
685-
id={fieldIds.rangeEndValue}
686-
disabled={!isRangeEndEnabled}
687-
value={
688-
value.timing.rangeEnd?.[1] ?? {
689-
type: "unit",
690-
value: 0,
691-
unit: "%",
692-
}
693-
}
694-
onChange={(rangeEnd, isEphemeral) => {
695-
if (rangeEnd === undefined && isEphemeral) {
696-
handleChange(undefined, true);
697-
return;
712+
isEphemeral
713+
);
714+
}}
715+
/>
716+
</Box>
717+
<Box css={{ gridArea: "rangeEndPercentage" }}>
718+
<RangeValueInput
719+
id={fieldIds.rangeEndValue}
720+
disabled={!isRangeEndEnabled}
721+
value={
722+
value.timing.rangeEnd?.[1] ?? {
723+
type: "unit",
724+
value: 0,
725+
unit: "%",
726+
}
698727
}
728+
onChange={(rangeEnd, isEphemeral) => {
729+
if (rangeEnd === undefined && isEphemeral) {
730+
handleChange(undefined, true);
731+
return;
732+
}
699733

700-
const defaultTimelineRangeName = timelineRangeNames[0]!;
701-
702-
handleChange(
703-
{
704-
...value,
705-
timing: {
706-
...value.timing,
707-
rangeEnd: [
708-
value.timing.rangeEnd?.[0] ?? defaultTimelineRangeName,
709-
rangeEnd,
710-
],
734+
const defaultTimelineRangeName = timelineRangeNames[0]!;
735+
736+
handleChange(
737+
{
738+
...value,
739+
timing: {
740+
...value.timing,
741+
rangeEnd: [
742+
value.timing.rangeEnd?.[0] ?? defaultTimelineRangeName,
743+
rangeEnd,
744+
],
745+
},
711746
},
712-
},
713-
isEphemeral
714-
);
747+
isEphemeral
748+
);
749+
}}
750+
/>
751+
</Box>
752+
753+
<Label
754+
css={{
755+
gridArea: "durationLabel",
715756
}}
716-
/>
717-
</Grid>
757+
htmlFor={fieldIds.duration}
758+
>
759+
Duration
760+
</Label>
718761

719-
<Grid
720-
gap={1}
721-
align={"center"}
722-
css={{
723-
gridTemplateColumns: "1fr 16px 1fr",
724-
paddingInline: theme.panel.paddingInline,
725-
}}
726-
>
727-
<Label htmlFor={fieldIds.duration}>Duration</Label>
728-
<div />
729-
<DurationInput
730-
id={fieldIds.duration}
731-
value={value.timing.duration}
732-
onChange={(duration, isEphemeral) => {
733-
if (duration === undefined && isEphemeral) {
734-
handleChange(undefined, true);
735-
return;
736-
}
762+
<Box css={{ gridArea: "durationValue" }}>
763+
<DurationInput
764+
id={fieldIds.duration}
765+
value={value.timing.duration}
766+
onChange={(duration, isEphemeral) => {
767+
if (duration === undefined && isEphemeral) {
768+
handleChange(undefined, true);
769+
return;
770+
}
737771

738-
handleChange(
739-
{
740-
...value,
741-
timing: {
742-
...value.timing,
743-
duration,
772+
handleChange(
773+
{
774+
...value,
775+
timing: {
776+
...value.timing,
777+
duration,
778+
},
744779
},
745-
},
746-
isEphemeral
747-
);
748-
}}
749-
/>
780+
isEphemeral
781+
);
782+
}}
783+
/>
784+
</Box>
785+
<Grid css={{ gridArea: "animation", alignSelf: "stretch" }}>
786+
<AnimationRanges
787+
rangeStart={value.timing.rangeStart}
788+
rangeEnd={value.timing.rangeEnd}
789+
/>
790+
</Grid>
750791
</Grid>
751792

752793
<Keyframes

0 commit comments

Comments
 (0)