Skip to content

Commit

Permalink
Fix resize animations in Dialog (#11643)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
MrFlashAccount authored Nov 28, 2024
1 parent b5f1106 commit 0c7e79c
Show file tree
Hide file tree
Showing 18 changed files with 786 additions and 368 deletions.
2 changes: 2 additions & 0 deletions app/common/src/text/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@
"cannotCreateAssetsHere": "You do not have the permissions to create assets here.",
"enableVersionChecker": "Enable Version Checker",
"enableVersionCheckerDescription": "Show a dialog if the current version of the desktop app does not match the latest version.",
"disableAnimations": "Disable animations",
"disableAnimationsDescription": "Disable all animations in the app.",
"removeTheLocalDirectoryXFromFavorites": "remove the local folder '$0' from your favorites",
"changeLocalRootDirectoryInSettings": "Change your root folder in Settings.",
"localStorage": "Local Storage",
Expand Down
20 changes: 16 additions & 4 deletions app/gui/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { Preview as ReactPreview } from '@storybook/react'
import type { Preview as VuePreview } from '@storybook/vue3'
import isChromatic from 'chromatic/isChromatic'
import { useLayoutEffect, useState } from 'react'
import { StrictMode, useLayoutEffect, useState } from 'react'

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

return (
<UIProviders locale="en-US" portalRoot={portalRoot}>
{Story(context)}
<Story {...context} />
</UIProviders>
)
},

(Story, context) => (
<>
<div className="enso-dashboard">{Story(context)}</div>
<div className="enso-dashboard">
<Story {...context} />
</div>
<div id="enso-portal-root" className="enso-portal-root" />
</>
),

(Story, context) => {
const [queryClient] = useState(() => createQueryClient())
return <QueryClientProvider client={queryClient}>{Story(context)}</QueryClientProvider>
return (
<QueryClientProvider client={queryClient}>
<Story {...context} />
</QueryClientProvider>
)
},

(Story, context) => (
<StrictMode>
<Story {...context} />
</StrictMode>
),
],
}

Expand Down
82 changes: 34 additions & 48 deletions app/gui/src/dashboard/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ import ModalProvider, * as modalProvider from '#/providers/ModalProvider'
import * as navigator2DProvider from '#/providers/Navigator2DProvider'
import SessionProvider from '#/providers/SessionProvider'
import * as textProvider from '#/providers/TextProvider'
import type { Spring } from 'framer-motion'
import { MotionConfig } from 'framer-motion'

import ConfirmRegistration from '#/pages/authentication/ConfirmRegistration'
import ForgotPassword from '#/pages/authentication/ForgotPassword'
Expand Down Expand Up @@ -105,16 +103,6 @@ import { InvitedToOrganizationModal } from '#/modals/InvitedToOrganizationModal'
// === Global configuration ===
// ============================

const DEFAULT_TRANSITION_OPTIONS: Spring = {
type: 'spring',
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
stiffness: 200,
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
damping: 30,
mass: 1,
velocity: 0,
}

