Reuben_OS / app /components /Window.tsx
Reubencf's picture
i AM SO DONE I HAVE TO STUDY FOR EXAMS
b15ad2e
'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<WindowProps> = ({
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 (
<div
className={`fixed flex flex-col overflow-hidden ${windowClass} ${className} rounded-lg shadow-2xl`}
style={{
top: '60px',
left: '10px',
right: '10px',
bottom: '80px',
zIndex: zIndex || 1000,
maxHeight: 'calc(100vh - 140px)'
}}
onMouseDown={bringToFront}
onTouchStart={bringToFront}
>
<div
className={`h-12 flex items-center px-3 space-x-2 border-b ${headerClass} ${headerClassName}`}
>
<div className="flex space-x-2 group relative z-10">
<button
className="traffic-light traffic-close w-6 h-6 sm:w-4 sm:h-4"
onClick={onClose}
aria-label="Close"
/>
<button
className="traffic-light traffic-min w-6 h-6 sm:w-4 sm:h-4"
onClick={onMinimize}
aria-label="Minimize"
/>
<button
className="traffic-light traffic-max w-6 h-6 sm:w-4 sm:h-4"
onClick={onMaximize}
aria-label="Maximize"
/>
</div>
<span className="font-semibold text-gray-700 flex-1 text-center text-sm truncate px-2">{title}</span>
</div>
<div className="flex-1 overflow-auto">{children}</div>
</div>
);
}
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 (
<Rnd
{...rndProps}
minWidth={400}
minHeight={300}
dragHandleClassName="window-drag-handle"
enableResizing={!isMaximized && resizable}
disableDragging={isMaximized}
onMouseDown={bringToFront}
onDragStart={() => {
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 }}
>
<div
className={`h-full macos-window flex flex-col ${className} ${windowClass}`}
onMouseDown={bringToFront}
>
<div
className={`window-drag-handle h-10 flex items-center px-4 space-x-4 border-b cursor-move ${headerClass} ${headerClassName}`}
onDoubleClick={handleMaximize}
>
<div className="flex space-x-2 group">
<div className="traffic-light traffic-close" onClick={onClose} />
<div className="traffic-light traffic-min" onClick={onMinimize} />
<div className="traffic-light traffic-max" onClick={handleMaximize} />
</div>
<span className="font-semibold text-gray-700 flex-1 text-center pr-16 text-sm select-none">{title}</span>
</div>
<div className={`flex-1 overflow-auto ${darkMode ? 'bg-gray-900/95 text-white' : 'bg-white/90'} backdrop-blur-sm ${contentClassName || ''}`} style={{ height: 'calc(100% - 2.5rem)' }}>
{children}
</div>
</div>
</Rnd>
);
};
export default Window;