Skip to content

Add animations on widget buttons and on action buttons#15631

Merged
bosiraphael merged 8 commits intomainfrom
r--add-animations-on-widgets-buttons
Nov 6, 2025
Merged

Add animations on widget buttons and on action buttons#15631
bosiraphael merged 8 commits intomainfrom
r--add-animations-on-widgets-buttons

Conversation

@bosiraphael
Copy link
Copy Markdown
Contributor

Animated:

  • Grip
  • Trash can
  • Action buttons
CleanShot.2025-11-05.at.10.37.36.mp4

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

adds framer-motion animations to widget UI components (grip, trash button) and page header action buttons

Key changes:

  • widget grip and trash button now animate in/out on hover with width and opacity transitions
  • action menu buttons in page header animate when appearing
  • hover state moved from CSS to React state management for better animation control

Issue found:

  • PageHeaderActionMenuButtons.tsx:24 uses incorrect key calculation (pinnedActions.length - index - 1) instead of action.key, which breaks React reconciliation and will cause animation glitches when actions are added/removed

Confidence Score: 2/5

  • not safe to merge due to critical key prop bug that breaks React reconciliation
  • the incorrect key calculation in PageHeaderActionMenuButtons will cause React to incorrectly reconcile components when actions are added/removed, leading to animation glitches and potential state bugs. widget animations are implemented correctly
  • packages/twenty-front/src/modules/action-menu/components/PageHeaderActionMenuButtons.tsx requires immediate fix for key prop

Important Files Changed

File Analysis

Filename Score Overview
packages/twenty-front/src/modules/action-menu/components/PageHeaderActionMenuButtons.tsx 1/5 adds framer-motion animations to action buttons but uses incorrect key prop that breaks React reconciliation
packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx 5/5 adds hover state management and passes hover events to WidgetCard for animation triggers
packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx 5/5 adds framer-motion animations for grip and trash button with hover-based visibility

Sequence Diagram

sequenceDiagram
    participant User
    participant WidgetCard
    participant WidgetRenderer
    participant WidgetCardHeader
    participant WidgetGrip
    participant TrashButton
    
    User->>WidgetCard: hover over widget
    WidgetCard->>WidgetRenderer: onMouseEnter()
    WidgetRenderer->>WidgetRenderer: setIsHovered(true)
    WidgetRenderer->>WidgetCardHeader: isWidgetCardHovered=true
    
    alt isInEditMode && !isEmpty
        WidgetCardHeader->>WidgetGrip: render with AnimatePresence
        WidgetGrip->>WidgetGrip: animate (width: 0→20, opacity: 0→1)
        
        alt onRemove exists && isWidgetCardHovered
            WidgetCardHeader->>TrashButton: render with AnimatePresence
            TrashButton->>TrashButton: animate (width: 0→auto, opacity: 0→1)
        end
    end
    
    User->>WidgetCard: move mouse away
    WidgetCard->>WidgetRenderer: onMouseLeave()
    WidgetRenderer->>WidgetRenderer: setIsHovered(false)
    WidgetRenderer->>WidgetCardHeader: isWidgetCardHovered=false
    
    alt isWidgetCardHovered becomes false
        WidgetCardHeader->>TrashButton: exit animation
        TrashButton->>TrashButton: animate (width: auto→0, opacity: 1→0)
    end
    
    Note over User,TrashButton: Action Menu Buttons Animation
    
    participant PageHeader
    participant ActionMenuButtons
    participant ActionContainer
    
    PageHeader->>ActionMenuButtons: render actions
    ActionMenuButtons->>ActionMenuButtons: filter pinnedActions
    
    loop for each pinned action
        ActionMenuButtons->>ActionContainer: render with framer-motion
        ActionContainer->>ActionContainer: animate (width: 0→unset, opacity: 0→1)
    end
Loading

8 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<>
{pinnedActions.map((action, index) => (
<StyledActionContainer
key={pinnedActions.length - index - 1}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: incorrect key calculation breaks React reconciliation and animations. should use action.key (as before) instead of reversing the index

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/twenty-front/src/modules/action-menu/components/PageHeaderActionMenuButtons.tsx
Line: 24:24

Comment:
**logic:** incorrect key calculation breaks React reconciliation and animations. should use action.key (as before) instead of reversing the index

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's made to animate the button with the same indexes into each other. We can't have that if we change the key.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucasbordeau I would like your take on this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use the action.id or action.key instead ? Using index is a bad practice indeed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right that using index-based keys is a bad practice. Looking at the code, I can see that action.key should be available based on the RAG context which shows it was used before.

The current implementation with pinnedActions.length - index - 1 is indeed problematic because:

  1. It breaks React's reconciliation algorithm
  2. Can cause animation glitches when items are added/removed
  3. Makes debugging harder

Here's the fix:

Suggested change
key={pinnedActions.length - index - 1}
key={action.key}

If action.key isn't available for some reason, action.id would be the next best option. Both would provide stable, unique identifiers that don't change when the array order changes, ensuring proper React reconciliation and smooth animations.

The animation behavior you want (buttons animating into each other's positions) will actually work better with stable keys because React will properly track which elements are moving rather than constantly recreating them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This key props should be used internally by React for its internal render engine, I don't think that it should impact the feature.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that React keys are internal to React's rendering engine, but they actually have a significant impact on animations and component behavior in this specific case.

The issue is that when you use pinnedActions.length - index - 1 as the key, React thinks components are being destroyed and recreated rather than moved when the array changes. This breaks Framer Motion's layout animations because:

  1. Layout animations rely on component identity - Framer Motion tracks components across renders using their React keys to create smooth position transitions
  2. Index-based keys cause identity confusion - When items are added/removed, the reversed index calculation makes React think different components occupy the same positions
  3. Animation state is lost - New component instances can't inherit animation state from "previous" instances that React thinks are completely different

Using action.key (or action.id) would give each action a stable identity, allowing Framer Motion to properly track which button is which and animate them smoothly between positions.

The key prop isn't just internal bookkeeping here - it's critical for the animation feature to work correctly. Your animation goals will actually work better with stable keys!

Suggested change
key={pinnedActions.length - index - 1}
key={action.key}

Copy link
Copy Markdown
Contributor

@lucasbordeau lucasbordeau Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't be too confident about Greptile's take on this very precise use case, but the general idea is that hacking the key props to obtain the desired result shouldn't be a proper solution because the reference should be stable across renders for React internal engine anyway.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Nov 5, 2025

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:54270

This environment will automatically shut down when the PR is closed or after 5 hours.

Copy link
Copy Markdown
Member

@Weiko Weiko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to address greptile comments otherwise LGTM

@bosiraphael bosiraphael merged commit 931d12c into main Nov 6, 2025
58 checks passed
@bosiraphael bosiraphael deleted the r--add-animations-on-widgets-buttons branch November 6, 2025 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants