Skip to content

Commit d124d3e

Browse files
committed
fix: resolve sidebar collapse white screen and state persistence
1 parent 88bde83 commit d124d3e

File tree

2 files changed

+44
-43
lines changed

2 files changed

+44
-43
lines changed

frontend/src/components/Navigation/Sidebar/AppSidebar.tsx

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,13 @@ import { Button } from '@/components/ui/button';
2727
import {
2828
Tooltip,
2929
TooltipContent,
30-
TooltipProvider,
3130
TooltipTrigger,
3231
} from '@/components/ui/tooltip';
3332

3433
export function AppSidebar() {
3534
const location = useLocation();
3635
const [version, setVersion] = useState<string>('1.0.0');
37-
const { open, setOpen, toggleSidebar } = useSidebar();
38-
39-
useEffect(() => {
40-
const savedState = localStorage.getItem('sidebar-open');
41-
if (savedState !== null) {
42-
const shouldBeOpen = savedState === 'true';
43-
setOpen(shouldBeOpen);
44-
}
45-
}, [setOpen]);
46-
47-
useEffect(() => {
48-
localStorage.setItem('sidebar-open', String(open));
49-
}, [open]);
36+
const { open, toggleSidebar } = useSidebar();
5037

5138
useEffect(() => {
5239
getVersion().then((version) => {
@@ -125,27 +112,25 @@ export function AppSidebar() {
125112
<div
126113
className={`flex transition-all duration-300 ${open ? 'justify-end px-4' : 'justify-center'}`}
127114
>
128-
<TooltipProvider>
129-
<Tooltip>
130-
<TooltipTrigger asChild>
131-
<Button
132-
variant="ghost"
133-
size="icon"
134-
onClick={toggleSidebar}
135-
className="hover:bg-accent h-8 w-8 transition-transform duration-300 ease-in-out"
136-
>
137-
<ChevronLeft
138-
className={`h-5 w-5 transition-transform duration-300 ease-in-out ${
139-
!open ? 'rotate-180' : ''
140-
}`}
141-
/>
142-
</Button>
143-
</TooltipTrigger>
144-
<TooltipContent side="right">
145-
<p>{open ? 'Collapse sidebar' : 'Expand sidebar'}</p>
146-
</TooltipContent>
147-
</Tooltip>
148-
</TooltipProvider>
115+
<Tooltip>
116+
<TooltipTrigger asChild>
117+
<Button
118+
variant="ghost"
119+
size="icon"
120+
onClick={toggleSidebar}
121+
className="hover:bg-accent h-8 w-8 transition-transform duration-300 ease-in-out"
122+
>
123+
<ChevronLeft
124+
className={`h-5 w-5 transition-transform duration-300 ease-in-out ${
125+
!open ? 'rotate-180' : ''
126+
}`}
127+
/>
128+
</Button>
129+
</TooltipTrigger>
130+
<TooltipContent side="right">
131+
<p>{open ? 'Collapse sidebar' : 'Expand sidebar'}</p>
132+
</TooltipContent>
133+
</Tooltip>
149134
</div>
150135
</div>
151136
</SidebarFooter>

frontend/src/components/ui/sidebar.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,22 @@ function SidebarProvider({
6868
const [openMobile, setOpenMobile] = React.useState(false);
6969

7070
// This is the internal state of the sidebar.
71-
// We use openProp and setOpenProp for control from outside the component.
7271
const [_open, _setOpen] = React.useState(defaultOpen);
7372
const open = openProp ?? _open;
73+
74+
// Restore sidebar state from cookie on mount
75+
React.useEffect(() => {
76+
try {
77+
const cookies = document.cookie.split('; ').find(row => row.startsWith(SIDEBAR_COOKIE_NAME + '='));
78+
if (cookies) {
79+
const savedState = cookies.split('=')[1] === 'true';
80+
_setOpen(savedState);
81+
}
82+
} catch {
83+
// Ignore cookie errors
84+
}
85+
}, []);
86+
7487
const setOpen = React.useCallback(
7588
(value: boolean | ((value: boolean) => boolean)) => {
7689
const openState = typeof value === 'function' ? value(open) : value;
@@ -81,7 +94,11 @@ function SidebarProvider({
8194
}
8295

8396
// This sets the cookie to keep the sidebar state.
84-
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
97+
try {
98+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
99+
} catch {
100+
101+
}
85102
},
86103
[setOpenProp, open],
87104
);
@@ -107,8 +124,7 @@ function SidebarProvider({
107124
return () => window.removeEventListener('keydown', handleKeyDown);
108125
}, [toggleSidebar]);
109126

110-
// We add a state so that we can do data-state="expanded" or "collapsed".
111-
// This makes it easier to style the sidebar with Tailwind classes.
127+
112128
const state = open ? 'expanded' : 'collapsed';
113129

114130
const contextValue = React.useMemo<SidebarContextProps>(
@@ -231,7 +247,7 @@ function Sidebar({
231247
side === 'left'
232248
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
233249
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
234-
// Adjust the padding for floating and inset variants.
250+
235251
variant === 'floating' || variant === 'inset'
236252
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
237253
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
@@ -425,7 +441,7 @@ function SidebarGroupAction({
425441
data-sidebar="group-action"
426442
className={cn(
427443
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
428-
// Increases the hit area of the button on mobile.
444+
429445
'after:absolute after:-inset-2 md:after:hidden',
430446
'group-data-[collapsible=icon]:hidden',
431447
className,
@@ -560,7 +576,7 @@ function SidebarMenuAction({
560576
data-sidebar="menu-action"
561577
className={cn(
562578
'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
563-
// Increases the hit area of the button on mobile.
579+
564580
'after:absolute after:-inset-2 md:after:hidden',
565581
'peer-data-[size=sm]/menu-button:top-1',
566582
'peer-data-[size=default]/menu-button:top-1.5',
@@ -604,7 +620,7 @@ function SidebarMenuSkeleton({
604620
}: React.ComponentProps<'div'> & {
605621
showIcon?: boolean;
606622
}) {
607-
// Random width between 50 to 90%.
623+
608624
const width = React.useMemo(() => {
609625
return `${Math.floor(Math.random() * 40) + 50}%`;
610626
}, []);

0 commit comments

Comments
 (0)