Skip to content

Commit 0c7e79c

Browse files
Fix resize animations in Dialog (#11643)
This PR fixes the Resize animations in Dialog component: 1. Removes resize for initial mount / fullscreen dialogs 2. Fixes measuring the content size 3. Fixes bugs in `useMeasure` hook 4. Adds memoization for Text and Loader components (because of react-compiler and because this components accept only primitive values)
1 parent b5f1106 commit 0c7e79c

18 files changed

+786
-368
lines changed

app/common/src/text/english.json

+2
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,8 @@
477477
"cannotCreateAssetsHere": "You do not have the permissions to create assets here.",
478478
"enableVersionChecker": "Enable Version Checker",
479479
"enableVersionCheckerDescription": "Show a dialog if the current version of the desktop app does not match the latest version.",
480+
"disableAnimations": "Disable animations",
481+
"disableAnimationsDescription": "Disable all animations in the app.",
480482
"removeTheLocalDirectoryXFromFavorites": "remove the local folder '$0' from your favorites",
481483
"changeLocalRootDirectoryInSettings": "Change your root folder in Settings.",
482484
"localStorage": "Local Storage",

app/gui/.storybook/preview.tsx

+16-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import type { Preview as ReactPreview } from '@storybook/react'
55
import type { Preview as VuePreview } from '@storybook/vue3'
66
import isChromatic from 'chromatic/isChromatic'
7-
import { useLayoutEffect, useState } from 'react'
7+
import { StrictMode, useLayoutEffect, useState } from 'react'
88

99
import invariant from 'tiny-invariant'
1010
import UIProviders from '../src/dashboard/components/UIProviders'
@@ -59,22 +59,34 @@ const reactPreview: ReactPreview = {
5959

6060
return (
6161
<UIProviders locale="en-US" portalRoot={portalRoot}>
62-
{Story(context)}
62+
<Story {...context} />
6363
</UIProviders>
6464
)
6565
},
6666

6767
(Story, context) => (
6868
<>
69-
<div className="enso-dashboard">{Story(context)}</div>
69+
<div className="enso-dashboard">
70+
<Story {...context} />
71+
</div>
7072
<div id="enso-portal-root" className="enso-portal-root" />
7173
</>
7274
),
7375

7476
(Story, context) => {
7577
const [queryClient] = useState(() => createQueryClient())
76-
return <QueryClientProvider client={queryClient}>{Story(context)}</QueryClientProvider>
78+
return (
79+
<QueryClientProvider client={queryClient}>
80+
<Story {...context} />
81+
</QueryClientProvider>
82+
)
7783
},
84+
85+
(Story, context) => (
86+
<StrictMode>
87+
<Story {...context} />
88+
</StrictMode>
89+
),
7890
],
7991
}
8092

app/gui/src/dashboard/App.tsx

+34-48
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ import ModalProvider, * as modalProvider from '#/providers/ModalProvider'
5959
import * as navigator2DProvider from '#/providers/Navigator2DProvider'
6060
import SessionProvider from '#/providers/SessionProvider'
6161
import * as textProvider from '#/providers/TextProvider'
62-
import type { Spring } from 'framer-motion'
63-
import { MotionConfig } from 'framer-motion'
6462

6563
import ConfirmRegistration from '#/pages/authentication/ConfirmRegistration'
6664
import ForgotPassword from '#/pages/authentication/ForgotPassword'
@@ -105,16 +103,6 @@ import { InvitedToOrganizationModal } from '#/modals/InvitedToOrganizationModal'
105103
// === Global configuration ===
106104
// ============================
107105

