155 lines
4.7 KiB
TypeScript
155 lines
4.7 KiB
TypeScript
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { cn } from "@/lib/utils";
|
|
import { usePage } from "@inertiajs/react";
|
|
import { UploadIcon } from "lucide-react";
|
|
import {
|
|
ChangeEvent,
|
|
MouseEvent,
|
|
useEffect,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
|
|
type Props = {
|
|
width: number | string;
|
|
height: number;
|
|
|
|
className?: string;
|
|
name: string;
|
|
label: string;
|
|
value?: string;
|
|
id?: string;
|
|
required?: boolean;
|
|
disabled?: boolean;
|
|
parentErrors?: Record<string, string> | null;
|
|
setData: (name: string, value: string) => void;
|
|
};
|
|
|
|
export default function SimpleVideoUpload(props: Props) {
|
|
const {
|
|
width,
|
|
height,
|
|
className,
|
|
name,
|
|
label,
|
|
id,
|
|
required,
|
|
disabled,
|
|
parentErrors,
|
|
setData,
|
|
} = props;
|
|
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const { errors } = usePage<{ errors: Record<string, string> }>().props;
|
|
const localErrors = parentErrors ?? errors ?? {};
|
|
|
|
const [source, setSource] = useState<string | null>(null);
|
|
const [fileName, setFileName] = useState<string | null>(null);
|
|
|
|
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
const url = URL.createObjectURL(file);
|
|
setSource(url);
|
|
setFileName(file.name);
|
|
|
|
// Important pour Inertia
|
|
setData(name, file as unknown as string);
|
|
};
|
|
|
|
const handleChoose = (event: React.MouseEvent<HTMLElement>) => {
|
|
event.preventDefault();
|
|
inputRef.current?.click();
|
|
};
|
|
|
|
// Nettoyage mémoire
|
|
useEffect(() => {
|
|
return () => {
|
|
if (source) {
|
|
URL.revokeObjectURL(source);
|
|
}
|
|
};
|
|
}, [source]);
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"flex flex-col gap-2 w-full",
|
|
className
|
|
)}
|
|
>
|
|
<Label htmlFor={name} className="font-medium">
|
|
{label}
|
|
{required && <span className="text-destructive ml-1">*</span>}
|
|
</Label>
|
|
|
|
{/* Input caché */}
|
|
<Input
|
|
id={id}
|
|
name={name}
|
|
type="file"
|
|
accept=".mov,.mp4"
|
|
ref={inputRef}
|
|
onChange={handleFileChange}
|
|
required={required}
|
|
disabled={disabled}
|
|
className="hidden"
|
|
/>
|
|
|
|
{/* Zone preview */}
|
|
<div
|
|
className={cn(
|
|
"relative text-sm flex justify-center border border-dashed overflow-hidden whitespace-normal [&_svg]:!size-auto [&_svg]:!shrink text-muted-foreground w-full h-36",
|
|
{
|
|
"border-destructive": localErrors[name],
|
|
"cursor-pointer": !disabled,
|
|
}
|
|
)}
|
|
style={{ width }}
|
|
onClick={!disabled ? handleChoose : undefined}
|
|
>
|
|
{!source ? (
|
|
<div className="flex flex-col justify-center items-center">
|
|
<UploadIcon />
|
|
<p className="text-center break-normal w-full mt-2">
|
|
Cliquer pour sélectionner une vidéo
|
|
</p>
|
|
<small>
|
|
Format accepté : .mp4, .mov
|
|
</small>
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-col gap-3 w-full items-center">
|
|
<video
|
|
className="rounded-md shadow-md max-w-full object-contain"
|
|
style={{ maxHeight: height }}
|
|
controls
|
|
src={source}
|
|
/>
|
|
|
|
<div className="text-sm text-muted-foreground text-center break-all">
|
|
{fileName}
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={handleChoose}
|
|
className="text-xs px-3 py-1 rounded-md bg-primary text-primary-foreground hover:opacity-90 transition"
|
|
>
|
|
Changer la vidéo
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Erreur */}
|
|
{localErrors[name] && (
|
|
<p className="text-sm text-destructive">
|
|
{localErrors[name]}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|