@gemboss/ui 0.44.0 → 0.46.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 +111 -0
- package/README.md +153 -5
- package/catalog/editor.html +1 -1
- package/catalog/index.html +229 -86
- package/gemboss-ui.css +2 -2
- package/package.json +6 -2
- package/parts/components.css +1 -1
- package/parts/core.css +1 -1
- package/parts/shell.css +1 -1
- package/src/editor/layers.tsx +1 -1
- package/src/editor.css +1 -1
- package/src/icons.tsx +2 -0
- package/src/index.ts +7 -2
- package/src/language-switcher-plain.js +1 -0
- package/src/language-switcher.css +1 -0
- package/src/language-switcher.tsx +206 -0
- package/src/reactions-data.ts +15 -4
- package/src/reactions.tsx +64 -15
- package/src/setup-guide.css +1 -1
- package/src/setup-guide.tsx +92 -22
- package/src/team-chat-plain.js +1 -1
- package/src/team-chat.tsx +13 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,117 @@
|
|
|
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
|
+
## 0.46.0 (21/10/2026)
|
|
6
|
+
|
|
7
|
+
**`.gemboss-page-section-content` is gone** (gemboss one panel P2b, 21/10/2026).
|
|
8
|
+
|
|
9
|
+
Removed
|
|
10
|
+
- The rule that made every button, input and textarea 44px inside `.gemboss-page-section-content`. That class was the admin
|
|
11
|
+
page editor's "Edit section content" drawer, which P2b replaced with the section's column on the Settings tab (the Canvas's
|
|
12
|
+
own). No element carries it any more, so the rule matched nothing (gemboss `a-tap-target-rule-outlived-its-markup`). The
|
|
13
|
+
page editor's dock rule (`.gemboss-pageedit .gemboss-ec-dock-body`) still sizes its controls.
|
|
14
|
+
|
|
15
|
+
**One language button for the fleet: `LanguageSwitcher`, and the button says the language** (Chris 21/10/2026: "bộ switch
|
|
16
|
+
ngôn ngữ ở gemcommunity, gemnews … chuẩn hoá ở gemboss ui để khi chọn một ngôn ngữ nó hiển thị lên trên cái icon luôn ví dụ
|
|
17
|
+
là VI, EN hay chữ Zhong của trung quốc"). Each surface drew its own; GemCommunity's topbar was a globe alone.
|
|
18
|
+
|
|
19
|
+
Added
|
|
20
|
+
- `LanguageSwitcher` (language-switcher.tsx, exported from the root): the kit's `details.ui-menu`, its button the `globe`
|
|
21
|
+
and the current language's short label ("EN", "VI", "中"), named "Language: Tiếng Việt"; the menu lists each language
|
|
22
|
+
by its native name in its own `lang`, the current one checked (`menuitemradio`), an optional `suggested` row first.
|
|
23
|
+
Props `value`, `languages`, `onChange(code)` (the app stores it), `suggested`, `labels` (`trigger`, `suggested`),
|
|
24
|
+
`variant` (`topbar`: ThemeToggle's 36 / 44px ghost; `inline`: a pill with a caret), `end`, `className`. A language with
|
|
25
|
+
an `href` is a link (`hreflang`). Keys: an opening focuses the checked row, ↑ ↓ wrap, Home / End, Escape back to the
|
|
26
|
+
button, Tab leaves; a click outside closes it. The list floats in the top layer through `./menu-layer`, as `Menu`'s.
|
|
27
|
+
- `FLEET_LANGUAGES` (en English EN, vi Tiếng Việt VI, zh 中文 中), `LANGUAGE_SWITCHER_LABELS`, `matchLanguage(languages,
|
|
28
|
+
navigator.languages | Accept-Language)`, `languageShort(language)`; types `Language`, `LanguageSwitcherLabels`,
|
|
29
|
+
`LanguageSwitcherProps`.
|
|
30
|
+
- `language-switcher.css` (`@gemboss/ui/language-switcher.css`), its own entry, 1.1 KB gzip (ceiling 1,593): the button,
|
|
31
|
+
both variants, the check column; the core's menu rules repeated at zero specificity, so a page with only the tokens and
|
|
32
|
+
this file (gemboss.ai) draws the same menu, and where the core is loaded nothing changes.
|
|
33
|
+
- `language-switcher-plain.js` (`@gemboss/ui/language-switcher-plain.js`), its own file, 2.3 KB gzip (ceiling 2,902):
|
|
34
|
+
`gembossUI.languageSwitcher(opts)` draws the component's DOM for a page with no React (`mount` replaces a placeholder)
|
|
35
|
+
and returns `{ el, value(), set(code), open(), close(), destroy() }`; with `FLEET_LANGUAGES`, `LANGUAGE_SWITCHER_LABELS`,
|
|
36
|
+
`matchLanguage`, `languageShort`.
|
|
37
|
+
- Icon `chevronDown` (the inline variant's caret).
|
|
38
|
+
|
|
39
|
+
Tests
|
|
40
|
+
- `language-switcher.test.tsx`: the markup and ARIA, the three short labels ("中", not "ZH"), translated labels, the
|
|
41
|
+
suggested row and separator, links, an unknown value, strings as text, the helpers, the plain file's defaults against
|
|
42
|
+
the TypeScript, no closing script tag in it, the CSS's classes, 44px rule and tokens only.
|
|
43
|
+
- `language-switcher-browser.test.ts` (`test:ui-browser`): in Chromium, the component and the plain switcher on the same
|
|
44
|
+
topbar and site header, DOM, focus, open state and `onChange` compared after each of 14 steps (keys, Escape, Enter, the
|
|
45
|
+
current row, a click outside, Tab), with defaults and with labels / suggested / inline / left / links; negative control
|
|
46
|
+
one `lang` changed. The label per language and the list's place; 36 / 44px, 44px rows; the phone bar at 390 / 360 / 320;
|
|
47
|
+
contrast in both themes; the standalone menu equal to the core's (negative control without the file).
|
|
48
|
+
|
|
49
|
+
**One onboarding checklist for GemBoss and GemCommunity** (21/10/2026, Chris: "cái onboarding checklist của gemcommunity
|
|
50
|
+
với của gemboss nút tick màu xanh progress bar nên đồng bộ nhé để dùng cùng một gemboss ui kit"). The admin's "Set up your
|
|
51
|
+
store" drew grey ✓ glyphs and a near-black bar in Polaris; GemCommunity's "Start here" drew filled green ticks, an iris bar
|
|
52
|
+
and a "✓ 2 completed" fold in its own markup. Both now render `SetupGuide` (README "Setup guide").
|
|
53
|
+
|
|
54
|
+
Changed
|
|
55
|
+
- The tick is an 18px ring; a done step fills it with `--gemboss-success` and a white check drawn by two borders (no
|
|
56
|
+
glyph, so it is the same tick in every app); `unknown` is a dashed ring with its `?`. The glyphs stay in the markup for a
|
|
57
|
+
page without the sheet.
|
|
58
|
+
- The bar fills in the same green from the first step (it was the accent, green only at the end); the
|
|
59
|
+
`ui-setup__bar--done` class is no longer rendered.
|
|
60
|
+
- The steps done when the guide first renders fold behind one "✓ N completed" row (`<details class="ui-setup__done">`, not
|
|
61
|
+
in the steps' `name` group, so opening it never shuts the open step). Decided once, like the open step: a step finished
|
|
62
|
+
on screen stays in the list. `foldDone={false}` lists them as before.
|
|
63
|
+
- The count's amber/green tones ship in `setup-guide.css` (scoped to `.ui-setup`): the admin loads `ui.css` without
|
|
64
|
+
`kit-plain.css`, where the badge tones live, and drew a grey count.
|
|
65
|
+
- The open step's body is indented 30px (18px mark + 12px gap).
|
|
66
|
+
|
|
67
|
+
Added
|
|
68
|
+
- `optional` (steps under a hairline and an "Optional" heading, never counted), `onDismiss` (a × at the end of the head,
|
|
69
|
+
44px under a thumb; a dismissible guide does not also fold), `SetupStep.action` (the step's control at the end of a
|
|
70
|
+
plain row, or under its body), `SetupStep.className`, `labels.completed` / `labels.optional` / `labels.dismiss`, and the
|
|
71
|
+
`SetupGuideLabels` type. Under a thumb a step's buttons are 44px too (the admin's rule, now the kit's).
|
|
72
|
+
|
|
73
|
+
Tests
|
|
74
|
+
- `setup-guide.test.tsx`: the fold (order, count, outside the group, `foldDone={false}`, nothing done → no fold), the
|
|
75
|
+
optional group never counted, `action` never inside a `<summary>`, `onDismiss` markup, one green token for tick and bar,
|
|
76
|
+
the × in the coarse block (with its negative control). `setup-guide-browser.test.tsx`: computed colours of the done tick,
|
|
77
|
+
its check and the bar, the todo ring; a step finished on screen stays out of the fold; opening the fold keeps the open
|
|
78
|
+
step open; the × dismisses; the ×, the rows and the fold are 44px at 390 (negative control: without the rule the × is 28).
|
|
79
|
+
- Catalog "Hướng dẫn thiết lập": the admin's and GemCommunity's guides as demos.
|
|
80
|
+
|
|
81
|
+
## 0.45.0 (21/10/2026)
|
|
82
|
+
|
|
83
|
+
**The reaction picker speaks the app's language** (21/10/2026, seen on GemCommunity's Vietnamese chat with 0.43.0: the
|
|
84
|
+
picker opened by the composer's emoji button and by "More reactions" said "Search emoji", "No emoji matches.", "Frequently
|
|
85
|
+
used", "Smileys"…).
|
|
86
|
+
|
|
87
|
+
Added
|
|
88
|
+
- `REACTION_LABELS` and type `ReactionLabels` (reactions.tsx, exported from the root): every word `Reactions` and
|
|
89
|
+
`ReactionPicker` write, English by default: `pickReaction`, `searchEmoji`, `noEmojiMatches`, `frequentlyUsed`,
|
|
90
|
+
`groupName(id)` (a group's heading by its English id in `REACTION_GROUPS`, which stays the key), `pickReactionEmoji(emoji,
|
|
91
|
+
words, mine)`, `addReaction`, `addReactionTo(target)`, `reactionChip(emoji, names, mine)`. A `labels` prop (any subset) on
|
|
92
|
+
`Reactions` (passed on to its picker) and on `ReactionPicker`; its `label` prop still wins over `pickReaction`.
|
|
93
|
+
- `TEAM_CHAT_LABELS` carries those keys (same defaults) and `messageFrom(author)` ("the message from Linh", the target a
|
|
94
|
+
message gives its reaction row). `Message` passes its `labels` to its `Reactions` row and to the "More reactions" picker,
|
|
95
|
+
and uses `addReaction` for that button's tooltip (was a hard-coded "Add reaction"); `Composer` passes its `labels` to the
|
|
96
|
+
emoji button's picker.
|
|
97
|
+
- `team-chat-plain.js`: `LABELS` has the same keys; `reactions(props)` and `reactionPicker(props)` take `labels`, `message`
|
|
98
|
+
passes its own to both, `emojiButton({ labels })` to its picker.
|
|
99
|
+
- `searchReactions(query, groupName?)`: with the picker's `groupName`, a query found in a TRANSLATED group name (case and
|
|
100
|
+
Vietnamese marks ignored: "mat cuoi" finds "Mặt cười") adds that whole group. A name left as its English id is not
|
|
101
|
+
searched, so an English picker finds what it did. The emoji's own search words stay English for now.
|
|
102
|
+
|
|
103
|
+
Unchanged
|
|
104
|
+
- An app that passes no labels draws the same markup, word for word (held by `reactions.test.tsx`).
|
|
105
|
+
|
|
106
|
+
Tests
|
|
107
|
+
- `reactions.test.tsx`: the defaults; markup with Vietnamese labels (picker both kinds, row); every label replaced by a
|
|
108
|
+
marker leaves no English but the data (search words, names); the translated-group search; a guard that parses
|
|
109
|
+
`reactions.tsx` (TypeScript's parser) and fails on any string or JSX text outside `// labels:begin` … `// labels:end`
|
|
110
|
+
that is not a class name, key or attribute value, with negative controls for five words put back.
|
|
111
|
+
- `team-chat-plain-data.test.ts`: `searchReactions` with group names against the TypeScript; no picker word written in the
|
|
112
|
+
plain code outside `LABELS`. `team-chat-plain-browser.test.ts`: the translated picker, row and message drawn both ways;
|
|
113
|
+
live, a Vietnamese message's two pickers and a Vietnamese composer's, searched by a group's Vietnamese name and to
|
|
114
|
+
nothing, the same DOM both ways. `team-chat.test.tsx`: a message's reaction words follow its labels.
|
|
115
|
+
|
|
5
116
|
## 0.44.0 (21/10/2026)
|
|
6
117
|
|
|
7
118
|
**The assistant frame for a page with no React: `gembossUI.assistantFrame`** (Chris 21/10/2026: GemFactory's assistant was a
|
package/README.md
CHANGED
|
@@ -73,6 +73,7 @@ its CSS out of the build with it (`sideEffects`). gzip of the minified file, as
|
|
|
73
73
|
| setup-guide | `@gemboss/ui/setup-guide.css` | `packages/ui/src/setup-guide.css` | `SetupGuide` (0.30.0) | 0.9 KB | `SetupGuide` imports it |
|
|
74
74
|
| reader-dialog | `@gemboss/ui/reader-dialog.css` | `packages/ui/src/reader-dialog.css` | `ReaderDialog`: the popup sized for reading a document (0.41.0) | 0.4 KB | `ReaderDialog` imports it |
|
|
75
75
|
| service-switcher | `@gemboss/ui/service-switcher.css` | `packages/ui/src/service-switcher.css` | `ServiceSwitcher` and `Switcher`: the button after the brand and its panel (0.36.0; any list, illustrated tiles 0.40.0) | 2.2 KB | `ServiceSwitcher` imports it, and `BrandLockup` through it (README "ServiceSwitcher") |
|
|
76
|
+
| language-switcher | `@gemboss/ui/language-switcher.css` | `packages/ui/src/language-switcher.css` | `LanguageSwitcher`: the globe + "EN" / "VI" / "中" and the menu of languages (21/10/2026; works without the core) | 1.1 KB | `LanguageSwitcher` imports it; a plain-HTML page links it with `language-switcher-plain.js` (README "LanguageSwitcher") |
|
|
76
77
|
| team-chat | `@gemboss/ui/team-chat.css` | `packages/ui/src/team-chat.css` | the team chat layer (20/10/2026): rail, messages, composer, thread and people panels, the screen's layout | 3.6 KB | `TeamChat` and its pieces import it (with `Avatar`'s portal.css and `Reactions`' reactions.css); a plain-HTML page links it (README "The team chat layer") |
|
|
77
78
|
| question-card | `@gemboss/ui/question-card.css` | `packages/ui/src/question-card.css` | `QuestionCard`: the assistant's one question with one to three answers, the pick chosen (0.43.0) | 0.9 KB | `QuestionCard` imports it |
|
|
78
79
|
| briefing-card | `@gemboss/ui/briefing-card.css` | `packages/ui/src/briefing-card.css` | `BriefingCard`: one proposal at the top of a screen, on an iris wash (0.43.0) | 0.5 KB | `BriefingCard` imports it |
|
|
@@ -1412,6 +1413,82 @@ its negative control changes one attribute and the comparison sees it. Then the
|
|
|
1412
1413
|
beside it, idempotence, `<head>`, alone, the wrong order). Catalog section "Chuyển dịch vụ", last frame: the plain page,
|
|
1413
1414
|
live.
|
|
1414
1415
|
|
|
1416
|
+
## LanguageSwitcher (21/10/2026): the button says which language the page is in
|
|
1417
|
+
|
|
1418
|
+
Chris 21/10/2026: "bộ switch ngôn ngữ ở gemcommunity, gemnews và xem những chỗ nào khác nên chuẩn hoá ở gemboss ui để khi
|
|
1419
|
+
chọn một ngôn ngữ nó hiển thị lên trên cái icon luôn ví dụ là VI, EN hay chữ Zhong của trung quốc ntn đó để dễ hiểu hơn là
|
|
1420
|
+
icon quả địa cầu như hiện tại". Each surface drew its own (GemCommunity's topbar a globe alone, its sign-in pages
|
|
1421
|
+
"🌐 EN ⌄", GemNews its own): one component now, whose button shows the globe AND the current language's short label,
|
|
1422
|
+
"EN", "VI", "中" (the character, not "ZH": the button is read by people).
|
|
1423
|
+
|
|
1424
|
+
```tsx
|
|
1425
|
+
import { FLEET_LANGUAGES, LanguageSwitcher, matchLanguage } from "@gemboss/ui";
|
|
1426
|
+
|
|
1427
|
+
// The topbar's end slot, beside ThemeToggle and the account menu. The app keeps the choice (cookie, storage, profile).
|
|
1428
|
+
<AppShell actions={<><ThemeToggle /><LanguageSwitcher value={lang} onChange={saveLang} labels={{ trigger: t("Language") }} /><AccountMenu … /></>} … />
|
|
1429
|
+
|
|
1430
|
+
// A sign-in page, a site header, a footer: the pill with a caret; the browser's language suggested first.
|
|
1431
|
+
<LanguageSwitcher variant="inline" value={lang} onChange={saveLang}
|
|
1432
|
+
suggested={matchLanguage(FLEET_LANGUAGES, navigator.languages)} labels={{ trigger: "Ngôn ngữ", suggested: "Gợi ý" }} />
|
|
1433
|
+
|
|
1434
|
+
// A site whose languages are paths: each row is a link (hreflang), so it switches with no script.
|
|
1435
|
+
<LanguageSwitcher value="vi" languages={FLEET_LANGUAGES.map((l) => ({ ...l, href: l.code === "en" ? "/" : `/${l.code}/` }))} />
|
|
1436
|
+
```
|
|
1437
|
+
|
|
1438
|
+
| | |
|
|
1439
|
+
| --- | --- |
|
|
1440
|
+
| Props | `value` (the current code, required; one not in the list shows its code upper case), `languages` (default `FLEET_LANGUAGES`: `{ code, label, short }` for en "English" EN, vi "Tiếng Việt" VI, zh "中文" 中; `label` is the NATIVE name, `short` what the button shows, default the code's first part upper case; `href` makes the row a link), `onChange(code)` (a row other than the current one; the app stores it and passes the new `value`), `suggested` (a code: that row first, with the hint, then a separator; one the list does not have changes nothing), `labels` (`trigger` "Language", `suggested` "Suggested": `LANGUAGE_SWITCHER_LABELS`), `variant` (`topbar` default, `inline`), `end` (default true: the menu's right edge on the button's), `className` (on the `<details>`) |
|
|
1441
|
+
| The button | `summary.gb-lang__btn`: the kit's `globe` at 18px, then `span.gb-lang__short` ("VI", in its own `lang`, so "中" takes a Chinese glyph), in the text colour (the globe muted), at least 2ch wide so EN / VI / 中 do not move the bar. Its name and tooltip: `<trigger>: <the current language's label>` ("Language: Tiếng Việt", "Ngôn ngữ: 中文"); `aria-haspopup="menu"`. `topbar`: ThemeToggle's ghost, 36px; `inline`: a pill on the surface with the chips' line and the `chevronDown` caret (new in the set), turned while open. 44px under a finger or at phone width (≤767px), both |
|
|
1442
|
+
| 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` |
|
|
1443
|
+
| 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 |
|
|
1444
|
+
| 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)` |
|
|
1445
|
+
| 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 |
|
|
1446
|
+
| 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) |
|
|
1447
|
+
|
|
1448
|
+
### Plain HTML: `gembossUI.languageSwitcher()` (21/10/2026)
|
|
1449
|
+
|
|
1450
|
+
For a page with no React (gemboss.ai, GemNews's extension popup, GemFactory, GemCare, GemPress): `language-switcher-plain.js`
|
|
1451
|
+
draws the component's DOM (classes, ARIA, order) and runs its keys, so `language-switcher.css` draws it unchanged. Its own
|
|
1452
|
+
file, 2.3 KB gzip, loaded only where the switcher is. A classic script that ADDS to `gembossUI`, after `kit-plain.js` when
|
|
1453
|
+
the page has it; it holds no closing script tag, so a server can inline it.
|
|
1454
|
+
|
|
1455
|
+
```html
|
|
1456
|
+
<link rel="stylesheet" href="/ui/language-switcher.css"> <!-- require.resolve("@gemboss/ui/language-switcher.css") -->
|
|
1457
|
+
…
|
|
1458
|
+
<span data-gb-language-switcher hidden></span> <!-- where the button goes -->
|
|
1459
|
+
…
|
|
1460
|
+
<script src="/ui/kit-plain.js"></script> <!-- if the page has it: FIRST (the menu then floats) -->
|
|
1461
|
+
<script src="/ui/language-switcher-plain.js"></script> <!-- @gemboss/ui/language-switcher-plain.js -->
|
|
1462
|
+
<script>
|
|
1463
|
+
var sw = gembossUI.languageSwitcher({
|
|
1464
|
+
value: document.documentElement.lang || "en",
|
|
1465
|
+
onChange: function (code) { document.cookie = "lang=" + code + "; path=/; max-age=31536000"; location.reload(); },
|
|
1466
|
+
suggested: gembossUI.matchLanguage(gembossUI.FLEET_LANGUAGES, navigator.languages),
|
|
1467
|
+
labels: { trigger: "Ngôn ngữ", suggested: "Gợi ý" },
|
|
1468
|
+
variant: "inline",
|
|
1469
|
+
mount: document.querySelector("[data-gb-language-switcher]"), // replaced by the switcher; none: append sw.el
|
|
1470
|
+
});
|
|
1471
|
+
</script>
|
|
1472
|
+
```
|
|
1473
|
+
|
|
1474
|
+
| | |
|
|
1475
|
+
| --- | --- |
|
|
1476
|
+
| 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) |
|
|
1477
|
+
| 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 |
|
|
1478
|
+
| `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 |
|
|
1479
|
+
| 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` |
|
|
1480
|
+
|
|
1481
|
+
`packages/ui/src/language-switcher-browser.test.ts` (Chromium, `test:ui-browser`) renders the component and the plain
|
|
1482
|
+
switcher on the same shells (the kit's topbar with the `topbar` variant, the site header with `inline`) and compares the
|
|
1483
|
+
two DOMs node for node, attribute for attribute, with focus, open state and the `onChange` calls, after each step of one
|
|
1484
|
+
script: closed, opened by a click, ↓ ↓ Home End ↑, Escape, ↓ on the button, Home + Enter, the current row, a click outside,
|
|
1485
|
+
Tab; with the defaults and with Vietnamese labels, a suggested row, the inline variant, left-aligned, links. NEGATIVE
|
|
1486
|
+
CONTROL: one `lang` changed on the plain page and the comparison sees it. Then: the short label per language, the list
|
|
1487
|
+
under the button's right edge, one tick; 36 / 44px and 44px rows; the phone bar at 390 / 360 / 320; contrast in both
|
|
1488
|
+
themes (label ≥ 4.5:1, tick ≥ 3:1); the standalone page's menu equal to the core's (negative control: without the file,
|
|
1489
|
+
the browser's popover box). `LANGUAGE_SWITCHER_SHOTS=<dir>` writes the screenshots (closed / open, both variants, both
|
|
1490
|
+
themes, vi / en / zh). `language-switcher.test.tsx` holds the markup, the labels, the helpers and the plain file's defaults.
|
|
1491
|
+
|
|
1415
1492
|
## Repeater rows (16/10/2026): `RepeaterList`, `useKeyedRows`
|
|
1416
1493
|
|
|
1417
1494
|
A setting that is a list (an accordion's items, a pricing table's plans, choice cards) is the element's content. The admin's
|
|
@@ -1703,8 +1780,9 @@ and GemCare's chat, draw and accept the same thing.
|
|
|
1703
1780
|
| Piece | What |
|
|
1704
1781
|
|---|---|
|
|
1705
1782
|
| `@gemboss/ui/reactions-data` (plain TS, also re-exported from the root) | `REACTION_GROUPS` (GemCare's 137 emoji in 4 groups, each with search words), `REACTION_EMOJI`, `isReactionEmoji` (the server's check: refuse anything else), `searchReactions`, `reactionLabel` (names, two then `+n`), `toggleReaction` (the optimistic guess drawn before the server answers), `QUICK_REACTIONS` (👍 ✅), type `Reaction` = `{ emoji, names, mine }` |
|
|
1706
|
-
| `Reactions` (React, client) | the chips (emoji + NAMES of who put it; yours outlined + filled, `aria-pressed`) and "Add reaction", which opens `ReactionPicker` in flow under the row. `canReact={false}`: chips only; no chips and no right to react: nothing |
|
|
1707
|
-
| `ReactionPicker` | search, "Frequently used" (this browser's last picks, `localStorage`, filled up to nine with `SUGGESTED_REACTIONS`), the groups; arrows move in the grid, Enter in the search picks the first, Escape closes and returns focus. `items` absent: a picker that WRITES an emoji (the team chat composer's): plain buttons, none pressed. `label` (its name, default
|
|
1783
|
+
| `Reactions` (React, client) | the chips (emoji + NAMES of who put it; yours outlined + filled, `aria-pressed`) and "Add reaction", which opens `ReactionPicker` in flow under the row. `canReact={false}`: chips only; no chips and no right to react: nothing. `labels` (below) reaches its picker too |
|
|
1784
|
+
| `ReactionPicker` | search, "Frequently used" (this browser's last picks, `localStorage`, filled up to nine with `SUGGESTED_REACTIONS`), the groups; arrows move in the grid, Enter in the search picks the first, Escape closes and returns focus. `items` absent: a picker that WRITES an emoji (the team chat composer's): plain buttons, none pressed. `label` (its name, default `labels.pickReaction`), `className`, `initialFocus` (`"search"`, default, or `"emoji"`: under a finger a focused search brings the keyboard up over it), `labels` |
|
|
1785
|
+
| `REACTION_LABELS`, type `ReactionLabels` (21/10/2026) | every word the two write, English by default; `labels` (any subset) translates. The keys and how a search treats a translated group: "The words" in the team chat layer below (`TEAM_CHAT_LABELS` carries the same keys) |
|
|
1708
1786
|
| `reactions.css` (its own entry, NOT inside `gemboss-ui.css`: link it where reactions are drawn, as `collections.css`) | `ui-reactions`, `ui-reactions__row`, `ui-reaction` (+ `is-mine`), `__emoji`, `__names`, `ui-reaction-add`, `ui-reaction-picker` (`__search`, `__body`, `__head`, `__grid`, `__btn`, `__empty`) |
|
|
1709
1787
|
|
|
1710
1788
|
Every target is 44px (a control, CLAUDE.md §9.1); the pill you see is 26px, transparent padding carries the rest, so a
|
|
@@ -1739,7 +1817,8 @@ token, so `data-theme="dark"` turns it dark unchanged. It holds no rule on `:roo
|
|
|
1739
1817
|
|
|
1740
1818
|
Props only: no fetching, no store, no router. The app owns the data and the actions; the components keep only the screen's
|
|
1741
1819
|
own state (the reaction picker a message opened, the composer's text when the app does not hold it, focus). Every word they
|
|
1742
|
-
write themselves is in `TEAM_CHAT_LABELS`; pass `labels` (any subset) to translate
|
|
1820
|
+
write themselves is in `TEAM_CHAT_LABELS`; pass `labels` (any subset) to translate (the reactions' and the picker's words
|
|
1821
|
+
too: "The words", below). `teamChatRows(messages, { meId,
|
|
1743
1822
|
isUnread })` (plain TS, `team-chat-data.ts`) says where the day separators and the "New messages" line go and which
|
|
1744
1823
|
messages are `grouped`: same author, same day, under 5 minutes apart, not a reply, and not the first new one (GemCare's
|
|
1745
1824
|
rules). `chatDayWord` gives "Today" / "Yesterday"; older days the app formats in its own locale. Days are the viewer's
|
|
@@ -1762,6 +1841,47 @@ field where the caret was (a person who opened it was writing); a press or focus
|
|
|
1762
1841
|
opens on the first emoji, not the search, so the keyboard does not come up over it. Under 360px of the screen's width the
|
|
1763
1842
|
box's three 44px buttons touch (no gap), so the field keeps 150px at 320.
|
|
1764
1843
|
|
|
1844
|
+
**The words of the reactions and the picker** (21/10/2026: GemCommunity's Vietnamese chat showed "Search emoji",
|
|
1845
|
+
"Frequently used" and "Smileys"). `TEAM_CHAT_LABELS` carries `REACTION_LABELS`' keys (reactions.tsx) with the same defaults,
|
|
1846
|
+
and a `Message` passes its `labels` to the `Reactions` row and to the picker "More reactions" opens, the `Composer` to the
|
|
1847
|
+
picker its emoji button opens. So one `labels` object translates the chat, the reaction row and both pickers; outside the
|
|
1848
|
+
team chat, `Reactions` and `ReactionPicker` take the same `labels`. An app that passes nothing gets the English below,
|
|
1849
|
+
word for word.
|
|
1850
|
+
|
|
1851
|
+
| Key | Default | Where |
|
|
1852
|
+
|---|---|---|
|
|
1853
|
+
| `pickReaction` | "Pick a reaction" | the picker's accessible name (a `label` prop wins: the composer's picker is named `pickEmoji`, "Pick an emoji") |
|
|
1854
|
+
| `searchEmoji` | "Search emoji" | the search field's placeholder and accessible name |
|
|
1855
|
+
| `noEmojiMatches` | "No emoji matches." | the search found nothing |
|
|
1856
|
+
| `frequentlyUsed` | "Frequently used" | the first heading |
|
|
1857
|
+
| `groupName(id)` | the id: "Smileys", "Hands", "Marks", "Work" | each group's heading. `id` is the group's English id in `REACTION_GROUPS`: it stays the key, the name is display only |
|
|
1858
|
+
| `pickReactionEmoji(emoji, words, mine)` | "Add 🎉 party tada celebrate", "Remove 👍 like thumbs up yes ok" | a picker button's accessible name where the picker puts reactions (the composer's: the emoji and its words, no verb) |
|
|
1859
|
+
| `addReaction` | "Add reaction" | the tooltip of the button that opens the picker: the reaction row's, the action bar's |
|
|
1860
|
+
| `addReactionTo(target)` | "Add a reaction to the message from Linh" ("… to this" with no `target`) | the reaction row's button name |
|
|
1861
|
+
| `reactionChip(emoji, names, mine)` | "👍, Linh, Quan. Remove yours", "🎉, Bo. Add yours" | a chip's accessible name (`names` joined by ", ") |
|
|
1862
|
+
| `messageFrom(author)` (team chat only) | "the message from Linh" | the `target` a message gives its reaction row |
|
|
1863
|
+
|
|
1864
|
+
```tsx
|
|
1865
|
+
const VI: Partial<TeamChatLabels> = {
|
|
1866
|
+
pickReaction: "Chọn cảm xúc", pickEmoji: "Chọn emoji", searchEmoji: "Tìm emoji", noEmojiMatches: "Không có emoji nào khớp.",
|
|
1867
|
+
frequentlyUsed: "Hay dùng",
|
|
1868
|
+
groupName: (id) => ({ Smileys: "Mặt cười", Hands: "Bàn tay", Marks: "Ký hiệu", Work: "Công việc" } as Record<string, string>)[id] ?? id,
|
|
1869
|
+
pickReactionEmoji: (e, words, mine) => `${mine ? "Bỏ" : "Thả"} ${e} ${words}`,
|
|
1870
|
+
addReaction: "Thêm cảm xúc", addReactionTo: (target) => `Thêm cảm xúc cho ${target ?? "mục này"}`,
|
|
1871
|
+
reactionChip: (e, names, mine) => `${e}, ${names}. ${mine ? "Bỏ của bạn" : "Thả cùng"}`, messageFrom: (a) => `tin nhắn của ${a}`,
|
|
1872
|
+
};
|
|
1873
|
+
<Message {...m} labels={VI} /> <Composer onSend={send} labels={VI} /> <Reactions items={r} labels={VI} />
|
|
1874
|
+
// A plain page: the same object.
|
|
1875
|
+
gembossUI.teamChat.message({ ...m, labels: VI }); gembossUI.teamChat.emojiButton({ textarea, labels: VI });
|
|
1876
|
+
```
|
|
1877
|
+
|
|
1878
|
+
**Search** matches each emoji's search words, which are English (as before), and, when `groupName` gives a group a name
|
|
1879
|
+
other than its id, that name too: ignoring case and Vietnamese marks ("mặt cười", "mat cuoi" or "Mặt" for "Mặt cười"), it
|
|
1880
|
+
adds the whole group, in picker order, to what the words found. An English name is not searched, so an English picker
|
|
1881
|
+
finds exactly what it did. Still English for now: the emoji's own search words, a button's tooltip (the first of them) and
|
|
1882
|
+
the words read after the emoji in a button's name. `reactions.test.tsx` holds `reactions.tsx` to the table (no string or
|
|
1883
|
+
JSX text outside it but class names, keys and attribute values), and `team-chat-plain-data.test.ts` the plain script.
|
|
1884
|
+
|
|
1765
1885
|
**The layout.** `.gb-tc` is a CSS grid and a size container: it decides by its OWN width (in an app shell the chat is the
|
|
1766
1886
|
window minus the shell's rail), with container queries. `TEAM_CHAT_DRAWER_MAX` (1100) and `TEAM_CHAT_PHONE_MAX` (760) are the
|
|
1767
1887
|
same numbers for JS.
|
|
@@ -2239,11 +2359,11 @@ after `kit-plain.js` when the page has it (it adds `gembossUI.teamChat`; loaded
|
|
|
2239
2359
|
| `readReceipt({ read, of, label })` | `ReadReceipt` (`null` when `of` ≤ 0) |
|
|
2240
2360
|
| `channelSection({ label, id, items })`, `channelItem(props)` | `ChannelSection`, `ChannelItem` (`href`: a link, else a button; a DM's `avatar`); `items` are rows or their props |
|
|
2241
2361
|
| `peopleSection({ label, count, id, items })`, `personRow(props)` | `PeopleSection`, `PersonRow` (a link, a button with `onSelect`, else a `div`) |
|
|
2242
|
-
| `reactions(props)`, `reactionPicker(props)` | `Reactions` (plus `open` / `onOpenChange`), `ReactionPicker` (`items` absent: the composer's, which writes an emoji; `label`, `className`) |
|
|
2362
|
+
| `reactions(props)`, `reactionPicker(props)` | `Reactions` (plus `open` / `onOpenChange`), `ReactionPicker` (`items` absent: the composer's, which writes an emoji; `label`, `className`); both take `labels` ("The words" above) |
|
|
2243
2363
|
| `emojiButton({ textarea, button, host, labels })` | the `Composer`'s emoji button and picker, for a composer the page writes itself (below) |
|
|
2244
2364
|
| `avatar({ name, src, size, className })`, `icon(name, size)` | `Avatar` (portal.tsx's initials and tone), `Icon` (the icons the builders draw, and `more`) |
|
|
2245
2365
|
| `rows(messages, opts)`, `dayKey(at, timeZone)`, `dayISO(at, timeZone)`, `dayWord(at, now, timeZone)` | `teamChatRows`, `chatDayKey`, `chatDayISO`, `chatDayWord` |
|
|
2246
|
-
| `LABELS` | `TEAM_CHAT_LABELS
|
|
2366
|
+
| `LABELS` | `TEAM_CHAT_LABELS` (the reactions' and the picker's keys included); `labels` (any subset) translates, functions included: `message` passes them to its reaction row and both pickers, `emojiButton` to its picker |
|
|
2247
2367
|
|
|
2248
2368
|
```html
|
|
2249
2369
|
<link rel="stylesheet" href="/ui/portal.css"><link rel="stylesheet" href="/ui/reactions.css"><link rel="stylesheet" href="/ui/team-chat.css">
|
|
@@ -2404,6 +2524,33 @@ visits (localStorage); `group` names the steps' `name` when two guides render in
|
|
|
2404
2524
|
]} />
|
|
2405
2525
|
```
|
|
2406
2526
|
|
|
2527
|
+
**One checklist for GemBoss and GemCommunity (21/10/2026).** Chris: "cái onboarding checklist của gemcommunity với của
|
|
2528
|
+
gemboss nút tick màu xanh progress bar nên đồng bộ nhé để dùng cùng một gemboss ui kit". The admin's "Set up your store"
|
|
2529
|
+
(StoreChecklistCard) and GemCommunity's feed "Start here" both render this guide; the guide took what each did better:
|
|
2530
|
+
|
|
2531
|
+
| | Before (GemBoss admin / GemCommunity) | Now |
|
|
2532
|
+
|---|---|---|
|
|
2533
|
+
| Tick | grey ✓ glyph / 18px green circle, white ✓ | 18px ring; done fills `--gemboss-success` with a white check drawn in CSS (same in every app and font); unknown is a dashed ring with `?` |
|
|
2534
|
+
| Bar | 6px near-black / 6px iris gradient | 6px, the ticks' green from the first step (it counts them); `ui-setup__bar--done` is no longer drawn |
|
|
2535
|
+
| Count | Polaris badge / "2/5 steps done" text | the kit badge, amber then green; its tones ship in `setup-guide.css` (the admin loads `ui.css`, not `kit-plain.css`) |
|
|
2536
|
+
| Done steps | listed / folded behind "✓ 2 completed" | folded behind one "✓ N completed" row (`foldDone`, default on; `labels.completed`) |
|
|
2537
|
+
| Extra steps | "Optional" group / none | `optional`: under a hairline and an "Optional" heading (`labels.optional`), never counted |
|
|
2538
|
+
| Row control | buttons in the open step / an "Open" button at the row's end | `body` for a step that explains; `action` at the end of a plain row (under the body when there is one) |
|
|
2539
|
+
| Put away | fold, remembered / × | fold with `rememberKey`, or `onDismiss`: a × (`labels.dismiss`) and no fold |
|
|
2540
|
+
|
|
2541
|
+
The fold is decided once, like the open step: a step finished while the guide is on screen stays in the list and turns
|
|
2542
|
+
green; the next render that mounts the guide folds it. `className` on a step is for the surface's own rule (GemCommunity
|
|
2543
|
+
hides its Telegram step where its rail asks for Telegram). The ×, the rows, the head and a step's buttons are 44px under a
|
|
2544
|
+
thumb and at phone width.
|
|
2545
|
+
|
|
2546
|
+
```tsx
|
|
2547
|
+
<SetupGuide title="Start here" description="A few steps to get into the loop." onDismiss={hide}
|
|
2548
|
+
labels={{ completed: (n) => `${n} completed`, dismiss: "Hide" }} steps={[
|
|
2549
|
+
{ id: "profile", title: "Fill your profile", state: "done" },
|
|
2550
|
+
{ id: "post", title: "Write your first post", action: <Link className="ui-btn" href="/">Open</Link> },
|
|
2551
|
+
]} />
|
|
2552
|
+
```
|
|
2553
|
+
|
|
2407
2554
|
## Collections (0.13.0): tables, lists, grids, boards
|
|
2408
2555
|
|
|
2409
2556
|
Chris settled these on 05/10/2026 after an inventory of 136 hand-made collections on 11 surfaces (86 tables scrolling
|
|
@@ -2629,6 +2776,7 @@ something the base cannot express, add a token or a prop here, and every surface
|
|
|
2629
2776
|
| `packages/ui/src/editor-layers-plain.js` | 0.22.0: `gembossUI.initEditorLayers()`, the layers', the outline's and the drawers' behaviour for plain HTML. Load after `kit-plain.js`. |
|
|
2630
2777
|
| `packages/ui/src/service-switcher-plain.js` | 0.36.0: `gembossUI.initServiceSwitcher()`, the ServiceSwitcher drawn and driven for plain HTML (`@gemboss/ui/service-switcher-plain.js`). After `kit-plain.js` when the page has it; works alone. See "ServiceSwitcher". |
|
|
2631
2778
|
| `packages/ui/src/team-chat-plain.js` | 20/10/2026: `gembossUI.teamChat`, builders that return the team chat's rows (messages, day separators, "New messages", jump, receipt, rail rows, people rows, reactions and the picker) with the components' markup, for plain HTML (`@gemboss/ui/team-chat-plain.js`). After `kit-plain.js` when the page has it; works alone. See "The team chat layer". |
|
|
2779
|
+
| `packages/ui/src/language-switcher-plain.js` | 21/10/2026: `gembossUI.languageSwitcher()`, the LanguageSwitcher drawn and driven for plain HTML (`@gemboss/ui/language-switcher-plain.js`), with `FLEET_LANGUAGES`, `matchLanguage`, `languageShort`. After `kit-plain.js` when the page has it; works alone. See "LanguageSwitcher". |
|
|
2632
2780
|
| `catalog/editor.html` | 0.14.0: the frame rendered from the components and running (`packages/ui/scripts/build-catalog-editor.mjs`), linked from `catalog/index.html`. |
|
|
2633
2781
|
| `packages/ui/src/editor.css` | 0.9.0: the editors (frame, top bar, preview, Configure dock, inspector and controls, Layers, settings drawer, page picker, menus). A separate entry, NOT inside `gemboss-ui.css`; the admin imports it from the editor components, never from a layout. See "Editor CSS" above. |
|
|
2634
2782
|
| `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 + the page width tiers (`ui-page`, 0.8.1). All inside `gemboss-ui.css`; the admin @imports `chat.css` and `chat-composer.css` itself. |
|
package/catalog/editor.html
CHANGED
|
@@ -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.
|
|
11
|
+
<h1>@gemboss/ui 0.46.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 "@gemboss/ui/editor.css";
|
|
14
14
|
import { EditorFrame, EditorTopbar, EditorBack, EditorChatToggle, EditorPaneTabs, EditorSaveState, DeviceToggle } from "@gemboss/ui/editor";
|