@startsimpli/ui 0.4.6 → 0.4.8

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 (122) hide show
  1. package/package.json +2 -1
  2. package/src/__mocks__/next/link.js +11 -0
  3. package/src/components/ActivityTimeline.tsx +173 -0
  4. package/src/components/LogActivityDialog.tsx +303 -0
  5. package/src/components/QuickLogButtons.tsx +32 -0
  6. package/src/components/account/__tests__/account.test.tsx +315 -0
  7. package/src/components/badge/StageBadge.tsx +31 -0
  8. package/src/components/badge/index.ts +3 -0
  9. package/src/components/command-palette/CommandGroup.tsx +23 -0
  10. package/src/components/command-palette/CommandPalette.tsx +327 -0
  11. package/src/components/command-palette/CommandResultItem.tsx +59 -0
  12. package/src/components/command-palette/__tests__/CommandGroup.test.tsx +81 -0
  13. package/src/components/command-palette/__tests__/CommandResultItem.test.tsx +166 -0
  14. package/src/components/command-palette/__tests__/command-palette-context.test.tsx +166 -0
  15. package/src/components/command-palette/__tests__/useCommandPaletteSearch.test.ts +271 -0
  16. package/src/components/command-palette/command-palette-context.tsx +51 -0
  17. package/src/components/command-palette/index.ts +9 -0
  18. package/src/components/command-palette/useCommandPaletteSearch.ts +114 -0
  19. package/src/components/compose/__tests__/compose.test.tsx +656 -0
  20. package/src/components/compose/compose-header.tsx +72 -0
  21. package/src/components/compose/compose-loading.tsx +13 -0
  22. package/src/components/compose/index.ts +6 -0
  23. package/src/components/compose/save-status-indicator.tsx +57 -0
  24. package/src/components/compose/send-confirmation-dialog.tsx +87 -0
  25. package/src/components/compose/subject-input.tsx +25 -0
  26. package/src/components/compose/useAutoSave.ts +93 -0
  27. package/src/components/dashboard/DashboardGrid.tsx +32 -0
  28. package/src/components/dashboard/DashboardSection.tsx +32 -0
  29. package/src/components/dashboard/MetricCard.tsx +129 -0
  30. package/src/components/dashboard/PeriodSelector.tsx +55 -0
  31. package/src/components/dashboard/PipelineFunnel.tsx +126 -0
  32. package/src/components/dashboard/SparklineTrend.tsx +102 -0
  33. package/src/components/dashboard/TopCampaigns.tsx +132 -0
  34. package/src/components/dashboard/__tests__/dashboard.test.tsx +785 -0
  35. package/src/components/dashboard/index.ts +20 -0
  36. package/src/components/dialog/ConfirmDialog.tsx +72 -0
  37. package/src/components/dialog/__tests__/ConfirmDialog.test.tsx +126 -0
  38. package/src/components/dialog/index.ts +3 -0
  39. package/src/components/email-dialogs/__tests__/email-dialogs.test.tsx +982 -0
  40. package/src/components/email-dialogs/index.ts +14 -0
  41. package/src/components/email-dialogs/merge-fields.tsx +196 -0
  42. package/src/components/email-dialogs/preview-dialog.tsx +194 -0
  43. package/src/components/email-dialogs/schedule-dialog.tsx +297 -0
  44. package/src/components/email-dialogs/template-picker.tsx +225 -0
  45. package/src/components/email-dialogs/test-send-dialog.tsx +188 -0
  46. package/src/components/email-editor/BlockRenderer.tsx +120 -0
  47. package/src/components/email-editor/__tests__/BlockRenderer.test.tsx +332 -0
  48. package/src/components/email-editor/__tests__/block-renderers.test.ts +624 -0
  49. package/src/components/email-editor/__tests__/email-html-renderer.test.ts +376 -0
  50. package/src/components/email-editor/add-block-menu.tsx +151 -0
  51. package/src/components/email-editor/block-toolbar.tsx +73 -0
  52. package/src/components/email-editor/blocks/__tests__/blocks.test.tsx +818 -0
  53. package/src/components/email-editor/blocks/button-block.tsx +44 -0
  54. package/src/components/email-editor/blocks/divider-block.tsx +43 -0
  55. package/src/components/email-editor/blocks/footer-block.tsx +39 -0
  56. package/src/components/email-editor/blocks/header-block.tsx +39 -0
  57. package/src/components/email-editor/blocks/image-block.tsx +61 -0
  58. package/src/components/email-editor/blocks/index.ts +9 -0
  59. package/src/components/email-editor/blocks/metrics-block.tsx +198 -0
  60. package/src/components/email-editor/blocks/social-block.tsx +75 -0
  61. package/src/components/email-editor/blocks/spacer-block.tsx +26 -0
  62. package/src/components/email-editor/blocks/text-block.tsx +75 -0
  63. package/src/components/email-editor/editor-sidebar.tsx +66 -0
  64. package/src/components/email-editor/email-editor.tsx +497 -0
  65. package/src/components/email-editor/hooks/__tests__/useDragDrop.test.ts +355 -0
  66. package/src/components/email-editor/hooks/__tests__/useEmailEditorState.test.ts +551 -0
  67. package/src/components/email-editor/hooks/useDragDrop.ts +181 -0
  68. package/src/components/email-editor/hooks/useEmailEditorState.ts +426 -0
  69. package/src/components/email-editor/index.ts +51 -0
  70. package/src/components/email-editor/panels/BlockPropertyPanel.tsx +637 -0
  71. package/src/components/email-editor/panels/GlobalStylesPanel.tsx +108 -0
  72. package/src/components/email-editor/panels/SectionSettingsPanel.tsx +80 -0
  73. package/src/components/email-editor/panels/__tests__/BlockPropertyPanel.test.tsx +707 -0
  74. package/src/components/email-editor/panels/__tests__/GlobalStylesPanel.test.tsx +226 -0
  75. package/src/components/email-editor/panels/index.ts +3 -0
  76. package/src/components/email-editor/renderer/block-renderers.ts +209 -0
  77. package/src/components/email-editor/renderer/email-html-renderer.ts +128 -0
  78. package/src/components/email-editor/types.ts +413 -0
  79. package/src/components/email-editor/utils/defaults.ts +116 -0
  80. package/src/components/email-editor/utils/undo-redo.ts +59 -0
  81. package/src/components/enrichment/EnrichButton.tsx +33 -0
  82. package/src/components/enrichment/EnrichmentProgress.tsx +66 -0
  83. package/src/components/enrichment/QualityBadge.tsx +43 -0
  84. package/src/components/enrichment/__tests__/enrichment.test.tsx +184 -0
  85. package/src/components/enrichment/index.ts +8 -0
  86. package/src/components/gantt/GanttBoardView.tsx +71 -0
  87. package/src/components/gantt/GanttChart.tsx +140 -887
  88. package/src/components/gantt/GanttFilterBar.tsx +100 -0
  89. package/src/components/gantt/GanttListView.tsx +63 -0
  90. package/src/components/gantt/GanttTimelineView.tsx +215 -0
  91. package/src/components/gantt/__tests__/GanttBoardView.test.tsx +305 -0
  92. package/src/components/gantt/__tests__/GanttFilterBar.test.tsx +544 -0
  93. package/src/components/gantt/__tests__/GanttListView.test.tsx +337 -0
  94. package/src/components/gantt/__tests__/GanttTimelineView.test.tsx +375 -0
  95. package/src/components/gantt/__tests__/gantt-utils.test.ts +341 -0
  96. package/src/components/gantt/__tests__/useGanttState.test.ts +535 -0
  97. package/src/components/gantt/hooks/useGanttState.ts +644 -0
  98. package/src/components/gantt/index.ts +10 -0
  99. package/src/components/gantt/types.ts +5 -5
  100. package/src/components/index.ts +46 -0
  101. package/src/components/integrations/ConnectionStatus.tsx +77 -0
  102. package/src/components/integrations/IntegrationCard.tsx +92 -0
  103. package/src/components/integrations/__tests__/integrations.test.tsx +191 -0
  104. package/src/components/integrations/index.ts +5 -0
  105. package/src/components/kanban/KanbanBoard.tsx +103 -0
  106. package/src/components/kanban/__tests__/kanban.test.tsx +157 -0
  107. package/src/components/kanban/index.ts +2 -0
  108. package/src/components/lists/CreateListDialog.tsx +158 -0
  109. package/src/components/lists/ListCard.tsx +77 -0
  110. package/src/components/lists/__tests__/lists.test.tsx +263 -0
  111. package/src/components/lists/index.ts +5 -0
  112. package/src/components/loading/__tests__/loading.test.tsx +114 -0
  113. package/src/components/navigation/__tests__/navigation.test.tsx +194 -0
  114. package/src/components/pipeline/StageTransitionModal.tsx +146 -0
  115. package/src/components/pipeline/__tests__/pipeline.test.tsx +169 -0
  116. package/src/components/pipeline/index.ts +2 -0
  117. package/src/components/settings/SettingsCard.tsx +33 -0
  118. package/src/components/settings/SettingsLayout.tsx +28 -0
  119. package/src/components/settings/SettingsNav.tsx +42 -0
  120. package/src/components/settings/__tests__/settings.test.tsx +181 -0
  121. package/src/components/settings/index.ts +6 -0
  122. package/src/components/wizard/__tests__/wizard.test.tsx +97 -0
