@gemboss/ui 0.47.0 → 0.48.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 CHANGED
@@ -2,8 +2,63 @@
2
2
 
3
3
  Versions up to 0.9.0 are described in the README, each in the section that introduced it.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.48.0 (21/10/2026)
8
+
9
+ **LanguageSwitcher: what its first three surfaces had to work around** (21/10/2026; GemCommunity #333, GemNews #2,
10
+ GemFeedback #82).
11
+
12
+ Added
13
+ - `gembossUI.languageSwitcher()` (plain JS) returns `update({ value, labels, suggested, languages })` and
14
+ `setLabels(labels)`: the switcher changes where it is, no new element. The button's name and label, the menu's name and
15
+ the suggested hint change in place; the rows are drawn again only when `languages` or `suggested` change, and the row
16
+ that had focus has it again. An open menu stays open (placed again), focus stays where it was. `labels` given replace
17
+ those words only; `suggested: null` takes the row away. GemNews's `paintLang()` rebuilt the switcher (destroy + new,
18
+ focus carried by hand) every time its language changed. `set()`, `value()`, `open()`, `close()`, `destroy()` unchanged.
19
+ - `TopbarNav` `foot`: the app's own section at the foot of the FOLDED tab list, under a line (`.gb-topnav-foot`), and the
20
+ class `gb-topnav-unfolded` for its copy in the topbar's end, shown only while the tabs are unfolded. A portal topbar
21
+ (brand, tab menu, search, theme, Sign in) has no room for a 55px language button at 320–390: GemFeedback hid it and
22
+ printed links at the foot of the page. Now `foot={<LanguageSwitcher variant="inline" end={false} …/>}` plus
23
+ `<LanguageSwitcher className="gb-topnav-unfolded" …/>` in the end slot: one language control in the bar at every width.
24
+ - `TopbarNav` `foldAt={760}`: the tabs fold over the whole phone topbar (≤760px) instead of under 600px, for a bar whose end
25
+ holds more (GemFeedback's three tabs ran under the switcher at 601–687). Default 600, unchanged.
26
+ - `topnav-fold.css` (`@gemboss/ui/topnav-fold.css`), a new entry `TopbarNav` imports after `portal.css`: the rules for `foot`,
27
+ `foldAt`, `gb-topnav-unfolded` and the ellipsis below. `portal.css` is in the frozen `gemboss-ui.css`, which had 24 bytes
28
+ left under its ceiling. 0.3 KB gzip minified (ceiling 789 in `weight-budget.json`).
29
+
30
+ Changed
31
+ - The LanguageSwitcher's rows, hint and separator are one class (0,1,0) instead of zero specificity (`:where`), with the
32
+ core's values: an app's `button { font: inherit }` (0,0,1) beat the 13px rows (GemCommunity 14 / 16px, GemNews 16px; both
33
+ restated 13px). An app restyles them on purpose with one class of its own. The surface and the box stay `:where`.
34
+ - Every `--gemboss-*` token `language-switcher.css` reads has its light value as a fallback: a page with neither the core nor
35
+ `gemboss-tokens.css` draws the switcher right (light).
36
+ - `TopbarNav`'s folded button: the open tab's name sits in `span.gb-topnav-fold-label` and gives way with an ellipsis before
37
+ it runs under the topbar's end ("Changelog" at 320 overlapped the search button by 40px, with or without a switcher).
38
+
39
+ Fixed
40
+ - The switcher's tick touched the language's name on a page with app-shell.css and no core (GemCommunity's /welcome, for the
41
+ brand): app-shell.css's `.ui-menu__item` (0,1,0) reads `--ui-space-2` / `--ui-radius-sm`, unset there, and beat the row's
42
+ gap. The row's gap and corners are at two classes (`.gb-lang__item.ui-menu__item`, 8px / 6px). app-shell.css itself is
43
+ unchanged: the frozen gemboss-ui.css has 5 bytes left under its ceiling, and two fallbacks there cost 3.
44
+ - README: "the switcher fits the phone bar at 320–390" is AppShell's bar; a portal Topbar + TopbarNav puts it in the tab
45
+ menu (`foot`).
46
+
5
47
  ## 0.47.0 (21/10/2026)
6
48
 
49
+ **The kit's menu rows at the admin's density** (Chris 21/10/2026, of a menu drawn 44px tall: "nó đang to hơn bình thường";
50
+ 0.43.0 did this for the editor's menus).
51
+
52
+ Changed
53
+ - `.ui-menu__item` (`Menu` / `MenuItem`, kit-plain menus, the assistant header's ⋯) and `.gemboss-account-menu-item`
54
+ (`AccountMenu`, the admin's account corner included) under a mouse: a Polaris ActionList item, 32px tall with 4px padding
55
+ above and below (were 44px with 12px, for every pointer). A kit menu's rows sit 6px in from a list padded 6
56
+ (`.ui-menu__list`, `.gb-dock-menu-list`: were 4), as an ActionList item in its section; the account menu's rows keep 16px at
57
+ the sides, under its head. Under a finger or on a screen 767px or narrower: 44px with 12px 16px, as before. The row states
58
+ `box-sizing: border-box` itself. `language-switcher.css` repeats the core's row for a page without the core (gemboss.ai), so it
59
+ follows: list padded 6, rows 32 / 4px 6px under a mouse, 44 / 12px 16px under a finger. The density gate measures both (`menuItem`, `menuSurface` in polaris-reference.json), red
60
+ on 0.46's rows. Measured on the admin (local): the account menu's four rows 176px → 128px, the assistant's ⋯ rows 44 → 32.
61
+
7
62
  **A choice made by looking, and a logo sheet** (GemBoss Brand › Logo, 21/10/2026: the seller picks one of three AI logos from
8
63
  GemMedia, sees each the way it will be used, and confirms one).
9
64
 
package/README.md CHANGED
@@ -59,6 +59,7 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
59
59
  | auth | `@gemboss/ui/auth.css` | `packages/ui/src/auth.css` | the sign-in screen (+ the grid backdrop, 0.30.0) | 2.8 KB | `AuthLayout` & co. import it |
60
60
  | assistant | `@gemboss/ui/assistant.css` | `packages/ui/src/assistant.css` | the dock and the launcher | 2.2 KB | `AssistantHost`, the launcher import it |
61
61
  | portal | `@gemboss/ui/portal.css` | `packages/ui/src/portal.css` | `TopbarNav`, `SearchDialog`, `Avatar`, `Leaderboard`, `Podium`, the `ThemeToggle` button | 2.1 KB | those components import it |
62
+ | topnav-fold | `@gemboss/ui/topnav-fold.css` | `packages/ui/src/topnav-fold.css` | `TopbarNav`'s folded menu after the frozen bundle (unreleased): `foot`, `foldAt={760}`, `gb-topnav-unfolded`, the open tab's ellipsis | 0.3 KB | `TopbarNav` imports it (after portal.css) |
62
63
  | site | `@gemboss/ui/site.css` | `packages/ui/src/site.css` | the public site layer (0.40.0): the bar, the lockup at the site size, links, buttons, the footer | 1.0 KB | `SiteHeader` / `SiteBrand` import it; a plain-HTML page links it |
63
64
  | theme-dark | `@gemboss/ui/theme-dark.css` | `packages/ui/src/theme-dark.css` | the dark palette | 0.6 KB | `ThemeToggle` and `themeScript` (`theme-script.ts`, without portal.css) import it |
64
65
  | collections | `@gemboss/ui/collections.css` | `packages/ui/src/collections.css` | tables, lists, grids, boards | 4.6 KB | `IndexTable`, `ResourceList`, `Board`… import it |
@@ -1463,8 +1464,8 @@ import { FLEET_LANGUAGES, LanguageSwitcher, matchLanguage } from "@gemboss/ui";
1463
1464
  | The menu | the kit's, not a new one: `details.ui-menu` (it opens with no JavaScript), its list `ui-menu__list gemboss-popover-surface` floated in the top layer by `./menu-layer` as `Menu`'s is, its rows `ui-menu__item`, so it looks like the account menu. `role="menu"` named `trigger`; each row a `menuitemradio` with `aria-checked`, `data-lang`, a check column (`.gb-lang__check`, shown on the checked row only) and the name in its own `lang` (`span.gb-lang__name lang="zh"`), so a screen reader says "中文" in Chinese. The suggested row carries `ui-menu__hint`, then `hr.ui-menu__sep` |
1464
1465
  | Keys | an opening (a click, Enter / Space, ↓ / ↑ on the button) puts focus on the checked row; ↑ ↓ wrap, Home / End; Escape closes it, focus back on the button; Tab closes it and focus goes on from the button, forward or back; a click outside closes it; a row closes it, focus on the button, and calls `onChange` when it is not the current one. Rows are out of the Tab order (`tabindex="-1"`), as a menu's are |
1465
1466
  | Helpers | `matchLanguage(languages, wanted)`: the first of `navigator.languages`, an Accept-Language header or one tag that the list has, exactly or by its base language ("zh-CN" → "zh"), else undefined; pure, so a server reads the header with it. `languageShort(language)` |
1466
- | CSS | `language-switcher.css`, its own entry, 1.1 KB gzip, imported by the component. The menu's base rules (the core's `ui-menu` ones) are repeated there at zero specificity (`:where`) with the core's values: where the core is loaded it wins and nothing changes; a page that links only the tokens and this file (gemboss.ai, whose pages the core would reset) draws the same menu. Tokens only, so `data-theme="dark"` follows |
1467
- | The topbar | at 320 / 360 / 390 the kit's phone bar still fits with the switcher between ThemeToggle and the account chip (☰, brand, search, toggle, switcher, chip; no horizontal scroll) |
1467
+ | CSS | `language-switcher.css`, its own entry, 1.1 KB gzip, imported by the component. The menu's base rules (the core's `ui-menu` ones) are repeated there with the core's values, so a page that links only this file (gemboss.ai, whose pages the core would reset; GemNews's extension) draws the same menu. The surface and the box at zero specificity (`:where`); the rows, the hint and the separator at ONE class (0,1,0, since 22/10/2026), the core's own weight: an app's `button { font: inherit }` (0,0,1) cannot make them 16px, and one class of the app's (`.gb-lang__item { … }` after it, `.my-bar .gb-lang__item`) restyles them on purpose (the gap and corners take two: `.gb-lang__item.ui-menu__item`, so app-shell.css's unset tokens on a page without the core cannot zero the gap). Tokens, so `data-theme="dark"` follows, each with its light value as the fallback: no core, no `gemboss-tokens.css`, still right (light) |
1468
+ | The topbar | AppShell's phone bar (☰, brand, search, toggle, switcher, account chip) fits it at 320 / 360 / 390, between ThemeToggle and the chip, no horizontal scroll. A portal `Topbar` with `TopbarNav` does NOT have that room (GemFeedback: brand, tab menu, search, theme, Sign in, no room for a 55px button at 320–390, and at 601–687 the tabs ran under it): there the switcher goes in the folded tab menu, `TopbarNav foot`, and its end-slot copy wears `gb-topnav-unfolded` (README "Public portal pieces" › TopbarNav) |
1468
1469
 
