@startsimpli/ui 0.4.7 → 0.4.9

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 (62) hide show
  1. package/package.json +21 -23
  2. package/src/__mocks__/next/link.js +11 -0
  3. package/src/components/account/__tests__/account.test.tsx +315 -0
  4. package/src/components/command-palette/CommandGroup.tsx +23 -0
  5. package/src/components/command-palette/CommandPalette.tsx +183 -200
  6. package/src/components/command-palette/CommandResultItem.tsx +59 -0
  7. package/src/components/command-palette/__tests__/CommandGroup.test.tsx +81 -0
  8. package/src/components/command-palette/__tests__/CommandResultItem.test.tsx +166 -0
  9. package/src/components/command-palette/__tests__/command-palette-context.test.tsx +166 -0
  10. package/src/components/command-palette/__tests__/useCommandPaletteSearch.test.ts +271 -0
  11. package/src/components/command-palette/index.ts +6 -0
  12. package/src/components/command-palette/useCommandPaletteSearch.ts +114 -0
  13. package/src/components/compose/__tests__/compose.test.tsx +656 -0
  14. package/src/components/dashboard/PipelineFunnel.tsx +126 -0
  15. package/src/components/dashboard/TopCampaigns.tsx +132 -0
  16. package/src/components/dashboard/__tests__/dashboard.test.tsx +785 -0
  17. package/src/components/dashboard/index.ts +6 -0
  18. package/src/components/dialog/ConfirmDialog.tsx +72 -0
  19. package/src/components/dialog/__tests__/ConfirmDialog.test.tsx +126 -0
  20. package/src/components/dialog/index.ts +3 -0
  21. package/src/components/email-dialogs/__tests__/email-dialogs.test.tsx +982 -0
  22. package/src/components/email-editor/BlockRenderer.tsx +120 -0
  23. package/src/components/email-editor/__tests__/BlockRenderer.test.tsx +332 -0
  24. package/src/components/email-editor/__tests__/block-renderers.test.ts +624 -0
  25. package/src/components/email-editor/__tests__/email-html-renderer.test.ts +376 -0
  26. package/src/components/email-editor/blocks/__tests__/blocks.test.tsx +818 -0
  27. package/src/components/email-editor/editor-sidebar.tsx +6 -731
  28. package/src/components/email-editor/email-editor.tsx +78 -467
  29. package/src/components/email-editor/hooks/__tests__/useDragDrop.test.ts +355 -0
  30. package/src/components/email-editor/hooks/__tests__/useEmailEditorState.test.ts +551 -0
  31. package/src/components/email-editor/hooks/useDragDrop.ts +181 -0
  32. package/src/components/email-editor/hooks/useEmailEditorState.ts +426 -0
  33. package/src/components/email-editor/index.ts +1 -0
  34. package/src/components/email-editor/panels/BlockPropertyPanel.tsx +637 -0
  35. package/src/components/email-editor/panels/GlobalStylesPanel.tsx +108 -0
  36. package/src/components/email-editor/panels/SectionSettingsPanel.tsx +80 -0
  37. package/src/components/email-editor/panels/__tests__/BlockPropertyPanel.test.tsx +707 -0
  38. package/src/components/email-editor/panels/__tests__/GlobalStylesPanel.test.tsx +226 -0
  39. package/src/components/email-editor/panels/index.ts +3 -0
  40. package/src/components/enrichment/__tests__/enrichment.test.tsx +184 -0
  41. package/src/components/gantt/GanttBoardView.tsx +71 -0
  42. package/src/components/gantt/GanttChart.tsx +134 -881
  43. package/src/components/gantt/GanttFilterBar.tsx +100 -0
  44. package/src/components/gantt/GanttListView.tsx +63 -0
  45. package/src/components/gantt/GanttTimelineView.tsx +215 -0
  46. package/src/components/gantt/__tests__/GanttBoardView.test.tsx +305 -0
  47. package/src/components/gantt/__tests__/GanttFilterBar.test.tsx +544 -0
  48. package/src/components/gantt/__tests__/GanttListView.test.tsx +337 -0
  49. package/src/components/gantt/__tests__/GanttTimelineView.test.tsx +375 -0
  50. package/src/components/gantt/__tests__/gantt-utils.test.ts +341 -0
  51. package/src/components/gantt/__tests__/useGanttState.test.ts +535 -0
  52. package/src/components/gantt/hooks/useGanttState.ts +644 -0
  53. package/src/components/gantt/index.ts +10 -0
  54. package/src/components/integrations/__tests__/integrations.test.tsx +191 -0
  55. package/src/components/kanban/__tests__/kanban.test.tsx +157 -0
  56. package/src/components/lists/__tests__/lists.test.tsx +263 -0
  57. package/src/components/loading/__tests__/loading.test.tsx +114 -0
  58. package/src/components/navigation/__tests__/navigation.test.tsx +194 -0
  59. package/src/components/pipeline/__tests__/pipeline.test.tsx +169 -0
  60. package/src/components/safe-html.tsx +9 -8
  61. package/src/components/settings/__tests__/settings.test.tsx +181 -0
  62. package/src/components/wizard/__tests__/wizard.test.tsx +97 -0
