File size: 3,755 Bytes
d325ede
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
 * ImagePreviewGrid — Thumbnail grid with remove button.
 * On mobile the remove button is always visible (no hover needed).
 */
import { AnimatePresence, motion } from 'framer-motion';
import type { ImageFile } from '../types';

interface ImagePreviewGridProps {
  files: ImageFile[];
  onRemove: (id: string) => void;
}

function formatFileSize(bytes: number): string {
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

export default function ImagePreviewGrid({ files, onRemove }: ImagePreviewGridProps) {
  if (files.length === 0) return null;

  return (
    <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="space-y-3">
      <div className="flex items-center justify-between">
        <h3 className="text-xs sm:text-sm font-medium text-white/60">
          {files.length} image{files.length !== 1 ? 's' : ''} selected
        </h3>
        {files.length >= 4 && (
          <span className="text-xs text-green-400 font-medium">✓ Required views ready</span>
        )}
      </div>

      <div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-2 sm:gap-3">
        <AnimatePresence mode="popLayout">
          {files.map((image, index) => (
            <motion.div
              key={image.id}
              layout
              initial={{ opacity: 0, scale: 0.8 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.8 }}
              transition={{ duration: 0.18 }}
              className="group relative"
            >
              <div className="relative aspect-square rounded-xl overflow-hidden bg-white/5 border border-white/10">
                <img
                  src={image.thumbnail}
                  alt={image.filename}
                  className="w-full h-full object-cover"
                  loading="lazy"
                />

                {/* Gradient overlay */}
                <div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent" />

                {/* Bottom info */}
                <div className="absolute bottom-0 left-0 right-0 p-1.5">
                  <p className="text-[10px] text-white/80 truncate leading-tight">{image.label}</p>
                  <p className="text-[9px] text-white/40">{formatFileSize(image.size)}</p>
                </div>

                {/* Index badge */}
                <span className="absolute top-1.5 left-1.5 px-1.5 py-0.5 text-[9px] rounded-md bg-indigo-500/80 text-white font-medium">
                  {index + 1}
                </span>

                {/* Remove button — always visible on touch, hover on desktop */}
                <button
                  onClick={() => onRemove(image.id)}
                  className="
                    absolute top-1.5 right-1.5
                    w-6 h-6 rounded-full
                    bg-black/60 hover:bg-red-500
                    flex items-center justify-center
                    /* Always show on touch devices, hover-reveal on desktop */
                    opacity-100 sm:opacity-0 sm:group-hover:opacity-100
                    transition-all duration-150
                    touch-manipulation
                  "
                  aria-label={`Remove ${image.filename}`}
                >
                  <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M6 18L18 6M6 6l12 12" />
                  </svg>
                </button>
              </div>
            </motion.div>
          ))}
        </AnimatePresence>
      </div>
    </motion.div>
  );
}