diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx index 13b55c4f3..efc1fdbbc 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx @@ -58,6 +58,7 @@ import { TableRow, } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { useLongPress } from "@/hooks/use-long-press"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { getDocumentTypeIcon, getDocumentTypeLabel } from "./DocumentTypeIcon"; import type { Document, DocumentStatus } from "./types"; @@ -285,31 +286,17 @@ function MobileCardWrapper({ onLongPress: () => void; children: React.ReactNode; }) { - const timerRef = useRef | null>(null); - const didLongPressRef = useRef(false); - - const clearTimer = useCallback(() => { - if (timerRef.current) { - clearTimeout(timerRef.current); - timerRef.current = null; - } - }, []); + const { handlers, wasLongPress } = useLongPress(onLongPress); return ( // biome-ignore lint/a11y/useSemanticElements: touch-only long-press wrapper for mobile
{ - didLongPressRef.current = false; - timerRef.current = setTimeout(() => { - didLongPressRef.current = true; - onLongPress(); - }, 500); - }} - onTouchMove={clearTimer} + onTouchStart={handlers.onTouchStart} + onTouchMove={handlers.onTouchMove} onTouchEnd={(e) => { - clearTimer(); - if (didLongPressRef.current) { + handlers.onTouchEnd(); + if (wasLongPress()) { e.preventDefault(); } }} diff --git a/surfsense_web/components/layout/ui/sidebar/AllPrivateChatsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/AllPrivateChatsSidebar.tsx index 36a20cf3f..c765c2e29 100644 --- a/surfsense_web/components/layout/ui/sidebar/AllPrivateChatsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/AllPrivateChatsSidebar.tsx @@ -16,7 +16,7 @@ import { } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { @@ -85,6 +85,24 @@ export function AllPrivateChatsSidebar({ const [isRenaming, setIsRenaming] = useState(false); const debouncedSearchQuery = useDebouncedValue(searchQuery, 300); + const longPressTimerRef = useRef | null>(null); + const longPressTriggeredRef = useRef(false); + + const handleLongPressStart = useCallback((threadId: number) => { + longPressTriggeredRef.current = false; + longPressTimerRef.current = setTimeout(() => { + longPressTriggeredRef.current = true; + setOpenDropdownId(threadId); + }, 500); + }, []); + + const handleLongPressCancel = useCallback(() => { + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current); + longPressTimerRef.current = null; + } + }, []); + const isSearchMode = !!debouncedSearchQuery.trim(); useEffect(() => { @@ -354,61 +372,72 @@ export function AllPrivateChatsSidebar({ isBusy && "opacity-50 pointer-events-none" )} > - {isMobile ? ( - - ) : ( - - - - - -

- {t("updated") || "Updated"}:{" "} - {format(new Date(thread.updatedAt), "MMM d, yyyy 'at' h:mm a")} -

-
-
- )} - - setOpenDropdownId(isOpen ? thread.id : null)} + {isMobile ? ( + + ) : ( + + + - + + {thread.title || "New Chat"} + + + +

+ {t("updated") || "Updated"}:{" "} + {format(new Date(thread.updatedAt), "MMM d, yyyy 'at' h:mm a")} +

+
+
+ )} + + setOpenDropdownId(isOpen ? thread.id : null)} + > + + + {!thread.archived && ( | null>(null); + const longPressTriggeredRef = useRef(false); + + const handleLongPressStart = useCallback((threadId: number) => { + longPressTriggeredRef.current = false; + longPressTimerRef.current = setTimeout(() => { + longPressTriggeredRef.current = true; + setOpenDropdownId(threadId); + }, 500); + }, []); + + const handleLongPressCancel = useCallback(() => { + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current); + longPressTimerRef.current = null; + } + }, []); + const isSearchMode = !!debouncedSearchQuery.trim(); useEffect(() => { @@ -354,61 +372,72 @@ export function AllSharedChatsSidebar({ isBusy && "opacity-50 pointer-events-none" )} > - {isMobile ? ( - - ) : ( - - - - - -

- {t("updated") || "Updated"}:{" "} - {format(new Date(thread.updatedAt), "MMM d, yyyy 'at' h:mm a")} -

-
-
- )} - - setOpenDropdownId(isOpen ? thread.id : null)} + {isMobile ? ( + + ) : ( + + + - + + {thread.title || "New Chat"} + + + +

+ {t("updated") || "Updated"}:{" "} + {format(new Date(thread.updatedAt), "MMM d, yyyy 'at' h:mm a")} +

+
+
+ )} + + setOpenDropdownId(isOpen ? thread.id : null)} + > + + + {!thread.archived && ( setDropdownOpen(true), []) + ); + + const handleClick = useCallback(() => { + if (wasLongPress()) return; + onClick?.(); + }, [onClick, wasLongPress]); return (
- {/* Actions dropdown */} -
- + {/* Actions dropdown - trigger hidden on mobile, long-press opens it instead */} +
+