wings-plugin-ui 0.0.0-stage → 0.1.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.
Files changed (6) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +179 -2
  3. package/package.json +44 -4
  4. package/plugin-ui.css +1292 -0
  5. package/ui.d.ts +276 -0
  6. package/ui.js +578 -0
package/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Josip Papež
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,180 @@
1
- # Temporary Holding Version
1
+ # wings-plugin-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ React components for [Wings](https://github.com/josippapez/Wings) plugin pages, and the stylesheet Wings draws them
4
+ with. The components are Base UI's, like the Wings app's own shadcn/ui ones, with shadcn's names and variants.
5
+ Build and pack the plugin with [wings-plugin-sdk](../plugin-sdk/README.md).
6
+
7
+ ```sh
8
+ npm install --save-dev wings-plugin-sdk wings-plugin-ui react react-dom @types/react @types/react-dom
9
+ ```
10
+
11
+ `wings-plugin-ui` lets a page say what it shows and where, and Wings decides how it looks. A page built from
12
+ it never touches the DOM, writes HTML or lays itself out with CSS, so when Wings moves or redesigns a place,
13
+ your page follows without a release.
14
+
15
+ ```tsx
16
+ import { Card, Gauge, Heading, showSidebar, useMainState } from "wings-plugin-ui";
17
+
18
+ type Usage = { type: "usage"; percent: number };
19
+
20
+ function Usage() {
21
+ // Says hello to your main script once, then shows the newest `usage` message it broadcasts.
22
+ const usage = useMainState<Usage>("usage");
23
+ if (!usage) return null;
24
+ return (
25
+ <>
26
+ <Heading>Usage</Heading>
27
+ <Card>
28
+ <Gauge label="Today" percent={usage.percent} levels={[75, 90]} />
29
+ </Card>
30
+ </>
31
+ );
32
+ }
33
+
34
+ showSidebar(<Usage />);
35
+ ```
36
+
37
+ ## Places
38
+
39
+ `showSidebar`, `showPopover`, `showView`, `showTab` and `showSettings` take any React element, your own
40
+ components too, and mount it in that place's shell: Wings' padding, spacing and scrolling for a sidebar, a
41
+ popover, a view (in a modal or a window), a tab or the settings page. Call one per page. `{ fill: true }` lays
42
+ the page out edge to edge with no padding, for a page that scrolls on its own, like a canvas.
43
+
44
+ You don't write the page's HTML. For `src/sidebar.tsx` that calls one of these, `wings-plugin build` writes
45
+ `sidebar.html` with a mount point, its CSS and its script, titled like the manifest's page. A `sidebar.html` you
46
+ write yourself wins: the build only writes a page that's missing or that it wrote before (it carries a
47
+ `Generated by wings-plugin build` line). A popover's page takes its sidebar's title, the name Wings gives the
48
+ popover too. A page that builds its DOM itself, without React, gets the same shell
49
+ from `mountShell(place, options)` in `wings-plugin-sdk/shell`, which returns the element to fill.
50
+
51
+ ## Talking to main
52
+
53
+ `useMainState<T>(type, hello?)` returns the newest message of `type` your plugin's other pages broadcast, or
54
+ `null` until one comes. It sends `hello` (`{ type: "hello" }` by default, `null` for none) once when the
55
+ component mounts, so main answers with what it has. `useBroadcast(listener)` from `wings-plugin-sdk/react` gets every
56
+ message.
57
+
58
+ ## Components
59
+
60
+ | Component | Props | Renders |
61
+ |---|---|---|
62
+ | `Row`, `Stack` | `gap` (`none`, `xs`, `s`, `m`, `l`), `grow`; `Row` also `align`, `justify`, `wrap`; `as` (`form`, `ul`, `ol` too) | Children side by side or one under the other; as a `ul` or `ol`, a list with no bullets. With `as`, the props and handlers are the element's: `<Row as="form" onSubmit={(e) => e.currentTarget.requestSubmit()}>` has a `HTMLFormElement` |
63
+ | `Text` | `tone` (`muted`, `info`, `success`, `warn`, `danger`), `size` (`small`, `tiny`), `weight` (`medium`, `strong`), `mono`, `numeric`, `truncate`, `clamp`, `grow`, `as` | A span, or the element in `as` |
64
+ | `Title` | `as` (`h1`, `h2`, `h3`), `size` (`large`), `truncate` | A page's or a card's title, like a sprint's name or a setup step's question |
65
+ | `Heading` | `level` (2, 3, 4), `actions` | A small section heading, `h2` by default, with chips or a button on its right |
66
+ | `Card` | `compact`, `as` (`form`, `ul`, `ol` too) | A card, a card that's a form, or a list. Typed for its `as` element like `Row` and `Stack` |
67
+ | `Gauge`, `Meter` | `label`, `percent`, `note`, `tone` or `levels` | A labelled bar with its percentage, or the bar alone, with the meter role |
68
+ | `Stats` | `items: { label, value, tone? }[]` | Small figures side by side, label over value |
69
+ | `Properties` | `items: { label, value }[]` | Label and value pairs |
70
+ | `List`, `ListItem`, `ListButton` | `ordered` and `compact` on `List`, `align` on the rows | A list of rows; a `ListButton` in a `ListItem` makes the row a button |
71
+ | `Dot`, `Spinner` | `tone`, `size`, `label`; `Dot` also `color` | A status dot or a spinner, announced only with a `label`. A dot's `color` comes from your data, like a work item type's `#cc293d` |
72
+ | `Pill`, `Tag` | `tone` | A filled pill, or an outlined tag like a branch |
73
+ | `Loading`, `Empty` | `Empty`: `title`, `detail` | Before the first data, and nothing to show |
74
+ | `Button` | `variant` (`primary`, `ghost`, `danger`), `size` (`small`), `busy` | A button, `type="button"`; `busy` shows a spinner and ignores clicks, keeping focus |
75
+ | `Chip`, `Chips` | `pressed`; `Chips`: `label`, `options`, `value`, `onChange` | A small quiet button, and a group of them that picks one, with `aria-pressed`. An option's other props, like `id`, go to its chip |
76
+ | `Tabs` | `label`, `items`, `value`, `onChange`, `children` | Tabs with arrow keys, Home and End, over their panel |
77
+ | `Field` | `label`, `hint`, `error`, `id`, `inline` | A labelled field that names and describes the input in it. `id` becomes the input's, for a page that puts focus back by id; `inline` puts the label beside it |
78
+ | `FieldGroup` | `label`, `hint`, `error` | Several controls under one label, like checkboxes: a `fieldset` with its `legend`, described by the hint; an error marks each checkbox invalid |
79
+ | `TextInput`, `SearchField`, `Select` | `label`; `Select`: `options`, `value`, `onChange` | Inputs Wings styles; outside a `Field`, `label` names them |
80
+ | `Checkbox` | `checked`, `onChange`, `children` | A checkbox with its label |
81
+ | `Combobox` | `label`, `value`, `onInput`, `options: { value, label, detail? }[]`, `onPick`, `onEnter`, `onKeyDown`, `freeText`, `openOnFocus`, `open`, `filter`, `empty` | A text field with suggestions under it: arrows, Enter, Escape and Tab, and a press outside closes it. With `freeText` it keeps what you type; otherwise it picks one of `options`. `openOnFocus={false}` for a page that puts focus back into it after a render. Without `freeText` the picked label is selected when the field gets focus and after a pick, so typing replaces it. `onEnter(text)` runs on Enter when no option is highlighted, with the typed text, and stops the Enter from submitting a form around it. An `onKeyDown` you pass runs first; `e.preventDefault()` in it skips the combobox's own keys. ArrowDown on a closed combobox opens it and highlights the first option, as in the [WAI-ARIA APG combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/). `open="whileText"` keeps the list shown for as long as the field has text and an option matches, even after Escape, Tab or a press outside (those only clear the highlighted option) and it closes when the text is emptied or an option is picked; `aria-expanded` follows whether the list is shown. Accessibility caveat: the APG pattern has Escape dismiss the popup and treats it as shown only while the user is working in the combobox, so this mode departs from it. Use it only where the suggestions are the page's main content, such as a people search that is useful to keep in view, and not for a field where a stray list would cover other controls |
82
+ | `Menu` | `label`, `items: { id, label, onSelect, tone? }[]`, `align`, `variant`, `size` | A button that opens a menu: arrows, Home, End, Enter, Escape |
83
+ | `Disclosure` | `summary` | More behind a line that opens it |
84
+
85
+ Every component passes other props, like `id`, `title`, `className`, `onClick` and `aria-*`, to its element.
86
+ `TextInput` and `SearchField` pass `ref` to the input on React 19.
87
+
88
+ A button that's working on something is `busy`: it shows a spinner, ignores clicks and says so with `aria-busy`
89
+ and `aria-disabled`, and unlike `disabled` it keeps the keyboard focus:
90
+
91
+ ```tsx
92
+ <Button variant="primary" busy={saving} onClick={save}>
93
+ Save
94
+ </Button>
95
+ ```
96
+
97
+ ## Ours or your own
98
+
99
+ The components are there for a page that looks like the rest of Wings without effort, not a rule. Write your
100
+ own components next to them, and your own CSS: import it from the page, `import "./sidebar.css"`, and the build
101
+ bundles it into `sidebar.css` and links it from the generated page. Match Wings with its variables, which every
102
+ page has:
103
+
104
+ | Variable | What |
105
+ |---|---|
106
+ | `--wings-fg`, `--wings-muted` | Text, and quieter text |
107
+ | `--wings-surface`, `--wings-hover`, `--wings-pressed`, `--wings-input` | Card, hovered, pressed and input backgrounds |
108
+ | `--wings-hairline`, `--wings-hairline-strong` | Borders |
109
+ | `--wings-ring` | The focus ring |
110
+ | `--wings-primary`, `--wings-primary-fg` | The primary button, and its text |
111
+ | `--wings-info`, `--wings-success`, `--wings-warning`, `--wings-danger` | Tones |
112
+ | `--wings-radius` | A card's corners |
113
+ | `--wings-menu-bg`, `--wings-menu-ring`, `--wings-menu-active`, `--wings-shadow` | A raised layer like a menu or a list of suggestions, as Wings' own menus draw it: its opaque background, its edge, the highlighted option and its shadow |
114
+ | `--wings-font`, `--wings-mono` | Fonts |
115
+
116
+ ```tsx
117
+ // src/sidebar.tsx
118
+ import "./sidebar.css";
119
+
120
+ function Banner({ children }: { children: string }) {
121
+ return <div className="banner">{children}</div>;
122
+ }
123
+ ```
124
+
125
+ ```css
126
+ /* src/sidebar.css */
127
+ .banner {
128
+ padding: 8px 10px;
129
+ border-radius: var(--wings-radius);
130
+ background: color-mix(in srgb, var(--wings-info) 14%, transparent);
131
+ color: var(--wings-info);
132
+ }
133
+ ```
134
+
135
+ Wings' base styles cover plain elements (buttons, inputs, selects and the page's font and colours), the `w-*`
136
+ classes and two older helpers, `.muted` and `.card`; nothing else, so your other classes are yours. Your CSS
137
+ loads after Wings', so it wins where you style a component, like `.my-list .w-list-button { height: 24px; }`.
138
+
139
+ ## The class contract
140
+
141
+ The components render plain elements with `w-*` class names and data attributes, like
142
+ `<div class="w-card" data-size="compact">`, and no styles of their own. Wings styles them in its
143
+ `plugin-ui.css`, which ships with the app, so a Wings update restyles every plugin. The names are a versioned
144
+ public contract. `Meter` sets `--w-value`, how full it is, and a `Dot` with a `color` sets `--w-color`; both are data
145
+ rather than styles.
146
+
147
+ `w-shell` with `data-place` (`sidebar`, `popover`, `view`, `tab`, `settings`) and `data-fill`; `w-row`,
148
+ `w-stack` with `data-gap`, `data-align`, `data-justify`, `data-wrap`, `data-grow`; `w-text` with `data-tone`,
149
+ `data-size`, `data-weight`, `data-mono`, `data-numeric`, `data-truncate`, `data-clamp`, `data-grow`;
150
+ `w-title` with `data-size`; `w-heading`, `w-heading-row`; `w-card` with `data-size`; `w-gauge`, `w-gauge-label`, `w-gauge-value`,
151
+ `w-gauge-note`, `w-meter` with `data-tone`, `w-meter-fill`; `w-stats`, `w-stat-label`, `w-stat-value`;
152
+ `w-props`, `w-prop`, `w-prop-label`, `w-prop-value`; `w-list` with `data-density`, `w-list-item`,
153
+ `w-list-button` with `data-align`; `w-dot` with `data-tone` or `data-color`; `w-pill`, `w-tag` with `data-tone`; `w-spinner` with `data-size`;
154
+ `w-loading`, `w-empty`, `w-empty-detail`; `w-button` with `data-variant`, `data-size`, `aria-disabled`; `w-chips`, `w-chip`;
155
+ `w-tabs`, `w-tab`; `w-field` with `data-layout`, `w-field-row`, `w-field-label`, `w-field-hint`,
156
+ `w-field-group`, `w-input`, `w-search`, `w-select`, `w-check`; `w-combobox`, `w-listbox`, `w-option`,
157
+ `w-option-label`, `w-option-detail`, `w-option-empty`; `w-menu-root`, `w-menu` with `data-align`, `w-menu-item`
158
+ with `data-tone`; `w-disclosure`. A `ul` or `ol` with `w-row`, `w-stack` or `w-card` loses its bullets.
159
+
160
+ ## Tailwind or any CSS
161
+
162
+ Wings' styles sit in CSS cascade layers, declared before anything else on the page, in this order:
163
+ `wings-base, theme, base, wings-components, components, utilities`. Any CSS a page brings that isn't in a layer
164
+ wins over all of Wings'. Tailwind v4 fits in as it is: `@import "tailwindcss";` puts its reset in `base`, under the
165
+ components here, and its utilities in `utilities`, over them. So a page can use Tailwind for its own parts next to
166
+ these components, and both look right. Build Tailwind's CSS with its CLI before `wings-plugin build` and import
167
+ it from the page. Rem units mean 16px, the browser's root size, while the page's text is 13px.
168
+
169
+ ## Linked packages
170
+
171
+ With the packages linked from a local folder (`npm link`, or `link:` in `package.json`) rather than installed,
172
+ TypeScript looks for React next to their folder. Point it at yours in `tsconfig.json`; the build already
173
+ does:
174
+
175
+ ```json
176
+ "paths": {
177
+ "react": ["./node_modules/@types/react"],
178
+ "react/*": ["./node_modules/@types/react/*"]
179
+ }
180
+ ```
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "wings-plugin-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "React components and the stylesheet for Wings plugin pages, on Base UI like the Wings app's shadcn/ui components.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "types": "./ui.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./ui.d.ts",
11
+ "default": "./ui.js"
12
+ },
13
+ "./plugin-ui.css": "./plugin-ui.css"
14
+ },
15
+ "files": [
16
+ "ui.js",
17
+ "ui.d.ts",
18
+ "plugin-ui.css",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/josippapez/Wings.git",
25
+ "directory": "packages/plugin-ui"
26
+ },
27
+ "scripts": {
28
+ "test": "tsc -p tsconfig.json && node --test"
29
+ },
30
+ "dependencies": {
31
+ "@base-ui/react": "^1.8.0"
32
+ },
33
+ "peerDependencies": {
34
+ "react": ">=19",
35
+ "react-dom": ">=19",
36
+ "wings-plugin-sdk": "^0.6.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "^19.1.8",
40
+ "jsdom": "^30.1.2",
41
+ "react": "^19.1.0",
42
+ "react-dom": "^19.1.0",
43
+ "typescript": "~6.0.3",
44
+ "wings-plugin-sdk": "link:../plugin-sdk"
45
+ }
46
+ }