anentrypoint-design 0.0.145 → 0.0.146

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.145",
3
+ "version": "0.0.146",
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",
@@ -279,6 +279,106 @@ export function Banner({ tone = 'info', message, visible, actionLabel, onAction,
279
279
  );
280
280
  }
281
281
 
282
+ function fmtRelTime(ts) {
283
+ const t = Number(ts) || 0;
284
+ if (!t) return '';
285
+ const ms = t > 1e12 ? t : t * 1000;
286
+ const d = Math.max(0, Date.now() - ms);
287
+ const m = Math.floor(d / 60000);
288
+ if (m < 1) return 'now';
289
+ if (m < 60) return m + 'm';
290
+ const hr = Math.floor(m / 60);
291
+ if (hr < 24) return hr + 'h';
292
+ return Math.floor(hr / 24) + 'd';
293
+ }
294
+
295
+ export function ThreadPanel({ threads = [], activeId = null, title = 'Threads', onSelect, onCreate, onClose } = {}) {
296
+ const list = Array.isArray(threads) ? threads : [];
297
+ return h('div', { class: 'cm-thread-panel', role: 'complementary', 'aria-label': title },
298
+ h('div', { class: 'cm-tp-head' },
299
+ h('span', { class: 'cm-tp-title' }, title),
300
+ h('div', { class: 'cm-tp-head-actions' },
301
+ onCreate ? h('button', { type: 'button', class: 'cm-tp-new', 'aria-label': 'new thread', title: 'New thread', onclick: onCreate }, '+') : null,
302
+ onClose ? h('button', { type: 'button', class: 'cm-tp-close', 'aria-label': 'close', title: 'Close', onclick: onClose }, '✕') : null
303
+ )
304
+ ),
305
+ h('div', { class: 'cm-tp-list' },
306
+ list.length
307
+ ? list.map(t => h('button', {
308
+ type: 'button', key: 'tp-' + t.id,
309
+ class: 'cm-tp-item' + (t.id === activeId ? ' is-active' : '') + (t.unread ? ' is-unread' : ''),
310
+ onclick: () => onSelect && onSelect(t.id)
311
+ },
312
+ t.unread ? h('span', { class: 'cm-tp-dot', 'aria-hidden': 'true' }) : null,
313
+ h('span', { class: 'cm-tp-item-title' }, t.title || '(untitled)'),
314
+ t.lastMessage ? h('span', { class: 'cm-tp-item-snippet' }, t.lastMessage) : null,
315
+ h('span', { class: 'cm-tp-item-meta' },
316
+ t.author ? h('span', { class: 'cm-tp-item-author' }, t.author) : null,
317
+ t.time ? h('span', { class: 'cm-tp-item-time' }, fmtRelTime(t.time)) : null
318
+ )
319
+ ))
320
+ : h('div', { class: 'cm-tp-empty' }, 'No threads yet')
321
+ )
322
+ );
323
+ }
324
+
325
+ export function ForumView({ posts = [], onSearch, onSort, onSelect, onNewPost } = {}) {
326
+ const list = Array.isArray(posts) ? posts : [];
327
+ return h('div', { class: 'cm-forum', role: 'region', 'aria-label': 'forum' },
328
+ h('div', { class: 'cm-forum-toolbar' },
329
+ h('input', {
330
+ type: 'search', class: 'cm-forum-search', placeholder: 'Search posts…',
331
+ 'aria-label': 'search posts',
332
+ oninput: onSearch ? (e) => onSearch(e.target.value) : null
333
+ }),
334
+ h('select', {
335
+ class: 'cm-forum-sort', 'aria-label': 'sort posts',
336
+ onchange: onSort ? (e) => onSort(e.target.value) : null
337
+ },
338
+ h('option', { value: 'recent' }, 'Recent'),
339
+ h('option', { value: 'replies' }, 'Most replies'),
340
+ h('option', { value: 'oldest' }, 'Oldest')
341
+ ),
342
+ onNewPost ? h('button', { type: 'button', class: 'cm-forum-new', onclick: onNewPost }, 'New post') : null
343
+ ),
344
+ h('div', { class: 'cm-forum-list' },
345
+ list.length
346
+ ? list.map(p => h('button', {
347
+ type: 'button', key: 'fp-' + p.id, class: 'cm-forum-item',
348
+ onclick: () => onSelect && onSelect(p.id)
349
+ },
350
+ h('div', { class: 'cm-forum-item-head' },
351
+ h('span', { class: 'cm-forum-item-title' }, p.title || '(untitled)'),
352
+ h('span', { class: 'cm-forum-item-replies' }, (Number(p.replyCount) || 0) + ' ▸')
353
+ ),
354
+ p.snippet ? h('div', { class: 'cm-forum-item-snippet' }, p.snippet) : null,
355
+ h('div', { class: 'cm-forum-item-meta' },
356
+ p.author ? h('span', { class: 'cm-forum-item-author' }, p.author) : null,
357
+ p.time ? h('span', { class: 'cm-forum-item-time' }, fmtRelTime(p.time)) : null,
358
+ Array.isArray(p.tags) && p.tags.length
359
+ ? h('span', { class: 'cm-forum-item-tags' }, ...p.tags.map((tag, i) =>
360
+ h('span', { class: 'cm-forum-tag', key: 'tg-' + i }, tag)))
361
+ : null
362
+ )
363
+ ))
364
+ : h('div', { class: 'cm-forum-empty' }, 'No posts yet')
365
+ )
366
+ );
367
+ }
368
+
369
+ export function PageView({ title = '', html = '', isAdmin = false, onEdit } = {}) {
370
+ return h('div', { class: 'cm-page', role: 'document' },
371
+ h('div', { class: 'cm-page-head' },
372
+ h('h1', { class: 'cm-page-title' }, title || ''),
373
+ isAdmin && onEdit ? h('button', { type: 'button', class: 'cm-page-edit', onclick: onEdit }, 'Edit') : null
374
+ ),
375
+ h('div', {
376
+ class: 'cm-page-body',
377
+ ref: (el) => { if (el) el.innerHTML = html || '<p class="cm-page-empty">This page is empty.</p>'; }
378
+ })
379
+ );
380
+ }
381
+
282
382
  export function CommunityShell({ serverRailProps, sidebarProps, children, memberListProps, voiceStripProps } = {}) {
283
383
  return h('div', { class: 'cm-shell' },
284
384
  serverRailProps ? ServerRail(serverRailProps) : null,
@@ -462,3 +462,91 @@ export function SettingsPopover({ title = 'Settings', open, anchorX = 0, anchorY
462
462
  }))
463
463
  );
