Add open / close mobile sidebar on left / right swipe

This commit is contained in:
MartinBraquet
2025-12-26 14:09:37 +02:00
parent 40702e7832
commit f2845eab91
2 changed files with 98 additions and 30 deletions

View File

@@ -19,7 +19,7 @@ export const xLink = "https://x.com/compassmeet"
export const formLink = "https://forms.gle/tKnXUMAbEreMK6FC6"
export const ANDROID_APP_URL = 'https://play.google.com/store/apps/details?id=com.compassconnections.app'
export const IS_MAINTENANCE = false // set to true to enable the maintenance mode banner
export const IS_MAINTENANCE = true // set to true to enable the maintenance mode banner
export const MIN_BIO_LENGTH = 250

View File

@@ -2,7 +2,7 @@ import Link from 'next/link'
import clsx from 'clsx'
import {MenuAlt3Icon} from '@heroicons/react/solid'
import {Dialog, Transition} from '@headlessui/react'
import {Fragment, useState} from 'react'
import {Fragment, useState, useRef, useEffect} from 'react'
import {useRouter} from 'next/router'
import Sidebar from './sidebar'
import {Item} from './sidebar-item'
@@ -13,6 +13,7 @@ import {trackCallback} from 'web/lib/service/analytics'
import {User} from 'common/user'
import {Col} from 'web/components/layout/col'
import {useProfile} from 'web/hooks/use-profile'
import {useIsMobile} from "web/hooks/use-is-mobile"
const itemClass =
'sm:hover:bg-ink-200 block w-full py-1 px-3 text-center sm:hover:text-primary-700 transition-colors'
@@ -38,37 +39,37 @@ export function BottomNavBar(props: {
}
return (<Col>
<nav
className={clsx(
"border-ink-200 dark:border-ink-300 text-ink-700 bg-canvas-50 fixed inset-x-0 bottom-0 z-50 flex select-none items-center justify-between border-t-2 text-xs lg:hidden sidebar-nav",
'safe-bottom',
)}
>
{navigationOptions.map((item) => (
<NavBarItem
key={item.name}
item={item}
currentPage={currentPage}
user={user}
/>
))}
<div
<nav
className={clsx(
itemClass,
'relative',
sidebarOpen ? selectedItemClass : ''
"border-ink-200 dark:border-ink-300 text-ink-700 bg-canvas-50 fixed inset-x-0 bottom-0 z-50 flex select-none items-center justify-between border-t-2 text-xs lg:hidden sidebar-nav",
'safe-bottom',
)}
onClick={() => setSidebarOpen(true)}
>
<MenuAlt3Icon className="mx-auto my-1 h-6 w-6" aria-hidden="true"/>
More
</div>
<MobileSidebar
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
sidebarNavigationOptions={sidebarNavigationOptions}
/>
</nav>
{navigationOptions.map((item) => (
<NavBarItem
key={item.name}
item={item}
currentPage={currentPage}
user={user}
/>
))}
<div
className={clsx(
itemClass,
'relative',
sidebarOpen ? selectedItemClass : ''
)}
onClick={() => setSidebarOpen(true)}
>
<MenuAlt3Icon className="mx-auto my-1 h-6 w-6" aria-hidden="true"/>
More
</div>
<MobileSidebar
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
sidebarNavigationOptions={sidebarNavigationOptions}
/>
</nav>
<div
className="fixed inset-x-0 bg-canvas-50"
@@ -187,6 +188,73 @@ export function MobileSidebar(props: {
sidebarNavigationOptions: Item[]
}) {
const {sidebarOpen, sidebarNavigationOptions, setSidebarOpen} = props
// Touch gesture handlers to open/close the mobile sidebar on any page
const touchStartX = useRef<number | null>(null)
const touchStartY = useRef<number | null>(null)
const gestureHandled = useRef(false)
const isMobile = useIsMobile(1024) // for lg threshold, like in BottomNavBar vs Sidebar
const HORIZONTAL_THRESHOLD = 50 // px required to count as swipe
// const EDGE_START_MAX = 30 // px from left edge to allow open gesture
// Prefer global pointer events so gestures work even if a child intercepts touches
useEffect(() => {
if (!isMobile) return
const onPointerDown = (e: PointerEvent) => {
// console.log("onPointerDown")
if (!['touch', 'mouse'].includes(e.pointerType)) return
touchStartX.current = e.clientX
touchStartY.current = e.clientY
gestureHandled.current = false
}
const onPointerMove = (e: PointerEvent) => {
// console.log("onPointerMove")
if (!['touch', 'mouse'].includes(e.pointerType)) return
if (gestureHandled.current) return
if (touchStartX.current == null) return
const deltaX = e.clientX - touchStartX.current
const deltaY = e.clientY - (touchStartY.current ?? 0)
// Ignore primarily vertical gestures
if (Math.abs(deltaY) > Math.abs(deltaX)) return
if (!sidebarOpen) {
// console.log("checking opening")
// Open gesture: swipe right starting from the very left edge
if (deltaX > HORIZONTAL_THRESHOLD) {
gestureHandled.current = true
setSidebarOpen(true)
}
} else {
// Close gesture: swipe left anywhere
if (deltaX < -HORIZONTAL_THRESHOLD) {
gestureHandled.current = true
setSidebarOpen(false)
}
}
}
const onPointerUp = (_e: PointerEvent) => {
// console.log("onPointerUp")
touchStartX.current = null
touchStartY.current = null
gestureHandled.current = false
}
window.addEventListener('pointerdown', onPointerDown, {passive: true})
window.addEventListener('pointermove', onPointerMove, {passive: true})
window.addEventListener('pointerup', onPointerUp, {passive: true})
return () => {
window.removeEventListener('pointerdown', onPointerDown)
window.removeEventListener('pointermove', onPointerMove)
window.removeEventListener('pointerup', onPointerUp)
}
}, [isMobile, sidebarOpen])
return (
<div>
<Transition.Root show={sidebarOpen} as={Fragment}>