@gemboss/ui 0.40.0 → 0.41.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/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  Versions up to 0.9.0 are described in the README, each in the section that introduced it.
4
4
 
5
+ ## 0.41.0 (20/10/2026)
6
+
7
+ **A document read over its list** (Chris 20/10/2026, after GemMedia's and partner-radar's Docs drew the same popup with
8
+ their own CSS: "làm đi").
9
+
10
+ Added
11
+ - `ReaderDialog`: the kit popup sized for reading, `min(1440px, 94vw)` × `min(92dvh, 1200px)`, the whole screen under
12
+ 768px with the head's actions on their own line. It is `Dialog` with `ui-reader` (same props, less `wide`). The body has
13
+ no padding and is a column the document fills: `ui-reader__frame` (an iframe) or `ui-reader__scroll` (a padded scroller
14
+ of the surface's own). README "ReaderDialog", catalog "Hộp đọc tài liệu".
15
+ - `packages/ui/src/reader-dialog.css` (`@gemboss/ui/reader-dialog.css`): its own entry, imported by the component; every
16
+ selector `dialog.ui-modal.ui-reader…`. Nothing moves into the core or the bundle.
17
+ - `reader-dialog.test.tsx` (markup, every class styled, selectors), `reader-dialog-browser.test.tsx` (Chromium: size,
18
+ fill, scroll, phone head, page lock, Escape; negative control without the entry finds the 460px popup).
19
+
20
+ **The team chat's rows for a plain page, from the package: `gembossUI.teamChat`** (Chris 20/10/2026: the markup in ONE place,
21
+ so GemCare stops writing it by hand and a change to it is made once).
22
+
23
+ Added
24
+ - `packages/ui/src/team-chat-plain.js` (`@gemboss/ui/team-chat-plain.js`, its own entry): builders that RETURN elements with
25
+ the components' markup for the same props: `message` (`Message`, every prop; plus `picking` / `reactionsPicking` /
26
+ `onPickingChange` for a page that redraws its list), `daySeparator`, `newMessagesLine`, `jumpToLatest` + `updateJump`,
27
+ `readReceipt`, `channelSection`, `channelItem`, `peopleSection`, `personRow`, `reactions`, `reactionPicker` (the package's
28
+ picker and its keys, "Frequently used" under the React picker's storage key), `avatar`, `icon`, and `rows` / `dayKey` /
29
+ `dayWord` (`teamChatRows`, `chatDayKey`, `chatDayWord`). Labels are `TEAM_CHAT_LABELS` (`LABELS`), any subset overridable.
30
+ Strings are text; callbacks are click listeners. A classic script, ES5, after `kit-plain.js` when the page has it.
31
+ 8,208 B gzip minified (ceiling 8,714 in `weight-budget.json`; a page that draws no team chat never loads it).
32
+ - `team-chat-plain-browser.test.ts` (Chromium, `test:ui-browser`): every builder, in every state, against
33
+ `renderToStaticMarkup` of its component as one tree (attributes, classes, text, SVG in full); the same clicks and keys on
34
+ the mounted components and on the plain rows leave the same DOM (the pickers, search, Escape and focus); callbacks,
35
+ escaping, toolbar and picker keys, `updateJump`, the load-order warning. `team-chat-plain-data.test.ts` (node): the
36
+ generated block (icons, emoji set; `UPDATE_PLAIN_TEAM_CHAT=1` writes it) and the ES5 copies (labels, initials, tone,
37
+ reaction helpers, row rules) held to the TypeScript.
38
+ - `team-chat.test.fixtures.tsx`: the contract test's data, now shared with the plain parity test (the README is unchanged).
39
+ - The catalog's "Chat đội" section has a live plain-HTML frame built with `gembossUI.teamChat`.
40
+
41
+ **A list whose items come from elsewhere** (item management IM2, 21/10/2026: a list that shows a content type).
42
+
43
+ Changed
44
+ - `RepeaterList` `onMove` and `onRemove` are optional. Without `onMove` the rows keep their order: no grip, no ↑ ↓, no
45
+ Alt+arrows, no drop. Without `onRemove`, no ✕. A row still opens (inline or in the popover) and still says it was picked
46
+ (`onActive`). The admin draws a list read from Content this way: its order and its rows are Content's. No CSS change.
47
+
5
48
  ## 0.40.0 (20/10/2026)
6
49
 
7
50
  **A list's item edits in a popover** (item management IM1a, Chris 20/10/2026; GemPages 7.5's "Item management" read in its code).
