@gemboss/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -0
- package/catalog/index.html +423 -0
- package/gemboss-ui.css +2889 -0
- package/package.json +51 -0
- package/src/GemMark.tsx +22 -0
- package/src/account.tsx +88 -0
- package/src/app-shell.css +65 -0
- package/src/assistant/dock.ts +142 -0
- package/src/assistant/host.tsx +371 -0
- package/src/assistant/index.ts +6 -0
- package/src/assistant/provider.tsx +269 -0
- package/src/assistant/sessions.tsx +450 -0
- package/src/assistant.css +407 -0
- package/src/auth.css +606 -0
- package/src/auth.tsx +202 -0
- package/src/brand.tsx +31 -0
- package/src/gemboss-field.css +108 -0
- package/src/gemboss-tokens.css +164 -0
- package/src/icons.tsx +75 -0
- package/src/index.ts +9 -0
- package/src/shell.css +1069 -0
- package/src/shell.tsx +212 -0
- package/src/ui.css +403 -0
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# @gemboss/ui
|
|
2
|
+
|
|
3
|
+
The GemBoss design system for every surface in the fleet: the admin's stylesheet as one CSS file,
|
|
4
|
+
and the admin shell (topbar + 240px rail + mobile drawer) as React components. The GemBoss admin
|
|
5
|
+
renders its own rail with these components, so what you get is what the admin is.
|
|
6
|
+
|
|
7
|
+
## Use it
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @gemboss/ui
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import "@gemboss/ui/gemboss-ui.css";
|
|
15
|
+
import { AppShell, RailItem, RailGroup, RailHeading, GemMark } from "@gemboss/ui";
|
|
16
|
+
|
|
17
|
+
<AppShell
|
|
18
|
+
brand={<a href="#/"><GemMark size={28} /> GemWatcher</a>}
|
|
19
|
+
center={<SearchBox />}
|
|
20
|
+
rail={<>
|
|
21
|
+
<RailHeading>Market</RailHeading>
|
|
22
|
+
<RailGroup active={view === "pulse"}>
|
|
23
|
+
<RailItem href="#/pulse" icon={<PulseIcon />} label="Pulse" active={view === "pulse"} />
|
|
24
|
+
</RailGroup>
|
|
25
|
+
</>}
|
|
26
|
+
mainAs="div" // your pages render their own <main>
|
|
27
|
+
>
|
|
28
|
+
{page}
|
|
29
|
+
</AppShell>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- Wrap each item in `RailGroup`: the selected pill only exists inside `.gnav-group`.
|
|
33
|
+
- `RailItem` renders `<a>`; pass `as={Link}` (Next) or `as="button"`. Every other prop is forwarded.
|
|
34
|
+
- No router, no data. The package only emits the markup the CSS was written for.
|
|
35
|
+
|
|
36
|
+
Surfaces with no React (gemcare, gempress) use the CSS alone and write the same markup by hand:
|
|
37
|
+
`nav.icon-nav > .gnav-scroll > .gnav-group > a.gnav-item > span.gnav-ico + span.gnav-label`.
|
|
38
|
+
`packages/ui/src/shell.tsx` is the reference.
|
|
39
|
+
|
|
40
|
+
## The sign-in screen
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import "@gemboss/ui/gemboss-ui.css"; // includes auth.css
|
|
44
|
+
import { AuthLayout, AuthForm, PasswordField, AuthError, AuthSubmit, AuthFoot } from "@gemboss/ui";
|
|
45
|
+
|
|
46
|
+
<AuthLayout brand="GemWatcher" tagline="GemBoss · internal tool"
|
|
47
|
+
panel={{ eyebrow: "GemWatcher", heading: "What competitors changed.", body: "…" }}>
|
|
48
|
+
<AuthForm onSubmit={submit}>
|
|
49
|
+
<PasswordField id="pw" label="Password" value={pw} onChange={(e) => setPw(e.target.value)} />
|
|
50
|
+
<AuthError>{error}</AuthError>
|
|
51
|
+
<AuthSubmit disabled={busy}>{busy ? "Signing in…" : "Sign in"}</AuthSubmit>
|
|
52
|
+
</AuthForm>
|
|
53
|
+
<AuthFoot>Team access only.</AuthFoot>
|
|
54
|
+
</AuthLayout>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The admin's /login renders exactly this (plus its own Google button). The screen sheds the tagline,
|
|
58
|
+
then the mark, then the foot lines as the visible height shrinks, follows the OS into dark, and
|
|
59
|
+
tracks the on-screen keyboard. Re-theme it with tokens on `.gb-auth`, e.g. a light iris panel:
|
|
60
|
+
`--gb-panel-bg`, `--gb-panel-ink`, `--gb-panel-muted`, `--gb-panel-eyebrow`, `--gb-panel-facet-o`.
|
|
61
|
+
|
|
62
|
+
## The assistant frame
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import "@gemboss/ui/gemboss-ui.css"; // includes assistant.css
|
|
66
|
+
import { AssistantProvider, AssistantHost, useAssistant } from "@gemboss/ui/assistant";
|
|
67
|
+
|
|
68
|
+
// once, around the whole app
|
|
69
|
+
<AssistantProvider options={{ storagePrefix: "gemwatcher.assistant",
|
|
70
|
+
isFullLink: (u) => u.hash === "#/assistant", isOnFullPage: () => location.hash === "#/assistant" }}>
|
|
71
|
+
<AppShell …>{screens}</AppShell>
|
|
72
|
+
{/* once, outside every route, so switching screens never unmounts a running answer */}
|
|
73
|
+
<AssistantHost title="Trợ lý" labels={VI} onExpand={openFull} onShrink={backToPopup} onNewChat={newChat}>
|
|
74
|
+
<YourChat onBusyChange={useAssistant().setBusy} />
|
|
75
|
+
</AssistantHost>
|
|
76
|
+
</AssistantProvider>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Four shapes, one mounted subtree: full screen (next to the rail), a popup that floats over every
|
|
80
|
+
screen and survives navigation, a status bar, and hidden-but-still-running. A live run is never
|
|
81
|
+
thrown off screen (`assistant/dock.ts` holds that rule). The popup resizes from its top edge and
|
|
82
|
+
slides along the bottom, by pointer or keyboard, and remembers both under `storagePrefix`.
|
|
83
|
+
|
|
84
|
+
The rail's way in is a `RailItem` with `className="gnav-assistant"`, linking to your full-screen
|
|
85
|
+
address (the admin puts it in `RailBottom`, above Inbox). That class draws the tinted pill, and the
|
|
86
|
+
working / answer-waiting dot the provider switches on through `<body>` classes. Leave
|
|
87
|
+
`bodyClassPrefix` at its default so those rules apply. If the popup is what the person last used, a
|
|
88
|
+
plain click on that link opens the popup instead of navigating.
|
|
89
|
+
|
|
90
|
+
The frame does not bring the chat itself (`children`) or any data. The list of past conversations
|
|
91
|
+
is a separate piece you put inside it:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { AssistantWorkspaceLayout, SessionRail, OpenChatTabs } from "@gemboss/ui/assistant";
|
|
95
|
+
|
|
96
|
+
<AssistantWorkspaceLayout dock={presentation === "dock"} rail={
|
|
97
|
+
<SessionRail sessions={sessions} activeId={active} busyIds={running}
|
|
98
|
+
collapsed={collapsed} onToggleCollapsed={toggle} onNewChat={newChat}
|
|
99
|
+
onOpen={open} onRename={rename} onTogglePin={pin} onDelete={remove} labels={VI} />
|
|
100
|
+
}>
|
|
101
|
+
{openChats.length > 1 && presentation === "dock" ? <OpenChatTabs chats={openChats} … /> : null}
|
|
102
|
+
{chat}
|
|
103
|
+
</AssistantWorkspaceLayout>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`SessionRail` owns the looking: search (diacritic-insensitive), Pinned / Today / Yesterday / 7 days /
|
|
107
|
+
30 days / Older, inline rename, the row menu and its two-step delete. Your callbacks own the doing.
|
|
108
|
+
Its styles are the `gemboss-assistant-*` rules in `packages/ui/src/shell.css` (the shared shell, which the admin
|
|
109
|
+
@imports too), and they reach you through `gemboss-ui.css` together with `gemboss-field.css`. The admin passes a second tab ("Talk to a person", gemcare) through `secondTab` and its button
|
|
110
|
+
through `fullActions`; a surface without those leaves them out.
|
|
111
|
+
|
|
112
|
+
## The topbar kit: lockup, account, icons
|
|
113
|
+
|
|
114
|
+
What goes INTO the frame, so a surface does not draw its own (28/09/2026: GemFactory's rail icons came
|
|
115
|
+
out solid black, its logo was the app tile, and its account corner was a name and a button).
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { AppShell, BrandLockup, AccountMenu, AccountMenuItem, Icon, RailItem } from "@gemboss/ui";
|
|
119
|
+
|
|
120
|
+
<AppShell
|
|
121
|
+
brand={<BrandLockup name="GemFactory" href="/" />}
|
|
122
|
+
actions={
|
|
123
|
+
<AccountMenu name="Minh Anh" email="ma@gemboss.ai" role="Designer">
|
|
124
|
+
<AccountMenuItem href="/settings">Cài đặt</AccountMenuItem>
|
|
125
|
+
<AccountMenuItem formAction="/logout" danger>Đăng xuất</AccountMenuItem>
|
|
126
|
+
</AccountMenu>
|
|
127
|
+
}
|
|
128
|
+
rail={<RailItem href="/" icon={<Icon name="board" />} label="Board" />}
|
|
129
|
+
/>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- `BrandLockup`: the admin wordmark's gem and proportions (26px tall, 13.3px/700 name) with your
|
|
133
|
+
product's name. The name hides below 460px, as the admin's wordmark does.
|
|
134
|
+
- `AccountMenu`: the admin's 32px initials chip and popover (same CSS). A `<details>`, so it opens
|
|
135
|
+
without JavaScript on a page that never hydrates; with React it also closes on an outside click and
|
|
136
|
+
on Escape. `AccountMenuItem` is a link (`href`), a POST form (`formAction`), or a button.
|
|
137
|
+
- `Icon`: the GemBoss set, 38 icons on a 20px grid with 1.5px strokes, drawn to sit next to Polaris.
|
|
138
|
+
`ICON_NAMES` lists them. Internal tools cannot use `@shopify/polaris-icons`: its licence only covers
|
|
139
|
+
applications that integrate with Shopify. Draw a missing one HERE, not in the surface: every rail
|
|
140
|
+
icon must keep `fill="none"` on its inner `<g>`, because `.gnav-ico svg { fill: currentColor }`
|
|
141
|
+
outranks an attribute on the `<svg>` itself and paints an outline icon solid.
|
|
142
|
+
|
|
143
|
+
## The catalog: every token, class and component, with its markup
|
|
144
|
+
|
|
145
|
+
`npm run catalog` builds `catalog/index.html` (also shipped in the npm tarball, and built by `prepack`):
|
|
146
|
+
one page (it links `../gemboss-ui.css`, which sits next to it in the repo and the tarball) with the tokens, the kit (`ui-btn`, `gemboss-field`, `ui-tabs`, `ui-table`,
|
|
147
|
+
`ui-modal`…), the shell, sign-in, the assistant's thread rail and the icon set, each beside the exact
|
|
148
|
+
markup that draws it.
|
|
149
|
+
|
|
150
|
+
It exists for the surfaces that are NOT React. gemcare (static HTML), gempress (Python) and GemFactory
|
|
151
|
+
(server-rendered) cannot import a component; they write markup, and until now the only place that
|
|
152
|
+
markup lived was the admin's source. The catalog's markup is rendered from the package, never typed, so
|
|
153
|
+
it cannot disagree with the components; `packages/ui/src/catalog.test.ts` fails if an example writes a class that
|
|
154
|
+
`gemboss-ui.css` does not style. Copy from it; do not restyle what it shows.
|
|
155
|
+
|
|
156
|
+
## Inherit, then customize
|
|
157
|
+
|
|
158
|
+
This package is the BASE (Chris, 28/09/2026). gemwatcher, content-radar, partner-radar,
|
|
159
|
+
gemcommunity and gemcare each inherit it and build their own product on top. Four layers, one owner
|
|
160
|
+
each:
|
|
161
|
+
|
|
162
|
+
| Layer | Lives in | Example |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| 1. Base | `@gemboss/ui` | tokens, kit CSS, topbar + rail + drawer, the gem mark |
|
|
165
|
+
| 2. Theme | the surface's repo, **tokens only** | gemcommunity's iris accent; gemwatcher's paper background |
|
|
166
|
+
| 3. Extension | the surface's repo, composed from the base | gemwatcher's data screens, content-radar's composer, gemcare's chat |
|
|
167
|
+
| 4. Promotion | back into `@gemboss/ui` | anything a SECOND surface needs, e.g. the sign-in screen (5 hand copies until it moved here, 28/09/2026) |
|
|
168
|
+
|
|
169
|
+
Allowed: redefine `--gemboss-*` / `--ui-*` tokens at the root of your surface; pass content through
|
|
170
|
+
the slots and props (`brand`, `center`, `actions`, rail items, icons, headings).
|
|
171
|
+
|
|
172
|
+
The base font is one of those tokens. `gemboss-ui.css` sets `html`, `body` and `button` in
|
|
173
|
+
`--gemboss-font-sans`, the admin's own body stack (Polaris `--p-font-family-sans`: Inter, then the
|
|
174
|
+
system UI font). The rule sits at zero specificity (`:where()`), so a surface re-themes with
|
|
175
|
+
`:root { --gemboss-font-sans: … }` and needs no `body { font-family }` patch. A surface that loads
|
|
176
|
+
its own web font does it itself; the package loads none.
|
|
177
|
+
|
|
178
|
+
Not allowed: copying the base's markup or CSS into your repo, or restyling its classes from outside
|
|
179
|
+
(`.gnav-item { … }` in a consumer). Such an override breaks silently on the next version. If you need
|
|
180
|
+
something the base cannot express, add a token or a prop here, and every surface gets it.
|
|
181
|
+
|
|
182
|
+
## What ships
|
|
183
|
+
|
|
184
|
+
| Path | What |
|
|
185
|
+
|---|---|
|
|
186
|
+
| `gemboss-ui.css` | built at pack time: `apps/admin/scripts/export-ui-css.mjs` (tokens + kit + field + `packages/ui/src/shell.css`, with Polaris `--p-*` resolved to their fallbacks) + `packages/ui/src/app-shell.css` + `packages/ui/src/auth.css` + `packages/ui/src/assistant.css` |
|
|
187
|
+
| `packages/ui/src/shell.css` | the SHARED SHELL (rail, topbar, main, mobile drawer, the assistant's thread rail, popover surface, account chip). The admin's `gemboss-shell.css` @imports it on line 2; the export refuses a shared-shell rule in the admin's sheet or an admin-only rule here (one owner). |
|
|
188
|
+
| `packages/ui/src/**/*.tsx` | the components, as TypeScript source. Next: add `@gemboss/ui` to `transpilePackages`. Vite compiles it as is. |
|
|
189
|
+
|
|
190
|
+
`gemboss-ui.css` is never committed and never edited. To change a colour or a radius, change
|
|
191
|
+
`packages/ui/src/gemboss-tokens.css` / `packages/ui/src/ui.css`; to change the rail, the topbar or the thread rail,
|
|
192
|
+
change `packages/ui/src/shell.css` (since 29/09/2026: before that it lived in the admin's stylesheet and was extracted).
|
|
193
|
+
The admin and the next published version both carry it.
|
|
194
|
+
|
|
195
|
+
## Release
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
cd packages/ui
|
|
199
|
+
npm version patch # or minor / major
|
|
200
|
+
npm publish # prepack builds gemboss-ui.css first
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Publish from a worktree at the MERGED commit on `main`, not from a branch, so the registry and
|
|
204
|
+
`main` hold the same code. The npm user is `gemboss` (owner of the `@gemboss` scope). npm refuses
|
|
205
|
+
to publish (403) from an account without two-factor auth; the account needs 2FA set to
|
|
206
|
+
"Authorization and publishing".
|
|
207
|
+
|
|
208
|
+
Consumers pin a version; a bump is a PR in their repo, not a surprise.
|