11import { useState } from "react" ;
22import {
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" ;
3537import { Keyframes } from "./animation-keyframes" ;
3638import { humanizeString } from "~/shared/string-utils" ;
39+ import { Link2Icon , Link2UnlinkedIcon } from "@webstudio-is/icons" ;
3740
3841const 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+
218233export 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