feat: add a image comfirm feature

This commit is contained in:
dayuan.jiang
2025-11-10 09:17:11 +09:00
parent c1923e84da
commit a3ada79a65

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import React, { useEffect } from "react"; import React, { useEffect, useState } from "react";
import Image from "next/image"; import Image from "next/image";
import { X } from "lucide-react"; import { X } from "lucide-react";
@@ -10,6 +10,8 @@ interface FilePreviewListProps {
} }
export function FilePreviewList({ files, onRemoveFile }: FilePreviewListProps) { export function FilePreviewList({ files, onRemoveFile }: FilePreviewListProps) {
const [selectedImage, setSelectedImage] = useState<string | null>(null);
// Cleanup object URLs on unmount // Cleanup object URLs on unmount
useEffect(() => { useEffect(() => {
const objectUrls = files const objectUrls = files
@@ -24,34 +26,67 @@ export function FilePreviewList({ files, onRemoveFile }: FilePreviewListProps) {
if (files.length === 0) return null; if (files.length === 0) return null;
return ( return (
<div className="flex flex-wrap gap-2 mt-2 p-2 bg-muted/50 rounded-md"> <>
{files.map((file, index) => ( <div className="flex flex-wrap gap-2 mt-2 p-2 bg-muted/50 rounded-md">
<div key={file.name + index} className="relative group"> {files.map((file, index) => {
<div className="w-20 h-20 border rounded-md overflow-hidden bg-muted"> const imageUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : null;
{file.type.startsWith("image/") ? ( return (
<Image <div key={file.name + index} className="relative group">
src={URL.createObjectURL(file)} <div
alt={file.name} className="w-20 h-20 border rounded-md overflow-hidden bg-muted cursor-pointer"
width={80} onClick={() => imageUrl && setSelectedImage(imageUrl)}
height={80} >
className="object-cover w-full h-full" {file.type.startsWith("image/") ? (
/> <Image
) : ( src={imageUrl!}
<div className="flex items-center justify-center h-full text-xs text-center p-1"> alt={file.name}
{file.name} width={80}
height={80}
className="object-cover w-full h-full"
/>
) : (
<div className="flex items-center justify-center h-full text-xs text-center p-1">
{file.name}
</div>
)}
</div> </div>
)} <button
</div> type="button"
onClick={() => onRemoveFile(file)}
className="absolute -top-2 -right-2 bg-destructive rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity"
aria-label="Remove file"
>
<X className="h-3 w-3" />
</button>
</div>
);
})}
</div>
{/* Image Modal/Lightbox */}
{selectedImage && (
<div
className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4"
onClick={() => setSelectedImage(null)}
>
<button <button
type="button" className="absolute top-4 right-4 bg-white rounded-full p-2 hover:bg-gray-200 transition-colors"
onClick={() => onRemoveFile(file)} onClick={() => setSelectedImage(null)}
className="absolute -top-2 -right-2 bg-destructive rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Close"
aria-label="Remove file"
> >
<X className="h-3 w-3" /> <X className="h-6 w-6" />
</button> </button>
<div className="relative max-w-7xl max-h-[90vh] w-full h-full">
<Image
src={selectedImage}
alt="Preview"
fill
className="object-contain"
onClick={(e) => e.stopPropagation()}
/>
</div>
</div> </div>
))} )}
</div> </>
); );
} }