@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,12 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Monitor, Smartphone } from 'lucide-react';
4
+ import { cn } from '../lib/utils.js';
5
+ /** Toggle between desktop and mobile preview modes. */
6
+ export function ViewControls({ device, onDeviceChange, desktopLabel, mobileLabel, }) {
7
+ return (_jsxs("div", { role: "radiogroup", "aria-label": "Preview width", "data-slot": "email-builder-view-controls", className: "inline-flex rounded-md border border-input p-0.5", children: [_jsx("button", { type: "button", role: "radio", "aria-checked": device === 'desktop', "aria-label": desktopLabel, onClick: () => onDeviceChange('desktop'), className: cn('inline-flex h-9 w-10 items-center justify-center rounded', device === 'desktop'
8
+ ? 'bg-primary text-primary-foreground'
9
+ : 'text-muted-foreground hover:bg-accent'), children: _jsx(Monitor, { className: "size-4", "aria-hidden": true }) }), _jsx("button", { type: "button", role: "radio", "aria-checked": device === 'mobile', "aria-label": mobileLabel, onClick: () => onDeviceChange('mobile'), className: cn('inline-flex h-9 w-10 items-center justify-center rounded', device === 'mobile'
10
+ ? 'bg-primary text-primary-foreground'
11
+ : 'text-muted-foreground hover:bg-accent'), children: _jsx(Smartphone, { className: "size-4", "aria-hidden": true }) })] }));
12
+ }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  export * from './auth/public.js';
2
2
  export * from './brand/public.js';
3
+ export * from './calendar/public.js';
3
4
  export * from './chat/public.js';
5
+ export * from './email-builder/public.js';
4
6
  export * from './marketing/public.js';
5
7
  export * from './panel/public.js';
8
+ export * from './post-preview/public.js';
6
9
  export * from './primitives/index.js';
7
10
  export * from './shell/index.js';
8
11
  export * from './theme/index.js';
9
12
  export * from './timeline/public.js';
13
+ export * from './workflow-builder/public.js';
package/dist/index.js CHANGED
@@ -1,9 +1,13 @@
1
1
  export * from './auth/public.js';
2
2
  export * from './brand/public.js';
3
+ export * from './calendar/public.js';
3
4
  export * from './chat/public.js';
5
+ export * from './email-builder/public.js';
4
6
  export * from './marketing/public.js';
5
7
  export * from './panel/public.js';
8
+ export * from './post-preview/public.js';
6
9
  export * from './primitives/index.js';
7
10
  export * from './shell/index.js'; // v2 surface
8
11
  export * from './theme/index.js';
9
12
  export * from './timeline/public.js';
13
+ export * from './workflow-builder/public.js';
@@ -10,9 +10,8 @@ export function formatClock(date, opts = {}) {
10
10
  return fmt.format(date);
11
11
  }
