71 lines
1.8 KiB
TypeScript
71 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { Suspense } from "react";
|
|
import Image from "next/image";
|
|
import { Canvas } from "@react-three/fiber";
|
|
import { OrbitControls, useGLTF, Center, Environment } from "@react-three/drei";
|
|
import { motion } from "framer-motion";
|
|
|
|
interface ProductViewerProps {
|
|
modelUrl?: string;
|
|
images?: string[];
|
|
autoRotate?: boolean;
|
|
}
|
|
|
|
// 3D Model Component
|
|
function Model({ modelUrl }: { modelUrl: string }) {
|
|
const { scene } = useGLTF(modelUrl);
|
|
return (
|
|
<Center>
|
|
<primitive object={scene} />
|
|
</Center>
|
|
);
|
|
}
|
|
|
|
function ModelCanvas({ modelUrl }: { modelUrl: string }) {
|
|
return (
|
|
<Canvas camera={{ position: [0, 0, 5], fov: 45 }}>
|
|
<Suspense fallback={null}>
|
|
<Model modelUrl={modelUrl} />
|
|
<OrbitControls autoRotate={true} autoRotateSpeed={4} />
|
|
<Environment preset="studio" />
|
|
</Suspense>
|
|
</Canvas>
|
|
);
|
|
}
|
|
|
|
export function ProductViewer({
|
|
modelUrl,
|
|
images = [],
|
|
autoRotate = true,
|
|
}: ProductViewerProps) {
|
|
const [primaryImage] = images;
|
|
|
|
return (
|
|
<div className="w-full h-full rounded-lg overflow-hidden bg-gray-100">
|
|
{modelUrl ? (
|
|
<div className="w-full h-full min-h-96">
|
|
<ModelCanvas modelUrl={modelUrl} />
|
|
</div>
|
|
) : primaryImage ? (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
className="relative w-full h-full aspect-square"
|
|
>
|
|
<Image
|
|
src={primaryImage}
|
|
alt="Product"
|
|
fill
|
|
className="object-cover"
|
|
/>
|
|
</motion.div>
|
|
) : (
|
|
<div className="w-full h-96 flex items-center justify-center bg-gray-200">
|
|
<span className="text-gray-500">No preview available</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|