react-x11 2.51.0 → 2.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -54
- package/docs/README.md +410 -0
- package/docs/accessibility.md +628 -0
- package/docs/appearance.md +416 -0
- package/docs/architecture/accessibility.md +470 -0
- package/docs/architecture/animation.md +707 -0
- package/docs/architecture/container-queries.md +525 -0
- package/docs/architecture/custom-layout.md +334 -0
- package/docs/architecture/drag-and-drop.md +941 -0
- package/docs/architecture/element-layer-contents.md +622 -0
- package/docs/architecture/frame-pacing.md +255 -0
- package/docs/architecture/grid-layout.md +358 -0
- package/docs/architecture/protocol-efficiency.md +357 -0
- package/docs/architecture/video.md +1149 -0
- package/docs/architecture/windowed-regions.md +854 -0
- package/docs/click-to-component.md +125 -0
- package/docs/clipboard.md +281 -0
- package/docs/components.md +1749 -0
- package/docs/dbus.md +277 -0
- package/docs/debugging.md +464 -0
- package/docs/desktop-calendar.md +284 -0
- package/docs/desktop.md +648 -0
- package/docs/devtools.md +250 -0
- package/docs/drag-and-drop.md +663 -0
- package/docs/ecosystem/animation.md +394 -0
- package/docs/ecosystem/data-fetching.md +201 -0
- package/docs/ecosystem/dev-tooling.md +436 -0
- package/docs/ecosystem/forms.md +313 -0
- package/docs/ecosystem/headless.md +219 -0
- package/docs/ecosystem/i18n.md +89 -0
- package/docs/ecosystem/icons.md +410 -0
- package/docs/ecosystem/layout.md +216 -0
- package/docs/ecosystem/routing.md +146 -0
- package/docs/ecosystem/state.md +361 -0
- package/docs/ecosystem/testing.md +500 -0
- package/docs/ecosystem/theming.md +540 -0
- package/docs/ecosystem.md +473 -0
- package/docs/elements.md +2762 -0
- package/docs/embedding.md +288 -0
- package/docs/events.md +1025 -0
- package/docs/extending.md +2242 -0
- package/docs/eyedropper.md +223 -0
- package/docs/filedialog.md +258 -0
- package/docs/frame.md +580 -0
- package/docs/gl.md +170 -0
- package/docs/globalmenu.md +278 -0
- package/docs/glx.md +137 -0
- package/docs/macos.md +3295 -0
- package/docs/notifications.md +164 -0
- package/docs/packaging.md +611 -0
- package/docs/permissions.md +148 -0
- package/docs/react-features.md +430 -0
- package/docs/remote.md +223 -0
- package/docs/scale.md +339 -0
- package/docs/security.md +132 -0
- package/docs/styling.md +1928 -0
- package/docs/system.md +599 -0
- package/docs/testing.md +514 -0
- package/docs/typescript.md +117 -0
- package/docs/uri-schemes.md +380 -0
- package/docs/wayland-backend.md +591 -0
- package/docs/wayland.md +545 -0
- package/docs/windows-embedding.md +271 -0
- package/docs/windows-gl.md +221 -0
- package/docs/windows-integrations.md +233 -0
- package/docs/windows.md +1404 -0
- package/package.json +10 -3
- package/src/bus.js +39 -26
- package/src/cli/create.d.ts +43 -0
- package/src/cli/create.js +305 -0
- package/src/cli/env.js +484 -0
- package/src/cli/index.js +29 -2
- package/src/cli/templates/AGENTS.template.md +195 -0
- package/src/cli/templates/DESIGN.template.md +269 -0
- package/src/desktopintegration.js +2 -1
- package/src/desktopsettings.js +193 -24
- package/src/filedialog.js +3 -3
- package/src/keyboardstate.js +28 -0
- package/src/node.d.ts +10 -10
- package/src/portal.js +31 -0
- package/src/screencolor.js +5 -10
- package/src/types/system.d.ts +8 -6
- package/src/wayland/app.js +59 -0
- package/src/wayland/clipboard.js +14 -7
- package/src/wayland/connection.js +3 -0
- package/src/wayland/context2d.js +129 -10
- package/src/wayland/protocols/index.json +6 -0
- package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
- package/src/wayland/seat.js +28 -1
- package/src/wayland/swapchain.js +23 -0
- package/src/wayland/xkb.js +137 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<!-- BEGIN:react-x11-agent-rules -->
|
|
2
|
+
|
|
3
|
+
# This is react-x11, not react-dom
|
|
4
|
+
|
|
5
|
+
This app is React drawing native desktop windows with react-x11
|
|
6
|
+
{{version}}. There is no DOM, no browser and no CSS. What you know about
|
|
7
|
+
React carries over: components, hooks, state, context, Suspense, error
|
|
8
|
+
boundaries. What you know about the DOM does not. Before writing UI, read the
|
|
9
|
+
docs installed with the package. They match the version this app runs and
|
|
10
|
+
are newer than your training data.
|
|
11
|
+
|
|
12
|
+
## Where to look
|
|
13
|
+
|
|
14
|
+
**Docs**, installed with the package (paths from this file's directory):
|
|
15
|
+
`node_modules/react-x11/docs/`
|
|
16
|
+
|
|
17
|
+
| page | for |
|
|
18
|
+
| --------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
19
|
+
| `react-features.md` | **read first**: where a DOM habit breaks (refs, measuring, portals, effects) |
|
|
20
|
+
| `elements.md` | the host elements and their props |
|
|
21
|
+
| `styling.md` | the `style` prop: layout, paint, `:hover` blocks, theme tokens |
|
|
22
|
+
| `components.md` | `Button`, `Select`, `Dialog`, `MenuBar`, `Tabs`, `Table`, `SplitPane`, … |
|
|
23
|
+
| `events.md` | events, focus, keys, shortcuts |
|
|
24
|
+
| `appearance.md` | light and dark, the accent, reduced motion |
|
|
25
|
+
| `globalmenu.md` | where `MenuBar` goes on each platform |
|
|
26
|
+
| `testing.md` | `react-x11/test`: render headless, query, click, type, write a PNG |
|
|
27
|
+
| `accessibility.md` | roles, names, what a screen reader hears |
|
|
28
|
+
| `filedialog.md`, `notifications.md`, `clipboard.md` | the desktop around the app |
|
|
29
|
+
| `drag-and-drop.md`, `system.md`, `packaging.md` | more of it, and shipping the app |
|
|
30
|
+
| `ecosystem/*.md` | state, forms, routing, data fetching, i18n with libraries you know |
|
|
31
|
+
| `README.md` | the map of every page |
|
|
32
|
+
|
|
33
|
+
The same pages are online at
|
|
34
|
+
`https://sidorares.github.io/react-x11/docs/reference/<page>`.
|
|
35
|
+
|
|
36
|
+
**Types**: `node_modules/react-x11/src/index.d.ts` and
|
|
37
|
+
`node_modules/react-x11/src/types/*.d.ts` hold every element, style key, event
|
|
38
|
+
and widget with its props. Grep them for any prop you are not sure of.
|
|
39
|
+
|
|
40
|
+
**Examples** are not in the package. These are pinned to this version:
|
|
41
|
+
`https://github.com/sidorares/react-x11/tree/v{{version}}/examples`, raw files
|
|
42
|
+
at `https://raw.githubusercontent.com/sidorares/react-x11/v{{version}}/examples/<file>`.
|
|
43
|
+
`finder.jsx` and `settings.jsx` (sidebar and content), `tasks.jsx` (list and
|
|
44
|
+
input), `widgets.jsx` (every control), `dashboard.jsx`, `menu.jsx`,
|
|
45
|
+
`theming.jsx`. Read the closest one or two before writing a screen.
|
|
46
|
+
|
|
47
|
+
## Rules you will trip on
|
|
48
|
+
|
|
49
|
+
Most of these throw with a message that names the fix. Read the message
|
|
50
|
+
before guessing.
|
|
51
|
+
|
|
52
|
+
- **Host elements are lowercase and few**: `<window>`, `<popup>`, `<box>`,
|
|
53
|
+
`<text>`, `<textinput>`, `<textarea>`, `<image>`, `<svg>`, `<canvas>`,
|
|
54
|
+
`<video>`, `<glarea>`. No `<div>`, `<span>`, `<button>`, `<input>`.
|
|
55
|
+
- **Everything drawn is inside a `<window>`.** `<window>` takes its geometry as
|
|
56
|
+
props, not style: `title`, `width`, `height`, `minWidth`, `minHeight`,
|
|
57
|
+
`resizable`.
|
|
58
|
+
- **Raw strings and numbers go only inside `<text>`.** `<box>hi</box>` throws.
|
|
59
|
+
A `<text>` inside a `<text>` is a styled span.
|
|
60
|
+
- **Style lives in `style`**, an object or an array (`[s.row, on && s.on]`,
|
|
61
|
+
falsy entries skipped). `<box padding={8}>` throws. There is no
|
|
62
|
+
`className`, no CSS file, no Tailwind, no styled-components.
|
|
63
|
+
- **State blocks paint only.** `':hover'`, `':active'`, `':focus'`,
|
|
64
|
+
`':focus-visible'`, `':focus-within'` and `':disabled'` inside `style` may set
|
|
65
|
+
colours, `background`, `opacity`, `outline`, `borderRadius`. Not padding,
|
|
66
|
+
size or `border` (its width is layout).
|
|
67
|
+
- **Flexbox is React Native's direction with CSS's shrink**: `flexDirection`
|
|
68
|
+
defaults to `'column'`, and children of a column stretch to its full width.
|
|
69
|
+
A `Button` in a column becomes a slab: give it `alignSelf: 'flex-start'`, or
|
|
70
|
+
put buttons in a `flexDirection: 'row'` box. `display: 'grid'` and `gap`
|
|
71
|
+
work as in CSS.
|
|
72
|
+
- **A pane smaller than its content scrolls** only with
|
|
73
|
+
`overflow: 'scroll'` (or `'hidden'`, or `minHeight: 0`). For 1000+ rows use
|
|
74
|
+
`Table`, which is virtualized.
|
|
75
|
+
- **Colours are theme tokens**: `'$background'`, `'$surface'`, `'$text'`,
|
|
76
|
+
`'$textMuted'`, `'$border'`, `'$accent'`, `'$accentText'`, `'$accentHover'`,
|
|
77
|
+
`'$accentActive'`, `'$surfaceHover'`, `'$surfaceActive'`,
|
|
78
|
+
`'$hoverBackground'`/`'$hoverText'` (a selected row), `'$danger'`,
|
|
79
|
+
`'$success'`, `'$warning'`, `'$info'`, `'$link'`. They follow the desktop's
|
|
80
|
+
light or dark scheme and its accent. A hard-coded grey is wrong in one of
|
|
81
|
+
the two. `useTheme()` gives the resolved values, for `<canvas>` drawing.
|
|
82
|
+
- **Events**: `onClick`, `onMouseDown`/`Up`/`Move`/`Enter`/`Leave`, `onWheel`,
|
|
83
|
+
`onContextMenu`, and `onKeyDown`/`onKeyUp` on an element that is
|
|
84
|
+
`focusable`. `Button` takes **`onPress`**. Every input's `onChange` gets an
|
|
85
|
+
event: read `ev.value`. Keys: compare `ev.keysym` with `XK_RETURN`,
|
|
86
|
+
`XK_ESCAPE`, `XK_DOWN`, … from `'react-x11/keysyms'`.
|
|
87
|
+
- **Shortcuts**: `[['Control', 'S']]` is ⌘S on macOS and Ctrl+S elsewhere. Put
|
|
88
|
+
them on `MenuBar` items (`shortcut`) or use `useAccelerator(chord, fn)`.
|
|
89
|
+
Never bind Copy, Paste, Cut, Select All or Undo in a menu while a text field
|
|
90
|
+
can have focus: the fields handle those already, and both would run.
|
|
91
|
+
- **Web UI libraries do not work** (MUI, Radix, shadcn, recharts, react-dom
|
|
92
|
+
anything). Plain JavaScript libraries do: zustand, zod, date-fns, `fetch`.
|
|
93
|
+
- **No `localStorage`.** Persist with
|
|
94
|
+
`createSettings({ appId, defaults })` from `react-x11`
|
|
95
|
+
(`const [value, setValue] = settings.use('key')`) or with `node:fs`.
|
|
96
|
+
- **Paths are absolute**: `new URL('./data.json', import.meta.url)`. On macOS
|
|
97
|
+
the app's JavaScript runs on a worker thread, where `process.chdir()` throws.
|
|
98
|
+
|
|
99
|
+
## Bigger components
|
|
100
|
+
|
|
101
|
+
Charts, a data table, a tree, tabs, markdown, a code editor, a rich text
|
|
102
|
+
editor, a calendar and date picker, a colour picker, a timeline, a reorderable
|
|
103
|
+
list, flow graphs, maps, formulas and a 3D scene are in
|
|
104
|
+
`@react-x11/components`. Install it, then import from its subpaths, not the
|
|
105
|
+
package root: `@react-x11/components/charts`, `/table`, `/tree`, `/tabs`,
|
|
106
|
+
`/markdown`, `/code-editor`, `/rich-text-editor`, `/calendar`,
|
|
107
|
+
`/color-picker`, `/timeline`, `/reorder`, `/flow`, `/maps`, `/formula`,
|
|
108
|
+
`/three`. Its `README.md` and `docs/components/<name>.md` are in
|
|
109
|
+
`node_modules/@react-x11/components/`, and each subpath has its own `.d.ts`.
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
- `{{run}} start` runs the app once.
|
|
114
|
+
- `{{run}} dev` runs it with hot reload. It keeps running and is meant for a
|
|
115
|
+
person at the keyboard; do not wait for it to exit.
|
|
116
|
+
- `npx react-x11 help` lists every `REACT_X11_*` variable.
|
|
117
|
+
`REACT_X11_DEBUG_LAYOUT=1` outlines every laid-out box, for a layout that
|
|
118
|
+
does not do what you expect.
|
|
119
|
+
|
|
120
|
+
## Check your work: run it and look at it
|
|
121
|
+
|
|
122
|
+
Passing tests are not the finish line. A clipped label, a stretched button or
|
|
123
|
+
a colour that is wrong in dark mode passes every test and is obvious in a
|
|
124
|
+
picture.
|
|
125
|
+
|
|
126
|
+
1. Render the app headless and write a picture of each scheme.
|
|
127
|
+
`react-x11/test` runs a real X server inside the test process: no window
|
|
128
|
+
opens, nothing to install but `pngjs` (`npm install -D pngjs`).
|
|
129
|
+
|
|
130
|
+
```jsx
|
|
131
|
+
// test/App.test.jsx — run with: node --import tsx --test test/*.test.jsx
|
|
132
|
+
import React from 'react';
|
|
133
|
+
import { mkdirSync } from 'node:fs';
|
|
134
|
+
import { afterEach, test } from 'node:test';
|
|
135
|
+
import {
|
|
136
|
+
cleanup,
|
|
137
|
+
renderX11,
|
|
138
|
+
screen,
|
|
139
|
+
toPNG,
|
|
140
|
+
userEvent,
|
|
141
|
+
} from 'react-x11/test';
|
|
142
|
+
import App from '../src/App.jsx';
|
|
143
|
+
|
|
144
|
+
afterEach(cleanup);
|
|
145
|
+
// a font file, so text measures the same every run; Linux finds one itself
|
|
146
|
+
const fonts = {
|
|
147
|
+
darwin: { 'sans-serif': '/System/Library/Fonts/Supplemental/Arial.ttf' },
|
|
148
|
+
win32: { 'sans-serif': 'C:\\Windows\\Fonts\\segoeui.ttf' },
|
|
149
|
+
}[process.platform];
|
|
150
|
+
|
|
151
|
+
for (const colorScheme of ['light', 'dark']) {
|
|
152
|
+
test(`renders in ${colorScheme}`, async () => {
|
|
153
|
+
const { ctx, window: win } = await renderX11(<App />, {
|
|
154
|
+
wrap: false, // App renders its own <window>
|
|
155
|
+
fonts,
|
|
156
|
+
colorScheme,
|
|
157
|
+
});
|
|
158
|
+
// drive it the way a person would, then assert on what shows
|
|
159
|
+
// await userEvent.click(screen.getByRole('button', { name: 'Add' }));
|
|
160
|
+
// screen.getByText('1 item');
|
|
161
|
+
mkdirSync('shots', { recursive: true });
|
|
162
|
+
const { width, height } = win; // the size the app's window came out
|
|
163
|
+
await toPNG(ctx, `shots/${colorScheme}.png`, { width, height });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Queries are Testing Library's: `getByText`, `getByRole(role, { name })`,
|
|
169
|
+
`getByPlaceholder`, `findBy*`, `queryBy*`, and `userEvent.click`, `type`,
|
|
170
|
+
`key`, `hover`. A `getBy*` that matches twice throws: add
|
|
171
|
+
`{ exact: true }` or narrow with `within(node)`.
|
|
172
|
+
|
|
173
|
+
2. **Open both PNGs and look at them.** Fix what looks wrong: clipped or
|
|
174
|
+
overlapping text, a cramped or empty window, rows that do not line up, a
|
|
175
|
+
colour that ignores dark mode, a control with no hover or pressed state.
|
|
176
|
+
Render again. Two or three passes is normal.
|
|
177
|
+
3. The headless render is the X11 backend: native control bezels, the macOS
|
|
178
|
+
menu bar and SF Symbols do not appear in it, and bold may come out regular.
|
|
179
|
+
Judge layout, spacing, colour and text from it. Then run `{{run}} start`
|
|
180
|
+
for a few seconds to confirm the real app starts with nothing in its
|
|
181
|
+
output.
|
|
182
|
+
|
|
183
|
+
## Design
|
|
184
|
+
|
|
185
|
+
Read [DESIGN.md](DESIGN.md) before writing UI. It says how this app should
|
|
186
|
+
look and how to get each piece out of react-x11.
|
|
187
|
+
|
|
188
|
+
<!-- END:react-x11-agent-rules -->
|
|
189
|
+
|
|
190
|
+
The block above comes from react-x11 {{version}}. Keep notes about this app
|
|
191
|
+
below it, so the block can be replaced with a newer version's.
|
|
192
|
+
|
|
193
|
+
## This app
|
|
194
|
+
|
|
195
|
+
<!-- What it is, who it is for, and the decisions an agent should keep to. -->
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Design
|
|
2
|
+
|
|
3
|
+
How this app should look and behave. People and coding agents both read it
|
|
4
|
+
before changing the UI.
|
|
5
|
+
|
|
6
|
+
The first section is this app's brief: fill it in. The rest is the house
|
|
7
|
+
style react-x11 {{version}} starts every app with: the conventions of the
|
|
8
|
+
desktops the app runs on, and how to get each one out of react-x11. Change
|
|
9
|
+
any of it that this app does differently. Then the agent follows your rules
|
|
10
|
+
and not the defaults.
|
|
11
|
+
|
|
12
|
+
## This app
|
|
13
|
+
|
|
14
|
+
- **What it is**: _one line: what someone opens it to do._
|
|
15
|
+
- **Who uses it, and how often**: _all day, or once a week?_
|
|
16
|
+
- **Platforms**: macOS, Windows and Linux, each looking like itself. _Or
|
|
17
|
+
name the one that matters most._
|
|
18
|
+
- **Layout**: _sidebar and content, three panes, content and inspector, or
|
|
19
|
+
one compact window (see "Window anatomy")._
|
|
20
|
+
- **Density**: regular. _Compact for a data-heavy tool._
|
|
21
|
+
- **Colour**: the desktop's own, with the user's accent. No brand palette.
|
|
22
|
+
_If there is a brand colour, name it and the one or two places it may
|
|
23
|
+
appear._
|
|
24
|
+
- **Voice**: short and plain. Buttons are verbs ("Add Account", not "OK").
|
|
25
|
+
|
|
26
|
+
## Look like the desktop it runs on
|
|
27
|
+
|
|
28
|
+
A desktop app is quiet, dense, aligned and driven from the keyboard. It is
|
|
29
|
+
light or dark because the desktop is. People notice in a second when an app is
|
|
30
|
+
a web page in a window.
|
|
31
|
+
|
|
32
|
+
react-x11 gives you most of this already:
|
|
33
|
+
|
|
34
|
+
- The theme tokens are the desktop's palette, its light or dark scheme and its
|
|
35
|
+
accent.
|
|
36
|
+
- The widgets wear AppKit's own bezels on macOS.
|
|
37
|
+
- `MenuBar` goes where the platform keeps menus.
|
|
38
|
+
|
|
39
|
+
Keep those defaults, and spend the effort on layout, copy and behaviour.
|
|
40
|
+
|
|
41
|
+
## Window anatomy
|
|
42
|
+
|
|
43
|
+
- **One main window** with a clear structure. Pick one of the standard
|
|
44
|
+
layouts:
|
|
45
|
+
- **Sidebar and content** (Mail, Files, Explorer): a list of places or
|
|
46
|
+
collections on the left, 200–260 px wide, then the content. This is the
|
|
47
|
+
default for anything with collections of things.
|
|
48
|
+
- **Sidebar, list and detail**: three columns, with the middle list
|
|
49
|
+
260–320 px wide.
|
|
50
|
+
- **Content and inspector**: a 240–300 px panel on the right with the
|
|
51
|
+
properties of the selection.
|
|
52
|
+
- **One compact window** for a single-purpose utility: a timer, a
|
|
53
|
+
converter. This needs no sidebar.
|
|
54
|
+
- **A toolbar** under the title bar: a 40–48 px row with the few most-used
|
|
55
|
+
actions as icon buttons, and a search field at the trailing end.
|
|
56
|
+
- **Dialogs for short tasks**: a modal dialog for a three-field "New item…"
|
|
57
|
+
form, and a popup anchored to what it edits for a small editor. Never take
|
|
58
|
+
over the whole window for a short form.
|
|
59
|
+
- **Size**: 900×600 by default for a sidebar app, 720×480 for a simple one,
|
|
60
|
+
about 360×480 for a utility. **Always set a minimum size** (about 600×400
|
|
61
|
+
for a sidebar app), so the layout never collapses.
|
|
62
|
+
|
|
63
|
+
## Menus and shortcuts
|
|
64
|
+
|
|
65
|
+
People try a shortcut before they look for a button. Write shortcuts with
|
|
66
|
+
`Control`: it is ⌘ on macOS and Ctrl elsewhere. The standard menus, in order:
|
|
67
|
+
|
|
68
|
+
- **File**: New (Control+N), Open… (Control+O), Close Window (Control+W), Save
|
|
69
|
+
(Control+S), Save As… (Control+Shift+S), Export…, Print… (Control+P)
|
|
70
|
+
- **Edit**: Undo, Redo, Cut, Copy, Paste, Delete, Select All, Find…
|
|
71
|
+
(Control+F)
|
|
72
|
+
- **View**: Show or Hide Sidebar, view modes (Control+1, Control+2, …), Zoom
|
|
73
|
+
In and Zoom Out
|
|
74
|
+
- _the app's own_: its own verbs (Item, Format, Playback, …), with shortcuts
|
|
75
|
+
for the frequent ones
|
|
76
|
+
- **Help**: _App name_ Help
|
|
77
|
+
|
|
78
|
+
And the rules around them:
|
|
79
|
+
|
|
80
|
+
- **Where the menu shows**: on macOS `MenuBar` becomes the system menu bar.
|
|
81
|
+
On Linux it goes to the desktop's panel where one hosts menus (KDE
|
|
82
|
+
Plasma), and is drawn in the window everywhere else, Windows included.
|
|
83
|
+
- **Settings and Quit**: on macOS, react-x11 adds the app menu itself, with
|
|
84
|
+
Quit (⌘Q) only. Put "Settings…" (Control+,) at the end of your first menu.
|
|
85
|
+
On Windows and Linux, put Settings… in Edit or File, and Quit (Control+Q) at
|
|
86
|
+
the end of File.
|
|
87
|
+
- An item that opens a window or asks for more ends in "…". An item that does
|
|
88
|
+
not apply is disabled, not hidden. Every frequent action is in a menu, even
|
|
89
|
+
when it also has a button.
|
|
90
|
+
- The text fields already handle Copy, Paste, Cut, Select All, Undo and Redo,
|
|
91
|
+
with a right-click menu of their own. Do not put those shortcuts on menu
|
|
92
|
+
items while a field can have focus: both would run.
|
|
93
|
+
|
|
94
|
+
## Type
|
|
95
|
+
|
|
96
|
+
- **Base size**: 13 on macOS, which is AppKit's body size, and 14 elsewhere,
|
|
97
|
+
which is react-x11's default. Set it once at the root (see the recipes
|
|
98
|
+
below). Never set `fontFamily`: the default is the system's UI face.
|
|
99
|
+
- **Scale**: base for body, controls and lists; base −2 in `'$textMuted'` for
|
|
100
|
+
secondary text and captions; base +2 and +4, semibold (600), for titles; base
|
|
101
|
+
+9 for a rare large title. Use at most three sizes on one screen.
|
|
102
|
+
Hierarchy comes from weight and the muted colour, not from size jumps.
|
|
103
|
+
- A number that changes while you watch (a timer, a price, a count) uses
|
|
104
|
+
`fontVariantNumeric: 'tabular-nums'`.
|
|
105
|
+
- A one-line label that can overflow: `textWrap: 'nowrap'` and
|
|
106
|
+
`textOverflow: 'ellipsis'`.
|
|
107
|
+
|
|
108
|
+
## Colour
|
|
109
|
+
|
|
110
|
+
- **Semantic tokens only**: `'$background'` for the window, `'$surface'` for
|
|
111
|
+
lists, fields and raised areas, `'$text'`, `'$textMuted'`, `'$border'` for
|
|
112
|
+
separators, `'$accent'`, `'$hoverBackground'` and `'$hoverText'` for a
|
|
113
|
+
selected row, and `'$danger'`, `'$success'`, `'$warning'`, `'$info'` for
|
|
114
|
+
status. A hard-coded grey is wrong in light or in dark.
|
|
115
|
+
- **The accent is the user's**, not yours. Use it for the primary button, the
|
|
116
|
+
selection, focus and toggles.
|
|
117
|
+
- **Colour highlights; it is not the ground.** Most of a window is neutral.
|
|
118
|
+
Big saturated panels are a web look.
|
|
119
|
+
- **Light and dark both look intended.** Shadows almost vanish in dark, so
|
|
120
|
+
separation comes from a slightly lighter surface instead.
|
|
121
|
+
|
|
122
|
+
## Spacing and sizes
|
|
123
|
+
|
|
124
|
+
- A 4 and 8 px grid: 20 px window margins (16 compact), 8 px between related
|
|
125
|
+
controls, 16–20 px between groups, 6–12 px of padding inside a row.
|
|
126
|
+
- Let the widgets size themselves: they follow the platform. Rows you draw
|
|
127
|
+
yourself: 28 px on macOS, 32–36 px elsewhere.
|
|
128
|
+
- Corners match the widgets'. `useTheme().radius` is their radius.
|
|
129
|
+
- Separators are 1 px lines in `'$border'`, not borders around every box.
|
|
130
|
+
- Align everything to a few vertical lines. Form labels are either
|
|
131
|
+
right-aligned against a column of fields or stacked above them in a narrow
|
|
132
|
+
pane.
|
|
133
|
+
|
|
134
|
+
## Interaction
|
|
135
|
+
|
|
136
|
+
- **Every input gets an answer on the event that started it**: a hover tint,
|
|
137
|
+
a distinct pressed state on mouse down, then the action on release.
|
|
138
|
+
- **Keyboard**: Tab moves focus, Space and Return activate, arrows move
|
|
139
|
+
through lists and grids, Return confirms a dialog, Escape cancels it,
|
|
140
|
+
Control+F focuses search, Delete removes the selection. The focus ring
|
|
141
|
+
shows on keyboard focus.
|
|
142
|
+
- **Selection**: one click selects (a filled row in `'$hoverBackground'`),
|
|
143
|
+
and a double click or Return opens.
|
|
144
|
+
- **Right click** shows a context menu with the item's verbs.
|
|
145
|
+
- **Dialog buttons** are named for what they do ("Delete 3 Items", not
|
|
146
|
+
"Yes"). On macOS and GNOME, Cancel comes first and the action is last, at
|
|
147
|
+
the trailing edge. On Windows and KDE the action comes first, then Cancel.
|
|
148
|
+
- **Destructive actions** can be undone, or are confirmed in a dialog whose
|
|
149
|
+
button says what it deletes.
|
|
150
|
+
- **Empty states**: a short centred line in `'$textMuted'` ("No Notes"), an
|
|
151
|
+
optional one-line explanation, and at most one button. Never a blank pane.
|
|
152
|
+
- **Success is shown in place**, not in a "Saved!" dialog. Dialogs are for
|
|
153
|
+
decisions.
|
|
154
|
+
|
|
155
|
+
## Icons
|
|
156
|
+
|
|
157
|
+
- Icons label places and actions in sidebars and toolbars. They do not
|
|
158
|
+
decorate paragraphs. Never use emoji as UI icons.
|
|
159
|
+
- Use the platform's own icons, which are monoline, sized like the text and
|
|
160
|
+
drawn in its colour: `<image src={{ symbol }} />` draws an SF Symbol on
|
|
161
|
+
macOS (`'folder'`) and an icon from the freedesktop theme on Linux
|
|
162
|
+
(`'folder-symbolic'`). A name the desktop lacks draws nothing, and Windows
|
|
163
|
+
has no icon theme to draw from. So an icon is never the only label, and an
|
|
164
|
+
app that needs icons everywhere ships its own.
|
|
165
|
+
- Affordances (chevrons, check, close, plus, more) come from `<Icon name>`,
|
|
166
|
+
which is the same everywhere.
|
|
167
|
+
|
|
168
|
+
## Motion and accessibility
|
|
169
|
+
|
|
170
|
+
- Motion is subtle and fast: 120–200 ms for hover, selection and disclosure.
|
|
171
|
+
Nothing bounces, and nothing animates on a timer the user did not start.
|
|
172
|
+
With reduced motion on, cut or cross-fade instead.
|
|
173
|
+
- Every control has a name a screen reader can say: an icon-only button needs
|
|
174
|
+
an `aria-label`. Text contrast is at least 4.5:1. Nothing is reachable by
|
|
175
|
+
mouse only. State is never shown by colour alone: add an icon or a word.
|
|
176
|
+
|
|
177
|
+
## The same, in react-x11
|
|
178
|
+
|
|
179
|
+
- **Window size and minimum**: `<window title="…" width={900} height={600}
|
|
180
|
+
minWidth={600} minHeight={400}>`
|
|
181
|
+
- **The app's name on macOS**: `createRoot({ cocoa: { appName: 'Notes' } })`:
|
|
182
|
+
what the menu bar and ⌘-Tab show. Without it they say "node".
|
|
183
|
+
- **Base type size**: `<ThemeProvider value={{ fontSize: process.platform ===
|
|
184
|
+
'darwin' ? 13 : 14 }}>` around the app.
|
|
185
|
+
- **Menu bar**: `<MenuBar menus={[{ label: 'File', items: [...] }]} />` inside
|
|
186
|
+
the window. Item: `{ label, onSelect, shortcut: [['Control', 'N']], enabled,
|
|
187
|
+
items }`, or `{ type: 'separator' }`. A check item: `toggleType:
|
|
188
|
+
'checkmark', toggleState: 0 or 1`.
|
|
189
|
+
- **A shortcut with no menu item**: `useAccelerator([['Control', 'K']],
|
|
190
|
+
handler)`
|
|
191
|
+
- **Sidebar**: a `<box>` with `width: 220`, `backgroundColor: '$surface'`,
|
|
192
|
+
`borderEndWidth: 1`, `borderColor: '$border'`, `padding: 8`, `gap: 2`. Rows:
|
|
193
|
+
`flexDirection: 'row'`, `alignItems: 'center'`, `gap: 8`, `paddingInline:
|
|
194
|
+
8`, `borderRadius: 6`, `':hover': { backgroundColor: '$surfaceHover' }`, and
|
|
195
|
+
when selected `backgroundColor: '$hoverBackground'` with text in
|
|
196
|
+
`'$hoverText'`. Section headers: base −2, `fontWeight: 600`, `'$textMuted'`.
|
|
197
|
+
See `examples/finder.jsx` and `settings.jsx`.
|
|
198
|
+
- **Resizable split**: `<SplitPane direction="row" defaultSize={240}
|
|
199
|
+
min={180}>` with two children.
|
|
200
|
+
- **Toolbar**: a row under the title bar: `flexDirection: 'row'`, `alignItems:
|
|
201
|
+
'center'`, `gap: 8`, `height: 44`, `paddingInline: 12`, `borderBottomWidth:
|
|
202
|
+
1`, `borderColor: '$border'`. Icon buttons in it, then `<textinput
|
|
203
|
+
placeholder="Search" />` last.
|
|
204
|
+
- **Buttons**: `<Button onPress={…}>`. `primary` is the default action, one
|
|
205
|
+
per view. `variant="outline"` for a secondary one, `variant="ghost"
|
|
206
|
+
size="small"` for toolbar and inline icon buttons. Never size a `Button`
|
|
207
|
+
yourself: `style` sizes its slot.
|
|
208
|
+
- **Other controls**: `Checkbox`, `Switch`, `RadioGroup` and `Radio`,
|
|
209
|
+
`Slider`, `Select` (`options`, `value`), `ProgressBar`, `Tooltip label="…"`,
|
|
210
|
+
`<textinput placeholder>`, `<textarea>`. Every `onChange(ev)` gives
|
|
211
|
+
`ev.value`.
|
|
212
|
+
- **Tabs**: `<Tabs items={[{ id, label, content }]} />`
|
|
213
|
+
- **Modal dialog**: `<Dialog open title="…" onClose={…}
|
|
214
|
+
actions={<>…buttons…</>}>`: a real window that keeps focus. Escape and the
|
|
215
|
+
close button call `onClose`. `autoFocus` on its first field.
|
|
216
|
+
- **Context menu and popover**: `<ContextMenu items={[…]}>` around the node it
|
|
217
|
+
belongs to. `<popup anchor={{ to: ref, placement: 'bottom' }}>` for a custom
|
|
218
|
+
popover.
|
|
219
|
+
- **Lists, tables, trees**: `Table` (`columns: [{ id, label, width, value,
|
|
220
|
+
align }]`, `rows`, `onActivate`) for sortable, virtualized data. `Tree` from
|
|
221
|
+
`@react-x11/components/tree`. Draw short lists yourself.
|
|
222
|
+
- **Hover and press on your own box**: `cursor: 'pointer'`, `':hover': {
|
|
223
|
+
backgroundColor: '$surfaceHover' }`, `':active': { backgroundColor:
|
|
224
|
+
'$surfaceActive' }`, `transition: 120`.
|
|
225
|
+
- **Focus and keys**: `focusable` (and `tabIndex`) on a custom control, and
|
|
226
|
+
the focus ring comes with it. `onKeyDown={(ev) => ev.keysym === XK_DOWN &&
|
|
227
|
+
…}` with keysyms from `react-x11/keysyms`. `role` and `aria-label` so a
|
|
228
|
+
screen reader can name it.
|
|
229
|
+
- **Light, dark, reduced motion**: tokens follow the desktop.
|
|
230
|
+
`useSystemAppearance()` gives `{ colorScheme, accent, reducedMotion, … }`,
|
|
231
|
+
and `transition: reducedMotion ? 0 : 150` respects it.
|
|
232
|
+
- **A tinted alert**: `backgroundColor: tint(theme.danger, 0.12)` with `tint`
|
|
233
|
+
from `react-x11/style` and `theme` from `useTheme()`, and `'$danger'` for
|
|
234
|
+
its text.
|
|
235
|
+
- **Files, notifications, settings**: `useFileDialog()` gives `openFile()` and
|
|
236
|
+
`saveFile()`, which resolve to paths or `null`. `useNotifier()` gives
|
|
237
|
+
`notify({ summary, body })`: check `available` first and never depend on it.
|
|
238
|
+
`createSettings({ appId, defaults })` remembers state.
|
|
239
|
+
- **Charts**: `@react-x11/components/charts`, at least 200 px tall, with
|
|
240
|
+
series in `'$accent'`, `'$info'`, `'$success'`, and a tooltip.
|
|
241
|
+
|
|
242
|
+
## What makes it look like a website (avoid)
|
|
243
|
+
|
|
244
|
+
- **A hero header**: a big title and tagline at the top of the window. The
|
|
245
|
+
window's title is in the title bar, and content starts with content.
|
|
246
|
+
- **Card soup**: every item in a rounded, shadowed, padded card on a grey
|
|
247
|
+
page. Use lists with separators and selection. Keep cards for things that
|
|
248
|
+
are visual: a gallery, a dashboard tile.
|
|
249
|
+
- **A centred landing page**: content in a narrow column with empty sides.
|
|
250
|
+
Fill the window and align to the leading edge.
|
|
251
|
+
- **Phone and web patterns**: hamburger menus, bottom tab bars, floating
|
|
252
|
+
action buttons. Use the menu bar, a sidebar, a toolbar.
|
|
253
|
+
- **Wide buttons**: the column-stretch trap, and buttons styled as links.
|
|
254
|
+
Buttons are compact and sit in a row at the trailing edge or in a toolbar.
|
|
255
|
+
- **Gradients, glows, neon, emoji icons, big shadows**, and everything
|
|
256
|
+
rounded to 16 px or more.
|
|
257
|
+
- **A palette of your own that ignores dark mode**, or replaces the user's
|
|
258
|
+
accent.
|
|
259
|
+
- **Sample data that looks fake**: "Lorem ipsum", "Item 1, Item 2". Seed 10–30
|
|
260
|
+
plausible, varied records. It is the cheapest polish there is.
|
|
261
|
+
- **Buttons that do nothing.** Cut them, or disable them and say why.
|
|
262
|
+
|
|
263
|
+
## Before calling it done
|
|
264
|
+
|
|
265
|
+
Look at a picture of every screen, in light and in dark. Is there one
|
|
266
|
+
obvious primary area? Do the leading edges line up, and are the row heights
|
|
267
|
+
consistent? Is there one accent-coloured thing per region, and is nothing
|
|
268
|
+
clipped? If it looks like part of the operating system rather than a
|
|
269
|
+
landing page, it is done.
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
//
|
|
9
9
|
// `appearance` the light/dark, accent, contrast and reduced-motion ladder
|
|
10
10
|
// (src/appearance.js) — the settings portal, then macOS, then
|
|
11
|
-
// XSETTINGS
|
|
11
|
+
// XSETTINGS — and, on the Wayland backend, the interaction
|
|
12
|
+
// settings read off the same portal (src/desktopsettings.js)
|
|
12
13
|
// `a11y` the AT-SPI bridge, started from `createRoot()`
|
|
13
14
|
// (src/a11y.js, docs/accessibility.md)
|
|
14
15
|
// `globalMenu` a `MenuBar` handing its menu to the panel instead of drawing
|