@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.
Files changed (207) hide show
  1. package/dist/calendar/calendar-toolbar.d.ts +2 -0
  2. package/dist/calendar/calendar-toolbar.js +22 -0
  3. package/dist/calendar/calendar.d.ts +2 -0
  4. package/dist/calendar/calendar.js +55 -0
  5. package/dist/calendar/day-view.d.ts +2 -0
  6. package/dist/calendar/day-view.js +40 -0
  7. package/dist/calendar/index.d.ts +6 -0
  8. package/dist/calendar/index.js +7 -0
  9. package/dist/calendar/internal/date.d.ts +26 -0
  10. package/dist/calendar/internal/date.js +107 -0
  11. package/dist/calendar/internal/day-cell.d.ts +15 -0
  12. package/dist/calendar/internal/day-cell.js +38 -0
  13. package/dist/calendar/internal/event-pill.d.ts +13 -0
  14. package/dist/calendar/internal/event-pill.js +30 -0
  15. package/dist/calendar/internal/time-slot.d.ts +17 -0
  16. package/dist/calendar/internal/time-slot.js +23 -0
  17. package/dist/calendar/month-view.d.ts +2 -0
  18. package/dist/calendar/month-view.js +46 -0
  19. package/dist/calendar/public.d.ts +6 -0
  20. package/dist/calendar/public.js +8 -0
  21. package/dist/calendar/types.d.ts +113 -0
  22. package/dist/calendar/types.js +15 -0
  23. package/dist/calendar/week-view.d.ts +2 -0
  24. package/dist/calendar/week-view.js +39 -0
  25. package/dist/chat/turn-card.js +10 -5
  26. package/dist/email-builder/block-error-boundary.d.ts +16 -0
  27. package/dist/email-builder/block-error-boundary.js +20 -0
  28. package/dist/email-builder/block-palette.d.ts +7 -0
  29. package/dist/email-builder/block-palette.js +22 -0
  30. package/dist/email-builder/block-registry.d.ts +15 -0
  31. package/dist/email-builder/block-registry.js +156 -0
  32. package/dist/email-builder/block-renderer.d.ts +5 -0
  33. package/dist/email-builder/block-renderer.js +40 -0
  34. package/dist/email-builder/block-renderers/button.d.ts +4 -0
  35. package/dist/email-builder/block-renderers/button.js +26 -0
  36. package/dist/email-builder/block-renderers/columns.d.ts +5 -0
  37. package/dist/email-builder/block-renderers/columns.js +27 -0
  38. package/dist/email-builder/block-renderers/divider.d.ts +4 -0
  39. package/dist/email-builder/block-renderers/divider.js +14 -0
  40. package/dist/email-builder/block-renderers/footer.d.ts +4 -0
  41. package/dist/email-builder/block-renderers/footer.js +12 -0
  42. package/dist/email-builder/block-renderers/heading.d.ts +4 -0
  43. package/dist/email-builder/block-renderers/heading.js +19 -0
  44. package/dist/email-builder/block-renderers/image.d.ts +4 -0
  45. package/dist/email-builder/block-renderers/image.js +18 -0
  46. package/dist/email-builder/block-renderers/social.d.ts +4 -0
  47. package/dist/email-builder/block-renderers/social.js +19 -0
  48. package/dist/email-builder/block-renderers/spacer.d.ts +4 -0
  49. package/dist/email-builder/block-renderers/spacer.js +5 -0
  50. package/dist/email-builder/block-renderers/text.d.ts +4 -0
  51. package/dist/email-builder/block-renderers/text.js +17 -0
  52. package/dist/email-builder/builder-canvas.d.ts +15 -0
  53. package/dist/email-builder/builder-canvas.js +26 -0
  54. package/dist/email-builder/builder-header.d.ts +9 -0
  55. package/dist/email-builder/builder-header.js +8 -0
  56. package/dist/email-builder/builder-left-tabs.d.ts +11 -0
  57. package/dist/email-builder/builder-left-tabs.js +19 -0
  58. package/dist/email-builder/email-builder.d.ts +3 -0
  59. package/dist/email-builder/email-builder.js +84 -0
  60. package/dist/email-builder/envelope-card.d.ts +7 -0
  61. package/dist/email-builder/envelope-card.js +16 -0
  62. package/dist/email-builder/floating-bar.d.ts +13 -0
  63. package/dist/email-builder/floating-bar.js +9 -0
  64. package/dist/email-builder/index.d.ts +4 -0
  65. package/dist/email-builder/index.js +12 -0
  66. package/dist/email-builder/internal/builder-state-utils.d.ts +7 -0
  67. package/dist/email-builder/internal/builder-state-utils.js +52 -0
  68. package/dist/email-builder/internal/dnd-wrapper.d.ts +10 -0
  69. package/dist/email-builder/internal/dnd-wrapper.js +9 -0
  70. package/dist/email-builder/internal/email-font-options.d.ts +5 -0
  71. package/dist/email-builder/internal/email-font-options.js +12 -0
  72. package/dist/email-builder/mobile-drawers.d.ts +10 -0
  73. package/dist/email-builder/mobile-drawers.js +7 -0
  74. package/dist/email-builder/mobile-tab-bar.d.ts +10 -0
  75. package/dist/email-builder/mobile-tab-bar.js +9 -0
  76. package/dist/email-builder/property-editors/_shared.d.ts +58 -0
  77. package/dist/email-builder/property-editors/_shared.js +52 -0
  78. package/dist/email-builder/property-editors/button-editor.d.ts +7 -0
  79. package/dist/email-builder/property-editors/button-editor.js +10 -0
  80. package/dist/email-builder/property-editors/columns-editor.d.ts +7 -0
  81. package/dist/email-builder/property-editors/columns-editor.js +15 -0
  82. package/dist/email-builder/property-editors/divider-editor.d.ts +7 -0
  83. package/dist/email-builder/property-editors/divider-editor.js +10 -0
  84. package/dist/email-builder/property-editors/footer-editor.d.ts +7 -0
  85. package/dist/email-builder/property-editors/footer-editor.js +6 -0
  86. package/dist/email-builder/property-editors/heading-editor.d.ts +7 -0
  87. package/dist/email-builder/property-editors/heading-editor.js +16 -0
  88. package/dist/email-builder/property-editors/image-editor.d.ts +8 -0
  89. package/dist/email-builder/property-editors/image-editor.js +17 -0
  90. package/dist/email-builder/property-editors/social-editor.d.ts +7 -0
  91. package/dist/email-builder/property-editors/social-editor.js +18 -0
  92. package/dist/email-builder/property-editors/spacer-editor.d.ts +7 -0
  93. package/dist/email-builder/property-editors/spacer-editor.js +6 -0
  94. package/dist/email-builder/property-editors/text-editor.d.ts +13 -0
  95. package/dist/email-builder/property-editors/text-editor.js +8 -0
  96. package/dist/email-builder/property-inspector.d.ts +11 -0
  97. package/dist/email-builder/property-inspector.js +42 -0
  98. package/dist/email-builder/public.d.ts +1 -0
  99. package/dist/email-builder/public.js +3 -0
  100. package/dist/email-builder/render-to-email-html.d.ts +11 -0
  101. package/dist/email-builder/render-to-email-html.js +219 -0
  102. package/dist/email-builder/starter-shell.d.ts +3 -0
  103. package/dist/email-builder/starter-shell.js +21 -0
  104. package/dist/email-builder/types.d.ts +182 -0
  105. package/dist/email-builder/types.js +41 -0
  106. package/dist/email-builder/view-controls.d.ts +10 -0
  107. package/dist/email-builder/view-controls.js +12 -0
  108. package/dist/index.d.ts +4 -0
  109. package/dist/index.js +4 -0
  110. package/dist/lib/format-time.js +2 -3
  111. package/dist/post-preview/index.d.ts +4 -0
  112. package/dist/post-preview/index.js +7 -0
  113. package/dist/post-preview/internal/avatar.d.ts +12 -0
  114. package/dist/post-preview/internal/avatar.js +21 -0
  115. package/dist/post-preview/internal/format-content.d.ts +12 -0
  116. package/dist/post-preview/internal/format-content.js +31 -0
  117. package/dist/post-preview/internal/platform-meta.d.ts +10 -0
  118. package/dist/post-preview/internal/platform-meta.js +18 -0
  119. package/dist/post-preview/internal/textarea.d.ts +8 -0
  120. package/dist/post-preview/internal/textarea.js +12 -0
  121. package/dist/post-preview/internal/use-swipe.d.ts +17 -0
  122. package/dist/post-preview/internal/use-swipe.js +29 -0
  123. package/dist/post-preview/platforms/bluesky.d.ts +15 -0
  124. package/dist/post-preview/platforms/bluesky.js +21 -0
  125. package/dist/post-preview/platforms/discord.d.ts +44 -0
  126. package/dist/post-preview/platforms/discord.js +32 -0
  127. package/dist/post-preview/platforms/facebook.d.ts +23 -0
  128. package/dist/post-preview/platforms/facebook.js +24 -0
  129. package/dist/post-preview/platforms/generic.d.ts +22 -0
  130. package/dist/post-preview/platforms/generic.js +28 -0
  131. package/dist/post-preview/platforms/google-business.d.ts +18 -0
  132. package/dist/post-preview/platforms/google-business.js +22 -0
  133. package/dist/post-preview/platforms/index.d.ts +24 -0
  134. package/dist/post-preview/platforms/index.js +13 -0
  135. package/dist/post-preview/platforms/instagram.d.ts +31 -0
  136. package/dist/post-preview/platforms/instagram.js +99 -0
  137. package/dist/post-preview/platforms/linkedin.d.ts +52 -0
  138. package/dist/post-preview/platforms/linkedin.js +131 -0
  139. package/dist/post-preview/platforms/telegram.d.ts +46 -0
  140. package/dist/post-preview/platforms/telegram.js +42 -0
  141. package/dist/post-preview/platforms/threads.d.ts +19 -0
  142. package/dist/post-preview/platforms/threads.js +24 -0
  143. package/dist/post-preview/platforms/tiktok.d.ts +18 -0
  144. package/dist/post-preview/platforms/tiktok.js +23 -0
  145. package/dist/post-preview/platforms/twitter.d.ts +40 -0
  146. package/dist/post-preview/platforms/twitter.js +132 -0
  147. package/dist/post-preview/platforms/youtube.d.ts +17 -0
  148. package/dist/post-preview/platforms/youtube.js +21 -0
  149. package/dist/post-preview/post-preview.d.ts +29 -0
  150. package/dist/post-preview/post-preview.js +70 -0
  151. package/dist/post-preview/public.d.ts +1 -0
  152. package/dist/post-preview/public.js +6 -0
  153. package/dist/post-preview/types.d.ts +82 -0
  154. package/dist/post-preview/types.js +1 -0
  155. package/dist/primitives/filter-rail.js +17 -2
  156. package/dist/shell/extras/shell-scrollable-content.js +2 -2
  157. package/dist/shell/extras/workbench-layout.js +24 -13
  158. package/dist/shell/right-panel.js +10 -1
  159. package/dist/shell/shell-header.js +1 -1
  160. package/dist/shell/theme-next-themes.js +2 -2
  161. package/dist/shell/theme.js +7 -1
  162. package/dist/timeline/lane-timeline.js +1 -1
  163. package/dist/timeline/scrub-bar.js +1 -1
  164. package/dist/timeline/timeline-tape.js +8 -5
  165. package/dist/voice/voice-orb-scene.js +4 -4
  166. package/dist/voice/voice-status-pill.js +8 -7
  167. package/dist/workflow-builder/edges/default-edge.d.ts +10 -0
  168. package/dist/workflow-builder/edges/default-edge.js +21 -0
  169. package/dist/workflow-builder/edges/index.d.ts +3 -0
  170. package/dist/workflow-builder/edges/index.js +5 -0
  171. package/dist/workflow-builder/index.d.ts +5 -0
  172. package/dist/workflow-builder/index.js +12 -0
  173. package/dist/workflow-builder/internal/trigger-icons.d.ts +4 -0
  174. package/dist/workflow-builder/internal/trigger-icons.js +43 -0
  175. package/dist/workflow-builder/internal/trigger-sentence.d.ts +14 -0
  176. package/dist/workflow-builder/internal/trigger-sentence.js +50 -0
  177. package/dist/workflow-builder/nodes/action-node.d.ts +3 -0
  178. package/dist/workflow-builder/nodes/action-node.js +7 -0
  179. package/dist/workflow-builder/nodes/base-node.d.ts +20 -0
  180. package/dist/workflow-builder/nodes/base-node.js +18 -0
  181. package/dist/workflow-builder/nodes/condition-node.d.ts +3 -0
  182. package/dist/workflow-builder/nodes/condition-node.js +11 -0
  183. package/dist/workflow-builder/nodes/delay-node.d.ts +3 -0
  184. package/dist/workflow-builder/nodes/delay-node.js +7 -0
  185. package/dist/workflow-builder/nodes/end-node.d.ts +3 -0
  186. package/dist/workflow-builder/nodes/end-node.js +7 -0
  187. package/dist/workflow-builder/nodes/index.d.ts +13 -0
  188. package/dist/workflow-builder/nodes/index.js +22 -0
  189. package/dist/workflow-builder/nodes/trigger-node.d.ts +3 -0
  190. package/dist/workflow-builder/nodes/trigger-node.js +8 -0
  191. package/dist/workflow-builder/public.d.ts +1 -0
  192. package/dist/workflow-builder/public.js +4 -0
  193. package/dist/workflow-builder/types.d.ts +140 -0
  194. package/dist/workflow-builder/types.js +18 -0
  195. package/dist/workflow-builder/workflow-builder.d.ts +10 -0
  196. package/dist/workflow-builder/workflow-builder.js +22 -0
  197. package/dist/workflow-builder/workflow-canvas.d.ts +26 -0
  198. package/dist/workflow-builder/workflow-canvas.js +25 -0
  199. package/dist/workflow-builder/workflow-card-compact.d.ts +2 -0
  200. package/dist/workflow-builder/workflow-card-compact.js +22 -0
  201. package/dist/workflow-builder/workflow-card.d.ts +2 -0
  202. package/dist/workflow-builder/workflow-card.js +44 -0
  203. package/dist/workflow-builder/workflow-header.d.ts +2 -0
  204. package/dist/workflow-builder/workflow-header.js +31 -0
  205. package/dist/workflow-builder/workflow-toolbox.d.ts +2 -0
  206. package/dist/workflow-builder/workflow-toolbox.js +19 -0
  207. package/package.json +72 -50