declare module '#/utilities/LocalStorage' {
/** */
interface LocalStorageData {
Expand Down Expand Up @@ -532,42 +520,40 @@ function AppRouter(props: AppRouterProps) {

return (
<FeatureFlagsProvider>
<MotionConfig reducedMotion="user" transition={DEFAULT_TRANSITION_OPTIONS}>
<RouterProvider navigate={navigate}>
<SessionProvider
saveAccessToken={authService.cognito.saveAccessToken.bind(authService.cognito)}
mainPageUrl={mainPageUrl}
userSession={userSession}
registerAuthEventListener={registerAuthEventListener}
refreshUserSession={refreshUserSession}
>
<BackendProvider remoteBackend={remoteBackend} localBackend={localBackend}>
<AuthProvider
shouldStartInOfflineMode={isAuthenticationDisabled}
authService={authService}
onAuthenticated={onAuthenticated}
>
<InputBindingsProvider inputBindings={inputBindings}>
{/* Ideally this would be in `Drive.tsx`, but it currently must be all the way out here
* due to modals being in `TheModal`. */}
<DriveProvider>
<errorBoundary.ErrorBoundary>
<LocalBackendPathSynchronizer />
<VersionChecker />
{routes}
<suspense.Suspense>
<errorBoundary.ErrorBoundary>
<devtools.EnsoDevtools />
</errorBoundary.ErrorBoundary>
</suspense.Suspense>
</errorBoundary.ErrorBoundary>
</DriveProvider>
</InputBindingsProvider>
</AuthProvider>
</BackendProvider>
</SessionProvider>
</RouterProvider>
</MotionConfig>
<RouterProvider navigate={navigate}>
<SessionProvider
saveAccessToken={authService.cognito.saveAccessToken.bind(authService.cognito)}
mainPageUrl={mainPageUrl}
userSession={userSession}
registerAuthEventListener={registerAuthEventListener}
refreshUserSession={refreshUserSession}
>
<BackendProvider remoteBackend={remoteBackend} localBackend={localBackend}>
<AuthProvider
shouldStartInOfflineMode={isAuthenticationDisabled}
authService={authService}
onAuthenticated={onAuthenticated}
>
<InputBindingsProvider inputBindings={inputBindings}>
{/* Ideally this would be in `Drive.tsx`, but it currently must be all the way out here
* due to modals being in `TheModal`. */}
<DriveProvider>
<errorBoundary.ErrorBoundary>
<LocalBackendPathSynchronizer />
<VersionChecker />
{routes}
<suspense.Suspense>
<errorBoundary.ErrorBoundary>
<devtools.EnsoDevtools />
</errorBoundary.ErrorBoundary>
</suspense.Suspense>
</errorBoundary.ErrorBoundary>
</DriveProvider>
</InputBindingsProvider>
</AuthProvider>
</BackendProvider>
</SessionProvider>
</RouterProvider>
</FeatureFlagsProvider>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type { Meta, StoryObj } from '@storybook/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useLayoutEffect, useRef } from 'react'
import { DialogTrigger } from 'react-aria-components'
import { Button } from '../Button'
import { Dialog, type DialogProps } from './Dialog'

type Story = StoryObj<DialogProps>

export default {
title: 'AriaComponents/Dialog',
component: Dialog,
render: (args) => (
<DialogTrigger defaultOpen>
<Button>Open Dialog</Button>

<Dialog {...args} />
</DialogTrigger>
),
args: {
type: 'modal',
title: 'Dialog Title',
children: 'Dialog Content',
},
} as Meta<DialogProps>

export const Default = {}

// Use a random query key to avoid caching
const QUERY_KEY = Math.random().toString()

function SuspenseContent({ delay = 10_000 }: { delay?: number }): React.ReactNode {
useSuspenseQuery({
queryKey: [QUERY_KEY],
gcTime: 0,
initialDataUpdatedAt: 0,
queryFn: () =>
new Promise((resolve) => {
setTimeout(() => {
resolve('resolved')
}, delay)
}),
})

return (
<div className="flex h-[250px] flex-col items-center justify-center text-center">
Unsuspended content
</div>
)
}

export const Suspened = {
args: {
children: <SuspenseContent delay={10_000_000_000} />,
},
}

function BrokenContent(): React.ReactNode {
throw new Error('💣')
}

export const Broken = {
args: {
children: <BrokenContent />,
},
}

function ResizableContent() {
const divRef = useRef<HTMLDivElement>(null)

useLayoutEffect(() => {
const getRandomHeight = () => Math.floor(Math.random() * 250 + 100)

if (divRef.current) {
divRef.current.style.height = `${getRandomHeight()}px`

setInterval(() => {
if (divRef.current) {
divRef.current.style.height = `${getRandomHeight()}px`
}
}, 2_000)
}
}, [])

return (
<div ref={divRef} className="flex flex-none items-center justify-center text-center">
This dialog should resize with animation
</div>
)
}

export const AnimateSize: Story = {
args: {
children: <ResizableContent />,
},
parameters: {
chromatic: { disableSnapshot: true },
},
}

export const Fullscreen = {
args: {
type: 'fullscreen',
},
}
Loading

0 comments on commit 0c7e79c

Please sign in to comment.