@startsimpli/ui 0.4.6 → 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,158 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '../../lib/utils'
5
+
6
+ export interface CreateListDialogProps {
7
+ open: boolean
8
+ onClose: () => void
9
+ onSubmit: (data: { name: string; description: string; sourceType: string }) => void
10
+ isSubmitting?: boolean
11
+ sourceTypes?: Array<{ value: string; label: string }>
12
+ }
13
+
14
+ const defaultSourceTypes = [
15
+ { value: 'static', label: 'Static' },
16
+ { value: 'funnel', label: 'Funnel' },
17
+ { value: 'query', label: 'Query' },
18
+ ]
19
+
20
+ export function CreateListDialog({
21
+ open,
22
+ onClose,
23
+ onSubmit,
24
+ isSubmitting = false,
25
+ sourceTypes = defaultSourceTypes,
26
+ }: CreateListDialogProps) {
27
+ const [name, setName] = React.useState('')
28
+ const [description, setDescription] = React.useState('')
29
+ const [sourceType, setSourceType] = React.useState(sourceTypes[0]?.value ?? 'static')
30
+
31
+ React.useEffect(() => {
32
+ if (open) {
33
+ setName('')
34
+ setDescription('')
35
+ setSourceType(sourceTypes[0]?.value ?? 'static')
36
+ }
37
+ }, [open, sourceTypes])
38
+
39
+ const handleSubmit = (e: React.FormEvent) => {
40
+ e.preventDefault()
41
+ if (!name.trim()) return
42
+ onSubmit({ name: name.trim(), description: description.trim(), sourceType })
43
+ }
44
+
45
+ if (!open) return null
46
+
47
+ return (
48
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
49
+ {/* Backdrop */}
50
+ <div
51
+ className="fixed inset-0 bg-black/50"
52
+ onClick={isSubmitting ? undefined : onClose}
53
+ aria-hidden="true"
54
+ />
55
+
56
+ {/* Dialog */}
57
+ <div className="relative z-50 w-full max-w-md rounded-lg border bg-background p-6 shadow-lg">
58
+ <h2 className="text-lg font-semibold text-foreground">Create List</h2>
59
+
60
+ <form onSubmit={handleSubmit} className="mt-4 space-y-4">
61
+ {/* Name */}
62
+ <div className="space-y-1.5">
63
+ <label htmlFor="list-name" className="text-sm font-medium text-foreground">
64
+ Name <span className="text-red-500">*</span>
65
+ </label>
66
+ <input
67
+ id="list-name"
68
+ type="text"
69
+ required
70
+ value={name}
71
+ onChange={(e) => setName(e.target.value)}
72
+ placeholder="Enter list name"
73
+ disabled={isSubmitting}
74
+ className={cn(
75
+ 'w-full rounded-md border bg-background px-3 py-2 text-sm',
76
+ 'placeholder:text-muted-foreground',
77
+ 'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1',
78
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
79
+ )}
80
+ />
81
+ </div>
82
+
83
+ {/* Description */}
84
+ <div className="space-y-1.5">
85
+ <label htmlFor="list-description" className="text-sm font-medium text-foreground">
86
+ Description
87
+ </label>
88
+ <textarea
89
+ id="list-description"
90
+ value={description}
91
+ onChange={(e) => setDescription(e.target.value)}
92
+ placeholder="Optional description"
93
+ rows={3}
94
+ disabled={isSubmitting}
95
+ className={cn(
96
+ 'w-full rounded-md border bg-background px-3 py-2 text-sm resize-none',
97
+ 'placeholder:text-muted-foreground',
98
+ 'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1',
99
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
100
+ )}
101
+ />
102
+ </div>
103
+
104
+ {/* Source Type */}
105
+ <div className="space-y-1.5">
106
+ <label htmlFor="list-source-type" className="text-sm font-medium text-foreground">
107
+ Source Type
108
+ </label>
109
+ <select
110
+ id="list-source-type"
111
+ value={sourceType}
112
+ onChange={(e) => setSourceType(e.target.value)}
113
+ disabled={isSubmitting}
114
+ className={cn(
115
+ 'w-full rounded-md border bg-background px-3 py-2 text-sm',
116
+ 'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1',
117
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
118
+ )}
119
+ >
120
+ {sourceTypes.map((st) => (
121
+ <option key={st.value} value={st.value}>
122
+ {st.label}
123
+ </option>
124
+ ))}
125
+ </select>
126
+ </div>
127
+
128
+ {/* Actions */}
129
+ <div className="flex justify-end gap-2 pt-2">
130
+ <button
131
+ type="button"
132
+ onClick={onClose}
133
+ disabled={isSubmitting}
134
+ className={cn(
135
+ 'rounded-md border px-4 py-2 text-sm font-medium',
136
+ 'hover:bg-muted transition-colors',
137
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
138
+ )}
139
+ >
140
+ Cancel
141
+ </button>
142
+ <button
143
+ type="submit"
144
+ disabled={isSubmitting || !name.trim()}
145
+ className={cn(
146
+ 'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
147
+ 'hover:bg-primary/90 transition-colors',
148
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
149
+ )}
150
+ >
151
+ {isSubmitting ? 'Creating...' : 'Create'}
152
+ </button>
153
+ </div>
154
+ </form>
155
+ </div>
156
+ </div>
157
+ )
158
+ }
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '../../lib/utils'
5
+
6
+ export interface ListCardProps {
7
+ name: string
8
+ sourceType: string
9
+ memberCount: number
10
+ createdAt: string
11
+ onClick?: () => void
12
+ className?: string
13
+ }
14
+
15
+ const sourceTypeBadgeClasses: Record<string, string> = {
16
+ static: 'bg-blue-100 text-blue-700 border-blue-200',
17
+ funnel: 'bg-purple-100 text-purple-700 border-purple-200',
18
+ query: 'bg-green-100 text-green-700 border-green-200',
19
+ }
20
+
21
+ export function ListCard({
22
+ name,
23
+ sourceType,
24
+ memberCount,
25
+ createdAt,
26
+ onClick,
27
+ className,
28
+ }: ListCardProps) {
29
+ const badgeClass =
30
+ sourceTypeBadgeClasses[sourceType] ??
31
+ 'bg-gray-100 text-gray-700 border-gray-200'
32
+
33
+ return (
34
+ <div
35
+ role={onClick ? 'button' : undefined}
36
+ tabIndex={onClick ? 0 : undefined}
37
+ onClick={onClick}
38
+ onKeyDown={
39
+ onClick
40
+ ? (e) => {
41
+ if (e.key === 'Enter' || e.key === ' ') {
42
+ e.preventDefault()
43
+ onClick()
44
+ }
45
+ }
46
+ : undefined
47
+ }
48
+ className={cn(
49
+ 'rounded-lg border bg-card text-card-foreground p-4 transition-all duration-150',
50
+ onClick && 'cursor-pointer hover:shadow-md hover:border-primary/30',
51
+ className,
52
+ )}
53
+ >
54
+ <div className="flex items-start justify-between gap-3">
55
+ <div className="min-w-0 flex-1">
56
+ <h4 className="text-sm font-semibold text-foreground truncate">
57
+ {name}
58
+ </h4>
59
+ <p className="text-xs text-muted-foreground mt-1">
60
+ {memberCount.toLocaleString()} {memberCount === 1 ? 'member' : 'members'}
61
+ </p>
62
+ </div>
63
+ <span
64
+ className={cn(
65
+ 'inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium shrink-0',
66
+ badgeClass,
67
+ )}
68
+ >
69
+ {sourceType}
70
+ </span>
71
+ </div>
72
+ <p className="text-xs text-muted-foreground mt-2">
73
+ Created {createdAt}
74
+ </p>
75
+ </div>
76
+ )
77
+ }
@@ -0,0 +1,5 @@
1
+ export { ListCard } from './ListCard'
2
+ export type { ListCardProps } from './ListCard'
3
+
4
+ export { CreateListDialog } from './CreateListDialog'
5
+ export type { CreateListDialogProps } from './CreateListDialog'
@@ -0,0 +1,146 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '../../lib/utils'
5
+
6
+ export interface StageTransitionModalProps {
7
+ open: boolean
8
+ onClose: () => void
9
+ onConfirm: (targetStage: string, notes?: string) => void
10
+ currentStage: string
11
+ stages: Array<{ id: string; label: string }>
12
+ entityName?: string
13
+ isSubmitting?: boolean
14
+ }
15
+
16
+ export function StageTransitionModal({
17
+ open,
18
+ onClose,
19
+ onConfirm,
20
+ currentStage,
21
+ stages,
22
+ entityName,
23
+ isSubmitting = false,
24
+ }: StageTransitionModalProps) {
25
+ const [targetStage, setTargetStage] = React.useState('')
26
+ const [notes, setNotes] = React.useState('')
27
+
28
+ React.useEffect(() => {
29
+ if (open) {
30
+ // Default to the first stage that isn't the current one
31
+ const firstOther = stages.find((s) => s.id !== currentStage)
32
+ setTargetStage(firstOther?.id ?? '')
33
+ setNotes('')
34
+ }
35
+ }, [open, currentStage, stages])
36
+
37
+ const handleConfirm = () => {
38
+ if (!targetStage) return
39
+ onConfirm(targetStage, notes.trim() || undefined)
40
+ }
41
+
42
+ const currentLabel = stages.find((s) => s.id === currentStage)?.label ?? currentStage
43
+
44
+ if (!open) return null
45
+
46
+ return (
47
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
48
+ {/* Backdrop */}
49
+ <div
50
+ className="fixed inset-0 bg-black/50"
51
+ onClick={isSubmitting ? undefined : onClose}
52
+ aria-hidden="true"
53
+ />
54
+
55
+ {/* Dialog */}
56
+ <div className="relative z-50 w-full max-w-md rounded-lg border bg-background p-6 shadow-lg">
57
+ <h2 className="text-lg font-semibold text-foreground">
58
+ Move {entityName ? `"${entityName}"` : 'Item'} to Stage
59
+ </h2>
60
+
61
+ <div className="mt-4 space-y-4">
62
+ {/* Current stage indicator */}
63
+ <div className="space-y-1.5">
64
+ <p className="text-sm font-medium text-foreground">Current Stage</p>
65
+ <p className="text-sm text-muted-foreground">{currentLabel}</p>
66
+ </div>
67
+
68
+ {/* Target stage select */}
69
+ <div className="space-y-1.5">
70
+ <label htmlFor="target-stage" className="text-sm font-medium text-foreground">
71
+ New Stage
72
+ </label>
73
+ <select
74
+ id="target-stage"
75
+ value={targetStage}
76
+ onChange={(e) => setTargetStage(e.target.value)}
77
+ disabled={isSubmitting}
78
+ className={cn(
79
+ 'w-full rounded-md border bg-background px-3 py-2 text-sm',
80
+ 'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1',
81
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
82
+ )}
83
+ >
84
+ {stages
85
+ .filter((s) => s.id !== currentStage)
86
+ .map((stage) => (
87
+ <option key={stage.id} value={stage.id}>
88
+ {stage.label}
89
+ </option>
90
+ ))}
91
+ </select>
92
+ </div>
93
+
94
+ {/* Notes */}
95
+ <div className="space-y-1.5">
96
+ <label htmlFor="transition-notes" className="text-sm font-medium text-foreground">
97
+ Notes <span className="text-muted-foreground">(optional)</span>
98
+ </label>
99
+ <textarea
100
+ id="transition-notes"
101
+ value={notes}
102
+ onChange={(e) => setNotes(e.target.value)}
103
+ placeholder="Add notes about this transition..."
104
+ rows={3}
105
+ disabled={isSubmitting}
106
+ className={cn(
107
+ 'w-full rounded-md border bg-background px-3 py-2 text-sm resize-none',
108
+ 'placeholder:text-muted-foreground',
109
+ 'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1',
110
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
111
+ )}
112
+ />
113
+ </div>
114
+
115
+ {/* Actions */}
116
+ <div className="flex justify-end gap-2 pt-2">
117
+ <button
118
+ type="button"
119
+ onClick={onClose}
120
+ disabled={isSubmitting}
121
+ className={cn(
122
+ 'rounded-md border px-4 py-2 text-sm font-medium',
123
+ 'hover:bg-muted transition-colors',
124
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
125
+ )}
126
+ >
127
+ Cancel
128
+ </button>
129
+ <button
130
+ type="button"
131
+ onClick={handleConfirm}
132
+ disabled={isSubmitting || !targetStage}
133
+ className={cn(
134
+ 'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
135
+ 'hover:bg-primary/90 transition-colors',
136
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
137
+ )}
138
+ >
139
+ {isSubmitting ? 'Moving...' : 'Confirm'}
140
+ </button>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ )
146
+ }
@@ -0,0 +1,2 @@
1
+ export { StageTransitionModal } from './StageTransitionModal'
2
+ export type { StageTransitionModalProps } from './StageTransitionModal'
@@ -0,0 +1,33 @@
1
+ import * as React from 'react'
2
+
3
+ export interface SettingsCardProps {
4
+ title: string
5
+ description?: string
6
+ icon?: React.ElementType
7
+ action?: React.ReactNode
8
+ children?: React.ReactNode
9
+ }
10
+
11
+ export function SettingsCard({ title, description, icon: Icon, action, children }: SettingsCardProps) {
12
+ return (
13
+ <div className="rounded-xl border bg-white text-gray-900 shadow-sm">
14
+ <div className="flex items-start justify-between p-6">
15
+ <div className="flex items-start gap-4">
16
+ {Icon && (
17
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100">
18
+ <Icon className="h-5 w-5 text-gray-600" />
19
+ </div>
20
+ )}
21
+ <div>
22
+ <h3 className="font-semibold leading-none tracking-tight">{title}</h3>
23
+ {description && (
24
+ <p className="text-sm text-gray-500 mt-1.5">{description}</p>
25
+ )}
26
+ </div>
27
+ </div>
28
+ {action && <div className="flex-shrink-0 ml-4">{action}</div>}
29
+ </div>
30
+ {children && <div className="px-6 pb-6 pt-0">{children}</div>}
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,28 @@
1
+ import * as React from 'react'
2
+
3
+ export interface SettingsNavItem {
4
+ id: string
5
+ label: string
6
+ href: string
7
+ icon?: React.ElementType
8
+ }
9
+
10
+ export interface SettingsLayoutProps {
11
+ children: React.ReactNode
12
+ title?: string
13
+ description?: string
14
+ }
15
+
16
+ export function SettingsLayout({ children, title, description }: SettingsLayoutProps) {
17
+ return (
18
+ <div className="space-y-6 max-w-4xl">
19
+ {title && (
20
+ <div>
21
+ <h1 className="text-3xl font-bold text-gray-900">{title}</h1>
22
+ {description && <p className="text-gray-600 mt-1">{description}</p>}
23
+ </div>
24
+ )}
25
+ {children}
26
+ </div>
27
+ )
28
+ }
@@ -0,0 +1,42 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+
5
+ export interface SettingsNavItem {
6
+ id: string
7
+ label: string
8
+ href: string
9
+ icon?: React.ElementType
10
+ }
11
+
12
+ export interface SettingsNavProps {
13
+ items: SettingsNavItem[]
14
+ activeId?: string
15
+ onNavigate: (href: string) => void
16
+ }
17
+
18
+ export function SettingsNav({ items, activeId, onNavigate }: SettingsNavProps) {
19
+ return (
20
+ <nav className="flex flex-col gap-1">
21
+ {items.map((item) => {
22
+ const isActive = item.id === activeId
23
+ const Icon = item.icon
24
+
25
+ return (
26
+ <button
27
+ key={item.id}
28
+ onClick={() => onNavigate(item.href)}
29
+ className={`flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors text-left ${
30
+ isActive
31
+ ? 'bg-gray-100 text-gray-900'
32
+ : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
33
+ }`}
34
+ >
35
+ {Icon && <Icon className="w-4 h-4 flex-shrink-0" />}
36
+ {item.label}
37
+ </button>
38
+ )
39
+ })}
40
+ </nav>
41
+ )
42
+ }
@@ -0,0 +1,6 @@
1
+ export { SettingsLayout } from './SettingsLayout'
2
+ export type { SettingsLayoutProps, SettingsNavItem as SettingsLayoutNavItem } from './SettingsLayout'
3
+ export { SettingsNav } from './SettingsNav'
4
+ export type { SettingsNavProps, SettingsNavItem } from './SettingsNav'
5
+ export { SettingsCard } from './SettingsCard'
6
+ export type { SettingsCardProps } from './SettingsCard'