Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions frontend/src/components/Sidebar/User.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Link as RouterLink } from "@tanstack/react-router"
import { ChevronsUpDown, LogOut, Settings } from "lucide-react"
import { useState } from "react"

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

export function User({ user }: { user: any }) {
const { logout } = useAuth()
const { isMobile, setOpenMobile } = useSidebar()
const { isMobile, setOpenMobile, closeMobileSidebar } = useSidebar()
const [isMenuOpen, setIsMenuOpen] = useState(false)

if (!user) return null

const handleMenuClick = () => {
setIsMenuOpen(false)
if (isMobile) {
setOpenMobile(false)
}
}
const handleLogout = async () => {
logout()
const handleLogout = () => {
setIsMenuOpen(false)

if (isMobile) {
void closeMobileSidebar().then(logout)
return
}

window.requestAnimationFrame(logout)
}

return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenu open={isMenuOpen} onOpenChange={setIsMenuOpen}>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
Expand All @@ -85,7 +95,12 @@ export function User({ user }: { user: any }) {
User Settings
</DropdownMenuItem>
</RouterLink>
<DropdownMenuItem onClick={handleLogout}>
<DropdownMenuItem
onSelect={(event) => {
event.preventDefault()
handleLogout()
}}
>
<LogOut />
Log Out
</DropdownMenuItem>
Expand Down
58 changes: 56 additions & 2 deletions frontend/src/components/ui/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type SidebarContextProps = {
setOpen: (open: boolean) => void
openMobile: boolean
setOpenMobile: (open: boolean) => void
closeMobileSidebar: () => Promise<void>
completeMobileSidebarClose: () => void
isMobile: boolean
toggleSidebar: () => void
}
Expand Down Expand Up @@ -66,6 +68,36 @@ function SidebarProvider({
}) {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
const mobileCloseResolversRef = React.useRef(new Set<() => void>())

const completeMobileSidebarClose = React.useCallback(() => {
const resolvers = Array.from(mobileCloseResolversRef.current)
mobileCloseResolversRef.current.clear()
resolvers.forEach((resolve) => resolve())
}, [])

const closeMobileSidebar = React.useCallback(() => {
if (!isMobile || !openMobile) {
return Promise.resolve()
}

setOpenMobile(false)

return new Promise<void>((resolve) => {
let timeoutId: number | undefined
const resolveOnce = () => {
if (timeoutId !== undefined) {
window.clearTimeout(timeoutId)
}
mobileCloseResolversRef.current.delete(resolveOnce)
resolve()
}

mobileCloseResolversRef.current.add(resolveOnce)
// Fallback for environments that do not fire CSS animation events.
timeoutId = window.setTimeout(resolveOnce, 350)
})
}, [isMobile, openMobile])

const getInitialOpen = () => {
if (typeof document === "undefined") return defaultOpen
Expand Down Expand Up @@ -131,9 +163,20 @@ function SidebarProvider({
isMobile,
openMobile,
setOpenMobile,
closeMobileSidebar,
completeMobileSidebarClose,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, toggleSidebar],
[
state,
open,
setOpen,
isMobile,
openMobile,
closeMobileSidebar,
completeMobileSidebarClose,
toggleSidebar,
],
)

return (
Expand Down Expand Up @@ -173,7 +216,13 @@ function Sidebar({
variant?: "sidebar" | "floating" | "inset"
collapsible?: "offcanvas" | "icon" | "none"
}) {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
const {
isMobile,
state,
openMobile,
setOpenMobile,
completeMobileSidebarClose,
} = useSidebar()

if (collapsible === "none") {
return (
Expand All @@ -198,6 +247,11 @@ function Sidebar({
data-slot="sidebar"
data-mobile="true"
className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
onAnimationEnd={(event) => {
if (event.currentTarget === event.target && !openMobile) {
completeMobileSidebarClose()
}
}}
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
Expand Down
Loading