'use client'; import React, { ReactNode, useState, useEffect } from 'react'; import { Rnd } from 'react-rnd'; interface WindowProps { id: string; title: string; isOpen: boolean; onClose: () => void; onMinimize?: () => void; onMaximize?: () => void; onFocus?: () => void; children: ReactNode; width?: number | string; height?: number | string; x?: number; y?: number; zIndex?: number; resizable?: boolean; className?: string; headerClassName?: string; contentClassName?: string; darkMode?: boolean; } const Window: React.FC = ({ id, title, isOpen, onClose, onMinimize, onMaximize, onFocus, children, width = 800, height = 600, x = 100, y = 100, zIndex = 1000, resizable = true, className = '', headerClassName = '', contentClassName = '', darkMode = false, }) => { const [isMaximized, setIsMaximized] = React.useState(false); const [previousSize, setPreviousSize] = React.useState({ width, height, x, y: Math.max(y, 32) }); const [isMobile, setIsMobile] = React.useState(false); const [isDraggingOrResizing, setIsDraggingOrResizing] = React.useState(false); // Use refs to track current position/size without causing re-renders const currentPositionRef = React.useRef({ x, y: Math.max(y, 32) }); const currentSizeRef = React.useRef({ width, height }); // Detect mobile device - update threshold to better match small screens useEffect(() => { const checkMobile = () => { setIsMobile(window.innerWidth <= 768); }; checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); if (!isOpen) return null; // Define window classes const windowClass = darkMode ? 'bg-gray-900 border-gray-700' : 'bg-[#f5f5f5] border-gray-300/50'; const headerClass = darkMode ? 'bg-gray-800 border-gray-700' : 'macos-window-header'; // Bring window to front function - define it early const bringToFront = () => { if (onFocus) { onFocus(); } }; // On mobile, render with reduced size instead of full screen if (isMobile) { return (
{title}
{children}
); } const handleMaximize = () => { if (!isMaximized) { setPreviousSize({ width: currentSizeRef.current.width, height: currentSizeRef.current.height, x: currentPositionRef.current.x, y: currentPositionRef.current.y }); setIsMaximized(true); } else { setIsMaximized(false); } if (onMaximize) onMaximize(); }; // Calculate Rnd props based on state const rndProps = isMaximized ? { position: { x: 0, y: 4 }, // 32px for TopBar offset (h-8 = 32px) size: { width: typeof window !== 'undefined' ? window.innerWidth : '100vw', height: typeof window !== 'undefined' ? window.innerHeight - 120 : 'calc(100vh - 120px)' }, disableDragging: true, enableResizing: false } : { // Use default for uncontrolled mode - only sets initial position default: { x: currentPositionRef.current.x, y: currentPositionRef.current.y, width: currentSizeRef.current.width, height: currentSizeRef.current.height }, disableDragging: false, enableResizing: resizable }; return ( { setIsDraggingOrResizing(true); bringToFront(); }} onDrag={(e, d) => { // Constrain dragging to not go above TopBar if (d.y < 4) { // Update position ref to boundary currentPositionRef.current = { x: d.x, y: 4 }; return false; } // Update position ref during drag currentPositionRef.current = { x: d.x, y: d.y }; }} onDragStop={(e, d) => { setIsDraggingOrResizing(false); if (!isMaximized) { // Ensure window doesn't go above TopBar and update position ref const constrainedY = Math.max(d.y, 4); currentPositionRef.current = { x: d.x, y: constrainedY }; } }} onResizeStart={() => { setIsDraggingOrResizing(true); bringToFront(); }} onResizeStop={(e, direction, ref, delta, position) => { setIsDraggingOrResizing(false); if (!isMaximized) { // Update size and position refs currentSizeRef.current = { width: ref.offsetWidth, height: ref.offsetHeight, }; currentPositionRef.current = position; } }} className="absolute" style={{ zIndex: zIndex || 1000 }} >
{title}
{children}
); }; export default Window;