Skip to content

Commit e650b19

Browse files
[WEB-5556] chore: tab navigation and sidebar improvements (#8218)
1 parent ec478a8 commit e650b19

File tree

3 files changed

+40
-16
lines changed

3 files changed

+40
-16
lines changed

apps/web/core/components/navigation/tab-navigation-root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export const TabNavigationRoot: FC<TTabNavigationRootProps> = observer((props) =
115115
const hiddenNavigationItems = allNavigationItems.filter((item) => tabPreferences.hiddenTabs.includes(item.key));
116116

117117
// Responsive tab layout hook
118-
const { visibleItems, overflowItems, hasOverflow, containerRef, itemRefs } = useResponsiveTabLayout({
118+
const { visibleItems, overflowItems, hasOverflow, itemRefs, containerRef } = useResponsiveTabLayout({
119119
visibleNavigationItems,
120120
hiddenNavigationItems,
121121
isActive,

apps/web/core/components/navigation/use-responsive-tab-layout.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { useEffect, useMemo, useRef, useState } from "react";
1+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
22
import type { TNavigationItem } from "./tab-navigation-root";
33

44
export type TResponsiveTabLayout = {
55
visibleItems: TNavigationItem[];
66
overflowItems: TNavigationItem[];
77
hasOverflow: boolean;
8-
containerRef: React.RefObject<HTMLDivElement>;
98
itemRefs: React.MutableRefObject<(HTMLDivElement | null)[]>;
9+
containerRef: (node: HTMLDivElement | null) => void;
1010
};
1111

1212
type UseResponsiveTabLayoutProps = {
@@ -30,9 +30,9 @@ export const useResponsiveTabLayout = ({
3030
hiddenNavigationItems,
3131
isActive,
3232
}: UseResponsiveTabLayoutProps): TResponsiveTabLayout => {
33-
// Refs for measuring space and items
34-
const containerRef = useRef<HTMLDivElement>(null);
33+
// Refs for measuring items
3534
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
35+
const resizeObserverRef = useRef<ResizeObserver | null>(null);
3636

3737
// State for responsive behavior
3838
const [containerWidth, setContainerWidth] = useState<number>(0);
@@ -42,24 +42,44 @@ export const useResponsiveTabLayout = ({
4242
const gap = 4; // gap-1 = 4px
4343
const overflowButtonWidth = 40;
4444

45-
const container = containerRef?.current;
45+
// Callback ref that sets up ResizeObserver when element is attached
46+
const containerRef = useCallback((node: HTMLDivElement | null) => {
47+
// Clean up previous observer if it exists
48+
if (resizeObserverRef.current) {
49+
resizeObserverRef.current.disconnect();
50+
resizeObserverRef.current = null;
51+
}
4652

47-
// ResizeObserver to measure container width
48-
useEffect(() => {
49-
if (!container) return;
53+
// If node is null (unmounting), just clean up
54+
if (!node) {
55+
setContainerWidth(0);
56+
return;
57+
}
5058

59+
// Set initial width immediately
60+
setContainerWidth(node.offsetWidth);
61+
62+
// Create and set up new ResizeObserver
5163
const resizeObserver = new ResizeObserver((entries) => {
5264
for (const entry of entries) {
5365
setContainerWidth(entry.contentRect.width);
5466
}
5567
});
5668

57-
resizeObserver.observe(container);
69+
resizeObserverRef.current = resizeObserver;
70+
resizeObserver.observe(node);
71+
}, []); // Empty deps - callback function remains stable
5872

59-
return () => {
60-
resizeObserver.disconnect();
61-
};
62-
}, [container]);
73+
// Cleanup effect to disconnect observer on component unmount
74+
useEffect(
75+
() => () => {
76+
if (resizeObserverRef.current) {
77+
resizeObserverRef.current.disconnect();
78+
resizeObserverRef.current = null;
79+
}
80+
},
81+
[]
82+
);
6383

6484
// Calculate how many items can fit
6585
useEffect(() => {
@@ -137,7 +157,7 @@ export const useResponsiveTabLayout = ({
137157
visibleItems,
138158
overflowItems,
139159
hasOverflow,
140-
containerRef,
141160
itemRefs,
161+
containerRef,
142162
};
143163
};

apps/web/core/components/workspace/sidebar/projects-list-item.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
6969
const { isMobile } = usePlatformOS();
7070
const { allowPermissions } = useUserPermissions();
7171
const { getIsProjectListOpen, toggleProjectListOpen } = useCommandPalette();
72-
const { toggleAnySidebarDropdown } = useAppTheme();
7372
const { preferences: projectPreferences } = useProjectNavigationPreferences();
73+
const { isExtendedProjectSidebarOpened, toggleExtendedProjectSidebar, toggleAnySidebarDropdown } = useAppTheme();
7474

7575
// states
7676
const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false);
@@ -259,6 +259,10 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
259259
} else {
260260
router.push(defaultTabUrl);
261261
}
262+
// close the extended sidebar if it is open
263+
if (isExtendedProjectSidebarOpened) {
264+
toggleExtendedProjectSidebar(false);
265+
}
262266
};
263267

264268
const isAccordionMode = projectPreferences.navigationMode === "accordion";

0 commit comments

Comments
 (0)