File size: 3,225 Bytes
d0c18f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { ReactNode } from 'react';
import { X, FileText, Image, Music, Video } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';

interface FileListItemProps {
  file: File;
  index?: number;
  onRemove: () => void;
  iconColor?: 'yellow' | 'green' | 'blue' | 'gray';
  className?: string;
}

const iconColorMap = {
  yellow: 'bg-yellow-100 text-yellow-600',
  green: 'bg-green-100 text-green-600',
  blue: 'bg-blue-100 text-blue-600',
  gray: 'bg-gray-100 text-gray-600',
};

const formatFileSize = (bytes: number): string => {
  if (bytes === 0) return '0 Bytes';
  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};

const getFileIcon = (file: File): ReactNode => {
  const ext = file.name.split('.').pop()?.toLowerCase() || '';
  const type = file.type;

  if (type.startsWith('image/') || ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext)) {
    return <Image className="h-5 w-5" />;
  }
  if (type.startsWith('video/') || ['mp4', 'avi', 'mov', 'mkv', 'webm'].includes(ext)) {
    return <Video className="h-5 w-5" />;
  }
  if (type.startsWith('audio/') || ['mp3', 'wav', 'aac', 'flac', 'ogg'].includes(ext)) {
    return <Music className="h-5 w-5" />;
  }
  return <FileText className="h-5 w-5" />;
};

const getIconColor = (file: File): 'yellow' | 'green' | 'blue' | 'gray' => {
  const ext = file.name.split('.').pop()?.toLowerCase() || '';
  const type = file.type;

  if (type.startsWith('image/') || ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(ext)) {
    return 'blue';
  }
  if (type.startsWith('audio/') || ['mp3', 'wav', 'aac', 'flac', 'ogg'].includes(ext)) {
    return 'green';
  }
  if (['md', 'txt', 'html', 'json', 'csv'].includes(ext)) {
    return 'yellow';
  }
  return 'gray';
};

export function FileListItem({
  file,
  index,
  onRemove,
  iconColor,
  className,
}: FileListItemProps) {
  const color = iconColor || getIconColor(file);
  const icon = getFileIcon(file);

  return (
    <div
      className={cn(
        'flex items-center gap-3 p-3 bg-muted/30 rounded-lg',
        className
      )}
    >
      {/* 序号 */}
      {index !== undefined && (
        <span
          className={cn(
            'bg-primary text-primary-foreground text-xs px-2 py-0.5 rounded',
            'font-medium min-w-[24px] text-center'
          )}
        >
          {index + 1}
        </span>
      )}

      {/* 图标容器 - 40px */}
      <div
        className={cn(
          'h-10 w-10 rounded-lg flex items-center justify-center',
          iconColorMap[color]
        )}
      >
        {icon}
      </div>

      {/* 文件信息 */}
      <div className="flex-1 min-w-0">
        <p className="text-sm font-medium truncate">{file.name}</p>
        <p className="text-xs text-muted-foreground">{formatFileSize(file.size)}</p>
      </div>

      {/* 移除按钮 */}
      <Button
        variant="destructive"
        size="icon"
        className="h-7 w-7"
        onClick={onRemove}
      >
        <X className="h-4 w-4" />
      </Button>
    </div>
  );
}