@@ -138,6 +138,8 @@ function AvatarStepActions({
138138 onSubmit : ( ) => void ;
139139 saveRecovery : ProfileStepState [ "saveRecovery" ] ;
140140} ) {
141+ const areNavigationActionsDisabled = isSaving || isUploadingAvatar ;
142+
141143 return (
142144 < AnimatePresence initial = { false } mode = "popLayout" >
143145 { hidden ? null : (
@@ -178,7 +180,7 @@ function AvatarStepActions({
178180 < Button
179181 className = "h-10 w-full text-muted-foreground hover:text-accent-foreground max-lg:pointer-events-auto"
180182 data-testid = "onboarding-skip"
181- disabled = { isSaving }
183+ disabled = { areNavigationActionsDisabled }
182184 onClick = { onSkipForNow }
183185 type = "button"
184186 variant = "ghost"
@@ -191,7 +193,7 @@ function AvatarStepActions({
191193 < Button
192194 className = "h-10 w-full text-muted-foreground hover:text-accent-foreground max-lg:pointer-events-auto"
193195 data-testid = "onboarding-next-without-saving"
194- disabled = { isSaving }
196+ disabled = { areNavigationActionsDisabled }
195197 onClick = { onContinueWithoutSaving }
196198 type = "button"
197199 variant = "ghost"
@@ -203,7 +205,7 @@ function AvatarStepActions({
203205 < Button
204206 className = "h-10 w-full text-muted-foreground hover:text-accent-foreground max-lg:pointer-events-auto"
205207 data-testid = "onboarding-back"
206- disabled = { isSaving }
208+ disabled = { areNavigationActionsDisabled }
207209 onClick = { onBack }
208210 type = "button"
209211 variant = "ghost"
@@ -237,15 +239,58 @@ export function AvatarStep({ actions, direction, state }: AvatarStepProps) {
237239 const [ avatarSquishKey , setAvatarSquishKey ] = React . useState ( 0 ) ;
238240 const [ avatarEditorMode , setAvatarEditorMode ] =
239241 React . useState < AvatarMode > ( "image" ) ;
242+ const [ animatedPreviewEl , setAnimatedPreviewEl ] =
243+ React . useState < HTMLDivElement | null > ( null ) ;
244+ const [ isAnimatedPreviewActive , setIsAnimatedPreviewActive ] =
245+ React . useState ( false ) ;
246+ const [ animatedPreviewCaption , setAnimatedPreviewCaption ] = React . useState <
247+ string | null
248+ > ( null ) ;
249+ const [ pendingAnimatedAvatarUrl , setPendingAnimatedAvatarUrl ] =
250+ React . useState < string | null > ( null ) ;
240251 const [ isCustomColorPickerOpen , setIsCustomColorPickerOpen ] =
241252 React . useState ( false ) ;
242- const canSubmit =
243- avatar . draftUrl . trim ( ) . length > 0 && ! isSaving && ! isUploadingAvatar ;
253+ const hasAvatarDraft = avatar . draftUrl . trim ( ) . length > 0 ;
254+ const canSubmit = hasAvatarDraft && ! isSaving && ! isUploadingAvatar ;
255+ const isAutoAdvancingAnimatedAvatar = pendingAnimatedAvatarUrl !== null ;
256+ const shouldHideActionsForAnimatedAvatar =
257+ avatarEditorMode === "animated" &&
258+ ( ! hasAvatarDraft || isAutoAdvancingAnimatedAvatar ) ;
259+ const areActionsHidden =
260+ isCustomColorPickerOpen || shouldHideActionsForAnimatedAvatar ;
244261 const previewName =
245262 name . draftValue . trim ( ) || name . savedValue . trim ( ) || "Your avatar" ;
246263 const animateEmojiAvatarChange = React . useCallback ( ( ) => {
247264 setAvatarSquishKey ( ( key ) => key + 1 ) ;
248265 } , [ ] ) ;
266+ const handleAnimatedAvatarApply = React . useCallback ( ( url : string ) => {
267+ setPendingAnimatedAvatarUrl ( url ) ;
268+ } , [ ] ) ;
269+
270+ React . useEffect ( ( ) => {
271+ if ( ! pendingAnimatedAvatarUrl ) {
272+ return ;
273+ }
274+ if ( avatar . draftUrl !== pendingAnimatedAvatarUrl ) {
275+ return ;
276+ }
277+ if ( saveRecovery . errorMessage ) {
278+ setPendingAnimatedAvatarUrl ( null ) ;
279+ return ;
280+ }
281+ if ( isSaving || isUploadingAvatar ) {
282+ return ;
283+ }
284+
285+ submit ( ) ;
286+ } , [
287+ avatar . draftUrl ,
288+ isSaving ,
289+ isUploadingAvatar ,
290+ pendingAnimatedAvatarUrl ,
291+ saveRecovery . errorMessage ,
292+ submit ,
293+ ] ) ;
249294
250295 return (
251296 < OnboardingSlideTransition
@@ -270,12 +315,35 @@ export function AvatarStep({ actions, direction, state }: AvatarStepProps) {
270315 </ p >
271316 </ div >
272317
273- < div className = "mt-12" >
274- < AvatarPreview
275- avatarSquishKey = { avatarSquishKey }
276- avatarUrl = { avatar . draftUrl }
277- previewName = { previewName }
278- />
318+ < div className = "mt-12 grid justify-items-center gap-3 lg:justify-items-start" >
319+ < div className = "relative h-48 w-48" >
320+ < div
321+ className = "pointer-events-none absolute inset-0 z-10"
322+ data-testid = "onboarding-avatar-animated-preview-slot"
323+ ref = { setAnimatedPreviewEl }
324+ />
325+ { isAnimatedPreviewActive ? null : (
326+ < AvatarPreview
327+ avatarSquishKey = { avatarSquishKey }
328+ avatarUrl = { avatar . draftUrl }
329+ previewName = { previewName }
330+ />
331+ ) }
332+ </ div >
333+
334+ < AnimatePresence initial = { false } >
335+ { animatedPreviewCaption ? (
336+ < motion . p
337+ animate = { { opacity : 1 , y : 0 } }
338+ className = "w-48 text-center text-sm font-medium text-muted-foreground"
339+ exit = { { opacity : 0 , y : - 4 } }
340+ initial = { { opacity : 0 , y : 4 } }
341+ transition = { AVATAR_ACTIONS_MOTION_TRANSITION }
342+ >
343+ { animatedPreviewCaption }
344+ </ motion . p >
345+ ) : null }
346+ </ AnimatePresence >
279347 </ div >
280348 </ div >
281349
@@ -286,10 +354,14 @@ export function AvatarStep({ actions, direction, state }: AvatarStepProps) {
286354 transition = { AVATAR_POSITION_MOTION_TRANSITION }
287355 >
288356 < ProfileAvatarEditor
357+ animatedPreviewContainer = { animatedPreviewEl }
289358 avatarUrl = { avatar . draftUrl }
290359 disabled = { isSaving }
291360 emojiPickerTheme = "auto"
292361 emojiPickerThemeVars = { NEUTRAL_EMOJI_PICKER_THEME_VARS }
362+ onAnimatedAvatarApply = { handleAnimatedAvatarApply }
363+ onAnimatedPreviewActiveChange = { setIsAnimatedPreviewActive }
364+ onAnimatedPreviewCaptionChange = { setAnimatedPreviewCaption }
293365 onCustomColorPickerOpenChange = { setIsCustomColorPickerOpen }
294366 onEmojiAvatarChange = { animateEmojiAvatarChange }
295367 onModeChange = { setAvatarEditorMode }
@@ -305,7 +377,7 @@ export function AvatarStep({ actions, direction, state }: AvatarStepProps) {
305377
306378 < AvatarStepActions
307379 canSubmit = { canSubmit }
308- hidden = { isCustomColorPickerOpen }
380+ hidden = { areActionsHidden }
309381 isSaving = { isSaving }
310382 isUploadingAvatar = { isUploadingAvatar }
311383 onBack = { back }
0 commit comments