Skip to content

PlWindowPane

A window, drawn the way one of eight systems draws it, with anything at all inside it. Not a real window and not pretending to be one, a frame that behaves.

React
tsx
import { PlWindowPane } from 'plass-ui';

<PlWindowPane title="Notes">
  <MyApp />
</PlWindowPane>;
dart
import 'package:plass_ui/plass_ui.dart';

PlWindowPane(title: const Text('Notes'), child: MyApp());

There is no desktop, no z-order and no dock. What there is is a title bar that drags, corners that resize and three buttons that are real buttons with real names, so a screenshot of an app, a demo of a feature or a piece of a landing page can be shown as the thing it will be rather than as a picture of it.

Nothing here is transformed. A dragged window moves on its position and a resized one changes its width and height, which is what keeps the text inside it at whole pixels through both gestures. A scale would resample every glyph in the window for the length of the drag, which is exactly what the house rule against transforming a surface exists to prevent.

Props

PropTypeDefaultDescription
os'macos' | 'macosx' | 'windows11' | 'windows10' | 'windows8' | 'windows7' | 'windowsxp' | 'linux''macos'Whose window this is a picture of. Decides where the controls sit, how they are drawn, and how tall the bar is
titleReactNodeThe window's name, and what names the window itself
iconReactNodeA glyph beside the title
actionsReactNodeAnything else the title bar carries
controlsboolean | readonly ('minimize' | 'maximize' | 'close')[]trueWhich of the three buttons the bar has. The order is the system's rather than the array's
accentbooleanfalseDyes the title bar with the colour family, the way Windows offers to
transparencynumber0How much of what is behind shows through the chrome. Never the content on it
activebooleanWhether this is the window in front. Left out, it works it out for itself
position'static' | 'absolute' | 'fixed''static'How the window is laid out
draggablebooleanfalseLets the title bar be dragged
resizablebooleanfalseLets the edges and corners be dragged
widthnumber | stringThe window's width
heightnumber | stringAnd its height. Left out, the window is as tall as what is in it
minWidthnumber180How small it may be dragged
minHeightnumberThe same downward. Defaults to the bar height
offset{ x: number; y: number }How far it has been dragged from where the layout put it
defaultOffset{ x: number; y: number }{ x: 0, y: 0 }Where an uncontrolled window starts
onOffsetChange(offset: { x: number; y: number }) => voidCalled while the bar is dragged
onResize(size: { width: number; height: number }) => voidFires with the window's size while an edge is dragged
openbooleanWhether the window is on screen at all. Closing it renders nothing
minimizedbooleanWhether the window is rolled up to its bar. There is nowhere to send it, so it stays
maximizedbooleanWhether the window fills whatever is holding it
scrollbooleantrueWhether content taller than the window scrolls
minimizeLabelstringOverrides the buttons' own names
resizeLabelstring'Resize window'What the one reachable resize handle is announced as
renderReactElementRenders something other than a div
childrenReactNodeWhat is in the window
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'The colour family the focus rings and an accent title bar take
elevationshared0 | 1 | 2 | 32The shadow around the window. 2, because a window is by definition not part of the page it is on
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The scale of the chrome. It does not touch the content
PropTypeDefaultDescription
osPlWindowOsPlWindowOs.macosWhose window this is a picture of. Decides where the controls sit, how they are drawn, and how tall the bar is
titleWidget?The window's name, and what names the window itself
iconWidget?A glyph beside the title
actionsWidget?Anything else the title bar carries
controlsSet<PlWindowControl>all threeWhich buttons the bar has. A Set where React takes boolean | array, since the order is the system's anyway
accentboolfalseDyes the title bar with the colour family, the way Windows offers to
transparencydouble0How much of what is behind shows through the chrome. Never the content on it
activebooltrueWhether this is the window in front. A plain value, unlike React's: there is no document to listen to
draggableboolfalseLets the title bar be dragged
resizableboolfalseLets the edges and corners be dragged
widthdouble?The window's width
heightdouble?And its height. Left out, the window is as tall as what is in it
minWidthdouble180How small it may be dragged
minHeightdouble?The same downward. Defaults to the bar height
offsetOffsetOffset.zeroHow far it has been dragged from where the layout put it. A leading edge moves the window as well as resizing it
onOffsetChangedValueChanged<Offset>?Called while the bar is dragged
onResizeValueChanged<Size>?Fires with the window's size while an edge is dragged
openbooltrueWhether the window is on screen at all. Closing it renders nothing
onOpenChangedValueChanged<bool>?Called when the close button is pressed
minimizedboolfalseWhether the window is rolled up to its bar. There is nowhere to send it, so it stays
onMinimizedChangedValueChanged<bool>?Called when the minimize button is pressed
maximizedboolfalseWhether the window fills whatever is holding it
onMaximizedChangedValueChanged<bool>?Called when the maximize button is pressed
minimizeLabelString?Overrides the buttons' own names
resizeLabelString?'Resize window'What the one reachable resize handle is announced as
childWidget?What is in the window
colorsharedPlassColorPlassColor.primaryThe colour family the focus rings and an accent title bar take
elevationsharedint2The shadow around the window. 2, because a window is by definition not part of the page it is on
sizesharedPlassSizePlassSize.mdThe scale of the chrome. It does not touch the content

