/** * Renders an image that is also its own upload button, for a dump's thumbnail * and a playlist's cover. * * @module */ import { useRef } from "react"; import { t } from "@lingui/core/macro"; /** * Passed to {@link ImagePicker} by an edit page, which uploads the file and * then passes the new image, or a local preview of it, as `src`. */ export interface ImagePickerProps { /** Shows a "+" placeholder when null. */ src: string | null; alt?: string; /** Sets the side, in pixels. Without it, `className` sizes the picker. */ size?: number; /** In pixels when a number, or any CSS length, such as a radius token. */ borderRadius?: number | string; /** Called with the file picked. The same file can be picked again. */ onChange: (file: File) => void; /** Disables the picker and shows "…" while the caller uploads. */ uploading?: boolean; /** Replaces the default types, which are JPEG, PNG, GIF and WebP. */ accept?: string; className?: string; } /** * Renders the image, or a placeholder, which opens the file picker on a click, * Enter or Space. The picker only reads the file, and uploads nothing itself. */ export function ImagePicker({ src, alt = "", size, borderRadius = 8, onChange, uploading = false, accept = "image/jpeg,image/png,image/gif,image/webp", className, }: ImagePickerProps): React.JSX.Element { const inputRef = useRef(null); const sizeStyle = size === undefined ? { borderRadius } : { width: size, height: size, borderRadius }; const pick = () => { if (!uploading) inputRef.current?.click(); }; const handleChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) onChange(file); // Or picking the same file again would fire no change. e.target.value = ""; }; return (
{ if (e.key !== "Enter" && e.key !== " ") return; // Or Space would also scroll the page. e.preventDefault(); pick(); }} > {src ? ( {alt} ) : (
+
)}
{uploading ? "…" : "✎"}
); }