1469
1470
  ### Plain HTML: `gembossUI.languageSwitcher()` (21/10/2026)
1470
1471
 
@@ -1495,7 +1496,8 @@ the page has it; it holds no closing script tag, so a server can inline it.
1495
1496
  | | |
1496
1497
  | --- | --- |
1497
1498
  | Options | the component's props, same names and defaults (`value`, `languages`, `onChange`, `suggested`, `labels`, `variant`, `end`, `className`), plus `mount`: an element the switcher REPLACES (a placeholder the server wrote) |
1498
- | Returns | `{ el, value(), set(code), open(), close(), destroy() }`. A chosen row updates the switcher itself before `onChange` (the component waits for its `value` prop); `set` is for a change from elsewhere; `destroy` removes it and its two document listeners |
1499
+ | Returns | `{ el, value(), set(code), update(props), setLabels(labels), open(), close(), destroy() }`. A chosen row updates the switcher itself before `onChange` (the component waits for its `value` prop); `set` is for a change from elsewhere; `destroy` removes it and its two document listeners |
1500
+ | `update` / `setLabels` (22/10/2026) | the page's language changed, or its list: `sw.setLabels({ trigger: T("menu.lang"), suggested: T("menu.lang_suggested") })`, `sw.update({ value, labels, suggested, languages })` (any of them). In place, as the component re-renders: the button's name and label, the menu's name, the hint; the rows drawn again only when `languages` or `suggested` change, the row that had focus focused again. An open menu stays open, placed again; the same `el`. `labels` given replace those words only; `suggested: null` takes the row away. No more destroy + new + carrying the focus by hand (GemNews's `paintLang`) |
1499
1501
  | `gembossUI` | `languageSwitcher`, `FLEET_LANGUAGES`, `LANGUAGE_SWITCHER_LABELS`, `matchLanguage`, `languageShort`. With `kit-plain.js` the open list floats in the top layer (`gembossUI.floatMenu`) and `initMenus` closes it like any kit menu; without it the list hangs under the button (`position: absolute`), as every kit menu does where there is no popover API |
