| import { useRef, useState } from "react"; |
|
|
| interface Props { |
| onChange: (files: File[]) => void; |
| max?: number; |
| existing?: number; |
| } |
|
|
| |
| |
| export default function PhotoUpload({ onChange, max = 8, existing = 0 }: Props) { |
| const [files, setFiles] = useState<File[]>([]); |
| const inputRef = useRef<HTMLInputElement>(null); |
| const room = Math.max(0, max - existing); |
|
|
| function addFiles(list: FileList | null) { |
| if (!list) return; |
| const next = [...files, ...Array.from(list)].slice(0, room); |
| setFiles(next); |
| onChange(next); |
| } |
|
|
| function removeAt(i: number) { |
| const next = files.filter((_, idx) => idx !== i); |
| setFiles(next); |
| onChange(next); |
| } |
|
|
| return ( |
| <div> |
| <div className="flex flex-wrap gap-3 mb-3"> |
| {files.map((f, i) => ( |
| <div key={i} className="relative"> |
| <img |
| src={URL.createObjectURL(f)} |
| alt={`preview ${i + 1}`} |
| className="h-24 w-24 rounded-md object-cover border border-gray-200" |
| /> |
| <button |
| type="button" |
| onClick={() => removeAt(i)} |
| className="absolute -top-2 -right-2 h-6 w-6 rounded-full bg-red-600 text-white text-xs" |
| aria-label="Remove photo" |
| > |
| × |
| </button> |
| </div> |
| ))} |
| </div> |
| <input |
| ref={inputRef} |
| type="file" |
| accept="image/*" |
| capture="environment" |
| multiple |
| className="hidden" |
| onChange={(e) => addFiles(e.target.files)} |
| /> |
| <button |
| type="button" |
| className="btn-secondary" |
| onClick={() => inputRef.current?.click()} |
| disabled={existing + files.length >= max} |
| > |
| Add photos ({existing + files.length}/{max}) |
| </button> |
| <p className="text-xs text-gray-500 mt-1"> |
| On a phone this opens your camera. Clear, well-lit photos of the whole dog match best. |
| </p> |
| </div> |
| ); |
| } |
|
|