@@ -0,0 +1,126 @@
1
+ import { Card } from '../ui/card';
2
+ import { TrendingDown } from 'lucide-react';
3
+
4
+ export interface FunnelStage {
5
+ stage: string;
6
+ count: number;
7
+ color?: string;
8
+ }
9
+
10
+ export interface PipelineFunnelProps {
11
+ stages: FunnelStage[];
12
+ title?: string;
13
+ onStageClick?: (stage: string) => void;
14
+ }
15
+
16
+ export function PipelineFunnel({
17
+ stages,
18
+ title = 'Pipeline Funnel',
19
+ onStageClick,
20
+ }: PipelineFunnelProps) {
21
+ if (!stages || stages.length === 0) {
22
+ return (
23
+ <Card className="p-6">
24
+ <h2 className="text-xl font-bold text-gray-900 mb-6">{title}</h2>
25
+ <div className="text-center py-8 text-gray-500">
26
+ <TrendingDown className="w-12 h-12 mx-auto mb-3 opacity-50" />
27
+ <p className="text-sm">No pipeline data available</p>
28
+ </div>
29
+ </Card>
30
+ );
31
+ }
32
+
33
+ const maxCount = Math.max(...stages.map(s => s.count));
34
+
35
+ return (
36
+ <Card className="p-6">
37
+ <h2 className="text-xl font-bold text-gray-900 mb-6">{title}</h2>
38
+
39
+ <div className="space-y-4" role="list" aria-label="Pipeline stages">
40
+ {stages.map((stage, index) => {
41
+ const percentage = maxCount > 0 ? (stage.count / maxCount) * 100 : 0;
42
+ const stagePercentage = stages[0]?.count > 0
43
+ ? Math.round((stage.count / stages[0].count) * 100)
44
+ : 0;
45
+
46
+ const defaultColors = [
47
+ 'from-accent-400 to-accent-500',
48
+ 'from-primary-400 to-primary-500',
49
+ 'from-primary-500 to-primary-600',
50
+ 'from-primary-600 to-primary-700',
51
+ ];
52
+
53
+ const gradientColor = stage.color || defaultColors[index % defaultColors.length];
54
+
55
+ const nextStage = stages[index + 1];
56
+ const conversionRate = nextStage
57
+ ? Math.round((nextStage.count / stage.count) * 100)
58
+ : null;
59
+
60
+ return (
61
+ <div
62
+ key={stage.stage}
63
+ role="listitem"
64
+ className="group"
65
+ onClick={() => onStageClick?.(stage.stage)}
66
+ >
67
+ <div className="flex items-center justify-between mb-2">
68
+ <span className="text-sm font-medium text-gray-700">
69
+ {stage.stage}
70
+ </span>
71
+ <div className="flex items-center gap-3">
72
+ <span className="text-xs text-gray-500">
73
+ {stagePercentage}% of total
74
+ </span>
75
+ <span className="text-sm font-bold text-gray-900">
76
+ {stage.count.toLocaleString()}
77
+ </span>
78
+ </div>
79
+ </div>
80
+
81
+ <div className="relative h-8 bg-gray-100 rounded-md overflow-hidden">
82
+ <button
83
+ className={`
84
+ absolute inset-y-0 left-0 bg-gradient-to-r ${gradientColor}
85
+ transition-all duration-300 group-hover:opacity-90
86
+ ${onStageClick ? 'cursor-pointer' : 'cursor-default'}
87
+ focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2
88
+ `}
89
+ style={{ width: `${percentage}%` }}
90
+ aria-label={`${stage.stage}: ${stage.count} prospects, ${stagePercentage}% of total`}
91
+ disabled={!onStageClick}
92
+ >
93
+ <span className="sr-only">
94
+ {stage.stage}: {stage.count} prospects
95
+ </span>
96
+ </button>
97
+
98
+ {conversionRate !== null && (
99
+ <div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
100
+ <span className="text-xs font-medium text-white bg-gray-900 bg-opacity-75 px-2 py-1 rounded">
101
+ {conversionRate}% convert to next stage
102
+ </span>
103
+ </div>
104
+ )}
105
+ </div>
106
+ </div>
107
+ );
108
+ })}
109
+ </div>
110
+
111
+ {stages.length >= 2 && (
112
+ <div className="mt-6 pt-6 border-t border-gray-200">
113
+ <div className="flex items-center justify-between text-sm">
114
+ <span className="text-gray-600">Overall Conversion</span>
115
+ <span className="font-bold text-gray-900">
116
+ {Math.round((stages[stages.length - 1].count / stages[0].count) * 100)}%
117
+ <span className="text-xs text-gray-500 ml-1">
118
+ ({stages[0].stage} → {stages[stages.length - 1].stage})
119
+ </span>
120
+ </span>
121
+ </div>
122
+ </div>
123
+ )}
124
+ </Card>
125
+ );
126
+ }
@@ -0,0 +1,132 @@
1
+ import { Card } from '../ui/card';
2
+ import { EmptyState } from '../states';
3
+ import { ArrowRight } from 'lucide-react';
4
+
5
+ export interface TopCampaign {
6
+ id: string;
7
+ name: string;
8
+ engagementRate: number;
9
+ status?: 'active' | 'paused' | 'completed';
10
+ }
11
+
12
+ export interface TopCampaignsProps {
13
+ campaigns: TopCampaign[];
14
+ title?: string;
15
+ maxItems?: number;
16
+ onViewAll?: () => void;
17
+ }
18
+
19
+ export function TopCampaigns({
20
+ campaigns,
21
+ title = 'Top Performing Campaigns',
22
+ maxItems = 5,
23
+ onViewAll,
24
+ }: TopCampaignsProps) {
25
+ const displayCampaigns = campaigns.slice(0, maxItems);
26
+
27
+ if (campaigns.length === 0) {
28
+ return (
29
+ <Card className="p-6">
30
+ <h2 className="text-xl font-bold text-gray-900 mb-6">{title}</h2>
31
+ <EmptyState
32
+ title="No campaigns yet"
33
+ description="Launch your first campaign to see performance metrics"
34
+ />
35
+ </Card>
36
+ );
37
+ }
38
+
39
+ return (
40
+ <Card className="p-6">
41
+ <div className="flex items-center justify-between mb-6">
42
+ <h2 className="text-xl font-bold text-gray-900">{title}</h2>
43
+ {onViewAll && campaigns.length > maxItems && (
44
+ <button
45
+ onClick={onViewAll}
46
+ className="text-sm text-primary-600 hover:text-primary-700 font-medium flex items-center gap-1 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 rounded"
47
+ >
48
+ View all
49
+ <ArrowRight className="w-4 h-4" />
50
+ </button>
51
+ )}
52
+ </div>
53
+
54
+ <div className="space-y-4" role="list" aria-label="Top campaigns by engagement">
55
+ {displayCampaigns.map((campaign, index) => {
56
+ const getProgressColor = (rate: number) => {
57
+ if (rate >= 75) return 'bg-success-500';
58
+ if (rate >= 50) return 'bg-primary-500';
59
+ if (rate >= 25) return 'bg-warning-500';
60
+ return 'bg-gray-400';
61
+ };
62
+
63
+ const progressColor = getProgressColor(campaign.engagementRate);
64
+
65
+ return (
66
+ <div
67
+ key={campaign.id}
68
+ role="listitem"
69
+ className="group space-y-2"
70
+ >
71
+ <div className="flex items-start justify-between gap-3">
72
+ <div className="flex items-start gap-2 min-w-0 flex-1">
73
+ <span className={`
74
+ flex-shrink-0 flex items-center justify-center w-6 h-6 rounded-full text-xs font-bold
75
+ ${index === 0 ? 'bg-accent-100 text-accent-700 border border-accent-200' : 'bg-gray-100 text-gray-600'}
76
+ `}>
77
+ {index + 1}
78
+ </span>
79
+
80
+ <div className="min-w-0 flex-1">
81
+ <h3 className="text-sm font-medium text-gray-900 truncate group-hover:text-primary-600 transition-colors">
82
+ {campaign.name}
83
+ </h3>
84
+ {campaign.status && (
85
+ <span className={`
86
+ inline-block text-xs px-2 py-0.5 rounded-full mt-1 border
87
+ ${campaign.status === 'active' ? 'bg-primary-50 text-primary-700 border-primary-200' : ''}
88
+ ${campaign.status === 'paused' ? 'bg-warning-50 text-warning-700 border-warning-200' : ''}
89
+ ${campaign.status === 'completed' ? 'bg-accent-50 text-accent-700 border-accent-200' : ''}
90
+ `}>
91
+ {campaign.status}
92
+ </span>
93
+ )}
94
+ </div>
95
+ </div>
96
+
97
+ <span className="flex-shrink-0 text-sm font-bold text-gray-900">
98
+ {campaign.engagementRate}%
99
+ </span>
100
+ </div>
101
+
102
+ <div className="relative h-2 bg-gray-100 rounded-full overflow-hidden">
103
+ <div
104
+ className={`absolute inset-y-0 left-0 ${progressColor} transition-all duration-500 rounded-full`}
105
+ style={{ width: `${campaign.engagementRate}%` }}
106
+ role="progressbar"
107
+ aria-valuenow={campaign.engagementRate}
108
+ aria-valuemin={0}
109
+ aria-valuemax={100}
110
+ aria-label={`${campaign.name} engagement rate: ${campaign.engagementRate}%`}
111
+ />
112
+ </div>
113
+ </div>
114
+ );
115
+ })}
116
+ </div>
117
+
118
+ {campaigns.length > 0 && (
119
+ <div className="mt-6 pt-6 border-t border-gray-200">
120
+ <div className="flex items-center justify-between text-sm">
121
+ <span className="text-gray-600">Average Engagement</span>
122
+ <span className="font-bold text-gray-900">
123
+ {Math.round(
124
+ campaigns.reduce((sum, c) => sum + c.engagementRate, 0) / campaigns.length
125
+ )}%
126
+ </span>
127
+ </div>
128
+ </div>
129
+ )}
130
+ </Card>
131
+ );
132
+ }