@gemboss/ui 0.1.1 → 0.2.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 +48 -0
- package/catalog/index.html +94 -8
- package/gemboss-ui.css +161 -11
- package/package.json +3 -2
- package/src/ui.css +159 -9
package/README.md
CHANGED
|
@@ -109,6 +109,36 @@ Its styles are the `gemboss-assistant-*` rules in `packages/ui/src/shell.css` (t
|
|
|
109
109
|
@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
110
|
through `fullActions`; a surface without those leaves them out.
|
|
111
111
|
|
|
112
|
+
## Resizable panels
|
|
113
|
+
|
|
114
|
+
`ui-resizer` is the seam a person drags between two panels; `ui-resizing` on `<body>` is the drag in
|
|
115
|
+
progress (`dragging` on the seam itself is optional and keeps its grip lit). The admin's four editors
|
|
116
|
+
(chat | preview) use them; they are made for gemcare's inbox columns too, which adopt them at their
|
|
117
|
+
next `ui:sync`. The CSS owns the LOOK: a grip that appears on hover, on
|
|
118
|
+
keyboard focus and while dragging; a 24px finger band on coarse pointers that keeps the visible seam
|
|
119
|
+
thin; and a full-page shield while dragging, so an iframe never swallows the pointer.
|
|
120
|
+
|
|
121
|
+
```html
|
|
122
|
+
<div class="ui-resizer" role="separator" aria-orientation="vertical" tabindex="0"
|
|
123
|
+
aria-label="Conversation list width" aria-valuenow="340" aria-valuemin="260" aria-valuemax="520"></div>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The surface owns where the seam sits and what it does. Five rules, each paid for once in the admin
|
|
127
|
+
(`apps/admin/src/components/shell/useResizableChatPanel.ts` holds the measurements):
|
|
128
|
+
|
|
129
|
+
1. **Pointer events, not mouse events.** `mousedown` never fires for a finger.
|
|
130
|
+
2. **Keyboard.** A focusable separator that ignores keys is a control that does nothing: arrows move
|
|
131
|
+
it, Home / End go to the floor and the ceiling. Leave Alt / Cmd / Ctrl + arrow to the browser
|
|
132
|
+
(Back, Forward), and say the REAL ceiling in `aria-valuemax`, not the constant.
|
|
133
|
+
3. **Clamp to the box, not to a constant.** A width saved on a 1920px screen follows the person to a
|
|
134
|
+
1280px one. The ceiling is what the box can spare after the other panel's floor.
|
|
135
|
+
4. **Release on `pointerup`, `pointercancel` and window `blur`.** A missed release leaves the shield
|
|
136
|
+
over the page and nothing can be clicked.
|
|
137
|
+
5. **Remember per browser** (localStorage), and recompute the clamp when the box changes size.
|
|
138
|
+
|
|
139
|
+
React surfaces: `useResizableChatPanel` in the admin is the reference. Others write the same markup and
|
|
140
|
+
the five rules by hand; the catalog's "Thanh kéo giữa hai cột" shows the markup.
|
|
141
|
+
|
|
112
142
|
## The topbar kit: lockup, account, icons
|
|
113
143
|
|
|
114
144
|
What goes INTO the frame, so a surface does not draw its own (28/09/2026: GemFactory's rail icons came
|
|
@@ -205,4 +235,22 @@ Publish from a worktree at the MERGED commit on `main`, not from a branch, so th
|
|
|
205
235
|
to publish (403) from an account without two-factor auth; the account needs 2FA set to
|
|
206
236
|
"Authorization and publishing".
|
|
207
237
|
|
|
238
|
+
The account's second factor is a passkey, so run the publish in a real terminal as
|
|
239
|
+
`npm publish --auth-type=web` and approve the page it opens (each approval page lives 5 minutes).
|
|
240
|
+
Without a terminal npm answers `EOTP` instead of offering the page. After `+ @gemboss/ui@x.y.z`
|
|
241
|
+
npm says the package "is being processed": `npm view` returns 404 for a few more minutes (0.1.1,
|
|
242
|
+
30/09/2026). Publishing from CI with npm trusted publishing needs a GitHub-hosted runner, and this
|
|
243
|
+
repo's are blocked by the Actions budget since 12/09/2026 (see `admin-freshness.yml`).
|
|
244
|
+
|
|
208
245
|
Consumers pin a version; a bump is a PR in their repo, not a surprise.
|
|
246
|
+
|
|
247
|
+
## Who is on which version
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
npm run fleet --prefix packages/ui # every surface vs the npm release (reads their repos with `gh`)
|
|
251
|
+
node packages/ui/scripts/fleet.mjs --release-only # main vs npm only; CI runs this as a warning
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Compared by content: sha256 of `gemboss-ui.css` minus its `gemboss commit:` line, so a gemboss commit
|
|
255
|
+
that leaves the design system alone does not make everyone look stale. Add a row to `CONSUMERS` in
|
|
256
|
+
`packages/ui/scripts/fleet.mjs` when a surface starts using the package.
|
package/catalog/index.html
CHANGED
|
@@ -26,8 +26,8 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
26
26
|
|
|
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
|
-
<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="#modal">Hộp thoại</a><a href="#shell">Khung app</a><a href="#auth">Đăng nhập</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.
|
|
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="#resizer">Thanh kéo giữa hai cột</a><a href="#modal">Hộp thoại</a><a href="#shell">Khung app</a><a href="#auth">Đăng nhập</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.2.0</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>62b85f8a0c75</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ể, một cỡ nhỏ. 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>
|
|
@@ -70,15 +70,93 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
70
70
|
<span class="ui-tag ui-tag--accent">Đã duyệt</span>
|
|
71
71
|
<span class="ui-tag ui-tag--chip">SECTION</span>
|
|
72
72
|
<span class="ui-badge">12</span>
|
|
73
|
+
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Tab bằng link (<code><nav class=ui-tabs></code>, <code>aria-current=page</code> là tab đang chọn)</figcaption><div class="cat-live"><nav class="ui-tabs" aria-label="Chế độ xem"><a href="#" aria-current="page">Tăng trưởng</a><a href="#">Mới lên</a><a href="#">Quảng cáo</a></nav></div><details class="cat-code"><summary>Markup</summary><pre><code><nav class="ui-tabs" aria-label="Chế độ xem">
|
|
74
|
+
<a href="#" aria-current="page">Tăng trưởng</a>
|
|
75
|
+
<a href="#">Mới lên</a>
|
|
76
|
+
<a href="#">Quảng cáo</a>
|
|
77
|
+
</nav></code></pre></details></figure><figure class="cat-demo"><figcaption>Lọc bằng link (<code>ui-seg</code> với <code><a></code>; danh sách dài dùng <code>ui-chips</code>)</figcaption><div class="cat-live"><div class="ui-seg" role="group" aria-label="Hạng"><a class="is-text" href="#" aria-current="page">Tất cả</a><a class="is-text" href="#">Plus</a><a class="is-text" href="#">Premier</a></div><div class="ui-chips" style="margin-top:12px"><a class="ui-tag ui-tag--chip" href="#" aria-current="page">CRO</a><a class="ui-tag ui-tag--chip" href="#">Klaviyo</a><a class="ui-tag ui-tag--chip" href="#">Meta Ads</a></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-seg" role="group" aria-label="Hạng">
|
|
78
|
+
<a class="is-text" href="#" aria-current="page">Tất cả</a>
|
|
79
|
+
<a class="is-text" href="#">Plus</a>
|
|
80
|
+
<a class="is-text" href="#">Premier</a>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="ui-chips" style="margin-top:12px">
|
|
83
|
+
<a class="ui-tag ui-tag--chip" href="#" aria-current="page">CRO</a>
|
|
84
|
+
<a class="ui-tag ui-tag--chip" href="#">Klaviyo</a>
|
|
85
|
+
<a class="ui-tag ui-tag--chip" href="#">Meta Ads</a>
|
|
86
|
+
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Tông màu cho tag (<code>--success</code> / <code>--warning</code> / <code>--danger</code>)</figcaption><div class="cat-live"><div class="cat-row"><span class="ui-tag ui-tag--success">Đã tích hợp</span><span class="ui-tag ui-tag--warning">Chờ duyệt</span><span class="ui-tag ui-tag--danger">Lỗi tải</span></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="cat-row">
|
|
87
|
+
<span class="ui-tag ui-tag--success">Đã tích hợp</span>
|
|
88
|
+
<span class="ui-tag ui-tag--warning">Chờ duyệt</span>
|
|
89
|
+
<span class="ui-tag ui-tag--danger">Lỗi tải</span>
|
|
90
|
+
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Danh sách link dạng chip (<code>ui-chips</code>: cao ≥ 28px, 44px dưới ngón tay, khe 8px)</figcaption><div class="cat-live"><div class="ui-chips"><a class="ui-tag ui-tag--chip" href="#">Judge.me Product Reviews</a><a class="ui-tag ui-tag--chip" href="#">Klaviyo</a><a class="ui-tag ui-tag--chip" href="#">Instafeed</a></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-chips">
|
|
91
|
+
<a class="ui-tag ui-tag--chip" href="#">Judge.me Product Reviews</a>
|
|
92
|
+
<a class="ui-tag ui-tag--chip" href="#">Klaviyo</a>
|
|
93
|
+
<a class="ui-tag ui-tag--chip" href="#">Instafeed</a>
|
|
73
94
|
</div></code></pre></details></figure></section>
|
|
74
|
-
<section id="surfaces"><h2>Thẻ, số liệu, bảng, trạng thái trống</h2><figure class="cat-demo"><figcaption>
|
|
95
|
+
<section id="surfaces"><h2>Thẻ, số liệu, bảng, trạng thái trống</h2><figure class="cat-demo"><figcaption>Tiêu đề trang (<code>ui-page-head</code>) · chữ phụ (<code>ui-muted</code>)</figcaption><div class="cat-live"><div class="ui-page-head"><h1>Đối thủ</h1><p>Page builder nào đang tích hợp với ai.</p></div><p class="ui-muted">Cập nhật 01/10, 300 trang app đã đọc.</p></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-page-head">
|
|
96
|
+
<h1>Đối thủ</h1>
|
|
97
|
+
<p>Page builder nào đang tích hợp với ai.</p>
|
|
98
|
+
</div>
|
|
99
|
+
<p class="ui-muted">Cập nhật 01/10, 300 trang app đã đọc.</p></code></pre></details></figure><figure class="cat-demo"><figcaption>Thanh công cụ (<code>ui-toolbar</code>)</figcaption><div class="cat-live"><div class="ui-toolbar"><div class="gemboss-field gemboss-field--group" style="flex:1 1 260px"><input class="gemboss-field__bare" placeholder="Tìm app…" aria-label="Tìm app"></div><select class="ui-select" aria-label="Xếp theo"><option>Store cài</option></select><button type="button" class="ui-btn">Xuất CSV</button></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-toolbar">
|
|
100
|
+
<div class="gemboss-field gemboss-field--group" style="flex:1 1 260px">
|
|
101
|
+
<input class="gemboss-field__bare" placeholder="Tìm app…" aria-label="Tìm app">
|
|
102
|
+
</div>
|
|
103
|
+
<select class="ui-select" aria-label="Xếp theo">
|
|
104
|
+
<option>Store cài</option>
|
|
105
|
+
</select>
|
|
106
|
+
<button type="button" class="ui-btn">Xuất CSV</button>
|
|
107
|
+
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Ô số liệu (<code>ui-metrics</code> / <code>ui-metric</code>, tông <code>--success|--warning|--danger</code>)</figcaption><div class="cat-live"><div class="ui-metrics"><div class="ui-metric"><span class="ui-metric__val">31.503</span><span class="ui-metric__lbl">App</span></div><div class="ui-metric ui-metric--success"><span class="ui-metric__val">157</span><span class="ui-metric__lbl">Khớp</span></div><div class="ui-metric ui-metric--danger"><span class="ui-metric__val">3</span><span class="ui-metric__lbl">Lỗi</span></div><a class="ui-metric" href="#"><span class="ui-metric__val">$41</span><span class="ui-metric__lbl">Chi phí tháng này</span></a></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-metrics">
|
|
108
|
+
<div class="ui-metric">
|
|
109
|
+
<span class="ui-metric__val">31.503</span>
|
|
110
|
+
<span class="ui-metric__lbl">App</span>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="ui-metric ui-metric--success">
|
|
113
|
+
<span class="ui-metric__val">157</span>
|
|
114
|
+
<span class="ui-metric__lbl">Khớp</span>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="ui-metric ui-metric--danger">
|
|
117
|
+
<span class="ui-metric__val">3</span>
|
|
118
|
+
<span class="ui-metric__lbl">Lỗi</span>
|
|
119
|
+
</div>
|
|
120
|
+
<a class="ui-metric" href="#">
|
|
121
|
+
<span class="ui-metric__val">$41</span>
|
|
122
|
+
<span class="ui-metric__lbl">Chi phí tháng này</span>
|
|
123
|
+
</a>
|
|
124
|
+
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Cặp nhãn – giá trị (<code>ui-dl</code>)</figcaption><div class="cat-live"><dl class="ui-dl"><div><dt>Quốc gia</dt><dd>IL</dd></div><div><dt>Store cài</dt><dd>133.340</dd></div><div><dt>Điểm tiềm năng</dt><dd>71</dd></div></dl></div><details class="cat-code"><summary>Markup</summary><pre><code><dl class="ui-dl">
|
|
125
|
+
<div>
|
|
126
|
+
<dt>Quốc gia</dt>
|
|
127
|
+
<dd>IL</dd>
|
|
128
|
+
</div>
|
|
129
|
+
<div>
|
|
130
|
+
<dt>Store cài</dt>
|
|
131
|
+
<dd>133.340</dd>
|
|
132
|
+
</div>
|
|
133
|
+
<div>
|
|
134
|
+
<dt>Điểm tiềm năng</dt>
|
|
135
|
+
<dd>71</dd>
|
|
136
|
+
</div>
|
|
137
|
+
</dl></code></pre></details></figure><figure class="cat-demo"><figcaption>Thông báo (<code>ui-notice</code>, tông <code>--warning|--danger|--success</code>)</figcaption><div class="cat-live"><div class="ui-notice ui-notice--warning">Token Slack đã hết hạn: bản tin thứ Hai sẽ không gửi được.</div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-notice ui-notice--warning">Token Slack đã hết hạn: bản tin thứ Hai sẽ không gửi được.</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Phân trang (<code>ui-pager</code>)</figcaption><div class="cat-live"><nav class="ui-pager" aria-label="Trang"><a href="#" rel="prev"><span aria-hidden="true">‹</span> Trước</a><span aria-current="page">2 / 9</span><a href="#" rel="next">Sau <span aria-hidden="true">›</span></a></nav></div><details class="cat-code"><summary>Markup</summary><pre><code><nav class="ui-pager" aria-label="Trang">
|
|
138
|
+
<a href="#" rel="prev">
|
|
139
|
+
<span aria-hidden="true">‹</span> Trước</a>
|
|
140
|
+
<span aria-current="page">2 / 9</span>
|
|
141
|
+
<a href="#" rel="next">Sau <span aria-hidden="true">›</span>
|
|
142
|
+
</a>
|
|
143
|
+
</nav></code></pre></details></figure><figure class="cat-demo"><figcaption>Card</figcaption><div class="cat-live"><div class="ui-card" style="padding:16px">Một khối nội dung. Nền, viền, bo góc và bóng lấy từ token.</div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-card" style="padding:16px">Một khối nội dung. Nền, viền, bo góc và bóng lấy từ token.</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Đầu thẻ có nút (<code>ui-card__head</code>)</figcaption><div class="cat-live"><div class="ui-card"><div class="ui-card__head"><h3 class="ui-card__title">Hạng đối tác</h3><div><button type="button" class="ui-btn ui-btn--primary">Đổi hạng</button><button type="button" class="ui-btn">Kết thúc</button></div></div><p class="ui-muted" style="margin:0">Partner từ 01/08/2026.</p></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-card">
|
|
144
|
+
<div class="ui-card__head">
|
|
145
|
+
<h3 class="ui-card__title">Hạng đối tác</h3>
|
|
146
|
+
<div>
|
|
147
|
+
<button type="button" class="ui-btn ui-btn--primary">Đổi hạng</button>
|
|
148
|
+
<button type="button" class="ui-btn">Kết thúc</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<p class="ui-muted" style="margin:0">Partner từ 01/08/2026.</p>
|
|
152
|
+
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Số liệu (<code>ui-stats</code> / <code>ui-stat</code>)</figcaption><div class="cat-live"><div class="ui-stats"><div class="ui-stat"><b>17</b> Nháp</div><div class="ui-stat is-clickable active"><b>4</b> Đang duyệt</div><div class="ui-stat is-clickable"><b>0</b> Loại</div></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-stats">
|
|
75
153
|
<div class="ui-stat">
|
|
76
154
|
<b>17</b> Nháp</div>
|
|
77
155
|
<div class="ui-stat is-clickable active">
|
|
78
156
|
<b>4</b> Đang duyệt</div>
|
|
79
157
|
<div class="ui-stat is-clickable">
|
|
80
158
|
<b>0</b> Loại</div>
|
|
81
|
-
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Bảng (<code>is-num</code> căn phải, số đều cột)</figcaption><div class="cat-live"><div class="ui-table-wrap"><table class="ui-table"><thead><tr><th>Store</th><th>Ngành</th><th class="is-num">Trang</th></tr></thead><tbody><tr><td>fluke.com</td><td>Audio</td><td class="is-num">1.284</td></tr><tr><td>glowrecipe.com</td><td>Mỹ phẩm</td><td class="is-num">312</td></tr></tbody></table></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-table-wrap">
|
|
159
|
+
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Bảng (<code>is-num</code> căn phải, số đều cột; <code>ui-sub</code> dòng phụ trong ô)</figcaption><div class="cat-live"><div class="ui-table-wrap"><table class="ui-table"><thead><tr><th>Store</th><th>Ngành</th><th class="is-num">Trang</th></tr></thead><tbody><tr><td>fluke.com<span class="ui-sub">Shopify Plus</span></td><td>Audio</td><td class="is-num">1.284</td></tr><tr><td>glowrecipe.com<span class="ui-sub">Shopify</span></td><td>Mỹ phẩm</td><td class="is-num">312</td></tr></tbody></table></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-table-wrap">
|
|
82
160
|
<table class="ui-table">
|
|
83
161
|
<thead>
|
|
84
162
|
<tr>
|
|
@@ -89,12 +167,14 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
89
167
|
</thead>
|
|
90
168
|
<tbody>
|
|
91
169
|
<tr>
|
|
92
|
-
<td>fluke.com</
|
|
170
|
+
<td>fluke.com<span class="ui-sub">Shopify Plus</span>
|
|
171
|
+
</td>
|
|
93
172
|
<td>Audio</td>
|
|
94
173
|
<td class="is-num">1.284</td>
|
|
95
174
|
</tr>
|
|
96
175
|
<tr>
|
|
97
|
-
<td>glowrecipe.com</
|
|
176
|
+
<td>glowrecipe.com<span class="ui-sub">Shopify</span>
|
|
177
|
+
</td>
|
|
98
178
|
<td>Mỹ phẩm</td>
|
|
99
179
|
<td class="is-num">312</td>
|
|
100
180
|
</tr>
|
|
@@ -102,6 +182,12 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
102
182
|
</table>
|
|
103
183
|
</div></code></pre></details></figure><figure class="cat-demo"><figcaption>Trống</figcaption><div class="cat-live"><div class="ui-empty">Chưa có gì ở đây.<button type="button" class="ui-btn ui-btn--primary">Tạo mục đầu tiên</button></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-empty">Chưa có gì ở đây.<button type="button" class="ui-btn ui-btn--primary">Tạo mục đầu tiên</button>
|
|
104
184
|
</div></code></pre></details></figure></section>
|
|
185
|
+
<section id="resizer"><h2>Thanh kéo giữa hai cột</h2><p>Thanh <code>ui-resizer</code> đứng giữa hai cột, người dùng kéo để đổi độ rộng. Phần NHÌN (vạch nắm hiện khi rê chuột hoặc focus bàn phím, vùng chạm 24px cho ngón tay, lớp che khi đang kéo) là của gemboss-ui.css; vị trí và hành vi là của bề mặt: dùng <b>pointer event</b> (chạm là một pointer), gắn <code>ui-resizing</code> lên <code><body></code> trong lúc kéo, phím mũi tên / Home / End di chuyển nó, kẹp theo bề rộng THẬT của khung (không theo một con số), và nhớ độ rộng theo trình duyệt. Admin: <code>useResizableChatPanel</code>; gemcare: viết tay theo đúng markup này.</p><figure class="cat-demo"><figcaption>Rê chuột hoặc Tab vào khe giữa để thấy vạch nắm</figcaption><div class="cat-live"><div style="display:grid;grid-template-columns:200px 5px minmax(0,1fr);height:140px;border:1px solid var(--gemboss-divider);border-radius:var(--gemboss-radius-md);overflow:hidden"><div style="padding:12px;background:var(--gemboss-surface)">Danh sách</div><div class="ui-resizer" role="separator" aria-orientation="vertical" aria-label="Độ rộng danh sách" tabindex="0" aria-valuenow="200" aria-valuemin="160" aria-valuemax="420"></div><div style="padding:12px;background:var(--gemboss-surface)">Nội dung</div></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div style="display:grid;grid-template-columns:200px 5px minmax(0,1fr);height:140px;border:1px solid var(--gemboss-divider);border-radius:var(--gemboss-radius-md);overflow:hidden">
|
|
186
|
+
<div style="padding:12px;background:var(--gemboss-surface)">Danh sách</div>
|
|
187
|
+
<div class="ui-resizer" role="separator" aria-orientation="vertical" aria-label="Độ rộng danh sách" tabindex="0" aria-valuenow="200" aria-valuemin="160" aria-valuemax="420">
|
|
188
|
+
</div>
|
|
189
|
+
<div style="padding:12px;background:var(--gemboss-surface)">Nội dung</div>
|
|
190
|
+
</div></code></pre></details></figure></section>
|
|
105
191
|
<section id="modal"><h2>Hộp thoại</h2><p>Lớp phủ <code>ui-overlay</code> + hộp <code>ui-modal</code> (ở đây vẽ không có lớp phủ để xem được trong trang). Thân hộp thoại tự đệm: <code>ui-modal__body</code> / <code>__foot</code> chưa có CSS nào, kể cả trong admin.</p><figure class="cat-demo"><figcaption>Hộp thoại (<code>ui-modal__head</code> có nút đóng 44px)</figcaption><div class="cat-live"><div class="ui-modal" role="dialog" aria-modal="true" aria-labelledby="cat-m"><div class="ui-modal__head"><div class="ui-modal__title" id="cat-m">Xoá phiên này?</div><button type="button" class="ui-modal__close" aria-label="Đóng"><svg viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" focusable="false"><g fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5l10 10M15 5 5 15"></path></g></svg></button></div><div style="padding:16px 20px">Lịch sử vẫn còn trong sổ; phiên chỉ ẩn khỏi danh sách.<div class="cat-row" style="justify-content:flex-end;margin-top:16px"><button type="button" class="ui-btn ui-btn--ghost">Huỷ</button><button type="button" class="ui-btn ui-btn--primary">Xoá</button></div></div></div></div><details class="cat-code"><summary>Markup</summary><pre><code><div class="ui-modal" role="dialog" aria-modal="true" aria-labelledby="cat-m">
|
|
106
192
|
<div class="ui-modal__head">
|
|
107
193
|
<div class="ui-modal__title" id="cat-m">Xoá phiên này?</div>
|
|
@@ -290,7 +376,7 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
290
376
|
</aside>
|
|
291
377
|
</div>
|
|
292
378
|
</main></code></pre></details></figure></section>
|
|
293
|
-
<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 / 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">
|
|
379
|
+
<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 / 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">22/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">
|
|
294
380
|
<aside class="gemboss-assistant-rail">
|
|
295
381
|
<div class="gemboss-assistant-rail-top">
|
|
296
382
|
<button type="button" class="gemboss-assistant-newchat" title="Phiên mới">
|
|
@@ -339,7 +425,7 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
|
|
|
339
425
|
</svg>
|
|
340
426
|
<span class="gemboss-assistant-thread-title">So sánh advertorial với listicle</span>
|
|
341
427
|
</span>
|
|
342
|
-
<span class="gemboss-assistant-thread-meta">
|
|
428
|
+
<span class="gemboss-assistant-thread-meta">22/8/2026</span>
|
|
343
429
|
</button>
|
|
344
430
|
<div class="gemboss-assistant-thread-actions">
|
|
345
431
|
<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: 62b85f8a0c756bf63d370bff60e6debaaf4ea4df
|
|
5
|
+
* sources sha256: fd64ea38ae59bb5ecf1c38da341be89f963c9fef11bd3189e83433588f4d88b5
|
|
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
|
* + 180 rules from packages/ui/src/shell.css (the shared shell)
|
|
@@ -214,6 +214,10 @@
|
|
|
214
214
|
--ui-accent-soft: var(--gemboss-active-bg, #f0f1f4); /* tinted bg for a selected/active state */
|
|
215
215
|
--ui-accent-strong: var(--gemboss-active-fg, #1b1c1e); /* readable accent text ON --ui-accent-soft */
|
|
216
216
|
--ui-text-strong: var(--gemboss-text-strong, #1b1c1e);
|
|
217
|
+
/* Chữ theo tông (ui-metric, …). Nền tối của admin ánh xạ lại bên dưới để còn đọc được. */
|
|
218
|
+
--ui-success-fg: var(--gemboss-success);
|
|
219
|
+
--ui-warning-fg: var(--gemboss-primary-dark);
|
|
220
|
+
--ui-danger-fg: var(--gemboss-danger-strong);
|
|
217
221
|
--ui-focus: var(--gemboss-primary, #1b1c1e);
|
|
218
222
|
--ui-radius-sm: 6px;
|
|
219
223
|
--ui-radius-md: 8px;
|
|
@@ -273,6 +277,10 @@
|
|
|
273
277
|
--ui-accent-soft: var(--adm-accent-soft);
|
|
274
278
|
--ui-accent-strong: var(--adm-accent-fg);
|
|
275
279
|
--ui-text-strong: var(--adm-text-strong);
|
|
280
|
+
/* Tông trên nền tối #16161a: pha 70% với trắng ⇒ tương phản 6,2 / 9,5 / 4,8 (đo 01/10/2026, review #3609). */
|
|
281
|
+
--ui-success-fg: color-mix(in srgb, var(--gemboss-success) 70%, #ffffff);
|
|
282
|
+
--ui-warning-fg: color-mix(in srgb, #c99a2e 70%, #ffffff);
|
|
283
|
+
--ui-danger-fg: color-mix(in srgb, var(--gemboss-danger-strong) 70%, #ffffff);
|
|
276
284
|
--ui-focus: var(--adm-accent);
|
|
277
285
|
}
|
|
278
286
|
|
|
@@ -303,6 +311,11 @@
|
|
|
303
311
|
padding: var(--ui-space-4); color: var(--ui-text);
|
|
304
312
|
}
|
|
305
313
|
.ui-card__title { font-size: 14px; font-weight: 600; margin: 0 0 var(--ui-space-2); }
|
|
314
|
+
/* Đầu thẻ có nút: `<div class="ui-card__head"><h2 class="ui-card__title">…</h2><button class="ui-btn">…</button></div>`.
|
|
315
|
+
Tiêu đề trái, hành động phải, xuống dòng khi hẹp (01/10/2026: partner-radar rel-head / agr). */
|
|
316
|
+
.ui-card__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--ui-space-2); margin-bottom: var(--ui-space-2); }
|
|
317
|
+
.ui-card__head > .ui-card__title { margin: 0; }
|
|
318
|
+
.ui-card__head > :last-child:not(.ui-card__title) { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); }
|
|
306
319
|
|
|
307
320
|
/* ── Select ──────────────────────────────────────────────────────────────────
|
|
308
321
|
Native <select> reskinned; custom caret as an inline SVG data-URI (caret color
|
|
@@ -347,6 +360,85 @@
|
|
|
347
360
|
}
|
|
348
361
|
.ui-tag--chip.ui-tag--accent { color: var(--adm-accent-fg, var(--ui-accent)); border-color: var(--ui-accent); background: transparent; }
|
|
349
362
|
|
|
363
|
+
/* Danh sách link dạng chip: `<div class="ui-chips"><a class="ui-tag ui-tag--chip" href>…</a>…</div>` (01/10/2026,
|
|
364
|
+
partner-radar /competitors: "đã tích hợp với GemPages"). `.ui-tag--chip` vốn là nhãn TĨNH cao ~19px, dưới mức 24px
|
|
365
|
+
của link trong danh sách (WCAG 2.5.8, §9.1) — visual-gate đo đúng chỗ đó đỏ ở 320 / 390 / 768. Chip là link thì cao
|
|
366
|
+
≥ 28px, chữ thường (không mono), có hover + focus-visible; dưới ngón tay là điều khiển, lên 44px (khối coarse dưới).
|
|
367
|
+
Khe 8px: hai target nhỏ hơn 44px phải cách nhau ≥ 8px. */
|
|
368
|
+
.ui-chips { display: flex; flex-wrap: wrap; gap: var(--ui-space-2, 8px); margin: 0; padding: 0; list-style: none; }
|
|
369
|
+
.ui-chips > a.ui-tag--chip {
|
|
370
|
+
min-height: 28px; padding: 4px 12px; border-radius: var(--ui-radius-md, 8px);
|
|
371
|
+
font-family: inherit; font-size: var(--gemboss-fs-caption, 12px); color: var(--ui-text); text-decoration: none;
|
|
372
|
+
}
|
|
373
|
+
.ui-chips > a.ui-tag--chip:hover { background: var(--ui-surface-2); border-color: var(--ui-text-muted); }
|
|
374
|
+
.ui-chips > a.ui-tag--chip:focus-visible { outline: 2px solid #005bd3; outline-offset: 1px; }
|
|
375
|
+
/* Danh sách LỌC dài (chuyên môn, hạng…) dùng ui-chips thay ui-seg: ui-seg không xuống dòng nên tràn ngang ở 320px.
|
|
376
|
+
Ô đang chọn = `aria-current="page"` (lọc là một URL), cùng tông với ui-seg đang chọn. */
|
|
377
|
+
.ui-chips > a.ui-tag--chip[aria-current="page"] { background: var(--ui-accent-soft); border-color: var(--ui-accent); color: var(--ui-accent-strong); font-weight: 600; }
|
|
378
|
+
|
|
379
|
+
/* ── Tông màu cho tag: `ui-tag--success | --warning | --danger` (token ngữ nghĩa có sẵn). Trạng thái, nguồn,
|
|
380
|
+
kết quả kiểm — thay cho màu viết cứng ở từng surface. */
|
|
381
|
+
.ui-tag--success { background: var(--gemboss-success-soft); color: var(--gemboss-success-strong, var(--gemboss-success)); }
|
|
382
|
+
.ui-tag--warning { background: var(--gemboss-warning-soft); color: var(--gemboss-warning); }
|
|
383
|
+
.ui-tag--danger { background: var(--gemboss-danger-soft); color: var(--gemboss-danger-strong, var(--gemboss-danger)); }
|
|
384
|
+
|
|
385
|
+
/* ── Chữ phụ: `<p class="ui-muted">`, `<span class="ui-muted">` — mô tả, chú thích, dòng meta. Cỡ caption, màu muted.
|
|
386
|
+
Trước đây mỗi surface tự viết `.muted` (partner-radar: 180 chỗ). */
|
|
387
|
+
.ui-muted { color: var(--ui-text-muted); font-size: var(--gemboss-fs-caption); }
|
|
388
|
+
|
|
389
|
+
/* ── Tiêu đề trang: `<div class="ui-page-head"><h1>…</h1><p>…</p></div>`. Đưa lên NGUYÊN VĂN từ admin
|
|
390
|
+
`.gemboss-admin-page-head` (01/10/2026, Chris: một chủ sở hữu; admin dùng bản này). Cố ý không đặt margin cho h1
|
|
391
|
+
và không dùng flex: admin không reset margin tiêu đề, nên đổi ở đây là đổi khoảng cách của 14 trang admin. */
|
|
392
|
+
.ui-page-head { margin-bottom: 20px; }
|
|
393
|
+
.ui-page-head h1 { color: var(--ui-text-strong); font-size: 22px; font-weight: 650; letter-spacing: -0.01em; }
|
|
394
|
+
.ui-page-head p { color: var(--ui-text-muted); font-size: 14px; margin-top: 4px; }
|
|
395
|
+
|
|
396
|
+
/* ── Thanh công cụ: `<div class="ui-toolbar">` — ô tìm giãn, điều khiển bám phải, một hàng gọn, xuống dòng khi hẹp.
|
|
397
|
+
Đưa lên từ admin `.gemboss-admin-toolbar`. Chỉ BỐ CỤC: ô tìm là `gemboss-field gemboss-field--group`. */
|
|
398
|
+
.ui-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-bottom: 20px; }
|
|
399
|
+
|
|
400
|
+
/* ── Ô số liệu: `<div class="ui-metrics"><div class="ui-metric"><span class="ui-metric__val">12</span>
|
|
401
|
+
<span class="ui-metric__lbl">Nhãn</span></div></div>`; tông `ui-metric--success | --warning | --danger`.
|
|
402
|
+
Đưa lên từ admin `.gemboss-ec-metric` (GemChat). Khác `ui-stat` (viên đếm nhỏ, có thể bấm làm bộ lọc). */
|
|
403
|
+
.ui-metrics { display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 0; }
|
|
404
|
+
.ui-metric { display: flex; flex-direction: column; gap: 1px; min-width: 78px; padding: 8px 12px; background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: 12px; }
|
|
405
|
+
/* Cả ô là link (`<a class="ui-metric" href>`): vùng chạm là cả ô (~52px), không phải con số 20px bên trong
|
|
406
|
+
(visual-gate đỏ ở partner-radar /ops 01/10/2026 khi link nằm trong __val). */
|
|
407
|
+
a.ui-metric { text-decoration: none; color: inherit; }
|
|
408
|
+
a.ui-metric:hover { border-color: var(--ui-text-muted); }
|
|
409
|
+
a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
|
|
410
|
+
.ui-metric__val { font-size: 17px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
|
|
411
|
+
.ui-metric__lbl { font-size: var(--gemboss-fs-caption); font-weight: 500; color: var(--ui-text-muted); }
|
|
412
|
+
.ui-metric--success { border-color: color-mix(in srgb, var(--gemboss-success) 40%, transparent); }
|
|
413
|
+
.ui-metric--success .ui-metric__val { color: var(--ui-success-fg); }
|
|
414
|
+
.ui-metric--warning { border-color: color-mix(in srgb, #c99a2e 40%, transparent); }
|
|
415
|
+
.ui-metric--warning .ui-metric__val { color: var(--ui-warning-fg); }
|
|
416
|
+
.ui-metric--danger { border-color: color-mix(in srgb, var(--gemboss-danger-strong) 40%, transparent); }
|
|
417
|
+
.ui-metric--danger .ui-metric__val { color: var(--ui-danger-fg); }
|
|
418
|
+
|
|
419
|
+
/* ── Cặp nhãn – giá trị: `<dl class="ui-dl"><div><dt>Quốc gia</dt><dd>IL</dd></div>…</dl>` — dữ kiện của một hồ sơ.
|
|
420
|
+
Lưới tự giãn: mỗi ô ≥ 160px, xuống dòng khi hẹp. */
|
|
421
|
+
.ui-dl { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px 16px; margin: 0; }
|
|
422
|
+
.ui-dl > div { min-width: 0; }
|
|
423
|
+
.ui-dl dt { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
|
|
424
|
+
.ui-dl dd { margin: 4px 0 0; font-weight: 600; color: var(--ui-text); overflow-wrap: anywhere; }
|
|
425
|
+
|
|
426
|
+
/* ── Thông báo: `<div class="ui-notice ui-notice--warning">…</div>`. Lỗi tải, cảnh báo cấu hình, việc cần người
|
|
427
|
+
làm. Mỗi tông mang màu chữ riêng (nền tông luôn sáng, kể cả trong admin tối). Không có tông = ghi chú trung tính.
|
|
428
|
+
role="status"/"alert" CHỈ khi thông báo xuất hiện sau khi trang đã tải (vùng live); có sẵn lúc tải thì không role. */
|
|
429
|
+
.ui-notice { padding: 12px 16px; border-radius: var(--ui-radius-md); border: 1px solid var(--ui-border); background: var(--ui-surface-2); color: var(--ui-text); font-size: var(--gemboss-fs-body); }
|
|
430
|
+
.ui-notice--warning { background: var(--gemboss-warning-soft); border-color: var(--gemboss-warning-border); color: var(--gemboss-warning); }
|
|
431
|
+
.ui-notice--danger { background: var(--gemboss-danger-soft); border-color: var(--gemboss-danger-border); color: var(--gemboss-danger-strong); }
|
|
432
|
+
.ui-notice--success { background: var(--gemboss-success-soft); border-color: var(--gemboss-success-border); color: var(--gemboss-success-strong); }
|
|
433
|
+
|
|
434
|
+
/* ── Phân trang: `<nav class="ui-pager" aria-label="Trang"><a href rel="prev"><span aria-hidden="true">‹</span> Trước</a><span aria-current="page">2 / 9</span>
|
|
435
|
+
<a href rel="next">Sau ›</a></nav>`. Link ≥ 32px (≥ 24px WCAG 2.5.8), 44px dưới ngón tay (khối coarse). */
|
|
436
|
+
.ui-pager { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 16px 0; }
|
|
437
|
+
.ui-pager > a { display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); color: var(--ui-text); text-decoration: none; font-size: var(--gemboss-fs-body); }
|
|
438
|
+
.ui-pager > a:hover { background: var(--ui-surface-2); }
|
|
439
|
+
.ui-pager > a:focus-visible { outline: 2px solid #005bd3; outline-offset: 1px; }
|
|
440
|
+
.ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--gemboss-fs-caption); }
|
|
441
|
+
|
|
350
442
|
/* ── Badge ────────────────────────────────────────────────────────────────────
|
|
351
443
|
Uppercase mono pill for a status/label (was gemboss-admin-storage-badge). */
|
|
352
444
|
.ui-badge {
|
|
@@ -402,13 +494,16 @@
|
|
|
402
494
|
Grouped toggle: icon squares (.ui-seg button) or text tabs (.is-text). The
|
|
403
495
|
selected segment uses the accent-soft state. */
|
|
404
496
|
.ui-seg { display: inline-flex; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); overflow: hidden; }
|
|
405
|
-
.ui-seg button {
|
|
497
|
+
.ui-seg :is(button, a) {
|
|
406
498
|
display: inline-grid; place-items: center; width: 36px; height: 36px;
|
|
407
499
|
border: 0; background: transparent; color: var(--ui-text-muted); cursor: pointer; font-family: inherit;
|
|
408
500
|
}
|
|
409
|
-
.ui-seg button.is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-body); font-weight: 500; }
|
|
410
|
-
.ui-seg button + button { border-left: 1px solid var(--ui-border); }
|
|
411
|
-
.ui-seg button:hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
501
|
+
.ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-body); font-weight: 500; }
|
|
502
|
+
.ui-seg :is(button, a) + :is(button, a) { border-left: 1px solid var(--ui-border); }
|
|
503
|
+
.ui-seg :is(button, a):hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
504
|
+
/* Lọc / chế độ xem bằng LINK (trang server-render, chọn = đổi URL): `<a aria-current="page">` là ô đang chọn. */
|
|
505
|
+
.ui-seg a { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; color: var(--ui-text-muted); }
|
|
506
|
+
.ui-seg a[aria-current="page"],
|
|
412
507
|
.ui-seg button.active { background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
|
|
413
508
|
.ui-seg svg { width: 16px; height: 16px; fill: currentColor; }
|
|
414
509
|
|
|
@@ -490,7 +585,9 @@
|
|
|
490
585
|
decision, not an oversight; it gets its own measurement pass or none. */
|
|
491
586
|
.ui-btn:not(.ui-btn--sm),
|
|
492
587
|
.ui-stat.is-clickable,
|
|
493
|
-
.ui-
|
|
588
|
+
.ui-chips > a.ui-tag--chip,
|
|
589
|
+
.ui-pager > a,
|
|
590
|
+
.ui-seg :is(button, a) { min-height: 44px; }
|
|
494
591
|
/* A clickable stat whose label is one short token ("7d" on /admin/gem-intelligence) measured 40x44
|
|
495
592
|
in the 13/09/2026 sweep: the height floor held, the width did not. */
|
|
496
593
|
.ui-stat.is-clickable { min-width: 44px; justify-content: center; }
|
|
@@ -499,7 +596,7 @@
|
|
|
499
596
|
.ui-select.ui-select,
|
|
500
597
|
.ui-select--slim.ui-select--slim { min-height: 44px; }
|
|
501
598
|
/* The icon-only segment is 36 WIDE as well — a target is an area. */
|
|
502
|
-
.ui-seg button { min-width: 44px; }
|
|
599
|
+
.ui-seg :is(button, a) { min-width: 44px; }
|
|
503
600
|
}
|
|
504
601
|
|
|
505
602
|
/* ── Skeleton ─────────────────────────────────────────────────────────────────
|
|
@@ -523,6 +620,46 @@
|
|
|
523
620
|
|
|
524
621
|
}
|
|
525
622
|
|
|
623
|
+
/* ── Resizer: the seam you drag between two panels ────────────────────────────
|
|
624
|
+
One look for every split in the fleet: the admin's editors (chat | preview) today; made for gemcare's
|
|
625
|
+
inbox columns too (they adopt it at their next ui:sync). The element is the drag target; its GEOMETRY (a flex item, a grid edge, an absolutely
|
|
626
|
+
placed strip) belongs to the surface. While a drag runs the surface puts `ui-resizing` on <body>
|
|
627
|
+
(and may put `dragging` on the seam itself to keep its grip lit).
|
|
628
|
+
Markup: <div class="ui-resizer" role="separator" aria-orientation="vertical" tabindex="0"
|
|
629
|
+
aria-label="…" aria-valuenow aria-valuemin aria-valuemax>. Pointer events, not mouse events (touch
|
|
630
|
+
is a pointer); arrow keys move it — a focusable separator that ignores the keyboard is INERT. */
|
|
631
|
+
/* `touch-action: none` is what makes a FINGER drag reach this element at all. `preventDefault()` on
|
|
632
|
+
`pointerdown` does not stop panning: the browser hands the gesture to the nearest scroller and sends
|
|
633
|
+
`pointercancel`, so the seam jumps once and stops. */
|
|
634
|
+
.ui-resizer { cursor: col-resize; position: relative; touch-action: none; outline: none; }
|
|
635
|
+
/* The grip pill is invisible at rest (a clean seam) and fades in over the zone, on keyboard focus,
|
|
636
|
+
and while a drag runs. Focus shows the pill rather than a ring: on a 5px seam a ring is a smear. */
|
|
637
|
+
.ui-resizer::before {
|
|
638
|
+
content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
639
|
+
width: 4px; height: 44px; border-radius: var(--gemboss-radius-full);
|
|
640
|
+
background: var(--gemboss-primary);
|
|
641
|
+
opacity: 0;
|
|
642
|
+
transition: opacity 0.14s ease, height 0.14s ease;
|
|
643
|
+
}
|
|
644
|
+
.ui-resizer:hover::before,
|
|
645
|
+
.ui-resizer:focus-visible::before,
|
|
646
|
+
.ui-resizer.dragging::before,
|
|
647
|
+
body.ui-resizing .ui-resizer::before { opacity: 1; height: 52px; }
|
|
648
|
+
/* A FINGER NEEDS MORE THAN 5px, and the line must still look like 5px. WCAG 2.5.8 puts the floor for
|
|
649
|
+
this kind of target at 24px, so the HIT area grows and the visible seam does not — widening the
|
|
650
|
+
element itself would also move the columns. Only where the pointer is coarse. */
|
|
651
|
+
@media (pointer: coarse) {
|
|
652
|
+
.ui-resizer::after {
|
|
653
|
+
content: ""; position: absolute; top: 0; bottom: 0; left: -14px; right: -14px;
|
|
654
|
+
touch-action: none; /* the band must claim the gesture too, or the scroller still wins */
|
|
655
|
+
z-index: 2; /* above the panels either side, transparent */
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
body.ui-resizing, body.ui-resizing * { cursor: col-resize !important; user-select: none !important; }
|
|
659
|
+
/* While dragging, a full-viewport transparent shield sits above everything — iframes included — so the
|
|
660
|
+
pointer stream is never captured by a cross-document frame (the classic "stuck cursor"). */
|
|
661
|
+
body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999; cursor: col-resize; }
|
|
662
|
+
|
|
526
663
|
/* ── Tabs · Table · Empty state ───────────────────────────────────────────────
|
|
527
664
|
Plain-CSS primitives for surfaces that are NOT React (gemcare's vanilla dashboard reads this
|
|
528
665
|
file through `npm run export:ui-css`). The React app has Polaris <Tabs>/<DataTable>/<EmptyState>
|
|
@@ -537,7 +674,8 @@
|
|
|
537
674
|
display: flex; gap: var(--ui-space-1); border-bottom: 1px solid var(--ui-border);
|
|
538
675
|
overflow-x: auto; scrollbar-width: none;
|
|
539
676
|
}
|
|
540
|
-
.ui-tabs > button
|
|
677
|
+
.ui-tabs > button,
|
|
678
|
+
.ui-tabs > a {
|
|
541
679
|
position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--ui-space-2);
|
|
542
680
|
min-height: 44px; padding: 0 var(--ui-space-3); border: 0; background: transparent; cursor: pointer;
|
|
543
681
|
border-radius: var(--ui-radius-sm) var(--ui-radius-sm) 0 0;
|
|
@@ -545,9 +683,15 @@
|
|
|
545
683
|
color: var(--ui-text-muted);
|
|
546
684
|
transition: color var(--gemboss-transition-fast) ease, background-color var(--gemboss-transition-fast) ease;
|
|
547
685
|
}
|
|
548
|
-
.ui-tabs > button:hover
|
|
549
|
-
.ui-tabs >
|
|
686
|
+
.ui-tabs > button:hover,
|
|
687
|
+
.ui-tabs > a:hover { color: var(--ui-text); background: var(--ui-surface-2); }
|
|
688
|
+
.ui-tabs > button:focus-visible,
|
|
689
|
+
.ui-tabs > a:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
|
|
690
|
+
.ui-tabs > a { text-decoration: none; }
|
|
691
|
+
/* Tab bằng link (mỗi tab là một URL): `<nav class="ui-tabs"><a aria-current="page">` — cùng hình với tab nút. */
|
|
692
|
+
.ui-tabs > a[aria-current="page"],
|
|
550
693
|
.ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong); font-weight: 600; }
|
|
694
|
+
.ui-tabs > a[aria-current="page"]::after,
|
|
551
695
|
.ui-tabs > button[aria-selected="true"]::after {
|
|
552
696
|
content: ""; position: absolute; left: var(--ui-space-3); right: var(--ui-space-3); bottom: 0;
|
|
553
697
|
height: 2px; border-radius: var(--gemboss-radius-full); background: var(--ui-accent);
|
|
@@ -573,6 +717,12 @@
|
|
|
573
717
|
.ui-table tbody tr:last-child > td { border-bottom: 0; }
|
|
574
718
|
.ui-table tbody tr:hover { background: var(--ui-surface-2); }
|
|
575
719
|
.ui-table .is-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
720
|
+
/* Dòng phụ dưới nội dung chính của một ô (lớp, nguồn, ngày…): `<td><a>Loox</a><span class="ui-sub">Cộng sinh</span></td>`.
|
|
721
|
+
Trước đây mỗi surface tự viết `.muted` riêng trong ô bảng (partner-radar, gemwatcher). */
|
|
722
|
+
/* Link nằm thẳng trong ô (tên bản ghi dẫn sang trang chi tiết) là link trong DANH SÁCH: ≥ 24px (WCAG 2.5.8, §9.1).
|
|
723
|
+
Chữ 13px cho hộp 17px; visual-gate đo đỏ ở partner-radar /competitors 01/10/2026. inline-flex giữ nó trên dòng. */
|
|
724
|
+
.ui-table td > a { display: inline-flex; align-items: center; min-height: 24px; }
|
|
725
|
+
.ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); font-weight: 400; }
|
|
576
726
|
|
|
577
727
|
/* Empty state: `<div class="ui-empty"><p class="ui-empty__title">…</p><p class="ui-empty__body">…</p>
|
|
578
728
|
<button class="ui-btn">…</button></div>`. Says what is missing and what to do next. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"build:css": "node scripts/build-css.mjs",
|
|
36
36
|
"prepack": "node scripts/build-css.mjs && node scripts/build-catalog.mjs",
|
|
37
37
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
-
"catalog": "node scripts/build-css.mjs && node scripts/build-catalog.mjs"
|
|
38
|
+
"catalog": "node scripts/build-css.mjs && node scripts/build-catalog.mjs",
|
|
39
|
+
"fleet": "node scripts/fleet.mjs"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
42
|
"react": ">=18"
|
package/src/ui.css
CHANGED
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
--ui-accent-soft: var(--gemboss-active-bg, #f0f1f4); /* tinted bg for a selected/active state */
|
|
32
32
|
--ui-accent-strong: var(--gemboss-active-fg, #1b1c1e); /* readable accent text ON --ui-accent-soft */
|
|
33
33
|
--ui-text-strong: var(--gemboss-text-strong, #1b1c1e);
|
|
34
|
+
/* Chữ theo tông (ui-metric, …). Nền tối của admin ánh xạ lại bên dưới để còn đọc được. */
|
|
35
|
+
--ui-success-fg: var(--gemboss-success);
|
|
36
|
+
--ui-warning-fg: var(--gemboss-primary-dark);
|
|
37
|
+
--ui-danger-fg: var(--gemboss-danger-strong);
|
|
34
38
|
--ui-focus: var(--gemboss-primary, #1b1c1e);
|
|
35
39
|
--ui-radius-sm: 6px;
|
|
36
40
|
--ui-radius-md: 8px;
|
|
@@ -90,6 +94,10 @@
|
|
|
90
94
|
--ui-accent-soft: var(--adm-accent-soft);
|
|
91
95
|
--ui-accent-strong: var(--adm-accent-fg);
|
|
92
96
|
--ui-text-strong: var(--adm-text-strong);
|
|
97
|
+
/* Tông trên nền tối #16161a: pha 70% với trắng ⇒ tương phản 6,2 / 9,5 / 4,8 (đo 01/10/2026, review #3609). */
|
|
98
|
+
--ui-success-fg: color-mix(in srgb, var(--gemboss-success) 70%, #ffffff);
|
|
99
|
+
--ui-warning-fg: color-mix(in srgb, #c99a2e 70%, #ffffff);
|
|
100
|
+
--ui-danger-fg: color-mix(in srgb, var(--gemboss-danger-strong) 70%, #ffffff);
|
|
93
101
|
--ui-focus: var(--adm-accent);
|
|
94
102
|
}
|
|
95
103
|
|
|
@@ -120,6 +128,11 @@
|
|
|
120
128
|
padding: var(--ui-space-4); color: var(--ui-text);
|
|
121
129
|
}
|
|
122
130
|
.ui-card__title { font-size: 14px; font-weight: 600; margin: 0 0 var(--ui-space-2); }
|
|
131
|
+
/* Đầu thẻ có nút: `<div class="ui-card__head"><h2 class="ui-card__title">…</h2><button class="ui-btn">…</button></div>`.
|
|
132
|
+
Tiêu đề trái, hành động phải, xuống dòng khi hẹp (01/10/2026: partner-radar rel-head / agr). */
|
|
133
|
+
.ui-card__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--ui-space-2); margin-bottom: var(--ui-space-2); }
|
|
134
|
+
.ui-card__head > .ui-card__title { margin: 0; }
|
|
135
|
+
.ui-card__head > :last-child:not(.ui-card__title) { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); }
|
|
123
136
|
|
|
124
137
|
/* ── Select ──────────────────────────────────────────────────────────────────
|
|
125
138
|
Native <select> reskinned; custom caret as an inline SVG data-URI (caret color
|
|
@@ -164,6 +177,85 @@
|
|
|
164
177
|
}
|
|
165
178
|
.ui-tag--chip.ui-tag--accent { color: var(--adm-accent-fg, var(--ui-accent)); border-color: var(--ui-accent); background: transparent; }
|
|
166
179
|
|
|
180
|
+
/* Danh sách link dạng chip: `<div class="ui-chips"><a class="ui-tag ui-tag--chip" href>…</a>…</div>` (01/10/2026,
|
|
181
|
+
partner-radar /competitors: "đã tích hợp với GemPages"). `.ui-tag--chip` vốn là nhãn TĨNH cao ~19px, dưới mức 24px
|
|
182
|
+
của link trong danh sách (WCAG 2.5.8, §9.1) — visual-gate đo đúng chỗ đó đỏ ở 320 / 390 / 768. Chip là link thì cao
|
|
183
|
+
≥ 28px, chữ thường (không mono), có hover + focus-visible; dưới ngón tay là điều khiển, lên 44px (khối coarse dưới).
|
|
184
|
+
Khe 8px: hai target nhỏ hơn 44px phải cách nhau ≥ 8px. */
|
|
185
|
+
.ui-chips { display: flex; flex-wrap: wrap; gap: var(--ui-space-2, 8px); margin: 0; padding: 0; list-style: none; }
|
|
186
|
+
.ui-chips > a.ui-tag--chip {
|
|
187
|
+
min-height: 28px; padding: 4px 12px; border-radius: var(--ui-radius-md, 8px);
|
|
188
|
+
font-family: inherit; font-size: var(--gemboss-fs-caption, 12px); color: var(--ui-text); text-decoration: none;
|
|
189
|
+
}
|
|
190
|
+
.ui-chips > a.ui-tag--chip:hover { background: var(--ui-surface-2); border-color: var(--ui-text-muted); }
|
|
191
|
+
.ui-chips > a.ui-tag--chip:focus-visible { outline: 2px solid var(--p-color-border-focus, #005bd3); outline-offset: 1px; }
|
|
192
|
+
/* Danh sách LỌC dài (chuyên môn, hạng…) dùng ui-chips thay ui-seg: ui-seg không xuống dòng nên tràn ngang ở 320px.
|
|
193
|
+
Ô đang chọn = `aria-current="page"` (lọc là một URL), cùng tông với ui-seg đang chọn. */
|
|
194
|
+
.ui-chips > a.ui-tag--chip[aria-current="page"] { background: var(--ui-accent-soft); border-color: var(--ui-accent); color: var(--ui-accent-strong); font-weight: 600; }
|
|
195
|
+
|
|
196
|
+
/* ── Tông màu cho tag: `ui-tag--success | --warning | --danger` (token ngữ nghĩa có sẵn). Trạng thái, nguồn,
|
|
197
|
+
kết quả kiểm — thay cho màu viết cứng ở từng surface. */
|
|
198
|
+
.ui-tag--success { background: var(--gemboss-success-soft); color: var(--gemboss-success-strong, var(--gemboss-success)); }
|
|
199
|
+
.ui-tag--warning { background: var(--gemboss-warning-soft); color: var(--gemboss-warning); }
|
|
200
|
+
.ui-tag--danger { background: var(--gemboss-danger-soft); color: var(--gemboss-danger-strong, var(--gemboss-danger)); }
|
|
201
|
+
|
|
202
|
+
/* ── Chữ phụ: `<p class="ui-muted">`, `<span class="ui-muted">` — mô tả, chú thích, dòng meta. Cỡ caption, màu muted.
|
|
203
|
+
Trước đây mỗi surface tự viết `.muted` (partner-radar: 180 chỗ). */
|
|
204
|
+
.ui-muted { color: var(--ui-text-muted); font-size: var(--gemboss-fs-caption); }
|
|
205
|
+
|
|
206
|
+
/* ── Tiêu đề trang: `<div class="ui-page-head"><h1>…</h1><p>…</p></div>`. Đưa lên NGUYÊN VĂN từ admin
|
|
207
|
+
`.gemboss-admin-page-head` (01/10/2026, Chris: một chủ sở hữu; admin dùng bản này). Cố ý không đặt margin cho h1
|
|
208
|
+
và không dùng flex: admin không reset margin tiêu đề, nên đổi ở đây là đổi khoảng cách của 14 trang admin. */
|
|
209
|
+
.ui-page-head { margin-bottom: 20px; }
|
|
210
|
+
.ui-page-head h1 { color: var(--ui-text-strong); font-size: 22px; font-weight: 650; letter-spacing: -0.01em; }
|
|
211
|
+
.ui-page-head p { color: var(--ui-text-muted); font-size: 14px; margin-top: 4px; }
|
|
212
|
+
|
|
213
|
+
/* ── Thanh công cụ: `<div class="ui-toolbar">` — ô tìm giãn, điều khiển bám phải, một hàng gọn, xuống dòng khi hẹp.
|
|
214
|
+
Đưa lên từ admin `.gemboss-admin-toolbar`. Chỉ BỐ CỤC: ô tìm là `gemboss-field gemboss-field--group`. */
|
|
215
|
+
.ui-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-bottom: 20px; }
|
|
216
|
+
|
|
217
|
+
/* ── Ô số liệu: `<div class="ui-metrics"><div class="ui-metric"><span class="ui-metric__val">12</span>
|
|
218
|
+
<span class="ui-metric__lbl">Nhãn</span></div></div>`; tông `ui-metric--success | --warning | --danger`.
|
|
219
|
+
Đưa lên từ admin `.gemboss-ec-metric` (GemChat). Khác `ui-stat` (viên đếm nhỏ, có thể bấm làm bộ lọc). */
|
|
220
|
+
.ui-metrics { display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 0; }
|
|
221
|
+
.ui-metric { display: flex; flex-direction: column; gap: 1px; min-width: 78px; padding: 8px 12px; background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: 12px; }
|
|
222
|
+
/* Cả ô là link (`<a class="ui-metric" href>`): vùng chạm là cả ô (~52px), không phải con số 20px bên trong
|
|
223
|
+
(visual-gate đỏ ở partner-radar /ops 01/10/2026 khi link nằm trong __val). */
|
|
224
|
+
a.ui-metric { text-decoration: none; color: inherit; }
|
|
225
|
+
a.ui-metric:hover { border-color: var(--ui-text-muted); }
|
|
226
|
+
a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
|
|
227
|
+
.ui-metric__val { font-size: 17px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
|
|
228
|
+
.ui-metric__lbl { font-size: var(--gemboss-fs-caption); font-weight: 500; color: var(--ui-text-muted); }
|
|
229
|
+
.ui-metric--success { border-color: color-mix(in srgb, var(--gemboss-success) 40%, transparent); }
|
|
230
|
+
.ui-metric--success .ui-metric__val { color: var(--ui-success-fg); }
|
|
231
|
+
.ui-metric--warning { border-color: color-mix(in srgb, #c99a2e 40%, transparent); }
|
|
232
|
+
.ui-metric--warning .ui-metric__val { color: var(--ui-warning-fg); }
|
|
233
|
+
.ui-metric--danger { border-color: color-mix(in srgb, var(--gemboss-danger-strong) 40%, transparent); }
|
|
234
|
+
.ui-metric--danger .ui-metric__val { color: var(--ui-danger-fg); }
|
|
235
|
+
|
|
236
|
+
/* ── Cặp nhãn – giá trị: `<dl class="ui-dl"><div><dt>Quốc gia</dt><dd>IL</dd></div>…</dl>` — dữ kiện của một hồ sơ.
|
|
237
|
+
Lưới tự giãn: mỗi ô ≥ 160px, xuống dòng khi hẹp. */
|
|
238
|
+
.ui-dl { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px 16px; margin: 0; }
|
|
239
|
+
.ui-dl > div { min-width: 0; }
|
|
240
|
+
.ui-dl dt { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
|
|
241
|
+
.ui-dl dd { margin: 4px 0 0; font-weight: 600; color: var(--ui-text); overflow-wrap: anywhere; }
|
|
242
|
+
|
|
243
|
+
/* ── Thông báo: `<div class="ui-notice ui-notice--warning">…</div>`. Lỗi tải, cảnh báo cấu hình, việc cần người
|
|
244
|
+
làm. Mỗi tông mang màu chữ riêng (nền tông luôn sáng, kể cả trong admin tối). Không có tông = ghi chú trung tính.
|
|
245
|
+
role="status"/"alert" CHỈ khi thông báo xuất hiện sau khi trang đã tải (vùng live); có sẵn lúc tải thì không role. */
|
|
246
|
+
.ui-notice { padding: 12px 16px; border-radius: var(--ui-radius-md); border: 1px solid var(--ui-border); background: var(--ui-surface-2); color: var(--ui-text); font-size: var(--gemboss-fs-body); }
|
|
247
|
+
.ui-notice--warning { background: var(--gemboss-warning-soft); border-color: var(--gemboss-warning-border); color: var(--gemboss-warning); }
|
|
248
|
+
.ui-notice--danger { background: var(--gemboss-danger-soft); border-color: var(--gemboss-danger-border); color: var(--gemboss-danger-strong); }
|
|
249
|
+
.ui-notice--success { background: var(--gemboss-success-soft); border-color: var(--gemboss-success-border); color: var(--gemboss-success-strong); }
|
|
250
|
+
|
|
251
|
+
/* ── Phân trang: `<nav class="ui-pager" aria-label="Trang"><a href rel="prev"><span aria-hidden="true">‹</span> Trước</a><span aria-current="page">2 / 9</span>
|
|
252
|
+
<a href rel="next">Sau ›</a></nav>`. Link ≥ 32px (≥ 24px WCAG 2.5.8), 44px dưới ngón tay (khối coarse). */
|
|
253
|
+
.ui-pager { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 16px 0; }
|
|
254
|
+
.ui-pager > a { display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); color: var(--ui-text); text-decoration: none; font-size: var(--gemboss-fs-body); }
|
|
255
|
+
.ui-pager > a:hover { background: var(--ui-surface-2); }
|
|
256
|
+
.ui-pager > a:focus-visible { outline: 2px solid var(--p-color-border-focus, #005bd3); outline-offset: 1px; }
|
|
257
|
+
.ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--gemboss-fs-caption); }
|
|
258
|
+
|
|
167
259
|
/* ── Badge ────────────────────────────────────────────────────────────────────
|
|
168
260
|
Uppercase mono pill for a status/label (was gemboss-admin-storage-badge). */
|
|
169
261
|
.ui-badge {
|
|
@@ -219,13 +311,16 @@
|
|
|
219
311
|
Grouped toggle: icon squares (.ui-seg button) or text tabs (.is-text). The
|
|
220
312
|
selected segment uses the accent-soft state. */
|
|
221
313
|
.ui-seg { display: inline-flex; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); overflow: hidden; }
|
|
222
|
-
.ui-seg button {
|
|
314
|
+
.ui-seg :is(button, a) {
|
|
223
315
|
display: inline-grid; place-items: center; width: 36px; height: 36px;
|
|
224
316
|
border: 0; background: transparent; color: var(--ui-text-muted); cursor: pointer; font-family: inherit;
|
|
225
317
|
}
|
|
226
|
-
.ui-seg button.is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-body); font-weight: 500; }
|
|
227
|
-
.ui-seg button + button { border-left: 1px solid var(--ui-border); }
|
|
228
|
-
.ui-seg button:hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
318
|
+
.ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-body); font-weight: 500; }
|
|
319
|
+
.ui-seg :is(button, a) + :is(button, a) { border-left: 1px solid var(--ui-border); }
|
|
320
|
+
.ui-seg :is(button, a):hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
321
|
+
/* Lọc / chế độ xem bằng LINK (trang server-render, chọn = đổi URL): `<a aria-current="page">` là ô đang chọn. */
|
|
322
|
+
.ui-seg a { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; color: var(--ui-text-muted); }
|
|
323
|
+
.ui-seg a[aria-current="page"],
|
|
229
324
|
.ui-seg button.active { background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
|
|
230
325
|
.ui-seg svg { width: 16px; height: 16px; fill: currentColor; }
|
|
231
326
|
|
|
@@ -307,7 +402,9 @@
|
|
|
307
402
|
decision, not an oversight; it gets its own measurement pass or none. */
|
|
308
403
|
.ui-btn:not(.ui-btn--sm),
|
|
309
404
|
.ui-stat.is-clickable,
|
|
310
|
-
.ui-
|
|
405
|
+
.ui-chips > a.ui-tag--chip,
|
|
406
|
+
.ui-pager > a,
|
|
407
|
+
.ui-seg :is(button, a) { min-height: 44px; }
|
|
311
408
|
/* A clickable stat whose label is one short token ("7d" on /admin/gem-intelligence) measured 40x44
|
|
312
409
|
in the 13/09/2026 sweep: the height floor held, the width did not. */
|
|
313
410
|
.ui-stat.is-clickable { min-width: 44px; justify-content: center; }
|
|
@@ -316,7 +413,7 @@
|
|
|
316
413
|
.ui-select.ui-select,
|
|
317
414
|
.ui-select--slim.ui-select--slim { min-height: 44px; }
|
|
318
415
|
/* The icon-only segment is 36 WIDE as well — a target is an area. */
|
|
319
|
-
.ui-seg button { min-width: 44px; }
|
|
416
|
+
.ui-seg :is(button, a) { min-width: 44px; }
|
|
320
417
|
}
|
|
321
418
|
|
|
322
419
|
/* ── Skeleton ─────────────────────────────────────────────────────────────────
|
|
@@ -340,6 +437,46 @@
|
|
|
340
437
|
|
|
341
438
|
}
|
|
342
439
|
|
|
440
|
+
/* ── Resizer: the seam you drag between two panels ────────────────────────────
|
|
441
|
+
One look for every split in the fleet: the admin's editors (chat | preview) today; made for gemcare's
|
|
442
|
+
inbox columns too (they adopt it at their next ui:sync). The element is the drag target; its GEOMETRY (a flex item, a grid edge, an absolutely
|
|
443
|
+
placed strip) belongs to the surface. While a drag runs the surface puts `ui-resizing` on <body>
|
|
444
|
+
(and may put `dragging` on the seam itself to keep its grip lit).
|
|
445
|
+
Markup: <div class="ui-resizer" role="separator" aria-orientation="vertical" tabindex="0"
|
|
446
|
+
aria-label="…" aria-valuenow aria-valuemin aria-valuemax>. Pointer events, not mouse events (touch
|
|
447
|
+
is a pointer); arrow keys move it — a focusable separator that ignores the keyboard is INERT. */
|
|
448
|
+
/* `touch-action: none` is what makes a FINGER drag reach this element at all. `preventDefault()` on
|
|
449
|
+
`pointerdown` does not stop panning: the browser hands the gesture to the nearest scroller and sends
|
|
450
|
+
`pointercancel`, so the seam jumps once and stops. */
|
|
451
|
+
.ui-resizer { cursor: col-resize; position: relative; touch-action: none; outline: none; }
|
|
452
|
+
/* The grip pill is invisible at rest (a clean seam) and fades in over the zone, on keyboard focus,
|
|
453
|
+
and while a drag runs. Focus shows the pill rather than a ring: on a 5px seam a ring is a smear. */
|
|
454
|
+
.ui-resizer::before {
|
|
455
|
+
content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
456
|
+
width: 4px; height: 44px; border-radius: var(--gemboss-radius-full);
|
|
457
|
+
background: var(--gemboss-primary);
|
|
458
|
+
opacity: 0;
|
|
459
|
+
transition: opacity 0.14s ease, height 0.14s ease;
|
|
460
|
+
}
|
|
461
|
+
.ui-resizer:hover::before,
|
|
462
|
+
.ui-resizer:focus-visible::before,
|
|
463
|
+
.ui-resizer.dragging::before,
|
|
464
|
+
body.ui-resizing .ui-resizer::before { opacity: 1; height: 52px; }
|
|
465
|
+
/* A FINGER NEEDS MORE THAN 5px, and the line must still look like 5px. WCAG 2.5.8 puts the floor for
|
|
466
|
+
this kind of target at 24px, so the HIT area grows and the visible seam does not — widening the
|
|
467
|
+
element itself would also move the columns. Only where the pointer is coarse. */
|
|
468
|
+
@media (pointer: coarse) {
|
|
469
|
+
.ui-resizer::after {
|
|
470
|
+
content: ""; position: absolute; top: 0; bottom: 0; left: -14px; right: -14px;
|
|
471
|
+
touch-action: none; /* the band must claim the gesture too, or the scroller still wins */
|
|
472
|
+
z-index: 2; /* above the panels either side, transparent */
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
body.ui-resizing, body.ui-resizing * { cursor: col-resize !important; user-select: none !important; }
|
|
476
|
+
/* While dragging, a full-viewport transparent shield sits above everything — iframes included — so the
|
|
477
|
+
pointer stream is never captured by a cross-document frame (the classic "stuck cursor"). */
|
|
478
|
+
body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999; cursor: col-resize; }
|
|
479
|
+
|
|
343
480
|
/* ── Tabs · Table · Empty state ───────────────────────────────────────────────
|
|
344
481
|
Plain-CSS primitives for surfaces that are NOT React (gemcare's vanilla dashboard reads this
|
|
345
482
|
file through `npm run export:ui-css`). The React app has Polaris <Tabs>/<DataTable>/<EmptyState>
|
|
@@ -354,7 +491,8 @@
|
|
|
354
491
|
display: flex; gap: var(--ui-space-1); border-bottom: 1px solid var(--ui-border);
|
|
355
492
|
overflow-x: auto; scrollbar-width: none;
|
|
356
493
|
}
|
|
357
|
-
.ui-tabs > button
|
|
494
|
+
.ui-tabs > button,
|
|
495
|
+
.ui-tabs > a {
|
|
358
496
|
position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--ui-space-2);
|
|
359
497
|
min-height: 44px; padding: 0 var(--ui-space-3); border: 0; background: transparent; cursor: pointer;
|
|
360
498
|
border-radius: var(--ui-radius-sm) var(--ui-radius-sm) 0 0;
|
|
@@ -362,9 +500,15 @@
|
|
|
362
500
|
color: var(--ui-text-muted);
|
|
363
501
|
transition: color var(--gemboss-transition-fast) ease, background-color var(--gemboss-transition-fast) ease;
|
|
364
502
|
}
|
|
365
|
-
.ui-tabs > button:hover
|
|
366
|
-
.ui-tabs >
|
|
503
|
+
.ui-tabs > button:hover,
|
|
504
|
+
.ui-tabs > a:hover { color: var(--ui-text); background: var(--ui-surface-2); }
|
|
505
|
+
.ui-tabs > button:focus-visible,
|
|
506
|
+
.ui-tabs > a:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
|
|
507
|
+
.ui-tabs > a { text-decoration: none; }
|
|
508
|
+
/* Tab bằng link (mỗi tab là một URL): `<nav class="ui-tabs"><a aria-current="page">` — cùng hình với tab nút. */
|
|
509
|
+
.ui-tabs > a[aria-current="page"],
|
|
367
510
|
.ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong); font-weight: 600; }
|
|
511
|
+
.ui-tabs > a[aria-current="page"]::after,
|
|
368
512
|
.ui-tabs > button[aria-selected="true"]::after {
|
|
369
513
|
content: ""; position: absolute; left: var(--ui-space-3); right: var(--ui-space-3); bottom: 0;
|
|
370
514
|
height: 2px; border-radius: var(--gemboss-radius-full); background: var(--ui-accent);
|
|
@@ -390,6 +534,12 @@
|
|
|
390
534
|
.ui-table tbody tr:last-child > td { border-bottom: 0; }
|
|
391
535
|
.ui-table tbody tr:hover { background: var(--ui-surface-2); }
|
|
392
536
|
.ui-table .is-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
537
|
+
/* Dòng phụ dưới nội dung chính của một ô (lớp, nguồn, ngày…): `<td><a>Loox</a><span class="ui-sub">Cộng sinh</span></td>`.
|
|
538
|
+
Trước đây mỗi surface tự viết `.muted` riêng trong ô bảng (partner-radar, gemwatcher). */
|
|
539
|
+
/* Link nằm thẳng trong ô (tên bản ghi dẫn sang trang chi tiết) là link trong DANH SÁCH: ≥ 24px (WCAG 2.5.8, §9.1).
|
|
540
|
+
Chữ 13px cho hộp 17px; visual-gate đo đỏ ở partner-radar /competitors 01/10/2026. inline-flex giữ nó trên dòng. */
|
|
541
|
+
.ui-table td > a { display: inline-flex; align-items: center; min-height: 24px; }
|
|
542
|
+
.ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); font-weight: 400; }
|
|
393
543
|
|
|
394
544
|
/* Empty state: `<div class="ui-empty"><p class="ui-empty__title">…</p><p class="ui-empty__body">…</p>
|
|
395
545
|
<button class="ui-btn">…</button></div>`. Says what is missing and what to do next. */
|