import { useRef, useState } from 'react'; import { formatDistanceToNow } from 'date-fns'; import { ru } from 'date-fns/locale'; import { Pin, BellOff, Bell, LogOut } from 'lucide-react'; import Avatar from './Avatar'; import { Chat } from '../types'; interface Props { chat: Chat; active: boolean; online?: boolean; onClick: () => void; onPin: (chatId: string, pinned: boolean) => void; onMute: (chatId: string, muted: boolean) => void; onLeave: (chatId: string) => void; } // How far buttons extend when snapped open const SNAP_LEFT = -120; const SNAP_RIGHT = 72; export default function ChatListItem({ chat, active, online, onClick, onPin, onMute, onLeave }: Props) { // swipeX: live drag offset; snapped: 'left' | 'right' | null = locked open const [swipeX, setSwipeX] = useState(0); const [snapped, setSnapped] = useState<'left' | 'right' | null>(null); const touchStart = useRef<{ x: number; y: number } | null>(null); const dragging = useRef(false); const time = chat.lastMessageAt ? formatDistanceToNow(new Date(chat.lastMessageAt), { addSuffix: false, locale: ru }) : ''; const icon = chat.type === 'channel' ? '📢' : chat.type === 'group' ? '👥' : null; // Current visual offset = live drag + snap position const baseX = snapped === 'left' ? SNAP_LEFT : snapped === 'right' ? SNAP_RIGHT : 0; const visualX = snapped ? baseX : swipeX; function handleTouchStart(e: React.TouchEvent) { touchStart.current = { x: e.touches[0].clientX, y: e.touches[0].clientY }; dragging.current = false; } function handleTouchMove(e: React.TouchEvent) { if (!touchStart.current) return; const dx = e.touches[0].clientX - touchStart.current.x; const dy = Math.abs(e.touches[0].clientY - touchStart.current.y); if (!dragging.current && dy > 10 && Math.abs(dx) < dy) { touchStart.current = null; return; } if (Math.abs(dx) > 6) dragging.current = true; if (!dragging.current) return; // If already snapped, allow dragging back from snap position const raw = snapped ? baseX + dx : dx; setSwipeX(Math.max(SNAP_LEFT, Math.min(SNAP_RIGHT, raw))); } function handleTouchEnd() { if (!dragging.current) { touchStart.current = null; return; } dragging.current = false; touchStart.current = null; if (swipeX > 50) { // Snapped right → pin immediately and close onPin(chat.id, !chat.isPinned); setSwipeX(0); setSnapped(null); } else if (swipeX < -60) { // Snap left open → user can tap action buttons setSwipeX(0); setSnapped('left'); } else { // Close setSwipeX(0); setSnapped(null); } } function close() { setSwipeX(0); setSnapped(null); } function handleMainClick() { if (snapped) { close(); return; } onClick(); } const showPinHint = swipeX > 30; const leftOpen = snapped === 'left'; return (
{/* Right bg: pin hint while dragging */}
{chat.isPinned ? 'Открепить' : 'Закрепить'}
{/* Left actions: always rendered, visible when snapped */}
{chat.type !== 'private' && ( )}
{/* Main row */}
); }