@startsimpli/ui 0.4.5 → 0.4.7

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 (69) hide show
  1. package/package.json +2 -1
  2. package/src/components/ActivityTimeline.tsx +173 -0
  3. package/src/components/LogActivityDialog.tsx +303 -0
  4. package/src/components/QuickLogButtons.tsx +32 -0
  5. package/src/components/badge/StageBadge.tsx +31 -0
  6. package/src/components/badge/index.ts +3 -0
  7. package/src/components/command-palette/CommandPalette.tsx +344 -0
  8. package/src/components/command-palette/command-palette-context.tsx +51 -0
  9. package/src/components/command-palette/index.ts +3 -0
  10. package/src/components/compose/compose-header.tsx +72 -0
  11. package/src/components/compose/compose-loading.tsx +13 -0
  12. package/src/components/compose/index.ts +6 -0
  13. package/src/components/compose/save-status-indicator.tsx +57 -0
  14. package/src/components/compose/send-confirmation-dialog.tsx +87 -0
  15. package/src/components/compose/subject-input.tsx +25 -0
  16. package/src/components/compose/useAutoSave.ts +93 -0
  17. package/src/components/dashboard/DashboardGrid.tsx +32 -0
  18. package/src/components/dashboard/DashboardSection.tsx +32 -0
  19. package/src/components/dashboard/MetricCard.tsx +129 -0
  20. package/src/components/dashboard/PeriodSelector.tsx +55 -0
  21. package/src/components/dashboard/SparklineTrend.tsx +102 -0
  22. package/src/components/dashboard/index.ts +14 -0
  23. package/src/components/email-dialogs/index.ts +14 -0
  24. package/src/components/email-dialogs/merge-fields.tsx +196 -0
  25. package/src/components/email-dialogs/preview-dialog.tsx +194 -0
  26. package/src/components/email-dialogs/schedule-dialog.tsx +297 -0
  27. package/src/components/email-dialogs/template-picker.tsx +225 -0
  28. package/src/components/email-dialogs/test-send-dialog.tsx +188 -0
  29. package/src/components/email-editor/add-block-menu.tsx +151 -0
  30. package/src/components/email-editor/block-toolbar.tsx +73 -0
  31. package/src/components/email-editor/blocks/button-block.tsx +44 -0
  32. package/src/components/email-editor/blocks/divider-block.tsx +43 -0
  33. package/src/components/email-editor/blocks/footer-block.tsx +39 -0
  34. package/src/components/email-editor/blocks/header-block.tsx +39 -0
  35. package/src/components/email-editor/blocks/image-block.tsx +61 -0
  36. package/src/components/email-editor/blocks/index.ts +9 -0
  37. package/src/components/email-editor/blocks/metrics-block.tsx +198 -0
  38. package/src/components/email-editor/blocks/social-block.tsx +75 -0
  39. package/src/components/email-editor/blocks/spacer-block.tsx +26 -0
  40. package/src/components/email-editor/blocks/text-block.tsx +75 -0
  41. package/src/components/email-editor/editor-sidebar.tsx +791 -0
  42. package/src/components/email-editor/email-editor.tsx +886 -0
  43. package/src/components/email-editor/index.ts +50 -0
  44. package/src/components/email-editor/renderer/block-renderers.ts +209 -0
  45. package/src/components/email-editor/renderer/email-html-renderer.ts +128 -0
  46. package/src/components/email-editor/types.ts +413 -0
  47. package/src/components/email-editor/utils/defaults.ts +116 -0
  48. package/src/components/email-editor/utils/undo-redo.ts +59 -0
  49. package/src/components/enrichment/EnrichButton.tsx +33 -0
  50. package/src/components/enrichment/EnrichmentProgress.tsx +66 -0
  51. package/src/components/enrichment/QualityBadge.tsx +43 -0
  52. package/src/components/enrichment/index.ts +8 -0
  53. package/src/components/gantt/GanttChart.tsx +25 -25
  54. package/src/components/gantt/types.ts +5 -5
  55. package/src/components/index.ts +46 -0
  56. package/src/components/integrations/ConnectionStatus.tsx +77 -0
  57. package/src/components/integrations/IntegrationCard.tsx +92 -0
  58. package/src/components/integrations/index.ts +5 -0
  59. package/src/components/kanban/KanbanBoard.tsx +103 -0
  60. package/src/components/kanban/index.ts +2 -0
  61. package/src/components/lists/CreateListDialog.tsx +158 -0
  62. package/src/components/lists/ListCard.tsx +77 -0
  63. package/src/components/lists/index.ts +5 -0
  64. package/src/components/pipeline/StageTransitionModal.tsx +146 -0
  65. package/src/components/pipeline/index.ts +2 -0
  66. package/src/components/settings/SettingsCard.tsx +33 -0
  67. package/src/components/settings/SettingsLayout.tsx +28 -0
  68. package/src/components/settings/SettingsNav.tsx +42 -0
  69. package/src/components/settings/index.ts +6 -0
