forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFilePreview.tsx
More file actions
108 lines (96 loc) · 3.38 KB
/
Copy pathFilePreview.tsx
File metadata and controls
108 lines (96 loc) · 3.38 KB
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
'use client';
import { X, FileText, Image as ImageIcon, Loader2 } from 'lucide-react';
import Image from '@tschk/moonshine-next/image';
import { cn } from '@/lib/utils';
interface FilePreviewItem {
file: File;
preview?: string;
uploading?: boolean;
uploadedId?: string;
}
interface FilePreviewProps {
files: FilePreviewItem[];
onRemove: (index: number) => void;
disabled?: boolean;
}
function isImageFile(file: File): boolean {
return file.type.startsWith('image/');
}
export function FilePreview({ files, onRemove, disabled }: FilePreviewProps) {
if (files.length === 0) return null;
return (
<div className="flex gap-2 px-4 py-3 overflow-x-auto">
{files.map((item, index) => (
<div
key={index}
className={cn(
'relative flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden',
'bg-bg-tertiary border border-bg-quaternary',
'group'
)}
>
{/* Preview content */}
{isImageFile(item.file) && item.preview ? (
<Image
src={item.preview}
alt={item.file.name}
fill
className="object-cover"
/>
) : (
<div className="w-full h-full flex flex-col items-center justify-center p-1">
<FileText className="w-6 h-6 text-text-tertiary mb-1" />
<span className="text-[10px] text-text-quaternary truncate max-w-full px-1">
{item.file.name.split('.').pop()?.toUpperCase()}
</span>
</div>
)}
{/* Upload loading overlay */}
{item.uploading && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
<Loader2 className="w-5 h-5 text-white animate-spin" />
</div>
)}
{/* Remove button */}
{!disabled && !item.uploading && (
<button
onClick={() => onRemove(index)}
className={cn(
'absolute -top-1 -right-1 w-5 h-5 rounded-full',
'bg-bg-primary border border-bg-tertiary',
'flex items-center justify-center',
'opacity-0 group-hover:opacity-100 transition-opacity',
'hover:bg-error hover:border-error hover:text-white'
)}
>
<X className="w-3 h-3" />
</button>
)}
{/* Uploaded indicator */}
{item.uploadedId && !item.uploading && (
<div className="absolute bottom-0 left-0 right-0 bg-green-500/80 py-0.5">
<span className="text-[8px] text-white text-center block">Ready</span>
</div>
)}
</div>
))}
</div>
);
}
// Allowed file types
export const ALLOWED_FILE_TYPES = {
images: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
documents: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'text/plain',
'text/markdown',
],
};
export const ALLOWED_EXTENSIONS = '.jpg,.jpeg,.png,.gif,.webp,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.md';
export const MAX_FILES = 4;