@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.19",
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&apos;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&apos;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';