feat(web): add tabletop scene controls
Disable auto-rotate and enable panning on the setup view, add a fullscreen toggle, and convert the controls to iconify icon buttons.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Suspense, type ReactNode } from 'react';
|
||||
import { Suspense, useEffect, useRef, useState, type ReactNode } from 'react';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { Bounds, ContactShadows, OrbitControls, useProgress } from '@react-three/drei';
|
||||
import { Bloom, EffectComposer, Vignette } from '@react-three/postprocessing';
|
||||
@@ -12,11 +13,56 @@ import { Bloom, EffectComposer, Vignette } from '@react-three/postprocessing';
|
||||
* The camera is fitted to the bounds of the content on mount. `Bounds` sits
|
||||
* inside the Suspense boundary, so it only mounts once the (suspending) content
|
||||
* has loaded and its geometry is present.
|
||||
*
|
||||
* `autoRotate` and `enablePan` tune the orbit controls (a tabletop view, for
|
||||
* example, pans instead of rotating). `fullscreen` adds a toggle button that
|
||||
* expands the scene to the full screen.
|
||||
*/
|
||||
export default function Scene({ children }: { children: ReactNode }) {
|
||||
export default function Scene({
|
||||
children,
|
||||
autoRotate = true,
|
||||
enablePan = false,
|
||||
fullscreen = false,
|
||||
overlay,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
autoRotate?: boolean;
|
||||
enablePan?: boolean;
|
||||
fullscreen?: boolean;
|
||||
/** HTML rendered inside the scene container (e.g. controls), shown in fullscreen. */
|
||||
overlay?: ReactNode;
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = () => setIsFullscreen(!!document.fullscreenElement);
|
||||
document.addEventListener('fullscreenchange', onChange);
|
||||
return () => document.removeEventListener('fullscreenchange', onChange);
|
||||
}, []);
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (document.fullscreenElement) void document.exitFullscreen();
|
||||
else void containerRef.current?.requestFullscreen();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative h-80 w-full overflow-hidden rounded-lg bg-linear-to-b from-zinc-900 to-zinc-950">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative h-80 w-full overflow-hidden rounded-lg bg-linear-to-b from-zinc-900 to-zinc-950"
|
||||
>
|
||||
<LoadingOverlay />
|
||||
{fullscreen && (
|
||||
<button
|
||||
onClick={toggleFullscreen}
|
||||
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
||||
aria-label={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
||||
className="absolute right-2 top-2 z-10 flex h-8 w-8 items-center justify-center rounded-md border border-zinc-700 bg-zinc-900/80 text-zinc-300 backdrop-blur transition hover:bg-zinc-800 hover:text-zinc-100"
|
||||
>
|
||||
<Icon icon={isFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen'} className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
{overlay}
|
||||
<Canvas
|
||||
camera={{ position: [2.2, 1.8, 2.6], fov: 40 }}
|
||||
dpr={[1, 2]}
|
||||
@@ -40,10 +86,10 @@ export default function Scene({ children }: { children: ReactNode }) {
|
||||
resolution={256}
|
||||
/>
|
||||
<OrbitControls
|
||||
enablePan={false}
|
||||
enablePan={enablePan}
|
||||
minDistance={0.01}
|
||||
maxDistance={8}
|
||||
autoRotate
|
||||
autoRotate={autoRotate}
|
||||
makeDefault
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user