@medalsocial/meda 1.7.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/README.md +2 -1
- 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/app-shell-workspace.js +1 -1
- package/dist/shell/context-rail.d.ts +4 -2
- package/dist/shell/context-rail.js +27 -19
- package/dist/shell/extras/shell-scrollable-content.js +2 -2
- package/dist/shell/extras/workbench-layout.js +24 -13
- package/dist/shell/index.d.ts +1 -1
- package/dist/shell/internal/mobile-drawers.d.ts +6 -2
- package/dist/shell/internal/mobile-drawers.js +10 -7
- package/dist/shell/right-panel.js +10 -1
- package/dist/shell/shell-header.js +1 -1
- package/dist/shell/shell-provider.js +69 -53
- package/dist/shell/theme-next-themes.js +2 -2
- package/dist/shell/theme.js +7 -1
- package/dist/shell/types.d.ts +4 -0
- package/dist/shell/use-shell-viewport.js +14 -4
- 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, {
|
|
@@ -39,7 +39,7 @@ export function AppShellWorkspace({ iconRail, contextRail, rightPanel, workspace
|
|
|
39
39
|
// <AppShell> wrapper already enforces h-screen for the workspace variant, so
|
|
40
40
|
// nested viewport-height divs collapse cleanly — no double-scroll.
|
|
41
41
|
const commandRegistry = useContext(CommandRegistryContext);
|
|
42
|
-
const shell = (_jsxs("div", { className: "flex h-screen flex-col", children: [isMobile ? (_jsx(MobileHeader, { globalActions: globalActions })) : (_jsx(ShellHeader, { globalActions: globalActions, headerCenter: headerCenter, appTabsRenderLink: appTabs?.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter })), banners ? (_jsx("div", { "data-meda-banners": "", className: "flex-shrink-0", children: banners })) : (false), _jsxs("div", { className: "relative flex flex-1 overflow-hidden", children: [!isMobile && iconRail && (_jsx(IconRail, { mainItems: iconRail.mainItems, utilityItems: iconRail.utilityItems, footer: iconRail.footer, activeId: iconRail.activeId, renderLink: iconRail.renderLink })), !isMobile && contextRail && (_jsx(ContextRail, { appId: contextRail.appId, module: contextRail.module, activeItemId: contextRail.activeItemId })), _jsx(ShellMain, { layout: mainLayout ?? 'workspace', className: mainClassName, children: children }), !isMobile && resolvedRightPanel.panelViews.length > 0 && (_jsx(RightPanel, { panelViews: staticPanelViews, defaultView: rightPanel?.defaultView }))] }), isMobile && hasDrawerContent && _jsx(MobileBottomNav, { items: navItems }), isMobile && hasDrawerContent && (_jsx(MobileDrawers, { menuItems: mobileMenuItems, menuActiveId: iconRail?.activeId, menuRenderLink: iconRail?.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter, module: contextRail?.module, moduleAppId: contextRail?.appId, panelViews: resolvedRightPanel.panelViews, defaultView: resolvedRightPanel.defaultView }))] }));
|
|
42
|
+
const shell = (_jsxs("div", { className: "flex h-screen flex-col", children: [isMobile ? (_jsx(MobileHeader, { globalActions: globalActions })) : (_jsx(ShellHeader, { globalActions: globalActions, headerCenter: headerCenter, appTabsRenderLink: appTabs?.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter })), banners ? (_jsx("div", { "data-meda-banners": "", className: "flex-shrink-0", children: banners })) : (false), _jsxs("div", { className: "relative flex flex-1 overflow-hidden", children: [!isMobile && iconRail && (_jsx(IconRail, { mainItems: iconRail.mainItems, utilityItems: iconRail.utilityItems, footer: iconRail.footer, activeId: iconRail.activeId, renderLink: iconRail.renderLink })), !isMobile && contextRail && (_jsx(ContextRail, { appId: contextRail.appId, module: contextRail.module, activeItemId: contextRail.activeItemId, header: contextRail.header, scroll: contextRail.scroll })), _jsx(ShellMain, { layout: mainLayout ?? 'workspace', className: mainClassName, children: children }), !isMobile && resolvedRightPanel.panelViews.length > 0 && (_jsx(RightPanel, { panelViews: staticPanelViews, defaultView: rightPanel?.defaultView }))] }), isMobile && hasDrawerContent && _jsx(MobileBottomNav, { items: navItems }), isMobile && hasDrawerContent && (_jsx(MobileDrawers, { menuItems: mobileMenuItems, menuActiveId: iconRail?.activeId, menuRenderLink: iconRail?.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter, module: contextRail?.module, moduleAppId: contextRail?.appId, moduleHeader: contextRail?.header, moduleScroll: contextRail?.scroll, panelViews: resolvedRightPanel.panelViews, defaultView: resolvedRightPanel.defaultView }))] }));
|
|
43
43
|
return commandRegistry ? shell : _jsx(CommandPalette, { children: shell });
|
|
44
44
|
}
|
|
45
45
|
function buildMobileNavItems(contextRail, panelViews) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { ContextModule, ShellLinkRenderArgs } from './types.js';
|
|
2
|
+
import type { ContextModule, ContextRailHeader, ContextRailScroll, ShellLinkRenderArgs } from './types.js';
|
|
3
3
|
export interface ContextRailProps {
|
|
4
4
|
/** Drives the persistence key for layout state. */
|
|
5
5
|
appId: string;
|
|
@@ -12,6 +12,8 @@ export interface ContextRailProps {
|
|
|
12
12
|
/** Optional active item id for nav active state (preferred over useMedaShell().selection). */
|
|
13
13
|
activeItemId?: string;
|
|
14
14
|
renderLink?: (args: ShellLinkRenderArgs) => ReactNode;
|
|
15
|
+
header?: ContextRailHeader;
|
|
16
|
+
scroll?: ContextRailScroll;
|
|
15
17
|
className?: string;
|
|
16
18
|
}
|
|
17
|
-
export declare function ContextRail({ appId, module, hidden, collapsible, activeItemId, renderLink, className, }: ContextRailProps): import("react/jsx-runtime").JSX.Element | null;
|
|
19
|
+
export declare function ContextRail({ appId, module, hidden, collapsible, activeItemId, renderLink, header, scroll, className, }: ContextRailProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -75,7 +75,7 @@ function ContextRailToggle({ railId }) {
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
76
|
// ContextRail
|
|
77
77
|
// ---------------------------------------------------------------------------
|
|
78
|
-
export function ContextRail({ appId, module, hidden = false, collapsible = true, activeItemId, renderLink, className, }) {
|
|
78
|
+
export function ContextRail({ appId, module, hidden = false, collapsible = true, activeItemId, renderLink, header = 'auto', scroll = 'auto', className, }) {
|
|
79
79
|
const band = useShellViewport();
|
|
80
80
|
const ctx = useMedaShell();
|
|
81
81
|
// collapsible={false} means the rail must always render expanded — even if
|
|
@@ -92,6 +92,8 @@ export function ContextRail({ appId, module, hidden = false, collapsible = true,
|
|
|
92
92
|
const [displayWidth, setDisplayWidth] = useState(null);
|
|
93
93
|
const width = displayWidth ?? ctx.contextRail.width;
|
|
94
94
|
const items = module?.items ?? [];
|
|
95
|
+
const hasRender = typeof module?.render === 'function';
|
|
96
|
+
const showHeader = header === 'visible' || (header === 'auto' && items.length > 0 && !hasRender);
|
|
95
97
|
if (band === 'mobile')
|
|
96
98
|
return null;
|
|
97
99
|
if (hidden) {
|
|
@@ -113,22 +115,28 @@ export function ContextRail({ appId, module, hidden = false, collapsible = true,
|
|
|
113
115
|
// transition then so the rail snaps to the cursor instead of lagging
|
|
114
116
|
// behind it.
|
|
115
117
|
const isDragging = displayWidth !== null;
|
|
116
|
-
return (_jsxs("aside", { id: railId, "data-testid": "context-rail", "aria-label": module.label, className: cn('relative h-full shrink-0 border-r border-shell-border bg-shell-context', !isDragging && 'transition-[width] duration-200 ease-in-out motion-reduce:transition-none', collapsed && 'w-0', className), style: { width: collapsed ? 0 : width }, children: [collapsible && _jsx(ContextRailToggle, { railId: railId }), _jsxs("div", { className: "h-full overflow-hidden", "aria-hidden": collapsed, inert: collapsed || undefined, children: [_jsxs("div", { className: "border-b border-shell-border px-4 py-3", children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: module.label }), module.description && (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: module.description }))] }), items.length > 0 && (_jsx("nav", { "aria-label": `${module.label} navigation`, className: "flex flex-col gap-0.5 p-2", children: items.map((item) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
return (_jsxs("aside", { id: railId, "data-testid": "context-rail", "aria-label": module.label, className: cn('relative h-full shrink-0 border-r border-shell-border bg-shell-context', !isDragging && 'transition-[width] duration-200 ease-in-out motion-reduce:transition-none', collapsed && 'w-0', className), style: { width: collapsed ? 0 : width }, children: [collapsible && _jsx(ContextRailToggle, { railId: railId }), _jsxs("div", { className: "flex h-full min-w-0 flex-col overflow-hidden", "aria-hidden": collapsed, inert: collapsed || undefined, children: [showHeader && (_jsxs("div", { className: "shrink-0 border-b border-shell-border px-4 py-3", children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: module.label }), module.description && (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: module.description }))] })), _jsxs("div", { "data-meda-context-rail-scroll-area": "", className: cn('min-h-0 flex-1', scroll === 'auto' ? 'overflow-y-auto overflow-x-hidden' : 'overflow-hidden'), children: [items.length > 0 && (_jsx("nav", { "aria-label": `${module.label} navigation`, className: "flex flex-col gap-0.5 p-2", children: items.map((item) => {
|
|
119
|
+
const isActive = item.id === activeItemId;
|
|
120
|
+
const klass = cn('flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors', isActive
|
|
121
|
+
? 'bg-primary/10 text-primary'
|
|
122
|
+
: 'text-muted-foreground hover:bg-accent hover:text-foreground');
|
|
123
|
+
const IconComp = item.icon;
|
|
124
|
+
const inner = (_jsxs(_Fragment, { children: [_jsx(IconComp, { size: 16, "aria-hidden": "true", className: "shrink-0" }), _jsx("span", { className: "truncate", children: item.label }), item.shortcut && (_jsx("kbd", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: item.shortcut }))] }));
|
|
125
|
+
const linkProps = {
|
|
126
|
+
href: item.to,
|
|
127
|
+
'aria-current': isActive ? 'page' : undefined,
|
|
128
|
+
className: klass,
|
|
129
|
+
children: inner,
|
|
130
|
+
};
|
|
131
|
+
if (renderLink) {
|
|
132
|
+
return (_jsx(Fragment, { children: renderLink({
|
|
133
|
+
item,
|
|
134
|
+
isActive,
|
|
135
|
+
className: klass,
|
|
136
|
+
children: inner,
|
|
137
|
+
linkProps,
|
|
138
|
+
}) }, item.id));
|
|
139
|
+
}
|
|
140
|
+
return _jsx("a", { ...linkProps }, item.id);
|
|
141
|
+
}) })), module.render?.({ workspaceId: ctx.workspace.id, appId })] })] }), !collapsed && (_jsx(ResizeHandle, { currentWidth: width, onResize: handleResize, onCommit: handleCommit }))] }));
|
|
134
142
|
}
|
|
@@ -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
|
}
|
package/dist/shell/index.d.ts
CHANGED
|
@@ -27,5 +27,5 @@ export type { MedaShellProviderProps } from './shell-provider.js';
|
|
|
27
27
|
export { MedaShellProvider, useMedaShell, useShellSelection } from './shell-provider.js';
|
|
28
28
|
export { DefaultThemeProvider, ThemeToggle, useTheme } from './theme.js';
|
|
29
29
|
export { NextThemesAdapter } from './theme-next-themes.js';
|
|
30
|
-
export type { AppDefinition, AppShellAppTabsConfig, AppShellAuthBranding, AppShellAuthConfig, AppShellContextRailConfig, AppShellIconRailConfig, AppShellRightPanelConfig, AppShellVariant, AppShellWorkspaceConfig, AppTabRenderLinkArgs, CommandDefinition, ContextItem, ContextModule, MobileBottomNavItem, PanelMode, PanelView, ShellLinkRenderArgs, ShellMainLayout, ShellRenderContext, ShellViewport, ThemeAdapter, WorkspaceDefinition, WorkspaceMenuItem, } from './types.js';
|
|
30
|
+
export type { AppDefinition, AppShellAppTabsConfig, AppShellAuthBranding, AppShellAuthConfig, AppShellContextRailConfig, AppShellIconRailConfig, AppShellRightPanelConfig, AppShellVariant, AppShellWorkspaceConfig, AppTabRenderLinkArgs, CommandDefinition, ContextItem, ContextModule, ContextRailHeader, ContextRailScroll, MobileBottomNavItem, PanelMode, PanelView, ShellLinkRenderArgs, ShellMainLayout, ShellRenderContext, ShellViewport, ThemeAdapter, WorkspaceDefinition, WorkspaceMenuItem, } from './types.js';
|
|
31
31
|
export { useShellViewport } from './use-shell-viewport.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { IconRailItem, IconRailProps } from '../icon-rail.js';
|
|
3
|
-
import type { ContextModule, PanelView, WorkspaceMenuItem } from '../types.js';
|
|
3
|
+
import type { ContextModule, ContextRailHeader, ContextRailScroll, PanelView, WorkspaceMenuItem } from '../types.js';
|
|
4
4
|
export interface MobileDrawersProps {
|
|
5
5
|
/** Menu drawer source (icon-rail items). */
|
|
6
6
|
menuItems?: IconRailItem[];
|
|
@@ -16,6 +16,10 @@ export interface MobileDrawersProps {
|
|
|
16
16
|
module?: ContextModule;
|
|
17
17
|
/** App id used when rendering module custom content. */
|
|
18
18
|
moduleAppId?: string;
|
|
19
|
+
/** Header behavior mirrored from the desktop ContextRail. */
|
|
20
|
+
moduleHeader?: ContextRailHeader;
|
|
21
|
+
/** Scroll behavior mirrored from the desktop ContextRail. */
|
|
22
|
+
moduleScroll?: ContextRailScroll;
|
|
19
23
|
/** Panels drawer source. */
|
|
20
24
|
panelViews?: PanelView[];
|
|
21
25
|
/**
|
|
@@ -33,4 +37,4 @@ export interface MobileDrawersProps {
|
|
|
33
37
|
* any custom-content drawers. Mount once near the AppShell root; drawers
|
|
34
38
|
* open/close via `ctx.mobileDrawer.open` provider state.
|
|
35
39
|
*/
|
|
36
|
-
export declare function MobileDrawers({ menuItems, menuActiveId, menuRenderLink, workspaceMenuItems, workspaceMenuFooter, module, moduleAppId, panelViews, defaultView, customContent, }: MobileDrawersProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
export declare function MobileDrawers({ menuItems, menuActiveId, menuRenderLink, workspaceMenuItems, workspaceMenuFooter, module, moduleAppId, moduleHeader, moduleScroll, panelViews, defaultView, customContent, }: MobileDrawersProps): import("react/jsx-runtime").JSX.Element;
|