NODEDC_DESIGN_GUIDELINE/packages/ui-react/src/MediaSourceField.tsx

119 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useId, type ChangeEvent, type ReactNode } from "react";
import { Icon } from "./Icon.js";
export type MediaSource = "file" | "url";
export type MediaPreviewKind = "image" | "gif" | "video";
export interface MediaSourceFieldProps {
label: string;
kindLabel?: string;
source: MediaSource;
url: string;
fileName?: string | null;
uploading?: boolean;
previewSrc?: string | null;
previewKind?: MediaPreviewKind | null;
accept?: string;
fileButtonLabel?: string;
emptyFileLabel?: string;
path?: ReactNode;
hint?: ReactNode;
error?: ReactNode;
onSourceChange: (source: MediaSource) => void;
onUrlChange: (url: string) => void;
onFileChange: (file?: File) => void | Promise<void>;
}
function inferredPreviewKind(src: string): MediaPreviewKind {
if (/\.(mp4|webm|mov|m4v|avi|mkv)(\?.*)?$/i.test(src)) return "video";
if (/\.gif(\?.*)?$/i.test(src)) return "gif";
return "image";
}
function MediaPreview({ src, kind }: { src: string; kind?: MediaPreviewKind | null }) {
const resolvedKind = kind ?? inferredPreviewKind(src);
if (resolvedKind === "video") return <video src={src} autoPlay loop muted playsInline />;
return <img src={src} alt="" />;
}
export function MediaSourceField({
label,
kindLabel = "media",
source,
url,
fileName,
uploading = false,
previewSrc,
previewKind,
accept = "image/*,video/*",
fileButtonLabel = "Выберите файл",
emptyFileLabel = "Файл не выбран",
path,
hint,
error,
onSourceChange,
onUrlChange,
onFileChange,
}: MediaSourceFieldProps) {
const inputId = useId();
const displayFileName = uploading ? "Сохраняем в storage..." : (fileName || emptyFileLabel);
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
void onFileChange(event.currentTarget.files?.[0]);
event.currentTarget.value = "";
};
return (
<div className="nodedc-media-field">
<div className="nodedc-media-field__label-row">
<span className="nodedc-media-field__label">{label}</span>
<span className="nodedc-media-field__kind">{kindLabel}</span>
</div>
<div className="nodedc-media-control" data-source={source}>
<div className="nodedc-media-file" hidden={source !== "file"} data-nodedc-media-source-panel="file">
<label className="nodedc-media-file__button" htmlFor={inputId}>{fileButtonLabel}</label>
<span className="nodedc-media-file__name" title={displayFileName}>{displayFileName}</span>
<input id={inputId} type="file" accept={accept} disabled={uploading} onChange={handleFileChange} />
</div>
<input
className="nodedc-media-url"
type="url"
value={url}
hidden={source !== "url"}
data-nodedc-media-source-panel="url"
placeholder="https://..."
autoComplete="off"
aria-label={`${label}: внешняя ссылка`}
onChange={(event) => onUrlChange(event.target.value)}
/>
<div className="nodedc-media-source-switch" aria-label={`${label}: источник`}>
<button
type="button"
className="nodedc-media-source-button"
data-active={source === "file" ? "true" : undefined}
data-nodedc-media-source-option="file"
aria-label="Файл с диска"
aria-pressed={source === "file"}
onClick={() => onSourceChange("file")}
>HD</button>
<button
type="button"
className="nodedc-media-source-button"
data-active={source === "url" ? "true" : undefined}
data-nodedc-media-source-option="url"
aria-label="Внешняя ссылка"
aria-pressed={source === "url"}
onClick={() => onSourceChange("url")}
>URL</button>
</div>
<div className="nodedc-media-preview" aria-hidden="true">
{previewSrc ? <MediaPreview src={previewSrc} kind={previewKind} /> : <Icon name="image" size={16} />}
</div>
</div>
{path ? <span className="nodedc-media-field__path">{path}</span> : null}
{hint ? <span className="nodedc-media-field__hint">{hint}</span> : null}
{error ? <span className="nodedc-media-field__error" role="alert">{error}</span> : null}
</div>
);
}