1
1
import { useState } from "react" ;
2
2
import {
3
3
Box ,
4
+ EnhancedTooltip ,
4
5
Flex ,
5
6
Grid ,
6
7
InputField ,
7
8
Label ,
8
9
Select ,
10
+ SmallToggleButton ,
9
11
theme ,
10
12
toast ,
11
13
} from "@webstudio-is/design-system" ;
@@ -34,6 +36,7 @@ import {
34
36
} from "@webstudio-is/css-engine" ;
35
37
import { Keyframes } from "./animation-keyframes" ;
36
38
import { humanizeString } from "~/shared/string-utils" ;
39
+ import { Link2Icon , Link2UnlinkedIcon } from "@webstudio-is/icons" ;
37
40
38
41
const fillModeDescriptions : Record <
39
42
NonNullable < ViewAnimation [ "timing" ] [ "fill" ] > ,
@@ -215,11 +218,27 @@ type AnimationPanelContentProps = {
215
218
( ( value : undefined , isEphemeral : true ) => void ) ;
216
219
} ;
217
220
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
+
218
233
export const AnimationPanelContent = ( {
219
234
onChange,
220
235
value,
221
236
type,
222
237
} : AnimationPanelContentProps ) => {
238
+ const [ isLinked , setIsLinked ] = useState (
239
+ value . timing . rangeStart ?. [ 0 ] === value . timing . rangeEnd ?. [ 0 ]
240
+ ) ;
241
+
223
242
const fieldIds = useIds ( [
224
243
"rangeStartName" ,
225
244
"rangeStartValue" ,
@@ -302,12 +321,13 @@ export const AnimationPanelContent = ({
302
321
gap = { 1 }
303
322
align = { "center" }
304
323
css = { {
305
- gridTemplateColumns : "1fr 1fr" ,
324
+ gridTemplateColumns : "1fr 16px 1fr" ,
306
325
paddingInline : theme . panel . paddingInline ,
307
326
flexShrink : 0 ,
308
327
} }
309
328
>
310
329
< Label htmlFor = { fieldIds . fill } > Fill Mode</ Label >
330
+ < div />
311
331
< Label htmlFor = { fieldIds . easing } > Easing</ Label >
312
332
313
333
< Select
@@ -358,6 +378,7 @@ export const AnimationPanelContent = ({
358
378
) ;
359
379
} }
360
380
/>
381
+ < div />
361
382
< EasingInput
362
383
id = { fieldIds . easing }
363
384
value = { value . timing . easing }
@@ -384,13 +405,14 @@ export const AnimationPanelContent = ({
384
405
gap = { 1 }
385
406
align = { "center" }
386
407
css = { {
387
- gridTemplateColumns : "1fr 1fr" ,
408
+ gridTemplateColumns : "1fr 16px 1fr" ,
388
409
paddingInline : theme . panel . paddingInline ,
389
410
flexShrink : 0 ,
390
411
} }
391
412
>
392
413
< Label htmlFor = { fieldIds . rangeStartName } > Range Start</ Label >
393
- < Label htmlFor = { fieldIds . rangeStartValue } > Value</ Label >
414
+ < div />
415
+ < Label htmlFor = { fieldIds . rangeEndName } > Range End</ Label >
394
416
395
417
< Select
396
418
id = { fieldIds . rangeStartName }
@@ -423,12 +445,14 @@ export const AnimationPanelContent = ({
423
445
...value . timing ,
424
446
rangeStart : [
425
447
timelineRangeName ,
426
- value . timing . rangeStart ?. [ 1 ] ?? {
427
- type : "unit" ,
428
- value : 0 ,
429
- unit : "%" ,
430
- } ,
448
+ value . timing . rangeStart ?. [ 1 ] ?? defaultRangeStart ,
431
449
] ,
450
+ rangeEnd : isLinked
451
+ ? [
452
+ timelineRangeName ,
453
+ value . timing . rangeEnd ?. [ 1 ] ?? defaultRangeEnd ,
454
+ ]
455
+ : value . timing . rangeEnd ,
432
456
} ,
433
457
} ,
434
458
true
@@ -442,53 +466,51 @@ export const AnimationPanelContent = ({
442
466
...value . timing ,
443
467
rangeStart : [
444
468
timelineRangeName ,
445
- value . timing . rangeStart ?. [ 1 ] ?? {
446
- type : "unit" ,
447
- value : 0 ,
448
- unit : "%" ,
449
- } ,
469
+ value . timing . rangeStart ?. [ 1 ] ?? defaultRangeStart ,
450
470
] ,
471
+ rangeEnd : isLinked
472
+ ? [
473
+ timelineRangeName ,
474
+ value . timing . rangeEnd ?. [ 1 ] ?? defaultRangeEnd ,
475
+ ]
476
+ : value . timing . rangeEnd ,
451
477
} ,
452
478
} ,
453
479
false
454
480
) ;
455
481
} }
456
482
/>
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 >
492
514
< Select
493
515
id = { fieldIds . rangeEndName }
494
516
options = { timelineRangeNames }
@@ -519,12 +541,14 @@ export const AnimationPanelContent = ({
519
541
...value . timing ,
520
542
rangeEnd : [
521
543
timelineRangeName ,
522
- value . timing . rangeEnd ?. [ 1 ] ?? {
523
- type : "unit" ,
524
- value : 0 ,
525
- unit : "%" ,
526
- } ,
544
+ value . timing . rangeEnd ?. [ 1 ] ?? defaultRangeEnd ,
527
545
] ,
546
+ rangeStart : isLinked
547
+ ? [
548
+ timelineRangeName ,
549
+ value . timing . rangeStart ?. [ 1 ] ?? defaultRangeStart ,
550
+ ]
551
+ : value . timing . rangeStart ,
528
552
} ,
529
553
} ,
530
554
true
@@ -538,18 +562,54 @@ export const AnimationPanelContent = ({
538
562
...value . timing ,
539
563
rangeEnd : [
540
564
timelineRangeName ,
541
- value . timing . rangeEnd ?. [ 1 ] ?? {
542
- type : "unit" ,
543
- value : 0 ,
544
- unit : "%" ,
545
- } ,
565
+ value . timing . rangeEnd ?. [ 1 ] ?? defaultRangeEnd ,
546
566
] ,
567
+ rangeStart : isLinked
568
+ ? [
569
+ timelineRangeName ,
570
+ value . timing . rangeStart ?. [ 1 ] ?? defaultRangeStart ,
571
+ ]
572
+ : value . timing . rangeStart ,
547
573
} ,
548
574
} ,
549
575
false
550
576
) ;
551
577
} }
552
578
/>
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 />
553
613
< RangeValueInput
554
614
id = { fieldIds . rangeEndValue }
555
615
value = {
0 commit comments