"use client";
import { useState, useCallback } from "react";
import {
Crop,
ZoomIn,
ZoomOut,
RotateCcw,
RotateCw,
Square,
RectangleHorizontal,
RectangleVertical,
Circle,
Check,
RefreshCw,
Upload,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Slider } from "@/components/ui/slider";
import { Label } from "@/components/ui/label";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
type AspectRatio = {
id: string;
label: string;
value: number | null;
icon: React.ElementType;
};
const aspectRatios: AspectRatio[] = [
{ id: "free", label: "Free", value: null, icon: Crop },
{ id: "square", label: "1:1", value: 1, icon: Square },
{ id: "portrait", label: "4:5", value: 4 / 5, icon: RectangleVertical },
{ id: "landscape", label: "16:9", value: 16 / 9, icon: RectangleHorizontal },
{ id: "circle", label: "Circle", value: 1, icon: Circle },
];
export const title = "React Dialog Block Image Crop";
export default function DialogImageCrop() {
const [open, setOpen] = useState(false);
const [zoom, setZoom] = useState(1);
const [rotation, setRotation] = useState(0);
const [aspectRatio, setAspectRatio] = useState("square");
const [isApplying, setIsApplying] = useState(false);
// Simulated crop area (in real app, this would come from a cropping library)
const [cropArea, setCropArea] = useState({ x: 50, y: 50, width: 200, height: 200 });
const selectedAspect = aspectRatios.find((a) => a.id === aspectRatio);
const isCircle = aspectRatio === "circle";
const handleZoomChange = (value: number[]) => {
setZoom(value[0]);
};
const handleRotate = (direction: "cw" | "ccw") => {
setRotation((prev) => {
const newRotation = direction === "cw" ? prev + 90 : prev - 90;
return newRotation % 360;
});
};
const handleReset = () => {
setZoom(1);
setRotation(0);
setAspectRatio("square");
};
const handleApply = async () => {
setIsApplying(true);
// Simulate crop processing
await new Promise((resolve) => setTimeout(resolve, 1000));
setIsApplying(false);
setOpen(false);
};
const handleClose = () => {
setOpen(false);
setTimeout(() => {
handleReset();
}, 200);
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<Crop className="mr-2 h-4 w-4" />
Crop Image
</Button>
</DialogTrigger>
</div>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Crop className="h-5 w-5" />
Crop Image
</DialogTitle>
<DialogDescription>
Adjust the crop area, zoom, and rotation
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Crop Preview Area */}
<div className="relative aspect-square w-full overflow-hidden rounded-lg border bg-muted">
{/* Simulated image with crop overlay */}
<div
className="absolute inset-0 flex items-center justify-center"
style={{
transform: `scale(${zoom}) rotate(${rotation}deg)`,
transition: "transform 0.2s ease-out",
}}
>
{/* Placeholder for actual image */}
<div className="h-64 w-64 bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center text-muted-foreground">
<Upload className="h-12 w-12" />
</div>
</div>
{/* Crop overlay */}
<div className="absolute inset-0 pointer-events-none">
{/* Dark overlay */}
<div className="absolute inset-0 bg-black/50" />
{/* Crop window */}
<div
className={cn(
"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 border-2 border-white bg-transparent",
isCircle ? "rounded-full" : "rounded-sm"
)}
style={{
width: selectedAspect?.value
? selectedAspect.value >= 1
? "60%"
: `${60 * selectedAspect.value}%`
: "60%",
aspectRatio: selectedAspect?.value || "auto",
boxShadow: "0 0 0 9999px rgba(0,0,0,0.5)",
}}
>
{/* Grid lines */}
{!isCircle && (
<>
<div className="absolute left-1/3 top-0 bottom-0 w-px bg-white/30" />
<div className="absolute left-2/3 top-0 bottom-0 w-px bg-white/30" />
<div className="absolute top-1/3 left-0 right-0 h-px bg-white/30" />
<div className="absolute top-2/3 left-0 right-0 h-px bg-white/30" />
</>
)}
{/* Corner handles */}
<div className="absolute -left-1 -top-1 h-3 w-3 border-l-2 border-t-2 border-white" />
<div className="absolute -right-1 -top-1 h-3 w-3 border-r-2 border-t-2 border-white" />
<div className="absolute -left-1 -bottom-1 h-3 w-3 border-l-2 border-b-2 border-white" />
<div className="absolute -right-1 -bottom-1 h-3 w-3 border-r-2 border-b-2 border-white" />
</div>
</div>
</div>
{/* Aspect Ratio Selection */}
<div className="space-y-2">
<Label>Aspect Ratio</Label>
<ToggleGroup
type="single"
value={aspectRatio}
onValueChange={(value) => value && setAspectRatio(value)}
className="flex flex-wrap justify-start gap-1"
>
{aspectRatios.map((ratio) => {
const Icon = ratio.icon;
return (
<ToggleGroupItem
key={ratio.id}
value={ratio.id}
aria-label={ratio.label}
className="flex items-center gap-1.5 px-3"
>
<Icon className="h-4 w-4" />
<span className="text-xs">{ratio.label}</span>
</ToggleGroupItem>
);
})}
</ToggleGroup>
</div>
<Separator />
{/* Zoom Control */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label>Zoom</Label>
<span className="text-sm text-muted-foreground">
{Math.round(zoom * 100)}%
</span>
</div>
<div className="flex items-center gap-3">
<ZoomOut className="h-4 w-4 text-muted-foreground" />
<Slider
value={[zoom]}
onValueChange={handleZoomChange}
min={0.5}
max={3}
step={0.1}
className="flex-1"
/>
<ZoomIn className="h-4 w-4 text-muted-foreground" />
</div>
</div>
{/* Rotation Control */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label>Rotation</Label>
<span className="text-sm text-muted-foreground">{rotation}°</span>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={() => handleRotate("ccw")}
>
<RotateCcw className="h-4 w-4" />
</Button>
<Slider
value={[rotation]}
onValueChange={(value) => setRotation(value[0])}
min={-180}
max={180}
step={1}
className="flex-1"
/>
<Button
variant="outline"
size="icon"
onClick={() => handleRotate("cw")}
>
<RotateCw className="h-4 w-4" />
</Button>
</div>
</div>
</div>
<DialogFooter className="flex-col sm:flex-row gap-2">
<Button
variant="ghost"
onClick={handleReset}
className="sm:mr-auto"
disabled={isApplying}
>
<RefreshCw className="mr-2 h-4 w-4" />
Reset
</Button>
<Button variant="outline" onClick={handleClose} disabled={isApplying}>
Cancel
</Button>
<Button onClick={handleApply} disabled={isApplying}>
{isApplying ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
Applying...
</>
) : (
<>
<Check className="mr-2 h-4 w-4" />
Apply Crop
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}