"use client"; /* eslint-disable @next/next/no-img-element */ import type { MediaKind } from "@prisma/client"; import { Check, Link2, Search, Type, Upload } from "lucide-react"; import { useEffect, useId, useMemo, useRef, useState } from "react"; import type { MediaOption } from "@/lib/media"; import { cn } from "@/lib/utils"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; export type MediaFieldState = { mode: "upload" | "external" | "library"; assetId: string; url: string; label: string; kind: MediaKind; isCleared?: boolean; }; type MediaFieldPickerProps = { title: string; value: MediaFieldState; onChange: (nextValue: MediaFieldState) => void; options: MediaOption[]; hasInitialValue?: boolean; inputName: string; fileFieldName: string; fileLabel?: string; externalLabel?: string; libraryLabel?: string; accept?: string; allowExternal?: boolean; allowClear?: boolean; clearLabel?: string; emptyValue?: Partial; }; export function MediaFieldPicker({ title, value, onChange, options, hasInitialValue = false, inputName, fileFieldName, fileLabel, externalLabel, libraryLabel, accept, allowExternal = true, allowClear = false, clearLabel = "Remove", emptyValue, }: MediaFieldPickerProps) { const hiddenInputRef = useRef(null); const searchId = useId(); const [libraryQuery, setLibraryQuery] = useState(""); const modeOptions: Array = allowExternal ? ["upload", "external", "library"] : ["upload", "library"]; const filteredOptions = options.filter((option) => option.kind === value.kind); const selectedOption = filteredOptions.find((option) => option.id === value.assetId) ?? null; const visibleLibraryOptions = useMemo(() => { const normalizedQuery = libraryQuery.trim().toLowerCase(); if (!normalizedQuery) { return filteredOptions; } return filteredOptions.filter((option) => option.label.toLowerCase().includes(normalizedQuery)); }, [filteredOptions, libraryQuery]); const resolvedLabel = value.mode === "library" ? selectedOption?.label ?? value.label : value.label; const serializedValue = JSON.stringify({ mode: value.mode, assetId: value.assetId, url: value.url, label: resolvedLabel, kind: value.kind, }); const previewUrl = value.isCleared ? "" : value.mode === "library" ? selectedOption?.url ?? "" : value.mode === "external" ? value.url : "" ; const hasCurrentValue = !value.isCleared && ((value.mode === "library" && value.assetId.trim() !== "") || (value.mode === "external" && value.url.trim() !== "")); const canClear = allowClear && (hasCurrentValue || (hasInitialValue && !value.isCleared)); useEffect(() => { const hiddenInput = hiddenInputRef.current; if (!hiddenInput) { return; } hiddenInput.dispatchEvent(new Event("input", { bubbles: true })); hiddenInput.dispatchEvent(new Event("change", { bubbles: true })); }, [serializedValue]); return (
{modeOptions.map((mode) => ( ))} {canClear ? ( ) : null}
{value.mode !== "library" ? (
onChange({ ...value, label: event.target.value, isCleared: false })} placeholder="Label" className="pl-9" />
) : null} {value.mode === "upload" ? (
) : null} {value.mode === "external" ? (
onChange({ ...value, url: event.target.value, isCleared: false })} placeholder={externalLabel ?? "External URL"} className="pl-9" />
) : null} {value.mode === "library" ? (
setLibraryQuery(event.target.value)} className="pl-9" placeholder="Search media" />
{visibleLibraryOptions.length > 0 ? ( visibleLibraryOptions.map((option) => { const isActive = option.id === value.assetId; return ( ); }) ) : (
No media found.
)}
) : null} {previewUrl ? ( value.kind === "IMAGE" ? (
{value.label
) : (
{previewUrl}
) ) : null}
); }