@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 +75 -5
- package/catalog/index.html +140 -14
- package/gemboss-ui.css +784 -11
- package/package.json +6 -1
- package/src/assistant/dock.ts +83 -5
- package/src/assistant/host.tsx +113 -17
- package/src/assistant/index.ts +1 -1
- package/src/assistant/provider.tsx +75 -8
- package/src/assistant.css +90 -1
- package/src/chat-composer.css +110 -0
- package/src/chat.css +427 -0
- package/src/gemboss-field.css +4 -0
- package/src/icons.tsx +1 -0
- package/src/index.ts +1 -0
- package/src/kit-plain.css +110 -0
- package/src/kit-plain.js +179 -0
- package/src/menu.tsx +64 -0
- package/src/shell.css +8 -4
- package/src/ui.css +27 -4
package/src/assistant.css
CHANGED
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
degrading to instant is the right failure for chrome nobody asked to watch. */
|
|
75
75
|
@starting-style {
|
|
76
76
|
.gb-dock-dock, .gb-dock-mini { transform: translateY(10px); opacity: 0; }
|
|
77
|
+
.gb-dock-side { transform: translateX(12px); opacity: 0; }
|
|
77
78
|
}
|
|
78
79
|
/* Nothing animates while a pointer is setting the numbers — see `dragging` in the host. */
|
|
79
80
|
.gb-dock-dragging, .gb-dock-dragging * { transition: none !important; }
|
|
@@ -112,6 +113,66 @@
|
|
|
112
113
|
background: var(--gemboss-app-bg);
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
/* ── Side: a full-height panel pinned to the right edge (01/10/2026) ─────────────────────────────
|
|
117
|
+
The third working shape, beside the popup and full screen: a long conversation next to the screen
|
|
118
|
+
it is about. Opt-in per surface (`<AssistantHost allowSide>`), because unlike the popup it TAKES
|
|
119
|
+
ROOM — the page beside it moves over (rule below), so nothing under it is hidden. That is why the
|
|
120
|
+
surface is solid rather than frosted: nothing sits behind it to show through.
|
|
121
|
+
Above `.gemboss-route-shell` (5), so the panel stays put while routes change — a sidebar that
|
|
122
|
+
vanishes on every click is a popup with extra steps. BELOW the topbar (300): the account menu
|
|
123
|
+
opens down out of the topbar's right end, exactly where this panel starts, and at the popup's 320
|
|
124
|
+
the menu fell behind the panel (review). 290 still clears the desktop invite (260/280). The host
|
|
125
|
+
is rendered inside `main` (AppShell's slot), which works because `main` makes no stacking context
|
|
126
|
+
or containing block; a surface that gives its `main` a transform or z-index must mount it outside. */
|
|
127
|
+
.gb-dock-side {
|
|
128
|
+
z-index: 290;
|
|
129
|
+
top: 56px;
|
|
130
|
+
right: 0;
|
|
131
|
+
bottom: 0;
|
|
132
|
+
width: var(--gb-dock-side-w, 400px);
|
|
133
|
+
background: var(--gemboss-surface);
|
|
134
|
+
-webkit-backdrop-filter: none;
|
|
135
|
+
backdrop-filter: none;
|
|
136
|
+
border: 0;
|
|
137
|
+
border-left: 1px solid var(--gemboss-divider);
|
|
138
|
+
border-radius: 0;
|
|
139
|
+
box-shadow: none;
|
|
140
|
+
overflow: visible; /* the seam's touch band reaches 14px past the left edge */
|
|
141
|
+
}
|
|
142
|
+
.gb-dock-side .gb-dock-body, .gb-dock-side .gb-dock-pane { overflow: hidden; }
|
|
143
|
+
/* The header does not drag a panel that is pinned. */
|
|
144
|
+
.gb-dock-side .gb-dock-header { cursor: default; touch-action: auto; }
|
|
145
|
+
/* The word for what the dot already says stands down here, as it does in a popup under 420px: the panel has
|
|
146
|
+
five controls in its header, and the title needs the room (measured: "Gem assi…" / "Re…" at 400). Visually
|
|
147
|
+
only — the dot is aria-hidden, so the word is the one place a screen reader hears "Working" (review #3622). */
|
|
148
|
+
.gb-dock-side .gb-dock-status {
|
|
149
|
+
flex: 0 0 auto; position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
150
|
+
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
151
|
+
}
|
|
152
|
+
.gb-dock-side .gb-dock-header:active { cursor: default; }
|
|
153
|
+
/* The seam: the panel's left edge, the fleet's one `.ui-resizer` (ui.css draws the grip). A 5px strip
|
|
154
|
+
centred on the border, ABOVE the page beside it. */
|
|
155
|
+
/* `.gb-dock-side >` is not decoration: `.ui-resizer { position: relative }` (ui.css) has the weight of one
|
|
156
|
+
class too, and which file a bundler loads last would decide whether the seam is a 5px strip or a 0px
|
|
157
|
+
line nobody can grab. */
|
|
158
|
+
.gb-dock-side > .gb-dock-sidegrip {
|
|
159
|
+
position: absolute;
|
|
160
|
+
top: 0;
|
|
161
|
+
bottom: 0;
|
|
162
|
+
left: -3px;
|
|
163
|
+
width: 5px;
|
|
164
|
+
z-index: 3;
|
|
165
|
+
}
|
|
166
|
+
/* The page makes room. `margin-right`, not padding: main's padding is set by four rules in shell.css
|
|
167
|
+
and a fifth here would fight them. The provider puts the class and the width on <body>; the default
|
|
168
|
+
`bodyClassPrefix` is `gemboss-assistant` — a surface with another prefix writes its own copy of this
|
|
169
|
+
one rule. No transition: the seam is set from a pointer, and a page that trails the finger is the
|
|
170
|
+
bug `.gb-dock` already avoids. */
|
|
171
|
+
@media (min-width: 1100px) {
|
|
172
|
+
body.gemboss-assistant-side .gemboss-platform-main { margin-right: var(--gb-assistant-side-w, 400px); }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
115
176
|
/* ── Full: the one control full screen needs — the way back to the popup ──────────────────────
|
|
116
177
|
Floated over the chat's own header, whose right half is empty, rather than given a bar of its
|
|
117
178
|
own: a whole strip of chrome for one button would push the conversation down on every screen. */
|
|
@@ -261,7 +322,10 @@
|
|
|
261
322
|
white-space: nowrap;
|
|
262
323
|
}
|
|
263
324
|
.gb-dock-header:active { cursor: grabbing; }
|
|
264
|
-
|
|
325
|
+
/* `margin-left: auto`: the status word was the only thing that stretched, so wherever it stands down (a popup under
|
|
326
|
+
420px, the side panel) the controls followed the title instead of the right edge — 350px adrift in a 720px panel
|
|
327
|
+
(review #3622). With the word showing, it still takes the free space first and nothing moves. */
|
|
328
|
+
.gb-dock-actions { display: flex; align-items: center; gap: 2px; cursor: default; margin-left: auto; }
|
|
265
329
|
/* One ACTION, then the three WINDOW controls — ordered by how much each changes the window: bigger,
|
|
266
330
|
smaller, gone. The gap is what says they are two different kinds of button; without it four icons
|
|
267
331
|
in a row read as one undifferentiated strip, which is what "các icon đang không đồng đều" was
|
|
@@ -404,4 +468,29 @@
|
|
|
404
468
|
bottom: 0;
|
|
405
469
|
border-radius: 0;
|
|
406
470
|
}
|
|
471
|
+
/* No room beside a phone screen: the side panel takes the screen, the way full does, and there is
|
|
472
|
+
nothing to drag. The page is not pushed (the rule above stops at 761). */
|
|
473
|
+
.gb-dock-side {
|
|
474
|
+
left: 0;
|
|
475
|
+
right: 0;
|
|
476
|
+
width: auto;
|
|
477
|
+
border-left: 0;
|
|
478
|
+
border-radius: 0; /* the phone .gb-dock rule above rounds the top; this panel is the screen */
|
|
479
|
+
}
|
|
480
|
+
.gb-dock-sidegrip { display: none; }
|
|
481
|
+
/* …so the button that pins it there would only say "full screen" a second time. */
|
|
482
|
+
.gb-dock-pinside { display: none; }
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/* 1100 = rail 240 + the panel's narrowest 380 + the page's narrowest 480 (SIDE_MIN_WIDTH, SIDE_PAGE_MIN in
|
|
486
|
+
assistant/dock.ts). Below it the two do not fit side by side — MEASURED at 768: pushed, the page kept
|
|
487
|
+
120px and its table ran under the panel. So between the phone and 1100 the panel covers the content
|
|
488
|
+
area the way full screen does, nothing is pushed, there is no seam to drag, and the popup does not
|
|
489
|
+
offer to pin.
|
|
490
|
+
AT THE END OF THE FILE on purpose: `.gb-dock-icon { display: grid }` comes later than the side
|
|
491
|
+
section and has the same weight, so placed there this block lost and the pin still showed at 768
|
|
492
|
+
(measured). */
|
|
493
|
+
@media (min-width: 761px) and (max-width: 1099.98px) {
|
|
494
|
+
.gb-dock-side { left: 240px; width: auto; }
|
|
495
|
+
.gb-dock-side .gb-dock-sidegrip, .gb-dock-pinside { display: none; }
|
|
407
496
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The chat COMPOSER — @gemboss/ui (moved from apps/admin/src/components/chat/chat-composer.css, 02/10/2026).
|
|
3
|
+
*
|
|
4
|
+
* The toolbar row under the text box (chips lead, tools, status, the one action pinned right), the one-row
|
|
5
|
+
* layout when there are no chips, the engine picker's layout and the Stop button. A second surface needs the
|
|
6
|
+
* toolbar — gemcare's assistant composer is a text box and a send button, i.e. the one-row case — and a copy
|
|
7
|
+
* would be a second owner. The box itself (`form.gemboss-ec-input`, its textarea and send button) is in chat.css.
|
|
8
|
+
*
|
|
9
|
+
* A SEPARATE file from chat.css on purpose, as it was a separate file in the admin: chat.css rides the shell's
|
|
10
|
+
* first load on every screen, while this sheet follows the chat chunk — the admin imports it from ChatComposer
|
|
11
|
+
* and EnginePicker, not from the layout (the shell sat at 239.1 / 240 KB brotli when this moved). Other surfaces
|
|
12
|
+
* get it inside gemboss-ui.css. Rules and their order are unchanged.
|
|
13
|
+
*
|
|
14
|
+
* Every selector here names a composer-only class (chiplabel · chipmark · chipsub · enginepick · enginepick-btn ·
|
|
15
|
+
* stop · toolbar · toolbar-lead · toolbar-tools), so a rule only matches while a composer is on screen.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/* The toolbar row: chips on the left, then status, then the one action button pinned right.
|
|
19
|
+
`align-items: center` — the old row was `flex-end`, so 28px chips sat on the BOTTOM edge of a 44px
|
|
20
|
+
button and their centres ran 8px below its centre. Measured at 1366: chips cy 819, send cy 811.
|
|
21
|
+
The lead group is the only thing that shrinks. Send is `flex: none` and the chips inside the lead
|
|
22
|
+
carry `min-width: 0`, so a narrow composer ellipsises a label instead of pushing send off the row. */
|
|
23
|
+
.gemboss-ec-toolbar { flex: 1 0 100%; display: flex; align-items: center; gap: 4px; min-width: 0; }
|
|
24
|
+
.gemboss-ec-toolbar-lead { flex: 1 1 auto; display: flex; align-items: center; gap: 4px; min-width: 0; }
|
|
25
|
+
/* The lead GROWS, and that is what pins status + action to the right edge. With nothing in it (a
|
|
26
|
+
browser tab has no engine picker, a driver without work mode has no select) it is removed, and
|
|
27
|
+
whatever follows takes the auto margin instead. */
|
|
28
|
+
.gemboss-ec-toolbar-lead:empty { display: none; }
|
|
29
|
+
.gemboss-ec-toolbar-lead:empty + * { margin-left: auto; }
|
|
30
|
+
/* TOOLS ARE NOT CHIPS. The 📎 lives here, never in the lead: the composer gate reads `leading` as the
|
|
31
|
+
chip strip twice over — `edge` wants the first chip's text flush with the typed text, and `oneRow`
|
|
32
|
+
only runs when the lead is EMPTY. A button parked in the lead pushed the chips 47px right AND left
|
|
33
|
+
`oneRow` unable to fail at all; the gate's own negative control said so out loud
|
|
34
|
+
("MÙ với oneRow — đối chứng ĐÃ QUAN SÁT ĐƯỢC và vẫn xanh") once the breakage stopped registering.
|
|
35
|
+
`flex: none` so it never steals the shrink budget the lead needs to ellipsise a long chip label. */
|
|
36
|
+
.gemboss-ec-toolbar-tools { display: flex; align-items: center; gap: 4px; flex: none; min-width: 0; }
|
|
37
|
+
.gemboss-ec-toolbar-tools:empty { display: none; }
|
|
38
|
+
/* NO CHIPS ⇒ ONE ROW. Product Studio has nothing in its lead, and it still spent a whole 44px row on
|
|
39
|
+
a lone send button beside empty space — 99-120px tall for a one-line box. The text box shares the
|
|
40
|
+
row now and send sits at its bottom edge, where a growing message keeps it. */
|
|
41
|
+
.gemboss-ec-input:has(> .gemboss-ec-toolbar > .gemboss-ec-toolbar-lead:empty) { flex-wrap: nowrap; align-items: flex-end; }
|
|
42
|
+
.gemboss-ec-input:has(> .gemboss-ec-toolbar > .gemboss-ec-toolbar-lead:empty) > textarea { flex: 1 1 0; }
|
|
43
|
+
.gemboss-ec-input:has(> .gemboss-ec-toolbar > .gemboss-ec-toolbar-lead:empty) > .gemboss-ec-toolbar { flex: 0 0 auto; }
|
|
44
|
+
/* The engine picker is a `gemboss-ec-chipctl` like the select beside it — its colour, weight, padding
|
|
45
|
+
and hover all come from that one rule. What is left here is layout: Popover wraps the activator in an
|
|
46
|
+
element of its own, and the chip can only shrink if every box between it and the toolbar can.
|
|
47
|
+
DESCENDANT, not child, for that reason — `>` matched nothing when the button sat inside Polaris'
|
|
48
|
+
wrapper, measured, after the first attempt left it unstyled. */
|
|
49
|
+
.gemboss-ec-enginepick { flex: 0 3 auto; min-width: 0; display: inline-flex; }
|
|
50
|
+
.gemboss-ec-enginepick > * { min-width: 0; max-width: 100%; display: flex; }
|
|
51
|
+
.gemboss-ec-enginepick button {
|
|
52
|
+
min-width: 0; max-width: 100%;
|
|
53
|
+
/* The same caret the work-mode select draws, at the same inset, so the two chips read as one type. */
|
|
54
|
+
padding-right: 24px;
|
|
55
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='currentColor' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
56
|
+
background-repeat: no-repeat;
|
|
57
|
+
background-position: right 7px center;
|
|
58
|
+
}
|
|
59
|
+
.gemboss-ec-chiplabel { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
60
|
+
.gemboss-ec-chipmark { display: none; }
|
|
61
|
+
.gemboss-ec-chipmark .gemboss-engine-mark { display: block; width: 16px; height: 16px; margin: 2px 0; }
|
|
62
|
+
/* The steps ChatComposer measured its way down to (data-density on the form). The ellipsis above stays
|
|
63
|
+
as the floor for an engine with no mark, never as the plan. */
|
|
64
|
+
.gemboss-ec-input:not([data-density="full"]) .gemboss-ec-chipsub { display: none; }
|
|
65
|
+
.gemboss-ec-input:is([data-density="icon"], [data-density="tight"]) .gemboss-ec-enginepick-btn.has-mark .gemboss-ec-chiplabel { display: none; }
|
|
66
|
+
.gemboss-ec-input:is([data-density="icon"], [data-density="tight"]) .gemboss-ec-enginepick-btn.has-mark .gemboss-ec-chipmark { display: block; }
|
|
67
|
+
/* A mark does not ellipsise, so once the chip IS the mark it holds its width — shrunk further it slid
|
|
68
|
+
under the mode chip beside it. The overflow that leaves is what tells ChatComposer to step on. */
|
|
69
|
+
.gemboss-ec-input:is([data-density="icon"], [data-density="tight"]) .gemboss-ec-enginepick:has(.has-mark) { flex-shrink: 0; }
|
|
70
|
+
/* Open: it is showing a popover, so it stops being quiet. */
|
|
71
|
+
.gemboss-ec-enginepick button[aria-expanded="true"] {
|
|
72
|
+
color: var(--gemboss-text); background-color: var(--gemboss-hover, rgba(0,0,0,0.05));
|
|
73
|
+
}
|
|
74
|
+
@media (pointer: coarse) {
|
|
75
|
+
/* SCOPED, and that word is load-bearing — this line was DEAD as `.gemboss-ec-stop`.
|
|
76
|
+
A media query adds no specificity, so the unscoped rule for the same class, declared ~1800 lines
|
|
77
|
+
LATER at its mouse size, beat this one on source order alone. Measured
|
|
78
|
+
2026-09-09 in a real coarse-pointer context: `matchMedia('(pointer: coarse)').matches === true`
|
|
79
|
+
and the computed width unchanged at its mouse size. Every phone got a Stop button ten pixels
|
|
80
|
+
under the target, while the rule meant to
|
|
81
|
+
fix that sat here reading as done — and the source test beside this block could not see it,
|
|
82
|
+
because a name being PRESENT is not the same fact as a rule WINNING.
|
|
83
|
+
`.gemboss-ec-input` is the same idiom the attach button above already uses for the same reason. */
|
|
84
|
+
.gemboss-ec-input .gemboss-ec-stop { width: 44px; height: 44px; min-width: 44px; }
|
|
85
|
+
/* SCOPED for the reason above: this control's own look is declared later, in the shell sheet. */
|
|
86
|
+
.gemboss-ec-input .gemboss-ec-stop-send { min-height: 44px; }
|
|
87
|
+
.gemboss-ec-enginepick button { min-height: 44px; }
|
|
88
|
+
}
|
|
89
|
+
/* Sized like the send button it replaces, so the composer does not reflow when a turn starts —
|
|
90
|
+
* a control that moves under the cursor is a control people misclick. So it is THE SAME CIRCLE AS SEND
|
|
91
|
+
* — same 44px, same 50% radius, only the colour and the glyph change. It used to be a 32px red
|
|
92
|
+
* rounded square beside a 44px round send: two shapes, two sizes, one slot. */
|
|
93
|
+
.gemboss-ec-stop {
|
|
94
|
+
flex: 0 0 auto;
|
|
95
|
+
display: inline-flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
width: 44px;
|
|
99
|
+
height: 44px;
|
|
100
|
+
min-width: 44px;
|
|
101
|
+
border: none;
|
|
102
|
+
border-radius: 50%;
|
|
103
|
+
background: var(--gemboss-critical, #d72c0d);
|
|
104
|
+
color: #fff;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
}
|
|
107
|
+
.gemboss-ec-stop svg { width: 12px; height: 12px; fill: currentColor; }
|
|
108
|
+
.gemboss-ec-stop:disabled { opacity: 0.6; cursor: default; }
|
|
109
|
+
.gemboss-ec-stop:hover { filter: brightness(1.08); }
|
|
110
|
+
.gemboss-ec-stop:focus-visible { outline: 2px solid var(--gemboss-focus, #005bd3); outline-offset: 2px; }
|