@lssm/example.learning-journey-ui-gamified 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,22 @@
1
+ $ bun build:bundle && bun build:types
2
+ $ tsdown
3
+ ℹ tsdown v0.17.0 powered by rolldown v1.0.0-beta.53
4
+ ℹ config file: /home/runner/work/contractspec/contractspec/packages/examples/learning-journey-ui-gamified/tsdown.config.js
5
+ ℹ entry: src/index.ts, src/views/index.ts, src/components/index.ts
6
+ ℹ target: esnext
7
+ ℹ tsconfig: tsconfig.json
8
+ ℹ Build start
9
+ ℹ dist/index.mjs  2.36 kB │ gzip: 0.83 kB
10
+ ℹ dist/components/index.mjs  0.18 kB │ gzip: 0.15 kB
11
+ ℹ dist/views/index.mjs  0.18 kB │ gzip: 0.15 kB
12
+ ℹ dist/views-B-DapxWu.mjs 18.35 kB │ gzip: 3.52 kB
13
+ ℹ dist/DayCalendar-Cha869Bi.mjs  6.20 kB │ gzip: 1.87 kB
14
+ ℹ dist/components-kh0CpIG2.mjs  0.01 kB │ gzip: 0.03 kB
15
+ ℹ dist/index.d.mts  0.80 kB │ gzip: 0.41 kB
16
+ ℹ dist/views/index.d.mts  0.14 kB │ gzip: 0.12 kB
17
+ ℹ dist/components/index.d.mts  0.14 kB │ gzip: 0.12 kB
18
+ ℹ dist/index-DrzEZfnF.d.mts  1.13 kB │ gzip: 0.47 kB
19
+ ℹ dist/index-Bi5V3Ihq.d.mts  0.92 kB │ gzip: 0.34 kB
20
+ ℹ 11 files, total: 30.42 kB
21
+ ✔ Build complete in 8002ms
22
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @lssm/example.learning-journey-ui-gamified
2
+
3
+ ## 0.0.0-canary-20251212210835
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [3086383]
8
+ - @lssm/lib.design-system@0.0.0-canary-20251212210835
9
+ - @lssm/lib.ui-kit-web@0.0.0-canary-20251212210835
10
+ - @lssm/example.learning-journey-ui-shared@0.0.0-canary-20251212210835
11
+ - @lssm/example.learning-journey.duo-drills@0.0.0-canary-20251212210835
12
+ - @lssm/example.learning-journey.quest-challenges@0.0.0-canary-20251212210835
13
+ - @lssm/module.learning-journey@0.0.0-canary-20251212210835
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @lssm/example.learning-journey-ui-gamified
2
+
3
+ Duolingo-style gamified learning UI for drills and quests.
4
+
5
+ ## Features
6
+
7
+ - **Overview**: Hero card with XP, streak, and next challenge preview
8
+ - **Steps**: Interactive flashcard-style step cards
9
+ - **Progress**: Mastery rings, XP bar, and badge display
10
+ - **Timeline**: Day calendar for quests, step timeline for drills
11
+
12
+ ## Usage
13
+
14
+ ```tsx
15
+ import { GamifiedMiniApp } from '@lssm/example.learning-journey-ui-gamified';
16
+ import { drillsLanguageBasicsTrack } from '@lssm/example.learning-journey.duo-drills/track';
17
+
18
+ function MyApp() {
19
+ return (
20
+ <GamifiedMiniApp track={drillsLanguageBasicsTrack} initialView="overview" />
21
+ );
22
+ }
23
+ ```
24
+
25
+ ## Components
26
+
27
+ - **FlashCard** - Tappable card that reveals action on flip
28
+ - **MasteryRing** - Circular progress indicator
29
+ - **DayCalendar** - 7-day grid for quest progress
30
+
@@ -0,0 +1,178 @@
1
+ import { useState } from "react";
2
+ import { Card, CardContent } from "@lssm/lib.ui-kit-web/ui/card";
3
+ import { Button } from "@lssm/lib.design-system";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { cn } from "@lssm/lib.ui-kit-web/ui/utils";
6
+
7
+ //#region src/components/FlashCard.tsx
8
+ function FlashCard({ step, isCompleted, isCurrent, onComplete }) {
9
+ const [isFlipped, setIsFlipped] = useState(false);
10
+ return /* @__PURE__ */ jsx(Card, {
11
+ className: cn("relative cursor-pointer overflow-hidden transition-all duration-300", isCurrent && "ring-primary ring-2", isCompleted && "opacity-60"),
12
+ onClick: () => !isCompleted && setIsFlipped(!isFlipped),
13
+ children: /* @__PURE__ */ jsxs(CardContent, {
14
+ className: "p-6",
15
+ children: [/* @__PURE__ */ jsxs("div", {
16
+ className: cn("space-y-4 transition-opacity duration-200", isFlipped ? "opacity-0" : "opacity-100"),
17
+ children: [
18
+ /* @__PURE__ */ jsxs("div", {
19
+ className: "flex items-start justify-between",
20
+ children: [/* @__PURE__ */ jsxs("div", {
21
+ className: "flex-1",
22
+ children: [/* @__PURE__ */ jsx("h3", {
23
+ className: "text-lg font-semibold",
24
+ children: step.title
25
+ }), step.description && /* @__PURE__ */ jsx("p", {
26
+ className: "text-muted-foreground mt-1 text-sm",
27
+ children: step.description
28
+ })]
29
+ }), step.xpReward && /* @__PURE__ */ jsxs("span", {
30
+ className: "rounded-full bg-green-500/10 px-2 py-1 text-xs font-semibold text-green-500",
31
+ children: [
32
+ "+",
33
+ step.xpReward,
34
+ " XP"
35
+ ]
36
+ })]
37
+ }),
38
+ isCompleted && /* @__PURE__ */ jsxs("div", {
39
+ className: "flex items-center gap-2 text-green-500",
40
+ children: [/* @__PURE__ */ jsx("span", { children: "✓" }), /* @__PURE__ */ jsx("span", {
41
+ className: "text-sm font-medium",
42
+ children: "Completed"
43
+ })]
44
+ }),
45
+ isCurrent && !isCompleted && /* @__PURE__ */ jsx("p", {
46
+ className: "text-muted-foreground text-xs",
47
+ children: "Tap to reveal action"
48
+ })
49
+ ]
50
+ }), isFlipped && !isCompleted && /* @__PURE__ */ jsxs("div", {
51
+ className: "absolute inset-0 flex flex-col items-center justify-center gap-4 bg-gradient-to-br from-violet-500/10 to-violet-600/10 p-6",
52
+ children: [/* @__PURE__ */ jsx("p", {
53
+ className: "text-center text-sm",
54
+ children: step.instructions ?? "Complete this step to earn XP"
55
+ }), /* @__PURE__ */ jsxs("div", {
56
+ className: "flex gap-2",
57
+ children: [/* @__PURE__ */ jsx(Button, {
58
+ variant: "outline",
59
+ size: "sm",
60
+ onClick: () => setIsFlipped(false),
61
+ children: "Back"
62
+ }), /* @__PURE__ */ jsx(Button, {
63
+ size: "sm",
64
+ onClick: (e) => {
65
+ e.stopPropagation();
66
+ onComplete?.();
67
+ },
68
+ children: "Mark Complete"
69
+ })]
70
+ })]
71
+ })]
72
+ })
73
+ });
74
+ }
75
+
76
+ //#endregion
77
+ //#region src/components/MasteryRing.tsx
78
+ const sizeStyles = {
79
+ sm: {
80
+ container: "h-16 w-16",
81
+ text: "text-xs",
82
+ ring: 48,
83
+ stroke: 4
84
+ },
85
+ md: {
86
+ container: "h-24 w-24",
87
+ text: "text-sm",
88
+ ring: 72,
89
+ stroke: 6
90
+ },
91
+ lg: {
92
+ container: "h-32 w-32",
93
+ text: "text-base",
94
+ ring: 96,
95
+ stroke: 8
96
+ }
97
+ };
98
+ const colorStyles = {
99
+ green: "stroke-green-500",
100
+ blue: "stroke-blue-500",
101
+ violet: "stroke-violet-500",
102
+ orange: "stroke-orange-500"
103
+ };
104
+ function MasteryRing({ label, percentage, size = "md", color = "violet" }) {
105
+ const styles = sizeStyles[size];
106
+ const radius = (styles.ring - styles.stroke) / 2;
107
+ const circumference = 2 * Math.PI * radius;
108
+ const strokeDashoffset = circumference - percentage / 100 * circumference;
109
+ return /* @__PURE__ */ jsxs("div", {
110
+ className: cn("relative flex flex-col items-center gap-1", styles.container),
111
+ children: [
112
+ /* @__PURE__ */ jsxs("svg", {
113
+ className: "absolute -rotate-90",
114
+ width: styles.ring,
115
+ height: styles.ring,
116
+ viewBox: `0 0 ${styles.ring} ${styles.ring}`,
117
+ children: [/* @__PURE__ */ jsx("circle", {
118
+ cx: styles.ring / 2,
119
+ cy: styles.ring / 2,
120
+ r: radius,
121
+ fill: "none",
122
+ strokeWidth: styles.stroke,
123
+ className: "stroke-muted"
124
+ }), /* @__PURE__ */ jsx("circle", {
125
+ cx: styles.ring / 2,
126
+ cy: styles.ring / 2,
127
+ r: radius,
128
+ fill: "none",
129
+ strokeWidth: styles.stroke,
130
+ strokeLinecap: "round",
131
+ strokeDasharray: circumference,
132
+ strokeDashoffset,
133
+ className: cn("transition-all duration-500", colorStyles[color])
134
+ })]
135
+ }),
136
+ /* @__PURE__ */ jsx("div", {
137
+ className: "flex h-full flex-col items-center justify-center",
138
+ children: /* @__PURE__ */ jsxs("span", {
139
+ className: cn("font-bold", styles.text),
140
+ children: [Math.round(percentage), "%"]
141
+ })
142
+ }),
143
+ /* @__PURE__ */ jsx("span", {
144
+ className: cn("text-muted-foreground mt-1 truncate", styles.text),
145
+ children: label
146
+ })
147
+ ]
148
+ });
149
+ }
150
+
151
+ //#endregion
152
+ //#region src/components/DayCalendar.tsx
153
+ function DayCalendar({ totalDays, currentDay, completedDays }) {
154
+ return /* @__PURE__ */ jsx("div", {
155
+ className: "grid grid-cols-7 gap-2",
156
+ children: Array.from({ length: totalDays }, (_, i) => i + 1).map((day) => {
157
+ const isCompleted = completedDays.includes(day);
158
+ const isCurrent = day === currentDay;
159
+ const isLocked = day > currentDay;
160
+ return /* @__PURE__ */ jsx("div", {
161
+ className: cn("flex h-12 w-12 flex-col items-center justify-center rounded-lg border text-sm font-medium transition-all", isCompleted && "border-green-500 bg-green-500/10 text-green-500", isCurrent && !isCompleted && "border-violet-500 bg-violet-500/10 text-violet-500 ring-2 ring-violet-500/50", isLocked && "border-muted bg-muted/50 text-muted-foreground", !isCompleted && !isCurrent && !isLocked && "border-border bg-card"),
162
+ children: isCompleted ? /* @__PURE__ */ jsx("span", {
163
+ className: "text-lg",
164
+ children: "✓"
165
+ }) : isLocked ? /* @__PURE__ */ jsx("span", {
166
+ className: "text-lg",
167
+ children: "🔒"
168
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
169
+ className: "text-muted-foreground text-xs",
170
+ children: "Day"
171
+ }), /* @__PURE__ */ jsx("span", { children: day })] })
172
+ }, day);
173
+ })
174
+ });
175
+ }
176
+
177
+ //#endregion
178
+ export { MasteryRing as n, FlashCard as r, DayCalendar as t };
@@ -0,0 +1,2 @@
1
+ import { n as MasteryRing, r as FlashCard, t as DayCalendar } from "../index-DrzEZfnF.mjs";
2
+ export { DayCalendar, FlashCard, MasteryRing };
@@ -0,0 +1,4 @@
1
+ import { n as MasteryRing, r as FlashCard, t as DayCalendar } from "../DayCalendar-Cha869Bi.mjs";
2
+ import "../components-kh0CpIG2.mjs";
3
+
4
+ export { DayCalendar, FlashCard, MasteryRing };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,33 @@
1
+ import { LearningViewProps } from "@lssm/example.learning-journey-ui-shared";
2
+ import * as react_jsx_runtime1 from "react/jsx-runtime";
3
+
4
+ //#region src/views/Overview.d.ts
5
+ interface GamifiedOverviewProps extends LearningViewProps {
6
+ onStart?: () => void;
7
+ }
8
+ declare function Overview({
9
+ track,
10
+ progress,
11
+ onStart
12
+ }: GamifiedOverviewProps): react_jsx_runtime1.JSX.Element;
13
+ //#endregion
14
+ //#region src/views/Steps.d.ts
15
+ declare function Steps({
16
+ track,
17
+ progress,
18
+ onStepComplete
19
+ }: LearningViewProps): react_jsx_runtime1.JSX.Element;
20
+ //#endregion
21
+ //#region src/views/Progress.d.ts
22
+ declare function Progress({
23
+ track,
24
+ progress
25
+ }: LearningViewProps): react_jsx_runtime1.JSX.Element;
26
+ //#endregion
27
+ //#region src/views/Timeline.d.ts
28
+ declare function Timeline({
29
+ track,
30
+ progress
31
+ }: LearningViewProps): react_jsx_runtime1.JSX.Element;
32
+ //#endregion
33
+ export { Overview as i, Progress as n, Steps as r, Timeline as t };
@@ -0,0 +1,44 @@
1
+ import * as react_jsx_runtime5 from "react/jsx-runtime";
2
+ import { LearningJourneyStepSpec } from "@lssm/module.learning-journey/track-spec";
3
+
4
+ //#region src/components/FlashCard.d.ts
5
+ interface FlashCardProps {
6
+ step: LearningJourneyStepSpec;
7
+ isCompleted: boolean;
8
+ isCurrent: boolean;
9
+ onComplete?: () => void;
10
+ }
11
+ declare function FlashCard({
12
+ step,
13
+ isCompleted,
14
+ isCurrent,
15
+ onComplete
16
+ }: FlashCardProps): react_jsx_runtime5.JSX.Element;
17
+ //#endregion
18
+ //#region src/components/MasteryRing.d.ts
19
+ interface MasteryRingProps {
20
+ label: string;
21
+ percentage: number;
22
+ size?: 'sm' | 'md' | 'lg';
23
+ color?: 'green' | 'blue' | 'violet' | 'orange';
24
+ }
25
+ declare function MasteryRing({
26
+ label,
27
+ percentage,
28
+ size,
29
+ color
30
+ }: MasteryRingProps): react_jsx_runtime5.JSX.Element;
31
+ //#endregion
32
+ //#region src/components/DayCalendar.d.ts
33
+ interface DayCalendarProps {
34
+ totalDays: number;
35
+ currentDay: number;
36
+ completedDays: number[];
37
+ }
38
+ declare function DayCalendar({
39
+ totalDays,
40
+ currentDay,
41
+ completedDays
42
+ }: DayCalendarProps): react_jsx_runtime5.JSX.Element;
43
+ //#endregion
44
+ export { MasteryRing as n, FlashCard as r, DayCalendar as t };
@@ -0,0 +1,18 @@
1
+ import { n as MasteryRing, r as FlashCard, t as DayCalendar } from "./index-DrzEZfnF.mjs";
2
+ import { i as Overview, n as Progress, r as Steps, t as Timeline } from "./index-Bi5V3Ihq.mjs";
3
+ import { LearningMiniAppProps } from "@lssm/example.learning-journey-ui-shared";
4
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
5
+
6
+ //#region src/GamifiedMiniApp.d.ts
7
+ type GamifiedMiniAppProps = Omit<LearningMiniAppProps, 'progress'> & {
8
+ progress?: LearningMiniAppProps['progress'];
9
+ };
10
+ declare function GamifiedMiniApp({
11
+ track,
12
+ progress: externalProgress,
13
+ onStepComplete: externalOnStepComplete,
14
+ onViewChange,
15
+ initialView
16
+ }: GamifiedMiniAppProps): react_jsx_runtime0.JSX.Element;
17
+ //#endregion
18
+ export { DayCalendar, FlashCard, GamifiedMiniApp, MasteryRing, Overview, Progress, Steps, Timeline };
package/dist/index.mjs ADDED
@@ -0,0 +1,59 @@
1
+ import { i as Overview, n as Progress, r as Steps, t as Timeline } from "./views-B-DapxWu.mjs";
2
+ import { n as MasteryRing, r as FlashCard, t as DayCalendar } from "./DayCalendar-Cha869Bi.mjs";
3
+ import "./components-kh0CpIG2.mjs";
4
+ import { useCallback, useState } from "react";
5
+ import { Card, CardContent } from "@lssm/lib.ui-kit-web/ui/card";
6
+ import { ViewTabs, useLearningProgress } from "@lssm/example.learning-journey-ui-shared";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+
9
+ //#region src/GamifiedMiniApp.tsx
10
+ function GamifiedMiniApp({ track, progress: externalProgress, onStepComplete: externalOnStepComplete, onViewChange, initialView = "overview" }) {
11
+ const [currentView, setCurrentView] = useState(initialView);
12
+ const { progress: internalProgress, completeStep: internalCompleteStep } = useLearningProgress(track);
13
+ const progress = externalProgress ?? internalProgress;
14
+ const handleViewChange = useCallback((view) => {
15
+ setCurrentView(view);
16
+ onViewChange?.(view);
17
+ }, [onViewChange]);
18
+ const handleStepComplete = useCallback((stepId) => {
19
+ if (externalOnStepComplete) externalOnStepComplete(stepId);
20
+ else internalCompleteStep(stepId);
21
+ }, [externalOnStepComplete, internalCompleteStep]);
22
+ const handleStartFromOverview = useCallback(() => {
23
+ setCurrentView("steps");
24
+ onViewChange?.("steps");
25
+ }, [onViewChange]);
26
+ const renderView = () => {
27
+ const viewProps = {
28
+ track,
29
+ progress,
30
+ onStepComplete: handleStepComplete
31
+ };
32
+ switch (currentView) {
33
+ case "overview": return /* @__PURE__ */ jsx(Overview, {
34
+ ...viewProps,
35
+ onStart: handleStartFromOverview
36
+ });
37
+ case "steps": return /* @__PURE__ */ jsx(Steps, { ...viewProps });
38
+ case "progress": return /* @__PURE__ */ jsx(Progress, { ...viewProps });
39
+ case "timeline": return /* @__PURE__ */ jsx(Timeline, { ...viewProps });
40
+ default: return /* @__PURE__ */ jsx(Overview, {
41
+ ...viewProps,
42
+ onStart: handleStartFromOverview
43
+ });
44
+ }
45
+ };
46
+ return /* @__PURE__ */ jsxs("div", {
47
+ className: "space-y-6",
48
+ children: [/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, {
49
+ className: "p-4",
50
+ children: /* @__PURE__ */ jsx(ViewTabs, {
51
+ currentView,
52
+ onViewChange: handleViewChange
53
+ })
54
+ }) }), renderView()]
55
+ });
56
+ }
57
+
58
+ //#endregion
59
+ export { DayCalendar, FlashCard, GamifiedMiniApp, MasteryRing, Overview, Progress, Steps, Timeline };
@@ -0,0 +1,2 @@
1
+ import { i as Overview, n as Progress, r as Steps, t as Timeline } from "../index-Bi5V3Ihq.mjs";
2
+ export { Overview, Progress, Steps, Timeline };
@@ -0,0 +1,4 @@
1
+ import { i as Overview, n as Progress, r as Steps, t as Timeline } from "../views-B-DapxWu.mjs";
2
+ import "../DayCalendar-Cha869Bi.mjs";
3
+
4
+ export { Overview, Progress, Steps, Timeline };