Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/log-portfolio/llms.txt

Use this file to discover all available pages before exploring further.

Window is the core UI shell that wraps every application in the portfolio. It draws the title bar, minimize/maximize/close controls, the content area, and the resize handle — and it wires all of them to the global WindowContext. Each instance is a self-contained motion.div that handles its own position and size state locally while delegating open/minimized/maximized/focused state to the context.

Props

windowData
object
required
The full window state object sourced directly from WindowContext. The component reads every field it needs from this single prop rather than accepting individual props.

WindowData Fields

FieldTypeDescription
idstringUnique identifier used to target context actions
titlestringText shown in the title bar
iconReactNode16 px icon rendered left of the title
contentReactNodeThe application component rendered inside the window
defaultSize{ width: number, height: number }Initial dimensions in pixels
isOpenbooleanWhen false the component returns null
isMinimizedbooleanWhen true the window div is absent from the DOM (exit animation plays)
isMaximizedbooleanWhen true the window fills the viewport and drag is disabled
zIndexnumberStacking order managed by context; increments on focus

Rendering Logic

// Returns null entirely when the window is not open
if (!windowData.isOpen) return null;

// AnimatePresence + isMinimized controls exit animation
return (
  <AnimatePresence>
    {!windowData.isMinimized && (
      <motion.div …>…</motion.div>
    )}
  </AnimatePresence>
);
The component returns null (not an empty fragment) when isOpen is false. This means the component is fully unmounted and its local position/size state is reset when a window is closed. Minimized windows, by contrast, keep their state alive — only the DOM node is removed via the exit animation.

Title Bar

The title bar div carries the class title-bar, which Framer Motion uses as the drag handle. It also receives one of two active/inactive classes:
StateClass
Active (focused) windowtitlebar-active
Inactive windowtitlebar-inactive
The active check is activeWindowId === windowData.id, sourced from WindowContext. Double-clicking the title bar calls maximizeWindow(id), toggling maximize on and off. The left side of the bar renders the 16 px icon followed by the window title in font-pixel with truncate so long titles do not overflow.

Window Controls

Three buttons sit in the top-right corner of the title bar:

Minimize

Renders a Lucide Minus icon at strokeWidth: 3. Calls minimizeWindow(id) on click. The window exits via the spring animation and disappears from the desktop, but remains in the taskbar.

Maximize / Restore

Renders Square when in normal mode and Copy (overlapping squares) when maximized. Calls maximizeWindow(id) which toggles the flag in context.

Close

Renders a Lucide X icon. Calls closeWindow(id), setting isOpen to false and removing the window from the taskbar entirely.
All three buttons call event.stopPropagation() to prevent the click from also triggering onMouseDown on the motion.div (which would call focusWindow unnecessarily).

Dragging

Dragging is powered by Framer Motion’s built-in drag prop:
<motion.div
  drag={!windowData.isMaximized}   // disabled when maximized
  dragMomentum={false}             // no momentum / sliding after release
  dragHandle=".title-bar"          // only the title bar initiates a drag
  onDragEnd={(event, info) => {
    setPosition({
      x: position.x + info.offset.x,
      y: position.y + info.offset.y,
    });
  }}
/>
dragMomentum: false ensures windows stop immediately on release, matching the feel of a real OS. Position is stored in local useState and applied back to the animate prop so it persists across re-renders.
When isMaximized is true, drag is set to false. The animate prop simultaneously overrides the position to x: 0, y: 0 and the dimensions to 100vw / calc(100vh - 40px), pushing the window edge-to-edge above the taskbar.

Resizing

A 16 × 16 px transparent div in the bottom-right corner acts as the resize handle:
<div
  className="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize"
  onMouseDown={(e) => {
    e.preventDefault();
    e.stopPropagation();

    const startX = e.clientX;
    const startY = e.clientY;
    const startWidth = size.width;
    const startHeight = size.height;

    const onMouseMove = (moveEvent) => {
      setSize({
        width:  Math.max(200, startWidth  + moveEvent.clientX - startX),
        height: Math.max(150, startHeight + moveEvent.clientY - startY),
      });
    };

    const onMouseUp = () => {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup",   onMouseUp);
    };

    document.addEventListener("mousemove", onMouseMove);
    document.addEventListener("mouseup",   onMouseUp);
  }}
/>
The listeners are attached to document rather than the handle itself so that the resize continues correctly when the cursor moves faster than the handle can follow. Minimum dimensions are 200 × 150 px. The handle renders a small SVG resize grip (three diagonal lines) at opacity-50.

Animations

All motion is handled through Framer Motion with a spring transition:
initial={{  scale: 0.8, opacity: 0 }}
animate={{  scale: 1,   opacity: 1, x: position.x, y: position.y,
            width: size.width,       height: size.height }}
exit={{     scale: 0.8, opacity: 0, y: "100vh" }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
ParameterValueEffect
type"spring"Physics-based easing instead of Bézier curves
damping25Controls oscillation — higher values settle faster
stiffness300Controls speed — higher values are snappier
Exit y"100vh"Window slides down off-screen when minimized or closed

Content Area

The application content is rendered inside:
<div className="flex-1 overflow-auto bg-white win-border-inset m-1 relative">
  {windowData.content}
</div>
  • flex-1 — the content area expands to fill the remaining height after the title bar.
  • overflow-auto — scroll bars appear automatically when content exceeds the window size.
  • win-border-inset — applies the recessed inner-bevel border that matches the Windows 98 aesthetic.
  • bg-white — most app content assumes a white canvas; individual apps may override this with their own background.

Initial Position

On first render, each window is centred in the viewport with a small random offset (±20 px) so that multiple windows opened in sequence do not stack perfectly on top of each other:
const [position, setPosition] = useState({
  x: (window.innerWidth  - size.width)  / 2 + (Math.random() * 40 - 20),
  y: (window.innerHeight - size.height) / 2 + (Math.random() * 40 - 20),
});

Build docs developers (and LLMs) love