@@ -17,6 +60,14 @@ Added
17
60
  - `packages/ui/src/editor-repeater.css`: the popover's placement and header, the picked row's outline, the picture (+220 B gzip,
18
61
  1,096 of its 1,384 B ceiling). The surface is the fleet's `gemboss-popover-surface`.
19
62
 
63
+ **An inspector's tabs stand on the fields' edge** (Chris 20/10/2026: "cùng là tabs mà chỗ to chỗ nhỏ").
64
+
65
+ Fixed
66
+ - `packages/ui/src/editor.css`: `.gemboss-ei-tabs` inside a settings body or the column's dock drop their 12px side margin
67
+ (`:is(.gemboss-settings-body,.gemboss-ec-dock-body)`), so the Settings · Advanced track lines up with the fields under it (the
68
+ canvas's element and section panels measured 34–360 and 316px over fields at 22–372). A docked inspector keeps its own, more
69
+ specific rule; a floating inspector keeps 12px. +15 B gzip (8,680 of 8,690).
70
+
20
71
  **A phone pays for the topbar once, and the full-screen assistant gets the whole width** (measured on the admin at 390×844,
21
72
  19/10/2026).
22
73
 
package/README.md CHANGED
@@ -71,6 +71,7 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
71
71
  | assistant-chat | `@gemboss/ui/assistant-chat.css` | `packages/ui/src/assistant-chat.css` | the chat pieces the package draws (0.29.0): column, starters that wrap, meta, errors, notes, paperclip | 0.9 KB | `AssistantChat` and its pieces import it |
72
72
  | minichart | `@gemboss/ui/minichart.css` | `packages/ui/src/minichart.css` | `Sparkline`, `ShareBar` (0.30.0) | 0.5 KB | those components import it |
73
73
  | setup-guide | `@gemboss/ui/setup-guide.css` | `packages/ui/src/setup-guide.css` | `SetupGuide` (0.30.0) | 0.9 KB | `SetupGuide` imports it |
74
+ | reader-dialog | `@gemboss/ui/reader-dialog.css` | `packages/ui/src/reader-dialog.css` | `ReaderDialog`: the popup sized for reading a document (0.41.0) | 0.4 KB | `ReaderDialog` imports it |
74
75
  | service-switcher | `@gemboss/ui/service-switcher.css` | `packages/ui/src/service-switcher.css` | `ServiceSwitcher` and `Switcher`: the button after the brand and its panel (0.36.0; any list, illustrated tiles 0.40.0) | 2.2 KB | `ServiceSwitcher` imports it, and `BrandLockup` through it (README "ServiceSwitcher") |
75
76
  | team-chat | `@gemboss/ui/team-chat.css` | `packages/ui/src/team-chat.css` | the team chat layer (20/10/2026): rail, messages, composer, thread and people panels, the screen's layout | 3.6 KB | `TeamChat` and its pieces import it (with `Avatar`'s portal.css and `Reactions`' reactions.css); a plain-HTML page links it (README "The team chat layer") |
76
77
  | bundle | `@gemboss/ui/gemboss-ui.css` | `gemboss-ui.css` | core + components + shell + auth + assistant + chat + portal + theme-dark | 21.9 KB | compatibility only (see CHANGELOG 0.23.0) |
@@ -1177,6 +1178,40 @@ The body scrolls under a head that stays, and scrolling past its end does not sc
1177
1178
  contain`). `packages/ui/src/sheet-browser.test.tsx` measures it in Chromium; its negative control runs the same probe
1178
1179
  without `sheet.css` and finds the centred popup. Catalog section "Ngăn trượt cạnh phải".
1179
1180
 
1181
+ ## ReaderDialog (0.41.0): a document read over its list
1182
+
1183
+ Two surfaces drew the same popup with their own CSS the same week, because `Dialog`'s widths (460, `wide` 620) are a
1184
+ form's: GemMedia's admin Docs (gemmedia #76) and partner-radar's Docs (gemlabs #1122) open a long document over the list
1185
+ of documents, nearly the whole window, the whole screen on a phone. Chris 20/10/2026: put it in the kit. `ReaderDialog`
1186
+ is that popup:
1187
+
1188
+ ```tsx
1189
+ import { ReaderDialog } from "@gemboss/ui";
1190
+
1191
+ <ReaderDialog open onClose={close} title={doc.title} subtitle={`v${doc.version} · updated ${doc.updated}`}
1192
+ actions={<><CopyLink slug={doc.slug} /><a className="ui-btn ui-btn--sm" href={doc.src} target="_blank">Open in a new tab</a></>}>
1193
+ <iframe className="ui-reader__frame" src={doc.src} title={doc.title} />
1194
+ </ReaderDialog>
1195
+ ```
1196
+
1197
+ The body is the document's. Give it one child that fills it: `iframe.ui-reader__frame` for a self-contained HTML
1198
+ document, or a scroller of your own with `ui-reader__scroll` (padded 24/32/48, 16/16/40 on a phone; it scrolls, keeps
1199
+ its overscroll, and takes `tabIndex={-1}` and focus so the arrow keys and Space read the document). Keys pressed inside
1200
+ an iframe do not reach the `<dialog>`: a surface that wants Escape there forwards it (`frame.contentWindow` keydown →
1201
+ `dialog.close()`, same-origin). A route that draws it (Next's intercepting route) leaves `open` true and goes back in
1202
+ `onClose`, as with `Sheet`.
1203
+
1204
+ | | |
1205
+ | --- | --- |
1206
+ | Markup | `<dialog class="ui-modal ui-reader">` with the popup's own `ui-modal__head` (title, `__subtitle`, `__actions`, ✕), `__body`, `__foot` |
1207
+ | Behaviour | It is `Dialog` with `ui-reader` on it (one render tree, one hook, `useModalDialog`): Escape, ✕ and a press that started on the backdrop close it, `onClose` hears each once, the page behind is inert and holds still (`html:has(dialog.ui-modal:modal)`), focus goes back to the opener. On open focus goes to the first control, as `Dialog`'s; move it to your document |
1208
+ | Size | `min(1440px, 94vw)` × `min(92dvh, 1200px)`, centred, from 768px up; the whole screen below that, where the actions take their own line under the title and ✕ stays beside it |
1209
+ | CSS | `reader-dialog.css`, its own entry, loaded by the component. Every selector is `dialog.ui-modal.ui-reader…` ((0,2,1)). It lays itself out (no padding, border-box, a column the document fills), so it works on a surface that loads `ui.css` alone. A plain-HTML surface links `@gemboss/ui/reader-dialog.css` beside `gemboss-ui.css` and opens the markup with `showModal()` |
1210
+
1211
+ `packages/ui/src/reader-dialog-browser.test.tsx` measures it in Chromium (size at four windows and three phones, the
1212
+ iframe flush under the head, the scroller scrolling under a head that stays, the phone's head, the page lock, Escape);
1213
+ its negative control runs without `reader-dialog.css` and finds the 460px popup. Catalog section "Hộp đọc tài liệu".
1214
+
1180
1215
  ## SettingsDialog (0.35.0): settings as one popup
1181
1216
 
1182
1217
  The admin's Settings (apps/admin `SettingsDialog`): one popup over the app, the sections in a rail on the left with a
@@ -2101,6 +2136,62 @@ closes a drawer, focus moves into it and back, the rail and the conversation are
2101
2136
  open state (`gb-tc-msg--active`, `aria-expanded` on its button); arrow keys along `div.gb-tc-acts[role=toolbar]`; when to
2102
2137
  show `.gb-tc-jump` and its count.
2103
2138
 
2139
+ ### Plain HTML: `gembossUI.teamChat` (20/10/2026)
2140
+
2141
+ So a plain page does not write the markup above by hand. `team-chat-plain.js` builds the rows from data and RETURNS
2142
+ elements whose markup is the component's for the same props: tags, classes, every attribute, the text, the icons' SVG.
2143
+ `team-chat-plain-browser.test.ts` draws every piece in every state both ways in Chromium (the contract's data,
2144
+ `team-chat.test.fixtures.tsx`, and the states beyond it) and holds them to one tree, so the markup is written once, in
2145
+ `team-chat.tsx`, and a plain page follows a change to it with a version bump. A classic script, no imports, inlinable:
2146
+ after `kit-plain.js` when the page has it (it adds `gembossUI.teamChat`; loaded first it says so in the console).
2147
+
2148
+ | Builder | Draws what this draws |
2149
+ |---|---|
2150
+ | `message(props)` | `Message`, every prop by the same name; plus `picking` / `reactionsPicking` (a picker drawn open) and `onPickingChange(which, open)` (`"bar"` or `"reactions"`) for a page that redraws its whole list |
2151
+ | `daySeparator(label, dateTime)`, `newMessagesLine(labels)` | `DaySeparator`, `NewMessagesLine` |
2152
+ | `jumpToLatest({ count, visible, onJump, labels })`, `updateJump(button, { count, visible, labels })` | `JumpToLatest`; `updateJump` redraws the same button (a scroll handler keeps its focus) |
2153
+ | `readReceipt({ read, of, label })` | `ReadReceipt` (`null` when `of` ≤ 0) |
2154
+ | `channelSection({ label, id, items })`, `channelItem(props)` | `ChannelSection`, `ChannelItem` (`href`: a link, else a button); `items` are rows or their props |
2155
+ | `peopleSection({ label, count, id, items })`, `personRow(props)` | `PeopleSection`, `PersonRow` (a link, a button with `onSelect`, else a `div`) |
2156
+ | `reactions(props)`, `reactionPicker(props)` | `Reactions` (plus `open` / `onOpenChange`), `ReactionPicker` |
2157
+ | `avatar({ name, src, size, className })`, `icon(name, size)` | `Avatar` (portal.tsx's initials and tone), `Icon` (the icons the builders draw, and `more`) |
2158
+ | `rows(messages, opts)`, `dayKey(at)`, `dayWord(at, now)` | `teamChatRows`, `chatDayKey`, `chatDayWord` |
2159
+ | `LABELS` | `TEAM_CHAT_LABELS`; `labels` (any subset) translates, functions included |
2160
+
2161
+ ```html
2162
+ <link rel="stylesheet" href="/ui/portal.css"><link rel="stylesheet" href="/ui/reactions.css"><link rel="stylesheet" href="/ui/team-chat.css">
2163
+ <script src="/ui/kit-plain.js"></script><script src="/ui/team-chat-plain.js"></script>
2164
+ ```
2165
+
2166
+ ```js
2167
+ const tc = gembossUI.teamChat;
2168
+ list.replaceChildren(...tc.rows(messages, { meId: me.id, isUnread: (m) => m.id > readUpTo }).map((r) =>
2169
+ r.kind === "day" ? tc.daySeparator(dayLabel(r.date), isoDay(r.date))
2170
+ : r.kind === "new" ? tc.newMessagesLine()
2171
+ : tc.message({ author: r.message.authorName, time: hhmm(r.message.at), dateTime: r.message.at, body: r.message.text,
2172
+ grouped: r.grouped, messageId: r.message.id, reactions: r.message.reactions, edited: Boolean(r.message.editedAt),
2173
+ receipt: tc.readReceipt({ read: 2, of: 3, label: "Read by Linh, Quan. Not yet: Bo" }),
2174
+ onReact: (emoji) => react(r.message.id, emoji), onReply: () => startReply(r.message), onOpenThread: (e) => openThread(r.message.id, e.currentTarget),
2175
+ moreActions: myMoreButton(r.message), picking: view.picking === r.message.id,
2176
+ onPickingChange: (which, open) => { if (which === "bar") view.picking = open ? r.message.id : null; } })));
2177
+ ```
2178
+
2179
+ Strings are TEXT (a body `"<img onerror=…>"` shows those characters); a slot that takes a node (`avatar`, `badges`,
2180
+ `attachments`, `extra`, `receipt`, `moreActions`, `editor`, a rich `time` or `body`) takes a DOM node as given. Callbacks are
2181
+ click listeners and get what React's `onClick` gets (the event; `onReact` the emoji). The behaviour is the components': the
2182
+ action bar is a toolbar (arrows, Home / End), "More reactions" opens the package's picker under the body and the reaction
2183
+ row's "Add reaction" one under the row (search, this browser's "Frequently used" under the React picker's own storage key,
2184
+ arrows, Enter picks the first match, Escape closes and gives focus back), a chip's click remembers its emoji. A row drawn
2185
+ with `picking` takes no focus: after a redraw the page puts focus back, as it does for anything it redraws.
2186
+
2187
+ Not built here: the screen's shell (`TeamChat`, `ChannelRail`, `ChatHeader`, `MessageList`, `Composer`, `ThreadPanel`,
2188
+ `PeoplePanel`) is markup a page writes once (the blocks above), with the behaviour listed just above.
2189
+
2190
+ The script carries two kinds of copy, both held by `team-chat-plain-data.test.ts`: the icons' drawing (icons.tsx) and the
2191
+ emoji set (reactions-data.ts) as a generated block (`UPDATE_PLAIN_TEAM_CHAT=1` on that test writes it), and the labels, the
2192
+ avatar's initials and tone, the reaction helpers and `teamChatRows` in ES5, called with the TypeScript's inputs and held to
2193
+ its answers.
2194
+
2104
2195
  ## Action card (0.28.0): an assistant proposes, a person applies
2105
2196
 
2106
2197
  Chris 08/10/2026: the Feedback Assistant lists likely duplicates and merges them, and carries out status, owner, label
@@ -2405,6 +2496,7 @@ something the base cannot express, add a token or a prop here, and every surface
2405
2496
  | `packages/ui/src/editor-plain.js` | 0.14.0: `gembossUI.initEditorFrame()`, the frame's behaviour for plain HTML; 0.17.0: `gembossUI.initEditorPanel()`, the settings panel's. Load after `kit-plain.js`. |
2406
2497
  | `packages/ui/src/editor-layers-plain.js` | 0.22.0: `gembossUI.initEditorLayers()`, the layers', the outline's and the drawers' behaviour for plain HTML. Load after `kit-plain.js`. |
2407
2498
  | `packages/ui/src/service-switcher-plain.js` | 0.36.0: `gembossUI.initServiceSwitcher()`, the ServiceSwitcher drawn and driven for plain HTML (`@gemboss/ui/service-switcher-plain.js`). After `kit-plain.js` when the page has it; works alone. See "ServiceSwitcher". |
2499
+ | `packages/ui/src/team-chat-plain.js` | 20/10/2026: `gembossUI.teamChat`, builders that return the team chat's rows (messages, day separators, "New messages", jump, receipt, rail rows, people rows, reactions and the picker) with the components' markup, for plain HTML (`@gemboss/ui/team-chat-plain.js`). After `kit-plain.js` when the page has it; works alone. See "The team chat layer". |
2408
2500
  | `catalog/editor.html` | 0.14.0: the frame rendered from the components and running (`packages/ui/scripts/build-catalog-editor.mjs`), linked from `catalog/index.html`. |
2409
2501
  | `packages/ui/src/editor.css` | 0.9.0: the editors (frame, top bar, preview, Configure dock, inspector and controls, Layers, settings drawer, page picker, menus). A separate entry, NOT inside `gemboss-ui.css`; the admin imports it from the editor components, never from a layout. See "Editor CSS" above. |
2410
2502
  | `packages/ui/src/chat.css`, `chat-composer.css`, `assistant.css`, `auth.css`, `app-shell.css` | the chat body, the composer, the assistant frame, the sign-in screen, the topbar slots + base text + the page width tiers (`ui-page`, 0.8.1). All inside `gemboss-ui.css`; the admin @imports `chat.css` and `chat-composer.css` itself. |
@@ -8,7 +8,7 @@
8
8
  .cat-code summary{padding:10px 16px;font-size:13px;cursor:pointer;color:var(--gemboss-text-muted);border-top:1px solid var(--gemboss-divider);min-height:24px}
9
9
  .cat-code pre,.cat-api{margin:0;padding:12px 16px;overflow:auto;background:#0f1012;color:#e8e8e6;font-size:12px;line-height:1.5;border-radius:0}
10
10
  .cat-api{border-radius:12px;margin:0 0 16px}</style></head><body><div class="cat">
11
- <h1>@gemboss/ui 0.40.0 · Editor</h1>
11
+ <h1>@gemboss/ui 0.41.0 · Editor</h1>
12
12
  <p>Khung của mọi editor: <code>EditorFrame</code> (cột trái · khe kéo · preview · cột phải dock trên mép phải preview) và <code>EditorTopbar</code> (← · tiêu đề · cụm giữa · cụm phải · dòng trạng thái). Là đúng khung của theme / page / section editor trong admin. Gói chỉ vẽ KHUNG; chat, Configure, canvas, page picker, menu, Publish là của editor, đi vào bằng slot. Trang HTML thuần nạp <code>editor.css</code> + <code>editor-plain.js</code> rồi gọi <code>gembossUI.initEditorFrame()</code>: kéo khe (chuột, chạm, phím mũi tên / Home / End), đổi thiết bị, mở ⋯ và Publish ▾, ẩn cột trái, Chat | Preview trên điện thoại. <a href="index.html">← Catalog</a></p>
13
13
  <h2>React</h2><pre class="cat-api"><code>import &quot;@gemboss/ui/editor.css&quot;;
14
14
  import { EditorFrame, EditorTopbar, EditorBack, EditorChatToggle, EditorPaneTabs, EditorSaveState, DeviceToggle } from &quot;@gemboss/ui/editor&quot;;