feat: mobile long-press context menu, settings logout button
Mobile: - Add useLongPress hook (500ms, movement cancellation, press feedback) - Disable drag-and-drop on mobile in useEmailDrag - Wire long-press context menu to all email list item components - Add select-none and visual press feedback (scale + ring) Settings: - Add logout button to settings page sidebar
This commit is contained in:
@@ -4,6 +4,7 @@ import { useCallback, DragEvent } from "react";
|
||||
import { Email } from "@/lib/jmap/types";
|
||||
import { useEmailStore } from "@/stores/email-store";
|
||||
import { useDragDropContext } from "@/contexts/drag-drop-context";
|
||||
import { useUIStore } from "@/stores/ui-store";
|
||||
|
||||
interface UseEmailDragOptions {
|
||||
email: Email;
|
||||
@@ -46,6 +47,7 @@ function createDragPreview(count: number): HTMLElement {
|
||||
export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailDragOptions): UseEmailDragReturn {
|
||||
const { selectedEmailIds, emails } = useEmailStore();
|
||||
const { startDrag, endDrag, isDragging, draggedEmails } = useDragDropContext();
|
||||
const isMobile = useUIStore((state) => state.isMobile);
|
||||
|
||||
const handleDragStart = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
// Determine which emails to drag:
|
||||
@@ -88,11 +90,13 @@ export function useEmailDrag({ email, sourceMailboxId, threadEmails }: UseEmailD
|
||||
const isThisEmailDragging = isDragging && draggedEmails.some(em => em.id === email.id);
|
||||
|
||||
return {
|
||||
dragHandlers: {
|
||||
draggable: true,
|
||||
onDragStart: handleDragStart,
|
||||
onDragEnd: handleDragEnd,
|
||||
},
|
||||
dragHandlers: isMobile
|
||||
? { draggable: false, onDragStart: () => {}, onDragEnd: () => {} }
|
||||
: {
|
||||
draggable: true,
|
||||
onDragStart: handleDragStart,
|
||||
onDragEnd: handleDragEnd,
|
||||
},
|
||||
isDragging: isThisEmailDragging,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
const LONG_PRESS_DURATION = 500;
|
||||
const MOVE_THRESHOLD = 10;
|
||||
|
||||
export function useLongPress(
|
||||
onLongPress: (position: { clientX: number; clientY: number }) => void,
|
||||
enabled: boolean = true
|
||||
) {
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const startPosRef = useRef<{ x: number; y: number } | null>(null);
|
||||
const firedRef = useRef(false);
|
||||
const [isPressed, setIsPressed] = useState(false);
|
||||
|
||||
const cancel = useCallback(() => {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
startPosRef.current = null;
|
||||
setIsPressed(false);
|
||||
}, []);
|
||||
|
||||
const onTouchStart = useCallback(
|
||||
(e: React.TouchEvent) => {
|
||||
if (!enabled) return;
|
||||
const touch = e.touches[0];
|
||||
startPosRef.current = { x: touch.clientX, y: touch.clientY };
|
||||
firedRef.current = false;
|
||||
setIsPressed(true);
|
||||
|
||||
timerRef.current = setTimeout(() => {
|
||||
firedRef.current = true;
|
||||
setIsPressed(false);
|
||||
onLongPress({ clientX: touch.clientX, clientY: touch.clientY });
|
||||
}, LONG_PRESS_DURATION);
|
||||
},
|
||||
[enabled, onLongPress]
|
||||
);
|
||||
|
||||
const onTouchMove = useCallback(
|
||||
(e: React.TouchEvent) => {
|
||||
if (!startPosRef.current || !timerRef.current) return;
|
||||
const touch = e.touches[0];
|
||||
const dx = Math.abs(touch.clientX - startPosRef.current.x);
|
||||
const dy = Math.abs(touch.clientY - startPosRef.current.y);
|
||||
if (dx > MOVE_THRESHOLD || dy > MOVE_THRESHOLD) {
|
||||
cancel();
|
||||
}
|
||||
},
|
||||
[cancel]
|
||||
);
|
||||
|
||||
const onTouchEnd = useCallback(
|
||||
(e: React.TouchEvent) => {
|
||||
if (firedRef.current) {
|
||||
e.preventDefault();
|
||||
}
|
||||
cancel();
|
||||
firedRef.current = false;
|
||||
},
|
||||
[cancel]
|
||||
);
|
||||
|
||||
const onTouchCancel = useCallback(() => {
|
||||
cancel();
|
||||
firedRef.current = false;
|
||||
}, [cancel]);
|
||||
|
||||
return { onTouchStart, onTouchEnd, onTouchMove, onTouchCancel, isPressed };
|
||||
}
|
||||
Reference in New Issue
Block a user