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/community.css +81 -0
- package/dist/247420.css +121 -0
- package/dist/247420.js +10 -10
- package/package.json +1 -1
- package/src/components/community.js +100 -0
- package/src/components/overlay-primitives.js +88 -0
- package/src/components/voice.js +25 -0
- package/src/components.js +5 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
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
|
+
}
|
package/src/components/voice.js
CHANGED
|
@@ -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 {
|