This is an automated email from the ASF dual-hosted git repository. enzomartellucci pushed a commit to branch enxdev/style/custom-tabs in repository https://gitbox.apache.org/repos/asf/superset.git
commit e24a1bfb977979d68b8db8afdf7cb4a71394fcad Author: Enzo Martellucci <[email protected]> AuthorDate: Mon Dec 29 13:33:29 2025 +0100 fix(dashboard): resolve tab reorder state sync issues Fix multiple issues with tab drag-and-drop reordering in dashboard edit mode: - Add key prop to DndContext to force re-initialization when tab order changes, preventing stale state in subsequent drag operations - Use useRef to track current tabIds in callbacks, avoiding stale closure issues - Remove transition during drag for immediate visual feedback - Ensure tab labels maintain full opacity during drag operations - Add activeKey to handleTabsReorder dependencies for proper state sync - Fix LineEditableTabs TypeScript types to properly expose TabsProps --- .../superset-ui-core/src/components/Tabs/Tabs.tsx | 7 +++++-- .../components/gridComponents/Tabs/Tabs.jsx | 2 +- .../gridComponents/TabsRenderer/TabsRenderer.tsx | 23 +++++++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx b/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx index 291b16efea..090d25da30 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import type { FC } from 'react'; import { css, styled, useTheme } from '@apache-superset/core/ui'; // eslint-disable-next-line no-restricted-imports @@ -161,8 +162,10 @@ export const StyledLineEditableTabs = styled(EditableTabs)` } `; -export const LineEditableTabs = Object.assign(StyledLineEditableTabs, { +export const LineEditableTabs: FC<TabsProps> & { + TabPane: typeof StyledTabPane; +} = Object.assign(StyledLineEditableTabs, { TabPane: StyledTabPane, -}); +}) as FC<TabsProps> & { TabPane: typeof StyledTabPane }; export default Tabs; diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs/Tabs.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs/Tabs.jsx index b23f1da567..6beb76db72 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tabs/Tabs.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs/Tabs.jsx @@ -362,7 +362,7 @@ const Tabs = props => { setActiveKey(currentActiveTabId); } }, - [props.component, props.updateComponents, selectedTabIndex], + [props.component, props.updateComponents, selectedTabIndex, activeKey], ); const { diff --git a/superset-frontend/src/dashboard/components/gridComponents/TabsRenderer/TabsRenderer.tsx b/superset-frontend/src/dashboard/components/gridComponents/TabsRenderer/TabsRenderer.tsx index 445ddf1db3..bb92c016ad 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/TabsRenderer/TabsRenderer.tsx +++ b/superset-frontend/src/dashboard/components/gridComponents/TabsRenderer/TabsRenderer.tsx @@ -22,6 +22,7 @@ import { ReactElement, RefObject, useCallback, + useRef, useState, } from 'react'; import { styled } from '@apache-superset/core/ui'; @@ -61,6 +62,16 @@ const StyledTabsContainer = styled.div<{ isDragging?: boolean }>` height: calc(100% - 47px); } + /* Ensure tab labels maintain full opacity during drag */ + .ant-tabs-tab { + .dragdroppable-tab, + .editable-title, + textarea { + opacity: 1; + color: inherit; + } + } + /* Hide ink-bar during drag */ ${({ isDragging }) => isDragging && @@ -126,7 +137,7 @@ const DraggableTabNode: React.FC<Readonly<DraggableTabNodeProps>> = ({ ...props.style, position: 'relative', transform: transform ? `translate3d(${transform.x}px, 0, 0)` : undefined, - transition, + transition: isDragging ? 'none' : transition, cursor: disabled ? 'default' : 'move', zIndex: isDragging ? 1000 : 'auto', opacity: 1, @@ -163,6 +174,10 @@ const TabsRenderer = memo<TabsRendererProps>( }) => { const [activeId, setActiveId] = useState<string | null>(null); + // Use ref to always have access to the current tabIds in callbacks + const tabIdsRef = useRef(tabIds); + tabIdsRef.current = tabIds; + const sensor = useSensor(PointerSensor, { activationConstraint: { distance: 10 }, }); @@ -173,9 +188,10 @@ const TabsRenderer = memo<TabsRendererProps>( const onDragEnd = useCallback( ({ active, over }: DragEndEvent) => { + const currentTabIds = tabIdsRef.current; if (active.id !== over?.id && onTabsReorder) { - const activeIndex = tabIds.findIndex(id => id === active.id); - const overIndex = tabIds.findIndex(id => id === over?.id); + const activeIndex = currentTabIds.findIndex(id => id === active.id); + const overIndex = currentTabIds.findIndex(id => id === over?.id); onTabsReorder(activeIndex, overIndex); } setActiveId(null); @@ -220,6 +236,7 @@ const TabsRenderer = memo<TabsRendererProps>( {...(editMode && { renderTabBar: (tabBarProps, DefaultTabBar) => ( <DndContext + key={tabIds.join('-')} sensors={[sensor]} onDragStart={onDragStart} onDragEnd={onDragEnd}
