@gemboss/ui 0.6.4 → 0.6.9
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 +7 -3
- package/catalog/index.html +3 -3
- package/gemboss-ui.css +14 -8
- package/package.json +1 -1
- package/src/app-shell.css +8 -2
- package/src/kit-plain.js +19 -6
- package/src/shell.css +13 -11
package/README.md
CHANGED
|
@@ -100,7 +100,8 @@ The host registers `allowSide` with the provider, and every way into the panel c
|
|
|
100
100
|
a "side" remembered in storage: a host without it gets the popup.
|
|
101
101
|
|
|
102
102
|
The rail's way in is a `RailItem` with `className="gnav-assistant"`, linking to your full-screen
|
|
103
|
-
address (the admin puts it in `RailBottom`, above Inbox). That class draws the tinted pill
|
|
103
|
+
address (the admin puts it in `RailBottom`, above Inbox). That class draws the iris-tinted pill (0.6.8; it
|
|
104
|
+
was a mix of `--gemboss-primary`, grey once primary went near-black, so it read as a second selected row), and the
|
|
104
105
|
working / answer-waiting dot the provider switches on through `<body>` classes. Leave
|
|
105
106
|
`bodyClassPrefix` at its default so those rules apply. If the popup is what the person last used, a
|
|
106
107
|
plain click on that link opens the popup instead of navigating.
|
|
@@ -233,7 +234,8 @@ New primitives, Polaris' shapes on GemBoss tokens (`kit-plain.css`; the admin us
|
|
|
233
234
|
|
|
234
235
|
And four targets that measured under 44px at phone width are now 44: a compact text button's width, the bare input
|
|
235
236
|
inside `gemboss-field--group`, the brand mark alone (≤460px), a toggle chip. A popup opened by an Escape handler no
|
|
236
|
-
longer catches that same Escape (`openModal` listens
|
|
237
|
+
longer catches that same Escape (0.6.7: `openModal` listens at once and skips only the keydown that opened it; 0.5.1
|
|
238
|
+
listened from the next task, which on a busy page lost Escape or Tab pressed right after a popup opened). The rail item and segmented buttons draw the iris focus ring (they had
|
|
237
239
|
none: the browser drew its own), the popup × too (it drew near-black); at phone width the chat composer is 16px (iOS
|
|
238
240
|
zoomed it at 14) and the thread rail's "new chat" / collapse buttons are 44px under a fine pointer as well. Headings carry
|
|
239
241
|
their own line-height (a wrapped 22px title overlapped itself on the 20px body line), a badge is a tint of the ink (it
|
|
@@ -352,7 +354,9 @@ The admin's "Search ⌘K" box for every surface, as a real form (works with no J
|
|
|
352
354
|
```
|
|
353
355
|
|
|
354
356
|
- Submits `GET action?q=…` (`name` changes the parameter). `defaultValue` keeps the current query on the results page.
|
|
355
|
-
-
|
|
357
|
+
- 36px under a mouse; 44px under a finger **and at phone width** (≤767px, 0.6.5: the kit's own controls use the same pair,
|
|
358
|
+
and with `pointer: coarse` alone a narrow window measured 36 and failed `tap_target_size`). The input itself spans the
|
|
359
|
+
full 44 (0.6.6): the box is a form, so the input is what a finger has to hit. ⌘K hint hidden on focus and below 460px.
|
|
356
360
|
- Shortcut on a page without React: `gembossUI.initSearch()` from `kit-plain.js` (⌘K / Ctrl+K and "/" focus it,
|
|
357
361
|
Escape leaves it). "/" is ignored while another field has focus.
|
|
358
362
|
|
package/catalog/index.html
CHANGED
|
@@ -27,7 +27,7 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
27
27
|
@media (max-width:760px){.cat{grid-template-columns:1fr;padding:16px}.cat-nav{position:static;flex-direction:row;flex-wrap:wrap}}
|
|
28
28
|
</style></head><body>
|
|
29
29
|
<div class="cat"><nav class="cat-nav" aria-label="Mục"><a href="#tokens">Token</a><a href="#buttons">Nút</a><a href="#fields">Ô nhập</a><a href="#choice">Tab, nhóm nút, thẻ</a><a href="#surfaces">Thẻ, số liệu, bảng, trạng thái trống</a><a href="#report">Báo cáo</a><a href="#resizer">Thanh kéo giữa hai cột</a><a href="#modal">Hộp thoại, toast, menu (JS thuần)</a><a href="#shell">Khung app</a><a href="#auth">Đăng nhập</a><a href="#chat">Thân chat</a><a href="#assistant">Trợ lý: cột phiên</a><a href="#icons">Icon</a></nav>
|
|
30
|
-
<main><header class="cat-head"><h1>@gemboss/ui 0.6.
|
|
30
|
+
<main><header class="cat-head"><h1>@gemboss/ui 0.6.9</h1><p>Ngôn ngữ thiết kế chung của các công cụ GemBoss. Markup dưới mỗi ví dụ là đúng thứ package render ra; bề mặt không dùng React (HTML tĩnh, Python) chép markup này và nạp <code>gemboss-ui.css</code>. Dựng từ gemboss <code>d82cf2553a58</code>.</p></header>
|
|
31
31
|
<section id="tokens"><h2>Token</h2><p>Đổi giao diện một bề mặt bằng cách đặt lại token, không viết đè class. Tên và giá trị đọc thẳng từ gemboss-ui.css.</p><div class="cat-swatches"><div class="cat-swatch"><i style="background:var(--gemboss-primary)"></i><code>--gemboss-primary</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-primary-dark)"></i><code>--gemboss-primary-dark</code><span>#000000</span></div><div class="cat-swatch"><i style="background:var(--gemboss-primary-soft)"></i><code>--gemboss-primary-soft</code><span>#f2f3f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent)"></i><code>--gemboss-accent</code><span>#5661f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent-strong)"></i><code>--gemboss-accent-strong</code><span>#3a44de</span></div><div class="cat-swatch"><i style="background:var(--gemboss-accent-soft)"></i><code>--gemboss-accent-soft</code><span>#eef0fe</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text-strong)"></i><code>--gemboss-text-strong</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text)"></i><code>--gemboss-text</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-text-muted)"></i><code>--gemboss-text-muted</code><span>#686b71</span></div><div class="cat-swatch"><i style="background:var(--gemboss-surface)"></i><code>--gemboss-surface</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-canvas)"></i><code>--gemboss-canvas</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-preview-canvas)"></i><code>--gemboss-preview-canvas</code><span>#eceef1</span></div><div class="cat-swatch"><i style="background:var(--gemboss-rail-bg)"></i><code>--gemboss-rail-bg</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-panel-bg)"></i><code>--gemboss-panel-bg</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-divider)"></i><code>--gemboss-divider</code><span>#e5e7ea</span></div><div class="cat-swatch"><i style="background:var(--gemboss-divider-soft)"></i><code>--gemboss-divider-soft</code><span>#edeff2</span></div><div class="cat-swatch"><i style="background:var(--gemboss-hover)"></i><code>--gemboss-hover</code><span>#eceef2</span></div><div class="cat-swatch"><i style="background:var(--gemboss-active-bg)"></i><code>--gemboss-active-bg</code><span>#f0f1f4</span></div><div class="cat-swatch"><i style="background:var(--gemboss-active-fg)"></i><code>--gemboss-active-fg</code><span>#1b1c1e</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger)"></i><code>--gemboss-danger</code><span>#d72c0d</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-strong)"></i><code>--gemboss-danger-strong</code><span>#b42318</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-soft)"></i><code>--gemboss-danger-soft</code><span>#fff0e9</span></div><div class="cat-swatch"><i style="background:var(--gemboss-danger-border)"></i><code>--gemboss-danger-border</code><span>#f5c9bc</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success)"></i><code>--gemboss-success</code><span>#007f5f</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-strong)"></i><code>--gemboss-success-strong</code><span>#0a6a4c</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-soft)"></i><code>--gemboss-success-soft</code><span>#e3f1df</span></div><div class="cat-swatch"><i style="background:var(--gemboss-success-border)"></i><code>--gemboss-success-border</code><span>#b7dfcd</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning)"></i><code>--gemboss-warning</code><span>#9a6700</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning-soft)"></i><code>--gemboss-warning-soft</code><span>#fff8e0</span></div><div class="cat-swatch"><i style="background:var(--gemboss-warning-border)"></i><code>--gemboss-warning-border</code><span>#f5d569</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-bg)"></i><code>--gemboss-dark-bg</code><span>#0a0a0c</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-rail)"></i><code>--gemboss-dark-rail</code><span>#0e0e11</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-surface)"></i><code>--gemboss-dark-surface</code><span>#16161a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-surface-2)"></i><code>--gemboss-dark-surface-2</code><span>#1e1e24</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-divider)"></i><code>--gemboss-dark-divider</code><span>#2a2a31</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-text)"></i><code>--gemboss-dark-text</code><span>#ededf0</span></div><div class="cat-swatch"><i style="background:var(--gemboss-dark-muted)"></i><code>--gemboss-dark-muted</code><span>#9a9aa4</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-bg)"></i><code>--gemboss-field-bg</code><span>#fdfdfd</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-bg-hover)"></i><code>--gemboss-field-bg-hover</code><span>#fafafa</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-ink)"></i><code>--gemboss-field-ink</code><span>#303030</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-placeholder)"></i><code>--gemboss-field-placeholder</code><span>#616161</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line)"></i><code>--gemboss-field-line</code><span>#8a8a8a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line-hover)"></i><code>--gemboss-field-line-hover</code><span>#616161</span></div><div class="cat-swatch"><i style="background:var(--gemboss-field-line-active)"></i><code>--gemboss-field-line-active</code><span>#1a1a1a</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-ink)"></i><code>--gemboss-chip-ink</code><span>#303633</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-line)"></i><code>--gemboss-chip-line</code><span>#cfd6ce</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-line-hover)"></i><code>--gemboss-chip-line-hover</code><span>#aab4ad</span></div><div class="cat-swatch"><i style="background:var(--gemboss-chip-bg-hover)"></i><code>--gemboss-chip-bg-hover</code><span>#f6f7f5</span></div><div class="cat-swatch"><i style="background:var(--gemboss-on-primary)"></i><code>--gemboss-on-primary</code><span>#ffffff</span></div><div class="cat-swatch"><i style="background:var(--gemboss-app-bg)"></i><code>--gemboss-app-bg</code><span>#ffffff</span></div></div><table class="ui-table cat-scale"><thead><tr><th>Token</th><th>Giá trị</th><th>Mẫu</th></tr></thead><tbody><tr><td><code>--gemboss-fs-micro</code></td><td>11px</td><td style="font-size:var(--gemboss-fs-micro)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-caption</code></td><td>12px</td><td style="font-size:var(--gemboss-fs-caption)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-body</code></td><td>14px</td><td style="font-size:var(--gemboss-fs-body)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-body-lg</code></td><td>16px</td><td style="font-size:var(--gemboss-fs-body-lg)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h3</code></td><td>16px</td><td style="font-size:var(--gemboss-fs-h3)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h2</code></td><td>20px</td><td style="font-size:var(--gemboss-fs-h2)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-h1</code></td><td>24px</td><td style="font-size:var(--gemboss-fs-h1)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-fs-display</code></td><td>30px</td><td style="font-size:var(--gemboss-fs-display)">Ngôn ngữ thiết kế chung</td></tr><tr><td><code>--gemboss-radius-xs</code></td><td>4px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-xs)"></i></td></tr><tr><td><code>--gemboss-radius-sm</code></td><td>6px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-sm)"></i></td></tr><tr><td><code>--gemboss-radius-md</code></td><td>8px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-md)"></i></td></tr><tr><td><code>--gemboss-radius-lg</code></td><td>12px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-lg)"></i></td></tr><tr><td><code>--gemboss-radius-xl</code></td><td>16px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-xl)"></i></td></tr><tr><td><code>--gemboss-radius-2xl</code></td><td>20px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-2xl)"></i></td></tr><tr><td><code>--gemboss-radius-full</code></td><td>999px</td><td><i class="cat-radius" style="border-radius:var(--gemboss-radius-full)"></i></td></tr><tr><td><code>--gemboss-shadow-resting</code></td><td class="cat-small">0 1px 2px rgba(31, 30, 29, 0.04), 0 2px 6px rgba(31, 30, 29, 0.06)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-resting);border:0"></i></td></tr><tr><td><code>--gemboss-shadow-raised</code></td><td class="cat-small">0 4px 16px rgba(31, 30, 29, 0.10)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-raised);border:0"></i></td></tr><tr><td><code>--gemboss-shadow-overlay</code></td><td class="cat-small">0 16px 48px rgba(31, 30, 29, 0.18)</td><td><i class="cat-radius" style="box-shadow:var(--gemboss-shadow-overlay);border:0"></i></td></tr></tbody></table></section>
|
|
32
32
|
<section id="buttons"><h2>Nút</h2><p>Một lớp <code>ui-btn</code>, ba biến thể, tông đỏ, một cỡ nhỏ. Cao 32px dưới chuột (bằng ô nhập và ô chọn), 44px dưới ngón tay và ở khổ điện thoại, cỡ nhỏ cũng vậy. Dùng được trên <code><button></code> lẫn <code><a></code> (căn giữa sẵn).</p><figure class="cat-demo"><figcaption>Mặc định · chính · ghost · nhỏ · tắt</figcaption><div class="cat-live"><div class="cat-row"><button type="button" class="ui-btn">Lưu nháp</button><button type="button" class="ui-btn ui-btn--primary">Xuất bản</button><button type="button" class="ui-btn ui-btn--ghost">Huỷ</button><button type="button" class="ui-btn ui-btn--sm">Nhỏ</button><button type="button" class="ui-btn ui-btn--primary" disabled>Đang lưu…</button><a class="ui-btn" href="#buttons">Link dạng nút</a></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="cat-row">
|
|
33
33
|
<button type="button" class="ui-btn">Lưu nháp</button>
|
|
@@ -813,7 +813,7 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
813
813
|
<button type="submit" aria-label="Gửi">↑</button>
|
|
814
814
|
</form>
|
|
815
815
|
</div></code></pre></details></figure></section>
|
|
816
|
-
<section id="assistant"><h2>Trợ lý: cột phiên</h2><p><code>SessionRail</code> (React). Khung toàn màn hình / cửa sổ nổi / cột phải cao hết màn (<code>allowSide</code>) / thanh là <code>AssistantHost</code>, cần React vì có trạng thái.</p><figure class="cat-demo"><figcaption>SessionRail</figcaption><div class="cat-live"><div style="height:360px;display:flex"><aside class="gemboss-assistant-rail"><div class="gemboss-assistant-rail-top"><button type="button" class="gemboss-assistant-newchat" title="Phiên mới"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H5.5A1.5 1.5 0 0 0 4 5.5v9A1.5 1.5 0 0 0 5.5 16h9a1.5 1.5 0 0 0 1.5-1.5V9"></path><path d="M14.5 3.5l2 2L10 12H8v-2z"></path></g></svg><span>Phiên mới</span></button><button type="button" class="gemboss-assistant-collapse" aria-label="Collapse sidebar" title="Collapse"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5l-5 5 5 5"></path></g></svg></button></div><div class="gemboss-assistant-search gemboss-field gemboss-field--group"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="5"></circle><path d="M13 13l3.5 3.5"></path></g></svg><input class="gemboss-field__bare" placeholder="Tìm phiên…" aria-label="Search history" value=""/><kbd class="gemboss-assistant-kbd">/</kbd></div><div class="gemboss-assistant-threads"><div class="gemboss-assistant-group"><div class="gemboss-assistant-group-label">Đã ghim</div><div class="gemboss-assistant-thread"><button type="button" class="gemboss-assistant-thread-main" title="So sánh advertorial với listicle"><span class="gemboss-assistant-thread-titlerow"><svg viewBox="0 0 20 20" class="gemboss-assistant-pin-dot" aria-hidden="true" focusable="false"><path d="M8 3.5h4l-.5 5 2.5 2.5H6l2.5-2.5z" fill="currentColor"></path><path d="M10 11v5.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg><span class="gemboss-assistant-thread-title">So sánh advertorial với listicle</span></span><span class="gemboss-assistant-thread-meta">
|
|
816
|
+
<section id="assistant"><h2>Trợ lý: cột phiên</h2><p><code>SessionRail</code> (React). Khung toàn màn hình / cửa sổ nổi / cột phải cao hết màn (<code>allowSide</code>) / thanh là <code>AssistantHost</code>, cần React vì có trạng thái.</p><figure class="cat-demo"><figcaption>SessionRail</figcaption><div class="cat-live"><div style="height:360px;display:flex"><aside class="gemboss-assistant-rail"><div class="gemboss-assistant-rail-top"><button type="button" class="gemboss-assistant-newchat" title="Phiên mới"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H5.5A1.5 1.5 0 0 0 4 5.5v9A1.5 1.5 0 0 0 5.5 16h9a1.5 1.5 0 0 0 1.5-1.5V9"></path><path d="M14.5 3.5l2 2L10 12H8v-2z"></path></g></svg><span>Phiên mới</span></button><button type="button" class="gemboss-assistant-collapse" aria-label="Collapse sidebar" title="Collapse"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5l-5 5 5 5"></path></g></svg></button></div><div class="gemboss-assistant-search gemboss-field gemboss-field--group"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="5"></circle><path d="M13 13l3.5 3.5"></path></g></svg><input class="gemboss-field__bare" placeholder="Tìm phiên…" aria-label="Search history" value=""/><kbd class="gemboss-assistant-kbd">/</kbd></div><div class="gemboss-assistant-threads"><div class="gemboss-assistant-group"><div class="gemboss-assistant-group-label">Đã ghim</div><div class="gemboss-assistant-thread"><button type="button" class="gemboss-assistant-thread-main" title="So sánh advertorial với listicle"><span class="gemboss-assistant-thread-titlerow"><svg viewBox="0 0 20 20" class="gemboss-assistant-pin-dot" aria-hidden="true" focusable="false"><path d="M8 3.5h4l-.5 5 2.5 2.5H6l2.5-2.5z" fill="currentColor"></path><path d="M10 11v5.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg><span class="gemboss-assistant-thread-title">So sánh advertorial với listicle</span></span><span class="gemboss-assistant-thread-meta">25/8/2026</span></button><div class="gemboss-assistant-thread-actions"><button type="button" class="gemboss-assistant-thread-act" aria-label="Options"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="10" r=".8"></circle><circle cx="10" cy="10" r=".8"></circle><circle cx="15" cy="10" r=".8"></circle></g></svg></button></div></div></div><div class="gemboss-assistant-group"><div class="gemboss-assistant-group-label">Hôm nay</div><div class="gemboss-assistant-thread active"><button type="button" class="gemboss-assistant-thread-main" title="Store nào đang dựng loạt landing?"><span class="gemboss-assistant-thread-titlerow"><span class="gemboss-assistant-thread-title">Store nào đang dựng loạt landing?</span></span><span class="gemboss-assistant-thread-meta">3 giờ trước</span></button><div class="gemboss-assistant-thread-actions"><button type="button" class="gemboss-assistant-thread-act" aria-label="Options"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="10" r=".8"></circle><circle cx="10" cy="10" r=".8"></circle><circle cx="15" cy="10" r=".8"></circle></g></svg></button></div></div></div><div class="gemboss-assistant-group"><div class="gemboss-assistant-group-label">Hôm qua</div><div class="gemboss-assistant-thread"><button type="button" class="gemboss-assistant-thread-main" title="Landing điểm cao nhất của corpus"><span class="gemboss-assistant-thread-titlerow"><span class="gemboss-assistant-thread-title">Landing điểm cao nhất của corpus</span></span><span class="gemboss-assistant-thread-meta">1 ngày trước</span></button><div class="gemboss-assistant-thread-actions"><button type="button" class="gemboss-assistant-thread-act" aria-label="Options"><svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="5" cy="10" r=".8"></circle><circle cx="10" cy="10" r=".8"></circle><circle cx="15" cy="10" r=".8"></circle></g></svg></button></div></div></div></div></aside></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div style="height:360px;display:flex">
|
|
817
817
|
<aside class="gemboss-assistant-rail">
|
|
818
818
|
<div class="gemboss-assistant-rail-top">
|
|
819
819
|
<button type="button" class="gemboss-assistant-newchat" title="Phiên mới">
|
|
@@ -862,7 +862,7 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
862
862
|
</svg>
|
|
863
863
|
<span class="gemboss-assistant-thread-title">So sánh advertorial với listicle</span>
|
|
864
864
|
</span>
|
|
865
|
-
<span class="gemboss-assistant-thread-meta">
|
|
865
|
+
<span class="gemboss-assistant-thread-meta">25/8/2026</span>
|
|
866
866
|
</button>
|
|
867
867
|
<div class="gemboss-assistant-thread-actions">
|
|
868
868
|
<button type="button" class="gemboss-assistant-thread-act" aria-label="Options">
|
package/gemboss-ui.css
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* GemBoss admin design system — GENERATED — do not edit; regenerate from gemboss:
|
|
3
3
|
* node apps/admin/scripts/export-ui-css.mjs --out <path> (npm run export:ui-css)
|
|
4
|
-
* gemboss commit:
|
|
5
|
-
* sources sha256:
|
|
4
|
+
* gemboss commit: d82cf2553a585b741231a905e8845db7353c4726
|
|
5
|
+
* sources sha256: 67e1eb62e369a8fedf0d081820f8dffd9fd39801df6ca597060851c99cec3b42
|
|
6
6
|
* contents: 1 allow-listed rule from apps/admin/src/app/globals.css + packages/ui/src/gemboss-tokens.css
|
|
7
7
|
* (verbatim) + packages/ui/src/ui.css (verbatim) + packages/ui/src/gemboss-field.css (verbatim)
|
|
8
8
|
* + 181 rules from packages/ui/src/shell.css (the shared shell)
|
|
@@ -1305,15 +1305,15 @@ textarea.gemboss-field { height: auto; padding: 6px 12px; resize: vertical; }
|
|
|
1305
1305
|
margin: 8px 6px;
|
|
1306
1306
|
}
|
|
1307
1307
|
.gnav-item.gnav-assistant {
|
|
1308
|
-
color: var(--gemboss-
|
|
1308
|
+
color: var(--gemboss-accent-strong);
|
|
1309
1309
|
font-weight: 600;
|
|
1310
|
-
background:
|
|
1310
|
+
background: var(--gemboss-accent-soft);
|
|
1311
1311
|
}
|
|
1312
1312
|
.gnav-item.gnav-assistant .gnav-ico svg {
|
|
1313
|
-
fill: var(--gemboss-
|
|
1313
|
+
fill: var(--gemboss-accent);
|
|
1314
1314
|
}
|
|
1315
1315
|
.gnav-item.gnav-assistant:hover {
|
|
1316
|
-
background: color-mix(in srgb, var(--gemboss-
|
|
1316
|
+
background: color-mix(in srgb, var(--gemboss-accent) 16%, var(--gemboss-surface));
|
|
1317
1317
|
}
|
|
1318
1318
|
.gnav-item.gnav-assistant.active {
|
|
1319
1319
|
background: var(--gemboss-active-bg);
|
|
@@ -2124,8 +2124,11 @@ body.gemboss-assistant-unread .gnav-item.gnav-assistant::after {
|
|
|
2124
2124
|
.gemboss-topbar-search:hover { border-color: var(--gemboss-field-line-hover); background: var(--gemboss-field-bg-hover); }
|
|
2125
2125
|
.gemboss-topbar-search:focus-within { border-color: var(--gemboss-field-line-active); background: var(--gemboss-surface); }
|
|
2126
2126
|
.gemboss-topbar-search-icon { width: 18px; height: 18px; color: currentColor; }
|
|
2127
|
+
/* The INPUT is the tap target, not the box: the box is a <form>, and a tap on its icon or border does not focus
|
|
2128
|
+
anything. Inside a 1px border, `height: 100%` left the input 42 of the box's 44 (visual-gate tap_target_size at
|
|
2129
|
+
320/390/768 on GemFactory, 03/10/2026). It reaches over both border lines; transparent, so the border still shows. */
|
|
2127
2130
|
.gemboss-topbar-search-input {
|
|
2128
|
-
min-width: 0; height: 100
|
|
2131
|
+
min-width: 0; height: calc(100% + 2px); border: 0; background: transparent; padding: 0; margin: -1px 0;
|
|
2129
2132
|
font: inherit; font-size: var(--gemboss-fs-body); line-height: 18px; color: var(--gemboss-field-ink); outline: none;
|
|
2130
2133
|
}
|
|
2131
2134
|
.gemboss-topbar-search-input::placeholder { color: var(--gemboss-field-placeholder); opacity: 1; }
|
|
@@ -2134,7 +2137,10 @@ body.gemboss-assistant-unread .gnav-item.gnav-assistant::after {
|
|
|
2134
2137
|
padding: 1px 6px; color: var(--gemboss-text-muted); font-family: inherit; font-size: var(--gemboss-fs-micro); line-height: 16px;
|
|
2135
2138
|
}
|
|
2136
2139
|
.gemboss-topbar-search:focus-within .gemboss-topbar-search-kbd { display: none; }
|
|
2137
|
-
|
|
2140
|
+
/* 44 under a finger AND at phone width (0.6.4, 03/10/2026): the same pair of conditions the kit's own controls use
|
|
2141
|
+
(kit-plain.css, `max-width: 47.9975em`). Under `pointer: coarse` alone, a narrow desktop window and every visual-gate
|
|
2142
|
+
run measured the box at 36 and failed `tap_target_size` at 320/390/768 on GemFactory /framework. */
|
|
2143
|
+
@media (pointer: coarse), (max-width: 47.9975em) { .gemboss-topbar-search { height: 44px; } }
|
|
2138
2144
|
/* A phone has no ⌘ key, and the box needs every pixel the bar has left. */
|
|
2139
2145
|
@media (max-width: 460px) { .gemboss-topbar-search-kbd { display: none; } }
|
|
2140
2146
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/ui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"description": "GemBoss design system for every surface in the fleet: the admin shell (topbar + rail + drawer) as React components, and the admin stylesheet exported as one CSS file.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
package/src/app-shell.css
CHANGED
|
@@ -120,8 +120,11 @@
|
|
|
120
120
|
.gemboss-topbar-search:hover { border-color: var(--gemboss-field-line-hover); background: var(--gemboss-field-bg-hover); }
|
|
121
121
|
.gemboss-topbar-search:focus-within { border-color: var(--gemboss-field-line-active); background: var(--gemboss-surface); }
|
|
122
122
|
.gemboss-topbar-search-icon { width: 18px; height: 18px; color: currentColor; }
|
|
123
|
+
/* The INPUT is the tap target, not the box: the box is a <form>, and a tap on its icon or border does not focus
|
|
124
|
+
anything. Inside a 1px border, `height: 100%` left the input 42 of the box's 44 (visual-gate tap_target_size at
|
|
125
|
+
320/390/768 on GemFactory, 03/10/2026). It reaches over both border lines; transparent, so the border still shows. */
|
|
123
126
|
.gemboss-topbar-search-input {
|
|
124
|
-
min-width: 0; height: 100
|
|
127
|
+
min-width: 0; height: calc(100% + 2px); border: 0; background: transparent; padding: 0; margin: -1px 0;
|
|
125
128
|
font: inherit; font-size: var(--gemboss-fs-body); line-height: 18px; color: var(--gemboss-field-ink); outline: none;
|
|
126
129
|
}
|
|
127
130
|
.gemboss-topbar-search-input::placeholder { color: var(--gemboss-field-placeholder); opacity: 1; }
|
|
@@ -130,6 +133,9 @@
|
|
|
130
133
|
padding: 1px 6px; color: var(--gemboss-text-muted); font-family: inherit; font-size: var(--gemboss-fs-micro); line-height: 16px;
|
|
131
134
|
}
|
|
132
135
|
.gemboss-topbar-search:focus-within .gemboss-topbar-search-kbd { display: none; }
|
|
133
|
-
|
|
136
|
+
/* 44 under a finger AND at phone width (0.6.4, 03/10/2026): the same pair of conditions the kit's own controls use
|
|
137
|
+
(kit-plain.css, `max-width: 47.9975em`). Under `pointer: coarse` alone, a narrow desktop window and every visual-gate
|
|
138
|
+
run measured the box at 36 and failed `tap_target_size` at 320/390/768 on GemFactory /framework. */
|
|
139
|
+
@media (pointer: coarse), (max-width: 47.9975em) { .gemboss-topbar-search { height: 44px; } }
|
|
134
140
|
/* A phone has no ⌘ key, and the box needs every pixel the bar has left. */
|
|
135
141
|
@media (max-width: 460px) { .gemboss-topbar-search-kbd { display: none; } }
|
package/src/kit-plain.js
CHANGED
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
if (o.onClose) o.onClose(value);
|
|
86
86
|
}
|
|
87
87
|
function onKey(e) {
|
|
88
|
-
if (MODALS[MODALS.length - 1] !== m) return;
|
|
88
|
+
if (e === opening || MODALS[MODALS.length - 1] !== m) return;
|
|
89
89
|
// Escape while an IME is composing (zh, ja, vi telex) cancels the composition, not the dialog.
|
|
90
90
|
if (e.key === "Escape") { if (e.isComposing || e.keyCode === 229) return; e.preventDefault(); m.close(null); return; }
|
|
91
91
|
if (e.key !== "Tab") return;
|
|
@@ -103,13 +103,26 @@
|
|
|
103
103
|
overlay.addEventListener("click", function (e) { if (e.target === overlay && downOnBackdrop) m.close(null); downOnBackdrop = false; });
|
|
104
104
|
var x = panel.querySelector(".ui-modal__head .ui-modal__close");
|
|
105
105
|
if (x) x.onclick = function () { m.close(null); };
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
106
|
+
// Keys belong to the popup from the moment it opens (0.6.4). 0.5.1 attached this listener a task later, and on a busy
|
|
107
|
+
// page the browser runs input before timers: Escape or Tab pressed right after opening was lost, the popup stayed open
|
|
108
|
+
// or Tab left the trap (CI lost "stacked popups" three runs in a row, 03/10/2026). The one key it must NOT take is the
|
|
109
|
+
// keydown that is opening it: a popup opened from a keydown handler (content-radar's composer asks "close without
|
|
110
|
+
// saving?" on Escape) would receive that same Escape when it bubbles up to window — a listener added during a dispatch
|
|
111
|
+
// still runs if its target has not been reached yet — and close at once. `window.event` is that event while it runs.
|
|
112
|
+
var opening = window.event;
|
|
113
|
+
addEventListener("keydown", onKey);
|
|
110
114
|
if (!MODALS.length) savedOverflow = document.body.style.overflow;
|
|
111
115
|
MODALS.push(m);
|
|
112
|
-
|
|
116
|
+
// Inside the top open MODAL <dialog>, not <body> (0.6.9, 03/10/2026). A `<dialog class="ui-modal">` opened with
|
|
117
|
+
// showModal() sits in the top layer and makes the rest of the page inert, so a confirm appended to <body> drew
|
|
118
|
+
// BEHIND it and could not be clicked: GemCommunity's lesson editor asked "Discard changes?" into a dead layer and
|
|
119
|
+
// had to move the overlay by hand. Appended inside that dialog, the overlay rides the top layer with it.
|
|
120
|
+
var host = document.body;
|
|
121
|
+
try {
|
|
122
|
+
var open = document.querySelectorAll("dialog[open]");
|
|
123
|
+
for (var di = open.length - 1; di >= 0; di--) { if (open[di].matches(":modal")) { host = open[di]; break; } }
|
|
124
|
+
} catch (_) { /* a browser without :modal keeps <body> */ }
|
|
125
|
+
host.append(overlay);
|
|
113
126
|
document.body.style.overflow = "hidden";
|
|
114
127
|
setTimeout(function () { (panel.querySelector("[autofocus]") || panel).focus(); }, 0);
|
|
115
128
|
return m;
|
package/src/shell.css
CHANGED
|
@@ -406,8 +406,8 @@
|
|
|
406
406
|
.gnav-item:focus-visible { outline: 2px solid var(--gemboss-accent); outline-offset: -2px; } /* the one iris ring (0.5.1): had none, the browser drew its own */
|
|
407
407
|
|
|
408
408
|
/* neutral gray on white (Polaris-like) */
|
|
409
|
-
/* Active hub:
|
|
410
|
-
LEAF is the destination, the hub quiets to
|
|
409
|
+
/* Active hub: the neutral selected pill (--gemboss-active-bg) when you're ON the hub's own page. But when a deeper
|
|
410
|
+
LEAF is the destination, the hub quiets to bold text only — so the leaf below carries
|
|
411
411
|
the strong "you are here" and the two don't compete. */
|
|
412
412
|
.gnav-item.active { color: var(--gemboss-active-fg); font-weight: 600; }
|
|
413
413
|
|
|
@@ -450,7 +450,7 @@
|
|
|
450
450
|
|
|
451
451
|
.gnav-subitem:hover { background: var(--gemboss-nav-hover); color: var(--gemboss-text); }
|
|
452
452
|
|
|
453
|
-
/* Active leaf — the strong "you are here":
|
|
453
|
+
/* Active leaf — the strong "you are here": the neutral selected pill + a near-black bar on the tree line. */
|
|
454
454
|
.gnav-subitem.active { color: var(--gemboss-active-fg); font-weight: 600; background: var(--gemboss-active-bg); }
|
|
455
455
|
|
|
456
456
|
.gnav-subitem.active:hover { background: var(--gemboss-active-bg); color: var(--gemboss-active-fg); }
|
|
@@ -470,19 +470,21 @@
|
|
|
470
470
|
/* section separator (Assistant · Settings) */
|
|
471
471
|
.gnav-sep { height: 1px; background: var(--gemboss-divider); margin: 8px 6px; }
|
|
472
472
|
|
|
473
|
-
/* Assistant = the AI front door, not just another row.
|
|
473
|
+
/* Assistant = the AI front door, not just another row. An iris-tinted pill that stands
|
|
474
474
|
apart from the neutral hubs above; deepens on hover; becomes the standard active pill
|
|
475
|
-
when it's the current page.
|
|
475
|
+
when it's the current page. It was a --gemboss-primary mix, which turned grey (#ececed)
|
|
476
|
+
once primary went near-black and read as a second selected row next to #f0f1f4 (0.6.8).
|
|
477
|
+
Contrast on --gemboss-accent-soft: label 6.05:1, icon and the iris focus ring 4.19:1 (16% hover: 5.56 / 3.84). */
|
|
476
478
|
.gnav-item.gnav-assistant {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
background:
|
|
479
|
+
color: var(--gemboss-accent-strong); font-weight: 600;
|
|
480
|
+
/* The kit's iris tint token, not a mix with #fff: a dark surface remaps -soft and -strong together, so the label keeps
|
|
481
|
+
its contrast there too (review #3710: a #fff mix under gempress's dark -strong would be 1.8:1). */
|
|
482
|
+
background: var(--gemboss-accent-soft);
|
|
481
483
|
}
|
|
482
484
|
|
|
483
|
-
.gnav-item.gnav-assistant .gnav-ico svg { fill: var(--gemboss-
|
|
485
|
+
.gnav-item.gnav-assistant .gnav-ico svg { fill: var(--gemboss-accent); }
|
|
484
486
|
|
|
485
|
-
.gnav-item.gnav-assistant:hover { background: color-mix(in srgb, var(--gemboss-
|
|
487
|
+
.gnav-item.gnav-assistant:hover { background: color-mix(in srgb, var(--gemboss-accent) 16%, var(--gemboss-surface)); }
|
|
486
488
|
|
|
487
489
|
.gnav-item.gnav-assistant.active { background: var(--gemboss-active-bg); color: var(--gemboss-active-fg); }
|
|
488
490
|
|