157 lines
4.5 KiB
TypeScript
157 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
/* eslint-disable @next/next/no-img-element */
|
|
|
|
import type { MediaKind } from "@prisma/client";
|
|
|
|
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;
|
|
};
|
|
|
|
type MediaFieldPickerProps = {
|
|
title: string;
|
|
value: MediaFieldState;
|
|
onChange: (nextValue: MediaFieldState) => void;
|
|
options: MediaOption[];
|
|
inputName: string;
|
|
fileFieldName: string;
|
|
fileLabel?: string;
|
|
externalLabel?: string;
|
|
libraryLabel?: string;
|
|
accept?: string;
|
|
};
|
|
|
|
const modeOptions: Array<MediaFieldState["mode"]> = ["upload", "external", "library"];
|
|
|
|
export function MediaFieldPicker({
|
|
title,
|
|
value,
|
|
onChange,
|
|
options,
|
|
inputName,
|
|
fileFieldName,
|
|
fileLabel,
|
|
externalLabel,
|
|
libraryLabel,
|
|
accept,
|
|
}: MediaFieldPickerProps) {
|
|
const filteredOptions = options.filter((option) => option.kind === value.kind);
|
|
const selectedOption = filteredOptions.find((option) => option.id === value.assetId) ?? null;
|
|
const previewUrl =
|
|
value.mode === "library"
|
|
? selectedOption?.url ?? ""
|
|
: value.mode === "external"
|
|
? value.url
|
|
: "";
|
|
|
|
return (
|
|
<div className="space-y-3 rounded-surface border border-border p-4">
|
|
<div className="space-y-2">
|
|
<Label>{title}</Label>
|
|
<div className="flex flex-wrap gap-2">
|
|
{modeOptions.map((mode) => (
|
|
<button
|
|
key={mode}
|
|
type="button"
|
|
onClick={() =>
|
|
onChange({
|
|
...value,
|
|
mode,
|
|
assetId: mode === "library" ? value.assetId : "",
|
|
url: mode === "external" ? value.url : "",
|
|
})
|
|
}
|
|
className={cn(
|
|
"rounded-pill border px-4 py-2 text-sm transition-colors",
|
|
value.mode === mode
|
|
? "border-border-strong bg-foreground text-background"
|
|
: "border-border bg-background text-foreground/75 hover:border-border-strong hover:text-foreground",
|
|
)}
|
|
>
|
|
{mode}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<input
|
|
type="hidden"
|
|
name={inputName}
|
|
value={JSON.stringify({
|
|
mode: value.mode,
|
|
assetId: value.assetId,
|
|
url: value.url,
|
|
label: value.label,
|
|
kind: value.kind,
|
|
})}
|
|
/>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Label</Label>
|
|
<Input
|
|
value={value.label}
|
|
onChange={(event) => onChange({ ...value, label: event.target.value })}
|
|
placeholder="Homepage Hero"
|
|
/>
|
|
</div>
|
|
|
|
{value.mode === "upload" ? (
|
|
<div className="space-y-2">
|
|
<Label>{fileLabel ?? fileFieldName}</Label>
|
|
<Input name={fileFieldName} type="file" accept={accept} />
|
|
</div>
|
|
) : null}
|
|
|
|
{value.mode === "external" ? (
|
|
<div className="space-y-2">
|
|
<Label>{externalLabel ?? "External URL"}</Label>
|
|
<Input
|
|
value={value.url}
|
|
onChange={(event) => onChange({ ...value, url: event.target.value })}
|
|
placeholder="https://example.com/image.jpg"
|
|
/>
|
|
</div>
|
|
) : null}
|
|
|
|
{value.mode === "library" ? (
|
|
<div className="space-y-2">
|
|
<Label>{libraryLabel ?? "Media Library"}</Label>
|
|
<select
|
|
value={value.assetId}
|
|
onChange={(event) => onChange({ ...value, assetId: event.target.value })}
|
|
className="flex h-11 w-full rounded-pill border border-border bg-background px-4 text-sm text-foreground shadow-sm"
|
|
>
|
|
<option value="">Select media</option>
|
|
{filteredOptions.map((option) => (
|
|
<option key={option.id} value={option.id}>
|
|
{option.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
) : null}
|
|
|
|
{previewUrl ? (
|
|
value.kind === "IMAGE" ? (
|
|
<div className="overflow-hidden rounded-nested border border-border bg-surface-1">
|
|
<img src={previewUrl} alt={value.label || title} className="h-40 w-full object-cover" />
|
|
</div>
|
|
) : (
|
|
<div className="rounded-nested border border-border bg-surface-1 px-4 py-3 text-sm text-muted-foreground">
|
|
{previewUrl}
|
|
</div>
|
|
)
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|