import React, { useState, useRef, useEffect } from 'react'; interface WindowShellProps { id: string; title: string; isOpen: boolean; isMinimized: boolean; onClose: () => void; onMinimize: () => void; activeWindow: string; setActiveWindow: (id: string) => void; children: React.ReactNode; icon?: string; defaultWidth?: string; defaultHeight?: string; defaultX?: number; defaultY?: number; titleBarColor?: string; isMobile?: boolean; } export default function WindowShell({ id, title, isOpen, isMinimized, onClose, onMinimize, activeWindow, setActiveWindow, children, icon = 'terminal', defaultWidth = '700px', defaultHeight = '600px', defaultX, defaultY, titleBarColor = '#0ea5e9', isMobile = false, }: WindowShellProps) { // Helper to parse default dimensions (e.g. '700px') into integer values const parseDimension = (val: string): number => { const num = parseInt(val, 10); return isNaN(num) ? 500 : num; }; const [position, setPosition] = useState({ x: defaultX ?? 0, y: defaultY ?? 0 }); const [dimensions, setDimensions] = useState({ width: parseDimension(defaultWidth), height: parseDimension(defaultHeight) }); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); const dragStart = useRef({ x: 0, y: 0 }); const resizeStart = useRef({ width: 0, height: 0, x: 0, y: 0 }); const windowRef = useRef(null); const isActive = activeWindow === id; useEffect(() => { // If not set, randomize offset slightly to feel "tossed on the desk" if (defaultX === undefined && defaultY === undefined) { const randomX = Math.floor(Math.random() * 40) - 20; const randomY = Math.floor(Math.random() * 40) - 20; setPosition({ x: randomX, y: randomY }); } }, [defaultX, defaultY]); const handleMouseDown = (e: React.MouseEvent) => { if (isMobile) return; // Only drag on left click and not on control buttons or resize handles if (e.button !== 0) return; const target = e.target as HTMLElement; if (target.closest('.window-control-btn') || target.closest('.window-resize-handle')) return; setActiveWindow(id); setIsDragging(true); dragStart.current = { x: e.clientX - position.x, y: e.clientY - position.y }; e.preventDefault(); }; const handleResizeMouseDown = (e: React.MouseEvent) => { if (isMobile) return; if (e.button !== 0) return; e.stopPropagation(); e.preventDefault(); setActiveWindow(id); setIsResizing(true); resizeStart.current = { width: dimensions.width, height: dimensions.height, x: e.clientX, y: e.clientY }; }; useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (isDragging) { const newX = e.clientX - dragStart.current.x; const newY = e.clientY - dragStart.current.y; setPosition({ x: newX, y: newY }); } else if (isResizing) { const deltaX = e.clientX - resizeStart.current.x; const deltaY = e.clientY - resizeStart.current.y; // Enforce minimum dimensions (350x250 px) const newWidth = Math.max(350, resizeStart.current.width + deltaX); const newHeight = Math.max(250, resizeStart.current.height + deltaY); setDimensions({ width: newWidth, height: newHeight }); } }; const handleMouseUp = () => { setIsDragging(false); setIsResizing(false); }; if (isDragging || isResizing) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging, isResizing, isMobile]); if (!isOpen || isMinimized) return null; return (
setActiveWindow(id)} style={isMobile ? { zIndex: isActive ? 40 : 30, } : { width: `${dimensions.width}px`, height: `${dimensions.height}px`, transform: `translate(calc(-50% + ${position.x}px), calc(-50% + ${position.y}px))`, zIndex: isActive ? 40 : 30, }} className={`absolute bg-paper-white border-[4px] flex flex-col overflow-hidden window-shadow transition-all ${ isMobile ? 'top-0 left-0 w-full h-[calc(100vh-56px)] max-w-none max-h-none border-t-0 border-x-0' : 'top-1/2 left-1/2 max-w-[98vw] max-h-[90vh] border-ink-black' } ${ isActive ? 'border-ink-black shadow-[8px_8px_0px_0px_rgba(30,41,59,1)]' : 'border-ink-black opacity-95 shadow-[4px_4px_0px_0px_rgba(30,41,59,1)]' }`} > {/* Title Bar */}
{icon && ( {icon} )} {title}
{!isMobile && ( )}
{/* Window Content */}
{children}
{/* Resize Handle */} {!isMobile && (
)}
); }