108-
const DEFAULT_TRANSITION_OPTIONS: Spring = {
109-
type: 'spring',
110-
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
111-
stiffness: 200,
112-
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
113-
damping: 30,
114-
mass: 1,
115-
velocity: 0,
116-
}
117-
118106
declare module '#/utilities/LocalStorage' {
119107
/** */
120108
interface LocalStorageData {
@@ -532,42 +520,40 @@ function AppRouter(props: AppRouterProps) {
532520

533521
return (
534522
<FeatureFlagsProvider>
535-
<MotionConfig reducedMotion="user" transition={DEFAULT_TRANSITION_OPTIONS}>
536-
<RouterProvider navigate={navigate}>
537-
<SessionProvider
538-
saveAccessToken={authService.cognito.saveAccessToken.bind(authService.cognito)}
539-
mainPageUrl={mainPageUrl}
540-
userSession={userSession}
541-
registerAuthEventListener={registerAuthEventListener}
542-
refreshUserSession={refreshUserSession}
543-
>
544-
<BackendProvider remoteBackend={remoteBackend} localBackend={localBackend}>
545-
<AuthProvider
546-
shouldStartInOfflineMode={isAuthenticationDisabled}
547-
authService={authService}
548-
onAuthenticated={onAuthenticated}
549-
>
550-
<InputBindingsProvider inputBindings={inputBindings}>
551-
{/* Ideally this would be in `Drive.tsx`, but it currently must be all the way out here
552-
* due to modals being in `TheModal`. */}
553-
<DriveProvider>
554-
<errorBoundary.ErrorBoundary>
555-
<LocalBackendPathSynchronizer />
556-
<VersionChecker />
557-
{routes}
558-
<suspense.Suspense>
559-
<errorBoundary.ErrorBoundary>
560-
<devtools.EnsoDevtools />
561-
</errorBoundary.ErrorBoundary>
562-
</suspense.Suspense>
563-
</errorBoundary.ErrorBoundary>
564-
</DriveProvider>
565-
</InputBindingsProvider>
566-
</AuthProvider>
567-
</BackendProvider>
568-
</SessionProvider>
569-
</RouterProvider>
570-
</MotionConfig>
523+
<RouterProvider navigate={navigate}>
524+
<SessionProvider
525+
saveAccessToken={authService.cognito.saveAccessToken.bind(authService.cognito)}
526+
mainPageUrl={mainPageUrl}
527+
userSession={userSession}
528+
registerAuthEventListener={registerAuthEventListener}
529+
refreshUserSession={refreshUserSession}
530+
>
531+
<BackendProvider remoteBackend={remoteBackend} localBackend={localBackend}>
532+
<AuthProvider
533+
shouldStartInOfflineMode={isAuthenticationDisabled}
534+
authService={authService}
535+
onAuthenticated={onAuthenticated}
536+
>
537+
<InputBindingsProvider inputBindings={inputBindings}>
538+
{/* Ideally this would be in `Drive.tsx`, but it currently must be all the way out here
539+
* due to modals being in `TheModal`. */}
540+
<DriveProvider>
541+
<errorBoundary.ErrorBoundary>
542+
<LocalBackendPathSynchronizer />
543+
<VersionChecker />
544+
{routes}
545+
<suspense.Suspense>
546+
<errorBoundary.ErrorBoundary>
547+
<devtools.EnsoDevtools />
548+
</errorBoundary.ErrorBoundary>
549+
</suspense.Suspense>
550+
</errorBoundary.ErrorBoundary>
551+
</DriveProvider>
552+
</InputBindingsProvider>
553+
</AuthProvider>
554+
</BackendProvider>
555+
</SessionProvider>
556+
</RouterProvider>
571557
</FeatureFlagsProvider>
572558
)
573559
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
import { useSuspenseQuery } from '@tanstack/react-query'
3+
import { useLayoutEffect, useRef } from 'react'
4+
import { DialogTrigger } from 'react-aria-components'
5+
import { Button } from '../Button'
6+
import { Dialog, type DialogProps } from './Dialog'
7+
8+
type Story = StoryObj<DialogProps>
9+
10+
export default {
11+
title: 'AriaComponents/Dialog',
12+
component: Dialog,
13+
render: (args) => (
14+
<DialogTrigger defaultOpen>
15+
<Button>Open Dialog</Button>
16+
17+
<Dialog {...args} />
18+
</DialogTrigger>
19+
),
20+
args: {
21+
type: 'modal',
22+
title: 'Dialog Title',
23+
children: 'Dialog Content',
24+
},
25+
} as Meta<DialogProps>
26+
27+
export const Default = {}
28+
29+
// Use a random query key to avoid caching
30+
const QUERY_KEY = Math.random().toString()
31+
32+
function SuspenseContent({ delay = 10_000 }: { delay?: number }): React.ReactNode {
33+
useSuspenseQuery({
34+
queryKey: [QUERY_KEY],
35+
gcTime: 0,
36+
initialDataUpdatedAt: 0,
37+
queryFn: () =>
38+
new Promise((resolve) => {
39+
setTimeout(() => {
40+
resolve('resolved')
41+
}, delay)
42+
}),
43+
})
44+
45+
return (
46+
<div className="flex h-[250px] flex-col items-center justify-center text-center">
47+
Unsuspended content
48+
</div>
49+
)
50+
}
51+
52+
export const Suspened = {
53+
args: {
54+
children: <SuspenseContent delay={10_000_000_000} />,
55+
},
56+
}
57+
58+
function BrokenContent(): React.ReactNode {
59+
throw new Error('💣')
60+
}
61+
62+
export const Broken = {
63+
args: {
64+
children: <BrokenContent />,
65+
},
66+
}
67+
68+
function ResizableContent() {
69+
const divRef = useRef<HTMLDivElement>(null)
70+
71+
useLayoutEffect(() => {
72+
const getRandomHeight = () => Math.floor(Math.random() * 250 + 100)
73+
74+
if (divRef.current) {
75+
divRef.current.style.height = `${getRandomHeight()}px`
76+
77+
setInterval(() => {
78+
if (divRef.current) {
79+
divRef.current.style.height = `${getRandomHeight()}px`
80+
}
81+
}, 2_000)
82+
}
83+
}, [])
84+
85+
return (
86+
<div ref={divRef} className="flex flex-none items-center justify-center text-center">
87+
This dialog should resize with animation
88+
</div>
89+
)
90+
}
91+
92+
export const AnimateSize: Story = {
93+
args: {
94+
children: <ResizableContent />,
95+
},
96+
parameters: {
97+
chromatic: { disableSnapshot: true },
98+
},
99+
}
100+
101+
export const Fullscreen = {
102+
args: {
103+
type: 'fullscreen',
104+
},
105+
}

0 commit comments

Comments
 (0)