464
464
  }
465
+
466
+ // AuthModal — centered login dialog: extension / generate / import (nsec) modes.
467
+ export function AuthModal({ mode = 'extension', error = '', busy = false, open = false, onModeChange, onConnectExtension, onGenerate, onImport, onClose } = {}) {
468
+ if (!open) return null;
469
+ const close = () => onClose && onClose();
470
+ const modes = [
471
+ { id: 'extension', label: 'Extension' },
472
+ { id: 'generate', label: 'Generate' },
473
+ { id: 'import', label: 'Import key' },
474
+ ];
475
+ let nsec = '';
476
+ const body = () => {
477
+ if (mode === 'generate') {
478
+ return [
479
+ h('p', { class: 'ov-auth-hint' }, 'Create a fresh Nostr identity. Back up the key after.'),
480
+ h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
481
+ onclick: () => onGenerate && onGenerate() }, busy ? 'Working…' : 'Generate new key'),
482
+ ];
483
+ }
484
+ if (mode === 'import') {
485
+ return [
486
+ h('p', { class: 'ov-auth-hint' }, 'Paste an existing nsec / hex secret key.'),
487
+ h('input', {
488
+ type: 'password', class: 'ov-auth-input', placeholder: 'nsec1…',
489
+ 'aria-label': 'secret key', disabled: busy ? true : null,
490
+ oninput: (e) => { nsec = e.target.value; },
491
+ onkeydown: (e) => { if (e.key === 'Enter') { e.preventDefault(); onImport && onImport(nsec); } },
492
+ }),
493
+ h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
494
+ onclick: () => onImport && onImport(nsec) }, busy ? 'Working…' : 'Import'),
495
+ ];
496
+ }
497
+ return [
498
+ h('p', { class: 'ov-auth-hint' }, 'Connect a NIP-07 browser extension (Alby, nos2x…).'),
499
+ h('button', { type: 'button', class: 'ov-auth-primary', disabled: busy ? true : null,
500
+ onclick: () => onConnectExtension && onConnectExtension() }, busy ? 'Connecting…' : 'Connect extension'),
501
+ ];
502
+ };
503
+ return h('div', {
504
+ class: 'ov-auth-backdrop', role: 'presentation',
505
+ ref: (el) => {
506
+ if (!el || el._ovAuth) return; el._ovAuth = true;
507
+ el.addEventListener('mousedown', (e) => {
508
+ const panel = el.querySelector('.ov-auth-panel');
509
+ if (panel && !panel.contains(e.target)) close();
510
+ });
511
+ },
512
+ },
513
+ h('div', {
514
+ class: 'ov-auth-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Sign in',
515
+ onkeydown: (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); } },
516
+ },
517
+ h('div', { class: 'ov-auth-head' },
518
+ h('h2', { class: 'ov-auth-title' }, 'Sign in'),
519
+ h('button', { type: 'button', class: 'ov-auth-x', 'aria-label': 'close', onclick: close }, '×')
520
+ ),
521
+ h('div', { class: 'ov-auth-tabs', role: 'tablist' },
522
+ ...modes.map(m => h('button', {
523
+ type: 'button', role: 'tab', key: 'am-' + m.id,
524
+ class: 'ov-auth-tab' + (m.id === mode ? ' is-active' : ''),
525
+ 'aria-selected': m.id === mode ? 'true' : 'false',
526
+ onclick: () => onModeChange && onModeChange(m.id),
527
+ }, m.label))
528
+ ),
529
+ h('div', { class: 'ov-auth-body' }, ...body()),
530
+ error ? h('div', { class: 'ov-auth-error', role: 'alert' }, String(error)) : null
531
+ )
532
+ );
533
+ }
534
+
535
+ // VideoLightbox — fullscreen video player overlay with backdrop dismiss.
536
+ export function VideoLightbox({ src, label = '', open = false, onClose } = {}) {
537
+ if (!open || !src) return null;
538
+ const close = () => onClose && onClose();
539
+ return h('div', {
540
+ class: 'ov-lightbox-backdrop', role: 'dialog', 'aria-modal': 'true', 'aria-label': label || 'Video',
541
+ tabindex: '-1',
542
+ onkeydown: (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); } },
543
+ ref: (el) => { if (el && !el._ovLb) { el._ovLb = true; queueMicrotask(() => el.focus()); } },
544
+ onmousedown: (e) => { if (e.target === e.currentTarget) close(); },
545
+ },
546
+ h('button', { type: 'button', class: 'ov-lightbox-x', 'aria-label': 'close', onclick: close }, '×'),
547
+ h('div', { class: 'ov-lightbox-stage' },
548
+ h('video', { class: 'ov-lightbox-video', src, controls: true, autoplay: true, playsinline: true }),
549
+ label ? h('div', { class: 'ov-lightbox-label' }, label) : null
550
+ )
551
+ );
552
+ }
@@ -192,6 +192,31 @@ export function VoiceSettingsModal({ open = false, mode = 'ptt', inputId, output
192
192
  );
193
193
  }
