anentrypoint-design 1.0.14 → 1.0.16
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/dist/247420.css +40 -3
- package/dist/247420.js +29 -29
- package/package.json +1 -1
- package/src/components/shell/app-shell.js +48 -11
- package/src/components/shell/icons.js +1 -0
- package/src/css/app-shell/kits-appended.css +4 -0
- package/src/css/app-shell/primitives.css +2 -2
- package/src/css/app-shell/topbar.css +34 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
|
@@ -15,9 +15,18 @@ const h = webjsx.createElement;
|
|
|
15
15
|
// call site having to remember to wire ThemeToggle() in by hand. Pass
|
|
16
16
|
// `themeToggle: false` to opt a shell out (e.g. a kit that renders its own).
|
|
17
17
|
export function Topbar({ brand = '247420', leaf = '', items = [], active = '', onNav, search, themeToggle = true } = {}) {
|
|
18
|
+
// `search` is either a plain placeholder string (Topbar renders the
|
|
19
|
+
// default uncontrolled input itself, and owns the icon below) or a
|
|
20
|
+
// caller-built VElement (has .type/.props — e.g. SearchInput(), a
|
|
21
|
+
// component that already renders its own icon + input + clear button).
|
|
22
|
+
// A VElement renders as-is with NO extra wrapper: SearchInput's own
|
|
23
|
+
// markup already covers the icon and the field, so wrapping it in
|
|
24
|
+
// Topbar's own '.app-search' icon+label previously doubled the search
|
|
25
|
+
// glyph (one from Topbar, one from inside SearchInput).
|
|
26
|
+
const isElement = search && typeof search === 'object' && 'type' in search;
|
|
18
27
|
return h('header', { class: 'app-topbar', role: 'banner' },
|
|
19
28
|
Brand({ name: brand, leaf }),
|
|
20
|
-
search ? h('label', { class: 'app-search' },
|
|
29
|
+
isElement ? search : (search ? h('label', { class: 'app-search' },
|
|
21
30
|
// Line-icon, not the literal word "search" as a pseudo-glyph: the
|
|
22
31
|
// text stand-in inherited .app-search .icon's 0.6 opacity, which
|
|
23
32
|
// dropped --fg-3 to 3.74:1 on --bg-2 and failed AA as real text.
|
|
@@ -25,15 +34,8 @@ export function Topbar({ brand = '247420', leaf = '', items = [], active = '', o
|
|
|
25
34
|
// contrast rule no longer applies to it and the affordance stops
|
|
26
35
|
// depending on a colour value at all.
|
|
27
36
|
h('span', { class: 'icon', 'aria-hidden': 'true' }, Icon('search', { size: 15 })),
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// .type/.props — e.g. a controlled <input> wired to app state)
|
|
31
|
-
// rendered as-is. Stringifying a VElement into placeholder/
|
|
32
|
-
// aria-label previously produced literal "[object Object]" text.
|
|
33
|
-
(search && typeof search === 'object' && 'type' in search)
|
|
34
|
-
? search
|
|
35
|
-
: h('input', { type: 'search', name: 'q', placeholder: search, 'aria-label': `search ${search}` })
|
|
36
|
-
) : null,
|
|
37
|
+
h('input', { type: 'search', name: 'q', placeholder: search, 'aria-label': `search ${search}` })
|
|
38
|
+
) : null),
|
|
37
39
|
h('nav', { 'aria-label': 'main navigation' }, ...items.map(([label, href]) => {
|
|
38
40
|
const cleanLabel = String(label).replace(' ->', '');
|
|
39
41
|
return h('a', {
|
|
@@ -149,8 +151,43 @@ export function Side({ sections = [] } = {}) {
|
|
|
149
151
|
}));
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
// Collapse state persists per-origin so the choice survives navigation
|
|
155
|
+
// between kit pages (the status bar is chrome, not per-page content).
|
|
156
|
+
const STATUS_COLLAPSE_KEY = 'ds-status-collapsed';
|
|
157
|
+
function isStatusCollapsed() {
|
|
158
|
+
try { return localStorage.getItem(STATUS_COLLAPSE_KEY) === '1'; } catch (_) { return false; }
|
|
159
|
+
}
|
|
160
|
+
function toggleStatusCollapsed(fromEl) {
|
|
161
|
+
const app = fromEl && fromEl.closest && fromEl.closest('.app');
|
|
162
|
+
const bar = app && app.querySelector('.app-status');
|
|
163
|
+
if (!bar) return;
|
|
164
|
+
const next = !bar.classList.contains('is-collapsed');
|
|
165
|
+
bar.classList.toggle('is-collapsed', next);
|
|
166
|
+
const btn = bar.querySelector('.app-status-toggle');
|
|
167
|
+
if (btn) btn.setAttribute('aria-expanded', next ? 'false' : 'true');
|
|
168
|
+
// .app-main's bottom padding mirrors the bar's real height via this var
|
|
169
|
+
// (set on .app, read by .app-main's padding-bottom) so collapsing frees
|
|
170
|
+
// the reserved clearance instead of leaving a dead gap.
|
|
171
|
+
if (app) app.style.setProperty('--app-status-h-live', next ? 'var(--space-4)' : 'var(--app-status-h)');
|
|
172
|
+
try { localStorage.setItem(STATUS_COLLAPSE_KEY, next ? '1' : '0'); } catch (_) { /* private mode: state just won't persist */ }
|
|
173
|
+
}
|
|
174
|
+
|
|
152
175
|
export function Status({ left = [], right = [] } = {}) {
|
|
153
|
-
|
|
176
|
+
const collapsed = isStatusCollapsed();
|
|
177
|
+
// Sync .app-main's reserved clearance to the persisted collapse state on
|
|
178
|
+
// first render, matching what toggleStatusCollapsed sets on click -- the
|
|
179
|
+
// ref runs on mount, before paint, so there's no expanded-then-collapse flash.
|
|
180
|
+
const syncLiveVar = (el) => {
|
|
181
|
+
if (!el) return;
|
|
182
|
+
const app = el.closest('.app');
|
|
183
|
+
if (app) app.style.setProperty('--app-status-h-live', collapsed ? 'var(--space-4)' : 'var(--app-status-h)');
|
|
184
|
+
};
|
|
185
|
+
return h('footer', { class: 'app-status' + (collapsed ? ' is-collapsed' : ''), role: 'contentinfo', ref: syncLiveVar },
|
|
186
|
+
h('button', {
|
|
187
|
+
class: 'app-status-toggle', type: 'button',
|
|
188
|
+
'aria-label': 'toggle status bar', 'aria-expanded': collapsed ? 'false' : 'true',
|
|
189
|
+
onclick: (e) => toggleStatusCollapsed(e.currentTarget),
|
|
190
|
+
}, Icon(collapsed ? 'chevron-up' : 'chevron-down', { size: 12 })),
|
|
154
191
|
...left.map((t, i) => h('span', { key: 'l' + i, class: 'item' }, t)),
|
|
155
192
|
h('span', { key: 'spread', class: 'spread', 'aria-hidden': 'true' }),
|
|
156
193
|
...right.map((t, i) => h('span', { key: 'r' + i, class: 'item' }, t))
|
|
@@ -20,6 +20,7 @@ export const ICON_PATHS = {
|
|
|
20
20
|
screen: '<rect x="3" y="4" width="18" height="13" rx="2"/><path d="M8 21h8M12 17v4"/>',
|
|
21
21
|
phone: '<path d="M5 4h3l2 5-2 1a11 11 0 0 0 5 5l1-2 5 2v3a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2z"/>',
|
|
22
22
|
members: '<circle cx="9" cy="8" r="3"/><path d="M3 20a6 6 0 0 1 12 0M16 6a3 3 0 0 1 0 6M21 20a6 6 0 0 0-4-5.7"/>',
|
|
23
|
+
user: '<circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/>',
|
|
23
24
|
menu: '<path d="M4 6h16M4 12h16M4 18h16"/>',
|
|
24
25
|
settings: '<circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M2 12h3M19 12h3M4.9 19.1 7 17M17 7l2.1-2.1"/>',
|
|
25
26
|
paperclip: '<path d="M21 11.5 12.5 20a5 5 0 0 1-7-7l8-8a3.5 3.5 0 0 1 5 5l-8 8a2 2 0 0 1-3-3l7.5-7.5"/>',
|
|
@@ -280,6 +280,10 @@
|
|
|
280
280
|
}
|
|
281
281
|
.ds-err-numeral {
|
|
282
282
|
font-family: var(--ff-mono); font-size: clamp(56px, 20vw, 120px);
|
|
283
|
+
/* Shares the headline's weight (base.css h1: font-weight:600) so the two
|
|
284
|
+
most important elements on the page read as one weight relationship
|
|
285
|
+
instead of a thin numeral against a bold headline. */
|
|
286
|
+
font-weight: 600;
|
|
283
287
|
line-height: 1; color: var(--panel-text-3); letter-spacing: -0.03em;
|
|
284
288
|
}
|
|
285
289
|
.ds-err-path {
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
.btn { background: var(--bg-2); color: var(--fg); border-color: var(--rule); }
|
|
18
18
|
.btn:hover { background: var(--bg-3); color: var(--fg); transform: translateY(-1px); box-shadow: var(--shadow-1); }
|
|
19
19
|
.btn-primary {
|
|
20
|
-
background: var(--
|
|
20
|
+
background: var(--accent); color: var(--accent-fg);
|
|
21
21
|
font-weight: 700;
|
|
22
22
|
}
|
|
23
|
-
.btn-primary:hover { background: var(--fg); color: var(--
|
|
23
|
+
.btn-primary:hover { background: var(--fg); color: var(--bg); transform: translateY(-1px); box-shadow: var(--shadow-2); }
|
|
24
24
|
.btn-ghost { background: transparent; color: var(--fg); box-shadow: inset 0 0 0 2px var(--fg); }
|
|
25
25
|
.btn-ghost:hover { background: color-mix(in oklab, var(--fg) 12%, transparent); transform: translateY(-1px); }
|
|
26
26
|
.btn:active, .btn-primary:active, .btn-ghost:active { transform: translateY(1px); box-shadow: none; }
|
|
@@ -79,6 +79,14 @@ body.canvas-host { background: transparent !important; }
|
|
|
79
79
|
.app-chrome > .app-crumb > .crumb-right { margin-left: auto; }
|
|
80
80
|
.app-topbar > .brand { flex: 0 0 auto; }
|
|
81
81
|
.app-topbar > .app-search { flex: 1 1 auto; }
|
|
82
|
+
/* SearchInput() passed directly as Topbar's search prop renders with no
|
|
83
|
+
Topbar-owned wrapper (see Topbar's isElement branch). Its own outer span
|
|
84
|
+
(.ds-search-input-wrap) is display:contents, so .ds-search-field becomes
|
|
85
|
+
the actual direct .app-topbar flex-item child — an inline-flex box that
|
|
86
|
+
otherwise shrinks to its content and starves the input's own width:100%
|
|
87
|
+
of anything to grow into, truncating the placeholder. Matches
|
|
88
|
+
.app-search's flex-basis above so both search-slot shapes size the same. */
|
|
89
|
+
.app-topbar > .ds-search-field { flex: 1 1 auto; max-width: 360px; min-width: 0; }
|
|
82
90
|
.app-topbar > nav { margin-left: auto; }
|
|
83
91
|
/* Utility cluster grouping: the theme toggle sits after the repo-info nav
|
|
84
92
|
links with no visual separation between the two groups, so the row read
|
|
@@ -310,7 +318,13 @@ body.canvas-host { background: transparent !important; }
|
|
|
310
318
|
}
|
|
311
319
|
|
|
312
320
|
.app-main {
|
|
313
|
-
padding
|
|
321
|
+
/* Bottom padding reserves clearance equal to the status bar's own height so
|
|
322
|
+
the last scrolled child never sits flush behind .app-status (a flex
|
|
323
|
+
sibling pinned below .app-main in the .app column, not a child of it).
|
|
324
|
+
--app-status-h-live tracks the bar's collapsed/expanded state (set by
|
|
325
|
+
the toggle in app-shell.js) and falls back to the full bar height when
|
|
326
|
+
unset (bar starts expanded / JS hasn't run yet). */
|
|
327
|
+
padding: var(--space-5) var(--pad-x) var(--app-status-h-live, var(--app-status-h));
|
|
314
328
|
min-width: 0;
|
|
315
329
|
/* Full-height flex column so a single flex:1 child (e.g. .chat) fills the
|
|
316
330
|
region between crumb and status instead of floating at content height. */
|
|
@@ -458,6 +472,25 @@ body.canvas-host { background: transparent !important; }
|
|
|
458
472
|
agent target) drop in priority order instead of breaking mid-word onto a
|
|
459
473
|
second line. */
|
|
460
474
|
.app-status { flex-wrap: nowrap; overflow: hidden; }
|
|
475
|
+
|
|
476
|
+
/* Collapse control — the status bar permanently claims --app-status-h of
|
|
477
|
+
viewport on every page; this toggle lets it shrink to a thin reachable
|
|
478
|
+
strip instead. The bar itself stays a fixed-height flex child (never
|
|
479
|
+
display:none) so the toggle remains reachable to re-expand. */
|
|
480
|
+
.app-status-toggle {
|
|
481
|
+
flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
|
|
482
|
+
width: 18px; height: 18px; padding: 0; margin-right: var(--space-1);
|
|
483
|
+
background: transparent; border: none; color: var(--fg-3); cursor: pointer;
|
|
484
|
+
border-radius: var(--r-0);
|
|
485
|
+
}
|
|
486
|
+
.app-status-toggle:hover { color: var(--fg); background: var(--bg-3); }
|
|
487
|
+
.app-status-toggle:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
|
|
488
|
+
.app-status.is-collapsed {
|
|
489
|
+
height: var(--space-4); min-height: var(--space-4);
|
|
490
|
+
overflow: hidden;
|
|
491
|
+
}
|
|
492
|
+
.app-status.is-collapsed .item,
|
|
493
|
+
.app-status.is-collapsed .spread { display: none; }
|
|
461
494
|
@media (max-width: 1100px) {
|
|
462
495
|
.app-status .item:last-of-type:not(:only-of-type) { display: none; }
|
|
463
496
|
}
|