@gemboss/ui 0.2.0 → 0.3.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 CHANGED
@@ -76,10 +76,25 @@ import { AssistantProvider, AssistantHost, useAssistant } from "@gemboss/ui/assi
76
76
  </AssistantProvider>
77
77
  ```
78
78
 
79
- Four shapes, one mounted subtree: full screen (next to the rail), a popup that floats over every
80
- screen and survives navigation, a status bar, and hidden-but-still-running. A live run is never
81
- thrown off screen (`assistant/dock.ts` holds that rule). The popup resizes from its top edge and
82
- slides along the bottom, by pointer or keyboard, and remembers both under `storagePrefix`.
79
+ Five shapes, one mounted subtree: full screen (next to the rail), a popup that floats over every
80
+ screen and survives navigation, a full-height side panel, a status bar, and hidden-but-still-running.
81
+ A live run is never thrown off screen (`assistant/dock.ts` holds that rule). The popup resizes from
82
+ its top edge and slides along the bottom, by pointer or keyboard, and remembers both under
83
+ `storagePrefix`.
84
+
85
+ The side panel is opt-in: `<AssistantHost allowSide>` puts a "Pin to the side" button in the popup
86
+ header, and the panel carries "Float as a popup" back. It stands under the topbar on the right, and
87
+ the page moves over for it instead of being covered: the provider puts `<prefix>-side` on `<body>` and
88
+ the width in `--gb-assistant-side-w` (`SIDE_WIDTH_VAR`), and `.gemboss-platform-main` takes that as
89
+ `margin-right`. A surface with its own layout reads the same variable. Its left edge is a
90
+ `.ui-resizer` (drag, or ←/→, Shift for 64px, Home/End), clamped to 380–720px and never leaving the page
91
+ less than 480px; the width is remembered. Panel and page only fit side by side from 1100px (rail 240
92
+ + 380 + 480). Between 761 and 1099 the panel covers the content area and is not offered; on a phone
93
+ it takes the screen. The status bar, a busy ×, leaving full screen mid-run and full
94
+ screen's own way back ("Back to the side panel") all return to whichever shape the person last
95
+ worked in (`floatShape`, remembered across reloads), so a pinned panel never pops back as a popup.
96
+ The host registers `allowSide` with the provider, and every way into the panel checks it, including
97
+ a "side" remembered in storage: a host without it gets the popup.
83
98
 
84
99
  The rail's way in is a `RailItem` with `className="gnav-assistant"`, linking to your full-screen
85
100
  address (the admin puts it in `RailBottom`, above Inbox). That class draws the tinted pill, and the
@@ -93,7 +108,7 @@ is a separate piece you put inside it:
93
108
  ```tsx
94
109
  import { AssistantWorkspaceLayout, SessionRail, OpenChatTabs } from "@gemboss/ui/assistant";
95
110
 