@@ -0,0 +1,31 @@
1
+ import type { PostPreviewBaseProps } from '../types.js';
2
+ export type InstagramPostType = 'feed' | 'carousel' | 'reel' | 'story';
3
+ export interface InstagramLabels {
4
+ placeholder: string;
5
+ justNow: string;
6
+ likes: (count: number) => string;
7
+ addImage: string;
8
+ moreOptions: string;
9
+ like: string;
10
+ comment: string;
11
+ share: string;
12
+ save: string;
13
+ carouselPrev: string;
14
+ carouselNext: string;
15
+ carouselCounter: (current: number, total: number) => string;
16
+ reelLabel: string;
17
+ reelAudio: string;
18
+ storyLabel: string;
19
+ storySendMessage: string;
20
+ }
21
+ export declare const DEFAULT_INSTAGRAM_LABELS: InstagramLabels;
22
+ export interface InstagramPreviewProps extends PostPreviewBaseProps {
23
+ labels?: Partial<InstagramLabels>;
24
+ /** Override the layout. Auto-detects to `carousel` for >1 media. */
25
+ instagramPostType?: InstagramPostType;
26
+ }
27
+ /**
28
+ * Instagram preview supporting Feed / Carousel / Reel / Story layouts.
29
+ * Auto-detects `carousel` when more than one media URL is present.
30
+ */
31
+ export declare function InstagramPreview({ displayName, username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, instagramPostType, }: InstagramPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,99 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Bookmark, ChevronLeft, ChevronRight, Heart, MessageCircle, MoreHorizontal, Music, Send, } from 'lucide-react';
4
+ import { useCallback, 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
+ import { useSwipe } from '../internal/use-swipe.js';
9
+ export const DEFAULT_INSTAGRAM_LABELS = {
10
+ placeholder: 'Write a caption...',
11
+ justNow: 'Just now',
12
+ likes: (count) => `${count} likes`,
13
+ addImage: 'Add an image to preview',
14
+ moreOptions: 'More options',
15
+ like: 'Like',
16
+ comment: 'Comment',
17
+ share: 'Share',
18
+ save: 'Save',
19
+ carouselPrev: 'Previous slide',
20
+ carouselNext: 'Next slide',
21
+ carouselCounter: (current, total) => `${current} / ${total}`,
22
+ reelLabel: 'Reels',
23
+ reelAudio: 'Original audio',
24
+ storyLabel: 'Story',
25
+ storySendMessage: 'Send message',
26
+ };
27
+ /**
28
+ * Instagram preview supporting Feed / Carousel / Reel / Story layouts.
29
+ * Auto-detects `carousel` when more than one media URL is present.
30
+ */
31
+ export function InstagramPreview({ displayName, username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, instagramPostType, }) {
32
+ const l = { ...DEFAULT_INSTAGRAM_LABELS, ...labels };
33
+ const formattedUsername = username.startsWith('@') ? username.slice(1) : username;
34
+ const hasMedia = mediaUrls !== undefined && mediaUrls.length > 0;
35
+ const postType = instagramPostType ?? (mediaUrls && mediaUrls.length > 1 ? 'carousel' : 'feed');
36
+ if (postType === 'reel') {
37
+ return (_jsx(ReelPreview, { displayName: displayName, username: formattedUsername, avatarUrl: avatarUrl, content: content, mediaUrl: hasMedia ? mediaUrls[0] : undefined, labels: l, className: className }));
38
+ }
39
+ if (postType === 'story') {
40
+ return (_jsx(StoryPreview, { displayName: displayName, username: formattedUsername, avatarUrl: avatarUrl, mediaUrl: hasMedia ? mediaUrls[0] : undefined, labels: l, className: className }));
41
+ }
42
+ return (_jsx(FeedPreview, { displayName: displayName, username: formattedUsername, avatarUrl: avatarUrl, content: content, mediaUrls: mediaUrls, editable: editable, onContentChange: onContentChange, isCarousel: postType === 'carousel', labels: l, className: className }));
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // Feed / Carousel layout
46
+ // ---------------------------------------------------------------------------
47
+ function FeedPreview({ displayName, username, avatarUrl, content, mediaUrls, editable, onContentChange, isCarousel, labels, className, }) {
48
+ const hasMedia = mediaUrls !== undefined && mediaUrls.length > 0;
49
+ const [carouselIndex, setCarouselIndex] = useState(0);
50
+ const goToPrev = useCallback(() => {
51
+ setCarouselIndex((prev) => Math.max(0, prev - 1));
52
+ }, []);
53
+ const goToNext = useCallback(() => {
54
+ if (!mediaUrls)
55
+ return;
56
+ setCarouselIndex((prev) => Math.min(mediaUrls.length - 1, prev + 1));
57
+ }, [mediaUrls]);
58
+ const swipe = useSwipe({ onSwipeLeft: goToNext, onSwipeRight: goToPrev });
59
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "instagram", "data-instagram-post-type": isCarousel ? 'carousel' : 'feed', className: cn('@container bg-white text-gray-900 @[420px]:text-[15px] dark:bg-black dark:text-white', className), children: [_jsxs("div", { className: "flex items-center justify-between p-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-8 w-8 ring-2 ring-pink-500 ring-offset-2" }), _jsx("p", { className: "font-semibold text-[14px]", children: username })] }), _jsx("button", { type: "button", "aria-label": labels.moreOptions, className: "-m-2 inline-flex min-h-11 min-w-11 items-center justify-center p-2", children: _jsx(MoreHorizontal, { className: "h-5 w-5" }) })] }), hasMedia ? (_jsxs("div", { "data-slot": "instagram-carousel-track", className: "relative aspect-square bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500", style: { touchAction: 'pan-y' }, ...(isCarousel && mediaUrls.length > 1
60
+ ? {
61
+ role: 'region',
62
+ 'aria-label': labels.carouselCounter(carouselIndex + 1, mediaUrls.length),
63
+ tabIndex: 0,
64
+ onKeyDown: (event) => {
65
+ if (event.key === 'ArrowLeft') {
66
+ event.preventDefault();
67
+ event.stopPropagation();
68
+ goToPrev();
69
+ }
70
+ else if (event.key === 'ArrowRight') {
71
+ event.preventDefault();
72
+ event.stopPropagation();
73
+ goToNext();
74
+ }
75
+ },
76
+ }
77
+ : {}), ...swipe, children: [_jsx("img", { src: mediaUrls[isCarousel ? carouselIndex : 0], alt: "Post", loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }), isCarousel && mediaUrls.length > 1 && (_jsxs(_Fragment, { children: [carouselIndex > 0 && (_jsx("button", { type: "button", onClick: goToPrev, "aria-label": labels.carouselPrev, className: "-translate-y-1/2 absolute top-1/2 left-2 inline-flex h-7 w-7 items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70", children: _jsx(ChevronLeft, { className: "h-4 w-4" }) })), carouselIndex < mediaUrls.length - 1 && (_jsx("button", { type: "button", onClick: goToNext, "aria-label": labels.carouselNext, className: "-translate-y-1/2 absolute top-1/2 right-2 inline-flex h-7 w-7 items-center justify-center rounded-full bg-black/50 text-white hover:bg-black/70", children: _jsx(ChevronRight, { className: "h-4 w-4" }) })), _jsx("div", { className: "absolute top-3 right-3 rounded-full bg-black/60 px-2.5 py-0.5 font-medium text-white text-xs", children: labels.carouselCounter(carouselIndex + 1, mediaUrls.length) }), _jsxs("div", { className: "-translate-x-1/2 absolute bottom-4 left-1/2 flex gap-1", children: [mediaUrls.slice(0, 5).map((url, i) => (_jsx("button", { type: "button", "aria-label": `Go to slide ${i + 1}`, ...(i === carouselIndex ? { 'aria-current': 'true' } : {}), onClick: () => setCarouselIndex(i), className: cn(
78
+ // Visible dot stays small; expand the touch target via padding so the
79
+ // hit area is at least 44x44px without breaking the IG visual.
80
+ 'inline-flex min-h-11 min-w-11 items-center justify-center p-2'), children: _jsx("span", { "aria-hidden": "true", className: cn('h-1.5 w-1.5 rounded-full transition-colors', i === carouselIndex ? 'bg-blue-500' : 'bg-white/60') }) }, url))), mediaUrls.length > 5 && (_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-white/40", "aria-hidden": "true" }))] })] }))] })) : (_jsx(MediaPlaceholder, { label: labels.addImage })), _jsxs("div", { className: "p-3", children: [_jsxs("div", { className: "mb-3 flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("button", { type: "button", "aria-label": labels.like, className: "-m-1 inline-flex min-h-11 min-w-11 items-center justify-center p-1 hover:opacity-60", children: _jsx(Heart, { className: "h-6 w-6" }) }), _jsx("button", { type: "button", "aria-label": labels.comment, className: "-m-1 inline-flex min-h-11 min-w-11 items-center justify-center p-1 hover:opacity-60", children: _jsx(MessageCircle, { className: "-scale-x-100 h-6 w-6" }) }), _jsx("button", { type: "button", "aria-label": labels.share, className: "-m-1 inline-flex min-h-11 min-w-11 items-center justify-center p-1 hover:opacity-60", children: _jsx(Send, { className: "-rotate-12 h-6 w-6" }) })] }), _jsx("button", { type: "button", "aria-label": labels.save, className: "-m-1 inline-flex min-h-11 min-w-11 items-center justify-center p-1 hover:opacity-60", children: _jsx(Bookmark, { className: "h-6 w-6" }) })] }), _jsx("p", { className: "mb-1 font-semibold text-[14px]", children: labels.likes(0) }), _jsxs("div", { className: "text-[14px]", children: [_jsx("span", { className: "mr-1 font-semibold", children: username }), editable ? (_jsx(Textarea, { value: content, onChange: (e) => onContentChange?.(e.target.value), placeholder: labels.placeholder, className: "mt-1 inline min-h-[40px] w-full text-[14px] text-gray-900 placeholder:text-gray-500" })) : (_jsx("span", { className: "whitespace-pre-wrap break-words", children: content }))] }), _jsx("p", { className: "mt-2 text-[10px] text-gray-400 uppercase tracking-wide", children: labels.justNow })] })] }));
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // Reel layout
84
+ // ---------------------------------------------------------------------------
85
+ function ReelPreview({ displayName, username, avatarUrl, content, mediaUrl, labels, className, }) {
86
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "instagram", "data-instagram-post-type": "reel", className: cn('@container relative aspect-[9/16] overflow-hidden rounded-lg bg-black @[420px]:rounded-xl', className), children: [mediaUrl ? (_jsx("img", { src: mediaUrl, alt: "Reel", loading: "lazy", decoding: "async", className: "h-full w-full object-cover" })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-b from-gray-800 to-gray-900", children: _jsxs("div", { className: "text-center text-gray-500", children: [_jsx("div", { className: "mx-auto mb-2 h-16 w-16 rounded-full border-2 border-gray-600 border-dashed" }), _jsx("p", { className: "text-sm", children: labels.reelLabel })] }) })), _jsx("div", { className: "absolute top-4 right-3 font-semibold text-sm text-white drop-shadow-lg", children: labels.reelLabel }), _jsxs("div", { className: "absolute right-3 bottom-20 flex flex-col items-center gap-5", children: [_jsxs("button", { type: "button", "aria-label": labels.like, className: "inline-flex min-h-11 min-w-11 flex-col items-center gap-0.5", children: [_jsx(Heart, { className: "h-7 w-7 text-white drop-shadow-lg" }), _jsx("span", { className: "text-[11px] text-white drop-shadow-lg", children: "0" })] }), _jsxs("button", { type: "button", "aria-label": labels.comment, className: "inline-flex min-h-11 min-w-11 flex-col items-center gap-0.5", children: [_jsx(MessageCircle, { className: "-scale-x-100 h-7 w-7 text-white drop-shadow-lg" }), _jsx("span", { className: "text-[11px] text-white drop-shadow-lg", children: "0" })] }), _jsx("button", { type: "button", "aria-label": labels.share, className: "inline-flex min-h-11 min-w-11 flex-col items-center gap-0.5", children: _jsx(Send, { className: "-rotate-12 h-7 w-7 text-white drop-shadow-lg" }) }), _jsx("button", { type: "button", "aria-label": labels.moreOptions, className: "inline-flex min-h-11 min-w-11 flex-col items-center gap-0.5", children: _jsx(MoreHorizontal, { className: "h-7 w-7 text-white drop-shadow-lg" }) }), _jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-8 w-8 rounded-md border-2 border-white/40" })] }), _jsxs("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent p-4 pb-5", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-8 w-8" }), _jsx("span", { className: "font-semibold text-sm text-white", children: username })] }), content ? _jsx("p", { className: "line-clamp-2 text-sm text-white/90", children: content }) : null, _jsxs("div", { className: "mt-3 flex items-center gap-2", children: [_jsx(Music, { className: "h-3 w-3 text-white/80", "aria-hidden": "true" }), _jsxs("p", { className: "flex-1 truncate text-white/80 text-xs", children: [labels.reelAudio, " \u00B7 ", username] })] })] })] }));
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // Story layout
90
+ // ---------------------------------------------------------------------------
91
+ function StoryPreview({ displayName, username, avatarUrl, mediaUrl, labels, className, }) {
92
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "instagram", "data-instagram-post-type": "story", className: cn('@container relative aspect-[9/16] overflow-hidden rounded-lg bg-black @[420px]:rounded-xl', className), children: [mediaUrl ? (_jsx("img", { src: mediaUrl, alt: "Story", loading: "lazy", decoding: "async", className: "h-full w-full object-cover" })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-b from-gray-800 to-gray-900", children: _jsxs("div", { className: "text-center text-gray-500", children: [_jsx("div", { className: "mx-auto mb-2 h-16 w-16 rounded-full border-2 border-gray-600 border-dashed" }), _jsx("p", { className: "text-sm", children: labels.storyLabel })] }) })), _jsx("div", { className: "absolute inset-x-0 top-0 px-2 pt-2", children: _jsx("div", { className: "h-0.5 w-full overflow-hidden rounded-full bg-white/30", children: _jsx("div", { className: "h-full w-1/3 rounded-full bg-white" }) }) }), _jsxs("div", { className: "absolute inset-x-0 top-3 flex items-center gap-2 px-4 pt-1", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-8 w-8 ring-2 ring-white/60" }), _jsx("span", { className: "font-semibold text-sm text-white drop-shadow-lg", children: username }), _jsx("span", { className: "text-white/70 text-xs drop-shadow-lg", children: labels.justNow }), _jsx("div", { className: "ml-auto", children: _jsx(MoreHorizontal, { className: "h-5 w-5 text-white drop-shadow-lg", "aria-hidden": "true" }) })] }), _jsx("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/50 to-transparent p-4 pt-8", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex-1 rounded-full border border-white/40 px-4 py-2", children: _jsx("span", { className: "text-sm text-white/60", children: labels.storySendMessage }) }), _jsx(Heart, { className: "h-6 w-6 text-white", "aria-hidden": "true" }), _jsx(Send, { className: "-rotate-12 h-6 w-6 text-white", "aria-hidden": "true" })] }) })] }));
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Shared
96
+ // ---------------------------------------------------------------------------
97
+ function MediaPlaceholder({ label }) {
98
+ return (_jsx("div", { className: "flex aspect-square items-center justify-center bg-gradient-to-br from-gray-100 to-gray-200", children: _jsxs("div", { className: "text-center text-gray-400", children: [_jsx("svg", { className: "mx-auto mb-2 h-16 w-16", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }), _jsx("p", { className: "text-sm", children: label })] }) }));
99
+ }
@@ -0,0 +1,52 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { PostPreviewBaseProps } from '../types.js';
3
+ /**
4
+ * LinkedIn mention metadata. Wire format used by the LinkedIn share API:
5
+ * `@[Name](urn:li:person:abc)` is rendered as a plain `Name` token in the
6
+ * UI but the spans here let consumers persist the mention range +
7
+ * URN reference.
8
+ */
9
+ export interface LinkedInMentionData {
10
+ /** Zero-based start offset within the post content. */
11
+ offset: number;
12
+ /** Length of the rendered name in the post content. */
13
+ length: number;
14
+ /** LinkedIn URN, e.g. `urn:li:person:ABC123`. */
15
+ urn: string;
16
+ /** Display name to highlight. */
17
+ name: string;
18
+ }
19
+ export interface LinkedInMentionPickerContext {
20
+ query: string;
21
+ onPick: (mention: LinkedInMentionData) => void;
22
+ onCancel: () => void;
23
+ }
24
+ export interface LinkedInLabels {
25
+ placeholder: string;
26
+ now: string;
27
+ moreOptions: string;
28
+ comments: (count: number) => string;
29
+ reposts: (count: number) => string;
30
+ like: string;
31
+ comment: string;
32
+ repost: string;
33
+ send: string;
34
+ }
35
+ export declare const DEFAULT_LINKEDIN_LABELS: LinkedInLabels;
36
+ export interface LinkedInPreviewProps extends PostPreviewBaseProps {
37
+ labels?: Partial<LinkedInLabels>;
38
+ /** Mention spans to highlight in rendered content. */
39
+ mentions?: LinkedInMentionData[];
40
+ /** Called when the user picks a new mention via `renderMentionPicker`. */
41
+ onMentionsChange?: (mentions: LinkedInMentionData[]) => void;
42
+ /** Optional render slot for an `@mention` picker. Called with the
43
+ * current query (text after `@`) when the user types `@xxx` in the
44
+ * editable textarea. */
45
+ renderMentionPicker?: (ctx: LinkedInMentionPickerContext) => ReactNode;
46
+ }
47
+ /**
48
+ * LinkedIn feed-card preview. Renders avatar, name, headline (`username`),
49
+ * content with optional `@mention` highlight spans, media grid, and the
50
+ * standard Like/Comment/Repost/Send footer.
51
+ */
52
+ export declare function LinkedInPreview({ displayName, username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, mentions, onMentionsChange, renderMentionPicker, }: LinkedInPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,131 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Globe, MessageSquare, MoreHorizontal, Repeat2, Send, ThumbsUp } 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
+ // skipcq: SCT-1000 — `LinkedInLabels` is a TypeScript label-strings interface,
9
+ // not a client credential. DeepSource's secrets regex misclassifies the name.
10
+ export const DEFAULT_LINKEDIN_LABELS = {
11
+ placeholder: 'Share your thoughts...',
12
+ now: 'now',
13
+ moreOptions: 'More options',
14
+ comments: (count) => `${count} comments`,
15
+ reposts: (count) => `${count} reposts`,
16
+ like: 'Like',
17
+ comment: 'Comment',
18
+ repost: 'Repost',
19
+ send: 'Send',
20
+ };
21
+ function useLinkedInMentionTrigger(textareaRef, value, onChange, mentions, onMentionsChange) {
22
+ const [isOpen, setIsOpen] = useState(false);
23
+ const [query, setQuery] = useState('');
24
+ const triggerStartRef = useRef(null);
25
+ const close = useCallback(() => {
26
+ setIsOpen(false);
27
+ setQuery('');
28
+ triggerStartRef.current = null;
29
+ }, []);
30
+ const handleInput = useCallback(() => {
31
+ const ta = textareaRef.current;
32
+ if (!ta)
33
+ return;
34
+ const cursor = ta.selectionStart ?? 0;
35
+ const text = ta.value;
36
+ let i = cursor - 1;
37
+ let foundAt = -1;
38
+ while (i >= 0) {
39
+ const ch = text[i];
40
+ if (ch === '@') {
41
+ if (i === 0 || /\s/.test(text[i - 1]))
42
+ foundAt = i;
43
+ break;
44
+ }
45
+ if (/\s/.test(ch))
46
+ break;
47
+ i -= 1;
48
+ }
49
+ if (foundAt === -1) {
50
+ if (isOpen)
51
+ close();
52
+ return;
53
+ }
54
+ triggerStartRef.current = foundAt;
55
+ setQuery(text.slice(foundAt + 1, cursor));
56
+ setIsOpen(true);
57
+ }, [textareaRef, isOpen, close]);
58
+ const insertMention = useCallback((picked) => {
59
+ const ta = textareaRef.current;
60
+ if (!ta)
61
+ return;
62
+ const start = triggerStartRef.current;
63
+ if (start === null)
64
+ return;
65
+ const cursor = ta.selectionStart ?? 0;
66
+ const before = value.slice(0, start);
67
+ const after = value.slice(cursor);
68
+ const next = `${before}${picked.name} ${after}`;
69
+ onChange?.(next);
70
+ const span = {
71
+ ...picked,
72
+ offset: start,
73
+ length: picked.name.length,
74
+ };
75
+ onMentionsChange?.([...mentions, span]);
76
+ close();
77
+ }, [textareaRef, value, onChange, mentions, onMentionsChange, close]);
78
+ return { isOpen, query, close, insertMention, handleInput };
79
+ }
80
+ function MentionedContent({ text, mentions }) {
81
+ if (!mentions || mentions.length === 0) {
82
+ return _jsx("span", { className: "whitespace-pre-wrap break-words text-[14px]", children: text });
83
+ }
84
+ // Sort spans by offset and render alternating plain/highlight segments.
85
+ const sorted = [...mentions].sort((a, b) => a.offset - b.offset);
86
+ const segments = [];
87
+ let cursor = 0;
88
+ sorted.forEach((mention, idx) => {
89
+ if (mention.offset > cursor) {
90
+ segments.push({
91
+ key: `t-${cursor}`,
92
+ isMention: false,
93
+ value: text.slice(cursor, mention.offset),
94
+ });
95
+ }
96
+ const end = mention.offset + mention.length;
97
+ segments.push({
98
+ key: `m-${idx}-${mention.urn}`,
99
+ isMention: true,
100
+ value: text.slice(mention.offset, end),
101
+ });
102
+ cursor = end;
103
+ });
104
+ if (cursor < text.length) {
105
+ segments.push({ key: `t-${cursor}`, isMention: false, value: text.slice(cursor) });
106
+ }
107
+ return (_jsx("span", { className: "whitespace-pre-wrap break-words text-[14px]", children: segments.map((seg) => seg.isMention ? (_jsx("span", { "data-slot": "linkedin-mention", className: "font-semibold text-[#0A66C2]", children: seg.value }, seg.key)) : (_jsx("span", { children: seg.value }, seg.key))) }));
108
+ }
109
+ /**
110
+ * LinkedIn feed-card preview. Renders avatar, name, headline (`username`),
111
+ * content with optional `@mention` highlight spans, media grid, and the
112
+ * standard Like/Comment/Repost/Send footer.
113
+ */
114
+ export function LinkedInPreview({ displayName, username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, mentions = [], onMentionsChange, renderMentionPicker, }) {
115
+ const l = { ...DEFAULT_LINKEDIN_LABELS, ...labels };
116
+ const textareaRef = useRef(null);
117
+ const mention = useLinkedInMentionTrigger(textareaRef, content, onContentChange, mentions, onMentionsChange);
118
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "linkedin", className: cn('@container bg-white text-gray-900 @[420px]:text-[15px] dark:bg-[#1B1F23] dark:text-[#E7E9EA]', className), children: [_jsxs("div", { className: "p-4 pb-0", children: [_jsxs("div", { className: "flex gap-3", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-12 w-12 flex-shrink-0" }), _jsx("div", { className: "min-w-0 flex-1", children: _jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { children: [_jsx("p", { className: "font-semibold text-[14px] text-gray-900", children: displayName }), _jsx("p", { className: "line-clamp-1 text-[12px] text-gray-500", children: username }), _jsxs("div", { className: "flex items-center gap-1 text-[12px] text-gray-500", children: [_jsx("span", { children: l.now }), _jsx("span", { children: "\u00B7" }), _jsx(Globe, { className: "h-3 w-3", "aria-hidden": "true" })] })] }), _jsx("button", { type: "button", "aria-label": l.moreOptions, className: "-m-1.5 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-1.5 text-gray-500 hover:bg-gray-100", children: _jsx(MoreHorizontal, { className: "h-5 w-5" }) })] }) })] }), 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-3 min-h-[80px] text-[14px] text-gray-900 placeholder:text-gray-500') }), renderMentionPicker && mention.isOpen
119
+ ? renderMentionPicker({
120
+ query: mention.query,
121
+ onPick: mention.insertMention,
122
+ onCancel: mention.close,
123
+ })
124
+ : null] })) : (_jsx("p", { className: "mt-3", children: _jsx(MentionedContent, { text: content, mentions: mentions }) }))] }), mediaUrls && mediaUrls.length > 0 && (_jsx("div", { className: "mt-3", children: mediaUrls.length === 1 ? (_jsx("div", { className: "aspect-video bg-gray-100", children: _jsx("img", { src: mediaUrls[0], alt: "Post media", loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }) })) : (_jsx(LinkedInMultiImageGrid, { mediaUrls: mediaUrls })) })), _jsxs("div", { className: "flex items-center justify-between border-gray-200 border-b px-4 py-2 text-[12px] text-gray-500", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("div", { className: "flex -space-x-1", children: [_jsx("span", { role: "img", "aria-label": "Like", className: "inline-flex h-4 w-4 items-center justify-center rounded-full bg-blue-600 text-[10px] text-white", children: _jsx(ThumbsUp, { className: "h-2.5 w-2.5", fill: "currentColor" }) }), _jsx("span", { role: "img", "aria-label": "Love", className: "inline-flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-[10px] text-white", children: "\u2665" })] }), _jsx("span", { className: "ml-1", children: "0" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { children: l.comments(0) }), _jsx("span", { children: "\u00B7" }), _jsx("span", { children: l.reposts(0) })] })] }), _jsxs("div", { className: "flex items-center justify-around px-2 py-1", children: [_jsxs("button", { type: "button", className: "flex min-h-11 flex-1 items-center justify-center gap-2 rounded-lg py-3 text-gray-600 hover:bg-gray-100", children: [_jsx(ThumbsUp, { className: "h-5 w-5" }), _jsx("span", { className: "font-medium text-[14px]", children: l.like })] }), _jsxs("button", { type: "button", className: "flex min-h-11 flex-1 items-center justify-center gap-2 rounded-lg py-3 text-gray-600 hover:bg-gray-100", children: [_jsx(MessageSquare, { className: "h-5 w-5" }), _jsx("span", { className: "font-medium text-[14px]", children: l.comment })] }), _jsxs("button", { type: "button", className: "flex min-h-11 flex-1 items-center justify-center gap-2 rounded-lg py-3 text-gray-600 hover:bg-gray-100", children: [_jsx(Repeat2, { className: "h-5 w-5" }), _jsx("span", { className: "font-medium text-[14px]", children: l.repost })] }), _jsxs("button", { type: "button", className: "flex min-h-11 flex-1 items-center justify-center gap-2 rounded-lg py-3 text-gray-600 hover:bg-gray-100", children: [_jsx(Send, { className: "h-5 w-5" }), _jsx("span", { className: "font-medium text-[14px]", children: l.send })] })] })] }));
125
+ }
126
+ function LinkedInMultiImageGrid({ mediaUrls }) {
127
+ if (mediaUrls.length === 3) {
128
+ return (_jsxs("div", { className: "grid gap-0.5", children: [_jsx("div", { className: "aspect-video bg-gray-100", children: _jsx("img", { src: mediaUrls[0], alt: "Media 1", loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }) }), _jsx("div", { className: "grid grid-cols-2 gap-0.5", children: mediaUrls.slice(1, 3).map((url, i) => (_jsx("div", { className: "relative aspect-square bg-gray-100", children: _jsx("img", { src: url, alt: `Media ${i + 2}`, loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }) }, url))) })] }));
129
+ }
130
+ return (_jsx("div", { className: "grid grid-cols-2 gap-0.5", children: mediaUrls.slice(0, 4).map((url, i) => (_jsxs("div", { className: "relative aspect-square bg-gray-100", children: [_jsx("img", { src: url, alt: `Media ${i + 1}`, loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }), i === 3 && mediaUrls.length > 4 && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/60", children: _jsxs("span", { className: "font-semibold text-2xl text-white", children: ["+", mediaUrls.length - 4] }) }))] }, url))) }));
131
+ }
@@ -0,0 +1,46 @@
1
+ import type { PostPreviewBaseProps } from '../types.js';
2
+ /**
3
+ * Telegram poll attached to a channel post.
4
+ *
5
+ * Field names mirror Telegram's bot API wire shape so callers can pass
6
+ * the same value to `sendPoll`.
7
+ */
8
+ export interface TelegramPollState {
9
+ question: string;
10
+ options: string[];
11
+ multiple?: boolean;
12
+ quiz?: boolean;
13
+ /** Required when `quiz` is true; index of the correct option. */
14
+ correctOptionId?: number;
15
+ }
16
+ export interface TelegramInlineKeyboardButton {
17
+ text: string;
18
+ url?: string;
19
+ callbackData?: string;
20
+ }
21
+ /**
22
+ * Inline keyboard markup. Field name `inline_keyboard` matches the
23
+ * Telegram bot API so consumers can serialise this object directly.
24
+ */
25
+ export interface TelegramInlineKeyboardMarkup {
26
+ inline_keyboard: TelegramInlineKeyboardButton[][];
27
+ }
28
+ export interface TelegramLabels {
29
+ placeholder: string;
30
+ pinnedMessage: string;
31
+ share: string;
32
+ buttonFallback: string;
33
+ pollOptionPlaceholder: (number: number) => string;
34
+ }
35
+ export declare const DEFAULT_TELEGRAM_LABELS: TelegramLabels;
36
+ export interface TelegramPreviewProps extends PostPreviewBaseProps {
37
+ labels?: Partial<TelegramLabels>;
38
+ poll?: TelegramPollState;
39
+ pinMessage?: boolean;
40
+ replyMarkup?: TelegramInlineKeyboardMarkup;
41
+ }
42
+ /**
43
+ * Telegram channel post preview. Renders a chat bubble with optional
44
+ * poll, pinned indicator, and inline keyboard.
45
+ */
46
+ export declare function TelegramPreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, poll, pinMessage, replyMarkup, }: TelegramPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Eye, Pin, Share2 } 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_TELEGRAM_LABELS = {
8
+ placeholder: 'Type a message...',
9
+ pinnedMessage: 'Pinned message',
10
+ share: 'Share',
11
+ buttonFallback: 'Button',
12
+ pollOptionPlaceholder: (number) => `Option ${number}`,
13
+ };
14
+ /**
15
+ * Telegram channel post preview. Renders a chat bubble with optional
16
+ * poll, pinned indicator, and inline keyboard.
17
+ */
18
+ export function TelegramPreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, poll, pinMessage, replyMarkup, }) {
19
+ const l = { ...DEFAULT_TELEGRAM_LABELS, ...labels };
20
+ return (_jsx("div", { "data-slot": "post-preview", "data-platform": "telegram", className: cn('@container min-h-[200px] bg-[#E7EBF0] p-4 text-gray-900 @[420px]:p-5', className), children: _jsxs("div", { className: "max-w-md", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-8 w-8" }), _jsx("span", { className: "font-medium text-[14px] text-[#168ACD]", children: displayName })] }), _jsxs("div", { className: "overflow-hidden rounded-xl rounded-tl-sm bg-white shadow-sm", children: [pinMessage ? (_jsxs("div", { "data-slot": "telegram-pinned", className: "flex items-center gap-1 border-gray-200 border-b px-3 py-2 text-[11px] text-gray-500", children: [_jsx(Pin, { className: "h-3 w-3", "aria-hidden": "true" }), _jsx("span", { children: l.pinnedMessage })] })) : null, mediaUrls && mediaUrls.length > 0 && (_jsx("div", { className: cn(mediaUrls.length === 1 && 'aspect-video', mediaUrls.length >= 2 && 'grid grid-cols-2 gap-0.5'), children: mediaUrls.slice(0, 4).map((url, i) => (_jsx("div", { className: cn('bg-gray-100', mediaUrls.length === 1 && 'aspect-video', mediaUrls.length >= 2 && 'aspect-square'), children: _jsx("img", { src: url, alt: `Media ${i + 1}`, loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }) }, url))) })), _jsxs("div", { className: "p-3", children: [renderBody({ poll, editable, content, onContentChange, labels: l }), replyMarkup?.inline_keyboard?.length ? (_jsx("div", { className: "mt-3 space-y-1", children: replyMarkup.inline_keyboard.map((row) => {
21
+ const rowKey = row
22
+ .map((b) => `${b.text}|${b.url ?? b.callbackData ?? ''}`)
23
+ .join('::');
24
+ return (_jsx("div", { className: "flex gap-1", children: row.map((button) => (_jsx("a", { href: button.url ?? '#', target: "_blank", rel: "noreferrer", className: "flex-1 rounded bg-[#168ACD] px-3 py-1.5 text-center text-white text-xs", children: button.text || l.buttonFallback }, `${button.text}-${button.url ?? button.callbackData ?? ''}`))) }, rowKey));
25
+ }) })) : null, _jsxs("div", { className: "mt-2 flex items-center justify-end gap-1 text-[12px] text-gray-500", children: [_jsx(Eye, { className: "h-3 w-3", "aria-hidden": "true" }), _jsx("span", { children: "0" }), _jsx("span", { className: "ml-2", children: "12:00" })] })] })] }), _jsxs("div", { className: "mt-2 flex items-center gap-2", children: [_jsxs("button", { type: "button", className: "inline-flex min-h-11 items-center gap-1 rounded-full bg-white px-3 py-1 text-[13px] shadow-sm", children: [_jsx("span", { "aria-hidden": "true", children: "\uD83D\uDC4D" }), _jsx("span", { className: "text-gray-500", children: "0" })] }), _jsxs("button", { type: "button", className: "inline-flex min-h-11 items-center gap-1 rounded-full bg-white px-3 py-1 text-[13px] shadow-sm", children: [_jsx("span", { "aria-hidden": "true", children: "\u2764\uFE0F" }), _jsx("span", { className: "text-gray-500", children: "0" })] }), _jsx("button", { type: "button", "aria-label": l.share, className: "inline-flex min-h-11 min-w-11 items-center justify-center rounded-full bg-white p-1.5 shadow-sm", children: _jsx(Share2, { className: "h-4 w-4 text-gray-500" }) })] })] }) }));
26
+ }
27
+ function renderBody({ poll, editable, content, onContentChange, labels, }) {
28
+ if (poll) {
29
+ return (_jsxs("div", { "data-slot": "telegram-poll", className: "space-y-2", children: [_jsx("p", { className: "font-medium", children: poll.question }), poll.options.map((option, index) => {
30
+ // Options can repeat / be empty during composing; combine with stable index in a non-positional way
31
+ // by counting prior empties so the key remains unique without leaking positional intent.
32
+ const key = option
33
+ ? `opt-${option}`
34
+ : `placeholder-${labels.pollOptionPlaceholder(index + 1)}`;
35
+ return (_jsx("div", { className: "rounded bg-gray-50 px-3 py-2 text-sm", children: option || labels.pollOptionPlaceholder(index + 1) }, key));
36
+ })] }));
37
+ }
38
+ if (editable) {
39
+ return (_jsx(Textarea, { value: content, onChange: (e) => onContentChange?.(e.target.value), placeholder: labels.placeholder, className: "min-h-[60px] text-[15px] placeholder:text-gray-500" }));
40
+ }
41
+ return _jsx("p", { className: "whitespace-pre-wrap break-words text-[15px]", children: content });
42
+ }
@@ -0,0 +1,19 @@
1
+ import type { PostPreviewBaseProps } from '../types.js';
2
+ export interface ThreadsLabels {
3
+ placeholder: string;
4
+ replies: (count: number) => string;
5
+ like: string;
6
+ comment: string;
7
+ repost: string;
8
+ share: string;
9
+ moreOptions: string;
10
+ }
11
+ export declare const DEFAULT_THREADS_LABELS: ThreadsLabels;
12
+ export interface ThreadsPreviewProps extends PostPreviewBaseProps {
13
+ labels?: Partial<ThreadsLabels>;
14
+ }
15
+ /**
16
+ * Threads feed-card preview. Honors a parent `.dark` class for the dark
17
+ * variant the platform itself uses.
18
+ */
19
+ export declare function ThreadsPreview({ displayName, username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, }: ThreadsPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Heart, MessageCircle, MoreHorizontal, Repeat2, Send } 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_THREADS_LABELS = {
8
+ placeholder: 'Start a thread...',
9
+ replies: (count) => `${count} replies`,
10
+ like: 'Like',
11
+ comment: 'Comment',
12
+ repost: 'Repost',
13
+ share: 'Share',
14
+ moreOptions: 'More options',
15
+ };
16
+ /**
17
+ * Threads feed-card preview. Honors a parent `.dark` class for the dark
18
+ * variant the platform itself uses.
19
+ */
20
+ export function ThreadsPreview({ displayName, username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, }) {
21
+ const l = { ...DEFAULT_THREADS_LABELS, ...labels };
22
+ const formattedUsername = username.startsWith('@') ? username.slice(1) : username;
23
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "threads", className: cn('@container bg-white p-4 text-gray-900 @[420px]:text-[15px] dark:bg-[#101010] dark:text-white', className), children: [_jsxs("div", { className: "flex gap-3", children: [_jsxs("div", { className: "flex flex-col items-center", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-9 w-9" }), _jsx("div", { className: "mt-2 min-h-[20px] w-0.5 flex-1 bg-gray-200 dark:bg-gray-700" })] }), _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", children: [_jsx("span", { className: "font-semibold text-[15px]", children: formattedUsername }), _jsx("span", { className: "text-[15px] text-gray-500", children: "\u00B7 now" })] }), _jsx("button", { type: "button", "aria-label": l.moreOptions, className: "-m-1.5 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-1.5 text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800", children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) })] }), editable ? (_jsx(Textarea, { value: content, onChange: (e) => onContentChange?.(e.target.value), placeholder: l.placeholder, className: "mt-1 min-h-[60px] text-[15px] placeholder:text-gray-500" })) : (_jsx("p", { className: "mt-1 whitespace-pre-wrap break-words text-[15px]", children: content })), mediaUrls && mediaUrls.length > 0 && (_jsx("div", { className: cn('mt-3 overflow-hidden rounded-xl', mediaUrls.length === 1 && 'aspect-video', mediaUrls.length === 2 && 'grid grid-cols-2 gap-1', mediaUrls.length >= 3 && 'grid grid-cols-2 gap-1'), children: mediaUrls.slice(0, 4).map((url, i) => (_jsx("div", { className: cn('bg-gray-100 dark:bg-gray-800', mediaUrls.length === 1 && 'aspect-video', mediaUrls.length >= 2 && 'aspect-square'), children: _jsx("img", { src: url, alt: `Media ${i + 1}`, loading: "lazy", decoding: "async", className: "h-full w-full rounded-lg object-cover" }) }, url))) })), _jsxs("div", { className: "mt-3 flex items-center gap-4 text-gray-500", children: [_jsx("button", { type: "button", "aria-label": l.like, className: "-m-2 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-2 hover:bg-gray-100 dark:hover:bg-gray-800", children: _jsx(Heart, { className: "h-5 w-5" }) }), _jsx("button", { type: "button", "aria-label": l.comment, className: "-m-2 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-2 hover:bg-gray-100 dark:hover:bg-gray-800", children: _jsx(MessageCircle, { className: "h-5 w-5" }) }), _jsx("button", { type: "button", "aria-label": l.repost, className: "-m-2 inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-2 hover:bg-gray-100 dark:hover:bg-gray-800", children: _jsx(Repeat2, { className: "h-5 w-5" }) }), _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-gray-100 dark:hover:bg-gray-800", children: _jsx(Send, { className: "h-5 w-5" }) })] })] })] }), _jsxs("div", { className: "mt-3 ml-12 flex items-center gap-3", children: [_jsxs("div", { className: "flex -space-x-1.5", children: [_jsx("div", { className: "h-5 w-5 rounded-full border-2 border-white bg-gray-300 dark:border-[#101010] dark:bg-gray-600" }), _jsx("div", { className: "h-5 w-5 rounded-full border-2 border-white bg-gray-400 dark:border-[#101010] dark:bg-gray-500" })] }), _jsx("span", { className: "text-[15px] text-gray-500", children: l.replies(0) })] })] }));
24
+ }
@@ -0,0 +1,18 @@
1
+ import type { PostPreviewBaseProps } from '../types.js';
2
+ export interface TikTokLabels {
3
+ placeholder: string;
4
+ share: string;
5
+ originalSound: (name: string) => string;
6
+ like: string;
7
+ comment: string;
8
+ bookmark: string;
9
+ }
10
+ export declare const DEFAULT_TIKTOK_LABELS: TikTokLabels;
11
+ export interface TikTokPreviewProps extends PostPreviewBaseProps {
12
+ labels?: Partial<TikTokLabels>;
13
+ }
14
+ /**
15
+ * TikTok video-overlay preview. Always renders against a dark background to
16
+ * match the platform's video-first UI.
17
+ */
18
+ export declare function TikTokPreview({ displayName, username, avatarUrl, content, editable, onContentChange, className, labels, }: TikTokPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Bookmark, Heart, MessageCircle, Share2 } 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_TIKTOK_LABELS = {
8
+ placeholder: 'Add a caption...',
9
+ share: 'Share',
10
+ originalSound: (name) => `Original sound - ${name}`,
11
+ like: 'Like',
12
+ comment: 'Comment',
13
+ bookmark: 'Bookmark',
14
+ };
15
+ /**
16
+ * TikTok video-overlay preview. Always renders against a dark background to
17
+ * match the platform's video-first UI.
18
+ */
19
+ export function TikTokPreview({ displayName, username, avatarUrl, content, editable = false, onContentChange, className, labels, }) {
20
+ const l = { ...DEFAULT_TIKTOK_LABELS, ...labels };
21
+ const formattedUsername = username.startsWith('@') ? username : `@${username}`;
22
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "tiktok", className: cn('@container relative min-h-[300px] bg-black text-white @[420px]:min-h-[420px]', className), children: [_jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent" }), _jsxs("div", { className: "absolute right-3 bottom-20 flex flex-col items-center gap-4", children: [_jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-11 w-11 ring-2 ring-white" }), _jsx("div", { className: "-mt-2.5 flex h-5 w-5 items-center justify-center rounded-full bg-[#FE2C55]", children: _jsx("span", { className: "font-bold text-white text-xs", "aria-hidden": "true", children: "+" }) })] }), _jsxs("button", { type: "button", "aria-label": l.like, className: "inline-flex min-h-11 min-w-11 flex-col items-center gap-1", children: [_jsx("div", { className: "rounded-full bg-gray-800/50 p-2", children: _jsx(Heart, { className: "h-7 w-7", fill: "white" }) }), _jsx("span", { className: "font-semibold text-xs", children: "0" })] }), _jsxs("button", { type: "button", "aria-label": l.comment, className: "inline-flex min-h-11 min-w-11 flex-col items-center gap-1", children: [_jsx("div", { className: "rounded-full bg-gray-800/50 p-2", children: _jsx(MessageCircle, { className: "h-7 w-7", fill: "white" }) }), _jsx("span", { className: "font-semibold text-xs", children: "0" })] }), _jsxs("button", { type: "button", "aria-label": l.bookmark, className: "inline-flex min-h-11 min-w-11 flex-col items-center gap-1", children: [_jsx("div", { className: "rounded-full bg-gray-800/50 p-2", children: _jsx(Bookmark, { className: "h-7 w-7" }) }), _jsx("span", { className: "font-semibold text-xs", children: "0" })] }), _jsxs("button", { type: "button", "aria-label": l.share, className: "inline-flex min-h-11 min-w-11 flex-col items-center gap-1", children: [_jsx("div", { className: "rounded-full bg-gray-800/50 p-2", children: _jsx(Share2, { className: "h-7 w-7" }) }), _jsx("span", { className: "font-semibold text-xs", children: l.share })] }), _jsx("div", { className: "mt-2 flex h-11 w-11 animate-spin items-center justify-center rounded-full border-4 border-gray-700 bg-gray-800", children: _jsx("div", { className: "h-4 w-4 rounded-full bg-gray-600" }) })] }), _jsxs("div", { className: "absolute right-16 bottom-0 left-0 p-4", children: [_jsx("p", { className: "mb-1 font-semibold text-[16px]", children: formattedUsername }), editable ? (_jsx(Textarea, { value: content, onChange: (e) => onContentChange?.(e.target.value), placeholder: l.placeholder, className: "min-h-[40px] text-[14px] text-white placeholder:text-gray-400" })) : (_jsx("p", { className: "whitespace-pre-wrap break-words text-[14px]", children: content })), _jsxs("div", { className: "mt-3 flex items-center gap-2", children: [_jsx("svg", { className: "h-3 w-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z" }) }), _jsx("span", { className: "text-[13px]", children: l.originalSound(displayName) })] })] })] }));
23
+ }
@@ -0,0 +1,40 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { PostPreviewBaseProps } from '../types.js';
3
+ /**
4
+ * Visual theme variants for Twitter / X.
5
+ * - `light`: white background, dark text.
6
+ * - `dim`: navy-blue dark variant.
7
+ * - `lights-out`: pure black variant.
8
+ */
9
+ export type TwitterTheme = 'light' | 'dim' | 'lights-out';
10
+ export interface TwitterMentionPickerContext {
11
+ query: string;
12
+ onPick: (mention: string) => void;
13
+ onCancel: () => void;
14
+ }
15
+ export interface TwitterLabels {
16
+ placeholder: string;
17
+ now: string;
18
+ moreOptions: string;
19
+ reply: string;
20
+ retweet: string;
21
+ like: string;
22
+ save: string;
23
+ share: string;
24
+ viewAnalytics: string;
25
+ }
26
+ export declare const DEFAULT_TWITTER_LABELS: TwitterLabels;
27
+ export interface TwitterPreviewProps extends PostPreviewBaseProps {
28
+ labels?: Partial<TwitterLabels>;
29
+ /** Visual theme. Defaults to `lights-out` to match X's default. */
30
+ theme?: TwitterTheme;
31
+ /** Optional render slot for an `@mention` picker. Called with the
32
+ * current query (text after `@`) when the user types `@xxx` in the
33
+ * editable textarea. */
34
+ renderMentionPicker?: (ctx: TwitterMentionPickerContext) => ReactNode;
35
+ }
36
+ /**
37
+ * Twitter / X feed-card preview. Supports light, dim, and lights-out
38
+ * themes plus an optional `@mention` picker render slot.
39
+ */
40
+ export declare function TwitterPreview({ displayName, username, avatarUrl, content, mediaUrls, characterLimit, editable, onContentChange, className, labels, theme, renderMentionPicker, }: TwitterPreviewProps): import("react/jsx-runtime").JSX.Element;