Files
kit-ui/components/favicon/FaviconFileUpload.tsx

157 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import * as React from 'react';
import { Upload, X, FileImage, HardDrive } from 'lucide-react';
import { cn } from '@/lib/utils/cn';
import { Button } from '@/components/ui/button';
export interface FaviconFileUploadProps {
onFileSelect: (file: File) => void;
onFileRemove: () => void;
selectedFile?: File | null;
disabled?: boolean;
}
export function FaviconFileUpload({
onFileSelect,
onFileRemove,
selectedFile,
disabled = false,
}: FaviconFileUploadProps) {
const [isDragging, setIsDragging] = React.useState(false);
const [dimensions, setDimensions] = React.useState<string | null>(null);
const fileInputRef = React.useRef<HTMLInputElement>(null);
React.useEffect(() => {
if (selectedFile) {
const img = new Image();
img.onload = () => {
setDimensions(`${img.width} × ${img.height}`);
URL.revokeObjectURL(img.src);
};
img.src = URL.createObjectURL(selectedFile);
} else {
setDimensions(null);
}
}, [selectedFile]);
const handleDragEnter = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (!disabled) setIsDragging(true);
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
if (disabled) return;
const files = Array.from(e.dataTransfer.files);
if (files.length > 0 && files[0].type.startsWith('image/')) {
onFileSelect(files[0]);
}
};
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
if (files.length > 0 && files[0].type.startsWith('image/')) {
onFileSelect(files[0]);
}
};
const handleClick = () => {
if (!disabled) fileInputRef.current?.click();
};
return (
<div className="w-full space-y-3">
<input
ref={fileInputRef}
type="file"
className="hidden"
accept="image/*"
onChange={handleFileInput}
disabled={disabled}
/>
{selectedFile ? (
<div className="border border-border rounded-lg p-4 bg-card/50 backdrop-blur-sm">
<div className="flex items-start gap-4">
<div className="p-2 bg-primary/10 rounded-lg">
<FileImage className="h-6 w-6 text-primary" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<p className="text-sm font-semibold text-foreground truncate" title={selectedFile.name}>
{selectedFile.name}
</p>
<Button
variant="ghost"
size="icon-xs"
onClick={onFileRemove}
disabled={disabled}
className="rounded-full hover:bg-destructive/10 hover:text-destructive"
>
<X className="h-3.5 w-3.5" />
</Button>
</div>
<div className="mt-2 flex gap-4 text-[10px] text-muted-foreground uppercase tracking-wider font-bold">
<div className="flex items-center gap-1.5">
<HardDrive className="h-3 w-3" />
<span>{(selectedFile.size / 1024).toFixed(1)} KB</span>
</div>
{dimensions && (
<div className="flex items-center gap-1.5">
<FileImage className="h-3 w-3" />
<span>{dimensions}</span>
</div>
)}
</div>
</div>
</div>
</div>
) : (
<div
onClick={handleClick}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={cn(
'border-2 border-dashed rounded-xl p-10 text-center cursor-pointer transition-all duration-300',
'hover:border-primary/50 hover:bg-primary/5',
{
'border-primary bg-primary/10 scale-[0.98]': isDragging,
'border-border bg-muted/30': !isDragging,
'opacity-50 cursor-not-allowed': disabled,
}
)}
>
<div className="bg-primary/10 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
<Upload className="h-8 w-8 text-primary" />
</div>
<p className="text-sm font-semibold text-foreground mb-1">
Drop icon source here
</p>
<p className="text-xs text-muted-foreground">
Recommended: 512x512 PNG or SVG
</p>
</div>
)}
</div>
);
}