minimize rolls the window up to its title bar rather than sending it anywhere, because a page has nowhere to send it to. maximize fills whatever is holding the window.

size scales the chrome and nothing else, the bar, the buttons and the title. A window's content is the caller's and is laid out at its own scale, exactly as it would be on a real desktop where the title bar does not grow with the document. It is the third component after PlBox and PlMockup where the ladder means something other than a control height.

Examples

os

React

Versions are separate entries wherever the title bar is what changed, which is why Windows has five and the others have one or two. XP painted its bar in Luna blue and framed the window in it; 7 made it glass; 8 threw both away for a flat square sheet; 10 ruled the bar off from the body; 11 rounded the corners and made the two one sheet again. macosx is Aqua against the flat macos that replaced it.

Which buttons a window has is the caller's decision; what order they sit in is the system's. macOS puts close first and Windows puts it last, and that is not something a caller should have to remember.

Nothing here is a copy of any of those systems: what is drawn is a bar, a border and three buttons at the proportions the system used, and no mark, wordmark or icon belonging to anyone else.

accent and active

React

A window behind the one in front keeps its shape and loses its emphasis: its colour drains, its shadow drops a step and its title greys. Never opacity, which would take the content down with the chrome.

On React, active works itself out if you leave it off. A window is in front until another one on the page is pressed or takes the focus. A click on the page around the windows changes nothing; a paragraph is not a desktop. On Flutter it is a plain value: there is no document to listen to, and a widget that reached across the tree to find the other windows would be inventing a desktop.

transparency

React

It applies to the title bar, the body's own fill and the border, never to the content on it, which stays exactly as legible as it was. On React anything above 0 also turns the blur on, so the page underneath is blurred rather than merely visible.

draggable and resizable

The title bar drags and the eight edges and corners resize. Both are off by default: a window in a page is usually a picture of one, and a frame that moved when a reader brushed it would be a surprise.

A resize stops at minWidth and minHeight, and onResize reports the size it stopped at. Dragging a left or a top edge moves the window as well as resizing it, so onOffsetChange fires during those too.

Accessibility

  • The window is a named group, taking its name from the title. On React that is role="group" with aria-labelledby; on Flutter it is a semantics container with explicitChildNodes, which is what stops the title, the buttons and every word of the content merging into one long name.
  • The three buttons are real buttons and say what they do. maximize becomes Restore once the window is filling its container, which is what every system calls it.
  • One of the eight resize handles is reachable without a pointer, and it is the corner that changes both axes at once. Eight tab stops around every window would cost a keyboard reader more than the seven extra directions are worth; the arrow keys move that corner. The other seven are hidden from the accessibility tree rather than left in it unnamed.
  • A minimized window's content is put out of reach rather than taken away. It is still in the tree, marked inert, so nothing under a rolled-up bar can be tabbed into.

Released under the MIT License