@startsimpli/ui 0.4.19 → 0.4.22
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.22",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -31,9 +31,13 @@
|
|
|
31
31
|
"@tanstack/react-query": ">=5.0.0",
|
|
32
32
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
33
33
|
"react": "^18.0.0 || ^19.0.0",
|
|
34
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
34
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
35
|
+
"@startsimpli/auth": "0.4.20"
|
|
35
36
|
},
|
|
36
37
|
"peerDependenciesMeta": {
|
|
38
|
+
"@startsimpli/auth": {
|
|
39
|
+
"optional": true
|
|
40
|
+
},
|
|
37
41
|
"@tanstack/react-query": {
|
|
38
42
|
"optional": true
|
|
39
43
|
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
CardDescription,
|
|
10
|
+
CardContent,
|
|
11
|
+
} from '../ui/card';
|
|
12
|
+
import { Checkbox } from '../ui/checkbox';
|
|
13
|
+
import { Label } from '../ui/label';
|
|
14
|
+
import { Sparkles, PlayCircle, HelpCircle, X } from 'lucide-react';
|
|
15
|
+
|
|
16
|
+
const STORAGE_KEY = 'workflow_guidance_dismissed';
|
|
17
|
+
|
|
18
|
+
export interface WorkflowEmptyStateProps {
|
|
19
|
+
onOpenTemplateGallery: () => void;
|
|
20
|
+
onStartFromScratch: () => void;
|
|
21
|
+
onOpenHelp?: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Empty State Component for Workflow Canvas
|
|
26
|
+
*
|
|
27
|
+
* Provides first-time user guidance with two paths:
|
|
28
|
+
* 1. Template Gallery - Pre-built workflows to learn from
|
|
29
|
+
* 2. Start From Scratch - Opens node palette with guidance
|
|
30
|
+
*
|
|
31
|
+
* Features:
|
|
32
|
+
* - localStorage-based dismissal (respects user preference)
|
|
33
|
+
* - Non-intrusive inline design (not a modal)
|
|
34
|
+
* - Action-oriented messaging (not "Welcome!")
|
|
35
|
+
* - Re-accessible via help button
|
|
36
|
+
*/
|
|
37
|
+
export function WorkflowEmptyState({
|
|
38
|
+
onOpenTemplateGallery,
|
|
39
|
+
onStartFromScratch,
|
|
40
|
+
onOpenHelp,
|
|
41
|
+
}: WorkflowEmptyStateProps) {
|
|
42
|
+
const [isDismissed, setIsDismissed] = useState(true);
|
|
43
|
+
const [dontShowAgain, setDontShowAgain] = useState(false);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
// Check if user has dismissed guidance
|
|
47
|
+
const dismissed = localStorage.getItem(STORAGE_KEY) === 'true';
|
|
48
|
+
setIsDismissed(dismissed);
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const handleTemplateGalleryClick = () => {
|
|
52
|
+
if (dontShowAgain) {
|
|
53
|
+
localStorage.setItem(STORAGE_KEY, 'true');
|
|
54
|
+
}
|
|
55
|
+
setIsDismissed(true);
|
|
56
|
+
onOpenTemplateGallery();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleStartFromScratchClick = () => {
|
|
60
|
+
if (dontShowAgain) {
|
|
61
|
+
localStorage.setItem(STORAGE_KEY, 'true');
|
|
62
|
+
}
|
|
63
|
+
setIsDismissed(true);
|
|
64
|
+
onStartFromScratch();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleDismiss = () => {
|
|
68
|
+
if (dontShowAgain) {
|
|
69
|
+
localStorage.setItem(STORAGE_KEY, 'true');
|
|
70
|
+
}
|
|
71
|
+
setIsDismissed(true);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (isDismissed) {
|
|
75
|
+
// Show persistent help button for returning users
|
|
76
|
+
return (
|
|
77
|
+
<div className="absolute bottom-6 right-6 z-10">
|
|
78
|
+
<Button
|
|
79
|
+
variant="outline"
|
|
80
|
+
size="icon"
|
|
81
|
+
onClick={onOpenHelp}
|
|
82
|
+
className="h-12 w-12 rounded-full shadow-lg hover:shadow-xl transition-all"
|
|
83
|
+
title="Need help? View templates and guidance"
|
|
84
|
+
>
|
|
85
|
+
<HelpCircle className="h-5 w-5" />
|
|
86
|
+
</Button>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
className="flex items-center justify-center z-10"
|
|
94
|
+
role="dialog"
|
|
95
|
+
aria-labelledby="welcome-title"
|
|
96
|
+
aria-describedby="welcome-description"
|
|
97
|
+
aria-modal="false"
|
|
98
|
+
>
|
|
99
|
+
<Card className="w-full max-w-[720px] pointer-events-auto animate-in fade-in-0 zoom-in-95 duration-300 relative">
|
|
100
|
+
{/* Close button */}
|
|
101
|
+
<button
|
|
102
|
+
onClick={handleDismiss}
|
|
103
|
+
aria-label="Close welcome panel"
|
|
104
|
+
aria-keyshortcuts="Escape"
|
|
105
|
+
className="absolute right-4 top-4 rounded-full p-2 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
106
|
+
>
|
|
107
|
+
<X className="w-5 h-5" aria-hidden="true" />
|
|
108
|
+
</button>
|
|
109
|
+
|
|
110
|
+
<CardHeader className="text-center pb-4 pt-10 px-10">
|
|
111
|
+
<CardTitle id="welcome-title" className="text-2xl font-semibold">
|
|
112
|
+
Welcome to Workflow Builder
|
|
113
|
+
</CardTitle>
|
|
114
|
+
<CardDescription
|
|
115
|
+
id="welcome-description"
|
|
116
|
+
className="text-sm text-muted-foreground leading-relaxed mt-2"
|
|
117
|
+
>
|
|
118
|
+
Create automated workflows with browser actions, logic, and AI
|
|
119
|
+
</CardDescription>
|
|
120
|
+
</CardHeader>
|
|
121
|
+
|
|
122
|
+
<CardContent className="flex flex-col gap-6 px-10 pb-10">
|
|
123
|
+
{/* Option 1: Template Gallery */}
|
|
124
|
+
<button
|
|
125
|
+
onClick={handleTemplateGalleryClick}
|
|
126
|
+
className="block w-full group text-left"
|
|
127
|
+
aria-label="Browse template gallery"
|
|
128
|
+
aria-describedby="template-gallery-desc"
|
|
129
|
+
>
|
|
130
|
+
<Card className="cursor-pointer transition-all duration-150 hover:shadow-md hover:border-primary/50 hover:scale-[1.02]">
|
|
131
|
+
<CardContent className="pt-6 pb-6">
|
|
132
|
+
<div className="flex items-start gap-4">
|
|
133
|
+
<div className="flex-shrink-0">
|
|
134
|
+
<div className="h-12 w-12 rounded-lg bg-primary/5 flex items-center justify-center group-hover:bg-primary/10 transition-colors">
|
|
135
|
+
<Sparkles className="h-6 w-6 text-primary/60" />
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<div className="flex-1 text-left">
|
|
139
|
+
<h3 className="font-medium text-base mb-1">
|
|
140
|
+
Browse Template Gallery
|
|
141
|
+
</h3>
|
|
142
|
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
143
|
+
Start with a pre-built workflow and customize it to your
|
|
144
|
+
needs. Perfect for learning common patterns.
|
|
145
|
+
</p>
|
|
146
|
+
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
|
|
147
|
+
<span className="inline-flex items-center gap-1">
|
|
148
|
+
⭐⭐⭐⭐⭐ Popular choice
|
|
149
|
+
</span>
|
|
150
|
+
<span>•</span>
|
|
151
|
+
<span>5 templates available</span>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</CardContent>
|
|
156
|
+
</Card>
|
|
157
|
+
<span id="template-gallery-desc" className="sr-only">
|
|
158
|
+
Choose from 5 pre-built workflow templates including login flows,
|
|
159
|
+
data extraction, API integration, multi-page navigation, and form
|
|
160
|
+
submission.
|
|
161
|
+
</span>
|
|
162
|
+
</button>
|
|
163
|
+
|
|
164
|
+
{/* Option 2: Start From Scratch */}
|
|
165
|
+
<button
|
|
166
|
+
onClick={handleStartFromScratchClick}
|
|
167
|
+
className="block w-full group text-left"
|
|
168
|
+
aria-label="Start from scratch"
|
|
169
|
+
aria-describedby="start-scratch-desc"
|
|
170
|
+
>
|
|
171
|
+
<Card className="cursor-pointer transition-all duration-150 hover:shadow-md hover:border-primary/50 hover:scale-[1.02]">
|
|
172
|
+
<CardContent className="pt-6 pb-6">
|
|
173
|
+
<div className="flex items-start gap-4">
|
|
174
|
+
<div className="flex-shrink-0">
|
|
175
|
+
<div className="h-12 w-12 rounded-lg bg-primary/5 flex items-center justify-center group-hover:bg-primary/10 transition-colors">
|
|
176
|
+
<PlayCircle className="h-6 w-6 text-primary/60" />
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="flex-1 text-left">
|
|
180
|
+
<h3 className="font-medium text-base mb-1">
|
|
181
|
+
Start From Scratch
|
|
182
|
+
</h3>
|
|
183
|
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
184
|
+
Build your workflow from the ground up. We'll
|
|
185
|
+
highlight the node palette and show you how to get started.
|
|
186
|
+
</p>
|
|
187
|
+
<div className="mt-2 text-xs text-muted-foreground">
|
|
188
|
+
For experienced users
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</CardContent>
|
|
193
|
+
</Card>
|
|
194
|
+
<span id="start-scratch-desc" className="sr-only">
|
|
195
|
+
Create a custom workflow by adding nodes from the node palette.
|
|
196
|
+
Ideal for users familiar with workflow automation.
|
|
197
|
+
</span>
|
|
198
|
+
</button>
|
|
199
|
+
|
|
200
|
+
{/* Don't show again checkbox */}
|
|
201
|
+
<div className="flex items-center justify-between pt-4 border-t">
|
|
202
|
+
<div className="flex items-center space-x-2">
|
|
203
|
+
<Checkbox
|
|
204
|
+
id="dontShowAgain"
|
|
205
|
+
checked={dontShowAgain}
|
|
206
|
+
onCheckedChange={(checked) => setDontShowAgain(checked === true)}
|
|
207
|
+
/>
|
|
208
|
+
<Label
|
|
209
|
+
htmlFor="dontShowAgain"
|
|
210
|
+
className="text-sm text-muted-foreground cursor-pointer"
|
|
211
|
+
>
|
|
212
|
+
Don't show this again
|
|
213
|
+
</Label>
|
|
214
|
+
</div>
|
|
215
|
+
<Button
|
|
216
|
+
variant="ghost"
|
|
217
|
+
size="sm"
|
|
218
|
+
onClick={handleDismiss}
|
|
219
|
+
className="text-muted-foreground hover:text-foreground"
|
|
220
|
+
>
|
|
221
|
+
Close
|
|
222
|
+
</Button>
|
|
223
|
+
</div>
|
|
224
|
+
</CardContent>
|
|
225
|
+
</Card>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Hook to manage empty state visibility and reset.
|
|
232
|
+
*/
|
|
233
|
+
export function useWorkflowEmptyState() {
|
|
234
|
+
const [isGuidanceDismissed, setIsGuidanceDismissed] = useState(true);
|
|
235
|
+
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
const dismissed = localStorage.getItem(STORAGE_KEY) === 'true';
|
|
238
|
+
setIsGuidanceDismissed(dismissed);
|
|
239
|
+
}, []);
|
|
240
|
+
|
|
241
|
+
const resetGuidance = () => {
|
|
242
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
243
|
+
setIsGuidanceDismissed(false);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const dismissGuidance = () => {
|
|
247
|
+
localStorage.setItem(STORAGE_KEY, 'true');
|
|
248
|
+
setIsGuidanceDismissed(true);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
isGuidanceDismissed,
|
|
253
|
+
resetGuidance,
|
|
254
|
+
dismissGuidance,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { Hash, User, StickyNote, Boxes } from 'lucide-react';
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
import { Badge } from '../ui/badge';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal, in-package shape of a workflow version. Mirrors the subset of the
|
|
10
|
+
* backend version record the history view actually reads — kept local so the
|
|
11
|
+
* component carries no app type coupling.
|
|
12
|
+
*/
|
|
13
|
+
export interface WorkflowVersion {
|
|
14
|
+
uuid: string;
|
|
15
|
+
versionNumber: number;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
isPublished?: boolean;
|
|
18
|
+
versionNote?: string | null;
|
|
19
|
+
createdBy?: {
|
|
20
|
+
firstName?: string | null;
|
|
21
|
+
lastName?: string | null;
|
|
22
|
+
email: string;
|
|
23
|
+
} | null;
|
|
24
|
+
/** Opaque workflow definition; only `nodes.length` is read for display. */
|
|
25
|
+
workflowData?: Record<string, unknown> | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WorkflowVersionHistoryProps {
|
|
29
|
+
versions: WorkflowVersion[];
|
|
30
|
+
/**
|
|
31
|
+
* Formats a version's ISO timestamp for display. Supplied by the consumer
|
|
32
|
+
* so this component stays free of date/locale dependencies. Defaults to the
|
|
33
|
+
* raw timestamp string when omitted.
|
|
34
|
+
*/
|
|
35
|
+
formatTimestamp?: (timestamp: string) => string;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function WorkflowVersionHistory({
|
|
40
|
+
versions,
|
|
41
|
+
formatTimestamp,
|
|
42
|
+
className,
|
|
43
|
+
}: WorkflowVersionHistoryProps) {
|
|
44
|
+
const sorted = useMemo(
|
|
45
|
+
() => [...versions].sort((a, b) => b.versionNumber - a.versionNumber),
|
|
46
|
+
[versions]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (versions.length === 0) {
|
|
50
|
+
return (
|
|
51
|
+
<div className={cn('text-center py-12', className)}>
|
|
52
|
+
<Hash className="h-12 w-12 mx-auto mb-3 text-muted-foreground" />
|
|
53
|
+
<h3 className="text-lg font-semibold mb-2">No Versions Yet</h3>
|
|
54
|
+
<p className="text-muted-foreground max-w-sm mx-auto">
|
|
55
|
+
No versions published yet. Use the canvas toolbar to publish a
|
|
56
|
+
version.
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const formatTime = formatTimestamp ?? ((t: string) => t);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ol className={cn('relative space-y-6', className)}>
|
|
66
|
+
{sorted.map((version, index) => {
|
|
67
|
+
const label = version.versionNote
|
|
68
|
+
? `v${version.versionNumber} — ${version.versionNote}`
|
|
69
|
+
: `v${version.versionNumber}`;
|
|
70
|
+
|
|
71
|
+
const nodeCount =
|
|
72
|
+
version.workflowData &&
|
|
73
|
+
Array.isArray(
|
|
74
|
+
(version.workflowData as Record<string, unknown>).nodes
|
|
75
|
+
)
|
|
76
|
+
? (
|
|
77
|
+
(version.workflowData as Record<string, unknown>)
|
|
78
|
+
.nodes as unknown[]
|
|
79
|
+
).length
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
const isLast = index === sorted.length - 1;
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<li key={version.uuid} className="relative pl-8">
|
|
86
|
+
{/* Timeline rail + dot */}
|
|
87
|
+
{!isLast && (
|
|
88
|
+
<span
|
|
89
|
+
className="absolute left-[7px] top-5 bottom-[-1.5rem] w-px bg-border"
|
|
90
|
+
aria-hidden="true"
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
<span
|
|
94
|
+
className={cn(
|
|
95
|
+
'absolute left-0 top-1 h-3.5 w-3.5 rounded-full border-2',
|
|
96
|
+
version.isPublished
|
|
97
|
+
? 'border-green-500 bg-green-500'
|
|
98
|
+
: 'border-muted-foreground bg-background'
|
|
99
|
+
)}
|
|
100
|
+
aria-hidden="true"
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
<div className="flex items-center justify-between gap-2">
|
|
104
|
+
<span className="font-medium text-sm">{label}</span>
|
|
105
|
+
<span className="text-xs text-muted-foreground">
|
|
106
|
+
{formatTime(version.timestamp)}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div className="mt-2 space-y-2 text-sm">
|
|
111
|
+
{version.isPublished && (
|
|
112
|
+
<div>
|
|
113
|
+
<Badge
|
|
114
|
+
variant="outline"
|
|
115
|
+
className="border-green-200 bg-green-50 text-green-700 dark:border-green-800 dark:bg-green-950/30 dark:text-green-400"
|
|
116
|
+
>
|
|
117
|
+
Published
|
|
118
|
+
</Badge>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
{version.createdBy && (
|
|
122
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
123
|
+
<User className="h-3.5 w-3.5 shrink-0" />
|
|
124
|
+
<span>
|
|
125
|
+
{version.createdBy.firstName
|
|
126
|
+
? `${version.createdBy.firstName} ${version.createdBy.lastName ?? ''}`.trim()
|
|
127
|
+
: version.createdBy.email}
|
|
128
|
+
</span>
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
{version.versionNote && (
|
|
132
|
+
<div className="flex items-start gap-2 text-muted-foreground">
|
|
133
|
+
<StickyNote className="h-3.5 w-3.5 shrink-0 mt-0.5" />
|
|
134
|
+
<span>{version.versionNote}</span>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
{nodeCount !== null && (
|
|
138
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
139
|
+
<Boxes className="h-3.5 w-3.5 shrink-0" />
|
|
140
|
+
<span>
|
|
141
|
+
{nodeCount} node{nodeCount !== 1 ? 's' : ''}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
</li>
|
|
147
|
+
);
|
|
148
|
+
})}
|
|
149
|
+
</ol>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -76,3 +76,12 @@ export { useNodeStatusOverlay } from './hooks/useNodeStatusOverlay';
|
|
|
76
76
|
|
|
77
77
|
// Category icon resolver (lucide)
|
|
78
78
|
export { getCategoryIcon } from './node-icons';
|
|
79
|
+
|
|
80
|
+
// Page composers — zero-coupling workflow UI (empty-state guidance + version history)
|
|
81
|
+
export { WorkflowEmptyState, useWorkflowEmptyState } from './WorkflowEmptyState';
|
|
82
|
+
export type { WorkflowEmptyStateProps } from './WorkflowEmptyState';
|
|
83
|
+
export { WorkflowVersionHistory } from './WorkflowVersionHistory';
|
|
84
|
+
export type {
|
|
85
|
+
WorkflowVersion,
|
|
86
|
+
WorkflowVersionHistoryProps,
|
|
87
|
+
} from './WorkflowVersionHistory';
|