@gemboss/ui 0.7.2 → 0.8.1

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/src/page.tsx ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * How wide a page's content may grow right of the rail (0.8.1). The admin's tiers, same numbers:
3
+ * default → `--ui-content-max` (900px): lists, tables, card grids, dashboards. Admin `.gemboss-w-gallery` / `-dashboard`.
4
+ * feed → 720px: something read top to bottom (activity, a log, a timeline). Admin `.gemboss-w-feed`.
5
+ * form → 662px: one column of fields (settings). Admin `.gemboss-w-form`, Polaris' narrowWidth.
6
+ * canvas → 1440px: content that is WIDER than a column of text and was measured to need it (a horizontal path,
7
+ * a wide comparison table). Admin `.gemboss-w-canvas`.
8
+ * full → no cap: a board or an editor that is the whole screen. Say why at the call site.
9
+ * The CSS is `.ui-page` + `.ui-page--<tier>` in app-shell.css; HTML surfaces write the class themselves.
10
+ *
11
+ * Its OWN module, not shell.tsx, and not "use client": the admin imports Shell / ShellMain / Rail* from shell.tsx, a
12
+ * client module the bundler keeps whole, and its first-load shell budget had 0.0 KB of room (240.0 / 240, 04/10/2026).
13
+ * In shell.tsx this code reached the admin's first load; here nothing the admin imports pulls it in. No state, so a
14
+ * server component can render it too.
15
+ */
16
+ import React from "react";
17
+
18
+ export type PageWidth = "default" | "feed" | "form" | "canvas" | "full";
19
+
20
+ /** `ui-page` plus the tier's modifier: for a surface that renders the wrapper itself (a server template). */
21
+ export function pageWidthClass(width: PageWidth = "default"): string {
22
+ return width === "default" ? "ui-page" : `ui-page ui-page--${width}`;
23
+ }
24
+
25
+ /**
26
+ * `div.ui-page`: the content column, capped at `width`'s tier and centred in the space right of the rail. Put ONE
27
+ * directly inside `AppShell`, with the CURRENT route's tier, so every screen is capped in one place:
28
+ * `<AppShell …><PageContainer width={route === "settings" ? "form" : "default"}>{page}</PageContainer></AppShell>`.
29
+ * A page cannot widen a cap its parent set, so do not nest a second one to get `canvas` or `full`.
30
+ */
31
+ export function PageContainer({ as: Comp = "div", width = "default", className, children, ...rest }:
32
+ { as?: React.ElementType; width?: PageWidth; className?: string; children?: React.ReactNode }
33
+ & Omit<React.HTMLAttributes<HTMLElement>, "className" | "children">) {
34
+ return <Comp className={className ? `${pageWidthClass(width)} ${className}` : pageWidthClass(width)} {...rest}>{children}</Comp>;
35
+ }
package/src/ui.css CHANGED
@@ -274,10 +274,11 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
274
274
 
275
275
  /* ── Modal ────────────────────────────────────────────────────────────────────
276
276
  Rendered INLINE (not portaled): a portal escapes the .gemboss-admin-shell token
277
- scope and would render light. Kept in-tree so --ui-* resolves to the surface. */
277
+ scope and would render light. Kept in-tree so --ui-* resolves to the surface.
278
+ 0.7.3: Polaris Modal's density (#1099, README "Popup density"). Popup only: --ui-control-h stays 32. */
278
279
  .ui-overlay {
279
280
  position: fixed; inset: 0; z-index: 500;
280
- background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(2px);
281
+ background: rgba(0, 0, 0, 0.5);
281
282
  display: grid; place-items: center; padding: 20px;
282
283
  }
283
284
  .ui-modal {
@@ -287,30 +288,39 @@ a.ui-metric:focus-visible { outline: 2px solid var(--ui-focus); outline-offset:
287
288
  }
288
289
  /* Head = title + the one close button every modal used to draw for itself. */
289
290
  .ui-modal__head {
290
- display: flex; align-items: flex-start; gap: 16px;
291
- padding: 16px 20px; border-bottom: 1px solid var(--ui-border);
292
- font-size: 15px; font-weight: 620; color: var(--ui-text);
291
+ display: flex; align-items: center; gap: 12px;
292
+ padding: 12px 16px; border-bottom: 1px solid var(--ui-border);
293
+ font-size: 14px; line-height: 20px; font-weight: 650; color: var(--ui-text);
293
294
  }
294
295
  .ui-modal__title { min-width: 0; flex: 1 1 auto; }
295
296
  .ui-modal__close {
296
297
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
297
- width: 44px; height: 44px; margin: -8px -8px -8px 0; /* a 44px target that does not grow the head */
298
+ width: 28px; height: 28px; padding: 0;
298
299
  border: 0; border-radius: var(--ui-radius-md, 8px); background: transparent;
299
300
  color: var(--ui-text-muted); cursor: pointer;
300
301
  }
301
302
  .ui-modal__close svg { width: 20px; height: 20px; }
302
303
  .ui-modal__close:hover { background: var(--ui-surface-2); color: var(--ui-text); }
303
304
  .ui-modal__close:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: -2px; }
305
+ /* Here, not in kit-plain.css: the admin loads only this file (0.7.3). */
306
+ .ui-modal__body { padding: 16px; }
307
+ .ui-modal__foot {
308
+ display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end;
309
+ padding: 12px 16px; border-top: 1px solid var(--ui-border);
310
+ }
311
+ :is(.ui-modal__head, .ui-modal__foot) .ui-btn { height: 28px; padding: 0 12px; font-size: var(--ui-fs-caption); }
312
+ /* 44px under a thumb or at phone width; -8px keeps the head 53. */
313
+ @media (pointer: coarse), (max-width: 47.9975em) {
314
+ .ui-modal__close { width: 44px; height: 44px; margin: -8px -8px -8px 0; }
315
+ :is(.ui-modal__head, .ui-modal__foot) .ui-btn { min-height: 44px; }
316
+ }
304
317
 
305
- /* `size="lg"` — the screen-shaped dialog. Only the HOOK lives here: this sheet is imported by the
306
- root layout, so every rule in it is paid on every screen, and the geometry of a dialog with one
307
- consumer belongs in that consumer's chunk (see SettingsDialog.module.css). */
318
+ /* `size="lg"`: only the HOOK here (this sheet is paid on every screen); its geometry is in
319
+ SettingsDialog.module.css, the one consumer's chunk. */
308
320
  .ui-modal--lg { position: relative; }
309
321
 
310
- /* The kit's multi-line field joins the ONE field family (01/10/2026, Chris: "form control đồng bộ"):
311
- same input-border token, hover and focus ring as `gemboss-field` and `.ui-select`. Before, it drew a
312
- lighter --ui-border box at 14px with no focus ring, so on GemFactory's compose form a textarea sat
313
- beside selects of a darker border and text fields of a third look (measured on prod). */
322
+ /* The kit's multi-line field is the ONE field family (01/10/2026, Chris: "form control đồng bộ"): same
323
+ border token, hover and focus ring as `gemboss-field` / `.ui-select` (it drew a third look on GemFactory). */
314
324
  .ui-textarea {
315
325
  width: 100%; min-height: 96px; resize: vertical; box-sizing: border-box;
316
326
  background: var(--p-color-input-bg-surface, var(--gemboss-field-bg));