@gemboss/ui 0.8.1 → 0.12.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 +122 -0
- package/README.md +212 -3
- package/catalog/index.html +140 -7
- package/gemboss-ui.css +199 -69
- package/package.json +12 -3
- package/src/app-shell.css +89 -0
- package/src/editor.css +1099 -0
- package/src/gemboss-tokens.css +1 -1
- package/src/index.ts +2 -0
- package/src/kit-plain.css +39 -12
- package/src/kit-plain.js +118 -1
- package/src/nav-dwell.ts +6 -0
- package/src/next/index.ts +4 -0
- package/src/next/nav.tsx +142 -0
- package/src/route-holding.tsx +65 -0
- package/src/shell.tsx +12 -7
- package/src/theme-dark.css +1 -0
- package/src/ui.css +67 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "GemBoss design system for every surface in the fleet: the admin shell (topbar + rail + drawer) as React components, and the admin stylesheet exported as one CSS file.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"./shell.css": "./src/shell.css",
|
|
21
21
|
"./chat.css": "./src/chat.css",
|
|
22
22
|
"./chat-composer.css": "./src/chat-composer.css",
|
|
23
|
+
"./editor.css": "./src/editor.css",
|
|
23
24
|
"./auth.css": "./src/auth.css",
|
|
24
25
|
"./kit-plain.css": "./src/kit-plain.css",
|
|
25
26
|
"./kit-plain.js": "./src/kit-plain.js",
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
"./assistant.css": "./src/assistant.css",
|
|
29
30
|
"./portal.css": "./src/portal.css",
|
|
30
31
|
"./theme-dark.css": "./src/theme-dark.css",
|
|
31
|
-
"./icon.svg": "./src/icon.svg"
|
|
32
|
+
"./icon.svg": "./src/icon.svg",
|
|
33
|
+
"./next": "./src/next/index.ts"
|
|
32
34
|
},
|
|
33
35
|
"files": [
|
|
34
36
|
"src/**/*.ts",
|
|
@@ -39,7 +41,8 @@
|
|
|
39
41
|
"!src/**/*.test.*",
|
|
40
42
|
"gemboss-ui.css",
|
|
41
43
|
"catalog/index.html",
|
|
42
|
-
"README.md"
|
|
44
|
+
"README.md",
|
|
45
|
+
"CHANGELOG.md"
|
|
43
46
|
],
|
|
44
47
|
"scripts": {
|
|
45
48
|
"build:css": "node scripts/build-css.mjs",
|
|
@@ -49,8 +52,14 @@
|
|
|
49
52
|
"fleet": "node scripts/fleet.mjs"
|
|
50
53
|
},
|
|
51
54
|
"peerDependencies": {
|
|
55
|
+
"next": ">=15.3",
|
|
52
56
|
"react": ">=18"
|
|
53
57
|
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"next": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
54
63
|
"publishConfig": {
|
|
55
64
|
"access": "public"
|
|
56
65
|
},
|
package/src/app-shell.css
CHANGED
|
@@ -26,6 +26,19 @@
|
|
|
26
26
|
/* Headings get their own line (Polaris heading line-heights are ~1.25-1.4): inheriting the body's 20px made a wrapped 22px
|
|
27
27
|
* title overlap itself (gempress, 0.5.1). */
|
|
28
28
|
:where(h1, h2, h3) { letter-spacing: -0.01em; line-height: 1.3; }
|
|
29
|
+
/* Polaris' heading reset (0.12.0, 05/10/2026): in the admin an <h2> or a <p> with no class is body text with no margin,
|
|
30
|
+
* because Polaris' global sheet sets `h1…h6, p { margin: 0; font-size: 1em; font-weight: regular }` and every heading
|
|
31
|
+
* that looks like one is a Text variant. Surfaces had the browser's instead: a bare <h2> at 19.5px bold with 16px margins
|
|
32
|
+
* sat in GemFactory and taskdesk pages beside kit cards titled 14px. Here, only INSIDE the shell (`.gemboss-platform-
|
|
33
|
+
* shell`, the AppShell root every surface on the kit renders): a login page or a public page keeps its own type. Never
|
|
34
|
+
* inside an article the surface shows: `.ui-prose` or `[data-content]` (the contract), and the wrappers the fleet already
|
|
35
|
+
* uses for rendered HTML, read on 05/10/2026, by exact name (gemcommunity `md-body` and `recap-md`,
|
|
36
|
+
* content-radar `article-body`, the assistants' `asst-md`; any class containing `prose` or `markdown`, any case, so ProseMirror too). A NEW wrapper: mark it `ui-prose` or
|
|
37
|
+
* `data-content`. scripts/density.mjs skips exactly this list (ARTICLE); density.test.ts fails if the two differ. A post body keeps the browser's scale. Only an element WITHOUT a class: a surface's `h2.gf-title` that sets only its size keeps the
|
|
38
|
+
* browser's bold and margins (a zero-specificity author rule still beats the browser's own sheet, review #3787). */
|
|
39
|
+
:where(.gemboss-platform-shell) :where(h1, h2, h3, h4, h5, h6, p):where(:not([class])):not(:where(.ui-prose, [data-content], [class*="prose" i], [class*="markdown" i], .md-body, .recap-md, .asst-md, .article-body) *) {
|
|
40
|
+
margin: 0; font-size: 1em; font-weight: inherit; line-height: inherit;
|
|
41
|
+
}
|
|
29
42
|
|
|
30
43
|
.gemboss-topbar-start { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
31
44
|
.gemboss-topbar-center { display: flex; justify-content: center; min-width: 0; }
|
|
@@ -67,6 +80,82 @@
|
|
|
67
80
|
}
|
|
68
81
|
.gnav-item.active .gnav-count { background: var(--gemboss-primary); color: var(--gemboss-on-primary); }
|
|
69
82
|
|
|
83
|
+
/* ── Navigation pending (0.10.0): a click answers on the first frame ─────────────────────────────────────────────
|
|
84
|
+
* Chris 05/10/2026: content-radar's rail (the icon turns into a spinner the moment you click, sub items too) is the
|
|
85
|
+
* standard for every surface. Measured 04/10 on content-radar with a server ~280 ms away: nothing on screen changed
|
|
86
|
+
* for 321-503 ms after a click; with this, 6-8 ms.
|
|
87
|
+
*
|
|
88
|
+
* ONE state, three ways to set it, so a React rail, a Next rail and a server-rendered rail look the same:
|
|
89
|
+
* - `aria-busy="true"` on the item: RailItem / RailSubItem `pending` (React), kit-plain.js on a followed link;
|
|
90
|
+
* - a `.gnav-pending` child: NavLink from `@gemboss/ui/next` (Next's useLinkStatus is only readable INSIDE the
|
|
91
|
+
* link, so the link cannot set its own attribute; it renders this status text and `:has()` reads it).
|
|
92
|
+
* `:is()` is forgiving: a browser without `:has()` still honours the aria-busy half of each rule.
|
|
93
|
+
* The icon keeps its box (visibility, not display), so the label never moves. Here and not in shell.css: the
|
|
94
|
+
* admin's shell sheet has <1 KB of first-load headroom, and the admin answers a click with its own holding screen.
|
|
95
|
+
*/
|
|
96
|
+
.gnav-item:is([aria-busy="true"], :has(> .gnav-pending)),
|
|
97
|
+
.gnav-subitem:is([aria-busy="true"], :has(> .gnav-pending)) { cursor: progress; }
|
|
98
|
+
.gnav-item:is([aria-busy="true"], :has(> .gnav-pending)) > .gnav-ico > * { visibility: hidden; }
|
|
99
|
+
.gnav-item:is([aria-busy="true"], :has(> .gnav-pending)) > .gnav-ico::after,
|
|
100
|
+
.gnav-subitem:is([aria-busy="true"], :has(> .gnav-pending))::after,
|
|
101
|
+
.gb-pending-mark {
|
|
102
|
+
content: ""; display: inline-block; vertical-align: middle; grid-area: 1 / 1; flex: none; box-sizing: border-box;
|
|
103
|
+
width: 20px; height: 20px; border-radius: 50%;
|
|
104
|
+
/* the item's own ink, so the spinner follows the active and hover colours */
|
|
105
|
+
border: 2px solid color-mix(in srgb, currentColor 18%, transparent); border-top-color: currentColor;
|
|
106
|
+
animation: gnav-spin 0.7s linear infinite;
|
|
107
|
+
}
|
|
108
|
+
/* A sub item has no icon box: a 12px spinner in its 44px indent, just before the label, out of the flow. At the row's end
|
|
109
|
+
* it would be a third item in a space-between row and push a trailing count to the middle (review #3777). */
|
|
110
|
+
.gnav-subitem:is([aria-busy="true"], :has(> .gnav-pending))::after {
|
|
111
|
+
position: absolute; left: 22px; top: 50%; margin-top: -6px; width: 12px; height: 12px; border-width: 1.5px;
|
|
112
|
+
}
|
|
113
|
+
@keyframes gnav-spin { to { transform: rotate(360deg); } }
|
|
114
|
+
/* Slower, never off: it is the only sign that the click was heard. */
|
|
115
|
+
@media (prefers-reduced-motion: reduce) {
|
|
116
|
+
.gnav-item:is([aria-busy="true"], :has(> .gnav-pending)) > .gnav-ico::after,
|
|
117
|
+
.gnav-subitem:is([aria-busy="true"], :has(> .gnav-pending))::after,
|
|
118
|
+
.gb-pending-mark { animation-duration: 2s; }
|
|
119
|
+
}
|
|
120
|
+
/* A link in the PAGE (a list row, a card, a tab) opts in with `data-gb-pending`: PendingLink (`@gemboss/ui/next`) or
|
|
121
|
+
* kit-plain.js put `span.gb-pending-mark` (role=status) inside it while the page loads. "inline" (default): 12px after
|
|
122
|
+
* the text. "corner": 16px in the top right corner, out of the flow, for a link that wraps a whole card (a grid card
|
|
123
|
+
* gains no column). Marked by attribute, not class: a surface that colours bare links with `a:not([class])` keeps it. */
|
|
124
|
+
a[data-gb-pending]:has(> .gb-pending-mark), a[data-gb-pending][aria-busy="true"] { cursor: progress; }
|
|
125
|
+
a[data-gb-pending] > .gb-pending-mark { display: inline-block; width: 12px; height: 12px; border-width: 1.5px; margin-left: 6px; vertical-align: -1px; }
|
|
126
|
+
a[data-gb-pending="corner"] { position: relative; }
|
|
127
|
+
a[data-gb-pending="corner"] > .gb-pending-mark { position: absolute; top: 12px; right: 12px; width: 16px; height: 16px; margin: 0; }
|
|
128
|
+
/* RouteHolding (./route-holding.tsx): a route group's loading boundary. The kit's skeleton lines in the content column,
|
|
129
|
+
* one shape for every route: a title bar, then four lines of falling length (the admin's holding screen). */
|
|
130
|
+
.gb-route-holding { padding-top: 4px; }
|
|
131
|
+
.gb-route-holding .ui-skeleton { gap: 16px; }
|
|
132
|
+
.gb-route-holding__heading { margin: 0 0 20px; font-size: 20px; font-weight: 650; line-height: 1.3; color: var(--gemboss-text); }
|
|
133
|
+
.gb-route-holding .ui-skeleton__line { height: 10px; }
|
|
134
|
+
.gb-route-holding .ui-skeleton__line.gb-route-holding__title { height: 20px; width: 34%; margin-bottom: 8px; }
|
|
135
|
+
.gb-route-holding .ui-skeleton__line:nth-last-child(4) { width: 46%; }
|
|
136
|
+
.gb-route-holding .ui-skeleton__line:nth-last-child(3) { width: 92%; }
|
|
137
|
+
.gb-route-holding .ui-skeleton__line:nth-last-child(2) { width: 78%; }
|
|
138
|
+
.gb-route-holding .ui-skeleton__line:nth-last-child(1) { width: 61%; }
|
|
139
|
+
/* RouteCover: the same frame over the content area from the click until the navigation commits. Shown only while
|
|
140
|
+
* `html[data-gb-navigating]` (set by the @gemboss/ui/next links while pending). No grace delay: the target is the new
|
|
141
|
+
* screen within 100 ms of the click, and a 40 ms grace measured 120-132 ms on content-radar's rail (05/10/2026). The
|
|
142
|
+
* 80 ms fade is what a router cache hit (20-90 ms) sees of it: a faint, partial frame, not a full flash.
|
|
143
|
+
* z 5: above the outgoing page, below the rail, the topbar (300) and the assistant (290). */
|
|
144
|
+
.gb-route-cover { display: none; }
|
|
145
|
+
html[data-gb-navigating] .gb-route-cover {
|
|
146
|
+
display: block; position: fixed; inset: 56px 0 0 240px; z-index: 5; overflow: hidden; box-sizing: border-box;
|
|
147
|
+
padding: 24px; background: var(--gemboss-app-bg); pointer-events: none;
|
|
148
|
+
animation: gb-route-cover-in 0.08s ease-out both;
|
|
149
|
+
}
|
|
150
|
+
@keyframes gb-route-cover-in { from { opacity: 0; } to { opacity: 1; } }
|
|
151
|
+
@media (max-width: 760px) { html[data-gb-navigating] .gb-route-cover { left: 0; padding: 16px; } }
|
|
152
|
+
@media (prefers-reduced-motion: reduce) { html[data-gb-navigating] .gb-route-cover { animation-duration: 0s; } }
|
|
153
|
+
/* The status text a NavLink renders inside a rail item: read, not seen. */
|
|
154
|
+
.gnav-pending {
|
|
155
|
+
position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
|
|
156
|
+
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
|
|
157
|
+
}
|
|
158
|
+
|
|
70
159
|
/* ── BrandLockup (./brand.tsx) ─────────────────────────────────────────────────────────────────
|
|
71
160
|
* The admin's `.gemboss-brand-lockup` box (40px tall, 8px padding, hover wash) around the wordmark's
|
|
72
161
|
* own proportions. Not that class itself: its ≤460px rule swaps in a background image at an
|