@gemboss/ui 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,10 +12,10 @@ npm install @gemboss/ui
12
12
 
13
13
  ```tsx
14
14
  import "@gemboss/ui/gemboss-ui.css";
15
- import { AppShell, RailItem, RailGroup, RailHeading, GemMark } from "@gemboss/ui";
15
+ import { AppShell, RailItem, RailGroup, RailHeading, BrandLockup } from "@gemboss/ui";
16
16
 
17
17
  <AppShell
18
- brand={<a href="#/"><GemMark size={28} /> GemWatcher</a>}
18
+ brand={<BrandLockup name="GemWatcher" href="#/" />}
19
19
  center={<SearchBox />}
20
20
  rail={<>
21
21
  <RailHeading>Market</RailHeading>
@@ -29,6 +29,9 @@ import { AppShell, RailItem, RailGroup, RailHeading, GemMark } from "@gemboss/ui
29
29
  </AppShell>
30
30
  ```
31
31
 
32
+ - The topbar brand is `BrandLockup` (the admin's: gem 20.8×26, name 13.3px/700, 40px box), never a bare
33
+ `GemMark`. `GemMark` is the app TILE (sign-in screen, favicon); at 28px in a topbar it reads as a second app
34
+ icon (this example taught that until 0.5.0, and gemwatcher shipped it).
32
35
  - Wrap each item in `RailGroup`: the selected pill only exists inside `.gnav-group`.
33
36
  - `RailItem` renders `<a>`; pass `as={Link}` (Next) or `as="button"`. Every other prop is forwarded.
34
37
  - No router, no data. The package only emits the markup the CSS was written for.
@@ -167,11 +170,82 @@ const m = openModal({ title: "Delete #12?", closeLabel: "Đóng", body: '<div cl
167
170
  onClose: (v) => v && toast("Đã xoá") });
168
171
  ```
169
172
 
170
- `title`, `body` and `foot` are HTML (escape your text); toast text is text. `beforeClose` may return a promise of
173
+ 0.5.0 adds three more, each one a hand copy on some surface first:
174
+
175
+ ```js
176
+ // a delete: red confirm button, focus starts on Cancel; resolves true / false. Strings are TEXT.
177
+ if (await gembossUI.confirm({ title: "Delete #12?", body: "It leaves the board.", confirmLabel: "Delete",
178
+ cancelLabel: "Huỷ", tone: "critical" })) remove(12);
179
+ gembossUI.initShell(); // AppShell markup written by hand: ☰ drawer, backdrop, Escape, followed link
180
+ gembossUI.initPasswordFields(); // the sign-in's reveal button; resetPasswordFields() on sign-out
181
+ ```
182
+
183
+ In a bundled app (Next, Vite) load it once with a bare `import "@gemboss/ui/kit-plain.js";` in a client module: the
184
+ package lists the file in `sideEffects`, so the bundler keeps it (before 0.5.0 it dropped it and `gembossUI` was
185
+ undefined in production builds). Use `confirm` instead of `window.confirm()`: the browser's box has no GemBoss look, no critical tone, and its
186
+ buttons cannot be named. For `initShell`, put `data-label-open` / `data-label-close` on the ☰ button to have its
187
+ name follow the state; for `initPasswordFields`, `data-label-show` / `data-label-hide` on the reveal button.
188
+
189
+ `title`, `body` and `foot` of `openModal` are HTML (escape your text); toast text is text. `beforeClose` may return a promise of
171
190
  false to keep the popup open (it gets the close value, and a guard that throws keeps it open). Its behaviour is
172
191
  asserted in Chromium by `packages/ui/src/kit-plain.test.ts` (`npm run test:ui-browser`, run by the CI job that has
173
192
  Chromium; `test:packages` excludes it); the catalog's "Hộp thoại" section runs it live.
174
193
 
194
+ ## Polaris parity (0.5.0)
195
+
196
+ The admin is Polaris; this package is how the rest of the fleet looks like it. Measured on 02/10/2026 across the six
197
+ surfaces on the package, the same gaps were filled six ways. What 0.5.0 settles, so a surface stops patching it:
198
+
199
+ | | Under a mouse | Under a thumb / ≤ 767px wide |
200
+ |---|---|---|
201
+ | Control height: `gemboss-field`, `ui-select`, `ui-btn`, `ui-seg` | **32px** (Polaris TextField / Select), `--ui-control-h` | **44px**, `ui-btn--sm` included |
202
+ | `ui-btn--sm` | 28px | 44px |
203
+ | Focus ring, every control | 2px iris `--ui-focus` (= `--gemboss-accent`, the admin's `--p-color-border-focus`) | same |
204
+ | Body text (`html`, `body`, at zero specificity) | 13px / 20px, weight 450, `--gemboss-text`, antialiased (Polaris bodyMd) | same |
205
+ | Kit text (tabs, table, notice, pager, empty, page-head lead) | `--ui-fs-body`, 13px | 16px in fields (iOS zoom) |
206
+
207
+ New primitives, Polaris' shapes on GemBoss tokens (`kit-plain.css`; the admin uses Polaris itself):
208
+
209
+ | Need | Class | Polaris |
210
+ |---|---|---|
211
+ | Delete / revoke | `ui-btn ui-btn--critical`, `ui-btn--primary ui-btn--critical` | Button `tone="critical"` |
212
+ | Status | `ui-badge` (12px/550, radius 8) `--success|--warning|--critical|--info`, `--dot` | Badge `tone` |
213
+ | Something to read or act on | `ui-banner` `__head` `__icon` `__title` `__close` `__body` `__actions`, same tones | Banner |
214
+ | Help / error under a field | `ui-help`, `ui-error`; `aria-invalid="true"` paints the field | Labelled help text, InlineError |
215
+ | Radio group | `fieldset.ui-choices` + `label.ui-check > input[type=radio]` | ChoiceList |
216
+ | On / off that applies at once | `label.ui-check.ui-switch > input[role=switch]` | (admin toggle) |
217
+ | Label for an icon button | `.ui-tooltip > button + .ui-tooltip__bubble[role=tooltip]` | Tooltip |
218
+ | Waiting | `ui-spinner` (20px), `--lg` (44px) | Spinner |
219
+ | Progress | `progress.ui-progress`, `--success|--critical` | ProgressBar small |
220
+ | Two-column form, table in a popup | `openModal({ className: "ui-modal--wide" })`, 620px | Modal default width |
221
+ | A popup in React (no kit-plain.js) | `<dialog class="ui-modal">` + `showModal()`: same head / body / foot, the kit's backdrop; the browser gives Escape and the inert page | Modal |
222
+ | Favicon | `@gemboss/ui/icon.svg` (the admin's `app/icon.svg`, byte for byte) | |
223
+
224
+ 0.5.1 adds what the first surfaces on 0.5.0 still drew themselves (GemFactory, content-radar):
225
+
226
+ | Need | Class / API |
227
+ |---|---|
228
+ | Title with buttons on the right | `ui-page-head ui-page-head--actions` > `div` (h1 + p) + `.ui-page-head__actions` |
229
+ | Chips that toggle in place (no URL) | `.ui-chips > button.ui-tag--chip[aria-pressed]` |
230
+ | A long badge label | `ui-badge ui-badge--wrap` |
231
+ | Close the account menu on a static page | `gembossUI.initMenus()` now closes `details.gb-account` as well |
232
+ | ☰ and eye names on a static render | `MobileMenuButton` / `PasswordField` emit `data-label-open/close`, `data-label-show/hide` |
233
+
234
+ And four targets that measured under 44px at phone width are now 44: a compact text button's width, the bare input
235
+ 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 from the next task). The rail item and segmented buttons draw the iris focus ring (they had
237
+ none: the browser drew its own), the popup × too (it drew near-black); at phone width the chat composer is 16px (iOS
238
+ zoomed it at 14) and the thread rail's "new chat" / collapse buttons are 44px under a fine pointer as well. Headings carry
239
+ their own line-height (a wrapped 22px title overlapped itself on the 20px body line), a badge is a tint of the ink (it
240
+ vanished on a hovered table row), `ui-seg` reads `aria-pressed`, a `[readonly]` field looks read-only, and a popup may
241
+ wrap body + foot in one `<form>` (`.ui-modal > form` takes no box). A metric that filters the view is a toggle: `button.ui-metric[aria-pressed]` (or
242
+ `a.ui-metric[aria-current="page"]` for a link), drawn with the iris edge when chosen.
243
+
244
+ If your surface still sets any of these itself (a `body { font-size }`, a `min-height: 44px` on `.ui-btn--sm`, a
245
+ `.danger` button, a `confirm()`), delete it on the upgrade: the package draws it now, and a surface rule wins the
246
+ cascade over the package's zero-specificity base. `packages/ui/src/kit-look.test.ts` measures the table above in
247
+ Chromium on the built CSS.
248
+
175
249
  ## Resizable panels
176
250
 
177
251
  `ui-resizer` is the seam a person drags between two panels; `ui-resizing` on `<body>` is the drag in
@@ -202,24 +276,33 @@ The surface owns where the seam sits and what it does. Five rules, each paid for
202
276
  React surfaces: `useResizableChatPanel` in the admin is the reference. Others write the same markup and
203
277
  the five rules by hand; the catalog's "Thanh kéo giữa hai cột" shows the markup.
204
278
 
205
- ## The assistant launcher (0.4.2)
279
+ ## The assistant launcher (0.4.2; not floating since 0.6.0)
206
280
 
207
- The top-right corner of the topbar is the ACCOUNT (`AccountMenu`). The button that opens a surface's AI assistant floats
208
- bottom-right instead (Chris, 02/10/2026): partner-radar had it top-right, and on a phone it became a ✦ circle exactly where
209
- content-radar shows the account chip.
281
+ The top-right corner of the topbar is the ACCOUNT (`AccountMenu`). The assistant button is NOT a floating button: 0.4.2
282
+ floated it bottom-right, and a fixed button sits on whatever text scrolls under it (visual-gate `content_covered` red at
283
+ 5 of 6 widths on every partner-radar page measured, 03/10/2026; GemCommunity had already dropped its floating buttons for
284
+ the same reason, #239). Chris chose to drop it. Render TWO copies; CSS displays exactly one at any width:
210
285
 
211
286
  ```tsx
212
- import { AssistantLauncher } from "@gemboss/ui";
287
+ import { AppShell, AssistantLauncher } from "@gemboss/ui";
213
288
 
214
- <AssistantLauncher label="Partner Assistant" shortcut="⌘J"
215
- hidden={dock.presentation !== "hidden"} onClick={() => dock.reopen()} />
289
+ const open = dock.presentation !== "hidden";
290
+ <AppShell
291
+ actions={<>
292
+ <AssistantLauncher placement="topbar" label="Partner Assistant" shortcut="⌘J" hidden={open} onClick={() => dock.reopen()} />
293
+ <AccountMenu … />
294
+ </>}
295
+ railBottom={<AssistantLauncher label="Partner Assistant" shortcut="⌘J" hidden={open} onClick={() => dock.reopen()} />}
296
+ />
216
297
  ```
217
298
 
218
- - No provider and no state: pass `hidden` while your assistant is open (it would sit under the dock).
219
- - 44px floor everywhere; label + shortcut on wide screens, the `sparkle` icon alone at ≤760px (the label stays the
220
- accessible name). Safe-area insets keep it off the iPhone home indicator. z-index 280: under the dock and the topbar,
221
- over page content.
299
+ - `placement="rail"` (default): the last row of the rail, full width, primary (near-black), label + shortcut. Hidden at
300
+ ≤760px, where the rail is a drawer nobody sees until they open it.
301
+ - `placement="topbar"`: a 44×44 icon button, left of the account chip, ≤760px only. The label stays the accessible name.
302
+ - No provider and no state: pass `hidden` while your assistant is open.
222
303
  - The shortcut is a hint only: the surface owns the key binding.
304
+ - 0.6.0, not a patch: an existing `<AssistantLauncher>` call changes meaning (no longer fixed; the default copy is the
305
+ rail one, hidden on phones). A surface on `^0.5.x` must move to `^0.6.0` in the same PR that places the two copies.
223
306
 
224
307
  ## The topbar kit: lockup, account, icons
225
308
 
@@ -307,7 +390,7 @@ the slots and props (`brand`, `center`, `actions`, rail items, icons, headings).
307
390
 
308
391
  The base font is one of those tokens. `gemboss-ui.css` sets `html`, `body` and `button` in
309
392
  `--gemboss-font-sans`, the admin's own body stack (Polaris `--p-font-family-sans`: Inter, then the
310
- system UI font). The rule sits at zero specificity (`:where()`), so a surface re-themes with
393
+ system UI font), and since 0.5.0 `html` / `body` at Polaris bodyMd (13px / 20px, 450, `--gemboss-text`). The rule sits at zero specificity (`:where()`), so a surface re-themes with
311
394
  `:root { --gemboss-font-sans: … }` and needs no `body { font-family }` patch. A surface that loads
312
395
  its own web font does it itself; the package loads none.
313
396
 
@@ -319,10 +402,12 @@ something the base cannot express, add a token or a prop here, and every surface
319
402
 
320
403
  | Path | What |
321
404
  |---|---|
322
- | `gemboss-ui.css` | built at pack time: `apps/admin/scripts/export-ui-css.mjs` (tokens + kit + field + `packages/ui/src/shell.css`, with Polaris `--p-*` resolved to their fallbacks) + `packages/ui/src/app-shell.css` + `packages/ui/src/auth.css` + `packages/ui/src/assistant.css` |
405
+ | `gemboss-ui.css` | built at pack time: `apps/admin/scripts/export-ui-css.mjs` (tokens + kit + field + `packages/ui/src/shell.css`, with Polaris `--p-*` resolved to their fallbacks) + `packages/ui/src/app-shell.css` + `auth.css` + `assistant.css` + `kit-plain.css` + `chat.css` + `chat-composer.css` (`packages/ui/scripts/build-css.mjs`) |
323
406
  | `packages/ui/src/shell.css` | the SHARED SHELL (rail, topbar, main, mobile drawer, the assistant's thread rail, popover surface, account chip). The admin's `gemboss-shell.css` @imports it on line 2; the export refuses a shared-shell rule in the admin's sheet or an admin-only rule here (one owner). |
324
407
  | `packages/ui/src/kit-plain.css` | primitives for surfaces that render plain HTML, not React (0.2.1): menu, key/value list, icon button; 0.3.0 adds `ui-link` / `ui-prose`, the media card (`ui-card--media`, `__media`, `__body`, `__foot`) and the popup's `ui-modal__body` / `__foot`. Shipped inside `gemboss-ui.css`; the admin does not import it (its first-load CSS budget has ~0.1 KB headroom).; (0.4.0) report pages: delta, large KPI grid, bar list, column chart, legend, funnel, card grid, section head. |
325
- | `packages/ui/src/kit-plain.js` | 0.3.0: the behaviour those surfaces each wrote by hand: `gembossUI.openModal` (Escape, backdrop, Tab trap, stacking, ask-before-close, focus back), `toast` (text, error tone), `initMenus` (outside click, item, blur, Escape closes the menu before the popup). A classic script: inline it or `<script src>`; see "Plain-HTML behaviour" below. |
408
+ | `packages/ui/src/kit-plain.js` | 0.3.0: the behaviour those surfaces each wrote by hand: `gembossUI.openModal` (Escape, backdrop, Tab trap, stacking, ask-before-close, focus back), `toast` (text, error tone), `initMenus` (outside click, item, blur, Escape closes the menu before the popup); 0.5.0: `confirm`, `initShell`, `initPasswordFields` / `resetPasswordFields`. A classic script: inline it or `<script src>`; see "Plain-HTML behaviour" above. |
409
+ | `packages/ui/src/chat.css`, `chat-composer.css`, `assistant.css`, `auth.css`, `app-shell.css` | the chat body, the composer, the assistant frame, the sign-in screen, the topbar slots + base text. All inside `gemboss-ui.css`; the admin @imports `chat.css` and `chat-composer.css` itself. |
410
+ | `packages/ui/src/icon.svg` | 0.5.0: the GemBoss app icon, for `<link rel="icon" type="image/svg+xml">`. Must stay byte-identical to `apps/admin/src/app/icon.svg` (`kit-look.test.ts` checks). |
326
411
  | `packages/ui/src/**/*.tsx` | the components, as TypeScript source. Next: add `@gemboss/ui` to `transpilePackages`. Vite compiles it as is. |
327
412
 
328
413
  `gemboss-ui.css` is never committed and never edited. To change a colour or a radius, change
@@ -27,15 +27,18 @@ 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.4.2</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>1ca4f9ec8573</code>.</p></header>
30
+ <main><header class="cat-head"><h1>@gemboss/ui 0.6.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>47291357064c</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
- <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>&lt;button&gt;</code> lẫn <code>&lt;a&gt;</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>&lt;div class=&quot;cat-row&quot;&gt;
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>&lt;button&gt;</code> lẫn <code>&lt;a&gt;</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>&lt;div class=&quot;cat-row&quot;&gt;
33
33
  &lt;button type=&quot;button&quot; class=&quot;ui-btn&quot;&gt;Lưu nháp&lt;/button&gt;
34
34
  &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--primary&quot;&gt;Xuất bản&lt;/button&gt;
35
35
  &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--ghost&quot;&gt;Huỷ&lt;/button&gt;
36
36
  &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--sm&quot;&gt;Nhỏ&lt;/button&gt;
37
37
  &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--primary&quot; disabled&gt;Đang lưu…&lt;/button&gt;
38
38
  &lt;a class=&quot;ui-btn&quot; href=&quot;#buttons&quot;&gt;Link dạng nút&lt;/a&gt;
39
+ &lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Tông đỏ cho thao tác xoá (<code>ui-btn--critical</code>, Polaris <code>tone=critical</code>)</figcaption><div class="cat-live"><div class="cat-row"><button type="button" class="ui-btn ui-btn--critical">Thu hồi khoá</button><button type="button" class="ui-btn ui-btn--primary ui-btn--critical">Xoá vĩnh viễn</button></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-row&quot;&gt;
40
+ &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--critical&quot;&gt;Thu hồi khoá&lt;/button&gt;
41
+ &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--primary ui-btn--critical&quot;&gt;Xoá vĩnh viễn&lt;/button&gt;
39
42
  &lt;/div&gt;</code></pre></details></figure></section>
40
43
  <section id="fields"><h2>Ô nhập</h2><p>Mọi ô chữ dùng <code>gemboss-field</code>: cùng viền, cùng focus, 44px trên màn chạm.</p><figure class="cat-demo"><figcaption>Ô chữ · ô có biểu tượng · chọn · vùng chữ</figcaption><div class="cat-live"><div class="cat-stack"><label class="ui-label" for="cat-f1">Tên chiến dịch</label><input id="cat-f1" class="gemboss-field" placeholder="Tên chiến dịch"><label class="gemboss-field gemboss-field--group"><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"><circle cx="8.8" cy="8.8" r="5.3"></circle><path d="M12.8 12.8 17 17"></path></g></svg><input class="gemboss-field__bare" placeholder="Tìm store theo domain…"></label><select class="ui-select"><option>Mọi ngành</option><option>Mỹ phẩm</option></select><textarea class="gemboss-field" rows="4" placeholder="Ghi chú"></textarea><textarea class="ui-textarea" placeholder="ui-textarea: cùng họ, cao sẵn 96px"></textarea><label class="ui-check"><input type="checkbox" checked> Chỉ thứ đã duyệt</label><label class="ui-check"><input type="checkbox"> Khoá preset</label></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-stack&quot;&gt;
41
44
  &lt;label class=&quot;ui-label&quot; for=&quot;cat-f1&quot;&gt;Tên chiến dịch&lt;/label&gt;
@@ -63,7 +66,20 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
63
66
  &lt;input type=&quot;checkbox&quot; checked&gt; Chỉ thứ đã duyệt&lt;/label&gt;
64
67
  &lt;label class=&quot;ui-check&quot;&gt;
65
68
  &lt;input type=&quot;checkbox&quot;&gt; Khoá preset&lt;/label&gt;
66
- &lt;/div&gt;</code></pre></details></figure></section>
69
+ &lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Gợi ý và lỗi dưới ô (<code>ui-help</code>, <code>ui-error</code>, <code>aria-invalid</code> tô đỏ ô)</figcaption><div class="cat-live"><div class="cat-stack"><label class="ui-label" for="cat-f2">Domain</label><input id="cat-f2" class="gemboss-field gemboss-field--tall" value="https://glowrecipe.com" aria-invalid="true" aria-describedby="cat-f2-h cat-f2-e"><p class="ui-help" id="cat-f2-h">Không kèm https://, ví dụ glowrecipe.com.</p><p class="ui-error" id="cat-f2-e">Bỏ phần https:// rồi lưu lại.</p></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-stack&quot;&gt;
70
+ &lt;label class=&quot;ui-label&quot; for=&quot;cat-f2&quot;&gt;Domain&lt;/label&gt;
71
+ &lt;input id=&quot;cat-f2&quot; class=&quot;gemboss-field gemboss-field--tall&quot; value=&quot;https://glowrecipe.com&quot; aria-invalid=&quot;true&quot; aria-describedby=&quot;cat-f2-h cat-f2-e&quot;&gt;
72
+ &lt;p class=&quot;ui-help&quot; id=&quot;cat-f2-h&quot;&gt;Không kèm https://, ví dụ glowrecipe.com.&lt;/p&gt;
73
+ &lt;p class=&quot;ui-error&quot; id=&quot;cat-f2-e&quot;&gt;Bỏ phần https:// rồi lưu lại.&lt;/p&gt;
74
+ &lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Radio (<code>ui-choices</code> + <code>ui-check</code>) · công tắc (<code>ui-switch</code>, áp dụng ngay, không có nút Lưu)</figcaption><div class="cat-live"><fieldset class="ui-choices"><legend class="ui-label">Gửi bản tin</legend><label class="ui-check"><input type="radio" name="cat-r" checked> Thứ Hai hằng tuần</label><label class="ui-check"><input type="radio" name="cat-r"> Mỗi ngày</label></fieldset><label class="ui-check ui-switch"><input type="checkbox" role="switch" checked> Báo khi có đối thủ mới</label></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;fieldset class=&quot;ui-choices&quot;&gt;
75
+ &lt;legend class=&quot;ui-label&quot;&gt;Gửi bản tin&lt;/legend&gt;
76
+ &lt;label class=&quot;ui-check&quot;&gt;
77
+ &lt;input type=&quot;radio&quot; name=&quot;cat-r&quot; checked&gt; Thứ Hai hằng tuần&lt;/label&gt;
78
+ &lt;label class=&quot;ui-check&quot;&gt;
79
+ &lt;input type=&quot;radio&quot; name=&quot;cat-r&quot;&gt; Mỗi ngày&lt;/label&gt;
80
+ &lt;/fieldset&gt;
81
+ &lt;label class=&quot;ui-check ui-switch&quot;&gt;
82
+ &lt;input type=&quot;checkbox&quot; role=&quot;switch&quot; checked&gt; Báo khi có đối thủ mới&lt;/label&gt;</code></pre></details></figure></section>
67
83
  <section id="choice"><h2>Tab, nhóm nút, thẻ</h2><figure class="cat-demo"><figcaption>Tab (<code>aria-selected</code> quyết định tab đang chọn)</figcaption><div class="cat-live"><div class="ui-tabs" role="tablist"><button type="button" role="tab" aria-selected="true">Tổng quan</button><button type="button" role="tab" aria-selected="false">Chi tiết</button><button type="button" role="tab" aria-selected="false" disabled>Sắp có</button></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;ui-tabs&quot; role=&quot;tablist&quot;&gt;
68
84
  &lt;button type=&quot;button&quot; role=&quot;tab&quot; aria-selected=&quot;true&quot;&gt;Tổng quan&lt;/button&gt;
69
85
  &lt;button type=&quot;button&quot; role=&quot;tab&quot; aria-selected=&quot;false&quot;&gt;Chi tiết&lt;/button&gt;
@@ -115,6 +131,12 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
115
131
  &lt;span class=&quot;ui-tag ui-tag--accent&quot;&gt;Đã duyệt&lt;/span&gt;
116
132
  &lt;span class=&quot;ui-tag ui-tag--chip&quot;&gt;SECTION&lt;/span&gt;
117
133
  &lt;span class=&quot;ui-badge&quot;&gt;12&lt;/span&gt;
134
+ &lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Badge theo tông (<code>ui-badge--success|--warning|--critical|--info</code>, <code>--dot</code> có chấm trạng thái)</figcaption><div class="cat-live"><div class="cat-row"><span class="ui-badge">Nháp</span><span class="ui-badge ui-badge--success ui-badge--dot">Đang chạy</span><span class="ui-badge ui-badge--warning">Chờ duyệt</span><span class="ui-badge ui-badge--critical">Lỗi tải</span><span class="ui-badge ui-badge--info">Mới</span></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-row&quot;&gt;
135
+ &lt;span class=&quot;ui-badge&quot;&gt;Nháp&lt;/span&gt;
136
+ &lt;span class=&quot;ui-badge ui-badge--success ui-badge--dot&quot;&gt;Đang chạy&lt;/span&gt;
137
+ &lt;span class=&quot;ui-badge ui-badge--warning&quot;&gt;Chờ duyệt&lt;/span&gt;
138
+ &lt;span class=&quot;ui-badge ui-badge--critical&quot;&gt;Lỗi tải&lt;/span&gt;
139
+ &lt;span class=&quot;ui-badge ui-badge--info&quot;&gt;Mới&lt;/span&gt;
118
140
  &lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Tab bằng link (<code>&lt;nav class=ui-tabs&gt;</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>&lt;nav class=&quot;ui-tabs&quot; aria-label=&quot;Chế độ xem&quot;&gt;
119
141
  &lt;a href=&quot;#&quot; aria-current=&quot;page&quot;&gt;Tăng trưởng&lt;/a&gt;
120
142
  &lt;a href=&quot;#&quot;&gt;Mới lên&lt;/a&gt;
@@ -179,7 +201,48 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
179
201
  &lt;dt&gt;Điểm tiềm năng&lt;/dt&gt;
180
202
  &lt;dd&gt;71&lt;/dd&gt;
181
203
  &lt;/div&gt;
182
- &lt;/dl&gt;</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>&lt;div class=&quot;ui-notice ui-notice--warning&quot;&gt;Token Slack đã hết hạn: bản tin thứ Hai sẽ không gửi được.&lt;/div&gt;</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>&lt;nav class=&quot;ui-pager&quot; aria-label=&quot;Trang&quot;&gt;
204
+ &lt;/dl&gt;</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>&lt;div class=&quot;ui-notice ui-notice--warning&quot;&gt;Token Slack đã hết hạn: bản tin thứ Hai sẽ không gửi được.&lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Banner (<code>ui-banner</code>, tông <code>--warning|--critical|--success|--info</code>): việc người đọc phải làm</figcaption><div class="cat-live"><div class="ui-banner ui-banner--warning"><div class="ui-banner__head"><svg class="ui-banner__icon" viewBox="0 0 20 20" aria-hidden="true"><path d="M10 2.5 1.5 17h17L10 2.5Zm-.75 5h1.5v5h-1.5v-5Zm.75 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/></svg><h3 class="ui-banner__title">Token Slack đã hết hạn</h3><button type="button" class="ui-banner__close" aria-label="Ẩn"><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 class="ui-banner__body"><p>Bản tin thứ Hai sẽ không gửi được cho tới khi kết nối lại.</p><div class="ui-banner__actions"><a class="ui-btn" href="#surfaces">Kết nối lại</a></div></div></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;ui-banner ui-banner--warning&quot;&gt;
205
+ &lt;div class=&quot;ui-banner__head&quot;&gt;
206
+ &lt;svg class=&quot;ui-banner__icon&quot; viewBox=&quot;0 0 20 20&quot; aria-hidden=&quot;true&quot;&gt;
207
+ &lt;path d=&quot;M10 2.5 1.5 17h17L10 2.5Zm-.75 5h1.5v5h-1.5v-5Zm.75 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&quot;/&gt;
208
+ &lt;/svg&gt;
209
+ &lt;h3 class=&quot;ui-banner__title&quot;&gt;Token Slack đã hết hạn&lt;/h3&gt;
210
+ &lt;button type=&quot;button&quot; class=&quot;ui-banner__close&quot; aria-label=&quot;Ẩn&quot;&gt;
211
+ &lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;
212
+ &lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;
213
+ &lt;path d=&quot;M5 5l10 10M15 5 5 15&quot;&gt;
214
+ &lt;/path&gt;
215
+ &lt;/g&gt;
216
+ &lt;/svg&gt;
217
+ &lt;/button&gt;
218
+ &lt;/div&gt;
219
+ &lt;div class=&quot;ui-banner__body&quot;&gt;
220
+ &lt;p&gt;Bản tin thứ Hai sẽ không gửi được cho tới khi kết nối lại.&lt;/p&gt;
221
+ &lt;div class=&quot;ui-banner__actions&quot;&gt;
222
+ &lt;a class=&quot;ui-btn&quot; href=&quot;#surfaces&quot;&gt;Kết nối lại&lt;/a&gt;
223
+ &lt;/div&gt;
224
+ &lt;/div&gt;
225
+ &lt;/div&gt;</code></pre></details></figure><figure class="cat-demo"><figcaption>Đang chờ (<code>ui-spinner</code>, <code>--lg</code>) · tiến độ (<code>ui-progress</code>) · chú giải (<code>ui-tooltip</code>, rê hoặc Tab vào nút)</figcaption><div class="cat-live"><div class="cat-row"><span class="ui-spinner" role="status"><span class="ui-sr-only">Đang tải…</span></span><span class="ui-spinner ui-spinner--lg" role="status"><span class="ui-sr-only">Đang tải…</span></span><progress class="ui-progress" value="37" max="100" aria-label="Nhập dữ liệu" style="flex:1 1 200px">37%</progress><progress class="ui-progress ui-progress--success" value="100" max="100" aria-label="Xong" style="flex:1 1 120px">100%</progress><span class="ui-tooltip" style="margin-top:48px"><button type="button" class="ui-btn ui-btn--icon" aria-label="Lưu trữ" aria-describedby="cat-tt"><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><span class="ui-tooltip__bubble" role="tooltip" id="cat-tt">Lưu trữ</span></span></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-row&quot;&gt;
226
+ &lt;span class=&quot;ui-spinner&quot; role=&quot;status&quot;&gt;
227
+ &lt;span class=&quot;ui-sr-only&quot;&gt;Đang tải…&lt;/span&gt;
228
+ &lt;/span&gt;
229
+ &lt;span class=&quot;ui-spinner ui-spinner--lg&quot; role=&quot;status&quot;&gt;
230
+ &lt;span class=&quot;ui-sr-only&quot;&gt;Đang tải…&lt;/span&gt;
231
+ &lt;/span&gt;
232
+ &lt;progress class=&quot;ui-progress&quot; value=&quot;37&quot; max=&quot;100&quot; aria-label=&quot;Nhập dữ liệu&quot; style=&quot;flex:1 1 200px&quot;&gt;37%&lt;/progress&gt;
233
+ &lt;progress class=&quot;ui-progress ui-progress--success&quot; value=&quot;100&quot; max=&quot;100&quot; aria-label=&quot;Xong&quot; style=&quot;flex:1 1 120px&quot;&gt;100%&lt;/progress&gt;
234
+ &lt;span class=&quot;ui-tooltip&quot; style=&quot;margin-top:48px&quot;&gt;
235
+ &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--icon&quot; aria-label=&quot;Lưu trữ&quot; aria-describedby=&quot;cat-tt&quot;&gt;
236
+ &lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;
237
+ &lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;
238
+ &lt;path d=&quot;M5 5l10 10M15 5 5 15&quot;&gt;
239
+ &lt;/path&gt;
240
+ &lt;/g&gt;
241
+ &lt;/svg&gt;
242
+ &lt;/button&gt;
243
+ &lt;span class=&quot;ui-tooltip__bubble&quot; role=&quot;tooltip&quot; id=&quot;cat-tt&quot;&gt;Lưu trữ&lt;/span&gt;
244
+ &lt;/span&gt;
245
+ &lt;/div&gt;</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>&lt;nav class=&quot;ui-pager&quot; aria-label=&quot;Trang&quot;&gt;
183
246
  &lt;a href=&quot;#&quot; rel=&quot;prev&quot;&gt;
184
247
  &lt;span aria-hidden=&quot;true&quot;&gt;‹&lt;/span&gt; Trước&lt;/a&gt;
185
248
  &lt;span aria-current=&quot;page&quot;&gt;2 / 9&lt;/span&gt;
@@ -497,10 +560,11 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
497
560
  &lt;/div&gt;
498
561
  &lt;div style=&quot;padding:12px;background:var(--gemboss-surface)&quot;&gt;Nội dung&lt;/div&gt;
499
562
  &lt;/div&gt;</code></pre></details></figure></section>
500
- <section id="modal"><h2>Hộp thoại, toast, menu (JS thuần)</h2><p>Lớp phủ <code>ui-overlay</code> + hộp <code>ui-modal</code>, thân <code>ui-modal__body</code>, chân <code>ui-modal__foot</code>. Bề mặt không dùng React nạp <code>@gemboss/ui/kit-plain.js</code> (script thường, chèn thẳng vào trang được) và gọi <code>gembossUI.openModal</code> / <code>toast</code> / <code>initMenus</code>: Escape, chạm nền, giữ Tab trong hộp, hộp chồng nhau, hỏi trước khi đóng. Bấm thử các nút dưới đây.</p><figure class="cat-demo"><figcaption>Bấm thử (<code>gembossUI</code>) <span>chạy kit-plain.js thật của gói</span></figcaption><div class="cat-live"><div class="cat-row"><button type="button" class="ui-btn ui-btn--primary" data-cat-js="modal">Mở hộp thoại</button><button type="button" class="ui-btn" data-cat-js="toast">Toast</button><button type="button" class="ui-btn" data-cat-js="toast-error">Toast lỗi</button></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-row&quot;&gt;
563
+ <section id="modal"><h2>Hộp thoại, toast, menu (JS thuần)</h2><p>Lớp phủ <code>ui-overlay</code> + hộp <code>ui-modal</code>, thân <code>ui-modal__body</code>, chân <code>ui-modal__foot</code>. Bề mặt không dùng React nạp <code>@gemboss/ui/kit-plain.js</code> (script thường, chèn thẳng vào trang được) và gọi <code>gembossUI.openModal</code> / <code>toast</code> / <code>initMenus</code>: Escape, chạm nền, giữ Tab trong hộp, hộp chồng nhau, hỏi trước khi đóng. Bấm thử các nút dưới đây.</p><figure class="cat-demo"><figcaption>Bấm thử (<code>gembossUI</code>) <span>chạy kit-plain.js thật của gói</span></figcaption><div class="cat-live"><div class="cat-row"><button type="button" class="ui-btn ui-btn--primary" data-cat-js="modal">Mở hộp thoại</button><button type="button" class="ui-btn" data-cat-js="toast">Toast</button><button type="button" class="ui-btn" data-cat-js="toast-error">Toast lỗi</button><button type="button" class="ui-btn ui-btn--critical" data-cat-js="confirm">Xác nhận xoá</button></div></div><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;cat-row&quot;&gt;
501
564
  &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--primary&quot; data-cat-js=&quot;modal&quot;&gt;Mở hộp thoại&lt;/button&gt;
502
565
  &lt;button type=&quot;button&quot; class=&quot;ui-btn&quot; data-cat-js=&quot;toast&quot;&gt;Toast&lt;/button&gt;
503
566
  &lt;button type=&quot;button&quot; class=&quot;ui-btn&quot; data-cat-js=&quot;toast-error&quot;&gt;Toast lỗi&lt;/button&gt;
567
+ &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--critical&quot; data-cat-js=&quot;confirm&quot;&gt;Xác nhận xoá&lt;/button&gt;
504
568
  &lt;/div&gt;</code></pre></details></figure><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 class="ui-modal__body">Lịch sử vẫn còn trong sổ; phiên chỉ ẩn khỏi danh sách.</div><div class="ui-modal__foot"><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><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;ui-modal&quot; role=&quot;dialog&quot; aria-modal=&quot;true&quot; aria-labelledby=&quot;cat-m&quot;&gt;
505
569
  &lt;div class=&quot;ui-modal__head&quot;&gt;
506
570
  &lt;div class=&quot;ui-modal__title&quot; id=&quot;cat-m&quot;&gt;Xoá phiên này?&lt;/div&gt;
@@ -519,10 +583,10 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
519
583
  &lt;button type=&quot;button&quot; class=&quot;ui-btn ui-btn--primary&quot;&gt;Xoá&lt;/button&gt;
520
584
  &lt;/div&gt;
521
585
  &lt;/div&gt;</code></pre></details></figure></section>
522
- <section id="shell"><h2>Khung app</h2><p>Topbar 56px, rail 240px, ≤760px thành ngăn kéo. Logo <code>BrandLockup</code>, tài khoản <code>AccountMenu</code> (bấm chữ viết tắt), mục Trợ lý ở chân rail có class <code>gnav-assistant</code>.</p><figure class="cat-demo cat-screen"><figcaption>AppShell + BrandLockup + AccountMenu + Icon</figcaption><iframe title="AppShell + BrandLockup + AccountMenu + Icon" style="height:460px" srcdoc="&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;../gemboss-ui.css&quot;&gt;&lt;/head&gt;&lt;body&gt;&lt;div class=&quot;gemboss-platform-shell&quot;&gt;&lt;header class=&quot;gemboss-platform-topbar&quot;&gt;&lt;div class=&quot;gemboss-topbar-start&quot;&gt;&lt;button type=&quot;button&quot; class=&quot;gemboss-mobile-menu-btn&quot; aria-label=&quot;Mở menu&quot; aria-expanded=&quot;false&quot; aria-controls=&quot;:R0:&quot;&gt;&lt;svg width=&quot;20&quot; height=&quot;20&quot; viewBox=&quot;0 0 20 20&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M3 5.5h14M3 10h14M3 14.5h14&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.7&quot; stroke-linecap=&quot;round&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;a class=&quot;gb-brand&quot; href=&quot;#&quot; aria-label=&quot;GemFactory home&quot;&gt;&lt;svg class=&quot;gb-brand-gem&quot; viewBox=&quot;0 0 32 40&quot; width=&quot;20.8&quot; height=&quot;26&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(0,0.8) scale(0.31)&quot;&gt;&lt;polygon points=&quot;26,14 74,14 66,50 34,50&quot; fill=&quot;#5661F5&quot; fill-opacity=&quot;0.92&quot;&gt;&lt;/polygon&gt;&lt;path d=&quot;M26,14 74,14 96,50 50,110 4,50Z&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;8.75&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M4,50H96M26,14 34,50M74,14 66,50M34,50 50,110M66,50 50,110&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;5.45&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;span class=&quot;gb-brand-name&quot;&gt;GemFactory&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;gemboss-topbar-center&quot;&gt;&lt;/div&gt;&lt;div class=&quot;gemboss-topbar-end&quot;&gt;&lt;details class=&quot;gb-account&quot;&gt;&lt;summary class=&quot;gemboss-account-chip&quot; aria-label=&quot;Account · Minh Anh&quot; title=&quot;Minh Anh&quot;&gt;MA&lt;/summary&gt;&lt;div role=&quot;menu&quot; class=&quot;gemboss-popover-surface gemboss-account-menu&quot;&gt;&lt;div class=&quot;gb-account-head&quot;&gt;&lt;div class=&quot;gb-account-name&quot;&gt;Minh Anh&lt;/div&gt;&lt;div class=&quot;gb-account-email&quot;&gt;ma@gemboss.ai&lt;/div&gt;&lt;div class=&quot;gb-account-role&quot;&gt;Designer&lt;/div&gt;&lt;/div&gt;&lt;a role=&quot;menuitem&quot; class=&quot;gemboss-account-menu-item&quot; href=&quot;#&quot;&gt;Cài đặt&lt;/a&gt;&lt;form method=&quot;post&quot; action=&quot;#&quot; class=&quot;gb-account-form&quot;&gt;&lt;button role=&quot;menuitem&quot; type=&quot;submit&quot; class=&quot;gemboss-account-menu-item danger&quot;&gt;Đăng xuất&lt;/button&gt;&lt;/form&gt;&lt;/div&gt;&lt;/details&gt;&lt;/div&gt;&lt;/header&gt;&lt;nav class=&quot;icon-nav&quot; aria-label=&quot;Main navigation&quot; id=&quot;:R0:&quot;&gt;&lt;div class=&quot;gnav-scroll&quot;&gt;&lt;div class=&quot;gnav-heading&quot;&gt;Kho&lt;/div&gt;&lt;div class=&quot;gnav-group active&quot;&gt;&lt;a class=&quot;gnav-item active&quot; aria-current=&quot;page&quot; href=&quot;#&quot;&gt;&lt;span class=&quot;gnav-ico&quot;&gt;&lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;rect x=&quot;3&quot; y=&quot;3.5&quot; width=&quot;4&quot; height=&quot;13&quot; rx=&quot;1.2&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;8&quot; y=&quot;3.5&quot; width=&quot;4&quot; height=&quot;9&quot; rx=&quot;1.2&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;13&quot; y=&quot;3.5&quot; width=&quot;4&quot; height=&quot;6&quot; rx=&quot;1.2&quot;&gt;&lt;/rect&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;gnav-label&quot;&gt;Board&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;gnav-group&quot;&gt;&lt;a class=&quot;gnav-item&quot; href=&quot;#&quot;&gt;&lt;span class=&quot;gnav-ico&quot;&gt;&lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;rect x=&quot;3.5&quot; y=&quot;3&quot; width=&quot;13&quot; height=&quot;3.5&quot; rx=&quot;1&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;3.5&quot; y=&quot;8.25&quot; width=&quot;13&quot; height=&quot;3.5&quot; rx=&quot;1&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;3.5&quot; y=&quot;13.5&quot; width=&quot;13&quot; height=&quot;3.5&quot; rx=&quot;1&quot;&gt;&lt;/rect&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;gnav-label&quot;&gt;Ghép trang&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;gnav-group&quot;&gt;&lt;a class=&quot;gnav-item&quot; href=&quot;#&quot;&gt;&lt;span class=&quot;gnav-ico&quot;&gt;&lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 3.2 17.3 16H2.7z&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M10 8.2v3.6M10 13.9v.1&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;gnav-label&quot;&gt;Framework thiếu&lt;/span&gt;&lt;span class=&quot;gnav-count&quot; aria-label=&quot;3 mục đang mở&quot;&gt;3&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;gnav-bottom&quot;&gt;&lt;div class=&quot;gnav-sep&quot;&gt;&lt;/div&gt;&lt;a class=&quot;gnav-item gnav-assistant&quot; href=&quot;#&quot;&gt;&lt;span class=&quot;gnav-ico&quot;&gt;&lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M3 4.5h14v9H8.5L5 16.5v-3H3z&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;gnav-label&quot;&gt;Trợ lý&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/nav&gt;&lt;button type=&quot;button&quot; class=&quot;gemboss-mobile-nav-backdrop&quot; aria-label=&quot;Đóng menu&quot; tabindex=&quot;-1&quot;&gt;&lt;/button&gt;&lt;main class=&quot;gemboss-platform-main&quot;&gt;&lt;div style=&quot;padding:24px&quot;&gt;&lt;h1 style=&quot;margin:0&quot;&gt;Nội dung màn&lt;/h1&gt;&lt;/div&gt;&lt;/main&gt;&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;"></iframe><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;gemboss-platform-shell&quot;&gt;
586
+ <section id="shell"><h2>Khung app</h2><p>Topbar 56px, rail 240px, ≤760px thành ngăn kéo. Logo <code>BrandLockup</code>, tài khoản <code>AccountMenu</code> (bấm chữ viết tắt), mục Trợ lý ở chân rail có class <code>gnav-assistant</code>.</p><figure class="cat-demo cat-screen"><figcaption>AppShell + BrandLockup + AccountMenu + Icon</figcaption><iframe title="AppShell + BrandLockup + AccountMenu + Icon" style="height:460px" srcdoc="&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;../gemboss-ui.css&quot;&gt;&lt;/head&gt;&lt;body&gt;&lt;div class=&quot;gemboss-platform-shell&quot;&gt;&lt;header class=&quot;gemboss-platform-topbar&quot;&gt;&lt;div class=&quot;gemboss-topbar-start&quot;&gt;&lt;button type=&quot;button&quot; class=&quot;gemboss-mobile-menu-btn&quot; aria-label=&quot;Mở menu&quot; aria-expanded=&quot;false&quot; data-label-open=&quot;Mở menu&quot; data-label-close=&quot;Đóng menu&quot; aria-controls=&quot;:R0:&quot;&gt;&lt;svg width=&quot;20&quot; height=&quot;20&quot; viewBox=&quot;0 0 20 20&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M3 5.5h14M3 10h14M3 14.5h14&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.7&quot; stroke-linecap=&quot;round&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/button&gt;&lt;a class=&quot;gb-brand&quot; href=&quot;#&quot; aria-label=&quot;GemFactory home&quot;&gt;&lt;svg class=&quot;gb-brand-gem&quot; viewBox=&quot;0 0 32 40&quot; width=&quot;20.8&quot; height=&quot;26&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(0,0.8) scale(0.31)&quot;&gt;&lt;polygon points=&quot;26,14 74,14 66,50 34,50&quot; fill=&quot;#5661F5&quot; fill-opacity=&quot;0.92&quot;&gt;&lt;/polygon&gt;&lt;path d=&quot;M26,14 74,14 96,50 50,110 4,50Z&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;8.75&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M4,50H96M26,14 34,50M74,14 66,50M34,50 50,110M66,50 50,110&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;5.45&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;span class=&quot;gb-brand-name&quot;&gt;GemFactory&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;gemboss-topbar-center&quot;&gt;&lt;/div&gt;&lt;div class=&quot;gemboss-topbar-end&quot;&gt;&lt;details class=&quot;gb-account&quot;&gt;&lt;summary class=&quot;gemboss-account-chip&quot; aria-label=&quot;Account · Minh Anh&quot; title=&quot;Minh Anh&quot;&gt;MA&lt;/summary&gt;&lt;div role=&quot;menu&quot; class=&quot;gemboss-popover-surface gemboss-account-menu&quot;&gt;&lt;div class=&quot;gb-account-head&quot;&gt;&lt;div class=&quot;gb-account-name&quot;&gt;Minh Anh&lt;/div&gt;&lt;div class=&quot;gb-account-email&quot;&gt;ma@gemboss.ai&lt;/div&gt;&lt;div class=&quot;gb-account-role&quot;&gt;Designer&lt;/div&gt;&lt;/div&gt;&lt;a role=&quot;menuitem&quot; class=&quot;gemboss-account-menu-item&quot; href=&quot;#&quot;&gt;Cài đặt&lt;/a&gt;&lt;form method=&quot;post&quot; action=&quot;#&quot; class=&quot;gb-account-form&quot;&gt;&lt;button role=&quot;menuitem&quot; type=&quot;submit&quot; class=&quot;gemboss-account-menu-item danger&quot;&gt;Đăng xuất&lt;/button&gt;&lt;/form&gt;&lt;/div&gt;&lt;/details&gt;&lt;/div&gt;&lt;/header&gt;&lt;nav class=&quot;icon-nav&quot; aria-label=&quot;Main navigation&quot; id=&quot;:R0:&quot;&gt;&lt;div class=&quot;gnav-scroll&quot;&gt;&lt;div class=&quot;gnav-heading&quot;&gt;Kho&lt;/div&gt;&lt;div class=&quot;gnav-group active&quot;&gt;&lt;a class=&quot;gnav-item active&quot; aria-current=&quot;page&quot; href=&quot;#&quot;&gt;&lt;span class=&quot;gnav-ico&quot;&gt;&lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;rect x=&quot;3&quot; y=&quot;3.5&quot; width=&quot;4&quot; height=&quot;13&quot; rx=&quot;1.2&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;8&quot; y=&quot;3.5&quot; width=&quot;4&quot; height=&quot;9&quot; rx=&quot;1.2&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;13&quot; y=&quot;3.5&quot; width=&quot;4&quot; height=&quot;6&quot; rx=&quot;1.2&quot;&gt;&lt;/rect&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;gnav-label&quot;&gt;Board&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;gnav-group&quot;&gt;&lt;a class=&quot;gnav-item&quot; href=&quot;#&quot;&gt;&lt;span class=&quot;gnav-ico&quot;&gt;&lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;rect x=&quot;3.5&quot; y=&quot;3&quot; width=&quot;13&quot; height=&quot;3.5&quot; rx=&quot;1&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;3.5&quot; y=&quot;8.25&quot; width=&quot;13&quot; height=&quot;3.5&quot; rx=&quot;1&quot;&gt;&lt;/rect&gt;&lt;rect x=&quot;3.5&quot; y=&quot;13.5&quot; width=&quot;13&quot; height=&quot;3.5&quot; rx=&quot;1&quot;&gt;&lt;/rect&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;gnav-label&quot;&gt;Ghép trang&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;gnav-group&quot;&gt;&lt;a class=&quot;gnav-item&quot; href=&quot;#&quot;&gt;&lt;span class=&quot;gnav-ico&quot;&gt;&lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M10 3.2 17.3 16H2.7z&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M10 8.2v3.6M10 13.9v.1&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;gnav-label&quot;&gt;Framework thiếu&lt;/span&gt;&lt;span class=&quot;gnav-count&quot; aria-label=&quot;3 mục đang mở&quot;&gt;3&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;gnav-bottom&quot;&gt;&lt;div class=&quot;gnav-sep&quot;&gt;&lt;/div&gt;&lt;a class=&quot;gnav-item gnav-assistant&quot; href=&quot;#&quot;&gt;&lt;span class=&quot;gnav-ico&quot;&gt;&lt;svg viewBox=&quot;0 0 20 20&quot; width=&quot;20&quot; height=&quot;20&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;path d=&quot;M3 4.5h14v9H8.5L5 16.5v-3H3z&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;span class=&quot;gnav-label&quot;&gt;Trợ lý&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;/nav&gt;&lt;button type=&quot;button&quot; class=&quot;gemboss-mobile-nav-backdrop&quot; aria-label=&quot;Đóng menu&quot; tabindex=&quot;-1&quot;&gt;&lt;/button&gt;&lt;main class=&quot;gemboss-platform-main&quot;&gt;&lt;div style=&quot;padding:24px&quot;&gt;&lt;h1 style=&quot;margin:0&quot;&gt;Nội dung màn&lt;/h1&gt;&lt;/div&gt;&lt;/main&gt;&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;"></iframe><details class="cat-code"><summary>Markup</summary><pre><code>&lt;div class=&quot;gemboss-platform-shell&quot;&gt;
523
587
  &lt;header class=&quot;gemboss-platform-topbar&quot;&gt;
524
588
  &lt;div class=&quot;gemboss-topbar-start&quot;&gt;
525
- &lt;button type=&quot;button&quot; class=&quot;gemboss-mobile-menu-btn&quot; aria-label=&quot;Mở menu&quot; aria-expanded=&quot;false&quot; aria-controls=&quot;:R0:&quot;&gt;
589
+ &lt;button type=&quot;button&quot; class=&quot;gemboss-mobile-menu-btn&quot; aria-label=&quot;Mở menu&quot; aria-expanded=&quot;false&quot; data-label-open=&quot;Mở menu&quot; data-label-close=&quot;Đóng menu&quot; aria-controls=&quot;:R0:&quot;&gt;
526
590
  &lt;svg width=&quot;20&quot; height=&quot;20&quot; viewBox=&quot;0 0 20 20&quot; aria-hidden=&quot;true&quot;&gt;
527
591
  &lt;path d=&quot;M3 5.5h14M3 10h14M3 14.5h14&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.7&quot; stroke-linecap=&quot;round&quot;&gt;
528
592
  &lt;/path&gt;
@@ -639,7 +703,7 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
639
703
  &lt;/div&gt;
640
704
  &lt;/main&gt;
641
705
  &lt;/div&gt;</code></pre></details></figure></section>
642
- <section id="auth"><h2>Đăng nhập</h2><figure class="cat-demo cat-screen"><figcaption>AuthLayout + PasswordField</figcaption><iframe title="AuthLayout + PasswordField" style="height:620px" srcdoc="&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;../gemboss-ui.css&quot;&gt;&lt;/head&gt;&lt;body&gt;&lt;main class=&quot;gb-auth&quot;&gt;&lt;div class=&quot;gb-auth-split&quot;&gt;&lt;div class=&quot;gb-auth-stage&quot;&gt;&lt;div class=&quot;gb-auth-card&quot;&gt;&lt;header class=&quot;gb-auth-header&quot;&gt;&lt;span class=&quot;gb-auth-mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;svg viewBox=&quot;0 0 32 32&quot; width=&quot;56&quot; height=&quot;56&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;rect width=&quot;32&quot; height=&quot;32&quot; rx=&quot;7&quot; style=&quot;fill:var(--gb-mark-bg)&quot;&gt;&lt;/rect&gt;&lt;g transform=&quot;translate(16,16) scale(0.24) translate(-50,-62)&quot;&gt;&lt;polygon points=&quot;26,14 74,14 66,50 34,50&quot; fill=&quot;#8a93ff&quot;&gt;&lt;/polygon&gt;&lt;path d=&quot;M26,14 74,14 96,50 50,110 4,50Z&quot; fill=&quot;none&quot; stroke=&quot;#f4f4f2&quot; stroke-width=&quot;5&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M4,50H96M26,14 34,50M74,14 66,50M34,50 50,110M66,50 50,110&quot; fill=&quot;none&quot; stroke=&quot;#f4f4f2&quot; stroke-opacity=&quot;0.72&quot; stroke-width=&quot;3&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;h1&gt;GemFactory&lt;/h1&gt;&lt;p&gt;Kho section của GemBoss&lt;/p&gt;&lt;/header&gt;&lt;form class=&quot;gb-auth-form&quot; method=&quot;post&quot; action=&quot;#&quot;&gt;&lt;div class=&quot;gb-auth-field&quot;&gt;&lt;label for=&quot;pw&quot;&gt;Mật khẩu&lt;/label&gt;&lt;div class=&quot;gb-auth-pw&quot;&gt;&lt;input class=&quot;gb-auth-input&quot; id=&quot;pw&quot; type=&quot;password&quot;/&gt;&lt;button type=&quot;button&quot; aria-controls=&quot;pw&quot; aria-label=&quot;Hiện&quot; title=&quot;Hiện&quot; class=&quot;gb-auth-pw-toggle&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.6&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;path d=&quot;M1.5 10S4.6 4.5 10 4.5 18.5 10 18.5 10 15.4 15.5 10 15.5 1.5 10 1.5 10Z&quot;&gt;&lt;/path&gt;&lt;circle cx=&quot;10&quot; cy=&quot;10&quot; r=&quot;2.6&quot;&gt;&lt;/circle&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;button type=&quot;submit&quot; class=&quot;gb-auth-submit&quot;&gt;Đăng nhập&lt;/button&gt;&lt;/form&gt;&lt;/div&gt;&lt;/div&gt;&lt;aside class=&quot;gb-auth-panel&quot;&gt;&lt;div class=&quot;gb-auth-panel-in&quot;&gt;&lt;p class=&quot;gb-auth-eyebrow&quot;&gt;GemBoss&lt;/p&gt;&lt;h2 class=&quot;gb-auth-panel-h&quot;&gt;Một ngôn ngữ thiết kế cho mọi công cụ&lt;/h2&gt;&lt;p class=&quot;gb-auth-panel-p&quot;&gt;Đăng nhập bằng mật khẩu đội đã gửi riêng.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;&lt;/main&gt;&lt;/body&gt;&lt;/html&gt;"></iframe><details class="cat-code"><summary>Markup</summary><pre><code>&lt;main class=&quot;gb-auth&quot;&gt;
706
+ <section id="auth"><h2>Đăng nhập</h2><figure class="cat-demo cat-screen"><figcaption>AuthLayout + PasswordField</figcaption><iframe title="AuthLayout + PasswordField" style="height:620px" srcdoc="&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;../gemboss-ui.css&quot;&gt;&lt;/head&gt;&lt;body&gt;&lt;main class=&quot;gb-auth&quot;&gt;&lt;div class=&quot;gb-auth-split&quot;&gt;&lt;div class=&quot;gb-auth-stage&quot;&gt;&lt;div class=&quot;gb-auth-card&quot;&gt;&lt;header class=&quot;gb-auth-header&quot;&gt;&lt;span class=&quot;gb-auth-mark&quot; aria-hidden=&quot;true&quot;&gt;&lt;svg viewBox=&quot;0 0 32 32&quot; width=&quot;56&quot; height=&quot;56&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;rect width=&quot;32&quot; height=&quot;32&quot; rx=&quot;7&quot; style=&quot;fill:var(--gb-mark-bg)&quot;&gt;&lt;/rect&gt;&lt;g transform=&quot;translate(16,16) scale(0.24) translate(-50,-62)&quot;&gt;&lt;polygon points=&quot;26,14 74,14 66,50 34,50&quot; fill=&quot;#8a93ff&quot;&gt;&lt;/polygon&gt;&lt;path d=&quot;M26,14 74,14 96,50 50,110 4,50Z&quot; fill=&quot;none&quot; stroke=&quot;#f4f4f2&quot; stroke-width=&quot;5&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M4,50H96M26,14 34,50M74,14 66,50M34,50 50,110M66,50 50,110&quot; fill=&quot;none&quot; stroke=&quot;#f4f4f2&quot; stroke-opacity=&quot;0.72&quot; stroke-width=&quot;3&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/span&gt;&lt;h1&gt;GemFactory&lt;/h1&gt;&lt;p&gt;Kho section của GemBoss&lt;/p&gt;&lt;/header&gt;&lt;form class=&quot;gb-auth-form&quot; method=&quot;post&quot; action=&quot;#&quot;&gt;&lt;div class=&quot;gb-auth-field&quot;&gt;&lt;label for=&quot;pw&quot;&gt;Mật khẩu&lt;/label&gt;&lt;div class=&quot;gb-auth-pw&quot;&gt;&lt;input class=&quot;gb-auth-input&quot; id=&quot;pw&quot; type=&quot;password&quot;/&gt;&lt;button type=&quot;button&quot; aria-controls=&quot;pw&quot; aria-label=&quot;Hiện&quot; title=&quot;Hiện&quot; data-label-show=&quot;Hiện&quot; data-label-hide=&quot;Ẩn&quot; class=&quot;gb-auth-pw-toggle&quot;&gt;&lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.6&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;path d=&quot;M1.5 10S4.6 4.5 10 4.5 18.5 10 18.5 10 15.4 15.5 10 15.5 1.5 10 1.5 10Z&quot;&gt;&lt;/path&gt;&lt;circle cx=&quot;10&quot; cy=&quot;10&quot; r=&quot;2.6&quot;&gt;&lt;/circle&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;button type=&quot;submit&quot; class=&quot;gb-auth-submit&quot;&gt;Đăng nhập&lt;/button&gt;&lt;/form&gt;&lt;/div&gt;&lt;/div&gt;&lt;aside class=&quot;gb-auth-panel&quot;&gt;&lt;div class=&quot;gb-auth-panel-in&quot;&gt;&lt;p class=&quot;gb-auth-eyebrow&quot;&gt;GemBoss&lt;/p&gt;&lt;h2 class=&quot;gb-auth-panel-h&quot;&gt;Một ngôn ngữ thiết kế cho mọi công cụ&lt;/h2&gt;&lt;p class=&quot;gb-auth-panel-p&quot;&gt;Đăng nhập bằng mật khẩu đội đã gửi riêng.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;&lt;/div&gt;&lt;/main&gt;&lt;/body&gt;&lt;/html&gt;"></iframe><details class="cat-code"><summary>Markup</summary><pre><code>&lt;main class=&quot;gb-auth&quot;&gt;
643
707
  &lt;div class=&quot;gb-auth-split&quot;&gt;
644
708
  &lt;div class=&quot;gb-auth-stage&quot;&gt;
645
709
  &lt;div class=&quot;gb-auth-card&quot;&gt;
@@ -666,7 +730,7 @@ body{margin:0;background:var(--gemboss-app-bg,#f1f1f1);color:var(--gemboss-text)
666
730
  &lt;label for=&quot;pw&quot;&gt;Mật khẩu&lt;/label&gt;
667
731
  &lt;div class=&quot;gb-auth-pw&quot;&gt;
668
732
  &lt;input class=&quot;gb-auth-input&quot; id=&quot;pw&quot; type=&quot;password&quot;/&gt;
669
- &lt;button type=&quot;button&quot; aria-controls=&quot;pw&quot; aria-label=&quot;Hiện&quot; title=&quot;Hiện&quot; class=&quot;gb-auth-pw-toggle&quot;&gt;
733
+ &lt;button type=&quot;button&quot; aria-controls=&quot;pw&quot; aria-label=&quot;Hiện&quot; title=&quot;Hiện&quot; data-label-show=&quot;Hiện&quot; data-label-hide=&quot;Ẩn&quot; class=&quot;gb-auth-pw-toggle&quot;&gt;
670
734
  &lt;svg width=&quot;18&quot; height=&quot;18&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.6&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;
671
735
  &lt;path d=&quot;M1.5 10S4.6 4.5 10 4.5 18.5 10 18.5 10 15.4 15.5 10 15.5 1.5 10 1.5 10Z&quot;&gt;
672
736
  &lt;/path&gt;
@@ -873,6 +937,7 @@ document.addEventListener("click", function (e) {
873
937
  var k = b.dataset.catJs;
874
938
  if (k === "toast") gembossUI.toast("Đã lưu");
875
939
  else if (k === "toast-error") gembossUI.toast("Không lưu được: mất mạng", { error: true });
940
+ else if (k === "confirm") gembossUI.confirm({ title: "Xoá phiên này?", body: "Lịch sử vẫn còn trong sổ.", confirmLabel: "Xoá", cancelLabel: "Huỷ", closeLabel: "Đóng", tone: "critical" }).then(function (ok) { gembossUI.toast(ok ? "Đã xoá" : "Đã huỷ"); });
876
941
  else {
877
942
  var m = gembossUI.openModal({ title: "Xoá phiên này?", closeLabel: "Đóng", body: '<div class="ui-modal__body">Lịch sử vẫn còn trong sổ; phiên chỉ ẩn khỏi danh sách.</div>',
878
943
  foot: '<button type="button" class="ui-btn ui-btn--ghost" data-v="0">Huỷ</button><button type="button" class="ui-btn ui-btn--primary" data-v="1" autofocus>Xoá</button>',