@@ -0,0 +1,50 @@
1
+ // Email Editor - barrel export
2
+ export { EmailEditor } from './email-editor'
3
+
4
+ // Types
5
+ export type {
6
+ Block,
7
+ BlockType,
8
+ TextBlock,
9
+ MetricsBlock,
10
+ MetricItem,
11
+ DividerBlock,
12
+ CTABlock,
13
+ ImageBlock,
14
+ SpacerBlock,
15
+ SocialBlock,
16
+ SocialLink,
17
+ HeaderBlock,
18
+ FooterBlock,
19
+ Section,
20
+ Row,
21
+ ColumnLayout,
22
+ GlobalStyles,
23
+ BlockStyle,
24
+ MergeFieldDefinition,
25
+ EditorSelection,
26
+ } from './types'
27
+
28
+ // Helpers
29
+ export {
30
+ createBlock,
31
+ createRow,
32
+ createSection,
33
+ getColumnCount,
34
+ getColumnWidths,
35
+ DEFAULT_GLOBAL_STYLES,
36
+ // Serialization
37
+ serializeSections,
38
+ deserializeSections,
39
+ serializeBlocks,
40
+ deserializeBlocks,
41
+ // Migration
42
+ migrateFromLegacy,
43
+ flattenToLegacy,
44
+ } from './types'
45
+
46
+ // Renderer
47
+ export { renderToEmailHtml, renderToPreviewHtml } from './renderer/email-html-renderer'
48
+
49
+ // Utilities
50
+ export { createInvestorUpdateTemplate, createEmptyTemplate, THEME_PRESETS } from './utils/defaults'
@@ -0,0 +1,209 @@
1
+ import {
2
+ Block,
3
+ TextBlock,
4
+ MetricsBlock,
5
+ DividerBlock,
6
+ CTABlock,
7
+ ImageBlock,
8
+ SpacerBlock,
9
+ SocialBlock,
10
+ HeaderBlock,
11
+ FooterBlock,
12
+ GlobalStyles,
13
+ } from '../types'
14
+
15
+ // Render a single block to email-safe HTML (table-based, inline CSS)
16
+ export function renderBlockToHtml(block: Block, globalStyles: GlobalStyles): string {
17
+ switch (block.type) {
18
+ case 'text':
19
+ return renderTextBlock(block, globalStyles)
20
+ case 'metrics':
21
+ return renderMetricsBlock(block)
22
+ case 'divider':
23
+ return renderDividerBlock(block)
24
+ case 'cta':
25
+ return renderButtonBlock(block)
26
+ case 'image':
27
+ return renderImageBlock(block)
28
+ case 'spacer':
29
+ return renderSpacerBlock(block)
30
+ case 'social':
31
+ return renderSocialBlock(block)
32
+ case 'header':
33
+ return renderHeaderBlock(block)
34
+ case 'footer':
35
+ return renderFooterBlock(block)
36
+ default:
37
+ return ''
38
+ }
39
+ }
40
+
41
+ function blockPaddingStyle(block: Block): string {
42
+ const s = block.style
43
+ if (!s) return ''
44
+ const parts: string[] = []
45
+ if (s.paddingTop) parts.push(`padding-top:${s.paddingTop}px`)
46
+ if (s.paddingBottom) parts.push(`padding-bottom:${s.paddingBottom}px`)
47
+ if (s.paddingLeft) parts.push(`padding-left:${s.paddingLeft}px`)
48
+ if (s.paddingRight) parts.push(`padding-right:${s.paddingRight}px`)
49
+ if (s.marginTop) parts.push(`margin-top:${s.marginTop}px`)
50
+ if (s.marginBottom) parts.push(`margin-bottom:${s.marginBottom}px`)
51
+ if (s.backgroundColor) parts.push(`background-color:${s.backgroundColor}`)
52
+ return parts.join(';')
53
+ }
54
+
55
+ function renderTextBlock(block: TextBlock, globalStyles: GlobalStyles): string {
56
+ const fontFamily = block.fontFamily || globalStyles.fontFamily
57
+ const fontSize = block.fontSize ? `${block.fontSize}px` : '16px'
58
+ const lineHeight = block.lineHeight ? `${block.lineHeight}` : '1.6'
59
+ const color = block.textColor || '#1f2937'
60
+ const extraStyle = blockPaddingStyle(block)
61
+
62
+ return `<div style="font-family:${fontFamily};font-size:${fontSize};line-height:${lineHeight};color:${color};${extraStyle}">${block.content}</div>`
63
+ }
64
+
65
+ function renderMetricsBlock(block: MetricsBlock): string {
66
+ const cols = block.columns || 2
67
+ const extraStyle = blockPaddingStyle(block)
68
+
69
+ const title = block.title
70
+ ? `<div style="text-align:center;margin-bottom:16px;font-size:18px;font-weight:600;${extraStyle}">${block.title}</div>`
71
+ : ''
72
+
73
+ const cells = block.metrics
74
+ .map((m) => {
75
+ const changeColor =
76
+ m.changeType === 'positive'
77
+ ? '#16a34a'
78
+ : m.changeType === 'negative'
79
+ ? '#dc2626'
80
+ : '#6b7280'
81
+ const changeHtml = m.change
82
+ ? `<div style="font-size:12px;color:${changeColor};margin-top:4px;">${m.change}</div>`
83
+ : ''
84
+ return `<td style="text-align:center;padding:16px;background:#f9fafb;border-radius:8px;width:${Math.floor(100 / cols)}%;">
85
+ <div style="font-size:24px;font-weight:bold;">${m.value}</div>
86
+ <div style="font-size:14px;color:#6b7280;margin-top:4px;">${m.label}</div>
87
+ ${changeHtml}
88
+ </td>`
89
+ })
90
+ .join('<td style="width:16px;"></td>')
91
+
92
+ return `${title}<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;margin:16px 0;"><tr>${cells}</tr></table>`
93
+ }
94
+
95
+ function renderDividerBlock(block: DividerBlock): string {
96
+ const extraStyle = blockPaddingStyle(block)
97
+ if (block.dividerStyle === 'space') {
98
+ return `<div style="height:32px;${extraStyle}"></div>`
99
+ }
100
+ const color = block.color || '#d1d5db'
101
+ const thickness = block.thickness || 1
102
+ const width = block.width || 100
103
+ return `<table width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;${extraStyle}"><tr><td align="center"><div style="width:${width}%;border-top:${thickness}px ${block.dividerStyle} ${color};"></div></td></tr></table>`
104
+ }
105
+
106
+ function renderButtonBlock(block: CTABlock): string {
107
+ const bg = block.buttonColor || '#2563eb'
108
+ const color = block.textColor || '#ffffff'
109
+ const radius = block.borderRadius ?? 6
110
+ const paddingH = block.paddingH ?? 24
111
+ const paddingV = block.paddingV ?? 12
112
+ const extraStyle = blockPaddingStyle(block)
113
+
114
+ const align = block.alignment || 'center'
115
+ return `<table width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;${extraStyle}"><tr><td align="${align}">
116
+ <!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" href="${block.url || '#'}" style="height:${paddingV * 2 + 20}px;v-text-anchor:middle;width:auto;" arcsize="${Math.round((radius / (paddingV * 2 + 20)) * 100)}%" strokecolor="${bg}" fillcolor="${bg}"><center style="color:${color};font-family:sans-serif;font-size:14px;font-weight:500;">${block.text}</center></v:roundrect><![endif]-->
117
+ <!--[if !mso]><!-->
118
+ <a href="${block.url || '#'}" style="display:inline-block;padding:${paddingV}px ${paddingH}px;background-color:${bg};color:${color};border-radius:${radius}px;text-decoration:none;font-weight:500;font-size:14px;font-family:sans-serif;">${block.text}</a>
119
+ <!--<![endif]-->
120
+ </td></tr></table>`
121
+ }
122
+
123
+ function renderImageBlock(block: ImageBlock): string {
124
+ if (!block.url) return ''
125
+ const width = block.width || 100
126
+ const align = block.alignment || 'center'
127
+ const extraStyle = blockPaddingStyle(block)
128
+ const captionHtml = block.caption
129
+ ? `<div style="font-size:14px;color:#6b7280;margin-top:8px;text-align:center;">${block.caption}</div>`
130
+ : ''
131
+
132
+ const imgTag = `<img src="${block.url}" alt="${block.alt || ''}" width="${Math.round(600 * (width / 100))}" style="display:block;max-width:100%;height:auto;border-radius:4px;" />`
133
+ const linked = block.linkUrl
134
+ ? `<a href="${block.linkUrl}" target="_blank">${imgTag}</a>`
135
+ : imgTag
136
+
137
+ return `<table width="100%" cellpadding="0" cellspacing="0" style="margin:8px 0;${extraStyle}"><tr><td align="${align}">${linked}${captionHtml}</td></tr></table>`
138
+ }
139
+
140
+ function renderSpacerBlock(block: SpacerBlock): string {
141
+ const height = block.height || 32
142
+ return `<div style="height:${height}px;line-height:${height}px;font-size:1px;">&nbsp;</div>`
143
+ }
144
+
145
+ function renderSocialBlock(block: SocialBlock): string {
146
+ const align = block.alignment || 'center'
147
+ const size = block.iconSize || 24
148
+ const extraStyle = blockPaddingStyle(block)
149
+
150
+ // Using text-based fallback icons for email compatibility
151
+ const platformLabels: Record<string, string> = {
152
+ linkedin: 'LinkedIn',
153
+ twitter: 'Twitter',
154
+ facebook: 'Facebook',
155
+ instagram: 'Instagram',
156
+ youtube: 'YouTube',
157
+ github: 'GitHub',
158
+ website: 'Website',
159
+ }
160
+ const platformColors: Record<string, string> = {
161
+ linkedin: '#0A66C2',
162
+ twitter: '#1DA1F2',
163
+ facebook: '#1877F2',
164
+ instagram: '#E4405F',
165
+ youtube: '#FF0000',
166
+ github: '#333333',
167
+ website: '#6b7280',
168
+ }
169
+
170
+ const links = block.links
171
+ .filter((l) => l.url)
172
+ .map((l) => {
173
+ const label = platformLabels[l.platform] || l.platform
174
+ const color = platformColors[l.platform] || '#6b7280'
175
+ return `<td style="padding:0 6px;"><a href="${l.url}" target="_blank" style="color:${color};text-decoration:none;font-size:${Math.max(12, size - 8)}px;font-weight:500;">${label}</a></td>`
176
+ })
177
+ .join('')
178
+
179
+ if (!links) return ''
180
+ return `<table width="100%" cellpadding="0" cellspacing="0" style="margin:16px 0;${extraStyle}"><tr><td align="${align}"><table cellpadding="0" cellspacing="0"><tr>${links}</tr></table></td></tr></table>`
181
+ }
182
+
183
+ function renderHeaderBlock(block: HeaderBlock): string {
184
+ const align = block.alignment || 'center'
185
+ const extraStyle = blockPaddingStyle(block)
186
+ const logo = block.logoUrl
187
+ ? `<img src="${block.logoUrl}" alt="${block.companyName}" style="height:40px;width:auto;margin-right:12px;vertical-align:middle;" />`
188
+ : ''
189
+ return `<table width="100%" cellpadding="0" cellspacing="0" style="margin:8px 0;${extraStyle}"><tr><td align="${align}" style="padding:16px 0;">
190
+ ${logo}<span style="font-size:20px;font-weight:600;vertical-align:middle;">${block.companyName}</span>
191
+ </td></tr></table>`
192
+ }
193
+
194
+ function renderFooterBlock(block: FooterBlock): string {
195
+ const align = block.alignment || 'center'
196
+ const extraStyle = blockPaddingStyle(block)
197
+ const addressHtml = block.address
198
+ ? `<div style="margin-top:4px;">${block.address}</div>`
199
+ : ''
200
+ const unsubHtml = block.showUnsubscribe
201
+ ? `<div style="margin-top:8px;"><a href="${block.unsubscribeUrl || '#'}" style="color:#6b7280;text-decoration:underline;">Unsubscribe</a> from these emails</div>`
202
+ : ''
203
+
204
+ return `<table width="100%" cellpadding="0" cellspacing="0" style="margin:8px 0;${extraStyle}"><tr><td align="${align}" style="font-size:12px;color:#6b7280;padding:16px 0;">
205
+ <div>${block.companyName}</div>
206
+ ${addressHtml}
207
+ ${unsubHtml}
208
+ </td></tr></table>`
209
+ }
@@ -0,0 +1,128 @@
1
+ import { Section, GlobalStyles, DEFAULT_GLOBAL_STYLES, getColumnWidths } from '../types'
2
+ import { renderBlockToHtml } from './block-renderers'
3
+
4
+ export interface RenderOptions {
5
+ subject?: string
6
+ preheaderText?: string
7
+ }
8
+
9
+ /**
10
+ * Converts sections to a full email-safe HTML document.
11
+ * Uses table-based layout with inline CSS for maximum email client compatibility.
12
+ */
13
+ export function renderToEmailHtml(
14
+ sections: Section[],
15
+ globalStyles: GlobalStyles = DEFAULT_GLOBAL_STYLES,
16
+ options: RenderOptions = {}
17
+ ): string {
18
+ const { subject = '', preheaderText = '' } = options
19
+ const contentWidth = globalStyles.contentWidth || 600
20
+ const bgColor = globalStyles.backgroundColor || '#f3f4f6'
21
+ const fontFamily = globalStyles.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
22
+
23
+ const sectionsHtml = sections.map((section) => renderSection(section, globalStyles, contentWidth)).join('')
24
+
25
+ const preheader = preheaderText
26
+ ? `<div style="display:none;font-size:1px;color:${bgColor};line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;">${preheaderText}</div>`
27
+ : ''
28
+
29
+ return `<!DOCTYPE html>
30
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
31
+ <head>
32
+ <meta charset="utf-8">
33
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
34
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
35
+ <meta name="x-apple-disable-message-reformatting">
36
+ <title>${subject}</title>
37
+ <!--[if mso]>
38
+ <noscript>
39
+ <xml>
40
+ <o:OfficeDocumentSettings>
41
+ <o:AllowPNG/>
42
+ <o:PixelsPerInch>96</o:PixelsPerInch>
43
+ </o:OfficeDocumentSettings>
44
+ </xml>
45
+ </noscript>
46
+ <![endif]-->
47
+ <style type="text/css">
48
+ body { margin:0; padding:0; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; }
49
+ table, td { border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; }
50
+ img { -ms-interpolation-mode:bicubic; border:0; height:auto; line-height:100%; outline:none; text-decoration:none; }
51
+ a { color:inherit; }
52
+ @media only screen and (max-width:620px) {
53
+ .email-container { width:100% !important; max-width:100% !important; }
54
+ .stack-column { display:block !important; width:100% !important; max-width:100% !important; }
55
+ .stack-column-center { text-align:center !important; }
56
+ }
57
+ </style>
58
+ </head>
59
+ <body style="margin:0;padding:0;background-color:${bgColor};font-family:${fontFamily};-webkit-font-smoothing:antialiased;">
60
+ ${preheader}
61
+ <!-- Email wrapper table -->
62
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color:${bgColor};">
63
+ <tr>
64
+ <td align="center" style="padding:24px 16px;">
65
+ <!-- Content container -->
66
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="${contentWidth}" class="email-container" style="max-width:${contentWidth}px;width:100%;">
67
+ ${sectionsHtml}
68
+ </table>
69
+ </td>
70
+ </tr>
71
+ </table>
72
+ </body>
73
+ </html>`
74
+ }
75
+
76
+ function renderSection(section: Section, globalStyles: GlobalStyles, contentWidth: number): string {
77
+ const bgColor = section.backgroundColor || '#ffffff'
78
+ const pt = section.paddingTop ?? 16
79
+ const pb = section.paddingBottom ?? 16
80
+ const pl = section.paddingLeft ?? 0
81
+ const pr = section.paddingRight ?? 0
82
+
83
+ const rowsHtml = section.rows.map((row) => {
84
+ const widths = getColumnWidths(row.layout)
85
+ const colCount = widths.length
86
+
87
+ if (colCount === 1) {
88
+ // Single column: simple
89
+ const blocksHtml = row.columns[0]
90
+ ?.map((block) => renderBlockToHtml(block, globalStyles))
91
+ .join('') || ''
92
+ return `<tr><td style="padding:0;">${blocksHtml}</td></tr>`
93
+ }
94
+
95
+ // Multi-column: use nested table
96
+ const columnsHtml = widths
97
+ .map((widthPct, i) => {
98
+ const colBlocks = row.columns[i] || []
99
+ const blocksHtml = colBlocks
100
+ .map((block) => renderBlockToHtml(block, globalStyles))
101
+ .join('')
102
+ const pxWidth = Math.round((contentWidth - pl - pr) * (widthPct / 100))
103
+ return `<td class="stack-column" valign="top" width="${pxWidth}" style="width:${widthPct}%;padding:0 4px;vertical-align:top;">${blocksHtml}</td>`
104
+ })
105
+ .join('')
106
+
107
+ return `<tr><td style="padding:0;">
108
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"><tr>${columnsHtml}</tr></table>
109
+ </td></tr>`
110
+ }).join('')
111
+
112
+ return `<tr><td style="background-color:${bgColor};padding:${pt}px ${pr}px ${pb}px ${pl}px;border-radius:8px;">
113
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
114
+ ${rowsHtml}
115
+ </table>
116
+ </td></tr>
117
+ <tr><td style="height:4px;font-size:1px;line-height:1px;">&nbsp;</td></tr>`
118
+ }
119
+
120
+ /**
121
+ * Render sections to a simple HTML string (for preview iframe, not for sending).
122
+ */
123
+ export function renderToPreviewHtml(
124
+ sections: Section[],
125
+ globalStyles: GlobalStyles = DEFAULT_GLOBAL_STYLES
126
+ ): string {
127
+ return renderToEmailHtml(sections, globalStyles)
128
+ }