From f2845eab91c47281ad0c7648ed8470c4e738fd11 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Fri, 26 Dec 2025 14:09:37 +0200 Subject: [PATCH] Add open / close mobile sidebar on left / right swipe --- common/src/constants.ts | 2 +- web/components/nav/bottom-nav-bar.tsx | 126 ++++++++++++++++++++------ 2 files changed, 98 insertions(+), 30 deletions(-) diff --git a/common/src/constants.ts b/common/src/constants.ts index 8d1b773f..e7b4e0ce 100644 --- a/common/src/constants.ts +++ b/common/src/constants.ts @@ -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 diff --git a/web/components/nav/bottom-nav-bar.tsx b/web/components/nav/bottom-nav-bar.tsx index 4aab84a7..9a1d3b94 100644 --- a/web/components/nav/bottom-nav-bar.tsx +++ b/web/components/nav/bottom-nav-bar.tsx @@ -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 ( - + {navigationOptions.map((item) => ( + + ))} +
setSidebarOpen(true)} + > +
+ +
(null) + const touchStartY = useRef(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 (