@@ -0,0 +1,355 @@
1
+ import { renderHook, act } from '@testing-library/react'
2
+ import { useDragDrop } from '../useDragDrop'
3
+ import { createSection, createRow, createBlock, Section } from '../../types'
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Helpers
7
+ // ---------------------------------------------------------------------------
8
+
9
+ /**
10
+ * Build a single-section, single-row, single-column state with the given
11
+ * number of text blocks.
12
+ */
13
+ function makeOneColSections(blockCount: number): Section[] {
14
+ const section = createSection()
15
+ section.rows[0].columns[0] = Array.from({ length: blockCount }, () => createBlock('text'))
16
+ return [section]
17
+ }
18
+
19
+ /**
20
+ * Build a single-section, single-row, two-column state.
21
+ * blocksPerCol controls how many text blocks each column starts with.
22
+ */
23
+ function makeTwoColSections(col0Count = 1, col1Count = 1): Section[] {
24
+ const section = createSection()
25
+ section.rows[0] = createRow('2')
26
+ section.rows[0].columns[0] = Array.from({ length: col0Count }, () => createBlock('text'))
27
+ section.rows[0].columns[1] = Array.from({ length: col1Count }, () => createBlock('text'))
28
+ return [section]
29
+ }
30
+
31
+ /** Create a minimal synthetic DragEvent mock with preventDefault() */
32
+ function fakeDragEvent(): React.DragEvent {
33
+ return { preventDefault: jest.fn() } as unknown as React.DragEvent
34
+ }
35
+
36
+ /** Default setup with shared mocks */
37
+ function setup(sections: Section[] = makeOneColSections(2)) {
38
+ const commitChange = jest.fn()
39
+ const setSelection = jest.fn()
40
+
41
+ const { result, rerender } = renderHook(
42
+ ({ secs }: { secs: Section[] }) =>
43
+ useDragDrop({ sections: secs, commitChange, setSelection }),
44
+ { initialProps: { secs: sections } },
45
+ )
46
+
47
+ return { result, commitChange, setSelection, rerender, sections }
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // dragStart
52
+ // ---------------------------------------------------------------------------
53
+
54
+ describe('useDragDrop — handleDragStart', () => {
55
+ it('sets dragState to the source coordinates', () => {
56
+ const { result } = setup()
57
+
58
+ act(() => {
59
+ result.current.handleDragStart(0, 0, 0, 1)
60
+ })
61
+
62
+ expect(result.current.dragState).toEqual({
63
+ sourceSection: 0,
64
+ sourceRow: 0,
65
+ sourceCol: 0,
66
+ sourceBlock: 1,
67
+ })
68
+ })
69
+
70
+ it('dragState is null before any drag starts', () => {
71
+ const { result } = setup()
72
+ expect(result.current.dragState).toBeNull()
73
+ })
74
+
75
+ it('overwriting dragState with a new dragStart replaces previous source', () => {
76
+ const { result } = setup(makeOneColSections(3))
77
+
78
+ act(() => { result.current.handleDragStart(0, 0, 0, 0) })
79
+ act(() => { result.current.handleDragStart(0, 0, 0, 2) })
80
+
81
+ expect(result.current.dragState?.sourceBlock).toBe(2)
82
+ })
83
+ })
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // dragOver
87
+ // ---------------------------------------------------------------------------
88
+
89
+ describe('useDragDrop — handleDragOver', () => {
90
+ it('sets dragOverTarget to the hovered position', () => {
91
+ const { result } = setup()
92
+ const evt = fakeDragEvent()
93
+
94
+ act(() => {
95
+ result.current.handleDragOver(evt, 0, 0, 0, 1)
96
+ })
97
+
98
+ expect(result.current.dragOverTarget).toEqual({
99
+ sectionIndex: 0,
100
+ rowIndex: 0,
101
+ colIndex: 0,
102
+ blockIndex: 1,
103
+ })
104
+ })
105
+
106
+ it('calls event.preventDefault', () => {
107
+ const { result } = setup()
108
+ const evt = fakeDragEvent()
109
+
110
+ act(() => {
111
+ result.current.handleDragOver(evt, 0, 0, 0, 0)
112
+ })
113
+
114
+ expect(evt.preventDefault).toHaveBeenCalled()
115
+ })
116
+
117
+ it('dragOverTarget is null before any dragOver', () => {
118
+ const { result } = setup()
119
+ expect(result.current.dragOverTarget).toBeNull()
120
+ })
121
+ })
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // dragEnd
125
+ // ---------------------------------------------------------------------------
126
+
127
+ describe('useDragDrop — handleDragEnd', () => {
128
+ it('resets dragState and dragOverTarget to null', () => {
129
+ const { result } = setup()
130
+ const evt = fakeDragEvent()
131
+
132
+ act(() => { result.current.handleDragStart(0, 0, 0, 0) })
133
+ act(() => { result.current.handleDragOver(evt, 0, 0, 0, 1) })
134
+ act(() => { result.current.handleDragEnd() })
135
+
136
+ expect(result.current.dragState).toBeNull()
137
+ expect(result.current.dragOverTarget).toBeNull()
138
+ })
139
+
140
+ it('handleDragEnd is safe to call without a prior drag', () => {
141
+ const { result } = setup()
142
+ expect(() => {
143
+ act(() => { result.current.handleDragEnd() })
144
+ }).not.toThrow()
145
+ })
146
+ })
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // drop — within the same column
150
+ // ---------------------------------------------------------------------------
151
+
152
+ describe('useDragDrop — handleDrop within same column', () => {
153
+ it('moves block forward (index 0 → 2) past a middle block in the same column', () => {
154
+ // With 3 blocks [A, B, C], drag A from index 0 to target index 2.
155
+ // After removing A the column is [B, C].
156
+ // adjustedTargetBlock = 2 - 1 = 1 (source 0 < target 2).
157
+ // Insert A at index 1 → [B, A, C].
158
+ const sections = makeOneColSections(3)
159
+ const col0 = sections[0].rows[0].columns[0]
160
+ const idA = col0[0].id
161
+ const idB = col0[1].id
162
+ const idC = col0[2].id
163
+
164
+ const { result, commitChange } = setup(sections)
165
+ const evt = fakeDragEvent()
166
+
167
+ act(() => { result.current.handleDragStart(0, 0, 0, 0) })
168
+ act(() => { result.current.handleDrop(evt, 0, 0, 0, 2) })
169
+
170
+ expect(commitChange).toHaveBeenCalledTimes(1)
171
+ const committed: Section[] = commitChange.mock.calls[0][0]
172
+ const col = committed[0].rows[0].columns[0]
173
+
174
+ expect(col[0].id).toBe(idB)
175
+ expect(col[1].id).toBe(idA)
176
+ expect(col[2].id).toBe(idC)
177
+ })
178
+
179
+ it('moves block backward (index 1 → 0) in the same column', () => {
180
+ const sections = makeOneColSections(2)
181
+ const firstId = sections[0].rows[0].columns[0][0].id
182
+ const secondId = sections[0].rows[0].columns[0][1].id
183
+
184
+ const { result, commitChange } = setup(sections)
185
+ const evt = fakeDragEvent()
186
+
187
+ act(() => { result.current.handleDragStart(0, 0, 0, 1) })
188
+ act(() => { result.current.handleDrop(evt, 0, 0, 0, 0) })
189
+
190
+ expect(commitChange).toHaveBeenCalledTimes(1)
191
+ const committed: Section[] = commitChange.mock.calls[0][0]
192
+ const col = committed[0].rows[0].columns[0]
193
+
194
+ expect(col[0].id).toBe(secondId)
195
+ expect(col[1].id).toBe(firstId)
196
+ })
197
+
198
+ it('calls event.preventDefault on drop', () => {
199
+ const { result } = setup(makeOneColSections(2))
200
+ const evt = fakeDragEvent()
201
+
202
+ act(() => { result.current.handleDragStart(0, 0, 0, 0) })
203
+ act(() => { result.current.handleDrop(evt, 0, 0, 0, 1) })
204
+
205
+ expect(evt.preventDefault).toHaveBeenCalled()
206
+ })
207
+
208
+ it('resets dragState and dragOverTarget after a successful drop', () => {
209
+ const { result } = setup(makeOneColSections(2))
210
+ const evt = fakeDragEvent()
211
+
212
+ act(() => { result.current.handleDragStart(0, 0, 0, 0) })
213
+ act(() => { result.current.handleDrop(evt, 0, 0, 0, 1) })
214
+
215
+ expect(result.current.dragState).toBeNull()
216
+ expect(result.current.dragOverTarget).toBeNull()
217
+ })
218
+
219
+ it('updates selection to the adjusted drop target position', () => {
220
+ // 3 blocks, drag index 0 to target 2 → adjustedTargetBlock = 1
221
+ const { result, setSelection } = setup(makeOneColSections(3))
222
+ const evt = fakeDragEvent()
223
+
224
+ act(() => { result.current.handleDragStart(0, 0, 0, 0) })
225
+ act(() => { result.current.handleDrop(evt, 0, 0, 0, 2) })
226
+
227
+ expect(setSelection).toHaveBeenCalledWith({
228
+ sectionIndex: 0,
229
+ rowIndex: 0,
230
+ columnIndex: 0,
231
+ blockIndex: 1, // adjusted: source (0) < target (2) so 2 - 1 = 1
232
+ })
233
+ })
234
+ })
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // drop — across columns
238
+ // ---------------------------------------------------------------------------
239
+
240
+ describe('useDragDrop — handleDrop across columns', () => {
241
+ it('moves block from col 0 to col 1', () => {
242
+ const sections = makeTwoColSections(1, 1)
243
+ const movedId = sections[0].rows[0].columns[0][0].id
244
+
245
+ const { result, commitChange } = setup(sections)
246
+ const evt = fakeDragEvent()
247
+
248
+ act(() => { result.current.handleDragStart(0, 0, 0, 0) })
249
+ act(() => { result.current.handleDrop(evt, 0, 0, 1, 0) })
250
+
251
+ const committed: Section[] = commitChange.mock.calls[0][0]
252
+ expect(committed[0].rows[0].columns[0]).toHaveLength(0) // source col empty
253
+ expect(committed[0].rows[0].columns[1]).toHaveLength(2)
254
+ expect(committed[0].rows[0].columns[1][0].id).toBe(movedId)
255
+ })
256
+
257
+ it('moves block from col 1 to col 0', () => {
258
+ const sections = makeTwoColSections(1, 1)
259
+ const movedId = sections[0].rows[0].columns[1][0].id
260
+
261
+ const { result, commitChange } = setup(sections)
262
+ const evt = fakeDragEvent()
263
+
264
+ act(() => { result.current.handleDragStart(0, 0, 1, 0) })
265
+ act(() => { result.current.handleDrop(evt, 0, 0, 0, 0) })
266
+
267
+ const committed: Section[] = commitChange.mock.calls[0][0]
268
+ expect(committed[0].rows[0].columns[1]).toHaveLength(0) // source col empty
269
+ expect(committed[0].rows[0].columns[0]).toHaveLength(2)
270
+ expect(committed[0].rows[0].columns[0][0].id).toBe(movedId)
271
+ })
272
+
273
+ it('does not adjust target index when moving between different columns', () => {
274
+ // Source col 0 index 0, target col 1 index 1: no adjustment expected
275
+ const sections = makeTwoColSections(1, 2)
276
+ const movedId = sections[0].rows[0].columns[0][0].id
277
+
278
+ const { result, commitChange } = setup(sections)
279
+ const evt = fakeDragEvent()
280
+
281
+ act(() => { result.current.handleDragStart(0, 0, 0, 0) })
282
+ act(() => { result.current.handleDrop(evt, 0, 0, 1, 1) })
283
+
284
+ const committed: Section[] = commitChange.mock.calls[0][0]
285
+ const col1 = committed[0].rows[0].columns[1]
286
+ // Target index 1 — block should land at position 1
287
+ expect(col1[1].id).toBe(movedId)
288
+ })
289
+ })
290
+
291
+ // ---------------------------------------------------------------------------
292
+ // drop — guard: no dragState
293
+ // ---------------------------------------------------------------------------
294
+
295
+ describe('useDragDrop — handleDrop guards', () => {
296
+ it('does nothing if dragState is null when drop fires', () => {
297
+ const { result, commitChange } = setup(makeOneColSections(2))
298
+ const evt = fakeDragEvent()
299
+
300
+ // Drop without a prior dragStart
301
+ act(() => {
302
+ result.current.handleDrop(evt, 0, 0, 0, 1)
303
+ })
304
+
305
+ expect(commitChange).not.toHaveBeenCalled()
306
+ })
307
+
308
+ it('does nothing if the source block is missing in sections', () => {
309
+ const sections = makeOneColSections(1)
310
+ const { result, commitChange } = setup(sections)
311
+ const evt = fakeDragEvent()
312
+
313
+ // Start drag at an index that doesn't exist
314
+ act(() => { result.current.handleDragStart(0, 0, 0, 99) })
315
+ act(() => { result.current.handleDrop(evt, 0, 0, 0, 0) })
316
+
317
+ expect(commitChange).not.toHaveBeenCalled()
318
+ })
319
+ })
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // setDragOverTarget
323
+ // ---------------------------------------------------------------------------
324
+
325
+ describe('useDragDrop — setDragOverTarget', () => {
326
+ it('is exposed and can be called directly to set the target', () => {
327
+ const { result } = setup()
328
+
329
+ act(() => {
330
+ result.current.setDragOverTarget({
331
+ sectionIndex: 0,
332
+ rowIndex: 0,
333
+ colIndex: 0,
334
+ blockIndex: 2,
335
+ })
336
+ })
337
+
338
+ expect(result.current.dragOverTarget).toEqual({
339
+ sectionIndex: 0,
340
+ rowIndex: 0,
341
+ colIndex: 0,
342
+ blockIndex: 2,
343
+ })
344
+ })
345
+
346
+ it('can be used to clear the target by setting null', () => {
347
+ const { result } = setup()
348
+ const evt = fakeDragEvent()
349
+
350
+ act(() => { result.current.handleDragOver(evt, 0, 0, 0, 0) })
351
+ act(() => { result.current.setDragOverTarget(null) })
352
+
353
+ expect(result.current.dragOverTarget).toBeNull()
354
+ })
355
+ })