194
194
 
195
+ export function VoiceControls({ muted = false, deafened = false, cameraOn = false, screenShareOn = false, onMic, onDeafen, onCamera, onScreenShare, onSettings, onLeave } = {}) {
196
+ const btn = (cls, on, label, glyph, handler) => h('button', {
197
+ type: 'button',
198
+ class: 'vx-vc-btn ' + cls + (on ? ' vx-vc-on' : '') + (handler ? '' : ' vx-vc-disabled'),
199
+ 'aria-pressed': on ? 'true' : 'false',
200
+ 'aria-label': label,
201
+ title: label,
202
+ disabled: handler ? null : true,
203
+ onclick: handler ? (e) => handler(e) : null
204
+ },
205
+ h('span', { class: 'vx-vc-glyph', 'aria-hidden': 'true' }, glyph)
206
+ );
207
+ return h('div', { class: 'vx-vc', role: 'toolbar', 'aria-label': 'voice controls' },
208
+ btn('vx-vc-mic', !muted, muted ? 'Unmute' : 'Mute', muted ? '🔇' : '🎙', onMic),
209
+ btn('vx-vc-deafen', !deafened, deafened ? 'Undeafen' : 'Deafen', deafened ? '🔕' : '🔊', onDeafen),
210
+ btn('vx-vc-camera', cameraOn, cameraOn ? 'Stop camera' : 'Start camera', '📷', onCamera),
211
+ btn('vx-vc-screen', screenShareOn, screenShareOn ? 'Stop sharing' : 'Share screen', '🖥', onScreenShare),
212
+ btn('vx-vc-settings', false, 'Voice settings', '⚙', onSettings),
213
+ h('button', {
214
+ type: 'button', class: 'vx-vc-btn vx-vc-leave', 'aria-label': 'Leave voice', title: 'Leave voice',
215
+ onclick: onLeave ? (e) => onLeave(e) : null
216
+ }, h('span', { class: 'vx-vc-glyph', 'aria-hidden': 'true' }, '📞'))
217
+ );
218
+ }
219
+
195
220
  export function AudioQueue({ segments = [], currentSegmentId = null, paused = false, onReplay, onSkip, onResume, onPause } = {}) {
196
221
  if (!segments || !segments.length) {
197
222
  return h('div', { class: 'vx-queue vx-queue-empty' },
package/src/components.js CHANGED
@@ -41,11 +41,12 @@ export {
41
41
  VoiceUser, UserPanel, ChannelSidebar,
42
42
  MemberItem, MemberList,
43
43
  ChatHeader, VoiceStrip, CommunityShell,
44
- MobileHeader, ReplyBar, Banner
44
+ MobileHeader, ReplyBar, Banner,
45
+ ThreadPanel, ForumView, PageView
45
46
  } from './components/community.js';
46
47
 
47
48
  export {
48
- PttButton, VadMeter, WebcamPreview, VoiceSettingsModal, AudioQueue
49
+ PttButton, VadMeter, WebcamPreview, VoiceSettingsModal, AudioQueue, VoiceControls
49
50
  } from './components/voice.js';
50
51
 
51
52
  export { ThemeToggle } from './components/theme-toggle.js';
@@ -75,7 +76,8 @@ export {
75
76
 
76
77
  export {
77
78
  Tooltip, Popover, Dropdown, useLongPress, useFloating,
78
- CommandPalette, EmojiPicker, BootOverlay, SettingsPopover
79
+ CommandPalette, EmojiPicker, BootOverlay, SettingsPopover,
80
+ AuthModal, VideoLightbox
79
81
  } from './components/overlay-primitives.js';
80
82
 
81
83
  export {