@lssm/example.learning-journey-ui-onboarding 0.0.0-canary-20251212210835

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.
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Button } from '@lssm/lib.design-system';
5
+ import { cn } from '@lssm/lib.ui-kit-web/ui/utils';
6
+
7
+ interface CodeSnippetProps {
8
+ code: string;
9
+ language?: string;
10
+ title?: string;
11
+ }
12
+
13
+ export function CodeSnippet({
14
+ code,
15
+ language = 'typescript',
16
+ title,
17
+ }: CodeSnippetProps) {
18
+ const [copied, setCopied] = useState(false);
19
+
20
+ const handleCopy = async () => {
21
+ await navigator.clipboard.writeText(code);
22
+ setCopied(true);
23
+ setTimeout(() => setCopied(false), 2000);
24
+ };
25
+
26
+ return (
27
+ <div className="bg-muted/50 overflow-hidden rounded-lg border">
28
+ {/* Header */}
29
+ <div className="bg-muted flex items-center justify-between border-b px-4 py-2">
30
+ <div className="flex items-center gap-2">
31
+ <span className="text-muted-foreground text-xs font-medium uppercase">
32
+ {language}
33
+ </span>
34
+ {title && (
35
+ <>
36
+ <span className="text-muted-foreground">•</span>
37
+ <span className="text-sm">{title}</span>
38
+ </>
39
+ )}
40
+ </div>
41
+ <Button
42
+ variant="ghost"
43
+ size="sm"
44
+ onClick={handleCopy}
45
+ className="h-7 text-xs"
46
+ >
47
+ {copied ? '✓ Copied' : 'Copy'}
48
+ </Button>
49
+ </div>
50
+
51
+ {/* Code */}
52
+ <pre className="overflow-x-auto p-4">
53
+ <code className="text-sm">{code}</code>
54
+ </pre>
55
+ </div>
56
+ );
57
+ }
58
+
@@ -0,0 +1,87 @@
1
+ 'use client';
2
+
3
+ import { cn } from '@lssm/lib.ui-kit-web/ui/utils';
4
+ import type { LearningJourneyStepSpec } from '@lssm/module.learning-journey/track-spec';
5
+
6
+ interface JourneyMapProps {
7
+ steps: LearningJourneyStepSpec[];
8
+ completedStepIds: string[];
9
+ currentStepId?: string | null;
10
+ }
11
+
12
+ const SURFACE_ICONS: Record<string, string> = {
13
+ templates: '📋',
14
+ 'spec-editor': '✏️',
15
+ regenerator: '🔄',
16
+ playground: '🎮',
17
+ evolution: '🤖',
18
+ dashboard: '📊',
19
+ settings: '⚙️',
20
+ default: '📍',
21
+ };
22
+
23
+ export function JourneyMap({
24
+ steps,
25
+ completedStepIds,
26
+ currentStepId,
27
+ }: JourneyMapProps) {
28
+ return (
29
+ <div className="relative overflow-x-auto pb-4">
30
+ <div className="flex min-w-max items-center gap-2">
31
+ {steps.map((step, index) => {
32
+ const isCompleted = completedStepIds.includes(step.id);
33
+ const isCurrent = step.id === currentStepId;
34
+ const surface = (step.metadata?.surface as string) ?? 'default';
35
+ const icon = SURFACE_ICONS[surface] ?? SURFACE_ICONS.default;
36
+
37
+ return (
38
+ <div key={step.id} className="flex items-center">
39
+ {/* Node */}
40
+ <div className="flex flex-col items-center gap-2">
41
+ <div
42
+ className={cn(
43
+ 'flex h-14 w-14 items-center justify-center rounded-2xl border-2 text-2xl transition-all',
44
+ isCompleted && 'border-green-500 bg-green-500/10',
45
+ isCurrent &&
46
+ !isCompleted &&
47
+ 'border-violet-500 bg-violet-500/10 ring-4 ring-violet-500/20',
48
+ !isCompleted && !isCurrent && 'border-muted bg-muted/50'
49
+ )}
50
+ >
51
+ {isCompleted ? '✓' : icon}
52
+ </div>
53
+ <div className="text-center">
54
+ <p
55
+ className={cn(
56
+ 'max-w-[100px] truncate text-xs font-medium',
57
+ isCompleted && 'text-green-500',
58
+ isCurrent && !isCompleted && 'text-violet-500',
59
+ !isCompleted && !isCurrent && 'text-muted-foreground'
60
+ )}
61
+ >
62
+ {step.title}
63
+ </p>
64
+ </div>
65
+ </div>
66
+
67
+ {/* Connector */}
68
+ {index < steps.length - 1 && (
69
+ <div
70
+ className={cn(
71
+ 'mx-2 h-1 w-8 rounded-full transition-colors',
72
+ completedStepIds.includes(steps[index + 1]?.id ?? '')
73
+ ? 'bg-green-500'
74
+ : isCompleted
75
+ ? 'bg-green-500/50'
76
+ : 'bg-muted'
77
+ )}
78
+ />
79
+ )}
80
+ </div>
81
+ );
82
+ })}
83
+ </div>
84
+ </div>
85
+ );
86
+ }
87
+
@@ -0,0 +1,138 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Button } from '@lssm/lib.design-system';
5
+ import { cn } from '@lssm/lib.ui-kit-web/ui/utils';
6
+ import type { LearningJourneyStepSpec } from '@lssm/module.learning-journey/track-spec';
7
+
8
+ interface StepChecklistProps {
9
+ step: LearningJourneyStepSpec;
10
+ stepNumber: number;
11
+ isCompleted: boolean;
12
+ isCurrent: boolean;
13
+ isExpanded: boolean;
14
+ onToggle: () => void;
15
+ onComplete?: () => void;
16
+ }
17
+
18
+ export function StepChecklist({
19
+ step,
20
+ stepNumber,
21
+ isCompleted,
22
+ isCurrent,
23
+ isExpanded,
24
+ onToggle,
25
+ onComplete,
26
+ }: StepChecklistProps) {
27
+ return (
28
+ <div
29
+ className={cn(
30
+ 'rounded-xl border transition-all',
31
+ isCompleted && 'border-green-500/50 bg-green-500/5',
32
+ isCurrent && !isCompleted && 'border-violet-500 bg-violet-500/5',
33
+ !isCompleted && !isCurrent && 'border-border'
34
+ )}
35
+ >
36
+ {/* Header */}
37
+ <button
38
+ type="button"
39
+ className="flex w-full items-center gap-4 p-4 text-left"
40
+ onClick={onToggle}
41
+ >
42
+ {/* Checkbox/Number */}
43
+ <div
44
+ className={cn(
45
+ 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 text-sm font-semibold transition-colors',
46
+ isCompleted && 'border-green-500 bg-green-500 text-white',
47
+ isCurrent && !isCompleted && 'border-violet-500 text-violet-500',
48
+ !isCompleted &&
49
+ !isCurrent &&
50
+ 'border-muted-foreground text-muted-foreground'
51
+ )}
52
+ >
53
+ {isCompleted ? '✓' : stepNumber}
54
+ </div>
55
+
56
+ {/* Title & Description */}
57
+ <div className="min-w-0 flex-1">
58
+ <h4
59
+ className={cn(
60
+ 'font-semibold',
61
+ isCompleted && 'text-green-500',
62
+ isCurrent && !isCompleted && 'text-foreground',
63
+ !isCompleted && !isCurrent && 'text-muted-foreground'
64
+ )}
65
+ >
66
+ {step.title}
67
+ </h4>
68
+ {!isExpanded && step.description && (
69
+ <p className="text-muted-foreground truncate text-sm">
70
+ {step.description}
71
+ </p>
72
+ )}
73
+ </div>
74
+
75
+ {/* XP Badge */}
76
+ {step.xpReward && (
77
+ <span
78
+ className={cn(
79
+ 'shrink-0 rounded-full px-2 py-1 text-xs font-semibold',
80
+ isCompleted
81
+ ? 'bg-green-500/10 text-green-500'
82
+ : 'bg-muted text-muted-foreground'
83
+ )}
84
+ >
85
+ +{step.xpReward} XP
86
+ </span>
87
+ )}
88
+
89
+ {/* Expand indicator */}
90
+ <span
91
+ className={cn(
92
+ 'shrink-0 transition-transform',
93
+ isExpanded && 'rotate-180'
94
+ )}
95
+ >
96
+
97
+ </span>
98
+ </button>
99
+
100
+ {/* Expanded Content */}
101
+ {isExpanded && (
102
+ <div className="border-t px-4 py-4">
103
+ {step.description && (
104
+ <p className="text-muted-foreground mb-4">{step.description}</p>
105
+ )}
106
+
107
+ {step.instructions && (
108
+ <div className="bg-muted mb-4 rounded-lg p-4">
109
+ <p className="mb-2 text-sm font-medium">Instructions:</p>
110
+ <p className="text-muted-foreground text-sm">
111
+ {step.instructions}
112
+ </p>
113
+ </div>
114
+ )}
115
+
116
+ {/* Action buttons */}
117
+ <div className="flex flex-wrap gap-2">
118
+ {step.actionUrl && (
119
+ <Button
120
+ variant="outline"
121
+ size="sm"
122
+ onClick={() => window.open(step.actionUrl, '_blank')}
123
+ >
124
+ {step.actionLabel ?? 'Try it'}
125
+ </Button>
126
+ )}
127
+ {!isCompleted && (
128
+ <Button size="sm" onClick={onComplete}>
129
+ Mark as Complete
130
+ </Button>
131
+ )}
132
+ </div>
133
+ </div>
134
+ )}
135
+ </div>
136
+ );
137
+ }
138
+
@@ -0,0 +1,4 @@
1
+ export { StepChecklist } from './StepChecklist';
2
+ export { CodeSnippet } from './CodeSnippet';
3
+ export { JourneyMap } from './JourneyMap';
4
+
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ // Main mini-app
2
+ export { OnboardingMiniApp } from './OnboardingMiniApp';
3
+
4
+ // Views
5
+ export { Overview, Steps, Progress, Timeline } from './views';
6
+
7
+ // Components
8
+ export { StepChecklist, CodeSnippet, JourneyMap } from './components';
9
+
@@ -0,0 +1,205 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@lssm/lib.design-system';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from '@lssm/lib.ui-kit-web/ui/card';
10
+ import { Progress } from '@lssm/lib.ui-kit-web/ui/progress';
11
+ import { XpBar } from '@lssm/example.learning-journey-ui-shared';
12
+ import type { LearningViewProps } from '@lssm/example.learning-journey-ui-shared';
13
+
14
+ interface OnboardingOverviewProps extends LearningViewProps {
15
+ onStart?: () => void;
16
+ }
17
+
18
+ export function Overview({
19
+ track,
20
+ progress,
21
+ onStart,
22
+ }: OnboardingOverviewProps) {
23
+ const totalSteps = track.steps.length;
24
+ const completedSteps = progress.completedStepIds.length;
25
+ const percentComplete =
26
+ totalSteps > 0 ? (completedSteps / totalSteps) * 100 : 0;
27
+ const isComplete = completedSteps === totalSteps;
28
+
29
+ // Estimate time remaining (rough: 5 min per step)
30
+ const remainingSteps = totalSteps - completedSteps;
31
+ const estimatedMinutes = remainingSteps * 5;
32
+
33
+ const totalXp =
34
+ track.totalXp ??
35
+ track.steps.reduce((sum, s) => sum + (s.xpReward ?? 0), 0) +
36
+ (track.completionRewards?.xpBonus ?? 0);
37
+
38
+ return (
39
+ <div className="space-y-6">
40
+ {/* Welcome Banner */}
41
+ <Card className="overflow-hidden bg-gradient-to-r from-blue-500/10 via-violet-500/10 to-purple-500/10">
42
+ <CardContent className="p-8">
43
+ <div className="flex flex-col items-center gap-6 text-center md:flex-row md:text-left">
44
+ <div className="flex h-20 w-20 items-center justify-center rounded-2xl bg-gradient-to-br from-blue-500 to-violet-600 text-4xl shadow-lg">
45
+ {isComplete ? '🎉' : '🚀'}
46
+ </div>
47
+ <div className="flex-1">
48
+ <h1 className="text-2xl font-bold">{track.name}</h1>
49
+ <p className="text-muted-foreground mt-1 max-w-2xl">
50
+ {track.description}
51
+ </p>
52
+ {!isComplete && (
53
+ <p className="text-muted-foreground mt-3 text-sm">
54
+ ⏱️ Estimated time:{' '}
55
+ {estimatedMinutes > 0
56
+ ? `~${estimatedMinutes} minutes`
57
+ : 'Less than a minute'}
58
+ </p>
59
+ )}
60
+ </div>
61
+ {!isComplete && (
62
+ <Button size="lg" onClick={onStart}>
63
+ {completedSteps > 0 ? 'Continue' : 'Get Started'}
64
+ </Button>
65
+ )}
66
+ </div>
67
+ </CardContent>
68
+ </Card>
69
+
70
+ {/* Progress Overview */}
71
+ <div className="grid gap-4 md:grid-cols-3">
72
+ <Card>
73
+ <CardHeader className="pb-2">
74
+ <CardTitle className="text-muted-foreground text-sm font-medium">
75
+ Progress
76
+ </CardTitle>
77
+ </CardHeader>
78
+ <CardContent>
79
+ <div className="text-3xl font-bold">
80
+ {Math.round(percentComplete)}%
81
+ </div>
82
+ <Progress value={percentComplete} className="mt-2 h-2" />
83
+ <p className="text-muted-foreground mt-2 text-sm">
84
+ {completedSteps} of {totalSteps} steps completed
85
+ </p>
86
+ </CardContent>
87
+ </Card>
88
+
89
+ <Card>
90
+ <CardHeader className="pb-2">
91
+ <CardTitle className="text-muted-foreground text-sm font-medium">
92
+ XP Earned
93
+ </CardTitle>
94
+ </CardHeader>
95
+ <CardContent>
96
+ <div className="text-3xl font-bold text-blue-500">
97
+ {progress.xpEarned}
98
+ </div>
99
+ <XpBar
100
+ current={progress.xpEarned}
101
+ max={totalXp}
102
+ showLabel={false}
103
+ size="sm"
104
+ />
105
+ </CardContent>
106
+ </Card>
107
+
108
+ <Card>
109
+ <CardHeader className="pb-2">
110
+ <CardTitle className="text-muted-foreground text-sm font-medium">
111
+ Time Remaining
112
+ </CardTitle>
113
+ </CardHeader>
114
+ <CardContent>
115
+ <div className="text-3xl font-bold">
116
+ {isComplete ? '✓' : `~${estimatedMinutes}m`}
117
+ </div>
118
+ <p className="text-muted-foreground mt-2 text-sm">
119
+ {isComplete ? 'All done!' : `${remainingSteps} steps to go`}
120
+ </p>
121
+ </CardContent>
122
+ </Card>
123
+ </div>
124
+
125
+ {/* Step Preview */}
126
+ <Card>
127
+ <CardHeader>
128
+ <CardTitle className="flex items-center gap-2">
129
+ <span>📋</span>
130
+ <span>Your Journey</span>
131
+ </CardTitle>
132
+ </CardHeader>
133
+ <CardContent>
134
+ <div className="space-y-3">
135
+ {track.steps.map((step, index) => {
136
+ const isStepCompleted = progress.completedStepIds.includes(
137
+ step.id
138
+ );
139
+ const isCurrent =
140
+ !isStepCompleted &&
141
+ track.steps
142
+ .slice(0, index)
143
+ .every((s) => progress.completedStepIds.includes(s.id));
144
+
145
+ return (
146
+ <div
147
+ key={step.id}
148
+ className="flex items-center gap-4 rounded-lg border p-3"
149
+ >
150
+ <div
151
+ className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm font-semibold ${
152
+ isStepCompleted
153
+ ? 'bg-green-500 text-white'
154
+ : isCurrent
155
+ ? 'bg-blue-500 text-white'
156
+ : 'bg-muted text-muted-foreground'
157
+ }`}
158
+ >
159
+ {isStepCompleted ? '✓' : index + 1}
160
+ </div>
161
+ <div className="min-w-0 flex-1">
162
+ <p
163
+ className={`font-medium ${
164
+ isStepCompleted
165
+ ? 'text-green-500'
166
+ : isCurrent
167
+ ? 'text-foreground'
168
+ : 'text-muted-foreground'
169
+ }`}
170
+ >
171
+ {step.title}
172
+ </p>
173
+ </div>
174
+ {step.xpReward && (
175
+ <span className="text-muted-foreground text-sm">
176
+ +{step.xpReward} XP
177
+ </span>
178
+ )}
179
+ </div>
180
+ );
181
+ })}
182
+ </div>
183
+ </CardContent>
184
+ </Card>
185
+
186
+ {/* Completion Message */}
187
+ {isComplete && (
188
+ <Card className="border-green-500/50 bg-green-500/5">
189
+ <CardContent className="flex items-center gap-4 p-6">
190
+ <div className="text-4xl">🎉</div>
191
+ <div>
192
+ <h3 className="text-lg font-semibold text-green-500">
193
+ Onboarding Complete!
194
+ </h3>
195
+ <p className="text-muted-foreground">
196
+ You've completed all {totalSteps} steps. Welcome aboard!
197
+ </p>
198
+ </div>
199
+ </CardContent>
200
+ </Card>
201
+ )}
202
+ </div>
203
+ );
204
+ }
205
+
@@ -0,0 +1,184 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardHeader,
7
+ CardTitle,
8
+ } from '@lssm/lib.ui-kit-web/ui/card';
9
+ import { Progress } from '@lssm/lib.ui-kit-web/ui/progress';
10
+ import { XpBar, BadgeDisplay } from '@lssm/example.learning-journey-ui-shared';
11
+ import type { LearningViewProps } from '@lssm/example.learning-journey-ui-shared';
12
+
13
+ export function ProgressView({ track, progress }: LearningViewProps) {
14
+ const totalSteps = track.steps.length;
15
+ const completedSteps = progress.completedStepIds.length;
16
+ const percentComplete =
17
+ totalSteps > 0 ? (completedSteps / totalSteps) * 100 : 0;
18
+
19
+ const totalXp =
20
+ track.totalXp ??
21
+ track.steps.reduce((sum, s) => sum + (s.xpReward ?? 0), 0) +
22
+ (track.completionRewards?.xpBonus ?? 0);
23
+
24
+ const remainingSteps = totalSteps - completedSteps;
25
+ const estimatedMinutes = remainingSteps * 5;
26
+
27
+ return (
28
+ <div className="space-y-6">
29
+ {/* Main Progress */}
30
+ <Card>
31
+ <CardHeader>
32
+ <CardTitle className="flex items-center gap-2">
33
+ <span>📈</span>
34
+ <span>Your Progress</span>
35
+ </CardTitle>
36
+ </CardHeader>
37
+ <CardContent className="space-y-6">
38
+ {/* Circular progress indicator */}
39
+ <div className="flex items-center justify-center">
40
+ <div className="relative flex h-40 w-40 items-center justify-center">
41
+ <svg
42
+ className="absolute h-full w-full -rotate-90"
43
+ viewBox="0 0 100 100"
44
+ >
45
+ <circle
46
+ cx="50"
47
+ cy="50"
48
+ r="45"
49
+ fill="none"
50
+ strokeWidth="8"
51
+ className="stroke-muted"
52
+ />
53
+ <circle
54
+ cx="50"
55
+ cy="50"
56
+ r="45"
57
+ fill="none"
58
+ strokeWidth="8"
59
+ strokeLinecap="round"
60
+ strokeDasharray={`${percentComplete * 2.83} 283`}
61
+ className="stroke-blue-500 transition-all duration-500"
62
+ />
63
+ </svg>
64
+ <div className="text-center">
65
+ <div className="text-3xl font-bold">
66
+ {Math.round(percentComplete)}%
67
+ </div>
68
+ <div className="text-muted-foreground text-sm">Complete</div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ {/* Stats row */}
74
+ <div className="grid grid-cols-3 gap-4 text-center">
75
+ <div>
76
+ <div className="text-2xl font-bold text-green-500">
77
+ {completedSteps}
78
+ </div>
79
+ <div className="text-muted-foreground text-sm">Completed</div>
80
+ </div>
81
+ <div>
82
+ <div className="text-2xl font-bold text-orange-500">
83
+ {remainingSteps}
84
+ </div>
85
+ <div className="text-muted-foreground text-sm">Remaining</div>
86
+ </div>
87
+ <div>
88
+ <div className="text-2xl font-bold">{estimatedMinutes}m</div>
89
+ <div className="text-muted-foreground text-sm">Est. Time</div>
90
+ </div>
91
+ </div>
92
+ </CardContent>
93
+ </Card>
94
+
95
+ {/* XP Progress */}
96
+ <Card>
97
+ <CardHeader>
98
+ <CardTitle className="flex items-center gap-2">
99
+ <span>⚡</span>
100
+ <span>Experience Points</span>
101
+ </CardTitle>
102
+ </CardHeader>
103
+ <CardContent className="space-y-4">
104
+ <div className="flex items-baseline gap-2">
105
+ <span className="text-3xl font-bold text-blue-500">
106
+ {progress.xpEarned}
107
+ </span>
108
+ <span className="text-muted-foreground">/ {totalXp} XP</span>
109
+ </div>
110
+ <XpBar
111
+ current={progress.xpEarned}
112
+ max={totalXp}
113
+ showLabel={false}
114
+ size="lg"
115
+ />
116
+ </CardContent>
117
+ </Card>
118
+
119
+ {/* Badges */}
120
+ <Card>
121
+ <CardHeader>
122
+ <CardTitle className="flex items-center gap-2">
123
+ <span>🏅</span>
124
+ <span>Achievements</span>
125
+ </CardTitle>
126
+ </CardHeader>
127
+ <CardContent>
128
+ <BadgeDisplay badges={progress.badges} size="lg" />
129
+ {progress.badges.length === 0 &&
130
+ track.completionRewards?.badgeKey && (
131
+ <p className="text-muted-foreground text-sm">
132
+ Complete all steps to earn the "
133
+ {track.completionRewards.badgeKey}" badge!
134
+ </p>
135
+ )}
136
+ </CardContent>
137
+ </Card>
138
+
139
+ {/* Step-by-step breakdown */}
140
+ <Card>
141
+ <CardHeader>
142
+ <CardTitle className="flex items-center gap-2">
143
+ <span>📋</span>
144
+ <span>Step Details</span>
145
+ </CardTitle>
146
+ </CardHeader>
147
+ <CardContent>
148
+ <div className="space-y-3">
149
+ {track.steps.map((step, index) => {
150
+ const isCompleted = progress.completedStepIds.includes(step.id);
151
+ const stepProgress = isCompleted ? 100 : 0;
152
+
153
+ return (
154
+ <div key={step.id} className="space-y-1">
155
+ <div className="flex items-center justify-between text-sm">
156
+ <span
157
+ className={
158
+ isCompleted ? 'text-green-500' : 'text-foreground'
159
+ }
160
+ >
161
+ {index + 1}. {step.title}
162
+ </span>
163
+ <span
164
+ className={
165
+ isCompleted ? 'text-green-500' : 'text-muted-foreground'
166
+ }
167
+ >
168
+ {isCompleted ? '✓' : 'Pending'}
169
+ </span>
170
+ </div>
171
+ <Progress value={stepProgress} className="h-1" />
172
+ </div>
173
+ );
174
+ })}
175
+ </div>
176
+ </CardContent>
177
+ </Card>
178
+ </div>
179
+ );
180
+ }
181
+
182
+ // Re-export with correct name
183
+ export { ProgressView as Progress };
184
+