anentrypoint-design 0.0.196 → 0.0.197
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/app-shell.css +185 -0
- package/chat.css +134 -0
- package/dist/247420.css +319 -0
- package/dist/247420.js +15 -15
- package/package.json +1 -1
- package/src/components/agent-chat.js +63 -10
- package/src/components/chat.js +19 -2
- package/src/components/context-pane.js +77 -0
- package/src/components/files.js +81 -2
- package/src/components/sessions.js +147 -0
- package/src/components/shell.js +189 -0
- package/src/components.js +8 -1
package/src/components/shell.js
CHANGED
|
@@ -277,6 +277,195 @@ export function AppShell({ topbar, crumb, side, main, status, narrow } = {}) {
|
|
|
277
277
|
);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
// Toggle a named WorkspaceShell column (left rail or right pane). Pure-DOM like
|
|
281
|
+
// toggleSide: WorkspaceShell is stateless chrome, the collapsed class lives on
|
|
282
|
+
// .ws-shell and is read by both CSS and the toggle buttons' aria-expanded.
|
|
283
|
+
function toggleWs(which) {
|
|
284
|
+
const shell = document.querySelector('.ws-shell');
|
|
285
|
+
if (!shell) return;
|
|
286
|
+
const cls = which === 'pane' ? 'ws-pane-collapsed' : 'ws-rail-collapsed';
|
|
287
|
+
const nowCollapsed = shell.classList.toggle(cls);
|
|
288
|
+
const btn = document.querySelector('.ws-' + which + '-toggle');
|
|
289
|
+
if (btn) btn.setAttribute('aria-expanded', nowCollapsed ? 'false' : 'true');
|
|
290
|
+
try {
|
|
291
|
+
localStorage.setItem('ds.ws.' + which, nowCollapsed ? 'collapsed' : 'open');
|
|
292
|
+
} catch (_) {}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Toggle a mobile WorkspaceShell DRAWER (sessions or pane). Distinct from the
|
|
296
|
+
// desktop width-collapse (toggleWs): on mobile the columns are fixed overlays
|
|
297
|
+
// revealed by .ws-sessions-open / .ws-pane-open. Opening one closes the other
|
|
298
|
+
// (only one drawer at a time over the content). Esc + scrim dismiss call this
|
|
299
|
+
// with open=false. Pure-DOM, matching the AppShell toggleSide pattern.
|
|
300
|
+
function toggleWsDrawer(which, open) {
|
|
301
|
+
const shell = document.querySelector('.ws-shell');
|
|
302
|
+
if (!shell) return;
|
|
303
|
+
const cls = which === 'pane' ? 'ws-pane-open' : 'ws-sessions-open';
|
|
304
|
+
const other = which === 'pane' ? 'ws-sessions-open' : 'ws-pane-open';
|
|
305
|
+
const next = open != null ? open : !shell.classList.contains(cls);
|
|
306
|
+
shell.classList.toggle(cls, next);
|
|
307
|
+
if (next) shell.classList.remove(other);
|
|
308
|
+
const btn = document.querySelector('.ws-' + which + '-drawer-toggle');
|
|
309
|
+
if (btn) btn.setAttribute('aria-expanded', next ? 'true' : 'false');
|
|
310
|
+
// When opening, move focus into the drawer and arm an Esc-to-close once.
|
|
311
|
+
if (next) {
|
|
312
|
+
const drawer = shell.querySelector(which === 'pane' ? '.ws-pane' : '.ws-sessions');
|
|
313
|
+
const focusable = drawer && drawer.querySelector('button, a, input, [tabindex]');
|
|
314
|
+
if (focusable) try { focusable.focus(); } catch (_) {}
|
|
315
|
+
const onKey = (e) => { if (e.key === 'Escape') { toggleWsDrawer(which, false); document.removeEventListener('keydown', onKey); if (btn) try { btn.focus(); } catch (_) {} } };
|
|
316
|
+
document.addEventListener('keydown', onKey);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function closeWsDrawers() {
|
|
320
|
+
const shell = document.querySelector('.ws-shell');
|
|
321
|
+
if (!shell) return;
|
|
322
|
+
shell.classList.remove('ws-sessions-open', 'ws-pane-open');
|
|
323
|
+
document.querySelectorAll('.ws-sessions-drawer-toggle, .ws-pane-drawer-toggle').forEach((b) => b.setAttribute('aria-expanded', 'false'));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Read persisted collapse state for a WorkspaceShell column so the layout is
|
|
327
|
+
// predictable across reloads (Claude-Desktop keeps the rail where you left it).
|
|
328
|
+
function wsCollapsed(which, fallback) {
|
|
329
|
+
try {
|
|
330
|
+
const v = localStorage.getItem('ds.ws.' + which);
|
|
331
|
+
if (v === 'collapsed') return true;
|
|
332
|
+
if (v === 'open') return false;
|
|
333
|
+
} catch (_) {}
|
|
334
|
+
return !!fallback;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// WorkspaceShell — a Claude-Desktop / cowork three-(or four-)column app shell.
|
|
338
|
+
//
|
|
339
|
+
// rail : the persistent left workspace nav (icon+label items, collapsible
|
|
340
|
+
// to icon-only). Pass the result of WorkspaceRail() or any vnode.
|
|
341
|
+
// sessions: an OPTIONAL second column (a conversation/session list) shown
|
|
342
|
+
// between the rail and the main content. Null hides it.
|
|
343
|
+
// main : the primary content column (chat thread, files view, dashboard...).
|
|
344
|
+
// pane : an OPTIONAL right context pane (per-conversation context, file
|
|
345
|
+
// preview...). Null hides it; collapsible when present.
|
|
346
|
+
// crumb : an optional thin top chrome bar (breadcrumb + status), spanning
|
|
347
|
+
// the content area only (the rail has its own header).
|
|
348
|
+
// status : an optional footer.
|
|
349
|
+
// narrow : caller's isNarrow() — drives the mobile single-column collapse.
|
|
350
|
+
// railCollapsed / paneCollapsed : initial collapse (persisted state wins).
|
|
351
|
+
//
|
|
352
|
+
// Pure stateless chrome (props in, vnode out). Collapse is DOM-class + a
|
|
353
|
+
// persisted flag, so the host does not have to thread collapse state through its
|
|
354
|
+
// own store. Visual styling lives in app-shell.css (.ws-*).
|
|
355
|
+
export function WorkspaceShell({ rail, sessions, main, pane, crumb, status, narrow,
|
|
356
|
+
railCollapsed = false, paneCollapsed = false,
|
|
357
|
+
railLabel = 'workspace navigation',
|
|
358
|
+
paneLabel = 'context' } = {}) {
|
|
359
|
+
const hasSessions = Boolean(sessions);
|
|
360
|
+
const hasPane = Boolean(pane);
|
|
361
|
+
const railIsCollapsed = wsCollapsed('rail', railCollapsed);
|
|
362
|
+
const paneIsCollapsed = hasPane ? wsCollapsed('pane', paneCollapsed) : true;
|
|
363
|
+
const shellCls = 'ws-shell'
|
|
364
|
+
+ (railIsCollapsed ? ' ws-rail-collapsed' : '')
|
|
365
|
+
+ (hasPane ? '' : ' ws-no-pane')
|
|
366
|
+
+ (hasPane && paneIsCollapsed ? ' ws-pane-collapsed' : '')
|
|
367
|
+
+ (hasSessions ? '' : ' ws-no-sessions')
|
|
368
|
+
+ (narrow ? ' narrow' : '');
|
|
369
|
+
return h('div', { class: shellCls },
|
|
370
|
+
h('a', { href: '#ws-main', class: 'skip-link' }, 'skip to main content'),
|
|
371
|
+
// Left rail column. Its own toggle collapses it to icon-only.
|
|
372
|
+
h('nav', { class: 'ws-rail', role: 'navigation', 'aria-label': railLabel },
|
|
373
|
+
h('button', {
|
|
374
|
+
class: 'ws-rail-toggle', type: 'button',
|
|
375
|
+
// Label reflects the ACTION the click performs (expand when
|
|
376
|
+
// collapsed, collapse when expanded), not a static word - a
|
|
377
|
+
// stale "collapse navigation" on an already-collapsed rail
|
|
378
|
+
// mis-announces the control to AT.
|
|
379
|
+
'aria-label': railIsCollapsed ? 'expand navigation' : 'collapse navigation',
|
|
380
|
+
title: railIsCollapsed ? 'expand navigation' : 'collapse navigation',
|
|
381
|
+
'aria-expanded': railIsCollapsed ? 'false' : 'true',
|
|
382
|
+
onclick: () => toggleWs('rail'),
|
|
383
|
+
}, Icon('menu')),
|
|
384
|
+
rail || null),
|
|
385
|
+
// Tap-scrim behind an open mobile drawer; click anywhere dismisses.
|
|
386
|
+
h('div', { class: 'ws-scrim', 'aria-hidden': 'true', onclick: () => closeWsDrawers() }),
|
|
387
|
+
// Optional sessions column. On mobile it is a drawer; selecting a row
|
|
388
|
+
// (any button click inside) auto-closes it, mirroring AppShell.
|
|
389
|
+
hasSessions
|
|
390
|
+
? h('div', { class: 'ws-sessions', role: 'complementary', 'aria-label': 'conversations',
|
|
391
|
+
onclick: (e) => { if (narrow && e.target.closest('button, a')) closeWsDrawers(); } }, sessions)
|
|
392
|
+
: null,
|
|
393
|
+
// Primary content column, with an optional thin crumb bar on top. On
|
|
394
|
+
// mobile the crumb hosts the drawer toggles (sessions on the left, pane
|
|
395
|
+
// on the right) so both overlay columns are reachable - without them the
|
|
396
|
+
// conversation list and context pane are dead on <=900px.
|
|
397
|
+
h('div', { class: 'ws-content' },
|
|
398
|
+
crumb
|
|
399
|
+
? h('div', { class: 'ws-crumb' },
|
|
400
|
+
hasSessions ? h('button', {
|
|
401
|
+
class: 'ws-drawer-toggle ws-sessions-drawer-toggle', type: 'button',
|
|
402
|
+
'aria-label': 'toggle conversations', 'aria-expanded': 'false',
|
|
403
|
+
onclick: () => toggleWsDrawer('sessions'),
|
|
404
|
+
}, Icon('thread')) : null,
|
|
405
|
+
h('div', { class: 'ws-crumb-main' }, crumb),
|
|
406
|
+
hasPane ? h('button', {
|
|
407
|
+
class: 'ws-drawer-toggle ws-pane-drawer-toggle', type: 'button',
|
|
408
|
+
'aria-label': 'toggle context pane', 'aria-expanded': 'false',
|
|
409
|
+
onclick: () => toggleWsDrawer('pane'),
|
|
410
|
+
}, Icon('page')) : null)
|
|
411
|
+
: null,
|
|
412
|
+
h('main', { class: 'ws-main' + (narrow ? ' narrow' : ''), id: 'ws-main', tabindex: '-1' },
|
|
413
|
+
...(Array.isArray(main) ? main : [main])),
|
|
414
|
+
status || null),
|
|
415
|
+
// Optional right context pane with its own collapse toggle.
|
|
416
|
+
hasPane
|
|
417
|
+
? h('aside', { class: 'ws-pane', role: 'complementary', 'aria-label': paneLabel },
|
|
418
|
+
h('button', {
|
|
419
|
+
class: 'ws-pane-toggle', type: 'button',
|
|
420
|
+
'aria-label': paneIsCollapsed ? 'show context pane' : 'hide context pane',
|
|
421
|
+
title: paneIsCollapsed ? 'show context pane' : 'hide context pane',
|
|
422
|
+
'aria-expanded': paneIsCollapsed ? 'false' : 'true',
|
|
423
|
+
onclick: () => toggleWs('pane'),
|
|
424
|
+
}, Icon(paneIsCollapsed ? 'chevron-left' : 'chevron-right')),
|
|
425
|
+
pane)
|
|
426
|
+
: null,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// WorkspaceRail — the contents of the WorkspaceShell left rail: a brand/header,
|
|
431
|
+
// a primary action (New chat), and a list of nav items. Each item collapses to
|
|
432
|
+
// an icon when the rail is collapsed (the label is kept in the DOM for AT and
|
|
433
|
+
// shown via CSS when expanded).
|
|
434
|
+
//
|
|
435
|
+
// brand : short product name shown in the rail header.
|
|
436
|
+
// action : { label, icon, onClick } a prominent primary button (New chat).
|
|
437
|
+
// items : [{ key, label, icon, active, count, onClick }] nav entries.
|
|
438
|
+
// footer : optional vnode pinned to the rail bottom (e.g. settings/theme).
|
|
439
|
+
export function WorkspaceRail({ brand = '247420', action, items = [], footer } = {}) {
|
|
440
|
+
return h('div', { class: 'ws-rail-inner' },
|
|
441
|
+
h('div', { class: 'ws-rail-head' },
|
|
442
|
+
h('span', { class: 'ws-rail-brand' }, brand)),
|
|
443
|
+
action
|
|
444
|
+
? h('button', {
|
|
445
|
+
class: 'ws-rail-action', type: 'button',
|
|
446
|
+
'aria-label': action.label,
|
|
447
|
+
onclick: action.onClick || null,
|
|
448
|
+
}, action.icon ? Icon(action.icon) : null, h('span', { class: 'ws-rail-action-label' }, action.label))
|
|
449
|
+
: null,
|
|
450
|
+
h('ul', { class: 'ws-rail-nav', role: 'list' },
|
|
451
|
+
...items.map((it) => h('li', { key: it.key || it.label, role: 'listitem' },
|
|
452
|
+
h('button', {
|
|
453
|
+
type: 'button',
|
|
454
|
+
class: 'ws-rail-item' + (it.active ? ' active' : ''),
|
|
455
|
+
'aria-current': it.active ? 'page' : null,
|
|
456
|
+
'aria-label': it.label + (it.count ? ' (' + it.count + ')' : ''),
|
|
457
|
+
title: it.label,
|
|
458
|
+
onclick: it.onClick || null,
|
|
459
|
+
},
|
|
460
|
+
it.icon ? Icon(it.icon) : h('span', { class: 'ws-rail-item-glyph', 'aria-hidden': 'true' }),
|
|
461
|
+
h('span', { class: 'ws-rail-item-label' }, it.label),
|
|
462
|
+
(it.count != null && it.count !== 0 && it.count !== '0')
|
|
463
|
+
? h('span', { class: 'ws-rail-item-count', 'aria-hidden': 'true' }, String(it.count))
|
|
464
|
+
: null)))),
|
|
465
|
+
footer ? h('div', { class: 'ws-rail-foot' }, footer) : null,
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
280
469
|
export function Heading({ level = 1, children, style = '', 'aria-level': ariaLevel }) {
|
|
281
470
|
return h('h' + level, { style, 'aria-level': ariaLevel != null ? String(ariaLevel) : null }, children);
|
|
282
471
|
}
|
package/src/components.js
CHANGED
|
@@ -6,6 +6,7 @@ export const h = webjsx.createElement;
|
|
|
6
6
|
export {
|
|
7
7
|
Brand, Chip, Btn, Glyph, Icon, IconButton, Badge,
|
|
8
8
|
Topbar, Crumb, Side, Status, AppShell,
|
|
9
|
+
WorkspaceShell, WorkspaceRail,
|
|
9
10
|
Heading, Lede, Dot, Rail
|
|
10
11
|
} from './components/shell.js';
|
|
11
12
|
|
|
@@ -26,9 +27,15 @@ export {
|
|
|
26
27
|
|
|
27
28
|
export { AgentChat } from './components/agent-chat.js';
|
|
28
29
|
|
|
30
|
+
export {
|
|
31
|
+
ConversationList, SessionCard, SessionDashboard
|
|
32
|
+
} from './components/sessions.js';
|
|
33
|
+
|
|
34
|
+
export { ContextPane } from './components/context-pane.js';
|
|
35
|
+
|
|
29
36
|
export {
|
|
30
37
|
fileGlyph, fmtFileSize,
|
|
31
|
-
FileIcon, FileRow, FileGrid, FileToolbar,
|
|
38
|
+
FileIcon, FileRow, FileGrid, FileSkeleton, sortFiles, FileToolbar,
|
|
32
39
|
DropZone, UploadProgress, EmptyState, BreadcrumbPath
|
|
33
40
|
} from './components/files.js';
|
|
34
41
|
|