@gemboss/ui 0.7.1 → 0.8.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 +88 -6
- package/catalog/index.html +71 -34
- package/gemboss-ui.css +79 -31
- package/package.json +3 -1
- package/src/assistant/dock.ts +19 -18
- package/src/assistant/host.tsx +40 -5
- package/src/assistant/provider.tsx +43 -19
- package/src/kit-plain.css +31 -12
- package/src/portal.css +14 -62
- package/src/portal.tsx +24 -12
- package/src/theme-dark.css +65 -0
- package/src/ui.css +23 -13
package/README.md
CHANGED
|
@@ -85,17 +85,27 @@ A live run is never thrown off screen (`assistant/dock.ts` holds that rule). The
|
|
|
85
85
|
its top edge and slides along the bottom, by pointer or keyboard, and remembers both under
|
|
86
86
|
`storagePrefix`.
|
|
87
87
|
|
|
88
|
-
The side panel is
|
|
89
|
-
|
|
88
|
+
The side panel is the DEFAULT shape since 0.8.0 (Chris, 04/10/2026): a browser that never chose opens the assistant
|
|
89
|
+
there, and `allowSide` defaults to true — a surface whose layout cannot make room passes `allowSide={false}` and gets
|
|
90
|
+
the popup. The popup header has "Pin to the side", and the panel carries "Float as a popup" back; the choice is kept
|
|
91
|
+
under `<prefix>.shape` (the older `floatShape` key recorded the old default for everyone, so it is not read). The rail
|
|
92
|
+
link reopens in that shape unless the person last worked full screen. A popup opens as tall as the window allows,
|
|
93
|
+
12px under the topbar (it was min(480, viewport − 176)); a height the person dragged to is kept, and dragging it back
|
|
94
|
+
to the top returns to "as tall as the window allows".
|
|
95
|
+
|
|
96
|
+
**Upgrading to 0.8.0:** a host that never passed `allowSide` now offers the panel. Its page must make room — render
|
|
97
|
+
inside `AppShell` (`.gemboss-platform-main` takes the margin) or pad by `var(--gb-assistant-side-w)` while
|
|
98
|
+
`<prefix>-side` is on `<body>` — or pass `allowSide={false}`. A full-screen "shrink" handler should call `reopen()`
|
|
99
|
+
(back to the working shape), not `openDock()`, which records "popup" as the person's choice. It stands under the topbar on the right, and
|
|
90
100
|
the page moves over for it instead of being covered: the provider puts `<prefix>-side` on `<body>` and
|
|
91
101
|
the width in `--gb-assistant-side-w` (`SIDE_WIDTH_VAR`), and `.gemboss-platform-main` takes that as
|
|
92
102
|
`margin-right`. A surface with its own layout reads the same variable. Its left edge is a
|
|
93
103
|
`.ui-resizer` (drag, or ←/→, Shift for 64px, Home/End), clamped to 380–720px and never leaving the page
|
|
94
104
|
less than 480px; the width is remembered. Panel and page only fit side by side from 1100px (rail 240
|
|
95
|
-
+ 380 + 480). Between 761 and 1099 the panel covers the content area and is not offered
|
|
96
|
-
it takes the screen. The status bar, a busy ×, leaving full screen mid-run and full
|
|
105
|
+
+ 380 + 480). Between 761 and 1099 the panel covers the content area and is not offered — the host
|
|
106
|
+
withdraws `allowSide` there (`SIDE_FITS_QUERY`), so even the default opens the popup; on a phone it takes the screen. The status bar, a busy ×, leaving full screen mid-run and full
|
|
97
107
|
screen's own way back ("Back to the side panel") all return to whichever shape the person last
|
|
98
|
-
worked in (`floatShape`, remembered across reloads), so a pinned panel never pops back as a popup.
|
|
108
|
+
worked in (`floatShape`, remembered across reloads under `<prefix>.shape`), so a pinned panel never pops back as a popup.
|
|
99
109
|
The host registers `allowSide` with the provider, and every way into the panel checks it, including
|
|
100
110
|
a "side" remembered in storage: a host without it gets the popup.
|
|
101
111
|
|
|
@@ -201,6 +211,7 @@ surfaces on the package, the same gaps were filled six ways. What 0.5.0 settles,
|
|
|
201
211
|
|---|---|---|
|
|
202
212
|
| Control height: `gemboss-field`, `ui-select`, `ui-btn`, `ui-seg` | **32px** (Polaris TextField / Select), `--ui-control-h` | **44px**, `ui-btn--sm` included |
|
|
203
213
|
| `ui-btn--sm` | 28px | 44px |
|
|
214
|
+
| `ui-btn` inside a popup's head or foot (0.7.3) | 28px, 12px | 44px |
|
|
204
215
|
| Focus ring, every control | 2px iris `--ui-focus` (= `--gemboss-accent`, the admin's `--p-color-border-focus`) | same |
|
|
205
216
|
| Body text (`html`, `body`, at zero specificity) | 13px / 20px, weight 450, `--gemboss-text`, antialiased (Polaris bodyMd) | same |
|
|
206
217
|
| Kit text (tabs, table, notice, pager, empty, page-head lead) | `--ui-fs-body`, 13px | 16px in fields (iOS zoom) |
|
|
@@ -248,6 +259,42 @@ If your surface still sets any of these itself (a `body { font-size }`, a `min-h
|
|
|
248
259
|
cascade over the package's zero-specificity base. `packages/ui/src/kit-look.test.ts` measures the table above in
|
|
249
260
|
Chromium on the built CSS.
|
|
250
261
|
|
|
262
|
+
## Popup density (0.7.3)
|
|
263
|
+
|
|
264
|
+
The kit popup (`.ui-overlay > .ui-modal`, `<dialog class="ui-modal">`, `openModal`, `Dialog`) is drawn at Polaris
|
|
265
|
+
Modal's density, the popup the admin opens. Chris, 04/10/2026, on GemFactory's detail popup: the admin's buttons are
|
|
266
|
+
smaller and save room (GemFeedback #1099). Measured at 1280 with a mouse:
|
|
267
|
+
|
|
268
|
+
| | Polaris Modal (admin) | 0.7.1 / 0.7.3 | 0.7.3 |
|
|
269
|
+
|---|---|---|---|
|
|
270
|
+
| × | 28×28 | 44×44 at every pointer | **28×28**; 44×44 under a thumb or ≤ 767px wide (head stays 53) |
|
|
271
|
+
| Head | 53px, padding 12/16 | 61px, padding 16/20 | **53px**, padding 12/16, `align-items: center` |
|
|
272
|
+
| Title | 14px / 650 | 15px / 620 | **14px / 650** |
|
|
273
|
+
| `ui-btn` in `__head` / `__foot` | 28 tall, 12px, padding 0/12 | 32, 13px, 0/16 | **28, 12px, 0/12**; 44 under a thumb or ≤ 767px |
|
|
274
|
+
| Body / foot padding | 16 / 16 | 16/20 / 12/20 | **16 / 12/16** |
|
|
275
|
+
| Scrim | rgba(0,0,0,.5), no blur | .6, blur 2px | **.5, no blur** (`::backdrop` too) |
|
|
276
|
+
|
|
277
|
+
Popup only: `--ui-control-h` stays 32, and a button outside a popup is still 32 tall.
|
|
278
|
+
|
|
279
|
+
`.ui-modal__body` and `.ui-modal__foot` moved from `kit-plain.css` to `ui.css`. The admin loads `ui.css` and not
|
|
280
|
+
`kit-plain.css`, so before 0.7.3 its kit popups drew a body with no padding and two foot buttons touching
|
|
281
|
+
(AutopilotConfirmModal). The scrolling layout of a tall popup stays in `kit-plain.css`.
|
|
282
|
+
|
|
283
|
+
A large popup can keep its subtitle and its own actions in the kit head instead of drawing a second bar
|
|
284
|
+
(`kit-plain.css`):
|
|
285
|
+
|
|
286
|
+
```html
|
|
287
|
+
<div class="ui-modal__head">
|
|
288
|
+
<div class="ui-modal__title">Mega menu<span class="ui-modal__subtitle">chrome/header</span></div>
|
|
289
|
+
<div class="ui-modal__actions"><button class="ui-btn">Copy</button><button class="ui-btn ui-btn--icon" aria-label="More">…</button></div>
|
|
290
|
+
<button type="button" class="ui-modal__close" aria-label="Close">…</button>
|
|
291
|
+
</div>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`packages/ui/src/modal-density.test.ts` measures the table in Chromium at 1280 (mouse), 390 and under a finger, on
|
|
295
|
+
`gemboss-ui.css` and on the admin's sheets alone. `UI_MODAL_ROOT=<a 0.7.1 tree>` runs it against the old CSS, where
|
|
296
|
+
it is red.
|
|
297
|
+
|
|
251
298
|
## Resizable panels
|
|
252
299
|
|
|
253
300
|
`ui-resizer` is the seam a person drags between two panels; `ui-resizing` on `<body>` is the drag in
|
|
@@ -390,7 +437,7 @@ import { Avatar, Dialog, Leaderboard, Podium, SearchDialog, ThemeToggle, themeSc
|
|
|
390
437
|
- `Dialog`: the kit's native `<dialog class="ui-modal">` (see kit-plain.css) driven by `open`; a
|
|
391
438
|
backdrop click closes it, focus returns to the opener.
|
|
392
439
|
- `ThemeToggle` + `themeScript`: `data-theme="dark|light"` on <html>, remembered in localStorage
|
|
393
|
-
(`gemboss-theme`), the OS's choice until then. `
|
|
440
|
+
(`gemboss-theme`), the OS's choice until then. `theme-dark.css` (in `gemboss-ui.css`) redefines every `--gemboss-*` colour under
|
|
394
441
|
`[data-theme="dark"]` (a test fails if a colour token is left out), so every kit class follows.
|
|
395
442
|
Opt-in: a surface without it (the admin) stays light.
|
|
396
443
|
- `Avatar` (24 · 32 · 40 · 56): picture or initials on one of eight tones picked from the name.
|
|
@@ -398,6 +445,16 @@ import { Avatar, Dialog, Leaderboard, Podium, SearchDialog, ThemeToggle, themeSc
|
|
|
398
445
|
all when empty. `Podium`: the first three, second · first · third.
|
|
399
446
|
- `initialsOf` moved to `initials.ts` (plain, not "use client") so server components can call it; it is
|
|
400
447
|
still exported from the package and from account.tsx.
|
|
448
|
+
- **Without the whole kit (0.7.2):** a surface with its own skin (GemCommunity) takes only these pieces'
|
|
449
|
+
CSS: `@gemboss/ui/gemboss-tokens.css` (variables only, no rule) + `@gemboss/ui/portal.css`. `Avatar`,
|
|
450
|
+
`Leaderboard` and `Podium` read nothing but `--gemboss-*` tokens (a test holds that), so they look the
|
|
451
|
+
same as on GemFeedback without `gemboss-ui.css` restyling the rest of the page. `SearchDialog` and
|
|
452
|
+
`Dialog` still need `ui.css` (the modal). `portal.css` holds no rule on `:root`, `html` or `body`; the dark palette is its own file,
|
|
453
|
+
`theme-dark.css`, so a surface that uses `data-theme` for something else (GemCommunity's sign-in pages) is not
|
|
454
|
+
turned dark by importing the leaderboard.
|
|
455
|
+
- Rows take `current` (the viewer's own row: tinted, and read as "Minh, you" via `currentLabel`), `badge`
|
|
456
|
+
(after the name, e.g. a level) and `id` (a `#me` anchor). `title` may be left out on a page whose own
|
|
457
|
+
`<h1>` names the board; pass `label` then. Screen-reader text uses `.gb-sr-only`, styled in portal.css.
|
|
401
458
|
|
|
402
459
|
## Report pages (0.4.0)
|
|
403
460
|
|
|
@@ -418,6 +475,31 @@ surface computes every number and ratio; the kit only draws. Markup for each is
|
|
|
418
475
|
- `ui-grid--2` / `--3`: card grid that drops to one column; `ui-section-head`: an `h2` + note between page head and
|
|
419
476
|
card titles.
|
|
420
477
|
|
|
478
|
+
## Long lists: pages or "load more" (0.7.3)
|
|
479
|
+
|
|
480
|
+
*Recommendation, not a rule: Claude, 04/10/2026, after Chris asked it to decide for GemFactory.*
|
|
481
|
+
|
|
482
|
+
| List | Pattern | Why |
|
|
483
|
+
|---|---|---|
|
|
484
|
+
| A work list: items with an #ID, filters, a review queue, links people send each other (GemFactory board) | **Pages**, `ui-pager` with numbers, position in the URL (`?p=3`) | Back returns to the same place, a link opens the same page, the total is visible. Infinite scroll loses all three. |
|
|
485
|
+
| One column of a Kanban (GemFactory), or a feed read top to bottom | **Load more**, `ui-more` | Nobody jumps to page 9 of one column or of a feed. |
|
|
486
|
+
|
|
487
|
+
`ui-pager`: `<a rel="prev">` · page links · `<span class="ui-pager__gap" aria-hidden="true">…</span>` · the current page as
|
|
488
|
+
`<a aria-current="page">` (filled) · `<a rel="next">` · `<span class="ui-pager__range">201–250 of 655</span>` (pushed right).
|
|
489
|
+
On the first or last page the edge is `<a role="link" aria-disabled="true">` with no `href`: a screen reader still reads a
|
|
490
|
+
link and says it is unavailable (a `<span aria-disabled>` has no role, so it reads as plain text). It is muted and does
|
|
491
|
+
not react to hover. Page links are at least 32x32, 44x44 under a finger. Arrow and word are two flex items, hence the 4px
|
|
492
|
+
gap. Under 576px only Previous · the current page · Next stay and the range wraps under them (eleven 44px targets
|
|
493
|
+
took three to four rows at 320). The old form `<span aria-current="page">2 / 9</span>` keeps its muted look. The surface computes which numbers to
|
|
494
|
+
show (first, last, current ± 1, a gap only where it hides two or more pages: GemFactory `pageWindow`).
|
|
495
|
+
|
|
496
|
+
`ui-more`: one centred row. Idle: a `ui-btn` ("Show 24 more"). Loading: `<span class="ui-spinner" aria-hidden="true">` +
|
|
497
|
+
`<span role="status">Loading…</span>`. Error: `<span role="alert">Couldn't load.</span>` + "Try again". End: a line. As an
|
|
498
|
+
IntersectionObserver sentinel it has height from its padding; zero the padding and it has none.
|
|
499
|
+
|
|
500
|
+
`.ui-prose` links are at `:where()` specificity, so a component's own link rule (`ui-pager`, `ui-seg`, `ui-tabs`) wins
|
|
501
|
+
inside a page-wide `.ui-prose` (GemFactory wraps every page in it; its page numbers were drawn as blue underlined text).
|
|
502
|
+
|
|
421
503
|
## The catalog: every token, class and component, with its markup
|
|
422
504
|
|
|
423
505
|
`npm run catalog` builds `catalog/index.html` (also shipped in the npm tarball, and built by `prepack`):
|