Mantine Window

Undolog

@gfazioli/mantine-window

A Mantine extension component that renders interactive JSON trees with syntax highlighting, collapsible nodes, copy-to-clipboard, and configurable expansion depth.

Installation

yarn add @gfazioli/mantine-window

After installation import package styles at the root of your application:

import '@gfazioli/mantine-window/styles.css';

You can import styles within a layer @layer mantine-window by importing @gfazioli/mantine-window/styles.layer.css file.

import '@gfazioli/mantine-window/styles.layer.css';

Usage

The Window component allows you to create draggable and resizable windows within your application. It provides a familiar desktop-like experience for users. You can customize the appearance and behavior of the windows using various props. Also, you can choose whether the window should be rendered within a portal or relative to its parent container using the withinPortal prop.

Note:

Usually, you would want to use the Window component within a portal to avoid layout issues. However, if you want the window to be constrained within a specific container, you can set the withinPortal prop to false and the container should have position: relative style.

For example, in the demos below, the Window component is rendered relative to its parent container which has position: relative style.

Hello, World! Hello, Mantiners! Say Hello to the Window component

This is a window with data

Color
Shadow
Radius
import { Window, type WindowProps } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';
function Demo() {
  return (
    {/** In this demo, the Window is positioned relative to its parent container. Check the docs below to learn more about the "withinPortal" prop. */}
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        defaultPosition={{ x: 0, y: 0}}
        defaultSize={{ width: 320, height: 256 }}
        persistState={false}
        withinPortal={false}
        maxWidth={500}
        maxHeight={500}
        opened
        title="Hello, World! Hello, Mantiners! Say Hello to the Window component"
      >
        <Title order={4}>This is a window with data</Title>
      </Window>
    </Box>
  );
}

Controlled

You can control the open and close state of the Window component using the opened and onClose props. This allows you to manage the visibility of the window programmatically.

import { Window } from '@gfazioli/mantine-window';
import { Button, Stack, Title } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [opened, { open, close }] = useDisclosure(false);

  return (
    <Stack pos="relative" style={{ width: '100%', height: 500 }}>
      <Button onClick={open}>Open window</Button>
      <Window title="Hello World" opened={opened} onClose={close} withinPortal={false}>
        <Title order={4}>This is a window</Title>
      </Window>
    </Stack>
  );
}

Within Portal vs Container

The withinPortal prop controls how the window is positioned. When withinPortal={true} (default), the window uses fixed positioning relative to the viewport. When withinPortal={false}, it uses absolute positioning relative to its parent container.

Fixed positioning (withinPortal=true):

  • Window stays in place when scrolling
  • Positioned relative to the browser viewport
  • Best for modals and overlays

Absolute positioning (withinPortal=false):

  • Window is constrained within parent container
  • Parent must have position: relative
  • Best for embedded UI components

Container with position: relative

import { Window } from '@gfazioli/mantine-window';
import { Button, Group, Stack, Text, Title } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [portalOpened, { open: openPortal, close: closePortal }] = useDisclosure(false);
  const [containerOpened, { open: openContainer, close: closeContainer }] = useDisclosure(false);

  return (
    <>
      <Group mb="md">
        <Button onClick={openPortal}>Open in Portal (Fixed)</Button>
        <Button onClick={openContainer}>Open in Container (Relative)</Button>
      </Group>

      {/* Window with withinPortal={true} (default) - fixed positioning */}
      <Window 
        title="Fixed Window (Portal)" 
        opened={portalOpened} 
        onClose={closePortal}
        withinPortal={true}
        persistState={false}
        defaultPosition={{ x: 100, y: 100 }}
      >
        <Stack p="md">
          <Title order={4}>Portal Window</Title>
          <Text size="sm">
            This window uses <strong>withinPortal=true</strong> (default).
            It is positioned fixed relative to the viewport and will stay in place even when scrolling.
          </Text>
        </Stack>
      </Window>

      {/* Container for relative window */}
      <Stack pos="relative" style={{ width: '100%', height: 400, border: '2px dashed gray' }} p="md">
        <Text c="dimmed" size="sm">Container with position: relative</Text>
        
        {/* Window with withinPortal={false} - absolute positioning */}
        <Window 
          title="Relative Window (Container)" 
          opened={containerOpened} 
          onClose={closeContainer}
          withinPortal={false}
          persistState={false}
          defaultPosition={{ x: 20, y: 50 }}
        >
          <Stack p="md">
            <Title order={4}>Container Window</Title>
            <Text size="sm">
              This window uses <strong>withinPortal=false</strong>.
              It is positioned absolute relative to its parent container and cannot be dragged outside.
            </Text>
          </Stack>
        </Window>
      </Stack>
    </>
  );
}

Custom Position

You can set the initial position of the window using the defaultPosition prop. This is useful when you want the window to appear at a specific location on the screen.

Custom Position

This window starts at x:200, y:150

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="Custom Position"
        opened
        defaultPosition={{ x: 200, y: 150 }}
        defaultSize={{ width: 400, height: 300 }}
        persistState={false}
        withinPortal={false}
      >
        <Title order={4}>This window starts at x:200, y:150</Title>
      </Window>
    </Box>
  );
}

Custom Size

Set custom initial dimensions for the window using the defaultSize prop. This allows you to control the width and height of the window when it first appears.

Custom Size

This window is 500x400 pixels

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="Custom Size"
        opened
        defaultPosition={{ x: 50, y: 50 }}
        defaultSize={{ width: 500, height: 400 }}
        persistState={false}
        withinPortal={false}
      >
        <Title order={4}>This window is 500x400 pixels</Title>
      </Window>
    </Box>
  );
}

Min/Max Size Constraints

