work-in-progress atproto PDS
typescript atproto pds atcute

feat: menu and dialog transitions

mary.my.id 841e161f 241ca27f

verified
Changed files
+201
packages
danaus
+2
packages/danaus/src/web/primitives/dialog-surface.tsx
··· 16 16 'sm:items-center', 17 17 // backdrop 18 18 'backdrop:bg-background-overlay', 19 + // entry/exit animations 20 + 'dialog-animate dialog-backdrop-animate', 19 21 ], 20 22 }); 21 23
+2
packages/danaus/src/web/primitives/menu-popover.tsx
··· 8 8 base: [ 9 9 'm-0 box-border w-max max-w-75 min-w-35 overflow-x-hidden rounded-md border border-transparent bg-neutral-background-1 p-1 text-neutral-foreground-1 shadow-16', 10 10 'anchored anchored-bottom-span-left try-flip-y', 11 + // entry/exit animations (slides down from anchor) 12 + 'popover-animate popover-slide-down', 11 13 ], 12 14 }); 13 15
+128
packages/danaus/src/web/styles/main.css
··· 89 89 90 90 --ease-*: initial; 91 91 --ease-fluent: cubic-bezier(0.33, 0, 0.67, 1); 92 + --ease-accelerate-min: cubic-bezier(0.8, 0, 0.78, 1); 93 + --ease-decelerate-mid: cubic-bezier(0, 0, 0, 1); 94 + 95 + --duration-*: initial; 96 + --duration-faster: 100ms; 97 + --duration-fast: 150ms; 98 + --duration-normal: 200ms; 99 + --duration-gentle: 250ms; 100 + --duration-slow: 300ms; 101 + --duration-slower: 400ms; 92 102 93 103 --animate-*: initial; 94 104 --animate-spin-linear: spin-linear 1.5s linear infinite; ··· 422 432 } 423 433 424 434 /* #endregion */ 435 + 436 + /* #region dialog animations */ 437 + 438 + /* 439 + * dialog entry/exit animations using @starting-style 440 + * inspired by FluentUI's Dialog motion: scale + fade with decelerate/accelerate easing 441 + */ 442 + 443 + @utility dialog-animate { 444 + /* transition properties for both dialog surface and backdrop */ 445 + transition-property: opacity, scale, overlay, display; 446 + transition-duration: var(--duration-faster); 447 + transition-timing-function: var(--ease-decelerate-mid); 448 + transition-behavior: allow-discrete; 449 + 450 + /* final open state */ 451 + &[open] { 452 + opacity: 1; 453 + scale: 1; 454 + } 455 + 456 + /* exit state (dialog closing) */ 457 + &:not([open]) { 458 + opacity: 0; 459 + scale: 0.95; 460 + transition-timing-function: var(--ease-accelerate-min); 461 + } 462 + 463 + /* entry starting state */ 464 + @starting-style { 465 + &[open] { 466 + opacity: 0; 467 + scale: 0.95; 468 + } 469 + } 470 + } 471 + 472 + /* backdrop animation (fade only, no scale) */ 473 + @utility dialog-backdrop-animate { 474 + &::backdrop { 475 + transition-property: opacity, overlay, display; 476 + transition-duration: var(--duration-faster); 477 + transition-timing-function: var(--ease-decelerate-mid); 478 + transition-behavior: allow-discrete; 479 + opacity: 1; 480 + } 481 + 482 + &:not([open])::backdrop { 483 + opacity: 0; 484 + transition-timing-function: var(--ease-accelerate-min); 485 + } 486 + 487 + @starting-style { 488 + &[open]::backdrop { 489 + opacity: 0; 490 + } 491 + } 492 + } 493 + 494 + /* #endregion */ 495 + 496 + /* #region popover animations */ 497 + 498 + /* 499 + * popover entry/exit animations using @starting-style 500 + * inspired by FluentUI's Menu motion: slide + fade based on placement 501 + */ 502 + 503 + @utility popover-animate { 504 + --_slide-x: 0; 505 + --_slide-y: 8px; 506 + 507 + transition-property: opacity, translate, overlay, display; 508 + transition-duration: var(--duration-faster); 509 + transition-timing-function: var(--ease-decelerate-mid); 510 + transition-behavior: allow-discrete; 511 + 512 + /* final open state */ 513 + &:popover-open { 514 + opacity: 1; 515 + translate: 0 0; 516 + } 517 + 518 + /* exit state */ 519 + &:not(:popover-open) { 520 + opacity: 0; 521 + translate: var(--_slide-x) var(--_slide-y); 522 + transition-timing-function: var(--ease-accelerate-min); 523 + } 524 + 525 + /* entry starting state */ 526 + @starting-style { 527 + &:popover-open { 528 + opacity: 0; 529 + translate: var(--_slide-x) var(--_slide-y); 530 + } 531 + } 532 + } 533 + 534 + /* slide direction variants based on anchor position */ 535 + @utility popover-slide-down { 536 + --_slide-x: 0; 537 + --_slide-y: -8px; 538 + } 539 + @utility popover-slide-up { 540 + --_slide-x: 0; 541 + --_slide-y: 8px; 542 + } 543 + @utility popover-slide-left { 544 + --_slide-x: 8px; 545 + --_slide-y: 0; 546 + } 547 + @utility popover-slide-right { 548 + --_slide-x: -8px; 549 + --_slide-y: 0; 550 + } 551 + 552 + /* #endregion */
+69
packages/danaus/src/web/styles/main.out.css
··· 81 81 --color-subtle-background-hover: var(--color-subtle-background-hover); 82 82 --color-subtle-background: var(--color-subtle-background); 83 83 --ease-fluent: cubic-bezier(0.33, 0, 0.67, 1); 84 + --ease-accelerate-min: cubic-bezier(0.8, 0, 0.78, 1); 85 + --ease-decelerate-mid: cubic-bezier(0, 0, 0, 1); 86 + --duration-faster: 100ms; 84 87 --shadow-2: var(--shadow-2); 85 88 --shadow-4: var(--shadow-4); 86 89 --shadow-8: var(--shadow-8); ··· 494 497 .basis-0 { 495 498 flex-basis: calc(var(--spacing) * 0); 496 499 } 500 + .popover-animate { 501 + --_slide-x: 0; 502 + --_slide-y: 8px; 503 + transition-property: opacity, translate, overlay, display; 504 + transition-duration: var(--duration-faster); 505 + transition-timing-function: var(--ease-decelerate-mid); 506 + transition-behavior: allow-discrete; 507 + &:popover-open { 508 + opacity: 1; 509 + translate: 0 0; 510 + } 511 + &:not(:popover-open) { 512 + opacity: 0; 513 + translate: var(--_slide-x) var(--_slide-y); 514 + transition-timing-function: var(--ease-accelerate-min); 515 + } 516 + @starting-style { 517 + &:popover-open { 518 + opacity: 0; 519 + translate: var(--_slide-x) var(--_slide-y); 520 + } 521 + } 522 + } 523 + .dialog-animate { 524 + transition-property: opacity, scale, overlay, display; 525 + transition-duration: var(--duration-faster); 526 + transition-timing-function: var(--ease-decelerate-mid); 527 + transition-behavior: allow-discrete; 528 + &[open] { 529 + opacity: 1; 530 + scale: 1; 531 + } 532 + &:not([open]) { 533 + opacity: 0; 534 + scale: 0.95; 535 + transition-timing-function: var(--ease-accelerate-min); 536 + } 537 + @starting-style { 538 + &[open] { 539 + opacity: 0; 540 + scale: 0.95; 541 + } 542 + } 543 + } 497 544 .cursor-pointer { 498 545 cursor: pointer; 499 546 } ··· 866 913 .no-underline { 867 914 text-decoration-line: none; 868 915 } 916 + .dialog-backdrop-animate { 917 + &::backdrop { 918 + transition-property: opacity, overlay, display; 919 + transition-duration: var(--duration-faster); 920 + transition-timing-function: var(--ease-decelerate-mid); 921 + transition-behavior: allow-discrete; 922 + opacity: 1; 923 + } 924 + &:not([open])::backdrop { 925 + opacity: 0; 926 + transition-timing-function: var(--ease-accelerate-min); 927 + } 928 + @starting-style { 929 + &[open]::backdrop { 930 + opacity: 0; 931 + } 932 + } 933 + } 869 934 .opacity-0 { 870 935 opacity: 0%; 871 936 } ··· 907 972 .outline-none { 908 973 --tw-outline-style: none; 909 974 outline-style: none; 975 + } 976 + .popover-slide-down { 977 + --_slide-x: 0; 978 + --_slide-y: -8px; 910 979 } 911 980 .select-none { 912 981 -webkit-user-select: none;