"use client";
import { useState, useMemo, useEffect } from "react";
import {
Video,
Link,
Copy,
Check,
AlertCircle,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
type Platform = "youtube" | "vimeo" | "unknown";
type VideoInfo = {
platform: Platform;
videoId: string;
embedUrl: string;
} | null;
const platformConfig = {
youtube: {
name: "YouTube",
color: "text-red-500",
regex: /(?:youtube\.com\/(?:watch\?v=|embed\/|v\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/,
embedUrl: (id: string, options: EmbedOptions) => {
const params = new URLSearchParams();
if (options.autoplay) params.set("autoplay", "1");
if (options.loop) params.set("loop", "1");
if (options.muted) params.set("mute", "1");
if (!options.controls) params.set("controls", "0");
if (options.startTime) params.set("start", options.startTime.toString());
const query = params.toString();
return `https://www.youtube.com/embed/${id}${query ? "?" + query : ""}`;
},
},
vimeo: {
name: "Vimeo",
color: "text-blue-500",
regex: /vimeo\.com\/(?:video\/)?(\d+)/,
embedUrl: (id: string, options: EmbedOptions) => {
const params = new URLSearchParams();
if (options.autoplay) params.set("autoplay", "1");
if (options.loop) params.set("loop", "1");
if (options.muted) params.set("muted", "1");
const query = params.toString();
return `https://player.vimeo.com/video/${id}${query ? "?" + query : ""}`;
},
},
};
type EmbedOptions = {
autoplay: boolean;
loop: boolean;
muted: boolean;
controls: boolean;
startTime: number;
};
const defaultOptions: EmbedOptions = {
autoplay: false,
loop: false,
muted: false,
controls: true,
startTime: 0,
};
export const title = "React Dialog Block Embed Video";
export default function DialogEmbedVideo() {
const [open, setOpen] = useState(false);
const [url, setUrl] = useState("");
const [options, setOptions] = useState<EmbedOptions>(defaultOptions);
const [copied, setCopied] = useState(false);
const videoInfo = useMemo((): VideoInfo => {
if (!url) return null;
for (const [platform, config] of Object.entries(platformConfig)) {
const match = url.match(config.regex);
if (match) {
return {
platform: platform as Platform,
videoId: match[1],
embedUrl: config.embedUrl(match[1], options),
};
}
}
return null;
}, [url, options]);
const embedCode = useMemo(() => {
if (!videoInfo) return "";
return `<iframe
width="560"
height="315"
src="${videoInfo.embedUrl}"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>`;
}, [videoInfo]);
const handleCopyCode = () => {
navigator.clipboard.writeText(embedCode);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const handleOptionChange = (key: keyof EmbedOptions, value: boolean | number) => {
setOptions((prev) => ({ ...prev, [key]: value }));
};
const handleClose = () => {
setOpen(false);
setTimeout(() => {
setUrl("");
setOptions(defaultOptions);
setCopied(false);
}, 200);
};
const platformInfo = videoInfo ? platformConfig[videoInfo.platform as keyof typeof platformConfig] : null;
return (
<Dialog open={open} onOpenChange={setOpen}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<Video className="mr-2 h-4 w-4" />
Embed Video
</Button>
</DialogTrigger>
</div>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Video className="h-5 w-5" />
Embed Video
</DialogTitle>
<DialogDescription>
Paste a video URL from YouTube or Vimeo to embed.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="video-url">Video URL</Label>
<div className="relative">
<Link className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="video-url"
placeholder="https://youtube.com/watch?v=... or https://vimeo.com/..."
value={url}
onChange={(e) => setUrl(e.target.value)}
className="pl-9"
/>
</div>
{url && !videoInfo && (
<p className="text-xs text-destructive flex items-center gap-1">
<AlertCircle className="h-3 w-3" />
Could not detect video from URL
</p>
)}
{videoInfo && platformInfo && (
<div className="flex items-center gap-2">
<Badge variant="secondary" className={platformInfo.color}>
{platformInfo.name}
</Badge>
<span className="text-xs text-muted-foreground">
Video ID: {videoInfo.videoId}
</span>
</div>
)}
</div>
{videoInfo && (
<>
<div className="aspect-video rounded-lg border overflow-hidden">
<iframe
src={videoInfo.embedUrl}
className="w-full h-full"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
<div className="flex flex-wrap gap-x-6 gap-y-3">
<label className="flex items-center gap-2 text-sm">
<Switch
checked={options.autoplay}
onCheckedChange={(v) => handleOptionChange("autoplay", v)}
/>
Autoplay
</label>
<label className="flex items-center gap-2 text-sm">
<Switch
checked={options.loop}
onCheckedChange={(v) => handleOptionChange("loop", v)}
/>
Loop
</label>
<label className="flex items-center gap-2 text-sm">
<Switch
checked={options.muted}
onCheckedChange={(v) => handleOptionChange("muted", v)}
/>
Muted
</label>
<label className="flex items-center gap-2 text-sm">
<Switch
checked={options.controls}
onCheckedChange={(v) => handleOptionChange("controls", v)}
/>
Controls
</label>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Embed Code</span>
<Button size="sm" variant="outline" onClick={handleCopyCode}>
{copied ? (
<>
<Check className="mr-2 h-3 w-3" />
Copied
</>
) : (
<>
<Copy className="mr-2 h-3 w-3" />
Copy
</>
)}
</Button>
</div>
<pre className="rounded-lg border p-3 text-xs font-mono whitespace-pre-wrap break-all overflow-hidden">
{embedCode}
</pre>
</div>
</>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button onClick={handleClose} disabled={!videoInfo}>
Insert Video
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}