96
- <AssistantWorkspaceLayout dock={presentation === "dock"} rail={
111
+ <AssistantWorkspaceLayout dock={presentation === "dock" || presentation === "side"} rail={
97
112
  <SessionRail sessions={sessions} activeId={active} busyIds={running}
98
113
  collapsed={collapsed} onToggleCollapsed={toggle} onNewChat={newChat}
99
114
  onOpen={open} onRename={rename} onTogglePin={pin} onDelete={remove} labels={VI} />
@@ -109,6 +124,54 @@ Its styles are the `gemboss-assistant-*` rules in `packages/ui/src/shell.css` (t
109
124
  @imports too), and they reach you through `gemboss-ui.css` together with `gemboss-field.css`. The admin passes a second tab ("Talk to a person", gemcare) through `secondTab` and its button
110
125
  through `fullActions`; a surface without those leaves them out.
111
126
 
127
+ ## The chat body
128
+
129
+ What an answer looks like, on any surface that puts a chat somewhere: `chat.css` (inside `gemboss-ui.css`;
130
+ the admin `@import`s it, so it is the same file the admin's own chat is drawn with).
131
+
132
+ | Piece | Classes |
133
+ |---|---|
134
+ | Header | `.gemboss-ec-head` › `.gemboss-ec-title` + `.gemboss-ec-sub` |
135
+ | Message list | `.gemboss-ec-scroll` (thin visible scrollbar) |
136
+ | Messages | `.gemboss-ec-msg.user` / `.gemboss-ec-msg.assistant` › `.gemboss-ec-bubble` |
137
+ | Markdown inside a bubble | `.gemboss-md-p / -h -h1..3 / -list / -quote / -rule / -code / -codeblock / -table(wrap) / -img(link)`, links `.gemboss-ec-md-link` |
138
+ | Steps the assistant took | `ul.gemboss-narr` › `li.gemboss-narr-row.ok|.running|.failed|.thinking|.trace|.steer` › `.gemboss-narr-dot` + `.gemboss-narr-text` (running: `.gemboss-narr-spin`) |
139
+ | Working / streaming | `.gemboss-ec-pending` (`-star`, `-verb`, `-meta`), `.gemboss-ec-cursor` after the streamed text |
140
+ | Suggestions, choices | `.gemboss-ec-starters` › `button.gemboss-ec-starter`; `.gemboss-ec-chips` › `button.gemboss-ec-chip` |
141
+ | Composer | `form.gemboss-ec-input` › `textarea` + `div.gemboss-ec-toolbar` › `div.gemboss-ec-toolbar-lead` (chips; leave it EMPTY for none) + `button[type=submit]` (44px; disabled = grey). An empty lead puts the box and the button on ONE row. Stop while streaming: `button.gemboss-ec-stop` in place of submit |
142
+
143
+ The toolbar row, the one-row layout, the engine picker's layout and Stop are in a second sheet,
144
+ `chat-composer.css` (also inside `gemboss-ui.css`): the admin imports it from its chat components rather than its
145
+ layout, so screens without a chat never load it. The catalog's "Thân chat" section is the markup to copy. NOT here, on purpose: the panel's own geometry
146
+ (`.gemboss-ec` width, editor columns), attachments, action cards, the engine picker and work mode — those
147
+ are still the admin's. The one exception is the chat panel's touch / mouse target blocks: one rule per pointer
148
+ kind names every control of the panel, admin-only ones included (they match nothing elsewhere). The behaviour (streaming, Stop, Enter to send, IME, scroll-to-bottom) belongs to
149
+ the surface.
150
+
151
+ ## Plain-HTML behaviour: `kit-plain.js` (0.3.0)
152
+
153
+ Surfaces without React (GemFactory, gemcare, gempress, partner-radar) render the kit's markup on the server and need
154
+ the few behaviours React components carry: a popup that traps Tab and closes on Escape, a toast, menus that close.
155
+ Each surface wrote its own; GemFactory's (reviewed into Tab trap, stacked Escape and the close-while-asking race) is
156
+ now this file. It is a classic script that sets `globalThis.gembossUI`, so a server-rendered page can inline it:
157
+
158
+ ```js
159
+ // server
160
+ const KIT_JS = readFileSync(require.resolve("@gemboss/ui/kit-plain.js"), "utf8");
161
+ html += `<script>${KIT_JS}</script>`;
162
+ // page
163
+ const { openModal, toast, initMenus } = gembossUI;
164
+ initMenus();
165
+ const m = openModal({ title: "Delete #12?", closeLabel: "Đóng", body: '<div class="ui-modal__body">…</div>',
166
+ foot: '<button class="ui-btn" data-no>Huỷ</button><button class="ui-btn ui-btn--primary" data-yes autofocus>Xoá</button>',
167
+ onClose: (v) => v && toast("Đã xoá") });
168
+ ```
169
+
170
+ `title`, `body` and `foot` are HTML (escape your text); toast text is text. `beforeClose` may return a promise of
171
+ false to keep the popup open (it gets the close value, and a guard that throws keeps it open). Its behaviour is
172
+ asserted in Chromium by `packages/ui/src/kit-plain.test.ts` (`npm run test:ui-browser`, run by the CI job that has
173
+ Chromium; `test:packages` excludes it); the catalog's "Hộp thoại" section runs it live.
174
+
112
175
  ## Resizable panels
113
176
 
114
177
  `ui-resizer` is the seam a person drags between two panels; `ui-resizing` on `<body>` is the drag in
@@ -169,6 +232,11 @@ import { AppShell, BrandLockup, AccountMenu, AccountMenuItem, Icon, RailItem } f
169
232
  applications that integrate with Shopify. Draw a missing one HERE, not in the surface: every rail
170
233
  icon must keep `fill="none"` on its inner `<g>`, because `.gnav-ico svg { fill: currentColor }`
171
234
  outranks an attribute on the `<svg>` itself and paints an outline icon solid.
235
+ - `Menu` / `MenuItem` / `MenuSeparator` (0.2.1): the "⋯" action menu. Same `<details>` rule as the account
236
+ menu (works as plain markup; with React it closes on an outside click, on Escape with focus back on the
237
+ trigger, and after a button row's handler). Rows share the account menu's row style. `MenuItem` is a link
238
+ (`href`) or a button; `disabled` on a link drops the href and sets `aria-disabled`; `className` is added
239
+ to the row's class.
172
240
 
173
241
  ## The catalog: every token, class and component, with its markup
174
242
 
@@ -215,6 +283,8 @@ something the base cannot express, add a token or a prop here, and every surface
215
283
  |---|---|
216
284
  | `gemboss-ui.css` | built at pack time: `apps/admin/scripts/export-ui-css.mjs` (tokens + kit + field + `packages/ui/src/shell.css`, with Polaris `--p-*` resolved to their fallbacks) + `packages/ui/src/app-shell.css` + `packages/ui/src/auth.css` + `packages/ui/src/assistant.css` |
217
285
  | `packages/ui/src/shell.css` | the SHARED SHELL (rail, topbar, main, mobile drawer, the assistant's thread rail, popover surface, account chip). The admin's `gemboss-shell.css` @imports it on line 2; the export refuses a shared-shell rule in the admin's sheet or an admin-only rule here (one owner). |
286
+ | `packages/ui/src/kit-plain.css` | primitives for surfaces that render plain HTML, not React (0.2.1): menu, key/value list, icon button; 0.3.0 adds `ui-link` / `ui-prose`, the media card (`ui-card--media`, `__media`, `__body`, `__foot`) and the popup's `ui-modal__body` / `__foot`. Shipped inside `gemboss-ui.css`; the admin does not import it (its first-load CSS budget has ~0.1 KB headroom). |
287
+ | `packages/ui/src/kit-plain.js` | 0.3.0: the behaviour those surfaces each wrote by hand: `gembossUI.openModal` (Escape, backdrop, Tab trap, stacking, ask-before-close, focus back), `toast` (text, error tone), `initMenus` (outside click, item, blur, Escape closes the menu before the popup). A classic script: inline it or `<script src>`; see "Plain-HTML behaviour" below. |
218
288
  | `packages/ui/src/**/*.tsx` | the components, as TypeScript source. Next: add `@gemboss/ui` to `transpilePackages`. Vite compiles it as is. |
219
289
 
220
290
  `gemboss-ui.css` is never committed and never edited. To change a colour or a radius, change