@medalsocial/meda 1.7.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +2 -1
  2. package/dist/calendar/calendar-toolbar.d.ts +2 -0
  3. package/dist/calendar/calendar-toolbar.js +22 -0
  4. package/dist/calendar/calendar.d.ts +2 -0
  5. package/dist/calendar/calendar.js +55 -0
  6. package/dist/calendar/day-view.d.ts +2 -0
  7. package/dist/calendar/day-view.js +40 -0
  8. package/dist/calendar/index.d.ts +6 -0
  9. package/dist/calendar/index.js +7 -0
  10. package/dist/calendar/internal/date.d.ts +26 -0
  11. package/dist/calendar/internal/date.js +107 -0
  12. package/dist/calendar/internal/day-cell.d.ts +15 -0
  13. package/dist/calendar/internal/day-cell.js +38 -0
  14. package/dist/calendar/internal/event-pill.d.ts +13 -0
  15. package/dist/calendar/internal/event-pill.js +30 -0
  16. package/dist/calendar/internal/time-slot.d.ts +17 -0
  17. package/dist/calendar/internal/time-slot.js +23 -0
  18. package/dist/calendar/month-view.d.ts +2 -0
  19. package/dist/calendar/month-view.js +46 -0
  20. package/dist/calendar/public.d.ts +6 -0
  21. package/dist/calendar/public.js +8 -0
  22. package/dist/calendar/types.d.ts +113 -0
  23. package/dist/calendar/types.js +15 -0
  24. package/dist/calendar/week-view.d.ts +2 -0
  25. package/dist/calendar/week-view.js +39 -0
  26. package/dist/chat/turn-card.js +10 -5
  27. package/dist/email-builder/block-error-boundary.d.ts +16 -0
  28. package/dist/email-builder/block-error-boundary.js +20 -0
  29. package/dist/email-builder/block-palette.d.ts +7 -0
  30. package/dist/email-builder/block-palette.js +22 -0
  31. package/dist/email-builder/block-registry.d.ts +15 -0
  32. package/dist/email-builder/block-registry.js +156 -0
  33. package/dist/email-builder/block-renderer.d.ts +5 -0
  34. package/dist/email-builder/block-renderer.js +40 -0
  35. package/dist/email-builder/block-renderers/button.d.ts +4 -0
  36. package/dist/email-builder/block-renderers/button.js +26 -0
  37. package/dist/email-builder/block-renderers/columns.d.ts +5 -0
  38. package/dist/email-builder/block-renderers/columns.js +27 -0
  39. package/dist/email-builder/block-renderers/divider.d.ts +4 -0
  40. package/dist/email-builder/block-renderers/divider.js +14 -0
  41. package/dist/email-builder/block-renderers/footer.d.ts +4 -0
  42. package/dist/email-builder/block-renderers/footer.js +12 -0
  43. package/dist/email-builder/block-renderers/heading.d.ts +4 -0
  44. package/dist/email-builder/block-renderers/heading.js +19 -0
  45. package/dist/email-builder/block-renderers/image.d.ts +4 -0
  46. package/dist/email-builder/block-renderers/image.js +18 -0
  47. package/dist/email-builder/block-renderers/social.d.ts +4 -0
  48. package/dist/email-builder/block-renderers/social.js +19 -0
  49. package/dist/email-builder/block-renderers/spacer.d.ts +4 -0
  50. package/dist/email-builder/block-renderers/spacer.js +5 -0
  51. package/dist/email-builder/block-renderers/text.d.ts +4 -0
  52. package/dist/email-builder/block-renderers/text.js +17 -0
  53. package/dist/email-builder/builder-canvas.d.ts +15 -0
  54. package/dist/email-builder/builder-canvas.js +26 -0
  55. package/dist/email-builder/builder-header.d.ts +9 -0
  56. package/dist/email-builder/builder-header.js +8 -0
  57. package/dist/email-builder/builder-left-tabs.d.ts +11 -0
  58. package/dist/email-builder/builder-left-tabs.js +19 -0
  59. package/dist/email-builder/email-builder.d.ts +3 -0
  60. package/dist/email-builder/email-builder.js +84 -0
  61. package/dist/email-builder/envelope-card.d.ts +7 -0
  62. package/dist/email-builder/envelope-card.js +16 -0
  63. package/dist/email-builder/floating-bar.d.ts +13 -0
  64. package/dist/email-builder/floating-bar.js +9 -0
  65. package/dist/email-builder/index.d.ts +4 -0
  66. package/dist/email-builder/index.js +12 -0
  67. package/dist/email-builder/internal/builder-state-utils.d.ts +7 -0
  68. package/dist/email-builder/internal/builder-state-utils.js +52 -0
  69. package/dist/email-builder/internal/dnd-wrapper.d.ts +10 -0
  70. package/dist/email-builder/internal/dnd-wrapper.js +9 -0
  71. package/dist/email-builder/internal/email-font-options.d.ts +5 -0
  72. package/dist/email-builder/internal/email-font-options.js +12 -0
  73. package/dist/email-builder/mobile-drawers.d.ts +10 -0
  74. package/dist/email-builder/mobile-drawers.js +7 -0
  75. package/dist/email-builder/mobile-tab-bar.d.ts +10 -0
  76. package/dist/email-builder/mobile-tab-bar.js +9 -0
  77. package/dist/email-builder/property-editors/_shared.d.ts +58 -0
  78. package/dist/email-builder/property-editors/_shared.js +52 -0
  79. package/dist/email-builder/property-editors/button-editor.d.ts +7 -0
  80. package/dist/email-builder/property-editors/button-editor.js +10 -0
  81. package/dist/email-builder/property-editors/columns-editor.d.ts +7 -0
  82. package/dist/email-builder/property-editors/columns-editor.js +15 -0
  83. package/dist/email-builder/property-editors/divider-editor.d.ts +7 -0
  84. package/dist/email-builder/property-editors/divider-editor.js +10 -0
  85. package/dist/email-builder/property-editors/footer-editor.d.ts +7 -0
  86. package/dist/email-builder/property-editors/footer-editor.js +6 -0
  87. package/dist/email-builder/property-editors/heading-editor.d.ts +7 -0
  88. package/dist/email-builder/property-editors/heading-editor.js +16 -0
  89. package/dist/email-builder/property-editors/image-editor.d.ts +8 -0
  90. package/dist/email-builder/property-editors/image-editor.js +17 -0
  91. package/dist/email-builder/property-editors/social-editor.d.ts +7 -0
  92. package/dist/email-builder/property-editors/social-editor.js +18 -0
  93. package/dist/email-builder/property-editors/spacer-editor.d.ts +7 -0
  94. package/dist/email-builder/property-editors/spacer-editor.js +6 -0
  95. package/dist/email-builder/property-editors/text-editor.d.ts +13 -0
  96. package/dist/email-builder/property-editors/text-editor.js +8 -0
  97. package/dist/email-builder/property-inspector.d.ts +11 -0
  98. package/dist/email-builder/property-inspector.js +42 -0
  99. package/dist/email-builder/public.d.ts +1 -0
  100. package/dist/email-builder/public.js +3 -0
  101. package/dist/email-builder/render-to-email-html.d.ts +11 -0
  102. package/dist/email-builder/render-to-email-html.js +219 -0
  103. package/dist/email-builder/starter-shell.d.ts +3 -0
  104. package/dist/email-builder/starter-shell.js +21 -0
  105. package/dist/email-builder/types.d.ts +182 -0
  106. package/dist/email-builder/types.js +41 -0
  107. package/dist/email-builder/view-controls.d.ts +10 -0
  108. package/dist/email-builder/view-controls.js +12 -0
  109. package/dist/index.d.ts +4 -0
  110. package/dist/index.js +4 -0
  111. package/dist/lib/format-time.js +2 -3
  112. package/dist/post-preview/index.d.ts +4 -0
  113. package/dist/post-preview/index.js +7 -0
  114. package/dist/post-preview/internal/avatar.d.ts +12 -0
  115. package/dist/post-preview/internal/avatar.js +21 -0
  116. package/dist/post-preview/internal/format-content.d.ts +12 -0
  117. package/dist/post-preview/internal/format-content.js +31 -0
  118. package/dist/post-preview/internal/platform-meta.d.ts +10 -0
  119. package/dist/post-preview/internal/platform-meta.js +18 -0
  120. package/dist/post-preview/internal/textarea.d.ts +8 -0
  121. package/dist/post-preview/internal/textarea.js +12 -0
  122. package/dist/post-preview/internal/use-swipe.d.ts +17 -0
  123. package/dist/post-preview/internal/use-swipe.js +29 -0
  124. package/dist/post-preview/platforms/bluesky.d.ts +15 -0
  125. package/dist/post-preview/platforms/bluesky.js +21 -0
  126. package/dist/post-preview/platforms/discord.d.ts +44 -0
  127. package/dist/post-preview/platforms/discord.js +32 -0
  128. package/dist/post-preview/platforms/facebook.d.ts +23 -0
  129. package/dist/post-preview/platforms/facebook.js +24 -0
  130. package/dist/post-preview/platforms/generic.d.ts +22 -0
  131. package/dist/post-preview/platforms/generic.js +28 -0
  132. package/dist/post-preview/platforms/google-business.d.ts +18 -0
  133. package/dist/post-preview/platforms/google-business.js +22 -0
  134. package/dist/post-preview/platforms/index.d.ts +24 -0
  135. package/dist/post-preview/platforms/index.js +13 -0
  136. package/dist/post-preview/platforms/instagram.d.ts +31 -0
  137. package/dist/post-preview/platforms/instagram.js +99 -0
  138. package/dist/post-preview/platforms/linkedin.d.ts +52 -0
  139. package/dist/post-preview/platforms/linkedin.js +131 -0
  140. package/dist/post-preview/platforms/telegram.d.ts +46 -0
  141. package/dist/post-preview/platforms/telegram.js +42 -0
  142. package/dist/post-preview/platforms/threads.d.ts +19 -0
  143. package/dist/post-preview/platforms/threads.js +24 -0
  144. package/dist/post-preview/platforms/tiktok.d.ts +18 -0
  145. package/dist/post-preview/platforms/tiktok.js +23 -0
  146. package/dist/post-preview/platforms/twitter.d.ts +40 -0
  147. package/dist/post-preview/platforms/twitter.js +132 -0
  148. package/dist/post-preview/platforms/youtube.d.ts +17 -0
  149. package/dist/post-preview/platforms/youtube.js +21 -0
  150. package/dist/post-preview/post-preview.d.ts +29 -0
  151. package/dist/post-preview/post-preview.js +70 -0
  152. package/dist/post-preview/public.d.ts +1 -0
  153. package/dist/post-preview/public.js +6 -0
  154. package/dist/post-preview/types.d.ts +82 -0
  155. package/dist/post-preview/types.js +1 -0
  156. package/dist/primitives/filter-rail.js +17 -2
  157. package/dist/shell/app-shell-workspace.js +1 -1
  158. package/dist/shell/context-rail.d.ts +4 -2
  159. package/dist/shell/context-rail.js +27 -19
  160. package/dist/shell/extras/shell-scrollable-content.js +2 -2
  161. package/dist/shell/extras/workbench-layout.js +24 -13
  162. package/dist/shell/index.d.ts +1 -1
  163. package/dist/shell/internal/mobile-drawers.d.ts +6 -2
  164. package/dist/shell/internal/mobile-drawers.js +10 -7
  165. package/dist/shell/right-panel.js +10 -1
  166. package/dist/shell/shell-header.js +1 -1
  167. package/dist/shell/shell-provider.js +69 -53
  168. package/dist/shell/theme-next-themes.js +2 -2
  169. package/dist/shell/theme.js +7 -1
  170. package/dist/shell/types.d.ts +4 -0
  171. package/dist/shell/use-shell-viewport.js +14 -4
  172. package/dist/timeline/lane-timeline.js +1 -1
  173. package/dist/timeline/scrub-bar.js +1 -1
  174. package/dist/timeline/timeline-tape.js +8 -5
  175. package/dist/voice/voice-orb-scene.js +4 -4
  176. package/dist/voice/voice-status-pill.js +8 -7
  177. package/dist/workflow-builder/edges/default-edge.d.ts +10 -0
  178. package/dist/workflow-builder/edges/default-edge.js +21 -0
  179. package/dist/workflow-builder/edges/index.d.ts +3 -0
  180. package/dist/workflow-builder/edges/index.js +5 -0
  181. package/dist/workflow-builder/index.d.ts +5 -0
  182. package/dist/workflow-builder/index.js +12 -0
  183. package/dist/workflow-builder/internal/trigger-icons.d.ts +4 -0
  184. package/dist/workflow-builder/internal/trigger-icons.js +43 -0
  185. package/dist/workflow-builder/internal/trigger-sentence.d.ts +14 -0
  186. package/dist/workflow-builder/internal/trigger-sentence.js +50 -0
  187. package/dist/workflow-builder/nodes/action-node.d.ts +3 -0
  188. package/dist/workflow-builder/nodes/action-node.js +7 -0
  189. package/dist/workflow-builder/nodes/base-node.d.ts +20 -0
  190. package/dist/workflow-builder/nodes/base-node.js +18 -0
  191. package/dist/workflow-builder/nodes/condition-node.d.ts +3 -0
  192. package/dist/workflow-builder/nodes/condition-node.js +11 -0
  193. package/dist/workflow-builder/nodes/delay-node.d.ts +3 -0
  194. package/dist/workflow-builder/nodes/delay-node.js +7 -0
  195. package/dist/workflow-builder/nodes/end-node.d.ts +3 -0
  196. package/dist/workflow-builder/nodes/end-node.js +7 -0
  197. package/dist/workflow-builder/nodes/index.d.ts +13 -0
  198. package/dist/workflow-builder/nodes/index.js +22 -0
  199. package/dist/workflow-builder/nodes/trigger-node.d.ts +3 -0
  200. package/dist/workflow-builder/nodes/trigger-node.js +8 -0
  201. package/dist/workflow-builder/public.d.ts +1 -0
  202. package/dist/workflow-builder/public.js +4 -0
  203. package/dist/workflow-builder/types.d.ts +140 -0
  204. package/dist/workflow-builder/types.js +18 -0
  205. package/dist/workflow-builder/workflow-builder.d.ts +10 -0
  206. package/dist/workflow-builder/workflow-builder.js +22 -0
  207. package/dist/workflow-builder/workflow-canvas.d.ts +26 -0
  208. package/dist/workflow-builder/workflow-canvas.js +25 -0
  209. package/dist/workflow-builder/workflow-card-compact.d.ts +2 -0
  210. package/dist/workflow-builder/workflow-card-compact.js +22 -0
  211. package/dist/workflow-builder/workflow-card.d.ts +2 -0
  212. package/dist/workflow-builder/workflow-card.js +44 -0
  213. package/dist/workflow-builder/workflow-header.d.ts +2 -0
  214. package/dist/workflow-builder/workflow-header.js +31 -0
  215. package/dist/workflow-builder/workflow-toolbox.d.ts +2 -0
  216. package/dist/workflow-builder/workflow-toolbox.js +19 -0
  217. package/package.json +72 -50
