Skip to content

Commit 4c92767

Browse files
committed
Fix mobile sidebar logout interaction
1 parent 13652b5 commit 4c92767

2 files changed

Lines changed: 76 additions & 7 deletions

File tree

frontend/src/components/Sidebar/User.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Link as RouterLink } from "@tanstack/react-router"
22
import { ChevronsUpDown, LogOut, Settings } from "lucide-react"
3+
import { useState } from "react"
34

45
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
56
import {
@@ -42,23 +43,32 @@ function UserInfo({ fullName, email }: UserInfoProps) {
4243

4344
export function User({ user }: { user: any }) {
4445
const { logout } = useAuth()
45-
const { isMobile, setOpenMobile } = useSidebar()
46+
const { isMobile, setOpenMobile, closeMobileSidebar } = useSidebar()
47+
const [isMenuOpen, setIsMenuOpen] = useState(false)
4648

4749
if (!user) return null
4850

4951
const handleMenuClick = () => {
52+
setIsMenuOpen(false)
5053
if (isMobile) {
5154
setOpenMobile(false)
5255
}
5356
}
54-
const handleLogout = async () => {
55-
logout()
57+
const handleLogout = () => {
58+
setIsMenuOpen(false)
59+
60+
if (isMobile) {
61+
void closeMobileSidebar().then(logout)
62+
return
63+
}
64+
65+
window.requestAnimationFrame(logout)
5666
}
5767

5868
return (
5969
<SidebarMenu>
6070
<SidebarMenuItem>
61-
<DropdownMenu>
71+
<DropdownMenu open={isMenuOpen} onOpenChange={setIsMenuOpen}>
6272
<DropdownMenuTrigger asChild>
6373
<SidebarMenuButton
6474
size="lg"
@@ -85,7 +95,12 @@ export function User({ user }: { user: any }) {
8595
User Settings
8696
</DropdownMenuItem>
8797
</RouterLink>
88-
<DropdownMenuItem onClick={handleLogout}>
98+
<DropdownMenuItem
99+
onSelect={(event) => {
100+
event.preventDefault()
101+
handleLogout()
102+
}}
103+
>
89104
<LogOut />
90105
Log Out
91106
</DropdownMenuItem>

frontend/src/components/ui/sidebar.tsx

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ type SidebarContextProps = {
3636
setOpen: (open: boolean) => void
3737
openMobile: boolean
3838
setOpenMobile: (open: boolean) => void
39+
closeMobileSidebar: () => Promise<void>
40+
completeMobileSidebarClose: () => void
3941
isMobile: boolean
4042
toggleSidebar: () => void
4143
}
@@ -66,6 +68,36 @@ function SidebarProvider({
6668
}) {
6769
const isMobile = useIsMobile()
6870
const [openMobile, setOpenMobile] = React.useState(false)
71+
const mobileCloseResolversRef = React.useRef(new Set<() => void>())
72+
73+
const completeMobileSidebarClose = React.useCallback(() => {
74+
const resolvers = Array.from(mobileCloseResolversRef.current)
75+
mobileCloseResolversRef.current.clear()
76+
resolvers.forEach((resolve) => resolve())
77+
}, [])
78+
79+
const closeMobileSidebar = React.useCallback(() => {
80+
if (!isMobile || !openMobile) {
81+
return Promise.resolve()
82+
}
83+
84+
setOpenMobile(false)
85+
86+
return new Promise<void>((resolve) => {
87+
let timeoutId: number | undefined
88+
const resolveOnce = () => {
89+
if (timeoutId !== undefined) {
90+
window.clearTimeout(timeoutId)
91+
}
92+
mobileCloseResolversRef.current.delete(resolveOnce)
93+
resolve()
94+
}
95+
96+
mobileCloseResolversRef.current.add(resolveOnce)
97+
// Fallback for environments that do not fire CSS animation events.
98+
timeoutId = window.setTimeout(resolveOnce, 350)
99+
})
100+
}, [isMobile, openMobile])
69101

70102
const getInitialOpen = () => {
71103
if (typeof document === "undefined") return defaultOpen
@@ -131,9 +163,20 @@ function SidebarProvider({
131163
isMobile,
132164
openMobile,
133165
setOpenMobile,
166+
closeMobileSidebar,
167+
completeMobileSidebarClose,
134168
toggleSidebar,
135169
}),
136-
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
170+
[
171+
state,
172+
open,
173+
setOpen,
174+
isMobile,
175+
openMobile,
176+
closeMobileSidebar,
177+
completeMobileSidebarClose,
178+
toggleSidebar,
179+
],
137180
)
138181

139182
return (
@@ -173,7 +216,13 @@ function Sidebar({
173216
variant?: "sidebar" | "floating" | "inset"
174217
collapsible?: "offcanvas" | "icon" | "none"
175218
}) {
176-
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
219+
const {
220+
isMobile,
221+
state,
222+
openMobile,
223+
setOpenMobile,
224+
completeMobileSidebarClose,
225+
} = useSidebar()
177226

178227
if (collapsible === "none") {
179228
return (
@@ -198,6 +247,11 @@ function Sidebar({
198247
data-slot="sidebar"
199248
data-mobile="true"
200249
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
250+
onAnimationEnd={(event) => {
251+
if (event.currentTarget === event.target && !openMobile) {
252+
completeMobileSidebarClose()
253+
}
254+
}}
201255
style={
202256
{
203257
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,

0 commit comments

Comments
 (0)