"use client" ;
import { useState, useEffect } from "react" ;
import {
Link,
ExternalLink,
Check,
Loader2,
AlertCircle,
Globe,
Type,
MousePointerClick,
} from "lucide-react" ;
import { Button } from "@/components/ui/button" ;
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" ;
import { Separator } from "@/components/ui/separator" ;
import { cn } from "@/lib/utils" ;
const urlPattern = / ^ (https ? : \/\/ ) ? ( [\da-z.-] + ) \. ( [a-z.] {2,6} )( [/\w .-] * ) * \/ ?$ / ;
export const title = "React Dialog Block Insert Link" ;
export default function DialogLinkInsert () {
const [ open , setOpen ] = useState ( false );
const [ url , setUrl ] = useState ( "" );
const [ linkText , setLinkText ] = useState ( "" );
const [ openInNewTab , setOpenInNewTab ] = useState ( true );
const [ addNoFollow , setAddNoFollow ] = useState ( false );
const [ isInserting , setIsInserting ] = useState ( false );
const [ urlError , setUrlError ] = useState ( "" );
const isValidUrl = urlPattern. test (url) || url. startsWith ( "mailto:" ) || url. startsWith ( "tel:" );
useEffect (() => {
if (url && ! isValidUrl) {
setUrlError ( "Please enter a valid URL" );
} else {
setUrlError ( "" );
}
}, [url, isValidUrl]);
const handleInsert = async () => {
if ( ! url || ! isValidUrl) return ;
setIsInserting ( true );
await new Promise (( resolve ) => setTimeout (resolve, 800 ));
setIsInserting ( false );
setOpen ( false );
// Reset form
setUrl ( "" );
setLinkText ( "" );
setOpenInNewTab ( true );
setAddNoFollow ( false );
};
const formatUrl = ( input : string ) => {
if ( ! input) return "" ;
if (input. startsWith ( "http://" ) || input. startsWith ( "https://" ) ||
input. startsWith ( "mailto:" ) || input. startsWith ( "tel:" )) {
return input;
}
return `https://${ input }` ;
};
const displayUrl = url ? formatUrl (url) : "" ;
return (
< Dialog open = {open} onOpenChange = {setOpen}>
< div className = "flex min-h-[350px] items-center justify-center" >
< DialogTrigger asChild >
< Button variant = "outline" >
< Link className = "mr-2 h-4 w-4" />
Insert Link
</ Button >
</ DialogTrigger >
</ div >
< DialogContent className = "sm:max-w-md" >
< DialogHeader >
< DialogTitle className = "flex items-center gap-2" >
< Link className = "h-5 w-5" />
Insert Link
</ DialogTitle >
< DialogDescription >
Add a hyperlink to your content.
</ DialogDescription >
</ DialogHeader >
< div className = "space-y-4 py-4" >
{ /* URL Input */ }
< div className = "space-y-2" >
< Label htmlFor = "url" className = "flex items-center gap-2" >
< Globe className = "h-3.5 w-3.5 text-muted-foreground" />
URL
</ Label >
< Input
id = "url"
type = "url"
placeholder = "https://example.com"
value = {url}
onChange = {( e ) => setUrl (e.target.value)}
className = { cn (urlError && "border-destructive" )}
/>
{urlError && (
< p className = "text-xs text-destructive flex items-center gap-1" >
< AlertCircle className = "h-3 w-3" />
{urlError}
</ p >
)}
</ div >
{ /* Link Text */ }
< div className = "space-y-2" >
< Label htmlFor = "text" className = "flex items-center gap-2" >
< Type className = "h-3.5 w-3.5 text-muted-foreground" />
Link Text
</ Label >
< Input
id = "text"
placeholder = "Click here (optional)"
value = {linkText}
onChange = {( e ) => setLinkText (e.target.value)}
/>
< p className = "text-xs text-muted-foreground" >
Leave empty to use the URL as link text
</ p >
</ div >
< Separator />
{ /* Options */ }
< div className = "space-y-3" >
< Label className = "text-sm font-medium" >Options</ Label >
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-2" >
< ExternalLink className = "h-4 w-4 text-muted-foreground" />
< div >
< p className = "text-sm" >Open in new tab</ p >
< p className = "text-xs text-muted-foreground" >
Adds target="_blank"
</ p >
</ div >
</ div >
< Switch
checked = {openInNewTab}
onCheckedChange = {setOpenInNewTab}
/>
</ div >
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-2" >
< MousePointerClick className = "h-4 w-4 text-muted-foreground" />
< div >
< p className = "text-sm" >Add nofollow</ p >
< p className = "text-xs text-muted-foreground" >
Adds rel="nofollow"
</ p >
</ div >
</ div >
< Switch
checked = {addNoFollow}
onCheckedChange = {setAddNoFollow}
/>
</ div >
</ div >
{ /* Preview */ }
{url && isValidUrl && (
<>
< Separator />
< div className = "space-y-2" >
< Label className = "text-sm font-medium" >Preview</ Label >
< div className = "rounded-lg border p-3" >
< a
href = "#"
onClick = {( e ) => e. preventDefault ()}
className = "text-primary hover:underline inline-flex items-center gap-1"
>
{linkText || displayUrl}
{openInNewTab && < ExternalLink className = "h-3 w-3" />}
</ a >
< p className = "text-xs text-muted-foreground mt-1 truncate" >
{displayUrl}
</ p >
</ div >
</ div >
</>
)}
</ div >
< DialogFooter >
< Button
variant = "outline"
onClick = {() => setOpen ( false )}
disabled = {isInserting}
>
Cancel
</ Button >
< Button
onClick = {handleInsert}
disabled = { ! url || ! isValidUrl || isInserting}
>
{isInserting ? (
<>
< Loader2 className = "mr-2 h-4 w-4 animate-spin" />
Inserting...
</>
) : (
<>
< Check className = "mr-2 h-4 w-4" />
Insert Link
</>
)}
</ Button >
</ DialogFooter >
</ DialogContent >
</ Dialog >
);
}