@gemboss/ui 0.10.0 → 0.13.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/CHANGELOG.md +113 -0
- package/README.md +155 -2
- package/catalog/index.html +2156 -15
- package/gemboss-ui.css +126 -66
- package/package.json +2 -1
- package/src/app-shell.css +13 -0
- package/src/collections.css +350 -0
- package/src/collections.tsx +414 -0
- package/src/editor.css +22 -19
- package/src/gemboss-tokens.css +1 -1
- package/src/index-table.tsx +216 -0
- package/src/index.ts +11 -0
- package/src/kit-plain.css +42 -9
- package/src/kit-plain.js +136 -1
- package/src/theme-dark.css +1 -0
- package/src/ui.css +67 -54
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: 4fa51b50e62110c020ab1052fada16e025665f4a
|
|
5
|
+
* sources sha256: 9b0060f874885bf4ba4ab9dc6448bc06ae386328bb9bfebcf23bfc6c2671561d
|
|
6
6
|
* contents: 1 allow-listed rule from apps/admin/src/app/globals.css + packages/ui/src/gemboss-tokens.css
|
|
7
7
|
* (verbatim) + packages/ui/src/ui.css (verbatim) + packages/ui/src/gemboss-field.css (verbatim)
|
|
8
8
|
* + 181 rules from packages/ui/src/shell.css (the shared shell)
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
and reading as "the font keeps shrinking". New rule: pick a step here, never a
|
|
97
97
|
raw px. Floor is 12px (--fs-caption); 11px (--fs-micro) is reserved for bold
|
|
98
98
|
micro-badges/count-pills ONLY — nothing below that. Body default is 14px, one
|
|
99
|
-
step up from Polaris bodyMd (13px; bodySm is 12) which the app had been leaning on for prose. The kit (ui.css) reads --ui-fs-body, 13px, because it sits beside Polaris components. */
|
|
99
|
+
step up from Polaris bodyMd (13px; bodySm is 12) which the app had been leaning on for prose. The kit (ui.css, kit-plain.css) reads --ui-fs-body, 13px, because it sits beside Polaris components; since 0.12.0 it reads none of body/h1-h3 here (README "Admin density"). */
|
|
100
100
|
--gemboss-fs-micro: 11px; /* bold count-pills / status chips only */
|
|
101
101
|
--gemboss-fs-caption: 12px; /* metadata, timestamps, secondary labels */
|
|
102
102
|
--gemboss-fs-body: 14px; /* default body / reading text */
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/* ═══ ui.css ═══ */
|
|
184
|
-
/*
|
|
184
|
+
/* ══
|
|
185
185
|
Tool/App UI kit — the shared primitive layer for the internal operator surfaces
|
|
186
186
|
(admin console, seller app chrome, theme-editor). ONE semantic token spine
|
|
187
187
|
(`--ui-*`) that every primitive reads, plus the primitive CSS itself.
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
The `--ui-*` layer is SEMANTIC and resolved per surface: components only read
|
|
195
195
|
`--ui-*`; each surface maps those to its own tokens (admin → `--adm-*` below),
|
|
196
196
|
so one <Button> looks native everywhere without hard-coding a palette.
|
|
197
|
-
|
|
197
|
+
══ */
|
|
198
198
|
|
|
199
199
|
:root {
|
|
200
200
|
--ui-surface: #ffffff;
|
|
@@ -229,10 +229,15 @@
|
|
|
229
229
|
--ui-space-2: 8px;
|
|
230
230
|
--ui-space-3: 12px;
|
|
231
231
|
--ui-space-4: 16px;
|
|
232
|
-
/*
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
/* Polaris' two heights under a mouse (0.12.0): a FIELD (gemboss-field, ui-select) is TextField/Select, 32px; a BUTTON
|
|
233
|
+
(ui-btn, ui-seg, ui-tabs) is Button, 28px. A toolbar row is 32 + 28, as in the admin. Under a thumb every control is
|
|
234
|
+
44px (the coarse block below; kit-plain.css adds phone WIDTH for non-admin surfaces). */
|
|
235
235
|
--ui-control-h: 32px;
|
|
236
|
+
--ui-btn-h: 28px;
|
|
237
|
+
/* Polaris Card (13.9.5): no grey frame of its own, --p-shadow-100 under it. The edge is ONE 1px line in the surface's
|
|
238
|
+
--ui-border (Polaris paints a bevel inset on a ::before; a border cannot be covered by a full-bleed child, and it
|
|
239
|
+
follows a surface's dark --ui-border, which a light bevel would not). */
|
|
240
|
+
--ui-card-shadow: 0 1px 0 rgba(26,26,26,.07);
|
|
236
241
|
--ui-fs-body: 13px;
|
|
237
242
|
--ui-fs-caption: 12px;
|
|
238
243
|
/* Content column width for the seller app shell — ONE source of truth for every wide surface
|
|
@@ -288,16 +293,17 @@
|
|
|
288
293
|
--ui-warning-fg: color-mix(in srgb, #c99a2e 70%, #ffffff);
|
|
289
294
|
--ui-danger-fg: color-mix(in srgb, var(--gemboss-danger-strong) 70%, #ffffff);
|
|
290
295
|
--ui-focus: var(--adm-accent);
|
|
296
|
+
--ui-card-shadow: none;
|
|
291
297
|
}
|
|
292
298
|
|
|
293
|
-
/* ── Button
|
|
299
|
+
/* ── Button ──
|
|
294
300
|
inline-flex + center so a <button> and an <a> render identically (anchors ignore
|
|
295
301
|
`height` as inline elements — the root cause of the misaligned link-buttons). */
|
|
296
302
|
.ui-btn {
|
|
297
303
|
display: inline-flex; align-items: center; justify-content: center; gap: var(--ui-space-2);
|
|
298
|
-
height: var(--ui-
|
|
304
|
+
height: var(--ui-btn-h); padding: 0 var(--ui-space-3); box-sizing: border-box;
|
|
299
305
|
line-height: 1; white-space: nowrap; vertical-align: middle;
|
|
300
|
-
border-radius: var(--ui-radius-md); font-family: inherit; font-size: var(--ui-fs-
|
|
306
|
+
border-radius: var(--ui-radius-md); font-family: inherit; font-size: var(--ui-fs-caption); font-weight: 550;
|
|
301
307
|
cursor: pointer; text-decoration: none; user-select: none;
|
|
302
308
|
border: 1px solid var(--ui-border); background: transparent; color: var(--ui-text);
|
|
303
309
|
transition: background .12s ease, border-color .12s ease;
|
|
@@ -309,33 +315,33 @@
|
|
|
309
315
|
.ui-btn--primary:hover { background: var(--ui-accent-hover); border-color: var(--ui-accent-hover); }
|
|
310
316
|
.ui-btn--ghost { border-color: transparent; background: transparent; color: var(--ui-text-muted); }
|
|
311
317
|
.ui-btn--ghost:hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
312
|
-
|
|
318
|
+
/* `ui-btn--sm`: the default size under a mouse since 0.12.0; it only marks a button the admin keeps compact under a finger. */
|
|
313
319
|
|
|
314
|
-
/* ── Card
|
|
320
|
+
/* ── Card ── */
|
|
315
321
|
.ui-card {
|
|
316
322
|
background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg);
|
|
317
|
-
padding: var(--ui-space-4); color: var(--ui-text);
|
|
323
|
+
box-shadow: var(--ui-card-shadow); padding: var(--ui-space-4); color: var(--ui-text);
|
|
318
324
|
}
|
|
319
|
-
.ui-card__title { font-size: 14px; font-weight:
|
|
325
|
+
.ui-card__title { font-size: 14px; line-height: 20px; font-weight: 650; margin: 0 0 var(--ui-space-2); }
|
|
320
326
|
/* Đầu thẻ có nút: `<div class="ui-card__head"><h2 class="ui-card__title">…</h2><button class="ui-btn">…</button></div>`.
|
|
321
327
|
Tiêu đề trái, hành động phải, xuống dòng khi hẹp (01/10/2026: partner-radar rel-head / agr). */
|
|
322
328
|
.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); }
|
|
323
329
|
.ui-card__head > .ui-card__title { margin: 0; }
|
|
324
330
|
.ui-card__head > :last-child:not(.ui-card__title) { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); }
|
|
325
331
|
|
|
326
|
-
/* ── Select
|
|
332
|
+
/* ── Select ──
|
|
327
333
|
Native <select> reskinned; custom caret as an inline SVG data-URI (caret color
|
|
328
334
|
uses the muted token via %-encoding fallback).
|
|
329
335
|
Its BOX is the text field's (29/09/2026, Chris): same --gemboss-field-* tokens for ground, line and
|
|
330
336
|
ink, same hover, same focus ring as `.gemboss-field`. Before, a grey --ui-surface-2 select stood next
|
|
331
337
|
to a bordered white field in the same form (gempress editor, admin forms), and `:focus` removed the
|
|
332
338
|
outline and only recoloured the border, so a keyboard user saw almost nothing. Height is
|
|
333
|
-
--ui-control-h, the
|
|
339
|
+
--ui-control-h, the field's (Polaris Select, 32): a field, not a button (28, --ui-btn-h). */
|
|
334
340
|
.ui-select {
|
|
335
341
|
height: var(--ui-control-h); padding: 0 32px 0 12px; max-width: 100%;
|
|
336
342
|
background: var(--gemboss-field-bg) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 20 20'><path fill='%239a9aa4' d='M5 7l5 5 5-5z'/></svg>") no-repeat right 10px center;
|
|
337
343
|
border: 1px solid var(--gemboss-field-line); border-radius: var(--ui-radius-md);
|
|
338
|
-
color: var(--gemboss-field-ink); font-family: inherit; font-size: var(--ui-fs-body); font-weight:
|
|
344
|
+
color: var(--gemboss-field-ink); font-family: inherit; font-size: var(--ui-fs-body); font-weight: 450;
|
|
339
345
|
appearance: none; -webkit-appearance: none; cursor: pointer;
|
|
340
346
|
}
|
|
341
347
|
.ui-select:hover:not(:disabled) {
|
|
@@ -350,13 +356,13 @@
|
|
|
350
356
|
.ui-select:disabled { opacity: .5; cursor: not-allowed; }
|
|
351
357
|
.ui-select--slim { height: 30px; font-size: var(--ui-fs-caption); }
|
|
352
358
|
|
|
353
|
-
/* ── Tag / Chip
|
|
359
|
+
/* ── Tag / Chip ──
|
|
354
360
|
Small inline labels. `solid` = filled muted pill (was gemboss-vault-tag); `chip` =
|
|
355
361
|
bordered mono token (was gemboss-vault-chip). `accent` tone for the highlighted state. */
|
|
356
362
|
.ui-tag {
|
|
357
363
|
display: inline-flex; align-items: center; gap: 6px;
|
|
358
|
-
font-size: var(--
|
|
359
|
-
padding:
|
|
364
|
+
font-size: var(--ui-fs-caption); line-height: 20px; font-weight: 450;
|
|
365
|
+
padding: 0 6px; border-radius: var(--ui-radius-md); background: var(--ui-surface-2); color: var(--ui-text-muted);
|
|
360
366
|
}
|
|
361
367
|
.ui-tag--accent { background: var(--adm-accent-soft, rgba(20,22,26,.05)); color: var(--adm-accent-fg, var(--ui-accent)); }
|
|
362
368
|
.ui-tag--chip {
|
|
@@ -390,13 +396,13 @@
|
|
|
390
396
|
|
|
391
397
|
/* ── Chữ phụ: `<p class="ui-muted">`, `<span class="ui-muted">` — mô tả, chú thích, dòng meta. Cỡ caption, màu muted.
|
|
392
398
|
Trước đây mỗi surface tự viết `.muted` (partner-radar: 180 chỗ). */
|
|
393
|
-
.ui-muted { color: var(--ui-text-muted); font-size: var(--
|
|
399
|
+
.ui-muted { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
|
|
394
400
|
|
|
395
401
|
/* ── Tiêu đề trang: `<div class="ui-page-head"><h1>…</h1><p>…</p></div>`. Đưa lên NGUYÊN VĂN từ admin
|
|
396
|
-
`.gemboss-admin-page-head` (01/10/2026, Chris: một chủ sở hữu; admin dùng bản này).
|
|
397
|
-
và không dùng flex:
|
|
402
|
+
`.gemboss-admin-page-head` (01/10/2026, Chris: một chủ sở hữu; admin dùng bản này). Không đặt margin cho h1
|
|
403
|
+
và không dùng flex: margin 0 đến từ reset (Polaris ở admin, app-shell.css trong shell của bề mặt từ 0.12.0). */
|
|
398
404
|
.ui-page-head { margin-bottom: 20px; }
|
|
399
|
-
.ui-page-head h1 { color: var(--ui-text-strong); font-size:
|
|
405
|
+
.ui-page-head h1 { color: var(--ui-text-strong); font-size: 20px; line-height: 24px; font-weight: 700; letter-spacing: -0.01em; } /* own line-height: the 20px body line overlapped a wrapped title (0.5.1) */
|
|
400
406
|
.ui-page-head p { color: var(--ui-text-muted); font-size: var(--ui-fs-body); margin-top: 4px; }
|
|
401
407
|
|
|
402
408
|
/* ── 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.
|
|
@@ -414,7 +420,7 @@ a.ui-metric { text-decoration: none; color: inherit; }
|
|
|
414
420
|
a.ui-metric:hover { border-color: var(--ui-text-muted); }
|
|
415
421
|
a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
|
|
416
422
|
.ui-metric__val { font-size: 17px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
|
|
417
|
-
.ui-metric__lbl { font-size: var(--
|
|
423
|
+
.ui-metric__lbl { font-size: var(--ui-fs-caption); font-weight: 500; color: var(--ui-text-muted); }
|
|
418
424
|
.ui-metric--success { border-color: color-mix(in srgb, var(--gemboss-success) 40%, transparent); }
|
|
419
425
|
.ui-metric--success .ui-metric__val { color: var(--ui-success-fg); }
|
|
420
426
|
.ui-metric--warning { border-color: color-mix(in srgb, #c99a2e 40%, transparent); }
|
|
@@ -426,7 +432,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
426
432
|
Lưới tự giãn: mỗi ô ≥ 160px, xuống dòng khi hẹp. */
|
|
427
433
|
.ui-dl { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px 16px; margin: 0; }
|
|
428
434
|
.ui-dl > div { min-width: 0; }
|
|
429
|
-
.ui-dl dt { font-size: var(--
|
|
435
|
+
.ui-dl dt { font-size: var(--ui-fs-caption); color: var(--ui-text-muted); }
|
|
430
436
|
.ui-dl dd { margin: 4px 0 0; font-weight: 600; color: var(--ui-text); overflow-wrap: anywhere; }
|
|
431
437
|
|
|
432
438
|
/* ── 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
|
|
@@ -443,22 +449,22 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
443
449
|
.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(--ui-fs-body); }
|
|
444
450
|
.ui-pager > a:hover { background: var(--ui-surface-2); }
|
|
445
451
|
.ui-pager > a:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
|
|
446
|
-
.ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--
|
|
452
|
+
.ui-pager > [aria-current="page"] { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); }
|
|
447
453
|
|
|
448
|
-
/* ── Badge
|
|
449
|
-
Polaris' Badge (02/10/2026; was an uppercase mono pill): 12px/550, padding 2/8, radius 8, the secondary
|
|
454
|
+
/* ── Badge ──
|
|
455
|
+
Polaris' Badge (02/10/2026; was an uppercase mono pill): 12px/450 (its text span; 550 was the box), padding 2/8, radius 8, the secondary
|
|
450
456
|
fill. The admin's /admin console draws it too (components/ui/Badge.tsx). Tones (`--success|--warning|--critical|
|
|
451
457
|
--info`, `--dot`) are in kit-plain.css, for the non-React surfaces; the seller app uses Polaris <Badge tone>. */
|
|
452
458
|
.ui-badge {
|
|
453
459
|
display: inline-flex; align-items: center; gap: 4px;
|
|
454
|
-
font-size: var(--ui-fs-caption); line-height: 16px; font-weight:
|
|
460
|
+
font-size: var(--ui-fs-caption); line-height: 16px; font-weight: 450; white-space: nowrap;
|
|
455
461
|
padding: 2px 8px; border-radius: var(--ui-radius-md); background: color-mix(in srgb, var(--ui-text) 8%, transparent); color: var(--ui-text-muted);
|
|
456
462
|
} /* a tint of the ink, not --ui-surface-2: that is the table row hover, and a badge vanished on hover (0.5.1) */
|
|
457
463
|
|
|
458
|
-
/* ── Modal
|
|
464
|
+
/* ── Modal ──
|
|
459
465
|
Rendered INLINE (not portaled): a portal escapes the .gemboss-admin-shell token
|
|
460
466
|
scope and would render light. Kept in-tree so --ui-* resolves to the surface.
|
|
461
|
-
0.7.3: Polaris Modal's density (#1099, README "Popup density").
|
|
467
|
+
0.7.3: Polaris Modal's density (#1099, README "Popup density"). Buttons are 28 everywhere since 0.12.0 (--ui-btn-h). */
|
|
462
468
|
.ui-overlay {
|
|
463
469
|
position: fixed; inset: 0; z-index: 500;
|
|
464
470
|
background: rgba(0, 0, 0, 0.5);
|
|
@@ -491,7 +497,6 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
491
497
|
display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end;
|
|
492
498
|
padding: 12px 16px; border-top: 1px solid var(--ui-border);
|
|
493
499
|
}
|
|
494
|
-
:is(.ui-modal__head, .ui-modal__foot) .ui-btn { height: 28px; padding: 0 12px; font-size: var(--ui-fs-caption); }
|
|
495
500
|
/* 44px under a thumb or at phone width; -8px keeps the head 53. */
|
|
496
501
|
@media (pointer: coarse), (max-width: 47.9975em) {
|
|
497
502
|
.ui-modal__close { width: 44px; height: 44px; margin: -8px -8px -8px 0; }
|
|
@@ -530,15 +535,15 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
530
535
|
.ui-select:not(.ui-select--slim) { min-height: 44px; }
|
|
531
536
|
}
|
|
532
537
|
|
|
533
|
-
/* ── Segmented control
|
|
538
|
+
/* ── Segmented control ──
|
|
534
539
|
Grouped toggle: icon squares (.ui-seg button) or text tabs (.is-text). The
|
|
535
540
|
selected segment uses the accent-soft state. */
|
|
536
541
|
.ui-seg { display: inline-flex; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md); overflow: hidden; }
|
|
537
542
|
.ui-seg :is(button, a) {
|
|
538
|
-
display: inline-grid; place-items: center; width: calc(var(--ui-
|
|
543
|
+
display: inline-grid; place-items: center; width: calc(var(--ui-btn-h) - 2px); height: calc(var(--ui-btn-h) - 2px);
|
|
539
544
|
border: 0; background: transparent; color: var(--ui-text-muted); cursor: pointer; font-family: inherit;
|
|
540
545
|
}
|
|
541
|
-
.ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-
|
|
546
|
+
.ui-seg :is(button, a).is-text { width: auto; padding: 0 12px; font-size: var(--ui-fs-caption); font-weight: 550; }
|
|
542
547
|
.ui-seg :is(button, a) + :is(button, a) { border-left: 1px solid var(--ui-border); }
|
|
543
548
|
.ui-seg :is(button, a):hover { background: var(--ui-surface-2); color: var(--ui-text); }
|
|
544
549
|
.ui-seg :is(button, a):focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
|
|
@@ -548,7 +553,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
548
553
|
.ui-seg button.active { background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
|
|
549
554
|
.ui-seg svg { width: 16px; height: 16px; fill: currentColor; }
|
|
550
555
|
|
|
551
|
-
/* ── Stat chip
|
|
556
|
+
/* ── Stat chip ──
|
|
552
557
|
Count-led pill; clickable ones double as filters (active = accent-soft). */
|
|
553
558
|
.ui-stats { display: flex; flex-wrap: wrap; gap: var(--ui-space-2); margin-bottom: 24px; }
|
|
554
559
|
.ui-stat {
|
|
@@ -563,7 +568,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
563
568
|
.ui-stat.is-clickable.active { border-color: var(--ui-accent); background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
|
|
564
569
|
.ui-stat.is-clickable.active b { color: var(--ui-accent-strong); }
|
|
565
570
|
|
|
566
|
-
/* ── Toast / notification bar
|
|
571
|
+
/* ── Toast / notification bar ──
|
|
567
572
|
App-wide "done / failed" pill for async actions (delete/duplicate/publish…).
|
|
568
573
|
Polaris-style: dark pill, bottom-center, error variant in critical red. Lives
|
|
569
574
|
in <ToastProvider> (components/ui/Toast.tsx), portaled to <body>. */
|
|
@@ -606,7 +611,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
606
611
|
}
|
|
607
612
|
@media (prefers-reduced-motion: reduce) { .gemboss-toast { animation: none; } }
|
|
608
613
|
|
|
609
|
-
/* ── The kit's controls under a thumb
|
|
614
|
+
/* ── The kit's controls under a thumb ──
|
|
610
615
|
* Measured by hit test on /admin/{theme-kernel,media,style-packs} at 390 and 1366 with a coarse
|
|
611
616
|
* pointer: segmented buttons 36x36, selects 30-36 tall, clickable stat tiles 29, the asset row's
|
|
612
617
|
* note button 30x30. §9.1 puts every one of them in the CONTROL tier (44px).
|
|
@@ -614,9 +619,11 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
614
619
|
* HERE rather than in each surface's stylesheet, because this is the shared primitive layer — the
|
|
615
620
|
* same reason the kit exists at all. The admin console and the seller app both read it.
|
|
616
621
|
*
|
|
617
|
-
* `pointer: coarse` only: a mouse keeps the density these surfaces were
|
|
618
|
-
* for every pointer is what #2147 did to a working width.
|
|
622
|
+
* The block below is `pointer: coarse` only: a mouse at desktop width keeps the density these surfaces were
|
|
623
|
+
* designed at. Growing a box for every pointer is what #2147 did to a working width.
|
|
619
624
|
*/
|
|
625
|
+
/* Phone WIDTH (< 768) with a mouse too (a narrow window): buttons and segments are 44 (0.12.0; at desktop width 28). */
|
|
626
|
+
@media (max-width: 47.9975em) { .ui-btn:not(.ui-btn--sm), .ui-seg :is(button, a) { min-height: 44px; } .ui-seg :is(button, a):not(.is-text) { min-width: 44px; } }
|
|
620
627
|
@media (pointer: coarse) {
|
|
621
628
|
/* NOT `.ui-textarea`: it already declares `min-height: 96px` earlier in this file, so adding it to
|
|
622
629
|
a 44px group LOWERS it — same specificity, later line. The note dialog's box would have gone from
|
|
@@ -643,7 +650,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
643
650
|
.ui-seg :is(button, a) { min-width: 44px; }
|
|
644
651
|
}
|
|
645
652
|
|
|
646
|
-
/* ── Skeleton
|
|
653
|
+
/* ── Skeleton ──
|
|
647
654
|
Vạch xám giữ chỗ cho nội dung chưa về. Ánh sáng chạy qua là gợi ý "đang chờ", không phải trang
|
|
648
655
|
trí — nên nó tắt hẳn với `prefers-reduced-motion`, giữ nguyên vạch tĩnh. */
|
|
649
656
|
.ui-skeleton { display: flex; flex-direction: column; gap: var(--ui-space-2); }
|
|
@@ -664,7 +671,7 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
|
|
|
664
671
|
|
|
665
672
|
}
|
|
666
673
|
|
|
667
|
-
/* ── Resizer: the seam you drag between two panels
|
|
674
|
+
/* ── Resizer: the seam you drag between two panels ──
|
|
668
675
|
One look for every split in the fleet: the admin's editors (chat | preview) today; made for gemcare's
|
|
669
676
|
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
|
|
670
677
|
placed strip) belongs to the surface. While a drag runs the surface puts `ui-resizing` on <body>
|
|
@@ -704,7 +711,7 @@ body.ui-resizing, body.ui-resizing * { cursor: col-resize !important; user-selec
|
|
|
704
711
|
pointer stream is never captured by a cross-document frame (the classic "stuck cursor"). */
|
|
705
712
|
body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999; cursor: col-resize; }
|
|
706
713
|
|
|
707
|
-
/* ── Tabs · Table · Empty state
|
|
714
|
+
/* ── Tabs · Table · Empty state ──
|
|
708
715
|
Plain-CSS primitives for surfaces that are NOT React (gemcare's vanilla dashboard reads this
|
|
709
716
|
file through `npm run export:ui-css`). The React app has Polaris <Tabs>/<DataTable>/<EmptyState>
|
|
710
717
|
for these; nothing in apps/admin uses the classes below, and they restyle no existing component.
|
|
@@ -712,18 +719,18 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
|
|
|
712
719
|
|
|
713
720
|
Tabs: `<div class="ui-tabs" role="tablist"><button role="tab" aria-selected="true">…` — the
|
|
714
721
|
selected state IS `aria-selected`, so what a screen reader announces and what is painted cannot
|
|
715
|
-
disagree.
|
|
716
|
-
|
|
722
|
+
disagree. Polaris Tabs under a mouse (0.12.0): 28px, 12px/550; 8px apart, the §9.1 gap for targets under 44.
|
|
723
|
+
44px under a finger or at phone width (the rule after these). */
|
|
717
724
|
.ui-tabs {
|
|
718
|
-
display: flex; gap: var(--ui-space-
|
|
725
|
+
display: flex; gap: var(--ui-space-2); border-bottom: 1px solid var(--ui-border);
|
|
719
726
|
overflow-x: auto; scrollbar-width: none;
|
|
720
727
|
}
|
|
721
728
|
.ui-tabs > button,
|
|
722
729
|
.ui-tabs > a {
|
|
723
730
|
position: relative; flex: 0 0 auto; display: inline-flex; align-items: center; gap: var(--ui-space-2);
|
|
724
|
-
min-height:
|
|
731
|
+
min-height: var(--ui-btn-h); padding: 0 var(--ui-space-3); border: 0; background: transparent; cursor: pointer;
|
|
725
732
|
border-radius: var(--ui-radius-sm) var(--ui-radius-sm) 0 0;
|
|
726
|
-
font-family: inherit; font-size: var(--ui-fs-
|
|
733
|
+
font-family: inherit; font-size: var(--ui-fs-caption); font-weight: 550; white-space: nowrap;
|
|
727
734
|
color: var(--ui-text-muted);
|
|
728
735
|
transition: color var(--gemboss-transition-fast) ease, background-color var(--gemboss-transition-fast) ease;
|
|
729
736
|
}
|
|
@@ -734,13 +741,14 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
|
|
|
734
741
|
.ui-tabs > a { text-decoration: none; }
|
|
735
742
|
/* 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. */
|
|
736
743
|
.ui-tabs > a[aria-current="page"],
|
|
737
|
-
.ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong);
|
|
744
|
+
.ui-tabs > button[aria-selected="true"] { color: var(--ui-text-strong); }
|
|
738
745
|
.ui-tabs > a[aria-current="page"]::after,
|
|
739
746
|
.ui-tabs > button[aria-selected="true"]::after {
|
|
740
747
|
content: ""; position: absolute; left: var(--ui-space-3); right: var(--ui-space-3); bottom: 0;
|
|
741
748
|
height: 2px; border-radius: var(--gemboss-radius-full); background: var(--ui-accent);
|
|
742
749
|
}
|
|
743
750
|
.ui-tabs > button:disabled { opacity: .5; cursor: not-allowed; }
|
|
751
|
+
@media (pointer: coarse), (max-width: 47.9975em) { .ui-tabs > :is(button, a) { min-height: 44px; } }
|
|
744
752
|
|
|
745
753
|
/* Table: wrap it — `<div class="ui-table-wrap"><table class="ui-table">` — so a wide table scrolls
|
|
746
754
|
inside its card instead of pushing the page sideways at 320px. Numeric cells take `.is-num`:
|
|
@@ -748,16 +756,21 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
|
|
|
748
756
|
.ui-table-wrap {
|
|
749
757
|
overflow-x: auto; max-width: 100%;
|
|
750
758
|
background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius-lg);
|
|
759
|
+
box-shadow: var(--ui-card-shadow);
|
|
751
760
|
}
|
|
752
761
|
.ui-table { width: 100%; border-collapse: collapse; font-size: var(--ui-fs-body); color: var(--ui-text); }
|
|
753
762
|
.ui-table th,
|
|
754
763
|
.ui-table td {
|
|
755
|
-
padding: var(--ui-space-2)
|
|
764
|
+
padding: var(--ui-space-2) 6px; text-align: left; vertical-align: middle;
|
|
756
765
|
border-bottom: 1px solid var(--ui-border);
|
|
757
766
|
}
|
|
758
|
-
.
|
|
759
|
-
|
|
760
|
-
}
|
|
767
|
+
/* Polaris DataTable (13.9.5, measured on /app/analytics 05/10/2026): cells 8px 6px (a row 36), header 12px 6px (45),
|
|
768
|
+
the first column 12px in from the edge. */
|
|
769
|
+
/* :where() keeps the edge columns at the base rule's weight, so a surface's own `.x-table td { padding }` still wins. */
|
|
770
|
+
.ui-table :is(th, td):where(:first-child) { padding-left: var(--ui-space-3); }
|
|
771
|
+
.ui-table :is(th, td):where(:last-child) { padding-right: var(--ui-space-3); }
|
|
772
|
+
.ui-table th { font-weight: 450; color: var(--ui-text-muted); white-space: nowrap; }
|
|
773
|
+
.ui-table thead th { padding-block: var(--ui-space-3); } /* the column header; a row header (tbody th) is a cell */
|
|
761
774
|
.ui-table tbody tr:last-child > td { border-bottom: 0; }
|
|
762
775
|
.ui-table tbody tr:hover { background: var(--ui-surface-2); }
|
|
763
776
|
.ui-table .is-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
@@ -766,7 +779,7 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
|
|
|
766
779
|
/* 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).
|
|
767
780
|
Chữ 13px cho hộp 17px; visual-gate đo đỏ ở partner-radar /competitors 01/10/2026. inline-flex giữ nó trên dòng. */
|
|
768
781
|
.ui-table td > a { display: inline-flex; align-items: center; min-height: 24px; }
|
|
769
|
-
.ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--
|
|
782
|
+
.ui-table .ui-sub { display: block; margin-top: 2px; font-size: var(--ui-fs-caption); color: var(--ui-text-muted); font-weight: 400; }
|
|
770
783
|
|
|
771
784
|
/* Empty state: `<div class="ui-empty"><p class="ui-empty__title">…</p><p class="ui-empty__body">…</p>
|
|
772
785
|
<button class="ui-btn">…</button></div>`. Says what is missing and what to do next. */
|
|
@@ -775,7 +788,7 @@ body.ui-resizing::after { content: ""; position: fixed; inset: 0; z-index: 9999;
|
|
|
775
788
|
padding: var(--ui-space-4); padding-block: 40px; text-align: center;
|
|
776
789
|
color: var(--ui-text-muted); font-size: var(--ui-fs-body);
|
|
777
790
|
}
|
|
778
|
-
.ui-empty__title { margin: 0; font-size:
|
|
791
|
+
.ui-empty__title { margin: 0; font-size: 14px; line-height: 20px; font-weight: 650; color: var(--ui-text-strong); }
|
|
779
792
|
.ui-empty__body { margin: 0; max-width: 48ch; line-height: 1.5; }
|
|
780
793
|
.ui-empty > .ui-btn { margin-top: var(--ui-space-2); }
|
|
781
794
|
|
|
@@ -2040,6 +2053,19 @@ body.gemboss-assistant-unread .gnav-item.gnav-assistant::after {
|
|
|
2040
2053
|
/* Headings get their own line (Polaris heading line-heights are ~1.25-1.4): inheriting the body's 20px made a wrapped 22px
|
|
2041
2054
|
* title overlap itself (gempress, 0.5.1). */
|
|
2042
2055
|
:where(h1, h2, h3) { letter-spacing: -0.01em; line-height: 1.3; }
|
|
2056
|
+
/* Polaris' heading reset (0.12.0, 05/10/2026): in the admin an <h2> or a <p> with no class is body text with no margin,
|
|
2057
|
+
* because Polaris' global sheet sets `h1…h6, p { margin: 0; font-size: 1em; font-weight: regular }` and every heading
|
|
2058
|
+
* that looks like one is a Text variant. Surfaces had the browser's instead: a bare <h2> at 19.5px bold with 16px margins
|
|
2059
|
+
* sat in GemFactory and taskdesk pages beside kit cards titled 14px. Here, only INSIDE the shell (`.gemboss-platform-
|
|
2060
|
+
* shell`, the AppShell root every surface on the kit renders): a login page or a public page keeps its own type. Never
|
|
2061
|
+
* inside an article the surface shows: `.ui-prose` or `[data-content]` (the contract), and the wrappers the fleet already
|
|
2062
|
+
* uses for rendered HTML, read on 05/10/2026, by exact name (gemcommunity `md-body` and `recap-md`,
|
|
2063
|
+
* content-radar `article-body`, the assistants' `asst-md`; any class containing `prose` or `markdown`, any case, so ProseMirror too). A NEW wrapper: mark it `ui-prose` or
|
|
2064
|
+
* `data-content`. scripts/density.mjs skips exactly this list (ARTICLE); density.test.ts fails if the two differ. A post body keeps the browser's scale. Only an element WITHOUT a class: a surface's `h2.gf-title` that sets only its size keeps the
|
|
2065
|
+
* browser's bold and margins (a zero-specificity author rule still beats the browser's own sheet, review #3787). */
|
|
2066
|
+
:where(.gemboss-platform-shell) :where(h1, h2, h3, h4, h5, h6, p):where(:not([class])):not(:where(.ui-prose, [data-content], [class*="prose" i], [class*="markdown" i], .md-body, .recap-md, .asst-md, .article-body) *) {
|
|
2067
|
+
margin: 0; font-size: 1em; font-weight: inherit; line-height: inherit;
|
|
2068
|
+
}
|
|
2043
2069
|
|
|
2044
2070
|
.gemboss-topbar-start { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
2045
2071
|
.gemboss-topbar-center { display: flex; justify-content: center; min-width: 0; }
|
|
@@ -3449,8 +3475,7 @@ html[data-gb-navigating] .gb-route-cover {
|
|
|
3449
3475
|
|
|
3450
3476
|
Key-value list: `<dl class="ui-kv"><dt>Owner</dt><dd>Lily</dd>…</dl>` — label column sized to
|
|
3451
3477
|
its longest label, value column takes the rest and wraps long keys and URLs. */
|
|
3452
|
-
.ui-btn--icon { flex: none; width: var(--ui-
|
|
3453
|
-
.ui-btn--icon.ui-btn--sm { width: 28px; }
|
|
3478
|
+
.ui-btn--icon { flex: none; width: var(--ui-btn-h); padding: 0; }
|
|
3454
3479
|
.ui-btn--icon svg { width: 18px; height: 18px; }
|
|
3455
3480
|
/* Menu: the SURFACE is the shell's `gemboss-popover-surface` and the rows are the account menu's row (shell.css
|
|
3456
3481
|
declares `.gemboss-account-menu-item, .ui-menu__item` together), so a kit menu and the account menu on one page
|
|
@@ -3516,7 +3541,23 @@ html[data-gb-navigating] .gb-route-cover {
|
|
|
3516
3541
|
.ui-link:focus-visible,
|
|
3517
3542
|
.ui-prose :where(a:not([class])):focus-visible { outline: 2px solid var(--ui-focus, #5661f5); outline-offset: 2px; border-radius: 2px; }
|
|
3518
3543
|
|
|
3519
|
-
.ui-card--media { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
|
|
3544
|
+
.ui-card--media { position: relative; padding: 0; overflow: hidden; display: flex; flex-direction: column; }
|
|
3545
|
+
/* 0.13.0 (README "Collections", card grid): the title is the card's link and its overlay covers the card
|
|
3546
|
+
(`a.ui-card__link`; the picture link is aria-hidden, out of the tab order); `.ui-card__menu` (a ⋯ ui-menu) sits in the
|
|
3547
|
+
picture's corner above it, and the card stops clipping so the open menu is not cut off; `data-ratio="4/3"` for a theme
|
|
3548
|
+
or a page. */
|
|
3549
|
+
.ui-card__link { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; color: var(--ui-text); font-size: var(--ui-fs-body); line-height: 20px; font-weight: 650; text-decoration: none; overflow-wrap: anywhere; }
|
|
3550
|
+
.ui-card__link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
|
|
3551
|
+
.ui-card__link:focus-visible { outline: none; }
|
|
3552
|
+
.ui-card--media:has(.ui-card__link) { cursor: pointer; }
|
|
3553
|
+
.ui-card--media:has(.ui-card__link:hover) { border-color: var(--ui-text-muted); }
|
|
3554
|
+
.ui-card--media:has(.ui-card__link:focus-visible) { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
|
|
3555
|
+
.ui-card--media :is(.ui-card__foot button, .ui-card__foot a, .ui-card__body a:not(.ui-card__link), .ui-card__body button, select, input, summary) { position: relative; z-index: 1; }
|
|
3556
|
+
.ui-card__menu { position: absolute; top: 8px; right: 8px; z-index: 2; }
|
|
3557
|
+
.ui-card--media:has(> .ui-card__menu) { overflow: visible; }
|
|
3558
|
+
.ui-card--media:has(> .ui-card__menu) > .ui-card__media { border-radius: inherit; border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
|
3559
|
+
.ui-card__meta { color: var(--ui-text-muted); font-size: var(--ui-fs-caption); line-height: 16px; }
|
|
3560
|
+
.ui-card--media[data-ratio="4/3"] > .ui-card__media { aspect-ratio: 4 / 3; }
|
|
3520
3561
|
.ui-card__media {
|
|
3521
3562
|
display: block; width: 100%; aspect-ratio: 16 / 10; margin: 0; padding: 0; border: 0;
|
|
3522
3563
|
border-bottom: 1px solid var(--ui-border); background: var(--ui-surface-2); overflow: hidden; color: inherit;
|
|
@@ -3541,8 +3582,6 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
|
|
|
3541
3582
|
</div><div class="ui-modal__actions"><button class="ui-btn">Copy</button></div><button class="ui-modal__close">…` */
|
|
3542
3583
|
.ui-modal__subtitle { display: block; margin-top: 2px; font-size: var(--ui-fs-caption); line-height: 16px; font-weight: 450; color: var(--ui-text-muted); overflow-wrap: anywhere; }
|
|
3543
3584
|
.ui-modal__actions { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-space-2); }
|
|
3544
|
-
/* An icon button in the popup's head or foot is square at the popup's 28 (ui.css gives those buttons 0/12 padding). */
|
|
3545
|
-
:is(.ui-modal__head, .ui-modal__foot) .ui-btn--icon { width: 28px; padding: 0; }
|
|
3546
3585
|
/* A popup taller than the screen: the body scrolls, the head and foot stay, so the actions are always reachable
|
|
3547
3586
|
(320×568 with a long form put Huỷ/Xoá below the fold, unreachable under the locked page; review #3633). */
|
|
3548
3587
|
.ui-overlay > .ui-modal { display: flex; flex-direction: column; max-height: calc(100dvh - 40px); }
|
|
@@ -3578,8 +3617,8 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
|
|
|
3578
3617
|
dưới cùng (delta + so với gì). */
|
|
3579
3618
|
.ui-metrics--grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--ui-space-3); margin: 0; }
|
|
3580
3619
|
.ui-metric--lg { gap: 4px; padding: var(--ui-space-3) var(--ui-space-4); }
|
|
3581
|
-
.ui-metric--lg .ui-metric__val { font-size:
|
|
3582
|
-
.ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--
|
|
3620
|
+
.ui-metric--lg .ui-metric__val { font-size: 24px; line-height: 32px; } /* Polaris headingXl, as the admin's analytics cards */
|
|
3621
|
+
.ui-metric--lg .ui-metric__lbl { order: -1; font-size: var(--ui-fs-body); } /* bodyMd 13 (0.12: was 14) */
|
|
3583
3622
|
.ui-metric__foot { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
|
|
3584
3623
|
@media (max-width: 520px) { .ui-metrics--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
3585
3624
|
|
|
@@ -3627,7 +3666,7 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
|
|
|
3627
3666
|
sau bước đầu mang `__rate` = tỉ lệ đi tiếp từ bước NGAY TRƯỚC (bề mặt tính). Ngang ở khổ rộng, dọc ở ≤ 520px. */
|
|
3628
3667
|
.ui-funnel { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--ui-space-2); margin: 0; padding: 0; list-style: none; }
|
|
3629
3668
|
.ui-funnel > li { position: relative; display: flex; flex-direction: column; gap: 2px; padding: var(--ui-space-3); border-radius: var(--ui-radius-md); background: var(--ui-surface-2); min-width: 0; }
|
|
3630
|
-
.ui-funnel__val { font-size:
|
|
3669
|
+
.ui-funnel__val { font-size: 20px; font-weight: 700; color: var(--ui-text); font-variant-numeric: tabular-nums; line-height: 24px; } /* Polaris headingLg */
|
|
3631
3670
|
.ui-funnel__lbl { font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); overflow-wrap: anywhere; }
|
|
3632
3671
|
.ui-funnel__rate { order: -1; font-size: var(--gemboss-fs-caption); font-weight: 600; color: var(--ui-series-1); font-variant-numeric: tabular-nums; }
|
|
3633
3672
|
@media (max-width: 520px) { .ui-funnel { grid-auto-flow: row; } }
|
|
@@ -3642,7 +3681,7 @@ button.ui-card__media, a.ui-card__media { cursor: pointer; }
|
|
|
3642
3681
|
@media (max-width: 960px) { .ui-grid--2 { grid-template-columns: minmax(0, 1fr); } }
|
|
3643
3682
|
@media (max-width: 720px) { .ui-grid--3 { grid-template-columns: minmax(0, 1fr); } }
|
|
3644
3683
|
.ui-section-head { margin: var(--ui-space-4) 0 0; }
|
|
3645
|
-
.ui-section-head > h2 { margin: 0; font-size:
|
|
3684
|
+
.ui-section-head > h2 { margin: 0; font-size: 14px; line-height: 20px; font-weight: 650; color: var(--ui-text); } /* Polaris headingMd (0.12: was 16) */
|
|
3646
3685
|
.ui-section-head > p { margin: 2px 0 0; font-size: var(--gemboss-fs-caption); color: var(--ui-text-muted); }
|
|
3647
3686
|
|
|
3648
3687
|
/* Nhóm nút CHỮ không bao giờ bẻ chữ (03/10/2026, thanh lọc của gemnews ở 390px: "Mọi ngôn ngữ" thành hai dòng, "中文"
|
|
@@ -3892,6 +3931,26 @@ button.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-off
|
|
|
3892
3931
|
/* Phone: 11 targets of 44px wrapped into 3-4 rows at 320 (measured). Keep Previous · current · Next; the range wraps under. */
|
|
3893
3932
|
@media (max-width: 35.9975em) { .ui-pager > a:not([rel]):not([aria-current]):not([aria-disabled]), .ui-pager__gap { display: none; } }
|
|
3894
3933
|
|
|
3934
|
+
/* 0.12.0: filter chip and card stack, both drawn by hand on every surface the 05/10/2026 density audit read.
|
|
3935
|
+
Filter chip: `<div class="ui-chips"><button type="button" class="ui-chip" aria-pressed="true">Shopify Plus</button>
|
|
3936
|
+
<a class="ui-chip" href="?country=us" aria-current="page">US</a></div>`. Polaris FilterPill's shape (radius 8, a 1px line,
|
|
3937
|
+
dashed until applied) at the Button's density: 28px, 12px/550, so a filter row and a button row are one height.
|
|
3938
|
+
Applied = `aria-pressed="true"` (in place) or `aria-current="page"` (a URL), solid line and the selected tint. */
|
|
3939
|
+
.ui-chip {
|
|
3940
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 4px; box-sizing: border-box;
|
|
3941
|
+
height: var(--ui-btn-h, 28px); padding: 0 12px; border: 1px dashed var(--ui-border); border-radius: var(--ui-radius-md);
|
|
3942
|
+
background: var(--ui-surface); color: var(--ui-text); font: inherit; font-size: var(--ui-fs-caption); font-weight: 550;
|
|
3943
|
+
line-height: 16px; white-space: nowrap; text-decoration: none; cursor: pointer;
|
|
3944
|
+
}
|
|
3945
|
+
.ui-chip:hover { border-style: solid; background: var(--ui-surface-2); }
|
|
3946
|
+
.ui-chip:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 1px; }
|
|
3947
|
+
.ui-chip:is([aria-pressed="true"], [aria-current="page"], [aria-current="true"]) { border-style: solid; border-color: var(--ui-accent); background: var(--ui-accent-soft); color: var(--ui-accent-strong); }
|
|
3948
|
+
@media (pointer: coarse), (max-width: 47.9975em) { .ui-chip { height: auto; min-height: 44px; min-width: 44px; } }
|
|
3949
|
+
/* Card stack: `<div class="ui-stack"><div class="ui-card">…</div><div class="ui-card">…</div></div>`. 16px between blocks,
|
|
3950
|
+
Polaris BlockStack gap 400, the admin's spacing between cards on a page. `--tight` is 8 (inside a card). */
|
|
3951
|
+
.ui-stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ui-space-4); }
|
|
3952
|
+
.ui-stack--tight { gap: var(--ui-space-2); }
|
|
3953
|
+
|
|
3895
3954
|
/* ═══ @gemboss/ui src/chat.css ═══ */
|
|
3896
3955
|
/*
|
|
3897
3956
|
* The chat BODY — what an answer looks like, wherever a GemBoss surface puts a chat (01/10/2026).
|
|
@@ -4638,6 +4697,7 @@ dialog.ui-modal.gb-search { width: min(640px, 100%); margin-top: 8vh; max-height
|
|
|
4638
4697
|
--gemboss-danger-border: #5c2a24;
|
|
4639
4698
|
--ui-accent-fg: #0d0d12;
|
|
4640
4699
|
--ui-warning-fg: #e8c063;
|
|
4700
|
+
--ui-card-shadow: none; /* a dark drop under a dark card is noise */
|
|
4641
4701
|
--gb-brand-ink: #f2f3f5;
|
|
4642
4702
|
}
|
|
4643
4703
|
/* Avatar initials in dark: the tone's light end, so the text clears 4.5:1 on its tinted disc. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"./chat.css": "./src/chat.css",
|
|
22
22
|
"./chat-composer.css": "./src/chat-composer.css",
|
|
23
23
|
"./editor.css": "./src/editor.css",
|
|
24
|
+
"./collections.css": "./src/collections.css",
|
|
24
25
|
"./auth.css": "./src/auth.css",
|
|
25
26
|
"./kit-plain.css": "./src/kit-plain.css",
|
|
26
27
|
"./kit-plain.js": "./src/kit-plain.js",
|
package/src/app-shell.css
CHANGED
|
@@ -26,6 +26,19 @@
|
|
|
26
26
|
/* Headings get their own line (Polaris heading line-heights are ~1.25-1.4): inheriting the body's 20px made a wrapped 22px
|
|
27
27
|
* title overlap itself (gempress, 0.5.1). */
|
|
28
28
|
:where(h1, h2, h3) { letter-spacing: -0.01em; line-height: 1.3; }
|
|
29
|
+
/* Polaris' heading reset (0.12.0, 05/10/2026): in the admin an <h2> or a <p> with no class is body text with no margin,
|
|
30
|
+
* because Polaris' global sheet sets `h1…h6, p { margin: 0; font-size: 1em; font-weight: regular }` and every heading
|
|
31
|
+
* that looks like one is a Text variant. Surfaces had the browser's instead: a bare <h2> at 19.5px bold with 16px margins
|
|
32
|
+
* sat in GemFactory and taskdesk pages beside kit cards titled 14px. Here, only INSIDE the shell (`.gemboss-platform-
|
|
33
|
+
* shell`, the AppShell root every surface on the kit renders): a login page or a public page keeps its own type. Never
|
|
34
|
+
* inside an article the surface shows: `.ui-prose` or `[data-content]` (the contract), and the wrappers the fleet already
|
|
35
|
+
* uses for rendered HTML, read on 05/10/2026, by exact name (gemcommunity `md-body` and `recap-md`,
|
|
36
|
+
* content-radar `article-body`, the assistants' `asst-md`; any class containing `prose` or `markdown`, any case, so ProseMirror too). A NEW wrapper: mark it `ui-prose` or
|
|
37
|
+
* `data-content`. scripts/density.mjs skips exactly this list (ARTICLE); density.test.ts fails if the two differ. A post body keeps the browser's scale. Only an element WITHOUT a class: a surface's `h2.gf-title` that sets only its size keeps the
|
|
38
|
+
* browser's bold and margins (a zero-specificity author rule still beats the browser's own sheet, review #3787). */
|
|
39
|
+
:where(.gemboss-platform-shell) :where(h1, h2, h3, h4, h5, h6, p):where(:not([class])):not(:where(.ui-prose, [data-content], [class*="prose" i], [class*="markdown" i], .md-body, .recap-md, .asst-md, .article-body) *) {
|
|
40
|
+
margin: 0; font-size: 1em; font-weight: inherit; line-height: inherit;
|
|
41
|
+
}
|
|
29
42
|
|
|
30
43
|
.gemboss-topbar-start { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
31
44
|
.gemboss-topbar-center { display: flex; justify-content: center; min-width: 0; }
|