1500
1502
  | A Chrome extension | a popup or options page loads both files by `src` (no inline script: an extension's CSP forbids it) and calls it from its own script; `onChange` writes `chrome.storage` |
1501
1503
 
@@ -1505,10 +1507,18 @@ two DOMs node for node, attribute for attribute, with focus, open state and the
1505
1507
  script: closed, opened by a click, ↓ ↓ Home End ↑, Escape, ↓ on the button, Home + Enter, the current row, a click outside,
1506
1508
  Tab; with the defaults and with Vietnamese labels, a suggested row, the inline variant, left-aligned, links. NEGATIVE
1507
1509
  CONTROL: one `lang` changed on the plain page and the comparison sees it. Then: the short label per language, the list
1508
- under the button's right edge, one tick; 36 / 44px and 44px rows; the phone bar at 390 / 360 / 320; contrast in both
1510
+ under the button's right edge, one tick; 36 / 44px and rows of 44 under a finger; the phone bar at 390 / 360 / 320; contrast in both
1509
1511
  themes (label ≥ 4.5:1, tick ≥ 3:1); the standalone page's menu equal to the core's (negative control: without the file,
1510
- the browser's popover box). `LANGUAGE_SWITCHER_SHOTS=<dir>` writes the screenshots (closed / open, both variants, both
1511
- themes, vi / en / zh). `language-switcher.test.tsx` holds the markup, the labels, the helpers and the plain file's defaults.
1512
+ the browser's popover box). 22/10/2026: `update` / `setLabels` with the menu open and a row focused (the plain DOM equal to
1513
+ the component's re-render, focus and open state kept); an app's `button { font: inherit; font-size: 16px; padding: 0 }`,
1514
+ `a {}` and `hr {}` before or after the kit, and a page with only this file (+ app-shell.css): 13px rows at the core's
1515
+ padding (4px 6px under a mouse, 12px 16px under a finger), the tick 8px from the name (negative controls: the rows back at
1516
+ `:where` → 16px; without the two-class gap rule, app-shell.css after it → 0px gap);
1517
+ GemFeedback's portal bar with the switcher in `TopbarNav foot`, `foldAt={760}`: one language control at 320–1024, nothing
1518
+ overlapping or cut (negative control: at 320 without the foot the tab menu is squeezed out).
1519
+ `LANGUAGE_SWITCHER_SHOTS=<dir>` writes the screenshots (closed / open, both variants, both themes, vi / en / zh).
1520
+ `language-switcher.test.tsx` holds the markup, the labels, the helpers, the plain file's defaults and the CSS contract (the
1521
+ rows at one class, every token with its fallback).
1512
1522
 
