@medalsocial/meda 2.0.0 → 2.1.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/dist/calendar/calendar-toolbar.d.ts +2 -0
- package/dist/calendar/calendar-toolbar.js +22 -0
- package/dist/calendar/calendar.d.ts +2 -0
- package/dist/calendar/calendar.js +55 -0
- package/dist/calendar/day-view.d.ts +2 -0
- package/dist/calendar/day-view.js +40 -0
- package/dist/calendar/index.d.ts +6 -0
- package/dist/calendar/index.js +7 -0
- package/dist/calendar/internal/date.d.ts +26 -0
- package/dist/calendar/internal/date.js +107 -0
- package/dist/calendar/internal/day-cell.d.ts +15 -0
- package/dist/calendar/internal/day-cell.js +38 -0
- package/dist/calendar/internal/event-pill.d.ts +13 -0
- package/dist/calendar/internal/event-pill.js +30 -0
- package/dist/calendar/internal/time-slot.d.ts +17 -0
- package/dist/calendar/internal/time-slot.js +23 -0
- package/dist/calendar/month-view.d.ts +2 -0
- package/dist/calendar/month-view.js +46 -0
- package/dist/calendar/public.d.ts +6 -0
- package/dist/calendar/public.js +8 -0
- package/dist/calendar/types.d.ts +113 -0
- package/dist/calendar/types.js +15 -0
- package/dist/calendar/week-view.d.ts +2 -0
- package/dist/calendar/week-view.js +39 -0
- package/dist/chat/turn-card.js +10 -5
- package/dist/email-builder/block-error-boundary.d.ts +16 -0
- package/dist/email-builder/block-error-boundary.js +20 -0
- package/dist/email-builder/block-palette.d.ts +7 -0
- package/dist/email-builder/block-palette.js +22 -0
- package/dist/email-builder/block-registry.d.ts +15 -0
- package/dist/email-builder/block-registry.js +156 -0
- package/dist/email-builder/block-renderer.d.ts +5 -0
- package/dist/email-builder/block-renderer.js +40 -0
- package/dist/email-builder/block-renderers/button.d.ts +4 -0
- package/dist/email-builder/block-renderers/button.js +26 -0
- package/dist/email-builder/block-renderers/columns.d.ts +5 -0
- package/dist/email-builder/block-renderers/columns.js +27 -0
- package/dist/email-builder/block-renderers/divider.d.ts +4 -0
- package/dist/email-builder/block-renderers/divider.js +14 -0
- package/dist/email-builder/block-renderers/footer.d.ts +4 -0
- package/dist/email-builder/block-renderers/footer.js +12 -0
- package/dist/email-builder/block-renderers/heading.d.ts +4 -0
- package/dist/email-builder/block-renderers/heading.js +19 -0
- package/dist/email-builder/block-renderers/image.d.ts +4 -0
- package/dist/email-builder/block-renderers/image.js +18 -0
- package/dist/email-builder/block-renderers/social.d.ts +4 -0
- package/dist/email-builder/block-renderers/social.js +19 -0
- package/dist/email-builder/block-renderers/spacer.d.ts +4 -0
- package/dist/email-builder/block-renderers/spacer.js +5 -0
- package/dist/email-builder/block-renderers/text.d.ts +4 -0
- package/dist/email-builder/block-renderers/text.js +17 -0
- package/dist/email-builder/builder-canvas.d.ts +15 -0
- package/dist/email-builder/builder-canvas.js +26 -0
- package/dist/email-builder/builder-header.d.ts +9 -0
- package/dist/email-builder/builder-header.js +8 -0
- package/dist/email-builder/builder-left-tabs.d.ts +11 -0
- package/dist/email-builder/builder-left-tabs.js +19 -0
- package/dist/email-builder/email-builder.d.ts +3 -0
- package/dist/email-builder/email-builder.js +84 -0
- package/dist/email-builder/envelope-card.d.ts +7 -0
- package/dist/email-builder/envelope-card.js +16 -0
- package/dist/email-builder/floating-bar.d.ts +13 -0
- package/dist/email-builder/floating-bar.js +9 -0
- package/dist/email-builder/index.d.ts +4 -0
- package/dist/email-builder/index.js +12 -0
- package/dist/email-builder/internal/builder-state-utils.d.ts +7 -0
- package/dist/email-builder/internal/builder-state-utils.js +52 -0
- package/dist/email-builder/internal/dnd-wrapper.d.ts +10 -0
- package/dist/email-builder/internal/dnd-wrapper.js +9 -0
- package/dist/email-builder/internal/email-font-options.d.ts +5 -0
- package/dist/email-builder/internal/email-font-options.js +12 -0
- package/dist/email-builder/mobile-drawers.d.ts +10 -0
- package/dist/email-builder/mobile-drawers.js +7 -0
- package/dist/email-builder/mobile-tab-bar.d.ts +10 -0
- package/dist/email-builder/mobile-tab-bar.js +9 -0
- package/dist/email-builder/property-editors/_shared.d.ts +58 -0
- package/dist/email-builder/property-editors/_shared.js +52 -0
- package/dist/email-builder/property-editors/button-editor.d.ts +7 -0
- package/dist/email-builder/property-editors/button-editor.js +10 -0
- package/dist/email-builder/property-editors/columns-editor.d.ts +7 -0
- package/dist/email-builder/property-editors/columns-editor.js +15 -0
- package/dist/email-builder/property-editors/divider-editor.d.ts +7 -0
- package/dist/email-builder/property-editors/divider-editor.js +10 -0
- package/dist/email-builder/property-editors/footer-editor.d.ts +7 -0
- package/dist/email-builder/property-editors/footer-editor.js +6 -0
- package/dist/email-builder/property-editors/heading-editor.d.ts +7 -0
- package/dist/email-builder/property-editors/heading-editor.js +16 -0
- package/dist/email-builder/property-editors/image-editor.d.ts +8 -0
- package/dist/email-builder/property-editors/image-editor.js +17 -0
- package/dist/email-builder/property-editors/social-editor.d.ts +7 -0
- package/dist/email-builder/property-editors/social-editor.js +18 -0
- package/dist/email-builder/property-editors/spacer-editor.d.ts +7 -0
- package/dist/email-builder/property-editors/spacer-editor.js +6 -0
- package/dist/email-builder/property-editors/text-editor.d.ts +13 -0
- package/dist/email-builder/property-editors/text-editor.js +8 -0
- package/dist/email-builder/property-inspector.d.ts +11 -0
- package/dist/email-builder/property-inspector.js +42 -0
- package/dist/email-builder/public.d.ts +1 -0
- package/dist/email-builder/public.js +3 -0
- package/dist/email-builder/render-to-email-html.d.ts +11 -0
- package/dist/email-builder/render-to-email-html.js +219 -0
- package/dist/email-builder/starter-shell.d.ts +3 -0
- package/dist/email-builder/starter-shell.js +21 -0
- package/dist/email-builder/types.d.ts +182 -0
- package/dist/email-builder/types.js +41 -0
- package/dist/email-builder/view-controls.d.ts +10 -0
- package/dist/email-builder/view-controls.js +12 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/lib/format-time.js +2 -3
- package/dist/post-preview/index.d.ts +4 -0
- package/dist/post-preview/index.js +7 -0
- package/dist/post-preview/internal/avatar.d.ts +12 -0
- package/dist/post-preview/internal/avatar.js +21 -0
- package/dist/post-preview/internal/format-content.d.ts +12 -0
- package/dist/post-preview/internal/format-content.js +31 -0
- package/dist/post-preview/internal/platform-meta.d.ts +10 -0
- package/dist/post-preview/internal/platform-meta.js +18 -0
- package/dist/post-preview/internal/textarea.d.ts +8 -0
- package/dist/post-preview/internal/textarea.js +12 -0
- package/dist/post-preview/internal/use-swipe.d.ts +17 -0
- package/dist/post-preview/internal/use-swipe.js +29 -0
- package/dist/post-preview/platforms/bluesky.d.ts +15 -0
- package/dist/post-preview/platforms/bluesky.js +21 -0
- package/dist/post-preview/platforms/discord.d.ts +44 -0
- package/dist/post-preview/platforms/discord.js +32 -0
- package/dist/post-preview/platforms/facebook.d.ts +23 -0
- package/dist/post-preview/platforms/facebook.js +24 -0
- package/dist/post-preview/platforms/generic.d.ts +22 -0
- package/dist/post-preview/platforms/generic.js +28 -0
- package/dist/post-preview/platforms/google-business.d.ts +18 -0
- package/dist/post-preview/platforms/google-business.js +22 -0
- package/dist/post-preview/platforms/index.d.ts +24 -0
- package/dist/post-preview/platforms/index.js +13 -0
- package/dist/post-preview/platforms/instagram.d.ts +31 -0
- package/dist/post-preview/platforms/instagram.js +99 -0
- package/dist/post-preview/platforms/linkedin.d.ts +52 -0
- package/dist/post-preview/platforms/linkedin.js +131 -0
- package/dist/post-preview/platforms/telegram.d.ts +46 -0
- package/dist/post-preview/platforms/telegram.js +42 -0
- package/dist/post-preview/platforms/threads.d.ts +19 -0
- package/dist/post-preview/platforms/threads.js +24 -0
- package/dist/post-preview/platforms/tiktok.d.ts +18 -0
- package/dist/post-preview/platforms/tiktok.js +23 -0
- package/dist/post-preview/platforms/twitter.d.ts +40 -0
- package/dist/post-preview/platforms/twitter.js +132 -0
- package/dist/post-preview/platforms/youtube.d.ts +17 -0
- package/dist/post-preview/platforms/youtube.js +21 -0
- package/dist/post-preview/post-preview.d.ts +29 -0
- package/dist/post-preview/post-preview.js +70 -0
- package/dist/post-preview/public.d.ts +1 -0
- package/dist/post-preview/public.js +6 -0
- package/dist/post-preview/types.d.ts +82 -0
- package/dist/post-preview/types.js +1 -0
- package/dist/primitives/filter-rail.js +17 -2
- package/dist/shell/extras/shell-scrollable-content.js +2 -2
- package/dist/shell/extras/workbench-layout.js +24 -13
- package/dist/shell/right-panel.js +10 -1
- package/dist/shell/shell-header.js +1 -1
- package/dist/shell/theme-next-themes.js +2 -2
- package/dist/shell/theme.js +7 -1
- package/dist/timeline/lane-timeline.js +1 -1
- package/dist/timeline/scrub-bar.js +1 -1
- package/dist/timeline/timeline-tape.js +8 -5
- package/dist/voice/voice-orb-scene.js +4 -4
- package/dist/voice/voice-status-pill.js +8 -7
- package/dist/workflow-builder/edges/default-edge.d.ts +10 -0
- package/dist/workflow-builder/edges/default-edge.js +21 -0
- package/dist/workflow-builder/edges/index.d.ts +3 -0
- package/dist/workflow-builder/edges/index.js +5 -0
- package/dist/workflow-builder/index.d.ts +5 -0
- package/dist/workflow-builder/index.js +12 -0
- package/dist/workflow-builder/internal/trigger-icons.d.ts +4 -0
- package/dist/workflow-builder/internal/trigger-icons.js +43 -0
- package/dist/workflow-builder/internal/trigger-sentence.d.ts +14 -0
- package/dist/workflow-builder/internal/trigger-sentence.js +50 -0
- package/dist/workflow-builder/nodes/action-node.d.ts +3 -0
- package/dist/workflow-builder/nodes/action-node.js +7 -0
- package/dist/workflow-builder/nodes/base-node.d.ts +20 -0
- package/dist/workflow-builder/nodes/base-node.js +18 -0
- package/dist/workflow-builder/nodes/condition-node.d.ts +3 -0
- package/dist/workflow-builder/nodes/condition-node.js +11 -0
- package/dist/workflow-builder/nodes/delay-node.d.ts +3 -0
- package/dist/workflow-builder/nodes/delay-node.js +7 -0
- package/dist/workflow-builder/nodes/end-node.d.ts +3 -0
- package/dist/workflow-builder/nodes/end-node.js +7 -0
- package/dist/workflow-builder/nodes/index.d.ts +13 -0
- package/dist/workflow-builder/nodes/index.js +22 -0
- package/dist/workflow-builder/nodes/trigger-node.d.ts +3 -0
- package/dist/workflow-builder/nodes/trigger-node.js +8 -0
- package/dist/workflow-builder/public.d.ts +1 -0
- package/dist/workflow-builder/public.js +4 -0
- package/dist/workflow-builder/types.d.ts +140 -0
- package/dist/workflow-builder/types.js +18 -0
- package/dist/workflow-builder/workflow-builder.d.ts +10 -0
- package/dist/workflow-builder/workflow-builder.js +22 -0
- package/dist/workflow-builder/workflow-canvas.d.ts +26 -0
- package/dist/workflow-builder/workflow-canvas.js +25 -0
- package/dist/workflow-builder/workflow-card-compact.d.ts +2 -0
- package/dist/workflow-builder/workflow-card-compact.js +22 -0
- package/dist/workflow-builder/workflow-card.d.ts +2 -0
- package/dist/workflow-builder/workflow-card.js +44 -0
- package/dist/workflow-builder/workflow-header.d.ts +2 -0
- package/dist/workflow-builder/workflow-header.js +31 -0
- package/dist/workflow-builder/workflow-toolbox.d.ts +2 -0
- package/dist/workflow-builder/workflow-toolbox.js +19 -0
- package/package.json +72 -50
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Bookmark, Heart, MessageCircle, MoreHorizontal, Repeat2, Share } from 'lucide-react';
|
|
4
|
+
import { useCallback, useRef, useState } from 'react';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
import { Avatar } from '../internal/avatar.js';
|
|
7
|
+
import { Textarea } from '../internal/textarea.js';
|
|
8
|
+
export const DEFAULT_TWITTER_LABELS = {
|
|
9
|
+
placeholder: "What's happening?",
|
|
10
|
+
now: 'now',
|
|
11
|
+
moreOptions: 'More options',
|
|
12
|
+
reply: 'Reply',
|
|
13
|
+
retweet: 'Retweet',
|
|
14
|
+
like: 'Like',
|
|
15
|
+
save: 'Save',
|
|
16
|
+
share: 'Share',
|
|
17
|
+
viewAnalytics: 'View post analytics',
|
|
18
|
+
};
|
|
19
|
+
function useMentionTrigger(textareaRef, value, onChange) {
|
|
20
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
21
|
+
const [query, setQuery] = useState('');
|
|
22
|
+
const triggerStartRef = useRef(null);
|
|
23
|
+
const close = useCallback(() => {
|
|
24
|
+
setIsOpen(false);
|
|
25
|
+
setQuery('');
|
|
26
|
+
triggerStartRef.current = null;
|
|
27
|
+
}, []);
|
|
28
|
+
const handleInput = useCallback(() => {
|
|
29
|
+
const ta = textareaRef.current;
|
|
30
|
+
if (!ta)
|
|
31
|
+
return;
|
|
32
|
+
const cursor = ta.selectionStart ?? 0;
|
|
33
|
+
const text = ta.value;
|
|
34
|
+
// Walk back from cursor to find `@` until whitespace or start.
|
|
35
|
+
let i = cursor - 1;
|
|
36
|
+
let foundAt = -1;
|
|
37
|
+
while (i >= 0) {
|
|
38
|
+
const ch = text[i];
|
|
39
|
+
if (ch === '@') {
|
|
40
|
+
// Must be at start or preceded by whitespace
|
|
41
|
+
if (i === 0 || /\s/.test(text[i - 1])) {
|
|
42
|
+
foundAt = i;
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
if (/\s/.test(ch))
|
|
47
|
+
break;
|
|
48
|
+
i -= 1;
|
|
49
|
+
}
|
|
50
|
+
if (foundAt === -1) {
|
|
51
|
+
if (isOpen)
|
|
52
|
+
close();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
triggerStartRef.current = foundAt;
|
|
56
|
+
setQuery(text.slice(foundAt + 1, cursor));
|
|
57
|
+
setIsOpen(true);
|
|
58
|
+
}, [textareaRef, isOpen, close]);
|
|
59
|
+
const insertMention = useCallback((mention) => {
|
|
60
|
+
const ta = textareaRef.current;
|
|
61
|
+
if (!ta)
|
|
62
|
+
return;
|
|
63
|
+
const start = triggerStartRef.current;
|
|
64
|
+
if (start === null)
|
|
65
|
+
return;
|
|
66
|
+
const cursor = ta.selectionStart ?? 0;
|
|
67
|
+
const before = value.slice(0, start);
|
|
68
|
+
const after = value.slice(cursor);
|
|
69
|
+
const formatted = mention.startsWith('@') ? mention : `@${mention}`;
|
|
70
|
+
const next = `${before}${formatted} ${after}`;
|
|
71
|
+
onChange?.(next);
|
|
72
|
+
close();
|
|
73
|
+
}, [textareaRef, value, onChange, close]);
|
|
74
|
+
return { isOpen, query, close, insertMention, handleInput };
|
|
75
|
+
}
|
|
76
|
+
function ContentWithMentions({ text, accent }) {
|
|
77
|
+
// Build typed parts with stable offsets so keys don't rely on positional index.
|
|
78
|
+
const segments = [];
|
|
79
|
+
let offset = 0;
|
|
80
|
+
for (const part of text.split(/(@[A-Za-z0-9_]+)/g)) {
|
|
81
|
+
if (!part)
|
|
82
|
+
continue;
|
|
83
|
+
segments.push({
|
|
84
|
+
key: `${offset}-${part}`,
|
|
85
|
+
isMention: /^@[A-Za-z0-9_]+$/.test(part),
|
|
86
|
+
value: part,
|
|
87
|
+
});
|
|
88
|
+
offset += part.length;
|
|
89
|
+
}
|
|
90
|
+
return (_jsx(_Fragment, { children: segments.map((seg) => seg.isMention ? (_jsx("span", { className: accent, children: seg.value }, seg.key)) : (_jsx("span", { children: seg.value }, seg.key))) }));
|
|
91
|
+
}
|
|
92
|
+
const THEME_CLASSES = {
|
|
93
|
+
light: {
|
|
94
|
+
surface: 'bg-white',
|
|
95
|
+
text: 'text-gray-900',
|
|
96
|
+
muted: 'text-gray-500',
|
|
97
|
+
border: 'border-gray-200',
|
|
98
|
+
mention: 'text-blue-500',
|
|
99
|
+
},
|
|
100
|
+
dim: {
|
|
101
|
+
surface: 'bg-[#15202B]',
|
|
102
|
+
text: 'text-white',
|
|
103
|
+
muted: 'text-gray-400',
|
|
104
|
+
border: 'border-[#38444D]',
|
|
105
|
+
mention: 'text-[#1D9BF0]',
|
|
106
|
+
},
|
|
107
|
+
'lights-out': {
|
|
108
|
+
surface: 'bg-black',
|
|
109
|
+
text: 'text-white',
|
|
110
|
+
muted: 'text-gray-500',
|
|
111
|
+
border: 'border-gray-800',
|
|
112
|
+
mention: 'text-blue-400',
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Twitter / X feed-card preview. Supports light, dim, and lights-out
|
|
117
|
+
* themes plus an optional `@mention` picker render slot.
|
|
118
|
+
*/
|
|
119
|
+
export function TwitterPreview({ displayName, username, avatarUrl, content, mediaUrls, characterLimit, editable = false, onContentChange, className, labels, theme = 'lights-out', renderMentionPicker, }) {
|
|
120
|
+
const l = { ...DEFAULT_TWITTER_LABELS, ...labels };
|
|
121
|
+
const t = THEME_CLASSES[theme];
|
|
122
|
+
const formattedUsername = username.startsWith('@') ? username : `@${username}`;
|
|
123
|
+
const textareaRef = useRef(null);
|
|
124
|
+
const mention = useMentionTrigger(textareaRef, content, onContentChange);
|
|
125
|
+
return (_jsx("div", { "data-slot": "post-preview", "data-platform": "twitter", "data-theme": theme, className: cn('@container p-4 @[420px]:p-5', t.surface, t.text, className), children: _jsxs("div", { className: "flex gap-3", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-10 w-10 flex-shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-1 text-[15px]", children: [_jsx("span", { className: cn('truncate font-bold', t.text), children: displayName }), _jsx("span", { className: cn('truncate', t.muted), children: formattedUsername }), _jsx("span", { className: t.muted, children: "\u00B7" }), _jsx("span", { className: t.muted, children: l.now })] }), _jsx("button", { type: "button", "aria-label": l.moreOptions, className: cn('-m-1.5 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-1.5', t.muted, theme === 'light' ? 'hover:bg-gray-100' : 'hover:bg-gray-800'), children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) })] }), editable ? (_jsxs("div", { className: "relative", children: [_jsx(Textarea, { ref: textareaRef, value: content, onChange: (e) => onContentChange?.(e.target.value), onInput: mention.handleInput, onKeyUp: mention.handleInput, placeholder: l.placeholder, className: cn('mt-1 min-h-[60px] text-[15px]', t.text, theme === 'light' ? 'placeholder:text-gray-500' : 'placeholder:text-gray-500') }), renderMentionPicker && mention.isOpen
|
|
126
|
+
? renderMentionPicker({
|
|
127
|
+
query: mention.query,
|
|
128
|
+
onPick: mention.insertMention,
|
|
129
|
+
onCancel: mention.close,
|
|
130
|
+
})
|
|
131
|
+
: null, characterLimit !== undefined && (_jsxs("div", { "data-slot": "twitter-character-counter", className: cn('mt-1 text-right text-xs', content.length > characterLimit ? 'text-red-500' : t.muted), children: [_jsx("span", { children: characterLimit - content.length }), _jsx("span", { className: "mx-1", children: "/" }), _jsx("span", { children: characterLimit })] }))] })) : (_jsx("p", { className: "mt-1 whitespace-pre-wrap break-words text-[15px]", children: _jsx(ContentWithMentions, { text: content, accent: t.mention }) })), mediaUrls && mediaUrls.length > 0 && (_jsx("div", { className: cn('mt-3 overflow-hidden rounded-2xl border', t.border, mediaUrls.length === 1 && 'aspect-video', mediaUrls.length >= 2 && 'grid aspect-video grid-cols-2 gap-0.5'), children: mediaUrls.slice(0, 4).map((url, i) => (_jsx("div", { className: cn('overflow-hidden bg-gray-800', mediaUrls.length === 1 && 'h-full', mediaUrls.length === 3 && i === 0 && 'row-span-2'), children: _jsx("img", { src: url, alt: `Media ${i + 1}`, loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }) }, url))) })), _jsxs("div", { className: cn('mt-3 flex max-w-[425px] items-center justify-between @[420px]:max-w-[480px]', t.muted), children: [_jsxs("button", { type: "button", "aria-label": l.reply, className: "-m-2 inline-flex min-h-11 min-w-11 items-center gap-1 rounded-full p-2 hover:bg-blue-500/10 hover:text-blue-400", children: [_jsx(MessageCircle, { className: "h-[18px] w-[18px]" }), _jsx("span", { className: "text-[13px]", children: "0" })] }), _jsxs("button", { type: "button", "aria-label": l.retweet, className: "-m-2 inline-flex min-h-11 min-w-11 items-center gap-1 rounded-full p-2 hover:bg-green-500/10 hover:text-green-400", children: [_jsx(Repeat2, { className: "h-[18px] w-[18px]" }), _jsx("span", { className: "text-[13px]", children: "0" })] }), _jsxs("button", { type: "button", "aria-label": l.like, className: "-m-2 inline-flex min-h-11 min-w-11 items-center gap-1 rounded-full p-2 hover:bg-pink-500/10 hover:text-pink-400", children: [_jsx(Heart, { className: "h-[18px] w-[18px]" }), _jsx("span", { className: "text-[13px]", children: "0" })] }), _jsx("button", { type: "button", "aria-label": l.viewAnalytics, className: "-m-2 inline-flex min-h-11 min-w-11 items-center gap-1 rounded-full p-2 hover:bg-blue-500/10 hover:text-blue-400", children: _jsx("svg", { className: "h-[18px] w-[18px]", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M8.75 21V3h2v18h-2zM18 21V8.5h2V21h-2zM4 21l.004-10h2L6 21H4zm9.248 0v-7h2v7h-2z" }) }) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", "aria-label": l.save, className: "-m-2 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-2 hover:bg-blue-500/10 hover:text-blue-400", children: _jsx(Bookmark, { className: "h-[18px] w-[18px]" }) }), _jsx("button", { type: "button", "aria-label": l.share, className: "-m-2 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-2 hover:bg-blue-500/10 hover:text-blue-400", children: _jsx(Share, { className: "h-[18px] w-[18px]" }) })] })] })] })] }) }));
|
|
132
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PostPreviewBaseProps } from '../types.js';
|
|
2
|
+
export interface YouTubeLabels {
|
|
3
|
+
placeholder: string;
|
|
4
|
+
justNow: string;
|
|
5
|
+
like: string;
|
|
6
|
+
dislike: string;
|
|
7
|
+
share: string;
|
|
8
|
+
moreOptions: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const DEFAULT_YOUTUBE_LABELS: YouTubeLabels;
|
|
11
|
+
export interface YouTubePreviewProps extends PostPreviewBaseProps {
|
|
12
|
+
labels?: Partial<YouTubeLabels>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* YouTube community-post preview. Honors `.dark` for the dark variant.
|
|
16
|
+
*/
|
|
17
|
+
export declare function YouTubePreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, }: YouTubePreviewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { MoreVertical, ThumbsDown, ThumbsUp } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../lib/utils.js';
|
|
5
|
+
import { Avatar } from '../internal/avatar.js';
|
|
6
|
+
import { Textarea } from '../internal/textarea.js';
|
|
7
|
+
export const DEFAULT_YOUTUBE_LABELS = {
|
|
8
|
+
placeholder: 'Create a post...',
|
|
9
|
+
justNow: 'just now',
|
|
10
|
+
like: 'Like',
|
|
11
|
+
dislike: 'Dislike',
|
|
12
|
+
share: 'Share',
|
|
13
|
+
moreOptions: 'More options',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* YouTube community-post preview. Honors `.dark` for the dark variant.
|
|
17
|
+
*/
|
|
18
|
+
export function YouTubePreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, }) {
|
|
19
|
+
const l = { ...DEFAULT_YOUTUBE_LABELS, ...labels };
|
|
20
|
+
return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "youtube", className: cn('@container bg-white p-4 text-gray-900 @[420px]:text-[14px] dark:bg-[#0f0f0f] dark:text-white', className), children: [_jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { className: "flex gap-3", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-10 w-10" }), _jsx("div", { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium text-[14px]", children: displayName }), _jsx("span", { className: "text-[12px] text-gray-500 dark:text-gray-400", children: l.justNow })] }) })] }), _jsx("button", { type: "button", "aria-label": l.moreOptions, className: "-m-2 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-2 text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800", children: _jsx(MoreVertical, { className: "h-5 w-5" }) })] }), editable ? (_jsx(Textarea, { value: content, onChange: (e) => onContentChange?.(e.target.value), placeholder: l.placeholder, className: "mt-3 min-h-[60px] text-[14px] placeholder:text-gray-500" })) : (_jsx("p", { className: "mt-3 whitespace-pre-wrap break-words text-[14px]", children: content })), mediaUrls && mediaUrls.length > 0 && (_jsx("div", { className: "mt-3 overflow-hidden rounded-xl", children: _jsx("div", { className: "aspect-video bg-gray-100 dark:bg-gray-800", children: _jsx("img", { src: mediaUrls[0], alt: "Media 1", loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }) }) })), _jsxs("div", { className: "mt-4 flex items-center gap-2", children: [_jsxs("button", { type: "button", className: "inline-flex min-h-11 items-center gap-1 rounded-full bg-gray-100 px-3 py-1.5 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700", "aria-label": l.like, children: [_jsx(ThumbsUp, { className: "h-4 w-4" }), _jsx("span", { className: "font-medium text-[13px]", children: "0" })] }), _jsx("button", { type: "button", "aria-label": l.dislike, className: "inline-flex min-h-11 min-w-11 items-center justify-center rounded-full bg-gray-100 p-2 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700", children: _jsx(ThumbsDown, { className: "h-4 w-4" }) }), _jsxs("button", { type: "button", "aria-label": l.share, className: "inline-flex min-h-11 items-center gap-1 rounded-full bg-gray-100 px-3 py-1.5 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700", children: [_jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21 6h-2v9H6v2c0 .55.45 1 1 1h11l4 4V7c0-.55-.45-1-1-1zm-4 6V3c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v14l4-4h10c.55 0 1-.45 1-1z" }) }), _jsx("span", { className: "font-medium text-[13px]", children: "0" })] })] })] }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
import type { BlueSkyPreviewProps, DiscordPreviewProps, FacebookPreviewProps, GenericPreviewProps, GoogleBusinessPreviewProps, InstagramPreviewProps, LinkedInPreviewProps, TelegramPreviewProps, ThreadsPreviewProps, TikTokPreviewProps, TwitterPreviewProps, YouTubePreviewProps } from './platforms/index.js';
|
|
3
|
+
import type { PostPreviewSlots } from './types.js';
|
|
4
|
+
export type PostPreviewProps = PostPreviewSlots & (({
|
|
5
|
+
platform: 'instagram';
|
|
6
|
+
} & InstagramPreviewProps) | ({
|
|
7
|
+
platform: 'twitter';
|
|
8
|
+
} & TwitterPreviewProps) | ({
|
|
9
|
+
platform: 'facebook';
|
|
10
|
+
} & FacebookPreviewProps) | ({
|
|
11
|
+
platform: 'linkedin';
|
|
12
|
+
} & LinkedInPreviewProps) | ({
|
|
13
|
+
platform: 'tiktok';
|
|
14
|
+
} & TikTokPreviewProps) | ({
|
|
15
|
+
platform: 'youtube';
|
|
16
|
+
} & YouTubePreviewProps) | ({
|
|
17
|
+
platform: 'threads';
|
|
18
|
+
} & ThreadsPreviewProps) | ({
|
|
19
|
+
platform: 'bluesky';
|
|
20
|
+
} & BlueSkyPreviewProps) | ({
|
|
21
|
+
platform: 'discord';
|
|
22
|
+
} & DiscordPreviewProps) | ({
|
|
23
|
+
platform: 'telegram';
|
|
24
|
+
} & TelegramPreviewProps) | ({
|
|
25
|
+
platform: 'google_business';
|
|
26
|
+
} & GoogleBusinessPreviewProps) | ({
|
|
27
|
+
platform: 'generic';
|
|
28
|
+
} & GenericPreviewProps));
|
|
29
|
+
export declare function PostPreview(props: PostPreviewProps): React.ReactElement;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { BlueSkyPreview } from './platforms/bluesky.js';
|
|
3
|
+
import { DiscordPreview } from './platforms/discord.js';
|
|
4
|
+
import { FacebookPreview } from './platforms/facebook.js';
|
|
5
|
+
import { GenericPreview } from './platforms/generic.js';
|
|
6
|
+
import { GoogleBusinessPreview } from './platforms/google-business.js';
|
|
7
|
+
import { InstagramPreview } from './platforms/instagram.js';
|
|
8
|
+
import { LinkedInPreview } from './platforms/linkedin.js';
|
|
9
|
+
import { TelegramPreview } from './platforms/telegram.js';
|
|
10
|
+
import { ThreadsPreview } from './platforms/threads.js';
|
|
11
|
+
import { TikTokPreview } from './platforms/tiktok.js';
|
|
12
|
+
import { TwitterPreview } from './platforms/twitter.js';
|
|
13
|
+
import { YouTubePreview } from './platforms/youtube.js';
|
|
14
|
+
export function PostPreview(props) {
|
|
15
|
+
const { platform, mode, renderEditor, renderMediaPicker, renderEmojiPicker, renderMentionPicker, onMediaUrlsChange, ...rest } = props;
|
|
16
|
+
const isEdit = mode === 'edit' || rest.editable === true;
|
|
17
|
+
const showToolbar = isEdit &&
|
|
18
|
+
(renderMediaPicker !== undefined ||
|
|
19
|
+
renderEmojiPicker !== undefined ||
|
|
20
|
+
renderMentionPicker !== undefined);
|
|
21
|
+
const platformProps = {
|
|
22
|
+
...rest,
|
|
23
|
+
editable: isEdit && renderEditor === undefined,
|
|
24
|
+
};
|
|
25
|
+
const editorNode = renderEditor?.({
|
|
26
|
+
platform,
|
|
27
|
+
value: rest.content ?? '',
|
|
28
|
+
onChange: (next) => rest.onContentChange?.(next),
|
|
29
|
+
});
|
|
30
|
+
const toolbarNode = showToolbar ? (_jsxs("div", { className: "meda-post-preview__toolbar", role: "toolbar", "aria-label": "Post editor", children: [renderMediaPicker?.({
|
|
31
|
+
platform,
|
|
32
|
+
current: rest.mediaUrls,
|
|
33
|
+
onPick: (next) => onMediaUrlsChange?.(next),
|
|
34
|
+
}), renderEmojiPicker?.({
|
|
35
|
+
onSelect: (emoji) => rest.onContentChange?.((rest.content ?? '') + emoji),
|
|
36
|
+
}), renderMentionPicker?.({ platform })] })) : null;
|
|
37
|
+
const PreviewElement = renderPlatform(platform, platformProps);
|
|
38
|
+
if (editorNode === undefined && toolbarNode === null) {
|
|
39
|
+
return PreviewElement;
|
|
40
|
+
}
|
|
41
|
+
return (_jsxs("div", { className: "meda-post-preview", "data-platform": platform, children: [toolbarNode, editorNode, PreviewElement] }));
|
|
42
|
+
}
|
|
43
|
+
function renderPlatform(platform, props) {
|
|
44
|
+
switch (platform) {
|
|
45
|
+
case 'instagram':
|
|
46
|
+
return _jsx(InstagramPreview, { ...props });
|
|
47
|
+
case 'twitter':
|
|
48
|
+
return _jsx(TwitterPreview, { ...props });
|
|
49
|
+
case 'facebook':
|
|
50
|
+
return _jsx(FacebookPreview, { ...props });
|
|
51
|
+
case 'linkedin':
|
|
52
|
+
return _jsx(LinkedInPreview, { ...props });
|
|
53
|
+
case 'tiktok':
|
|
54
|
+
return _jsx(TikTokPreview, { ...props });
|
|
55
|
+
case 'youtube':
|
|
56
|
+
return _jsx(YouTubePreview, { ...props });
|
|
57
|
+
case 'threads':
|
|
58
|
+
return _jsx(ThreadsPreview, { ...props });
|
|
59
|
+
case 'bluesky':
|
|
60
|
+
return _jsx(BlueSkyPreview, { ...props });
|
|
61
|
+
case 'discord':
|
|
62
|
+
return _jsx(DiscordPreview, { ...props });
|
|
63
|
+
case 'telegram':
|
|
64
|
+
return _jsx(TelegramPreview, { ...props });
|
|
65
|
+
case 'google_business':
|
|
66
|
+
return _jsx(GoogleBusinessPreview, { ...props });
|
|
67
|
+
case 'generic':
|
|
68
|
+
return _jsx(GenericPreview, { ...props });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// open/meda/src/post-preview/public.ts
|
|
2
|
+
//
|
|
3
|
+
// Re-exported into `src/index.ts` so consumers can import from the
|
|
4
|
+
// package root as well as the subpath. Subpath import is preferred
|
|
5
|
+
// for tree-shaking; root import is provided for convenience.
|
|
6
|
+
export * from './index.js';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Public types for the post-preview surface.
|
|
4
|
+
*
|
|
5
|
+
* These types are framework-neutral and intentionally free of
|
|
6
|
+
* Convex / app-specific identifiers. Platform-specific extras
|
|
7
|
+
* (Telegram poll, Discord embeds, LinkedIn mentions) live next to
|
|
8
|
+
* their platform module.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Platforms with a dedicated preview component. Any platform not in
|
|
12
|
+
* this list should use `GenericPreview` as a fallback.
|
|
13
|
+
*/
|
|
14
|
+
export type PlatformId = 'instagram' | 'twitter' | 'facebook' | 'linkedin' | 'tiktok' | 'youtube' | 'threads' | 'bluesky' | 'discord' | 'telegram' | 'google_business' | 'generic';
|
|
15
|
+
export interface PostPreviewSlots {
|
|
16
|
+
/** When 'edit', enables editable mode. Sugar for `editable: true`. */
|
|
17
|
+
mode?: 'preview' | 'edit';
|
|
18
|
+
/** Replaces the platform's content textarea when in edit mode. */
|
|
19
|
+
renderEditor?: (ctx: {
|
|
20
|
+
platform: PlatformId;
|
|
21
|
+
value: string;
|
|
22
|
+
onChange: (next: string) => void;
|
|
23
|
+
}) => React.ReactNode;
|
|
24
|
+
/** Renders a media picker trigger in the edit toolbar. */
|
|
25
|
+
renderMediaPicker?: (ctx: {
|
|
26
|
+
platform: PlatformId;
|
|
27
|
+
current: string[] | undefined;
|
|
28
|
+
onPick: (next: string[]) => void;
|
|
29
|
+
}) => React.ReactNode;
|
|
30
|
+
/** Renders an emoji picker trigger in the edit toolbar. */
|
|
31
|
+
renderEmojiPicker?: (ctx: {
|
|
32
|
+
onSelect: (emoji: string) => void;
|
|
33
|
+
}) => React.ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* Renders a mention picker trigger in the edit toolbar. The consumer owns
|
|
36
|
+
* the mention search state and content insertion; the slot only receives
|
|
37
|
+
* the active platform so it can scope its own data source. Use
|
|
38
|
+
* `renderEditor` together with platform-specific mention props (e.g.
|
|
39
|
+
* `LinkedInMentionPickerContext`) to wire selection back into content.
|
|
40
|
+
*/
|
|
41
|
+
renderMentionPicker?: (ctx: {
|
|
42
|
+
platform: PlatformId;
|
|
43
|
+
}) => React.ReactNode;
|
|
44
|
+
/** Called when renderMediaPicker invokes onPick. */
|
|
45
|
+
onMediaUrlsChange?: (mediaUrls: string[]) => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Visual frame mode for any platform preview.
|
|
49
|
+
* - `card` (default): fluid feed-card layout. Fits container width.
|
|
50
|
+
* - `device`: wraps in a phone shell with status bar / tab bar at
|
|
51
|
+
* a native phone aspect ratio.
|
|
52
|
+
*/
|
|
53
|
+
export type PostPreviewFrame = 'card' | 'device';
|
|
54
|
+
/**
|
|
55
|
+
* Shared props for every platform preview.
|
|
56
|
+
*
|
|
57
|
+
* Per-platform components extend this with their own optional extras
|
|
58
|
+
* (e.g. `TelegramPreviewProps` adds `poll`, `pinMessage`, `replyMarkup`).
|
|
59
|
+
*/
|
|
60
|
+
export interface PostPreviewBaseProps {
|
|
61
|
+
/** Channel display name (e.g. "Acme Studios"). */
|
|
62
|
+
displayName: string;
|
|
63
|
+
/** Channel handle/username; leading `@` is optional and stripped. */
|
|
64
|
+
username: string;
|
|
65
|
+
/** Channel avatar URL. Falls back to initials when omitted. */
|
|
66
|
+
avatarUrl?: string;
|
|
67
|
+
/** Post body text. Plain text; per-platform formatting (mentions,
|
|
68
|
+
* hashtags) is applied at render time. */
|
|
69
|
+
content: string;
|
|
70
|
+
/** Image / video URLs for the post media. */
|
|
71
|
+
mediaUrls?: string[];
|
|
72
|
+
/** Optional override for the per-platform character limit display. */
|
|
73
|
+
characterLimit?: number;
|
|
74
|
+
/** Visual frame mode. See `PostPreviewFrame`. */
|
|
75
|
+
frame?: PostPreviewFrame;
|
|
76
|
+
/** Enable inline editing of `content`. Requires `onContentChange`. */
|
|
77
|
+
editable?: boolean;
|
|
78
|
+
/** Called on every keystroke when `editable` is true. */
|
|
79
|
+
onContentChange?: (content: string) => void;
|
|
80
|
+
/** Class applied to the outer wrapper element. */
|
|
81
|
+
className?: string;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -7,8 +7,23 @@ function FilterRailGroup({ title, description, children, className, ...props })
|
|
|
7
7
|
}
|
|
8
8
|
function FilterRailRoot({ title, description, search, actions, footer, children, className, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }) {
|
|
9
9
|
const titleId = useId();
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Use `!== undefined` (not truthy) so that callers passing an explicit
|
|
11
|
+
// empty string clear the attribute instead of falling through to the
|
|
12
|
+
// title-derived fallback.
|
|
13
|
+
let label;
|
|
14
|
+
if (ariaLabel !== undefined) {
|
|
15
|
+
label = ariaLabel;
|
|
16
|
+
}
|
|
17
|
+
else if (ariaLabelledBy === undefined && typeof title === 'string') {
|
|
18
|
+
label = title;
|
|
19
|
+
}
|
|
20
|
+
let labelledBy;
|
|
21
|
+
if (ariaLabelledBy !== undefined) {
|
|
22
|
+
labelledBy = ariaLabelledBy;
|
|
23
|
+
}
|
|
24
|
+
else if (label === undefined && title) {
|
|
25
|
+
labelledBy = titleId;
|
|
26
|
+
}
|
|
12
27
|
return (_jsxs("aside", { "data-slot": "filter-rail", "aria-label": label, "aria-labelledby": labelledBy, className: cn('flex min-h-0 w-full flex-col border-border bg-card text-card-foreground', className), ...props, children: [title || description || actions ? (_jsxs("div", { "data-slot": "filter-rail-header", className: "flex shrink-0 items-start justify-between gap-3 border-b border-border px-4 py-3", children: [_jsxs("div", { className: "min-w-0", children: [title ? (_jsx("h2", { id: titleId, "data-slot": "filter-rail-title", className: "text-sm font-semibold text-foreground", children: title })) : null, description ? (_jsx("p", { "data-slot": "filter-rail-description", className: "mt-0.5 text-xs text-muted-foreground", children: description })) : null] }), actions ? (_jsx("div", { "data-slot": "filter-rail-actions", className: "shrink-0", children: actions })) : null] })) : null, search ? (_jsx("div", { "data-slot": "filter-rail-search", className: "shrink-0 border-b border-border p-3", children: search })) : null, _jsx("div", { "data-slot": "filter-rail-content", className: "min-h-0 flex-1 space-y-5 overflow-y-auto p-4", children: children }), footer ? (_jsx("div", { "data-slot": "filter-rail-footer", className: "shrink-0 border-t border-border p-3", children: footer })) : null] }));
|
|
13
28
|
}
|
|
14
29
|
export const FilterRail = Object.assign(FilterRailRoot, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
function joinClasses(...values) {
|
|
3
3
|
return values.filter(Boolean).join(' ');
|
|
4
4
|
}
|
|
5
5
|
export function ShellScrollableContent({ children, desktopDockOffset = 0, layout, maxWidth, }) {
|
|
6
|
-
return (
|
|
6
|
+
return (_jsxs("main", { "data-testid": "shell-workspace-viewport", className: "shell-scrollbar-hidden absolute inset-0 min-w-0 overflow-y-auto overflow-x-hidden pb-[var(--bottom-nav-height)] transition-[padding] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] md:pb-0", style: desktopDockOffset > 0 ? { paddingRight: `${desktopDockOffset}px` } : undefined, children: [layout === 'centered' && (_jsx("div", { "data-testid": "shell-content-centered", className: "flex min-h-full items-center justify-center px-5 py-5 sm:px-6 sm:py-6 md:px-8 md:py-8 lg:px-10", children: _jsx("div", { className: "flex w-full max-w-3xl items-center justify-center", children: children }) })), layout === 'fullbleed' && (_jsx("div", { "data-testid": "shell-content-fullbleed", className: "mx-auto w-full px-5 py-5 sm:px-6 sm:py-6 md:px-8 md:py-8 lg:px-10", style: maxWidth ? { maxWidth: `${maxWidth}px` } : undefined, children: children })), layout !== 'centered' && layout !== 'fullbleed' && (_jsx("div", { "data-testid": "shell-content-workspace", className: joinClasses('mx-auto w-full px-5 py-5 sm:px-6 sm:py-6 md:px-8 md:py-8 lg:px-10'), style: maxWidth ? { maxWidth: `${maxWidth}px` } : undefined, children: children }))] }));
|
|
7
7
|
}
|
|
@@ -13,17 +13,28 @@ function getWorkbenchMaxWidth(viewportBand) {
|
|
|
13
13
|
}
|
|
14
14
|
export function WorkbenchLayout({ viewportBand, main, aside, toolbar, className, }) {
|
|
15
15
|
const maxWidth = getWorkbenchMaxWidth(viewportBand);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
let layout;
|
|
17
|
+
if (!aside) {
|
|
18
|
+
layout = 'single';
|
|
19
|
+
}
|
|
20
|
+
else if (viewportBand === 'mobile' || viewportBand === 'tablet') {
|
|
21
|
+
layout = 'stacked';
|
|
22
|
+
}
|
|
23
|
+
else if (viewportBand === 'desktop') {
|
|
24
|
+
layout = 'split';
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
layout = 'multi-zone';
|
|
28
|
+
}
|
|
29
|
+
let columnStyle;
|
|
30
|
+
if (layout === 'split') {
|
|
31
|
+
columnStyle = { gridTemplateColumns: 'minmax(0, 1fr) minmax(280px, 320px)' };
|
|
32
|
+
}
|
|
33
|
+
else if (layout === 'multi-zone') {
|
|
34
|
+
columnStyle = { gridTemplateColumns: 'minmax(0, 1fr) minmax(300px, 360px)' };
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
columnStyle = undefined;
|
|
38
|
+
}
|
|
39
|
+
return (_jsxs("section", { "data-testid": "workbench-layout", "data-band": viewportBand, className: joinClasses('mx-auto flex w-full flex-col gap-6', className), style: maxWidth ? { maxWidth: `${maxWidth}px` } : undefined, children: [toolbar ? _jsx("div", { "data-testid": "workbench-toolbar", children: toolbar }) : null, _jsxs("div", { "data-testid": "workbench-columns", "data-layout": layout, className: joinClasses('w-full gap-5', layout === 'stacked' || layout === 'single' ? 'flex flex-col' : 'grid'), style: columnStyle, children: [_jsx("div", { "data-testid": "workbench-main", className: "min-w-0", children: main }), aside ? (_jsx("aside", { "data-testid": "workbench-aside", className: "min-w-0", children: aside })) : null] })] }));
|
|
29
40
|
}
|
|
@@ -123,7 +123,16 @@ export function RightPanel({ panelViews = EMPTY_PANEL_VIEWS, defaultView, modes
|
|
|
123
123
|
// Z-index escalation: fullscreen covers header + rails
|
|
124
124
|
const zIndexClass = mode === 'fullscreen' ? 'z-[var(--z-shell-fullscreen)]' : 'z-[var(--z-shell-panel)]';
|
|
125
125
|
const activePanelView = resolvedPanelViews.find((v) => v.id === activeView);
|
|
126
|
-
|
|
126
|
+
let cycleAriaLabel;
|
|
127
|
+
if (mode === 'panel') {
|
|
128
|
+
cycleAriaLabel = 'Expand panel';
|
|
129
|
+
}
|
|
130
|
+
else if (mode === 'expanded') {
|
|
131
|
+
cycleAriaLabel = 'Maximize panel';
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
cycleAriaLabel = 'Restore panel';
|
|
135
|
+
}
|
|
127
136
|
const handleResize = (w) => setDisplayWidth(w);
|
|
128
137
|
const handleCommit = (w) => {
|
|
129
138
|
setDisplayWidth(null);
|
|
@@ -83,7 +83,7 @@ export function AppTabs({ renderLink } = {}) {
|
|
|
83
83
|
},
|
|
84
84
|
}) }, app.id));
|
|
85
85
|
}
|
|
86
|
-
return (_jsx("button", { type: "button", "aria-current": isActive ? 'page' : undefined, onClick: handleClick, onMouseEnter: () =>
|
|
86
|
+
return (_jsx("button", { type: "button", "aria-current": isActive ? 'page' : undefined, onClick: handleClick, onMouseEnter: () => undefined, className: className, children: children }, app.id));
|
|
87
87
|
}) }));
|
|
88
88
|
}
|
|
89
89
|
// ---------------------------------------------------------------------------
|
|
@@ -46,7 +46,7 @@ function getClientThemeSnapshot() {
|
|
|
46
46
|
}
|
|
47
47
|
function subscribeToSystemTheme(mql, listener) {
|
|
48
48
|
if (!mql)
|
|
49
|
-
return () =>
|
|
49
|
+
return () => undefined;
|
|
50
50
|
if (typeof mql.addEventListener === 'function') {
|
|
51
51
|
mql.addEventListener('change', listener);
|
|
52
52
|
return () => mql.removeEventListener('change', listener);
|
|
@@ -55,7 +55,7 @@ function subscribeToSystemTheme(mql, listener) {
|
|
|
55
55
|
mql.addListener(listener);
|
|
56
56
|
return () => mql.removeListener?.(listener);
|
|
57
57
|
}
|
|
58
|
-
return () =>
|
|
58
|
+
return () => undefined;
|
|
59
59
|
}
|
|
60
60
|
export function NextThemesAdapter({ children }) {
|
|
61
61
|
const [theme, setThemeState] = useState('system');
|
package/dist/shell/theme.js
CHANGED
|
@@ -31,7 +31,13 @@ export function DefaultThemeProvider({ children }) {
|
|
|
31
31
|
useEffect(() => {
|
|
32
32
|
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
33
33
|
const apply = () => {
|
|
34
|
-
|
|
34
|
+
let resolved;
|
|
35
|
+
if (theme !== 'system') {
|
|
36
|
+
resolved = theme;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
resolved = mq.matches ? 'dark' : 'light';
|
|
40
|
+
}
|
|
35
41
|
setResolvedTheme(resolved);
|
|
36
42
|
document.documentElement.classList.toggle('dark', resolved === 'dark');
|
|
37
43
|
};
|
|
@@ -65,7 +65,7 @@ function formatTime(d) {
|
|
|
65
65
|
}
|
|
66
66
|
function buildTicks(start, end, range) {
|
|
67
67
|
const span = end.getTime() - start.getTime();
|
|
68
|
-
const tickCount = range === '
|
|
68
|
+
const tickCount = range === '24h' ? 8 : 7;
|
|
69
69
|
const out = [];
|
|
70
70
|
for (let i = 0; i <= tickCount; i++) {
|
|
71
71
|
const t = new Date(start.getTime() + (span * i) / tickCount);
|
|
@@ -34,7 +34,7 @@ export function ScrubBar({ durationMs, positionMs, isLive = false, marks, onSeek
|
|
|
34
34
|
onSeek(Math.min(durationMs, positionMs + step));
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
-
return (_jsxs("div", { className: cn('rounded-2xl border border-border bg-card p-3.5', className), children: [_jsxs("div", { className: "mb-2.5 flex items-center justify-between", children: [_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Timeline \u00B7 this call" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("span", { className: "px-1.5 text-[11px] tabular-nums text-muted-foreground", children: [formatDuration(positionMs), isLive ? ' / live' : ` / ${formatDuration(durationMs)}`] }), onSkipBack && (_jsx(TransportBtn, { label: "Skip back", onClick: onSkipBack, children: _jsx(SkipBack, { className: "size-3.5" }) })), showPlayPause && (_jsx(TransportBtn, { label: isPlayingValue ? 'Pause' : 'Play', variant: "primary", onClick: onPlayPause ?? (() =>
|
|
37
|
+
return (_jsxs("div", { className: cn('rounded-2xl border border-border bg-card p-3.5', className), children: [_jsxs("div", { className: "mb-2.5 flex items-center justify-between", children: [_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Timeline \u00B7 this call" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("span", { className: "px-1.5 text-[11px] tabular-nums text-muted-foreground", children: [formatDuration(positionMs), isLive ? ' / live' : ` / ${formatDuration(durationMs)}`] }), onSkipBack && (_jsx(TransportBtn, { label: "Skip back", onClick: onSkipBack, children: _jsx(SkipBack, { className: "size-3.5" }) })), showPlayPause && (_jsx(TransportBtn, { label: isPlayingValue ? 'Pause' : 'Play', variant: "primary", onClick: onPlayPause ?? (() => undefined), children: isPlayingValue ? _jsx(Pause, { className: "size-3.5" }) : _jsx(Play, { className: "size-3.5" }) })), onSkipForward && (_jsx(TransportBtn, { label: "Skip forward", onClick: onSkipForward, children: _jsx(SkipForward, { className: "size-3.5" }) }))] })] }), _jsxs("div", { ref: trackRef, role: "slider", "aria-label": "Conversation position", "aria-valuemin": 0, "aria-valuemax": durationMs, "aria-valuenow": positionMs, onClick: handleSeek, onKeyDown: handleKeyDown, tabIndex: 0, className: "relative h-8 cursor-pointer overflow-hidden rounded-md bg-muted focus:outline-none focus:ring-2 focus:ring-primary/50", children: [_jsx("span", { "aria-hidden": "true", className: "absolute inset-y-0 left-0 bg-primary/10", style: { width: `${fillPct}%` } }), marks.map((m) => (_jsx("span", { "data-mark-kind": m.kind, title: m.label ?? m.kind, className: cn('absolute inset-y-1 w-0.5 rounded-sm', KIND_COLOR[m.kind]), style: { left: `${m.positionPct}%` } }, m.id))), _jsx("span", { "aria-hidden": "true", "data-chromatic": "ignore", className: "absolute -inset-y-0.5 w-0.5 bg-foreground", style: { left: `${fillPct}%` } })] }), _jsxs("div", { className: "flex justify-between pt-2 text-[10px] tabular-nums text-muted-foreground", children: [_jsx("span", { children: "0:00" }), _jsx("span", { children: isLive ? 'now' : formatDuration(durationMs) })] })] }));
|
|
38
38
|
}
|
|
39
39
|
function TransportBtn({ label, onClick, variant, children, }) {
|
|
40
40
|
return (_jsx("button", { type: "button", "aria-label": label, onClick: onClick, className: cn('inline-flex size-6 items-center justify-center rounded', variant === 'primary'
|
|
@@ -6,6 +6,13 @@ import { EventCard } from './event-card.js';
|
|
|
6
6
|
const DEFAULT_PX_PER_SEC = 1.2;
|
|
7
7
|
const DEFAULT_FUTURE_PAD_SEC = 300;
|
|
8
8
|
const DEFAULT_PAST_SPAN_SEC = 6 * 60 * 60;
|
|
9
|
+
function getSegmentColorClass(event) {
|
|
10
|
+
if (event.isLive)
|
|
11
|
+
return 'bg-success-600 shadow-[0_0_12px_rgba(16,185,129,0.5)]';
|
|
12
|
+
if (event.kind === 'error')
|
|
13
|
+
return 'bg-destructive';
|
|
14
|
+
return 'bg-info-500 shadow-[0_0_8px_rgba(56,189,248,0.35)]';
|
|
15
|
+
}
|
|
9
16
|
/** Convert a unix-ms instant to canvas y (px). y=0 at canvas top. */
|
|
10
17
|
function instantToY(instantMs, nowMs, pxPerSec, futurePadSec) {
|
|
11
18
|
const secsAfterNow = (instantMs - nowMs) / 1000;
|
|
@@ -53,11 +60,7 @@ export function TimelineTape({ now, events, pxPerSec = DEFAULT_PX_PER_SEC, futur
|
|
|
53
60
|
const height = Math.max(2, Math.abs(endY - startY));
|
|
54
61
|
return { event: e, top, height };
|
|
55
62
|
});
|
|
56
|
-
return (_jsxs("div", { className: cn('relative ml-3.5 mr-3', className), style: { height: `${canvasHeight}px` }, "data-canvas-height": canvasHeight, children: [ticks.map((t) => t.major ? (_jsxs("div", { children: [_jsx("span", { className: "absolute left-0 w-[30px] -translate-y-[7px] text-right text-[11px] font-medium tabular-nums text-muted-foreground", style: { top: `${t.y}px` }, children: t.label }), _jsx("span", { className: "absolute h-px w-2 bg-border", style: { left: 32, top: `${t.y}px` } })] }, `maj-${t.y}`)) : (_jsx("span", { className: "absolute h-px w-1.5 bg-border/40", style: { left: 35, top: `${t.y}px` } }, `min-${t.y}`))), segments.map(({ event, top, height }) => (_jsx("span", { "aria-hidden": "true", "data-tape-segment-id": event.id, "data-tape-segment-live": String(event.isLive ?? false), className: cn('absolute w-1 rounded-sm', event.
|
|
57
|
-
? 'bg-success-600 shadow-[0_0_12px_rgba(16,185,129,0.5)]'
|
|
58
|
-
: event.kind === 'error'
|
|
59
|
-
? 'bg-destructive'
|
|
60
|
-
: 'bg-info-500 shadow-[0_0_8px_rgba(56,189,248,0.35)]'), style: { left: 44, top: `${top}px`, height: `${height}px` } }, `seg-${event.id}`))), events.map((event) => {
|
|
63
|
+
return (_jsxs("div", { className: cn('relative ml-3.5 mr-3', className), style: { height: `${canvasHeight}px` }, "data-canvas-height": canvasHeight, children: [ticks.map((t) => t.major ? (_jsxs("div", { children: [_jsx("span", { className: "absolute left-0 w-[30px] -translate-y-[7px] text-right text-[11px] font-medium tabular-nums text-muted-foreground", style: { top: `${t.y}px` }, children: t.label }), _jsx("span", { className: "absolute h-px w-2 bg-border", style: { left: 32, top: `${t.y}px` } })] }, `maj-${t.y}`)) : (_jsx("span", { className: "absolute h-px w-1.5 bg-border/40", style: { left: 35, top: `${t.y}px` } }, `min-${t.y}`))), segments.map(({ event, top, height }) => (_jsx("span", { "aria-hidden": "true", "data-tape-segment-id": event.id, "data-tape-segment-live": String(event.isLive ?? false), className: cn('absolute w-1 rounded-sm', getSegmentColorClass(event)), style: { left: 44, top: `${top}px`, height: `${height}px` } }, `seg-${event.id}`))), events.map((event) => {
|
|
61
64
|
const startY = instantToY(event.startedAt, nowMs, pxPerSec, futurePadSec);
|
|
62
65
|
const isSubEvent = event.kind === 'sub-event';
|
|
63
66
|
return (_jsx("div", { "data-event-id": event.id, className: "absolute", style: { top: `${startY}px`, left: 56, right: 0 }, children: _jsx(EventCard, { event: event, size: isSubEvent ? 'tiny' : 'default', onSelect: onSelect }) }, event.id));
|
|
@@ -23,13 +23,13 @@ const PHASE_TARGET = {
|
|
|
23
23
|
function splitmix32(seed) {
|
|
24
24
|
let a = seed;
|
|
25
25
|
return () => {
|
|
26
|
-
a = (a +
|
|
26
|
+
a = (a + 0x9e_37_79_b9) | 0;
|
|
27
27
|
let t = a ^ (a >>> 16);
|
|
28
|
-
t = Math.imul(t,
|
|
28
|
+
t = Math.imul(t, 0x21_f0_aa_ad);
|
|
29
29
|
t = t ^ (t >>> 15);
|
|
30
|
-
t = Math.imul(t,
|
|
30
|
+
t = Math.imul(t, 0x73_5a_2d_97);
|
|
31
31
|
t = t ^ (t >>> 15);
|
|
32
|
-
return (t >>> 0) /
|
|
32
|
+
return (t >>> 0) / 4_294_967_296;
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
export function Scene({ level = 0, outputLevel = 0, phase = 'idle', colors, reducedMotion = false, pressed = false, variant = 'aurora', }) {
|