@@ -0,0 +1,7 @@
1
+ import type { HeadingBlockProps } from '../types.js';
2
+ interface Props {
3
+ value: HeadingBlockProps;
4
+ onChange: (patch: Partial<HeadingBlockProps>) => void;
5
+ }
6
+ export declare function HeadingPropertyEditor({ value, onChange }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { EMAIL_FONT_OPTIONS } from '../internal/email-font-options.js';
4
+ import { AlignmentToggle, ColorInput, Field, NumberInput, SelectInput, SpacingEditor, TextInput, } from './_shared.js';
5
+ export function HeadingPropertyEditor({ value, onChange }) {
6
+ return (_jsxs("div", { "data-slot": "email-builder-property-heading", className: "flex flex-col gap-3", children: [_jsx(Field, { label: "Text", htmlFor: "heading-text", children: _jsx(TextInput, { id: "heading-text", value: value.text, onChange: (text) => onChange({ text }) }) }), _jsx(Field, { label: "Level", htmlFor: "heading-level", children: _jsx(SelectInput, { id: "heading-level", value: String(value.level), options: [
7
+ { value: '1', label: 'H1' },
8
+ { value: '2', label: 'H2' },
9
+ { value: '3', label: 'H3' },
10
+ ], onChange: (v) => onChange({ level: Number(v) }) }) }), _jsx(Field, { label: "Alignment", children: _jsx(AlignmentToggle, { value: value.alignment, onChange: (alignment) => onChange({ alignment }) }) }), _jsx(Field, { label: "Color", htmlFor: "heading-color", children: _jsx(ColorInput, { id: "heading-color", value: value.color, onChange: (color) => onChange({ color }) }) }), _jsx(Field, { label: "Font family", htmlFor: "heading-font", children: _jsx(SelectInput, { id: "heading-font", value: value.fontFamily, options: EMAIL_FONT_OPTIONS.map((o) => ({ value: o.value, label: o.label })), onChange: (fontFamily) => onChange({ fontFamily }) }) }), _jsx(Field, { label: "Font weight", children: _jsx(SelectInput, { value: String(value.fontWeight), options: [
11
+ { value: '400', label: 'Regular' },
12
+ { value: '500', label: 'Medium' },
13
+ { value: '600', label: 'Semibold' },
14
+ { value: '700', label: 'Bold' },
15
+ ], onChange: (v) => onChange({ fontWeight: Number(v) }) }) }), _jsx(Field, { label: "Font size (px)", htmlFor: "heading-fs", children: _jsx(NumberInput, { id: "heading-fs", value: value.fontSize, min: 10, max: 96, onChange: (fontSize) => onChange({ fontSize }) }) }), _jsx(Field, { label: "Padding", children: _jsx(SpacingEditor, { value: value.padding, onChange: (padding) => onChange({ padding }) }) })] }));
16
+ }
@@ -0,0 +1,8 @@
1
+ import type { EmailBuilderProps, ImageBlockProps } from '../types.js';
2
+ interface Props {
3
+ value: ImageBlockProps;
4
+ onChange: (patch: Partial<ImageBlockProps>) => void;
5
+ renderMediaPicker?: EmailBuilderProps['renderMediaPicker'];
6
+ }
7
+ export declare function ImagePropertyEditor({ value, onChange, renderMediaPicker }: Props): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { cn } from '../../lib/utils.js';
5
+ import { AlignmentToggle, Field, NumberInput, SpacingEditor, TextInput } from './_shared.js';
6
+ export function ImagePropertyEditor({ value, onChange, renderMediaPicker }) {
7
+ const [pickerOpen, setPickerOpen] = useState(false);
8
+ return (_jsxs("div", { "data-slot": "email-builder-property-image", className: "flex flex-col gap-3", children: [_jsx(Field, { label: "Source URL", htmlFor: "img-src", children: _jsx(TextInput, { id: "img-src", value: value.src, onChange: (src) => onChange({ src }) }) }), renderMediaPicker ? (_jsx("button", { type: "button", onClick: () => setPickerOpen(true), className: cn('inline-flex h-9 items-center justify-center rounded-md border border-input bg-background px-3 font-medium text-sm', 'hover:bg-accent hover:text-accent-foreground'), children: "Pick from media library" })) : null, pickerOpen && renderMediaPicker
9
+ ? renderMediaPicker({
10
+ onPick: (url) => {
11
+ onChange({ src: url });
12
+ setPickerOpen(false);
13
+ },
14
+ onCancel: () => setPickerOpen(false),
15
+ })
16
+ : null, _jsx(Field, { label: "Alt text", htmlFor: "img-alt", children: _jsx(TextInput, { id: "img-alt", value: value.alt, onChange: (alt) => onChange({ alt }) }) }), _jsx(Field, { label: "Link URL", htmlFor: "img-link", children: _jsx(TextInput, { id: "img-link", value: value.linkUrl, onChange: (linkUrl) => onChange({ linkUrl }) }) }), _jsx(Field, { label: "Width (px)", children: _jsx(NumberInput, { value: value.width, min: 20, max: 1200, onChange: (width) => onChange({ width }) }) }), _jsx(Field, { label: "Alignment", children: _jsx(AlignmentToggle, { value: value.alignment, onChange: (alignment) => onChange({ alignment }) }) }), _jsx(Field, { label: "Padding", children: _jsx(SpacingEditor, { value: value.padding, onChange: (padding) => onChange({ padding }) }) })] }));
17
+ }
@@ -0,0 +1,7 @@
1
+ import type { SocialBlockProps } from '../types.js';
2
+ interface Props {
3
+ value: SocialBlockProps;
4
+ onChange: (patch: Partial<SocialBlockProps>) => void;
5
+ }
6
+ export declare function SocialPropertyEditor({ value, onChange }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Plus, Trash2 } from 'lucide-react';
4
+ import { cn } from '../../lib/utils.js';
5
+ import { AlignmentToggle, Field, NumberInput, SpacingEditor, TextInput } from './_shared.js';
6
+ export function SocialPropertyEditor({ value, onChange }) {
7
+ const updateLink = (index, patch) => {
8
+ const links = value.links.map((l, i) => (i === index ? { ...l, ...patch } : l));
9
+ onChange({ links });
10
+ };
11
+ const removeLink = (index) => {
12
+ onChange({ links: value.links.filter((_, i) => i !== index) });
13
+ };
14
+ const addLink = () => {
15
+ onChange({ links: [...value.links, { platform: 'twitter', url: 'https://' }] });
16
+ };
17
+ return (_jsxs("div", { "data-slot": "email-builder-property-social", className: "flex flex-col gap-3", children: [_jsx(Field, { label: "Links", children: _jsxs("div", { className: "flex flex-col gap-2", children: [value.links.map((link, i) => (_jsxs("div", { className: "flex flex-col gap-1.5 rounded-md border border-input p-2", children: [_jsx(TextInput, { value: link.platform, placeholder: "Platform", onChange: (platform) => updateLink(i, { platform }) }), _jsx(TextInput, { value: link.url, placeholder: "https://", onChange: (url) => updateLink(i, { url }) }), _jsx(TextInput, { value: link.iconUrl ?? '', placeholder: "Icon URL (optional)", onChange: (iconUrl) => updateLink(i, { iconUrl }) }), _jsxs("button", { type: "button", onClick: () => removeLink(i), className: cn('inline-flex h-8 items-center justify-center gap-1 rounded-md text-destructive text-xs hover:bg-destructive/10'), "aria-label": "Remove link", children: [_jsx(Trash2, { className: "size-3.5" }), " Remove"] })] }, i))), _jsxs("button", { type: "button", onClick: addLink, className: "inline-flex h-9 items-center justify-center gap-1 rounded-md border border-input border-dashed text-sm hover:bg-accent", children: [_jsx(Plus, { className: "size-4" }), " Add link"] })] }) }), _jsx(Field, { label: "Icon size (px)", children: _jsx(NumberInput, { value: value.iconSize, min: 12, max: 64, onChange: (iconSize) => onChange({ iconSize }) }) }), _jsx(Field, { label: "Spacing (px)", children: _jsx(NumberInput, { value: value.spacing, min: 0, max: 48, onChange: (spacing) => onChange({ spacing }) }) }), _jsx(Field, { label: "Alignment", children: _jsx(AlignmentToggle, { value: value.alignment, onChange: (alignment) => onChange({ alignment }) }) }), _jsx(Field, { label: "Padding", children: _jsx(SpacingEditor, { value: value.padding, onChange: (padding) => onChange({ padding }) }) })] }));
18
+ }
@@ -0,0 +1,7 @@
1
+ import type { SpacerBlockProps } from '../types.js';
2
+ interface Props {
3
+ value: SpacerBlockProps;
4
+ onChange: (patch: Partial<SpacerBlockProps>) => void;
5
+ }
6
+ export declare function SpacerPropertyEditor({ value, onChange }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,6 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Field, NumberInput } from './_shared.js';
4
+ export function SpacerPropertyEditor({ value, onChange }) {
5
+ return (_jsx("div", { "data-slot": "email-builder-property-spacer", className: "flex flex-col gap-3", children: _jsx(Field, { label: "Height (px)", children: _jsx(NumberInput, { value: value.height, min: 1, max: 400, onChange: (height) => onChange({ height }) }) }) }));
6
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { TextBlockProps } from '../types.js';
3
+ interface Props {
4
+ value: TextBlockProps;
5
+ onChange: (patch: Partial<TextBlockProps>) => void;
6
+ /** Optional rich-text editor slot (Lexical, etc.). Defaults to a textarea. */
7
+ renderEditor?: (ctx: {
8
+ value: string;
9
+ onChange: (next: string) => void;
10
+ }) => ReactNode;
11
+ }
12
+ export declare function TextPropertyEditor({ value, onChange, renderEditor }: Props): import("react/jsx-runtime").JSX.Element;
13
+ export {};
@@ -0,0 +1,8 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { EMAIL_FONT_OPTIONS } from '../internal/email-font-options.js';
4
+ import { AlignmentToggle, ColorInput, Field, NumberInput, SelectInput, SpacingEditor, TextAreaInput, } from './_shared.js';
5
+ export function TextPropertyEditor({ value, onChange, renderEditor }) {
6
+ const onContent = (content) => onChange({ content });
7
+ return (_jsxs("div", { "data-slot": "email-builder-property-text", className: "flex flex-col gap-3", children: [_jsx(Field, { label: "Content", htmlFor: "text-content", children: renderEditor ? (renderEditor({ value: value.content, onChange: onContent })) : (_jsx(TextAreaInput, { id: "text-content", value: value.content, onChange: onContent, rows: 5 })) }), _jsx(Field, { label: "Alignment", children: _jsx(AlignmentToggle, { value: value.alignment, onChange: (alignment) => onChange({ alignment }) }) }), _jsx(Field, { label: "Color", htmlFor: "text-color", children: _jsx(ColorInput, { id: "text-color", value: value.color, onChange: (color) => onChange({ color }) }) }), _jsx(Field, { label: "Font family", children: _jsx(SelectInput, { value: value.fontFamily, options: EMAIL_FONT_OPTIONS.map((o) => ({ value: o.value, label: o.label })), onChange: (fontFamily) => onChange({ fontFamily }) }) }), _jsx(Field, { label: "Font size (px)", children: _jsx(NumberInput, { value: value.fontSize, min: 10, max: 48, onChange: (fontSize) => onChange({ fontSize }) }) }), _jsx(Field, { label: "Padding", children: _jsx(SpacingEditor, { value: value.padding, onChange: (padding) => onChange({ padding }) }) })] }));
8
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { BlockKind, BlockPropsMap, EmailBlock, EmailBuilderProps } from './types.js';
3
+ interface PropertyInspectorProps {
4
+ block: EmailBlock | null;
5
+ onChange: <K extends BlockKind>(blockId: string, patch: Partial<BlockPropsMap[K]>) => void;
6
+ emptyContent: ReactNode;
7
+ renderMediaPicker?: EmailBuilderProps['renderMediaPicker'];
8
+ renderTextEditor?: EmailBuilderProps['renderTextEditor'];
9
+ }
10
+ export declare function PropertyInspector({ block, onChange, emptyContent, renderMediaPicker, renderTextEditor, }: PropertyInspectorProps): import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ButtonPropertyEditor } from './property-editors/button-editor.js';
4
+ import { ColumnsPropertyEditor } from './property-editors/columns-editor.js';
5
+ import { DividerPropertyEditor } from './property-editors/divider-editor.js';
6
+ import { FooterPropertyEditor } from './property-editors/footer-editor.js';
7
+ import { HeadingPropertyEditor } from './property-editors/heading-editor.js';
8
+ import { ImagePropertyEditor } from './property-editors/image-editor.js';
9
+ import { SocialPropertyEditor } from './property-editors/social-editor.js';
10
+ import { SpacerPropertyEditor } from './property-editors/spacer-editor.js';
11
+ import { TextPropertyEditor } from './property-editors/text-editor.js';
12
+ export function PropertyInspector({ block, onChange, emptyContent, renderMediaPicker, renderTextEditor, }) {
13
+ if (!block) {
14
+ return (_jsx("div", { "data-slot": "email-builder-inspector-empty", className: "flex h-full items-center justify-center p-6 text-center text-muted-foreground text-sm", children: emptyContent }));
15
+ }
16
+ return (_jsxs("div", { "data-slot": "email-builder-inspector", className: "flex flex-col gap-3 p-4", children: [_jsxs("div", { className: "font-semibold text-foreground text-sm capitalize", children: [block.kind, " properties"] }), renderEditor(block, onChange, { renderMediaPicker, renderTextEditor })] }));
17
+ }
18
+ function renderEditor(block, onChange, slots) {
19
+ const id = block.id;
20
+ switch (block.kind) {
21
+ case 'heading':
22
+ return (_jsx(HeadingPropertyEditor, { value: block.props, onChange: (p) => onChange(id, p) }));
23
+ case 'text':
24
+ return (_jsx(TextPropertyEditor, { value: block.props, onChange: (p) => onChange(id, p), renderEditor: slots.renderTextEditor }));
25
+ case 'image':
26
+ return (_jsx(ImagePropertyEditor, { value: block.props, onChange: (p) => onChange(id, p), renderMediaPicker: slots.renderMediaPicker }));
27
+ case 'button':
28
+ return (_jsx(ButtonPropertyEditor, { value: block.props, onChange: (p) => onChange(id, p) }));
29
+ case 'divider':
30
+ return (_jsx(DividerPropertyEditor, { value: block.props, onChange: (p) => onChange(id, p) }));
31
+ case 'spacer':
32
+ return (_jsx(SpacerPropertyEditor, { value: block.props, onChange: (p) => onChange(id, p) }));
33
+ case 'columns':
34
+ return (_jsx(ColumnsPropertyEditor, { value: block.props, onChange: (p) => onChange(id, p) }));
35
+ case 'social':
36
+ return (_jsx(SocialPropertyEditor, { value: block.props, onChange: (p) => onChange(id, p) }));
37
+ case 'footer':
38
+ return (_jsx(FooterPropertyEditor, { value: block.props, onChange: (p) => onChange(id, p) }));
39
+ default:
40
+ return null;
41
+ }
42
+ }
@@ -0,0 +1 @@
1
+ export { type EmailBlock, type EmailBrand, EmailBuilder, type EmailBuilderLabels, type EmailBuilderProps, type EmailDocument, renderToEmailHtml, } from './index.js';
@@ -0,0 +1,3 @@
1
+ // Root re-export for `@medalsocial/meda` — exposes the most common
2
+ // types/components without forcing consumers to import the full subpath.
3
+ export { EmailBuilder, renderToEmailHtml, } from './index.js';
@@ -0,0 +1,11 @@
1
+ import type { EmailBrand, EmailDocument } from './types.js';
2
+ export interface RenderToEmailHtmlOptions {
3
+ brand?: EmailBrand;
4
+ /** Override the default content width (px). */
5
+ contentWidth?: number;
6
+ /** Background color of the surrounding email shell. */
7
+ backgroundColor?: string;
8
+ /** Background color of the content table. */
9
+ contentBackgroundColor?: string;
10
+ }
11
+ export declare function renderToEmailHtml(doc: EmailDocument, options?: RenderToEmailHtmlOptions): string;
@@ -0,0 +1,219 @@
1
+ // Render an EmailDocument to email-safe HTML. Adapted from
2
+ // apps/web/src/components/email-builder/render-to-email-html.ts. Differences:
3
+ // - No DOMPurify dependency (consumer can re-sanitize the output if desired).
4
+ // Block content is text-escaped at render time; raw HTML is not supported in
5
+ // this surface.
6
+ // - No dark-mode or site-url plumbing.
7
+ // - No `contentSlot`/`html`/`video`/`otp` block kinds (out of scope).
8
+ import { getColumnWidths } from './block-registry.js';
9
+ const BUTTON_SIZES = {
10
+ sm: { paddingX: 16, paddingY: 8, fontSize: 14 },
11
+ md: { paddingX: 24, paddingY: 12, fontSize: 16 },
12
+ lg: { paddingX: 32, paddingY: 16, fontSize: 18 },
13
+ };
14
+ export function renderToEmailHtml(doc, options = {}) {
15
+ const brand = options.brand ?? {};
16
+ const contentWidth = options.contentWidth ?? 600;
17
+ const backgroundColor = options.backgroundColor ?? '#f4f4f4';
18
+ const contentBackgroundColor = options.contentBackgroundColor ?? '#ffffff';
19
+ const fontFamily = brand.fontBody ??
20
+ '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
21
+ const textColor = '#111827';
22
+ const preheader = doc.envelope?.preheader ?? '';
23
+ const subject = doc.envelope?.subject ?? '';
24
+ const body = doc.blocks.map((b) => renderBlock(b, brand, contentWidth)).join('\n');
25
+ return `<!DOCTYPE html>
26
+ <html xmlns="http://www.w3.org/1999/xhtml">
27
+ <head>
28
+ <meta charset="utf-8">
29
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
30
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
31
+ <meta name="x-apple-disable-message-reformatting">
32
+ <title>${esc(subject)}</title>
33
+ </head>
34
+ <body style="margin: 0; padding: 0; background-color: ${esc(backgroundColor)}; font-family: ${esc(fontFamily)}; color: ${esc(textColor)};">
35
+ ${preheader ? `<div style="display:none;visibility:hidden;mso-hide:all;font-size:1px;color:${esc(backgroundColor)};line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;">${esc(preheader)}</div>` : ''}
36
+ <table cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: ${esc(backgroundColor)};">
37
+ <tr>
38
+ <td align="center" valign="top">
39
+ <table cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: ${esc(contentBackgroundColor)}; max-width: ${contentWidth}px; margin: 0 auto;">
40
+ ${body}
41
+ </table>
42
+ </td>
43
+ </tr>
44
+ </table>
45
+ </body>
46
+ </html>`;
47
+ }
48
+ function renderBlock(block, brand, contentWidth) {
49
+ switch (block.kind) {
50
+ case 'heading':
51
+ return renderHeading(block.props, brand);
52
+ case 'text':
53
+ return renderText(block.props, brand);
54
+ case 'image':
55
+ return renderImage(block.props);
56
+ case 'button':
57
+ return renderButton(block.props, brand);
58
+ case 'divider':
59
+ return renderDivider(block.props);
60
+ case 'spacer':
61
+ return renderSpacer(block.props);
62
+ case 'columns':
63
+ return renderColumns(block, brand, contentWidth);
64
+ case 'social':
65
+ return renderSocial(block.props);
66
+ case 'footer':
67
+ return renderFooter(block.props);
68
+ default:
69
+ return '';
70
+ }
71
+ }
72
+ function renderHeading(p, brand) {
73
+ const tag = `h${p.level}`;
74
+ const fontFamily = p.fontFamily || brand.fontHeading || '';
75
+ return `<tr>
76
+ <td style="padding: ${pad(p.padding)}; text-align: ${p.alignment};">
77
+ <${tag} style="margin: 0; color: ${esc(p.color)}; font-size: ${p.fontSize}px; font-weight: ${p.fontWeight}; ${fontFamily ? `font-family: ${esc(fontFamily)};` : ''} line-height: 1.3;">${esc(p.text)}</${tag}>
78
+ </td>
79
+ </tr>`;
80
+ }
81
+ function renderText(p, brand) {
82
+ const fontFamily = p.fontFamily || brand.fontBody || '';
83
+ // Content is plain text — split paragraphs on blank lines and `<br>` single breaks.
84
+ const escaped = esc(p.content)
85
+ .split(/\n{2,}/)
86
+ .map((para) => `<p style="margin: 0 0 12px 0;">${para.replace(/\n/g, '<br>')}</p>`)
87
+ .join('\n ');
88
+ return `<tr>
89
+ <td style="padding: ${pad(p.padding)}; text-align: ${p.alignment}; color: ${esc(p.color)}; font-size: ${p.fontSize}px; font-weight: ${p.fontWeight}; ${fontFamily ? `font-family: ${esc(fontFamily)};` : ''} line-height: 1.55; word-break: break-word;">
90
+ ${escaped}
91
+ </td>
92
+ </tr>`;
93
+ }
94
+ function renderImage(p) {
95
+ if (!p.src)
96
+ return '';
97
+ const heightAttr = p.height === 'auto' ? '' : ` height="${p.height}"`;
98
+ const img = `<img src="${esc(p.src)}" alt="${esc(p.alt)}" width="${p.width}"${heightAttr} border="0" style="display: block; width: 100%; max-width: ${p.width}px; height: auto;">`;
99
+ const content = p.linkUrl ? `<a href="${esc(p.linkUrl)}">${img}</a>` : img;
100
+ return `<tr>
101
+ <td style="padding: ${pad(p.padding)}; text-align: ${p.alignment};">
102
+ ${wrapAligned(content, p.alignment)}
103
+ </td>
104
+ </tr>`;
105
+ }
106
+ function renderButton(p, brand) {
107
+ const s = BUTTON_SIZES[p.size];
108
+ const bg = p.backgroundColor || brand.brandColor || '#4F46E5';
109
+ const widthAttr = p.fullWidth ? ' width="100%"' : '';
110
+ return `<tr>
111
+ <td style="padding: ${pad(p.padding)};" align="${p.alignment}">
112
+ <table cellspacing="0" cellpadding="0" border="0" align="${p.alignment}"${widthAttr}>
113
+ <tr>
114
+ <td bgcolor="${esc(bg)}" style="background-color: ${esc(bg)}; border-radius: ${p.borderRadius}px; padding: ${s.paddingY}px ${s.paddingX}px; text-align: center;">
115
+ <a href="${esc(p.url)}" style="color: ${esc(p.textColor)}; font-size: ${s.fontSize}px; font-weight: 600; text-decoration: none;">${esc(p.text)}</a>
116
+ </td>
117
+ </tr>
118
+ </table>
119
+ </td>
120
+ </tr>`;
121
+ }
122
+ function renderDivider(p) {
123
+ return `<tr>
124
+ <td style="padding: ${pad(p.padding)};">
125
+ <table cellspacing="0" cellpadding="0" border="0" width="${p.width}%" align="center">
126
+ <tr>
127
+ <td style="border-top: ${p.thickness}px ${p.style} ${esc(p.color)}; font-size: 0;">&nbsp;</td>
128
+ </tr>
129
+ </table>
130
+ </td>
131
+ </tr>`;
132
+ }
133
+ function renderSpacer(p) {
134
+ return `<tr>
135
+ <td height="${p.height}" style="font-size: 0; line-height: 0;">&nbsp;</td>
136
+ </tr>`;
137
+ }
138
+ function renderColumns(block, brand, contentWidth) {
139
+ const p = block.props;
140
+ const widths = getColumnWidths(p.layout);
141
+ const children = block.children ?? [];
142
+ const bg = p.backgroundColor !== 'transparent' ? ` background-color: ${esc(p.backgroundColor)};` : '';
143
+ const cells = widths
144
+ .map((width, i) => {
145
+ const colBlocks = (children[i] ?? [])
146
+ .map((b) => renderBlock(b, brand, contentWidth))
147
+ .join('\n');
148
+ const padRight = i < widths.length - 1 && p.gap > 0 ? ` style="padding-right: ${p.gap}px;"` : '';
149
+ return `<td valign="${p.verticalAlignment}" width="${Math.round((width / 100) * contentWidth)}"${padRight}>
150
+ <table cellspacing="0" cellpadding="0" border="0" width="100%">
151
+ ${colBlocks}
152
+ </table>
153
+ </td>`;
154
+ })
155
+ .join('\n');
156
+ return `<tr>
157
+ <td style="padding: ${pad(p.padding)};${bg}">
158
+ <table cellspacing="0" cellpadding="0" border="0" width="100%">
159
+ <tr>
160
+ ${cells}
161
+ </tr>
162
+ </table>
163
+ </td>
164
+ </tr>`;
165
+ }
166
+ function renderSocial(p) {
167
+ const visible = p.links.filter((l) => Boolean(l.url));
168
+ if (visible.length === 0)
169
+ return '';
170
+ const cells = visible
171
+ .map((link, i) => `<td${i < visible.length - 1 ? ` style="padding-right: ${p.spacing}px;"` : ''}>
172
+ <a href="${esc(link.url)}" style="text-decoration: none;">
173
+ ${link.iconUrl
174
+ ? `<img src="${esc(link.iconUrl)}" alt="${esc(link.platform)}" width="${p.iconSize}" height="${p.iconSize}" border="0" style="display: block;">`
175
+ : `<span style="display: inline-block; padding: 4px 10px; background: #e5e7eb; color: #111827; font-size: 12px; border-radius: 999px;">${esc(link.platform)}</span>`}
176
+ </a>
177
+ </td>`)
178
+ .join('\n');
179
+ return `<tr>
180
+ <td style="padding: ${pad(p.padding)}; text-align: ${p.alignment};">
181
+ <table cellspacing="0" cellpadding="0" border="0" align="${p.alignment}">
182
+ <tr>${cells}</tr>
183
+ </table>
184
+ </td>
185
+ </tr>`;
186
+ }
187
+ function renderFooter(p) {
188
+ const lines = [];
189
+ if (p.companyName)
190
+ lines.push(esc(p.companyName));
191
+ if (p.address)
192
+ lines.push(esc(p.address));
193
+ if (p.customText)
194
+ lines.push(esc(p.customText));
195
+ return `<tr>
196
+ <td style="padding: ${pad(p.padding)}; text-align: ${p.alignment}; color: ${esc(p.textColor)}; font-size: ${p.fontSize}px; line-height: 1.5;">
197
+ ${lines.join('<br>\n ')}
198
+ ${p.unsubscribeText ? `<br><a href="{{unsubscribeUrl}}" style="color: ${esc(p.textColor)}; text-decoration: underline;">${esc(p.unsubscribeText)}</a>` : ''}
199
+ </td>
200
+ </tr>`;
201
+ }
202
+ // ============================================
203
+ // Helpers
204
+ // ============================================
205
+ function esc(str) {
206
+ return String(str)
207
+ .replace(/&/g, '&amp;')
208
+ .replace(/</g, '&lt;')
209
+ .replace(/>/g, '&gt;')
210
+ .replace(/"/g, '&quot;');
211
+ }
212
+ function pad(p) {
213
+ return `${p.top}px ${p.right}px ${p.bottom}px ${p.left}px`;
214
+ }
215
+ function wrapAligned(content, alignment) {
216
+ return `<table cellspacing="0" cellpadding="0" border="0" align="${alignment}">
217
+ <tr><td>${content}</td></tr>
218
+ </table>`;
219
+ }
@@ -0,0 +1,3 @@
1
+ import type { EmailDocument } from './types.js';
2
+ /** A simple non-empty starter document used by stories and the empty-state CTA. */
3
+ export declare function createStarterDocument(): EmailDocument;
@@ -0,0 +1,21 @@
1
+ import { createBlock } from './block-registry.js';
2
+ /** A simple non-empty starter document used by stories and the empty-state CTA. */
3
+ export function createStarterDocument() {
4
+ return {
5
+ blocks: [
6
+ createBlock('heading', { text: 'Welcome to your email' }),
7
+ createBlock('text', {
8
+ content: 'Use the palette on the left to add blocks. Click a block to edit its properties.',
9
+ }),
10
+ createBlock('button', { text: 'Get started', url: 'https://example.com' }),
11
+ createBlock('divider'),
12
+ createBlock('footer'),
13
+ ],
14
+ envelope: {
15
+ subject: 'Hello from Meda',
16
+ fromName: 'Meda',
17
+ fromEmail: 'hello@example.com',
18
+ preheader: 'A first look at the Meda email builder.',
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,182 @@
1
+ import type { ReactNode } from 'react';
2
+ export declare const BLOCK_KINDS: readonly ["heading", "text", "image", "button", "divider", "spacer", "columns", "social", "footer"];
3
+ export type BlockKind = (typeof BLOCK_KINDS)[number];
4
+ export type Alignment = 'left' | 'center' | 'right';
5
+ export interface SpacingValue {
6
+ top: number;
7
+ right: number;
8
+ bottom: number;
9
+ left: number;
10
+ }
11
+ export interface HeadingBlockProps {
12
+ text: string;
13
+ level: 1 | 2 | 3;
14
+ alignment: Alignment;
15
+ color: string;
16
+ fontFamily: string;
17
+ fontWeight: 400 | 500 | 600 | 700;
18
+ fontSize: number;
19
+ padding: SpacingValue;
20
+ }
21
+ export interface TextBlockProps {
22
+ /**
23
+ * Plain or simple HTML text. The reference Lexical editor was deferred — the
24
+ * default property editor renders a `<textarea>`; consumers can swap a richer
25
+ * editor in via the `renderTextEditor` slot in a future iteration.
26
+ */
27
+ content: string;
28
+ alignment: Alignment;
29
+ color: string;
30
+ fontFamily: string;
31
+ fontWeight: 300 | 400 | 500 | 600 | 700;
32
+ fontSize: number;
33
+ padding: SpacingValue;
34
+ }
35
+ export interface ImageBlockProps {
36
+ src: string;
37
+ alt: string;
38
+ linkUrl: string;
39
+ width: number;
40
+ height: number | 'auto';
41
+ alignment: Alignment;
42
+ padding: SpacingValue;
43
+ }
44
+ export interface ButtonBlockProps {
45
+ text: string;
46
+ url: string;
47
+ backgroundColor: string;
48
+ textColor: string;
49
+ borderRadius: number;
50
+ size: 'sm' | 'md' | 'lg';
51
+ alignment: Alignment;
52
+ fullWidth: boolean;
53
+ padding: SpacingValue;
54
+ }
55
+ export interface DividerBlockProps {
56
+ color: string;
57
+ thickness: number;
58
+ width: number;
59
+ style: 'solid' | 'dashed' | 'dotted';
60
+ padding: SpacingValue;
61
+ }
62
+ export interface SpacerBlockProps {
63
+ height: number;
64
+ }
65
+ export type ColumnLayout = '50-50' | '33-67' | '67-33' | '33-33-33';
66
+ export interface ColumnsBlockProps {
67
+ layout: ColumnLayout;
68
+ verticalAlignment: 'top' | 'middle' | 'bottom';
69
+ backgroundColor: string;
70
+ gap: number;
71
+ mobileStacking: boolean;
72
+ padding: SpacingValue;
73
+ }
74
+ export interface SocialLink {
75
+ platform: string;
76
+ url: string;
77
+ iconUrl?: string;
78
+ }
79
+ export interface SocialBlockProps {
80
+ links: SocialLink[];
81
+ iconSize: number;
82
+ spacing: number;
83
+ alignment: Alignment;
84
+ padding: SpacingValue;
85
+ }
86
+ export interface FooterBlockProps {
87
+ companyName: string;
88
+ address: string;
89
+ customText: string;
90
+ unsubscribeText: string;
91
+ textColor: string;
92
+ fontSize: number;
93
+ alignment: Alignment;
94
+ padding: SpacingValue;
95
+ }
96
+ export interface BlockPropsMap {
97
+ heading: HeadingBlockProps;
98
+ text: TextBlockProps;
99
+ image: ImageBlockProps;
100
+ button: ButtonBlockProps;
101
+ divider: DividerBlockProps;
102
+ spacer: SpacerBlockProps;
103
+ columns: ColumnsBlockProps;
104
+ social: SocialBlockProps;
105
+ footer: FooterBlockProps;
106
+ }
107
+ export interface EmailBlock<K extends BlockKind = BlockKind> {
108
+ id: string;
109
+ kind: K;
110
+ props: BlockPropsMap[K];
111
+ /** Only used by `kind: 'columns'` — one entry per column. */
112
+ children?: EmailBlock[][];
113
+ }
114
+ export interface EmailEnvelope {
115
+ subject?: string;
116
+ fromName?: string;
117
+ fromEmail?: string;
118
+ replyTo?: string;
119
+ preheader?: string;
120
+ }
121
+ export interface EmailDocument {
122
+ blocks: EmailBlock[];
123
+ envelope?: EmailEnvelope;
124
+ }
125
+ export interface EmailBrand {
126
+ logoUrl?: string;
127
+ brandColor?: string;
128
+ accentColor?: string;
129
+ fontHeading?: string;
130
+ fontBody?: string;
131
+ }
132
+ export interface EmailBuilderLabels {
133
+ addBlock: string;
134
+ blockPalette: string;
135
+ blocksTab: string;
136
+ designTab: string;
137
+ envelopeTab: string;
138
+ rightRailEmpty: string;
139
+ openBlocks: string;
140
+ openInspector: string;
141
+ openSettings: string;
142
+ preview: string;
143
+ desktopPreview: string;
144
+ mobilePreview: string;
145
+ exportHtml: string;
146
+ emptyTitle: string;
147
+ emptyDescription: string;
148
+ inspectorEmpty: string;
149
+ duplicateBlock: string;
150
+ deleteBlock: string;
151
+ moveUp: string;
152
+ moveDown: string;
153
+ }
154
+ export declare const defaultEmailBuilderLabels: EmailBuilderLabels;
155
+ export type DevicePreview = 'desktop' | 'mobile';
156
+ export interface MediaPickerContext {
157
+ onPick: (url: string) => void;
158
+ onCancel: () => void;
159
+ }
160
+ export interface SavedBlocksContext {
161
+ onPick: (block: EmailBlock) => void;
162
+ }
163
+ export interface EmailBuilderProps {
164
+ document: EmailDocument;
165
+ onDocumentChange: (next: EmailDocument) => void;
166
+ brand?: EmailBrand;
167
+ /** Slot for picking media (image picker, etc.). Receives a callback to commit. */
168
+ renderMediaPicker?: (ctx: MediaPickerContext) => ReactNode;
169
+ /** Slot for the saved-blocks library. */
170
+ renderSavedBlocks?: (ctx: SavedBlocksContext) => ReactNode;
171
+ /** Slot for a richer text editor (e.g. Lexical). Defaults to a `<textarea>`. */
172
+ renderTextEditor?: (ctx: {
173
+ value: string;
174
+ onChange: (next: string) => void;
175
+ }) => ReactNode;
176
+ /** Override the export-HTML button handler. Receives the rendered HTML string. */
177
+ onExportHtml?: (html: string) => void;
178
+ labels?: Partial<EmailBuilderLabels>;
179
+ className?: string;
180
+ /** Initial device preview. Defaults to `desktop`. */
181
+ initialDevice?: DevicePreview;
182
+ }