177 lines
7.8 KiB
TypeScript
177 lines
7.8 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { X, Send, FileText, Film, Music, Archive, File } from 'lucide-react';
|
|
|
|
interface FilePreviewModalProps {
|
|
file: File | null;
|
|
onConfirm: (file: File, caption: string) => Promise<void>;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
function formatFileSize(bytes: number): string {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
function getFileIcon(type: string) {
|
|
if (type.startsWith('video/')) return <Film className="w-12 h-12 text-blue-400" />;
|
|
if (type.startsWith('audio/')) return <Music className="w-12 h-12 text-purple-400" />;
|
|
if (type.includes('pdf') || type.includes('document') || type.includes('word'))
|
|
return <FileText className="w-12 h-12 text-red-400" />;
|
|
if (type.includes('zip') || type.includes('compressed') || type.includes('tar'))
|
|
return <Archive className="w-12 h-12 text-yellow-400" />;
|
|
return <File className="w-12 h-12 text-[#8696a0]" />;
|
|
}
|
|
|
|
function getFileLabel(type: string): string {
|
|
if (type.startsWith('image/')) return 'Imagem';
|
|
if (type.startsWith('video/')) return 'Vídeo';
|
|
if (type.startsWith('audio/')) return 'Áudio';
|
|
if (type.includes('pdf')) return 'PDF';
|
|
return 'Arquivo';
|
|
}
|
|
|
|
export default function FilePreviewModal({ file, onConfirm, onCancel }: FilePreviewModalProps) {
|
|
const [caption, setCaption] = useState('');
|
|
const [sending, setSending] = useState(false);
|
|
const [objectUrl, setObjectUrl] = useState<string | null>(null);
|
|
const captionRef = useRef<HTMLInputElement>(null);
|
|
|
|
const isImage = file?.type.startsWith('image/');
|
|
const isVideo = file?.type.startsWith('video/');
|
|
|
|
// Create / revoke object URL
|
|
useEffect(() => {
|
|
if (!file) { setObjectUrl(null); return; }
|
|
const url = URL.createObjectURL(file);
|
|
setObjectUrl(url);
|
|
setCaption('');
|
|
setSending(false);
|
|
setTimeout(() => captionRef.current?.focus(), 200);
|
|
return () => URL.revokeObjectURL(url);
|
|
}, [file]);
|
|
|
|
// ESC cancels
|
|
useEffect(() => {
|
|
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onCancel(); };
|
|
document.addEventListener('keydown', onKey);
|
|
return () => document.removeEventListener('keydown', onKey);
|
|
}, [onCancel]);
|
|
|
|
const handleSend = useCallback(async () => {
|
|
if (!file || sending) return;
|
|
setSending(true);
|
|
try {
|
|
await onConfirm(file, caption.trim());
|
|
} finally {
|
|
setSending(false);
|
|
}
|
|
}, [file, caption, sending, onConfirm]);
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSend();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{file && (
|
|
<motion.div
|
|
key="file-preview-overlay"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="fixed inset-0 z-[9998] flex flex-col bg-[#111b21]"
|
|
>
|
|
{/* ── Top bar ── */}
|
|
<div className="flex items-center justify-between px-5 py-3 bg-[#202c33] shrink-0">
|
|
<button
|
|
onClick={onCancel}
|
|
className="p-2 rounded-full hover:bg-white/10 text-[#aebac1] transition-colors"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
<span className="text-[#e9edef] text-sm font-medium">
|
|
{file ? getFileLabel(file.type) : ''}
|
|
</span>
|
|
<div className="w-9" /> {/* spacer */}
|
|
</div>
|
|
|
|
{/* ── Preview area ── */}
|
|
<div className="flex-1 flex items-center justify-center p-4 min-h-0 overflow-hidden">
|
|
{isImage && objectUrl && (
|
|
<motion.img
|
|
src={objectUrl}
|
|
alt="preview"
|
|
initial={{ scale: 0.92, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ duration: 0.25 }}
|
|
className="max-w-full max-h-full object-contain rounded-xl shadow-2xl"
|
|
/>
|
|
)}
|
|
|
|
{isVideo && objectUrl && (
|
|
<motion.video
|
|
src={objectUrl}
|
|
controls
|
|
initial={{ scale: 0.92, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ duration: 0.25 }}
|
|
className="max-w-full max-h-full rounded-xl shadow-2xl"
|
|
/>
|
|
)}
|
|
|
|
{!isImage && !isVideo && file && (
|
|
<motion.div
|
|
initial={{ scale: 0.92, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ duration: 0.25 }}
|
|
className="flex flex-col items-center gap-4 bg-[#202c33] rounded-2xl px-12 py-10 shadow-2xl"
|
|
>
|
|
{getFileIcon(file.type)}
|
|
<div className="text-center">
|
|
<p className="text-[#e9edef] text-sm font-medium max-w-[280px] break-all text-center">
|
|
{file.name}
|
|
</p>
|
|
<p className="text-[#8696a0] text-xs mt-1">{formatFileSize(file.size)}</p>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
|
|
{/* ── Caption + Send bar ── */}
|
|
<div className="shrink-0 bg-[#202c33] px-4 py-3 flex items-center gap-3">
|
|
<input
|
|
ref={captionRef}
|
|
type="text"
|
|
placeholder="Adicionar legenda…"
|
|
value={caption}
|
|
onChange={(e) => setCaption(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
disabled={sending}
|
|
className="flex-1 bg-[#2a3942] text-[#e9edef] placeholder:text-[#8696a0] text-sm rounded-full px-4 py-2.5 outline-none border-none focus:ring-0 disabled:opacity-50"
|
|
/>
|
|
<button
|
|
onClick={handleSend}
|
|
disabled={sending}
|
|
className="w-11 h-11 bg-[#00a884] hover:bg-[#02c89f] disabled:opacity-50 rounded-full flex items-center justify-center transition-colors shadow-md active:scale-95"
|
|
>
|
|
{sending ? (
|
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
) : (
|
|
<Send className="w-5 h-5 text-white" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|