Control the minimum and maximum dimensions during resize operations using minWidth, minHeight, maxWidth, and maxHeight props. This prevents the window from becoming too small or too large.

Constrained Resize

Try resizing this window

Min: 300x200px

Max: 600x450px

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="Constrained Resize"
        opened
        defaultPosition={{ x: 50, y: 50 }}
        defaultSize={{ width: 400, height: 300 }}
        minWidth={300}
        minHeight={200}
        maxWidth={600}
        maxHeight={450}
        persistState={false}
        withinPortal={false}
      >
        <Title order={4}>Try resizing this window</Title>
        <p>Min: 300x200px</p>
        <p>Max: 600x450px</p>
      </Window>
    </Box>
  );
}

Drag Boundaries

Restrict the draggable area of the window using the dragBounds prop. This ensures the window stays within specific boundaries and cannot be dragged outside the defined area.

Bounded Dragging

Try dragging this window

It can only move within specific bounds:

X: 50-500, Y: 50-400

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="Bounded Dragging"
        opened
        defaultPosition={{ x: 100, y: 50 }}
        defaultSize={{ width: 400, height: 300 }}
        dragBounds={{
          minX: 50,
          maxX: 500,
          minY: 50,
          maxY: 400,
        }}
        persistState={false}
        withinPortal={false}
      >
        <Title order={4}>Try dragging this window</Title>
        <p>It can only move within specific bounds:</p>
        <p>X: 50-500, Y: 50-400</p>
      </Window>
    </Box>
  );
}

Persistence

Enable state persistence to save the window's position and size in localStorage by setting persistState={true} (default is true). When enabled, the window will remember its position and size across page refreshes. Set persistState={false} to disable this behavior and always start at the default position and size.

With Persistence

Position and size are saved in localStorage

Move or resize this window, then refresh the page

The window will remember its last position and size

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="With Persistence"
        id="persistent-window-demo"
        opened
        defaultPosition={{ x: 50, y: 50 }}
        defaultSize={{ width: 400, height: 300 }}
        persistState
        withinPortal={false}
      >
        <Title order={4}>Position and size are saved in localStorage</Title>
        <p>Move or resize this window, then refresh the page</p>
        <p>The window will remember its last position and size</p>
      </Window>
    </Box>
  );
}

Callbacks

Use onPositionChange and onSizeChange callbacks to respond to window movements and resize operations. These callbacks receive the current position or size as arguments.

With Callbacks

Open console to see callbacks

Move or resize this window to trigger callbacks

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="With Callbacks"
        opened
        defaultPosition={{ x: 50, y: 50 }}
        defaultSize={{ width: 400, height: 300 }}
        persistState={false}
        withinPortal={false}
        onPositionChange={(pos) => console.log('Position changed:', pos)}
        onSizeChange={(size) => console.log('Size changed:', size)}
      >
        <Title order={4}>Open console to see callbacks</Title>
        <p>Move or resize this window to trigger callbacks</p>
      </Window>
    </Box>
  );
}

Centered Window

Create a centered, fixed window that cannot be moved or resized by combining defaultPosition, defaultSize, resizable="none", and draggable="none" props.

Centered Window

Centered, fixed window

This window is centered and cannot be moved or resized

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="Centered Window"
        opened
        defaultPosition={{ x: 190, y: 100 }}
        defaultSize={{ width: 420, height: 300 }}
        resizable="none"
        draggable="none"
        persistState={false}
        withinPortal={false}
      >
        <Title order={4}>Centered, fixed window</Title>
        <p>This window is centered and cannot be moved or resized</p>
      </Window>
    </Box>
  );
}

Multiple Windows

You can render multiple windows in the same container. Each window maintains its own z-index and can be brought to the front by clicking on it. Use unique id props to ensure proper state persistence.

Window 1

First Window

Click on a window to bring it to front

Window 2

Second Window

Click on a window to bring it to front

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="Window 1"
        id="demo-window-1"
        opened
        defaultPosition={{ x: 20, y: 20 }}
        defaultSize={{ width: 300, height: 250 }}
        persistState={false}
        withinPortal={false}
      >
        <Title order={4}>First Window</Title>
        <p>Click on a window to bring it to front</p>
      </Window>
      <Window
        title="Window 2"
        id="demo-window-2"
        opened
        defaultPosition={{ x: 180, y: 80 }}
        defaultSize={{ width: 300, height: 250 }}
        persistState={false}
        withinPortal={false}
      >
        <Title order={4}>Second Window</Title>
        <p>Click on a window to bring it to front</p>
      </Window>
    </Box>
  );
}

Disable Collapsing

Prevent the window from being collapsed by setting collapsable={false}. This removes the collapse functionality, including the double-click handler on the header.

No Collapsable

This window cannot be collapsed

Double-clicking the header will not collapse the window

import { Window } from '@gfazioli/mantine-window';
import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box pos="relative" style={{ width: '100%', height: 500 }}>
      <Window
        title="No Collapsable"
        opened
        defaultPosition={{ x: 50, y: 50 }}
        defaultSize={{ width: 400, height: 300 }}
        collapsable={false}
        persistState={false}
        withinPortal={false}
      >
        <Title order={4}>This window cannot be collapsed</Title>
        <p>Double-clicking the header will not collapse the window</p>
      </Window>
    </Box>
  );
}

Styles API

Window supports Styles API, you can add styles to any inner element of the component with classNames prop. Follow Styles API documentation to learn more.

You can customize the appearance of the Window component using the Styles API. This allows you to modify styles for various parts of the component, such as keys, values, brackets, and more.

Styles API

This is a window with data

Component Styles API

Hover over selectors to highlight corresponding elements

/*
 * Hover over selectors to apply outline styles
 *
 */