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.
import { PlWindowPane } from 'plass-ui';
<PlWindowPane title="Notes">
<MyApp />
</PlWindowPane>;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
| Prop | Type | Default | Description |
|---|---|---|---|
| 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 |
| title | ReactNode | — | The window's name, and what names the window itself |
| icon | ReactNode | — | A glyph beside the title |
| actions | ReactNode | — | Anything else the title bar carries |
| controls | boolean | readonly ('minimize' | 'maximize' | 'close')[] | true | Which of the three buttons the bar has. The order is the system's rather than the array's |
| accent | boolean | false | Dyes the title bar with the colour family, the way Windows offers to |
| transparency | number | 0 | How much of what is behind shows through the chrome. Never the content on it |
| active | boolean | — | Whether 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 |
| draggable | boolean | false | Lets the title bar be dragged |
| resizable | boolean | false | Lets the edges and corners be dragged |
| width | number | string | — | The window's width |
| height | number | string | — | And its height. Left out, the window is as tall as what is in it |
| minWidth | number | 180 | How small it may be dragged |
| minHeight | number | — | The 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 }) => void | — | Called while the bar is dragged |
| onResize | (size: { width: number; height: number }) => void | — | Fires with the window's size while an edge is dragged |
| open | boolean | — | Whether the window is on screen at all. Closing it renders nothing |
| minimized | boolean | — | Whether the window is rolled up to its bar. There is nowhere to send it, so it stays |
| maximized | boolean | — | Whether the window fills whatever is holding it |
| scroll | boolean | true | Whether content taller than the window scrolls |
| minimizeLabel | string | — | Overrides the buttons' own names |
| resizeLabel | string | 'Resize window' | What the one reachable resize handle is announced as |
| render | ReactElement | — | Renders something other than a div |
| children | ReactNode | — | What is in the window |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | The colour family the focus rings and an accent title bar take |
| elevationshared | 0 | 1 | 2 | 3 | 2 | The 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 |
| Prop | Type | Default | Description |
|---|---|---|---|
| os | PlWindowOs | PlWindowOs.macos | Whose window this is a picture of. Decides where the controls sit, how they are drawn, and how tall the bar is |
| title | Widget? | — | The window's name, and what names the window itself |
| icon | Widget? | — | A glyph beside the title |
| actions | Widget? | — | Anything else the title bar carries |
| controls | Set<PlWindowControl> | all three | Which buttons the bar has. A Set where React takes boolean | array, since the order is the system's anyway |
| accent | bool | false | Dyes the title bar with the colour family, the way Windows offers to |
| transparency | double | 0 | How much of what is behind shows through the chrome. Never the content on it |
| active | bool | true | Whether this is the window in front. A plain value, unlike React's: there is no document to listen to |
| draggable | bool | false | Lets the title bar be dragged |
| resizable | bool | false | Lets the edges and corners be dragged |
| width | double? | — | The window's width |
| height | double? | — | And its height. Left out, the window is as tall as what is in it |
| minWidth | double | 180 | How small it may be dragged |
| minHeight | double? | — | The same downward. Defaults to the bar height |
| offset | Offset | Offset.zero | How far it has been dragged from where the layout put it. A leading edge moves the window as well as resizing it |
| onOffsetChanged | ValueChanged<Offset>? | — | Called while the bar is dragged |
| onResize | ValueChanged<Size>? | — | Fires with the window's size while an edge is dragged |
| open | bool | true | Whether the window is on screen at all. Closing it renders nothing |
| onOpenChanged | ValueChanged<bool>? | — | Called when the close button is pressed |
| minimized | bool | false | Whether the window is rolled up to its bar. There is nowhere to send it, so it stays |
| onMinimizedChanged | ValueChanged<bool>? | — | Called when the minimize button is pressed |
| maximized | bool | false | Whether the window fills whatever is holding it |
| onMaximizedChanged | ValueChanged<bool>? | — | Called when the maximize button is pressed |
| minimizeLabel | String? | — | Overrides the buttons' own names |
| resizeLabel | String? | 'Resize window' | What the one reachable resize handle is announced as |
| child | Widget? | — | What is in the window |
| colorshared | PlassColor | PlassColor.primary | The colour family the focus rings and an accent title bar take |
| elevationshared | int | 2 | The shadow around the window. 2, because a window is by definition not part of the page it is on |
| sizeshared | PlassSize | PlassSize.md | The 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
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
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
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"witharia-labelledby; on Flutter it is a semantics container withexplicitChildNodes, 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.
maximizebecomes 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.