Add smooth page transitions to onboarding v2 (#22392)

## Before


https://github.com/user-attachments/assets/d2fcd5ce-7e34-4f07-9a52-cac8acdc37cd

## After


https://github.com/user-attachments/assets/5c245949-cff9-41b2-802d-3deeb562efa6


On a full-page load of a v2 onboarding URL (the post-signup
workspace-subdomain redirect), Lingui's `I18nProvider` renders `null`
until the locale chunk async-activates, so the app is blank for ~2s
before the verify step appears. Steps also hard-cut and flashed a loader
between each other.

- Show a pulsing-logo loader until the locale activates (a gate above
`I18nProvider`), scoped to onboarding v2 paths so every other page is
unchanged.
- Cross-fade between steps and preload their chunks on entry, so
navigating never flashes the loader.

Frontend-only; i18n loading itself is untouched.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22392?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-07-01 14:50:13 +02:00
committed by GitHub
parent 2b1417770e
commit 4d96ec489b
7 changed files with 260 additions and 80 deletions
@@ -0,0 +1,19 @@
import { OnboardingPulsingLogo } from '@/onboarding/components/OnboardingPulsingLogo';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledContainer = styled.div`
align-items: center;
background: ${themeCssVariables.background.primary};
display: flex;
flex-direction: column;
height: 100dvh;
justify-content: center;
width: 100%;
`;
export const OnboardingPageLoader = () => (
<StyledContainer>
<OnboardingPulsingLogo />
</StyledContainer>
);
@@ -0,0 +1,55 @@
import { styled } from '@linaria/react';
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';
import { useContext } from 'react';
import { useLocation, useOutlet } from 'react-router-dom';
import { ThemeContext } from 'twenty-ui/theme-constants';
const StyledTransitionContainer = styled.div`
display: flex;
flex: 1;
min-height: 0;
min-width: 0;
position: relative;
`;
const StyledTransitionPage = styled(motion.div)`
display: flex;
inset: 0;
min-height: 0;
min-width: 0;
position: absolute;
`;
export const OnboardingV2TransitionOutlet = () => {
const { pathname } = useLocation();
const outlet = useOutlet();
const shouldReduceMotion = useReducedMotion();
const { theme } = useContext(ThemeContext);
return (
<StyledTransitionContainer>
<AnimatePresence initial={false}>
<StyledTransitionPage
key={pathname}
initial={{ opacity: 0, y: theme.spacingMultiplicator }}
animate={{ opacity: 1, y: 0 }}
exit={{
opacity: 0,
y: -theme.spacingMultiplicator,
pointerEvents: 'none',
}}
transition={
shouldReduceMotion
? { duration: 0 }
: {
duration: theme.animation.duration.normal,
ease: 'easeInOut',
}
}
>
{outlet}
</StyledTransitionPage>
</AnimatePresence>
</StyledTransitionContainer>
);
};