Browse Source

feat(issue-views): Improve tab switch animations (#77890)

Makes the tab switch animation not absolute trash by:
1. Keeping text static (by only animating it's change in position and
not it's change in width)
2. Slightly delaying when the ellipsis menu animates in (otherwise, as a
consequence of keeping the text static, the text will appear behind the
ellipsis menu for a moment when switching to that tab)

before: 


https://github.com/user-attachments/assets/c9608738-ea73-4434-ab60-22bbd6d71e94

after:



https://github.com/user-attachments/assets/41d95acb-15bf-4b30-b7cb-799b471f3afa


(these are both in local dev envs, so the prod animations should be a
bit smoother)
Michael Sun 5 months ago
parent
commit
172313975f
1 changed files with 23 additions and 12 deletions
  1. 23 12
      static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx

+ 23 - 12
static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx

@@ -3,6 +3,7 @@ import 'intersection-observer'; // polyfill
 import {useCallback, useContext, useEffect, useState} from 'react';
 import styled from '@emotion/styled';
 import type {Node} from '@react-types/shared';
+import {motion} from 'framer-motion';
 
 import {
   DraggableTabList,
@@ -387,24 +388,34 @@ export function DraggableTabBar({
           disabled={tab.key === editingTabKey}
         >
           <TabContentWrap>
-            <EditableTabTitle
-              label={tab.label}
-              isEditing={editingTabKey === tab.key}
-              setIsEditing={isEditing => setEditingTabKey(isEditing ? tab.key : null)}
-              onChange={newLabel => handleOnTabRenamed(newLabel.trim(), tab.key)}
-              tabKey={tab.key}
-            />
+            <motion.div layout="position" transition={{duration: 0.25}}>
+              <EditableTabTitle
+                label={tab.label}
+                isEditing={editingTabKey === tab.key}
+                setIsEditing={isEditing => setEditingTabKey(isEditing ? tab.key : null)}
+                onChange={newLabel => handleOnTabRenamed(newLabel.trim(), tab.key)}
+                tabKey={tab.key}
+              />
+            </motion.div>
             {/* If tablistState isn't initialized, we want to load the elipsis menu
                 for the initial tab, that way it won't load in a second later
                 and cause the tabs to shift and animate on load.
             */}
             {((tabListState && tabListState?.selectedKey === tab.key) ||
               (!tabListState && tab.key === initialTabKey)) && (
-              <DraggableTabMenuButton
-                hasUnsavedChanges={!!tab.unsavedChanges}
-                menuOptions={makeMenuOptions(tab)}
-                aria-label={`${tab.label} Tab Options`}
-              />
+              <motion.div
+                // This stops the ellipsis menu from animating in on load (when tabListState isn't initialized yet),
+                // but enables the animation later on when switching tabs
+                initial={tabListState ? {opacity: 0} : false}
+                animate={{opacity: 1}}
+                transition={{delay: 0.1}}
+              >
+                <DraggableTabMenuButton
+                  hasUnsavedChanges={!!tab.unsavedChanges}
+                  menuOptions={makeMenuOptions(tab)}
+                  aria-label={`${tab.label} Ellipsis Menu`}
+                />
+              </motion.div>
             )}
           </TabContentWrap>
         </DraggableTabList.Item>