@contractspec/example.learning-journey-ui-gamified 1.44.0

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 (71) hide show
  1. package/.turbo/turbo-build$colon$bundle.log +57 -0
  2. package/.turbo/turbo-build.log +58 -0
  3. package/CHANGELOG.md +262 -0
  4. package/LICENSE +21 -0
  5. package/README.md +32 -0
  6. package/dist/GamifiedMiniApp.d.ts +17 -0
  7. package/dist/GamifiedMiniApp.d.ts.map +1 -0
  8. package/dist/GamifiedMiniApp.js +63 -0
  9. package/dist/GamifiedMiniApp.js.map +1 -0
  10. package/dist/components/DayCalendar.d.ts +16 -0
  11. package/dist/components/DayCalendar.d.ts.map +1 -0
  12. package/dist/components/DayCalendar.js +33 -0
  13. package/dist/components/DayCalendar.js.map +1 -0
  14. package/dist/components/FlashCard.d.ts +19 -0
  15. package/dist/components/FlashCard.d.ts.map +1 -0
  16. package/dist/components/FlashCard.js +80 -0
  17. package/dist/components/FlashCard.js.map +1 -0
  18. package/dist/components/MasteryRing.d.ts +18 -0
  19. package/dist/components/MasteryRing.d.ts.map +1 -0
  20. package/dist/components/MasteryRing.js +82 -0
  21. package/dist/components/MasteryRing.js.map +1 -0
  22. package/dist/components/index.d.ts +4 -0
  23. package/dist/components/index.js +5 -0
  24. package/dist/docs/index.d.ts +1 -0
  25. package/dist/docs/index.js +1 -0
  26. package/dist/docs/learning-journey-ui-gamified.docblock.d.ts +1 -0
  27. package/dist/docs/learning-journey-ui-gamified.docblock.js +20 -0
  28. package/dist/docs/learning-journey-ui-gamified.docblock.js.map +1 -0
  29. package/dist/example.d.ts +33 -0
  30. package/dist/example.d.ts.map +1 -0
  31. package/dist/example.js +35 -0
  32. package/dist/example.js.map +1 -0
  33. package/dist/index.d.ts +12 -0
  34. package/dist/index.js +14 -0
  35. package/dist/views/Overview.d.ts +15 -0
  36. package/dist/views/Overview.d.ts.map +1 -0
  37. package/dist/views/Overview.js +161 -0
  38. package/dist/views/Overview.js.map +1 -0
  39. package/dist/views/Progress.d.ts +11 -0
  40. package/dist/views/Progress.d.ts.map +1 -0
  41. package/dist/views/Progress.js +143 -0
  42. package/dist/views/Progress.js.map +1 -0
  43. package/dist/views/Steps.d.ts +12 -0
  44. package/dist/views/Steps.d.ts.map +1 -0
  45. package/dist/views/Steps.js +56 -0
  46. package/dist/views/Steps.js.map +1 -0
  47. package/dist/views/Timeline.d.ts +11 -0
  48. package/dist/views/Timeline.d.ts.map +1 -0
  49. package/dist/views/Timeline.js +133 -0
  50. package/dist/views/Timeline.js.map +1 -0
  51. package/dist/views/index.d.ts +5 -0
  52. package/dist/views/index.js +6 -0
  53. package/example.ts +1 -0
  54. package/package.json +79 -0
  55. package/src/GamifiedMiniApp.tsx +93 -0
  56. package/src/components/DayCalendar.tsx +53 -0
  57. package/src/components/FlashCard.tsx +100 -0
  58. package/src/components/MasteryRing.tsx +81 -0
  59. package/src/components/index.ts +3 -0
  60. package/src/docs/index.ts +1 -0
  61. package/src/docs/learning-journey-ui-gamified.docblock.ts +18 -0
  62. package/src/example.ts +24 -0
  63. package/src/index.ts +10 -0
  64. package/src/views/Overview.tsx +164 -0
  65. package/src/views/Progress.tsx +183 -0
  66. package/src/views/Steps.tsx +50 -0
  67. package/src/views/Timeline.tsx +197 -0
  68. package/src/views/index.ts +4 -0
  69. package/tsconfig.json +10 -0
  70. package/tsconfig.tsbuildinfo +1 -0
  71. package/tsdown.config.js +17 -0
