@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 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.