12
12
  export function formatDuration(ms) {
13
- if (ms < 0)
14
- ms = 0;
15
- const totalSec = Math.floor(ms / 1000);
13
+ const clampedMs = ms < 0 ? 0 : ms;
14
+ const totalSec = Math.floor(clampedMs / 1000);
16
15
  const h = Math.floor(totalSec / 3600);
17
16
  const m = Math.floor((totalSec % 3600) / 60);
18
17
  const s = totalSec % 60;
@@ -0,0 +1,4 @@
1
+ export type { BlueSkyLabels, BlueSkyPreviewProps, DiscordEmbedDraft, DiscordLabels, DiscordPreviewProps, FacebookLabels, FacebookPreviewProps, GenericLabels, GenericPreviewProps, GoogleBusinessLabels, GoogleBusinessPreviewProps, InstagramLabels, InstagramPostType, InstagramPreviewProps, LinkedInLabels, LinkedInMentionData, LinkedInMentionPickerContext, LinkedInPreviewProps, TelegramInlineKeyboardButton, TelegramInlineKeyboardMarkup, TelegramLabels, TelegramPollState, TelegramPreviewProps, ThreadsLabels, ThreadsPreviewProps, TikTokLabels, TikTokPreviewProps, TwitterLabels, TwitterMentionPickerContext, TwitterPreviewProps, TwitterTheme, YouTubeLabels, YouTubePreviewProps, } from './platforms/index.js';
2
+ export { DEFAULT_BLUESKY_LABELS, DEFAULT_DISCORD_LABELS, DEFAULT_FACEBOOK_LABELS, DEFAULT_GENERIC_LABELS, DEFAULT_GOOGLE_BUSINESS_LABELS, DEFAULT_INSTAGRAM_LABELS, DEFAULT_LINKEDIN_LABELS, DEFAULT_TELEGRAM_LABELS, DEFAULT_THREADS_LABELS, DEFAULT_TIKTOK_LABELS, DEFAULT_TWITTER_LABELS, DEFAULT_YOUTUBE_LABELS, } from './platforms/index.js';
3
+ export { PostPreview, type PostPreviewProps } from './post-preview.js';
4
+ export type { PlatformId, PostPreviewBaseProps, PostPreviewFrame, PostPreviewSlots, } from './types.js';
@@ -0,0 +1,7 @@
1
+ // open/meda/src/post-preview/index.ts
2
+ //
3
+ // Subpath barrel for `@medalsocial/meda/post-preview`. Single top-level
4
+ // `PostPreview` component dispatches per-platform via the `platform` prop.
5
+ // Per-platform components, chromes, and helpers remain internal.
6
+ export { DEFAULT_BLUESKY_LABELS, DEFAULT_DISCORD_LABELS, DEFAULT_FACEBOOK_LABELS, DEFAULT_GENERIC_LABELS, DEFAULT_GOOGLE_BUSINESS_LABELS, DEFAULT_INSTAGRAM_LABELS, DEFAULT_LINKEDIN_LABELS, DEFAULT_TELEGRAM_LABELS, DEFAULT_THREADS_LABELS, DEFAULT_TIKTOK_LABELS, DEFAULT_TWITTER_LABELS, DEFAULT_YOUTUBE_LABELS, } from './platforms/index.js';
7
+ export { PostPreview } from './post-preview.js';
@@ -0,0 +1,12 @@
1
+ export interface AvatarProps {
2
+ src?: string;
3
+ displayName: string;
4
+ className?: string;
5
+ }
6
+ /**
7
+ * Internal avatar primitive used by post-preview platforms.
8
+ * Plain `<img>` with an initials fallback; deliberately not exported
9
+ * from the public surface so we don't lock consumers in. Consumers
10
+ * who want a richer Avatar primitive should pull one from elsewhere.
11
+ */
12
+ export declare function Avatar({ src, displayName, className }: AvatarProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { cn } from '../../lib/utils.js';
5
+ function initialOf(name) {
6
+ const trimmed = name.trim();
7
+ if (!trimmed)
8
+ return '?';
9
+ return trimmed.charAt(0).toUpperCase();
10
+ }
11
+ /**
12
+ * Internal avatar primitive used by post-preview platforms.
13
+ * Plain `<img>` with an initials fallback; deliberately not exported
14
+ * from the public surface so we don't lock consumers in. Consumers
15
+ * who want a richer Avatar primitive should pull one from elsewhere.
16
+ */
17
+ export function Avatar({ src, displayName, className }) {
18
+ const [errored, setErrored] = useState(false);
19
+ const showFallback = !src || errored;
20
+ return (_jsx("span", { "data-slot": "post-preview-avatar", className: cn('inline-flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-sm font-medium text-muted-foreground', className), children: showFallback ? (_jsx("span", { role: "img", "aria-label": displayName, children: initialOf(displayName) })) : (_jsx("img", { src: src, alt: displayName, loading: "lazy", decoding: "async", onError: () => setErrored(true), className: "h-full w-full object-cover" })) }));
21
+ }
@@ -0,0 +1,12 @@
1
+ export type ContentPartType = 'text' | 'mention' | 'hashtag' | 'url';
2
+ export interface ContentPart {
3
+ type: ContentPartType;
4
+ value: string;
5
+ }
6
+ /**
7
+ * Split a content string into typed parts so platform components can
8
+ * apply per-platform styling for mentions, hashtags, and URLs.
9
+ *
10
+ * Empty strings return an empty array (callers can render nothing).
11
+ */
12
+ export declare function splitContent(content: string): ContentPart[];
@@ -0,0 +1,31 @@
1
+ const PATTERN = /(@[A-Za-z0-9_]+|#[A-Za-z0-9_]+|https?:\/\/\S+)/g;
2
+ /**
3
+ * Split a content string into typed parts so platform components can
4
+ * apply per-platform styling for mentions, hashtags, and URLs.
5
+ *
6
+ * Empty strings return an empty array (callers can render nothing).
7
+ */
8
+ export function splitContent(content) {
9
+ if (!content)
10
+ return [];
11
+ const parts = [];
12
+ let lastIndex = 0;
13
+ for (const match of content.matchAll(PATTERN)) {
14
+ const matchIndex = match.index ?? 0;
15
+ if (matchIndex > lastIndex) {
16
+ parts.push({ type: 'text', value: content.slice(lastIndex, matchIndex) });
17
+ }
18
+ const value = match[0];
19
+ if (value.startsWith('@'))
20
+ parts.push({ type: 'mention', value });
21
+ else if (value.startsWith('#'))
22
+ parts.push({ type: 'hashtag', value });
23
+ else
24
+ parts.push({ type: 'url', value });
25
+ lastIndex = matchIndex + value.length;
26
+ }
27
+ if (lastIndex < content.length) {
28
+ parts.push({ type: 'text', value: content.slice(lastIndex) });
29
+ }
30
+ return parts;
31
+ }
@@ -0,0 +1,10 @@
1
+ import type { PlatformId } from '../types.js';
2
+ export interface PlatformMeta {
3
+ /** Human-readable name (e.g. "Twitter / X"). */
4
+ displayName: string;
5
+ /** Platform brand color (hex with leading #). */
6
+ brandColor: string;
7
+ /** Soft per-platform character limit (consumers may override per render). */
8
+ characterLimit: number;
9
+ }
10
+ export declare const PLATFORM_META: Record<PlatformId, PlatformMeta>;
@@ -0,0 +1,18 @@
1
+ export const PLATFORM_META = {
2
+ twitter: { displayName: 'X', brandColor: '#000000', characterLimit: 280 },
3
+ linkedin: { displayName: 'LinkedIn', brandColor: '#0A66C2', characterLimit: 3000 },
4
+ instagram: { displayName: 'Instagram', brandColor: '#E4405F', characterLimit: 2200 },
5
+ facebook: { displayName: 'Facebook', brandColor: '#1877F2', characterLimit: 63_206 },
6
+ threads: { displayName: 'Threads', brandColor: '#000000', characterLimit: 500 },
7
+ bluesky: { displayName: 'Bluesky', brandColor: '#0085FF', characterLimit: 300 },
8
+ tiktok: { displayName: 'TikTok', brandColor: '#000000', characterLimit: 4000 },
9
+ youtube: { displayName: 'YouTube', brandColor: '#FF0000', characterLimit: 5000 },
10
+ google_business: {
11
+ displayName: 'Google Business',
12
+ brandColor: '#4285F4',
13
+ characterLimit: 1500,
14
+ },
15
+ telegram: { displayName: 'Telegram', brandColor: '#26A5E4', characterLimit: 4096 },
16
+ discord: { displayName: 'Discord', brandColor: '#5865F2', characterLimit: 2000 },
17
+ generic: { displayName: 'Generic', brandColor: '#6B7280', characterLimit: 63_206 },
18
+ };
@@ -0,0 +1,8 @@
1
+ import { type ComponentProps } from 'react';
2
+ export type TextareaProps = ComponentProps<'textarea'>;
3
+ /**
4
+ * Internal textarea used by post-preview platforms in editable mode.
5
+ * Minimal styling — platform-specific previews layer on their own
6
+ * background, font, and color via className. Not exported publicly.
7
+ */
8
+ export declare const Textarea: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, "ref"> & import("react").RefAttributes<HTMLTextAreaElement>>;
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { forwardRef } from 'react';
4
+ import { cn } from '../../lib/utils.js';
5
+ /**
6
+ * Internal textarea used by post-preview platforms in editable mode.
7
+ * Minimal styling — platform-specific previews layer on their own
8
+ * background, font, and color via className. Not exported publicly.
9
+ */
10
+ export const Textarea = forwardRef(function Textarea({ className, ...props }, ref) {
11
+ return (_jsx("textarea", { ref: ref, "data-slot": "post-preview-textarea", className: cn('block w-full resize-none border-0 bg-transparent p-0 outline-none', 'placeholder:text-muted-foreground/60', 'focus-visible:ring-0 focus-visible:ring-offset-0', className), ...props }));
12
+ });
@@ -0,0 +1,17 @@
1
+ import type { TouchEvent } from 'react';
2
+ export interface UseSwipeOptions {
3
+ onSwipeLeft: () => void;
4
+ onSwipeRight: () => void;
5
+ }
6
+ /**
7
+ * Touch swipe detection for carousel navigation. Tracks touchstart/touchend
8
+ * and fires onSwipeLeft / onSwipeRight when the horizontal delta exceeds
9
+ * SWIPE_THRESHOLD (50px). Returns event handlers to spread onto the
10
+ * swipeable container.
11
+ *
12
+ * Ported from `apps/web/src/components/composer/editors/use-swipe.ts`.
13
+ */
14
+ export declare function useSwipe({ onSwipeLeft, onSwipeRight }: UseSwipeOptions): {
15
+ onTouchStart: (e: TouchEvent) => void;
16
+ onTouchEnd: (e: TouchEvent) => void;
17
+ };
@@ -0,0 +1,29 @@
1
+ import { useCallback, useRef } from 'react';
2
+ const SWIPE_THRESHOLD = 50;
3
+ /**
4
+ * Touch swipe detection for carousel navigation. Tracks touchstart/touchend
5
+ * and fires onSwipeLeft / onSwipeRight when the horizontal delta exceeds
6
+ * SWIPE_THRESHOLD (50px). Returns event handlers to spread onto the
7
+ * swipeable container.
8
+ *
9
+ * Ported from `apps/web/src/components/composer/editors/use-swipe.ts`.
10
+ */
11
+ export function useSwipe({ onSwipeLeft, onSwipeRight }) {
12
+ const touchStartX = useRef(null);
13
+ const onTouchStart = useCallback((e) => {
14
+ touchStartX.current = e.touches[0].clientX;
15
+ }, []);
16
+ const onTouchEnd = useCallback((e) => {
17
+ if (touchStartX.current === null)
18
+ return;
19
+ const deltaX = e.changedTouches[0].clientX - touchStartX.current;
20
+ touchStartX.current = null;
21
+ if (Math.abs(deltaX) < SWIPE_THRESHOLD)
22
+ return;
23
+ if (deltaX < 0)
24
+ onSwipeLeft();
25
+ else
26
+ onSwipeRight();
27
+ }, [onSwipeLeft, onSwipeRight]);
28
+ return { onTouchStart, onTouchEnd };
29
+ }
@@ -0,0 +1,15 @@
1
+ import type { PostPreviewBaseProps } from '../types.js';
2
+ export interface BlueSkyLabels {
3
+ placeholder: string;
4
+ share: string;
5
+ moreOptions: string;
6
+ }
7
+ export declare const DEFAULT_BLUESKY_LABELS: BlueSkyLabels;
8
+ export interface BlueSkyPreviewProps extends PostPreviewBaseProps {
9
+ labels?: Partial<BlueSkyLabels>;
10
+ }
11
+ /**
12
+ * BlueSky feed-card preview. Auto-formats the username with `.bsky.social`
13
+ * if no domain is present.
14
+ */
15
+ export declare function BlueSkyPreview({ displayName, username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, }: BlueSkyPreviewProps): 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 { Heart, MessageCircle, MoreHorizontal, Repeat2, Share } 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_BLUESKY_LABELS = {
8
+ placeholder: "What's up?",
9
+ share: 'Share',
10
+ moreOptions: 'More options',
11
+ };
12
+ /**
13
+ * BlueSky feed-card preview. Auto-formats the username with `.bsky.social`
14
+ * if no domain is present.
15
+ */
16
+ export function BlueSkyPreview({ displayName, username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, }) {
17
+ const l = { ...DEFAULT_BLUESKY_LABELS, ...labels };
18
+ const handle = username.startsWith('@') ? username.slice(1) : username;
19
+ const formattedHandle = handle.includes('.') ? handle : `${handle}.bsky.social`;
20
+ return (_jsx("div", { "data-slot": "post-preview", "data-platform": "bluesky", className: cn('@container bg-white p-4 text-gray-900 @[420px]:text-[15px]', className), children: _jsxs("div", { className: "flex gap-3", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-11 w-11 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-baseline gap-1", children: [_jsx("span", { className: "cursor-pointer font-bold text-[15px] text-gray-900 hover:underline", children: displayName }), _jsxs("span", { className: "text-[14px] text-gray-500", children: ["@", formattedHandle] }), _jsx("span", { className: "text-[14px] text-gray-400", 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-400 hover:bg-gray-100", 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] text-gray-900 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 border border-gray-200', mediaUrls.length === 1 && 'aspect-video', mediaUrls.length === 2 && 'grid grid-cols-2 gap-0.5', mediaUrls.length >= 3 && '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', mediaUrls.length >= 3 && '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: "mt-3 flex items-center gap-1 text-gray-500", children: [_jsxs("button", { type: "button", "aria-label": "Comments", className: "-ml-2 inline-flex min-h-11 min-w-11 items-center gap-1.5 rounded-full p-2 hover:bg-blue-50 hover:text-[#1185FE]", children: [_jsx(MessageCircle, { className: "h-[18px] w-[18px]" }), _jsx("span", { className: "text-[13px]", children: "0" })] }), _jsxs("button", { type: "button", "aria-label": "Repost", className: "inline-flex min-h-11 min-w-11 items-center gap-1.5 rounded-full p-2 hover:bg-green-50 hover:text-green-600", children: [_jsx(Repeat2, { className: "h-[18px] w-[18px]" }), _jsx("span", { className: "text-[13px]", children: "0" })] }), _jsxs("button", { type: "button", "aria-label": "Like", className: "inline-flex min-h-11 min-w-11 items-center gap-1.5 rounded-full p-2 hover:bg-red-50 hover:text-red-500", children: [_jsx(Heart, { className: "h-[18px] w-[18px]" }), _jsx("span", { className: "text-[13px]", 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 p-2 hover:bg-blue-50 hover:text-[#1185FE]", children: _jsx(Share, { className: "h-[18px] w-[18px]" }) })] })] })] }) }));
21
+ }
@@ -0,0 +1,44 @@
1
+ import type { PostPreviewBaseProps } from '../types.js';
2
+ /**
3
+ * Discord embed draft. Mirrors the Discord webhook embed shape.
4
+ */
5
+ export interface DiscordEmbedDraft {
6
+ title?: string;
7
+ description?: string;
8
+ /** 0xRRGGBB integer (Discord wire format). */
9
+ color?: number;
10
+ url?: string;
11
+ thumbnailUrl?: string;
12
+ imageUrl?: string;
13
+ authorName?: string;
14
+ authorIconUrl?: string;
15
+ fields?: Array<{
16
+ name: string;
17
+ value: string;
18
+ inline?: boolean;
19
+ }>;
20
+ footerText?: string;
21
+ /** ISO 8601 timestamp. */
22
+ timestamp?: string;
23
+ }
24
+ export interface DiscordLabels {
25
+ placeholder: string;
26
+ timestamp: string;
27
+ embedsSuppressed: string;
28
+ reply: string;
29
+ close: string;
30
+ moreOptions: string;
31
+ addReaction: string;
32
+ }
33
+ export declare const DEFAULT_DISCORD_LABELS: DiscordLabels;
34
+ export interface DiscordPreviewProps extends PostPreviewBaseProps {
35
+ labels?: Partial<DiscordLabels>;
36
+ embeds?: DiscordEmbedDraft[];
37
+ /** Hide rendered embeds (matches Discord's "Suppress Embeds" message option). */
38
+ suppressEmbeds?: boolean;
39
+ }
40
+ /**
41
+ * Discord channel message preview. Renders the user message followed by
42
+ * any embeds (unless `suppressEmbeds` is true).
43
+ */
44
+ export declare function DiscordPreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, embeds, suppressEmbeds, }: DiscordPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { MoreHorizontal, Plus, Smile } 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_DISCORD_LABELS = {
8
+ placeholder: 'Message #channel',
9
+ timestamp: 'Today at 12:00 PM',
10
+ embedsSuppressed: 'Embeds are hidden in this preview.',
11
+ reply: 'React',
12
+ close: 'Close',
13
+ moreOptions: 'More options',
14
+ addReaction: 'Add reaction',
15
+ };
16
+ /**
17
+ * Discord channel message preview. Renders the user message followed by
18
+ * any embeds (unless `suppressEmbeds` is true).
19
+ */
20
+ export function DiscordPreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, embeds, suppressEmbeds, }) {
21
+ const l = { ...DEFAULT_DISCORD_LABELS, ...labels };
22
+ const visibleEmbeds = (embeds ?? []).filter((embed) => [embed.title, embed.description, embed.url, embed.imageUrl].some((field) => typeof field === 'string' && field.trim().length > 0));
23
+ return (_jsx("div", { "data-slot": "post-preview", "data-platform": "discord", className: cn('@container min-h-[150px] bg-white text-[#313338] @[420px]:text-[15px]', className), children: _jsxs("div", { className: "group relative px-4 py-1 hover:bg-[#F2F3F5]", children: [_jsxs("div", { className: "-top-4 absolute right-4 hidden items-center rounded border border-[#DDDEE1] bg-white shadow-lg group-hover:flex", children: [_jsx("button", { type: "button", "aria-label": l.reply, className: "inline-flex min-h-11 min-w-11 items-center justify-center p-1.5 text-[#747681] hover:bg-[#F2F3F5] hover:text-[#313338]", children: _jsx(Smile, { className: "h-5 w-5" }) }), _jsx("button", { type: "button", "aria-label": l.moreOptions, className: "inline-flex min-h-11 min-w-11 items-center justify-center p-1.5 text-[#747681] hover:bg-[#F2F3F5] hover:text-[#313338]", children: _jsx(MoreHorizontal, { className: "h-5 w-5" }) })] }), _jsxs("div", { className: "flex gap-4 py-0.5", children: [_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "mt-0.5 h-10 w-10 flex-shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "cursor-pointer font-medium text-[#060607] text-[15px] hover:underline", children: displayName }), _jsx("span", { className: "text-[#5C5E66] text-[11px]", children: l.timestamp })] }), editable ? (_jsx(Textarea, { value: content, onChange: (e) => onContentChange?.(e.target.value), placeholder: l.placeholder, className: "mt-0.5 min-h-[40px] text-[#313338] text-[15px] placeholder:text-[#747681]" })) : (_jsx("p", { className: "mt-0.5 whitespace-pre-wrap break-words text-[#313338] text-[15px]", children: content })), mediaUrls && mediaUrls.length > 0 && (_jsx("div", { className: "mt-2 max-w-md", children: _jsx("div", { className: cn('overflow-hidden rounded', mediaUrls.length === 1 && 'max-w-[400px]', mediaUrls.length >= 2 && 'grid max-w-[400px] grid-cols-2 gap-1'), children: mediaUrls.slice(0, 4).map((url, i) => (_jsx("div", { className: cn('overflow-hidden rounded bg-[#F2F3F5]', 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 cursor-pointer object-cover hover:opacity-90" }) }, url))) }) })), !suppressEmbeds && visibleEmbeds.length > 0 && (_jsx("div", { "data-slot": "discord-embeds", className: "mt-3 flex max-w-[520px] flex-col gap-2", children: visibleEmbeds.map((embed, index) => (_jsx(DiscordEmbedCard, { embed: embed }, embedKey(embed, index)))) })), suppressEmbeds && (_jsx("p", { "data-slot": "discord-embeds-suppressed", className: "mt-2 text-[#5C5E66] text-xs", children: l.embedsSuppressed })), _jsxs("div", { className: "mt-2 flex items-center gap-1", children: [_jsxs("button", { type: "button", className: "flex min-h-11 items-center gap-1 rounded border border-transparent bg-[#F2F3F5] px-3 py-0.5 text-[14px] hover:border-[#5865F2] hover:bg-[#E8E9EB]", children: [_jsx("span", { "aria-hidden": "true", children: "\uD83D\uDC4D" }), _jsx("span", { className: "text-[#5C5E66] text-[12px]", children: "1" })] }), _jsx("button", { type: "button", "aria-label": l.addReaction, className: "inline-flex min-h-11 min-w-11 items-center justify-center rounded border border-transparent bg-[#F2F3F5] hover:border-[#5865F2] hover:bg-[#E8E9EB]", children: _jsx(Plus, { className: "h-4 w-4 text-[#747681]" }) })] })] })] })] }) }));
24
+ }
25
+ function embedKey(embed, index) {
26
+ return `${embed.title ?? ''}::${embed.url ?? ''}::${embed.imageUrl ?? ''}::${index}`;
27
+ }
28
+ function DiscordEmbedCard({ embed }) {
29
+ const accent = typeof embed.color === 'number' ? `#${embed.color.toString(16).padStart(6, '0')}` : '#5865f2';
30
+ const href = embed.url?.trim();
31
+ return (_jsxs("div", { "data-slot": "discord-embed", className: "overflow-hidden rounded-md border border-[#DDDEE1] border-l-4 bg-[#F2F3F5]", style: { borderLeftColor: accent }, children: [_jsxs("div", { className: "p-3", children: [embed.title ? (_jsx("p", { className: "font-medium text-[#060607] text-sm", children: href ? (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", className: "hover:underline", children: embed.title })) : (embed.title) })) : null, embed.description ? (_jsx("p", { className: "mt-1 whitespace-pre-wrap text-[#313338] text-sm", children: embed.description })) : null, href ? _jsx("p", { className: "mt-2 truncate text-[#0068e0] text-xs", children: href }) : null] }), embed.imageUrl ? (_jsx("img", { src: embed.imageUrl, alt: embed.title ?? 'Discord embed', loading: "lazy", decoding: "async", className: "w-full object-cover" })) : null] }));
32
+ }
@@ -0,0 +1,23 @@
1
+ import type { PostPreviewBaseProps } from '../types.js';
2
+ /**
3
+ * Facebook preview labels. Override individual entries via the `labels` prop.
4
+ */
5
+ export interface FacebookLabels {
6
+ justNow: string;
7
+ placeholder: string;
8
+ comments: (count: number) => string;
9
+ shares: (count: number) => string;
10
+ like: string;
11
+ comment: string;
12
+ share: string;
13
+ moreOptions: string;
14
+ }
15
+ export declare const DEFAULT_FACEBOOK_LABELS: FacebookLabels;
16
+ export interface FacebookPreviewProps extends PostPreviewBaseProps {
17
+ labels?: Partial<FacebookLabels>;
18
+ }
19
+ /**
20
+ * Facebook feed-card preview. Renders displayName, avatar, content, optional
21
+ * media grid, and the standard Like/Comment/Share footer.
22
+ */
23
+ export declare function FacebookPreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, }: FacebookPreviewProps): 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 { Globe, MessageCircle, MoreHorizontal, Share2, 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_FACEBOOK_LABELS = {
8
+ justNow: 'Just now',
9
+ placeholder: "What's on your mind?",
10
+ comments: (count) => `${count} comments`,
11
+ shares: (count) => `${count} shares`,
12
+ like: 'Like',
13
+ comment: 'Comment',
14
+ share: 'Share',
15
+ moreOptions: 'More options',
16
+ };
17
+ /**
18
+ * Facebook feed-card preview. Renders displayName, avatar, content, optional
19
+ * media grid, and the standard Like/Comment/Share footer.
20
+ */
21
+ export function FacebookPreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, }) {
22
+ const l = { ...DEFAULT_FACEBOOK_LABELS, ...labels };
23
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "facebook", className: cn('@container bg-white text-gray-900 @[420px]:text-[15px] dark:bg-[#242526] dark:text-[#E4E6EB]', className), children: [_jsxs("div", { className: "p-4", 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" }), _jsxs("div", { children: [_jsx("p", { className: "cursor-pointer font-semibold text-[15px] text-gray-900 hover:underline", children: displayName }), _jsxs("div", { className: "flex items-center gap-1 text-[13px] text-gray-500", children: [_jsx("span", { children: l.justNow }), _jsx("span", { children: "\u00B7" }), _jsx(Globe, { className: "h-3 w-3", "aria-hidden": "true" })] })] })] }), _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-500 hover:bg-gray-100", children: _jsx(MoreHorizontal, { 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-[15px] text-gray-900 placeholder:text-gray-500" })) : (_jsx("p", { className: "mt-3 whitespace-pre-wrap break-words text-[15px]", children: content }))] }), mediaUrls && mediaUrls.length > 0 && (_jsx("div", { className: cn('bg-gray-100', mediaUrls.length === 1 && 'aspect-video', mediaUrls.length === 2 && 'grid grid-cols-2 gap-0.5', mediaUrls.length === 3 && 'grid grid-cols-2 gap-0.5', mediaUrls.length >= 4 && 'grid grid-cols-2 gap-0.5'), children: mediaUrls.slice(0, 4).map((url, i) => (_jsxs("div", { className: cn('relative bg-gray-200', mediaUrls.length === 1 && 'aspect-video', mediaUrls.length === 2 && 'aspect-square', mediaUrls.length === 3 && i === 0 && 'row-span-2', mediaUrls.length >= 4 && 'aspect-square'), children: [_jsx("img", { src: url, alt: `Media ${i + 1}`, loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }), mediaUrls.length > 4 && i === 3 && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: _jsxs("span", { className: "font-semibold text-2xl text-white", children: ["+", mediaUrls.length - 4] }) }))] }, url))) })), _jsxs("div", { className: "flex items-center justify-between px-4 py-2 text-[13px] text-gray-500", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("div", { className: "flex -space-x-1", children: [_jsx("div", { className: "flex h-[18px] w-[18px] items-center justify-center rounded-full bg-[#1877F2]", children: _jsx(ThumbsUp, { className: "h-2.5 w-2.5 text-white", fill: "currentColor" }) }), _jsx("div", { className: "flex h-[18px] w-[18px] items-center justify-center rounded-full bg-red-500", children: _jsx("span", { className: "text-[10px]", "aria-hidden": "true", children: "\u2665" }) })] }), _jsx("span", { children: "0" })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { children: l.comments(0) }), _jsx("span", { children: l.shares(0) })] })] }), _jsx("div", { className: "mx-4 border-gray-200 border-t" }), _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-2 text-gray-600 hover:bg-gray-100", children: [_jsx(ThumbsUp, { className: "h-5 w-5" }), _jsx("span", { className: "font-semibold text-[15px]", children: l.like })] }), _jsxs("button", { type: "button", className: "flex min-h-11 flex-1 items-center justify-center gap-2 rounded-lg py-2 text-gray-600 hover:bg-gray-100", children: [_jsx(MessageCircle, { className: "h-5 w-5" }), _jsx("span", { className: "font-semibold text-[15px]", children: l.comment })] }), _jsxs("button", { type: "button", className: "flex min-h-11 flex-1 items-center justify-center gap-2 rounded-lg py-2 text-gray-600 hover:bg-gray-100", children: [_jsx(Share2, { className: "h-5 w-5" }), _jsx("span", { className: "font-semibold text-[15px]", children: l.share })] })] })] }));
24
+ }
@@ -0,0 +1,22 @@
1
+ import type { PlatformId, PostPreviewBaseProps } from '../types.js';
2
+ export interface GenericLabels {
3
+ placeholderTemplate: (platform: string) => string;
4
+ badgeTemplate: (platform: string) => string;
5
+ moreOptions: string;
6
+ }
7
+ export declare const DEFAULT_GENERIC_LABELS: GenericLabels;
8
+ export interface GenericPreviewProps extends PostPreviewBaseProps {
9
+ /**
10
+ * The platform this preview represents. Accepts a known `PlatformId` (in
11
+ * which case the per-platform display name is used) or any free-form
12
+ * string for unknown platforms.
13
+ */
14
+ platform: PlatformId | (string & {});
15
+ labels?: Partial<GenericLabels>;
16
+ }
17
+ /**
18
+ * Generic preview. Used as a fallback for any platform without a dedicated
19
+ * component. Layout is intentionally neutral so it composes well inside
20
+ * `PlatformChrome` if a consumer wants to add device framing.
21
+ */
22
+ export declare function GenericPreview({ platform, displayName, username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, }: GenericPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { MoreHorizontal } from 'lucide-react';
4
+ import { cn } from '../../lib/utils.js';
5
+ import { Avatar } from '../internal/avatar.js';
6
+ import { PLATFORM_META } from '../internal/platform-meta.js';
7
+ import { Textarea } from '../internal/textarea.js';
8
+ export const DEFAULT_GENERIC_LABELS = {
9
+ placeholderTemplate: (platform) => `Write your ${platform} post...`,
10
+ badgeTemplate: (platform) => `${platform} preview`,
11
+ moreOptions: 'More options',
12
+ };
13
+ function resolvePlatformName(platform) {
14
+ if (platform in PLATFORM_META) {
15
+ return PLATFORM_META[platform].displayName;
16
+ }
17
+ return platform;
18
+ }
19
+ /**
20
+ * Generic preview. Used as a fallback for any platform without a dedicated
21
+ * component. Layout is intentionally neutral so it composes well inside
22
+ * `PlatformChrome` if a consumer wants to add device framing.
23
+ */
24
+ export function GenericPreview({ platform, displayName, username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, }) {
25
+ const l = { ...DEFAULT_GENERIC_LABELS, ...labels };
26
+ const platformName = resolvePlatformName(platform);
27
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "generic", "data-generic-platform": platform, className: cn('@container bg-white p-4 text-gray-900 @[420px]:text-[14px] dark:bg-neutral-950 dark:text-neutral-100', 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 gap-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "truncate font-semibold text-[14px]", children: displayName }), _jsxs("span", { className: "truncate text-[13px] text-gray-500", children: ["@", username] })] }), _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-neutral-800", children: _jsx(MoreHorizontal, { className: "h-4 w-4" }) })] }), editable ? (_jsx(Textarea, { value: content, onChange: (e) => onContentChange?.(e.target.value), placeholder: l.placeholderTemplate(platformName), className: "mt-2 min-h-[60px] text-[14px] placeholder:text-gray-500" })) : (_jsx("p", { className: "mt-2 whitespace-pre-wrap break-words text-[14px]", children: content })), mediaUrls && mediaUrls.length > 0 && (_jsx("div", { className: "mt-3 overflow-hidden rounded-lg", children: mediaUrls.length === 1 ? (_jsx("div", { className: "aspect-video bg-gray-200", children: _jsx("img", { src: mediaUrls[0], alt: "Post media", loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }) })) : (_jsx("div", { className: "grid grid-cols-2 gap-1", children: mediaUrls.slice(0, 4).map((url, i) => (_jsx("div", { className: "aspect-square bg-gray-200", children: _jsx("img", { src: url, alt: `Media ${i + 1}`, loading: "lazy", decoding: "async", className: "h-full w-full object-cover" }) }, url))) })) }))] })] }), _jsx("div", { className: "mt-3 border-gray-200 border-t pt-3 dark:border-neutral-800", children: _jsx("p", { className: "text-gray-500 text-xs", children: l.badgeTemplate(platformName) }) })] }));
28
+ }
@@ -0,0 +1,18 @@
1
+ import type { PostPreviewBaseProps } from '../types.js';
2
+ export interface GoogleBusinessLabels {
3
+ placeholder: string;
4
+ postedJustNow: string;
5
+ learnMore: string;
6
+ like: string;
7
+ share: string;
8
+ moreOptions: string;
9
+ }
10
+ export declare const DEFAULT_GOOGLE_BUSINESS_LABELS: GoogleBusinessLabels;
11
+ export interface GoogleBusinessPreviewProps extends PostPreviewBaseProps {
12
+ labels?: Partial<GoogleBusinessLabels>;
13
+ }
14
+ /**
15
+ * Google Business Profile update-post preview. Renders the business name,
16
+ * timestamp, content, optional media, and a Learn-more CTA button.
17
+ */
18
+ export declare function GoogleBusinessPreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable, onContentChange, className, labels, }: GoogleBusinessPreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Building2, ExternalLink, MoreVertical, 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_GOOGLE_BUSINESS_LABELS = {
8
+ placeholder: 'Share an update about your business...',
9
+ postedJustNow: 'Posted just now',
10
+ learnMore: 'Learn more',
11
+ like: 'Like',
12
+ share: 'Share',
13
+ moreOptions: 'More options',
14
+ };
15
+ /**
16
+ * Google Business Profile update-post preview. Renders the business name,
17
+ * timestamp, content, optional media, and a Learn-more CTA button.
18
+ */
19
+ export function GoogleBusinessPreview({ displayName, username: _username, avatarUrl, content, mediaUrls, editable = false, onContentChange, className, labels, }) {
20
+ const l = { ...DEFAULT_GOOGLE_BUSINESS_LABELS, ...labels };
21
+ return (_jsxs("div", { "data-slot": "post-preview", "data-platform": "google_business", className: cn('@container overflow-hidden rounded-lg border border-gray-200 bg-white text-gray-900 @[420px]:text-[14px] dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-100', className), children: [_jsxs("div", { className: "flex items-start justify-between p-4", children: [_jsxs("div", { className: "flex gap-3", children: [avatarUrl ? (_jsx(Avatar, { src: avatarUrl, displayName: displayName, className: "h-10 w-10 rounded" })) : (_jsx("span", { role: "img", "aria-label": displayName, className: "inline-flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded bg-[#4285F4] text-white", children: _jsx(Building2, { className: "h-5 w-5" }) })), _jsxs("div", { children: [_jsx("p", { className: "font-medium text-[14px] text-gray-900", children: displayName }), _jsx("p", { className: "text-[12px] text-gray-500", children: l.postedJustNow })] })] }), _jsx("button", { type: "button", "aria-label": l.moreOptions, className: "inline-flex min-h-11 min-w-11 items-center justify-center rounded-full p-1 text-gray-500 hover:bg-gray-100", children: _jsx(MoreVertical, { className: "h-5 w-5" }) })] }), _jsx("div", { className: "px-4 pb-3", children: editable ? (_jsx(Textarea, { value: content, onChange: (e) => onContentChange?.(e.target.value), placeholder: l.placeholder, className: "min-h-[80px] text-[14px] text-gray-900 placeholder:text-gray-500" })) : (_jsx("p", { className: "whitespace-pre-wrap break-words text-[14px]", children: content })) }), mediaUrls && mediaUrls.length > 0 && (_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("div", { className: "border-gray-100 border-t p-4", children: _jsxs("button", { type: "button", className: "flex min-h-11 w-full items-center justify-center gap-2 rounded-md bg-[#1A73E8] px-4 py-2 font-medium text-[14px] text-white hover:bg-[#1557B0]", children: [_jsx("span", { children: l.learnMore }), _jsx(ExternalLink, { className: "h-4 w-4" })] }) }), _jsxs("div", { className: "flex items-center gap-4 px-4 pb-4", children: [_jsxs("button", { type: "button", className: "inline-flex min-h-11 items-center gap-1.5 text-gray-600 hover:text-[#1A73E8]", "aria-label": l.like, children: [_jsx(ThumbsUp, { className: "h-4 w-4" }), _jsx("span", { className: "text-[13px]", children: l.like })] }), _jsxs("button", { type: "button", "aria-label": l.share, className: "inline-flex min-h-11 items-center gap-1.5 text-gray-600 hover:text-[#1A73E8]", children: [_jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z" }) }), _jsx("span", { className: "text-[13px]", children: l.share })] })] })] }));
22
+ }
@@ -0,0 +1,24 @@
1
+ export type { BlueSkyLabels, BlueSkyPreviewProps } from './bluesky.js';
2
+ export { BlueSkyPreview, DEFAULT_BLUESKY_LABELS } from './bluesky.js';
3
+ export type { DiscordEmbedDraft, DiscordLabels, DiscordPreviewProps, } from './discord.js';
4
+ export { DEFAULT_DISCORD_LABELS, DiscordPreview } from './discord.js';
5
+ export type { FacebookLabels, FacebookPreviewProps } from './facebook.js';
6
+ export { DEFAULT_FACEBOOK_LABELS, FacebookPreview } from './facebook.js';
7
+ export type { GenericLabels, GenericPreviewProps } from './generic.js';
8
+ export { DEFAULT_GENERIC_LABELS, GenericPreview } from './generic.js';
9
+ export type { GoogleBusinessLabels, GoogleBusinessPreviewProps, } from './google-business.js';
10
+ export { DEFAULT_GOOGLE_BUSINESS_LABELS, GoogleBusinessPreview, } from './google-business.js';
11
+ export type { InstagramLabels, InstagramPostType, InstagramPreviewProps, } from './instagram.js';
12
+ export { DEFAULT_INSTAGRAM_LABELS, InstagramPreview } from './instagram.js';
13
+ export type { LinkedInLabels, LinkedInMentionData, LinkedInMentionPickerContext, LinkedInPreviewProps, } from './linkedin.js';
14
+ export { DEFAULT_LINKEDIN_LABELS, LinkedInPreview } from './linkedin.js';
15
+ export type { TelegramInlineKeyboardButton, TelegramInlineKeyboardMarkup, TelegramLabels, TelegramPollState, TelegramPreviewProps, } from './telegram.js';
16
+ export { DEFAULT_TELEGRAM_LABELS, TelegramPreview } from './telegram.js';
17
+ export type { ThreadsLabels, ThreadsPreviewProps } from './threads.js';
18
+ export { DEFAULT_THREADS_LABELS, ThreadsPreview } from './threads.js';
19
+ export type { TikTokLabels, TikTokPreviewProps } from './tiktok.js';
20
+ export { DEFAULT_TIKTOK_LABELS, TikTokPreview } from './tiktok.js';
21
+ export type { TwitterLabels, TwitterMentionPickerContext, TwitterPreviewProps, TwitterTheme, } from './twitter.js';
22
+ export { DEFAULT_TWITTER_LABELS, TwitterPreview } from './twitter.js';
23
+ export type { YouTubeLabels, YouTubePreviewProps } from './youtube.js';
24
+ export { DEFAULT_YOUTUBE_LABELS, YouTubePreview } from './youtube.js';
@@ -0,0 +1,13 @@
1
+ // Re-exports for the post-preview platforms barrel.
2
+ export { BlueSkyPreview, DEFAULT_BLUESKY_LABELS } from './bluesky.js';
3
+ export { DEFAULT_DISCORD_LABELS, DiscordPreview } from './discord.js';
4
+ export { DEFAULT_FACEBOOK_LABELS, FacebookPreview } from './facebook.js';
5
+ export { DEFAULT_GENERIC_LABELS, GenericPreview } from './generic.js';
6
+ export { DEFAULT_GOOGLE_BUSINESS_LABELS, GoogleBusinessPreview, } from './google-business.js';
7
+ export { DEFAULT_INSTAGRAM_LABELS, InstagramPreview } from './instagram.js';
8
+ export { DEFAULT_LINKEDIN_LABELS, LinkedInPreview } from './linkedin.js';
9
+ export { DEFAULT_TELEGRAM_LABELS, TelegramPreview } from './telegram.js';
10
+ export { DEFAULT_THREADS_LABELS, ThreadsPreview } from './threads.js';
11
+ export { DEFAULT_TIKTOK_LABELS, TikTokPreview } from './tiktok.js';
12
+ export { DEFAULT_TWITTER_LABELS, TwitterPreview } from './twitter.js';
13
+ export { DEFAULT_YOUTUBE_LABELS, YouTubePreview } from './youtube.js';