1513
1523
  ## Repeater rows (16/10/2026): `RepeaterList`, `useKeyedRows`
1514
1524
 
@@ -1696,7 +1706,25 @@ import { Avatar, Dialog, Leaderboard, Podium, SearchDialog, ThemeToggle, themeSc
1696
1706
  - `TopbarNav`: section tabs in the topbar after the brand, the active one underlined on the topbar's
1697
1707
  edge. A topbar holding tabs drops the centre search column (`:has(.gb-topnav)`), so the tabs get the
1698
1708
  room. 36px under a mouse, 44px under a finger. Under 600px the tabs fold into one button naming the
1699
- open tab, with the tabs in a list under it (a row that scrolls sideways hid the third tab at 390).
1709
+ open tab, with the tabs in a list under it (a row that scrolls sideways hid the third tab at 390). The open tab's name
1710
+ gives way with an ellipsis before it runs under the topbar's end (22/10/2026: "Changelog" at 320).
1711
+ - `foot` (22/10/2026): the app's own section at the foot of the folded list, under a line; shown only folded. What the
1712
+ topbar's end has no room for once the tabs fold, the language first. Its copy in the end slot wears
1713
+ `gb-topnav-unfolded`, hidden wherever the tabs fold, so the bar has one at every width, one at a time:
1714
+
1715
+ ```tsx
1716
+ const Lang = (p: Partial<LanguageSwitcherProps>) => <LanguageSwitcher value={lang} languages={withHrefs} {...p} />;
1717
+ <div className="gemboss-topbar-start"><BrandLockup name="GemFeedback" href="/" />
1718
+ <TopbarNav as={Link} items={tabs} foldAt={760} foot={<Lang variant="inline" end={false} />} /></div>
1719
+ <div className="gemboss-topbar-center" />
1720
+ <div className="gemboss-topbar-end"><SearchDialog … /><ThemeToggle /><Lang className="gb-topnav-unfolded" /><SignInButton … /></div>
1721
+ ```
1722
+ - `foldAt={760}`: fold over the whole phone topbar (≤760px) instead of under 600px, for a bar whose end holds more
1723
+ (GemFeedback signed out: search, theme, language, Sign in; its three tabs ran under the switcher at 601–687). Measured
1724
+ in Chromium: brand + folded tabs + search + theme + Sign in fit from 320 with the language in the foot, and from 761 the
1725
+ tabs + all four controls.
1726
+ - These three and the ellipsis are `topnav-fold.css`, an entry of their own that `TopbarNav` imports after `portal.css`
1727
+ (which is in the frozen `gemboss-ui.css`). A page that renders TopbarNav's markup without the component links it.
1700
1728
  - `SearchDialog`: the topbar search button opening a 640px popup (Featurebase's, the admin's ⌘K).
1701
1729
  `initial` before typing, `search(q)` while typing (150 ms debounce, latest answer wins); ↑ ↓ Enter
1702
1730
  Esc; ⌘K / Ctrl+K and "/" open it. Results are links; without JavaScript the button goes to
@@ -8,7 +8,7 @@
8
8
  .cat-code summary{padding:10px 16px;font-size:13px;cursor:pointer;color:var(--gemboss-text-muted);border-top:1px solid var(--gemboss-divider);min-height:24px}
9
9
  .cat-code pre,.cat-api{margin:0;padding:12px 16px;overflow:auto;background:#0f1012;color:#e8e8e6;font-size:12px;line-height:1.5;border-radius:0}
10
10
  .cat-api{border-radius:12px;margin:0 0 16px}</style></head><body><div class="cat">
11
- <h1>@gemboss/ui 0.47.0 · Editor</h1>
11
+ <h1>@gemboss/ui 0.48.0 · Editor</h1>
12
12
  <p>Khung của mọi editor: <code>EditorFrame</code> (cột trái · khe kéo · preview · cột phải dock trên mép phải preview) và <code>EditorTopbar</code> (← · tiêu đề · cụm giữa · cụm phải · dòng trạng thái). Là đúng khung của theme / page / section editor trong admin. Gói chỉ vẽ KHUNG; chat, Configure, canvas, page picker, menu, Publish là của editor, đi vào bằng slot. Trang HTML thuần nạp <code>editor.css</code> + <code>editor-plain.js</code> rồi gọi <code>gembossUI.initEditorFrame()</code>: kéo khe (chuột, chạm, phím mũi tên / Home / End), đổi thiết bị, mở ⋯ và Publish ▾, ẩn cột trái, Chat | Preview trên điện thoại. <a href="index.html">← Catalog</a></p>
13
13
  <h2>React</h2><pre class="cat-api"><code>import &quot;@gemboss/ui/editor.css&quot;;
14
14
  import { EditorFrame, EditorTopbar, EditorBack, EditorChatToggle, EditorPaneTabs, EditorSaveState, DeviceToggle } from &quot;@gemboss/ui/editor&quot;;