@@ -0,0 +1,143 @@
1
+ 'use client';
2
+
3
+ import { MasteryRing } from "../components/MasteryRing.js";
4
+ import { Card, CardContent, CardHeader, CardTitle } from "@contractspec/lib.ui-kit-web/ui/card";
5
+ import { BadgeDisplay, XpBar } from "@contractspec/example.learning-journey-ui-shared";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+
8
+ //#region src/views/Progress.tsx
9
+ function Progress({ track, progress }) {
10
+ const totalXp = track.totalXp ?? track.steps.reduce((sum, s) => sum + (s.xpReward ?? 0), 0) + (track.completionRewards?.xpBonus ?? 0);
11
+ const completedSteps = progress.completedStepIds.length;
12
+ const totalSteps = track.steps.length;
13
+ const percentComplete = totalSteps > 0 ? completedSteps / totalSteps * 100 : 0;
14
+ const surfaces = /* @__PURE__ */ new Map();
15
+ track.steps.forEach((step) => {
16
+ const surface = step.metadata?.surface ?? "general";
17
+ const current = surfaces.get(surface) ?? {
18
+ total: 0,
19
+ completed: 0
20
+ };
21
+ surfaces.set(surface, {
22
+ total: current.total + 1,
23
+ completed: current.completed + (progress.completedStepIds.includes(step.id) ? 1 : 0)
24
+ });
25
+ });
26
+ const surfaceColors = [
27
+ "green",
28
+ "blue",
29
+ "violet",
30
+ "orange"
31
+ ];
32
+ return /* @__PURE__ */ jsxs("div", {
33
+ className: "space-y-6",
34
+ children: [
35
+ /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, {
36
+ className: "flex items-center gap-2",
37
+ children: [/* @__PURE__ */ jsx("span", { children: "⚡" }), /* @__PURE__ */ jsx("span", { children: "Experience Points" })]
38
+ }) }), /* @__PURE__ */ jsxs(CardContent, {
39
+ className: "space-y-4",
40
+ children: [
41
+ /* @__PURE__ */ jsxs("div", {
42
+ className: "flex items-baseline gap-2",
43
+ children: [/* @__PURE__ */ jsx("span", {
44
+ className: "text-4xl font-bold text-violet-500",
45
+ children: progress.xpEarned.toLocaleString()
46
+ }), /* @__PURE__ */ jsxs("span", {
47
+ className: "text-muted-foreground",
48
+ children: [
49
+ "/ ",
50
+ totalXp.toLocaleString(),
51
+ " XP"
52
+ ]
53
+ })]
54
+ }),
55
+ /* @__PURE__ */ jsx(XpBar, {
56
+ current: progress.xpEarned,
57
+ max: totalXp,
58
+ showLabel: false,
59
+ size: "lg"
60
+ }),
61
+ track.completionRewards?.xpBonus && percentComplete < 100 && /* @__PURE__ */ jsxs("p", {
62
+ className: "text-muted-foreground text-sm",
63
+ children: [
64
+ "🎁 Complete all steps for a",
65
+ " ",
66
+ /* @__PURE__ */ jsxs("span", {
67
+ className: "font-semibold text-green-500",
68
+ children: [
69
+ "+",
70
+ track.completionRewards.xpBonus,
71
+ " XP"
72
+ ]
73
+ }),
74
+ " ",
75
+ "bonus!"
76
+ ]
77
+ })
78
+ ]
79
+ })] }),
80
+ /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, {
81
+ className: "flex items-center gap-2",
82
+ children: [/* @__PURE__ */ jsx("span", { children: "🎯" }), /* @__PURE__ */ jsx("span", { children: "Skill Mastery" })]
83
+ }) }), /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("div", {
84
+ className: "flex flex-wrap justify-center gap-6",
85
+ children: [Array.from(surfaces.entries()).map(([surface, data], index) => /* @__PURE__ */ jsx(MasteryRing, {
86
+ label: surface.charAt(0).toUpperCase() + surface.slice(1),
87
+ percentage: data.completed / data.total * 100,
88
+ color: surfaceColors[index % surfaceColors.length],
89
+ size: "lg"
90
+ }, surface)), /* @__PURE__ */ jsx(MasteryRing, {
91
+ label: "Overall",
92
+ percentage: percentComplete,
93
+ color: "violet",
94
+ size: "lg"
95
+ })]
96
+ }) })] }),
97
+ /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, {
98
+ className: "flex items-center gap-2",
99
+ children: [/* @__PURE__ */ jsx("span", { children: "🏅" }), /* @__PURE__ */ jsx("span", { children: "Badges Earned" })]
100
+ }) }), /* @__PURE__ */ jsxs(CardContent, { children: [/* @__PURE__ */ jsx(BadgeDisplay, {
101
+ badges: progress.badges,
102
+ size: "lg",
103
+ maxVisible: 10
104
+ }), progress.badges.length === 0 && /* @__PURE__ */ jsx("p", {
105
+ className: "text-muted-foreground text-sm",
106
+ children: "Complete the track to earn your first badge!"
107
+ })] })] }),
108
+ /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, {
109
+ className: "flex items-center gap-2",
110
+ children: [/* @__PURE__ */ jsx("span", { children: "📊" }), /* @__PURE__ */ jsx("span", { children: "Step Breakdown" })]
111
+ }) }), /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", {
112
+ className: "space-y-2",
113
+ children: track.steps.map((step) => {
114
+ const isCompleted = progress.completedStepIds.includes(step.id);
115
+ return /* @__PURE__ */ jsxs("div", {
116
+ className: "flex items-center justify-between rounded-lg border p-3",
117
+ children: [/* @__PURE__ */ jsxs("div", {
118
+ className: "flex items-center gap-3",
119
+ children: [/* @__PURE__ */ jsx("span", {
120
+ className: isCompleted ? "text-green-500" : "text-muted-foreground",
121
+ children: isCompleted ? "✓" : "○"
122
+ }), /* @__PURE__ */ jsx("span", {
123
+ className: isCompleted ? "text-foreground" : "text-muted-foreground",
124
+ children: step.title
125
+ })]
126
+ }), step.xpReward && /* @__PURE__ */ jsxs("span", {
127
+ className: `text-sm font-medium ${isCompleted ? "text-green-500" : "text-muted-foreground"}`,
128
+ children: [
129
+ isCompleted ? "+" : "",
130
+ step.xpReward,
131
+ " XP"
132
+ ]
133
+ })]
134
+ }, step.id);
135
+ })
136
+ }) })] })
137
+ ]
138
+ });
139
+ }
140
+
141
+ //#endregion
142
+ export { Progress };
143
+ //# sourceMappingURL=Progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Progress.js","names":["surfaceColors: ('green' | 'blue' | 'violet' | 'orange')[]"],"sources":["../../src/views/Progress.tsx"],"sourcesContent":["'use client';\n\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n} from '@contractspec/lib.ui-kit-web/ui/card';\nimport {\n XpBar,\n BadgeDisplay,\n} from '@contractspec/example.learning-journey-ui-shared';\nimport { MasteryRing } from '../components/MasteryRing';\nimport type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';\n\nexport function Progress({ track, progress }: LearningViewProps) {\n const totalXp =\n track.totalXp ??\n track.steps.reduce((sum, s) => sum + (s.xpReward ?? 0), 0) +\n (track.completionRewards?.xpBonus ?? 0);\n\n const completedSteps = progress.completedStepIds.length;\n const totalSteps = track.steps.length;\n const percentComplete =\n totalSteps > 0 ? (completedSteps / totalSteps) * 100 : 0;\n\n // Group steps by metadata surface for mastery rings\n const surfaces = new Map<string, { total: number; completed: number }>();\n track.steps.forEach((step) => {\n const surface = (step.metadata?.surface as string) ?? 'general';\n const current = surfaces.get(surface) ?? { total: 0, completed: 0 };\n surfaces.set(surface, {\n total: current.total + 1,\n completed:\n current.completed +\n (progress.completedStepIds.includes(step.id) ? 1 : 0),\n });\n });\n\n const surfaceColors: ('green' | 'blue' | 'violet' | 'orange')[] = [\n 'green',\n 'blue',\n 'violet',\n 'orange',\n ];\n\n return (\n <div className=\"space-y-6\">\n {/* XP Progress */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <span>⚡</span>\n <span>Experience Points</span>\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-violet-500\">\n {progress.xpEarned.toLocaleString()}\n </span>\n <span className=\"text-muted-foreground\">\n / {totalXp.toLocaleString()} XP\n </span>\n </div>\n <XpBar\n current={progress.xpEarned}\n max={totalXp}\n showLabel={false}\n size=\"lg\"\n />\n\n {track.completionRewards?.xpBonus && percentComplete < 100 && (\n <p className=\"text-muted-foreground text-sm\">\n 🎁 Complete all steps for a{' '}\n <span className=\"font-semibold text-green-500\">\n +{track.completionRewards.xpBonus} XP\n </span>{' '}\n bonus!\n </p>\n )}\n </CardContent>\n </Card>\n\n {/* Mastery Rings */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <span>🎯</span>\n <span>Skill Mastery</span>\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"flex flex-wrap justify-center gap-6\">\n {Array.from(surfaces.entries()).map(([surface, data], index) => (\n <MasteryRing\n key={surface}\n label={surface.charAt(0).toUpperCase() + surface.slice(1)}\n percentage={(data.completed / data.total) * 100}\n color={surfaceColors[index % surfaceColors.length]}\n size=\"lg\"\n />\n ))}\n <MasteryRing\n label=\"Overall\"\n percentage={percentComplete}\n color=\"violet\"\n size=\"lg\"\n />\n </div>\n </CardContent>\n </Card>\n\n {/* Badges */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <span>🏅</span>\n <span>Badges Earned</span>\n </CardTitle>\n </CardHeader>\n <CardContent>\n <BadgeDisplay badges={progress.badges} size=\"lg\" maxVisible={10} />\n {progress.badges.length === 0 && (\n <p className=\"text-muted-foreground text-sm\">\n Complete the track to earn your first badge!\n </p>\n )}\n </CardContent>\n </Card>\n\n {/* Step Breakdown */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <span>📊</span>\n <span>Step Breakdown</span>\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n {track.steps.map((step) => {\n const isCompleted = progress.completedStepIds.includes(step.id);\n return (\n <div\n key={step.id}\n className=\"flex items-center justify-between rounded-lg border p-3\"\n >\n <div className=\"flex items-center gap-3\">\n <span\n className={\n isCompleted ? 'text-green-500' : 'text-muted-foreground'\n }\n >\n {isCompleted ? '✓' : '○'}\n </span>\n <span\n className={\n isCompleted\n ? 'text-foreground'\n : 'text-muted-foreground'\n }\n >\n {step.title}\n </span>\n </div>\n {step.xpReward && (\n <span\n className={`text-sm font-medium ${isCompleted ? 'text-green-500' : 'text-muted-foreground'}`}\n >\n {isCompleted ? '+' : ''}\n {step.xpReward} XP\n </span>\n )}\n </div>\n );\n })}\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { LearningViewProps } from "@contractspec/example.learning-journey-ui-shared";
2
+ import * as react_jsx_runtime5 from "react/jsx-runtime";
3
+
4
+ //#region src/views/Steps.d.ts
5
+ declare function Steps({
6
+ track,
7
+ progress,
8
+ onStepComplete
9
+ }: LearningViewProps): react_jsx_runtime5.JSX.Element;
10
+ //#endregion
11
+ export { Steps };
12
+ //# sourceMappingURL=Steps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Steps.d.ts","names":[],"sources":["../../src/views/Steps.tsx"],"sourcesContent":[],"mappings":";;;;iBAKgB,KAAA;;;;GAA2C,oBAAiB,kBAAA,CAAA,GAAA,CAAA"}
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import { FlashCard } from "../components/FlashCard.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+
6
+ //#region src/views/Steps.tsx
7
+ function Steps({ track, progress, onStepComplete }) {
8
+ const currentStepIndex = track.steps.findIndex((s) => !progress.completedStepIds.includes(s.id));
9
+ return /* @__PURE__ */ jsxs("div", {
10
+ className: "space-y-6",
11
+ children: [
12
+ /* @__PURE__ */ jsxs("div", {
13
+ className: "text-center",
14
+ children: [/* @__PURE__ */ jsx("h2", {
15
+ className: "text-xl font-bold",
16
+ children: "Complete Your Challenges"
17
+ }), /* @__PURE__ */ jsx("p", {
18
+ className: "text-muted-foreground",
19
+ children: "Tap each card to reveal the action, then mark as complete"
20
+ })]
21
+ }),
22
+ /* @__PURE__ */ jsx("div", {
23
+ className: "grid gap-4 md:grid-cols-2",
24
+ children: track.steps.map((step, index) => {
25
+ return /* @__PURE__ */ jsx(FlashCard, {
26
+ step,
27
+ isCompleted: progress.completedStepIds.includes(step.id),
28
+ isCurrent: index === currentStepIndex,
29
+ onComplete: () => onStepComplete?.(step.id)
30
+ }, step.id);
31
+ })
32
+ }),
33
+ /* @__PURE__ */ jsxs("div", {
34
+ className: "text-muted-foreground text-center text-sm",
35
+ children: [
36
+ progress.completedStepIds.length,
37
+ " of ",
38
+ track.steps.length,
39
+ " completed",
40
+ track.completionRewards?.xpBonus && /* @__PURE__ */ jsxs("span", {
41
+ className: "ml-2 text-green-500",
42
+ children: [
43
+ "(+",
44
+ track.completionRewards.xpBonus,
45
+ " XP bonus on completion)"
46
+ ]
47
+ })
48
+ ]
49
+ })
50
+ ]
51
+ });
52
+ }
53
+
54
+ //#endregion
55
+ export { Steps };
56
+ //# sourceMappingURL=Steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Steps.js","names":[],"sources":["../../src/views/Steps.tsx"],"sourcesContent":["'use client';\n\nimport { FlashCard } from '../components/FlashCard';\nimport type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';\n\nexport function Steps({ track, progress, onStepComplete }: LearningViewProps) {\n const currentStepIndex = track.steps.findIndex(\n (s) => !progress.completedStepIds.includes(s.id)\n );\n\n return (\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"text-center\">\n <h2 className=\"text-xl font-bold\">Complete Your Challenges</h2>\n <p className=\"text-muted-foreground\">\n Tap each card to reveal the action, then mark as complete\n </p>\n </div>\n\n {/* Card Stack */}\n <div className=\"grid gap-4 md:grid-cols-2\">\n {track.steps.map((step, index) => {\n const isCompleted = progress.completedStepIds.includes(step.id);\n const isCurrent = index === currentStepIndex;\n\n return (\n <FlashCard\n key={step.id}\n step={step}\n isCompleted={isCompleted}\n isCurrent={isCurrent}\n onComplete={() => onStepComplete?.(step.id)}\n />\n );\n })}\n </div>\n\n {/* Progress Summary */}\n <div className=\"text-muted-foreground text-center text-sm\">\n {progress.completedStepIds.length} of {track.steps.length} completed\n {track.completionRewards?.xpBonus && (\n <span className=\"ml-2 text-green-500\">\n (+{track.completionRewards.xpBonus} XP bonus on completion)\n </span>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAKA,SAAgB,MAAM,EAAE,OAAO,UAAU,kBAAqC;CAC5E,MAAM,mBAAmB,MAAM,MAAM,WAClC,MAAM,CAAC,SAAS,iBAAiB,SAAS,EAAE,GAAG,CACjD;AAED,QACE,qBAAC;EAAI,WAAU;;GAEb,qBAAC;IAAI,WAAU;eACb,oBAAC;KAAG,WAAU;eAAoB;MAA6B,EAC/D,oBAAC;KAAE,WAAU;eAAwB;MAEjC;KACA;GAGN,oBAAC;IAAI,WAAU;cACZ,MAAM,MAAM,KAAK,MAAM,UAAU;AAIhC,YACE,oBAAC;MAEO;MACN,aAPgB,SAAS,iBAAiB,SAAS,KAAK,GAAG;MAQ3D,WAPc,UAAU;MAQxB,kBAAkB,iBAAiB,KAAK,GAAG;QAJtC,KAAK,GAKV;MAEJ;KACE;GAGN,qBAAC;IAAI,WAAU;;KACZ,SAAS,iBAAiB;KAAO;KAAK,MAAM,MAAM;KAAO;KACzD,MAAM,mBAAmB,WACxB,qBAAC;MAAK,WAAU;;OAAsB;OACjC,MAAM,kBAAkB;OAAQ;;OAC9B;;KAEL;;GACF"}
@@ -0,0 +1,11 @@
1
+ import { LearningViewProps } from "@contractspec/example.learning-journey-ui-shared";
2
+ import * as react_jsx_runtime6 from "react/jsx-runtime";
3
+
4
+ //#region src/views/Timeline.d.ts
5
+ declare function Timeline({
6
+ track,
7
+ progress
8
+ }: LearningViewProps): react_jsx_runtime6.JSX.Element;
9
+ //#endregion
10
+ export { Timeline };
11
+ //# sourceMappingURL=Timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.d.ts","names":[],"sources":["../../src/views/Timeline.tsx"],"sourcesContent":[],"mappings":";;;;iBAWgB,QAAA;;;GAA8B,oBAAiB,kBAAA,CAAA,GAAA,CAAA"}
@@ -0,0 +1,133 @@
1
+ 'use client';
2
+
3
+ import { DayCalendar } from "../components/DayCalendar.js";
4
+ import { Card, CardContent, CardHeader, CardTitle } from "@contractspec/lib.ui-kit-web/ui/card";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+
7
+ //#region src/views/Timeline.tsx
8
+ function Timeline({ track, progress }) {
9
+ if (track.steps.some((s) => s.availability?.unlockOnDay !== void 0)) {
10
+ const totalDays = Math.max(...track.steps.map((s) => s.availability?.unlockOnDay ?? 1), 7);
11
+ const completedDays = track.steps.filter((s) => progress.completedStepIds.includes(s.id)).map((s) => s.availability?.unlockOnDay ?? 1);
12
+ const currentDay = track.steps.find((s) => !progress.completedStepIds.includes(s.id))?.availability?.unlockOnDay ?? 1;
13
+ return /* @__PURE__ */ jsxs("div", {
14
+ className: "space-y-6",
15
+ children: [
16
+ /* @__PURE__ */ jsxs("div", {
17
+ className: "text-center",
18
+ children: [/* @__PURE__ */ jsx("h2", {
19
+ className: "text-xl font-bold",
20
+ children: track.name
21
+ }), /* @__PURE__ */ jsx("p", {
22
+ className: "text-muted-foreground",
23
+ children: "Complete each day's challenge to progress"
24
+ })]
25
+ }),
26
+ /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, {
27
+ className: "flex items-center gap-2",
28
+ children: [/* @__PURE__ */ jsx("span", { children: "📅" }), /* @__PURE__ */ jsx("span", { children: "Your Journey" })]
29
+ }) }), /* @__PURE__ */ jsx(CardContent, {
30
+ className: "flex justify-center",
31
+ children: /* @__PURE__ */ jsx(DayCalendar, {
32
+ totalDays,
33
+ currentDay,
34
+ completedDays
35
+ })
36
+ })] }),
37
+ /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs(CardTitle, {
38
+ className: "flex items-center gap-2",
39
+ children: [/* @__PURE__ */ jsx("span", { children: "📝" }), /* @__PURE__ */ jsx("span", { children: "Daily Challenges" })]
40
+ }) }), /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", {
41
+ className: "space-y-3",
42
+ children: track.steps.map((step) => {
43
+ const day = step.availability?.unlockOnDay ?? 1;
44
+ const isCompleted = progress.completedStepIds.includes(step.id);
45
+ const isLocked = day > currentDay;
46
+ return /* @__PURE__ */ jsxs("div", {
47
+ className: `flex items-start gap-4 rounded-lg border p-4 ${isLocked ? "opacity-50" : ""}`,
48
+ children: [
49
+ /* @__PURE__ */ jsx("div", {
50
+ className: "bg-muted flex h-10 w-10 shrink-0 items-center justify-center rounded-lg font-semibold",
51
+ children: isCompleted ? "✓" : isLocked ? "🔒" : day
52
+ }),
53
+ /* @__PURE__ */ jsxs("div", {
54
+ className: "flex-1",
55
+ children: [/* @__PURE__ */ jsx("h4", {
56
+ className: "font-semibold",
57
+ children: step.title
58
+ }), /* @__PURE__ */ jsx("p", {
59
+ className: "text-muted-foreground text-sm",
60
+ children: step.description
61
+ })]
62
+ }),
63
+ step.xpReward && /* @__PURE__ */ jsxs("span", {
64
+ className: `text-sm font-medium ${isCompleted ? "text-green-500" : "text-muted-foreground"}`,
65
+ children: [
66
+ "+",
67
+ step.xpReward,
68
+ " XP"
69
+ ]
70
+ })
71
+ ]
72
+ }, step.id);
73
+ })
74
+ }) })] })
75
+ ]
76
+ });
77
+ }
78
+ return /* @__PURE__ */ jsxs("div", {
79
+ className: "space-y-6",
80
+ children: [/* @__PURE__ */ jsxs("div", {
81
+ className: "text-center",
82
+ children: [/* @__PURE__ */ jsx("h2", {
83
+ className: "text-xl font-bold",
84
+ children: "Learning Path"
85
+ }), /* @__PURE__ */ jsx("p", {
86
+ className: "text-muted-foreground",
87
+ children: "Follow the steps to master this skill"
88
+ })]
89
+ }), /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, {
90
+ className: "p-6",
91
+ children: /* @__PURE__ */ jsxs("div", {
92
+ className: "relative",
93
+ children: [/* @__PURE__ */ jsx("div", { className: "bg-border absolute top-0 left-5 h-full w-0.5" }), /* @__PURE__ */ jsx("div", {
94
+ className: "space-y-6",
95
+ children: track.steps.map((step, index) => {
96
+ const isCompleted = progress.completedStepIds.includes(step.id);
97
+ const isCurrent = !isCompleted && track.steps.slice(0, index).every((s) => progress.completedStepIds.includes(s.id));
98
+ return /* @__PURE__ */ jsxs("div", {
99
+ className: "relative flex gap-4 pl-2",
100
+ children: [/* @__PURE__ */ jsx("div", {
101
+ className: `relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 ${isCompleted ? "border-green-500 bg-green-500 text-white" : isCurrent ? "border-violet-500 bg-violet-500 text-white" : "border-border bg-background"}`,
102
+ children: isCompleted ? "✓" : index + 1
103
+ }), /* @__PURE__ */ jsx("div", {
104
+ className: "flex-1 pb-4",
105
+ children: /* @__PURE__ */ jsxs("div", {
106
+ className: "flex items-start justify-between gap-2",
107
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h4", {
108
+ className: `font-semibold ${isCompleted ? "text-foreground" : isCurrent ? "text-violet-500" : "text-muted-foreground"}`,
109
+ children: step.title
110
+ }), /* @__PURE__ */ jsx("p", {
111
+ className: "text-muted-foreground mt-1 text-sm",
112
+ children: step.description
113
+ })] }), step.xpReward && /* @__PURE__ */ jsxs("span", {
114
+ className: `shrink-0 rounded-full px-2 py-1 text-xs font-semibold ${isCompleted ? "bg-green-500/10 text-green-500" : "bg-muted text-muted-foreground"}`,
115
+ children: [
116
+ "+",
117
+ step.xpReward,
118
+ " XP"
119
+ ]
120
+ })]
121
+ })
122
+ })]
123
+ }, step.id);
124
+ })
125
+ })]
126
+ })
127
+ }) })]
128
+ });
129
+ }
130
+
131
+ //#endregion
132
+ export { Timeline };
133
+ //# sourceMappingURL=Timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.js","names":[],"sources":["../../src/views/Timeline.tsx"],"sourcesContent":["'use client';\n\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n} from '@contractspec/lib.ui-kit-web/ui/card';\nimport { DayCalendar } from '../components/DayCalendar';\nimport type { LearningViewProps } from '@contractspec/example.learning-journey-ui-shared';\n\nexport function Timeline({ track, progress }: LearningViewProps) {\n // Check if this is a quest with day unlocks\n const hasQuestDays = track.steps.some(\n (s) => s.availability?.unlockOnDay !== undefined\n );\n\n if (hasQuestDays) {\n // Quest-style calendar view\n const totalDays = Math.max(\n ...track.steps.map((s) => s.availability?.unlockOnDay ?? 1),\n 7\n );\n\n const completedDays = track.steps\n .filter((s) => progress.completedStepIds.includes(s.id))\n .map((s) => s.availability?.unlockOnDay ?? 1);\n\n // Current day is the first incomplete day\n const currentDay =\n track.steps.find((s) => !progress.completedStepIds.includes(s.id))\n ?.availability?.unlockOnDay ?? 1;\n\n return (\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"text-center\">\n <h2 className=\"text-xl font-bold\">{track.name}</h2>\n <p className=\"text-muted-foreground\">\n Complete each day's challenge to progress\n </p>\n </div>\n\n {/* Calendar Grid */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <span>📅</span>\n <span>Your Journey</span>\n </CardTitle>\n </CardHeader>\n <CardContent className=\"flex justify-center\">\n <DayCalendar\n totalDays={totalDays}\n currentDay={currentDay}\n completedDays={completedDays}\n />\n </CardContent>\n </Card>\n\n {/* Daily Steps */}\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <span>📝</span>\n <span>Daily Challenges</span>\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {track.steps.map((step) => {\n const day = step.availability?.unlockOnDay ?? 1;\n const isCompleted = progress.completedStepIds.includes(step.id);\n const isLocked = day > currentDay;\n\n return (\n <div\n key={step.id}\n className={`flex items-start gap-4 rounded-lg border p-4 ${\n isLocked ? 'opacity-50' : ''\n }`}\n >\n <div className=\"bg-muted flex h-10 w-10 shrink-0 items-center justify-center rounded-lg font-semibold\">\n {isCompleted ? '✓' : isLocked ? '🔒' : day}\n </div>\n <div className=\"flex-1\">\n <h4 className=\"font-semibold\">{step.title}</h4>\n <p className=\"text-muted-foreground text-sm\">\n {step.description}\n </p>\n </div>\n {step.xpReward && (\n <span\n className={`text-sm font-medium ${\n isCompleted\n ? 'text-green-500'\n : 'text-muted-foreground'\n }`}\n >\n +{step.xpReward} XP\n </span>\n )}\n </div>\n );\n })}\n </div>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n // Drill-style timeline (step order)\n return (\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"text-center\">\n <h2 className=\"text-xl font-bold\">Learning Path</h2>\n <p className=\"text-muted-foreground\">\n Follow the steps to master this skill\n </p>\n </div>\n\n {/* Timeline */}\n <Card>\n <CardContent className=\"p-6\">\n <div className=\"relative\">\n {/* Vertical line */}\n <div className=\"bg-border absolute top-0 left-5 h-full w-0.5\" />\n\n {/* Steps */}\n <div className=\"space-y-6\">\n {track.steps.map((step, index) => {\n const isCompleted = progress.completedStepIds.includes(step.id);\n const isCurrent =\n !isCompleted &&\n track.steps\n .slice(0, index)\n .every((s) => progress.completedStepIds.includes(s.id));\n\n return (\n <div key={step.id} className=\"relative flex gap-4 pl-2\">\n {/* Node */}\n <div\n className={`relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 ${\n isCompleted\n ? 'border-green-500 bg-green-500 text-white'\n : isCurrent\n ? 'border-violet-500 bg-violet-500 text-white'\n : 'border-border bg-background'\n }`}\n >\n {isCompleted ? '✓' : index + 1}\n </div>\n\n {/* Content */}\n <div className=\"flex-1 pb-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <h4\n className={`font-semibold ${\n isCompleted\n ? 'text-foreground'\n : isCurrent\n ? 'text-violet-500'\n : 'text-muted-foreground'\n }`}\n >\n {step.title}\n </h4>\n <p className=\"text-muted-foreground mt-1 text-sm\">\n {step.description}\n </p>\n </div>\n {step.xpReward && (\n <span\n className={`shrink-0 rounded-full px-2 py-1 text-xs font-semibold ${\n isCompleted\n ? 'bg-green-500/10 text-green-500'\n : 'bg-muted text-muted-foreground'\n }`}\n >\n +{step.xpReward} XP\n </span>\n )}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { Overview } from "./Overview.js";
2
+ import { Steps } from "./Steps.js";
3
+ import { Progress } from "./Progress.js";
4
+ import { Timeline } from "./Timeline.js";
5
+ export { Overview, Progress, Steps, Timeline };
@@ -0,0 +1,6 @@
1
+ import { Overview } from "./Overview.js";
2
+ import { Steps } from "./Steps.js";
3
+ import { Progress } from "./Progress.js";
4
+ import { Timeline } from "./Timeline.js";
5
+
6
+ export { Overview, Progress, Steps, Timeline };
package/example.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from './src/example';
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@contractspec/example.learning-journey-ui-gamified",
3
+ "version": "1.44.0",
4
+ "description": "Duolingo-style gamified learning UI for drills and quests.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js",
10
+ "./components": "./dist/components/index.js",
11
+ "./components/DayCalendar": "./dist/components/DayCalendar.js",
12
+ "./components/FlashCard": "./dist/components/FlashCard.js",
13
+ "./components/MasteryRing": "./dist/components/MasteryRing.js",
14
+ "./docs": "./dist/docs/index.js",
15
+ "./docs/learning-journey-ui-gamified.docblock": "./dist/docs/learning-journey-ui-gamified.docblock.js",
16
+ "./example": "./dist/example.js",
17
+ "./GamifiedMiniApp": "./dist/GamifiedMiniApp.js",
18
+ "./views": "./dist/views/index.js",
19
+ "./views/Overview": "./dist/views/Overview.js",
20
+ "./views/Progress": "./dist/views/Progress.js",
21
+ "./views/Steps": "./dist/views/Steps.js",
22
+ "./views/Timeline": "./dist/views/Timeline.js",
23
+ "./*": "./*"
24
+ },
25
+ "scripts": {
26
+ "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
27
+ "publish:pkg:canary": "bun publish:pkg --tag canary",
28
+ "build": "bun build:types && bun build:bundle",
29
+ "build:bundle": "tsdown",
30
+ "build:types": "tsc --noEmit",
31
+ "dev": "bun build:bundle --watch",
32
+ "clean": "rimraf dist .turbo",
33
+ "lint": "bun lint:fix",
34
+ "lint:fix": "eslint src --fix",
35
+ "lint:check": "eslint src",
36
+ "test": "bun test"
37
+ },
38
+ "dependencies": {
39
+ "@contractspec/lib.schema": "1.44.0",
40
+ "@contractspec/lib.contracts": "1.44.0",
41
+ "@contractspec/example.learning-journey-ui-shared": "1.44.0",
42
+ "@contractspec/example.learning-journey-duo-drills": "1.44.0",
43
+ "@contractspec/example.learning-journey-quest-challenges": "1.44.0",
44
+ "@contractspec/module.learning-journey": "1.44.0",
45
+ "@contractspec/lib.design-system": "1.44.0",
46
+ "@contractspec/lib.ui-kit-web": "1.44.0",
47
+ "react": "19.2.3"
48
+ },
49
+ "devDependencies": {
50
+ "@contractspec/tool.tsdown": "1.44.0",
51
+ "@contractspec/tool.typescript": "1.44.0",
52
+ "@types/react": "^19.1.6",
53
+ "tsdown": "^0.18.3",
54
+ "typescript": "^5.9.3"
55
+ },
56
+ "peerDependencies": {
57
+ "react": "^19.2.3"
58
+ },
59
+ "module": "./dist/index.js",
60
+ "publishConfig": {
61
+ "exports": {
62
+ ".": "./dist/index.js",
63
+ "./example": "./dist/example.js",
64
+ "./docs": "./dist/docs/index.js",
65
+ "./views": "./dist/views/index.js",
66
+ "./components": "./dist/components/index.js",
67
+ "./*": "./*"
68
+ },
69
+ "registry": "https://registry.npmjs.org/",
70
+ "access": "public"
71
+ },
72
+ "license": "MIT",
73
+ "repository": {
74
+ "type": "git",
75
+ "url": "https://github.com/lssm-tech/contractspec.git",
76
+ "directory": "packages/examples/learning-journey-ui-gamified"
77
+ },
78
+ "homepage": "https://contractspec.io"
79
+ }
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback } from 'react';
4
+ import { Card, CardContent } from '@contractspec/lib.ui-kit-web/ui/card';
5
+ import {
6
+ ViewTabs,
7
+ useLearningProgress,
8
+ type LearningView,
9
+ type LearningMiniAppProps,
10
+ } from '@contractspec/example.learning-journey-ui-shared';
11
+ import { Overview } from './views/Overview';
12
+ import { Steps } from './views/Steps';
13
+ import { Progress } from './views/Progress';
14
+ import { Timeline } from './views/Timeline';
15
+
16
+ type GamifiedMiniAppProps = Omit<LearningMiniAppProps, 'progress'> & {
17
+ progress?: LearningMiniAppProps['progress'];
18
+ };
19
+
20
+ export function GamifiedMiniApp({
21
+ track,
22
+ progress: externalProgress,
23
+ onStepComplete: externalOnStepComplete,
24
+ onViewChange,
25
+ initialView = 'overview',
26
+ }: GamifiedMiniAppProps) {
27
+ const [currentView, setCurrentView] = useState<LearningView>(initialView);
28
+
29
+ // Use internal progress if not provided externally
30
+ const { progress: internalProgress, completeStep: internalCompleteStep } =
31
+ useLearningProgress(track);
32
+
33
+ const progress = externalProgress ?? internalProgress;
34
+
35
+ const handleViewChange = useCallback(
36
+ (view: LearningView) => {
37
+ setCurrentView(view);
38
+ onViewChange?.(view);
39
+ },
40
+ [onViewChange]
41
+ );
42
+
43
+ const handleStepComplete = useCallback(
44
+ (stepId: string) => {
45
+ if (externalOnStepComplete) {
46
+ externalOnStepComplete(stepId);
47
+ } else {
48
+ internalCompleteStep(stepId);
49
+ }
50
+ },
51
+ [externalOnStepComplete, internalCompleteStep]
52
+ );
53
+
54
+ const handleStartFromOverview = useCallback(() => {
55
+ setCurrentView('steps');
56
+ onViewChange?.('steps');
57
+ }, [onViewChange]);
58
+
59
+ const renderView = () => {
60
+ const viewProps = {
61
+ track,
62
+ progress,
63
+ onStepComplete: handleStepComplete,
64
+ };
65
+
66
+ switch (currentView) {
67
+ case 'overview':
68
+ return <Overview {...viewProps} onStart={handleStartFromOverview} />;
69
+ case 'steps':
70
+ return <Steps {...viewProps} />;
71
+ case 'progress':
72
+ return <Progress {...viewProps} />;
73
+ case 'timeline':
74
+ return <Timeline {...viewProps} />;
75
+ default:
76
+ return <Overview {...viewProps} onStart={handleStartFromOverview} />;
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div className="space-y-6">
82
+ {/* Navigation */}
83
+ <Card>
84
+ <CardContent className="p-4">
85
+ <ViewTabs currentView={currentView} onViewChange={handleViewChange} />
86
+ </CardContent>
87
+ </Card>
88
+
89
+ {/* Current View */}
90
+ {renderView()}
91
+ </div>
92
+ );
93
+ }