@wakastellar/ui 2.3.4 → 2.4.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 (194) hide show
  1. package/dist/blocks/dashboard/index.d.ts +4 -1
  2. package/dist/blocks/empty-states/index.d.ts +4 -1
  3. package/dist/blocks/error-pages/index.d.ts +4 -1
  4. package/dist/blocks/index.d.ts +1 -1
  5. package/dist/blocks/landing/index.d.ts +4 -1
  6. package/dist/blocks/pricing/index.d.ts +5 -1
  7. package/dist/blocks/sidebar/index.d.ts +5 -1
  8. package/dist/index.cjs.js +130 -130
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.es.js +7905 -7856
  11. package/dist/stories/Button.d.ts +14 -0
  12. package/dist/stories/Button.stories.d.ts +8 -0
  13. package/dist/stories/Header.d.ts +11 -0
  14. package/dist/stories/Header.stories.d.ts +6 -0
  15. package/dist/stories/Page.d.ts +2 -0
  16. package/dist/stories/Page.stories.d.ts +6 -0
  17. package/dist/types/index.d.ts +1 -0
  18. package/dist/types/link.d.ts +23 -0
  19. package/package.json +11 -3
  20. package/src/blocks/activity-timeline/ActivityTimeline.stories.tsx +460 -0
  21. package/src/blocks/apm-overview/APMOverview.stories.tsx +435 -0
  22. package/src/blocks/auth-2fa/Auth2FA.stories.tsx +308 -0
  23. package/src/blocks/calendar-view/CalendarView.stories.tsx +398 -0
  24. package/src/blocks/chat/Chat.stories.tsx +466 -0
  25. package/src/blocks/chat-interface/ChatInterface.stories.tsx +412 -0
  26. package/src/blocks/checkout-flow/CheckoutFlow.stories.tsx +408 -0
  27. package/src/blocks/cicd-builder/CICDBuilder.stories.tsx +499 -0
  28. package/src/blocks/cloud-cost-dashboard/CloudCostDashboard.stories.tsx +356 -0
  29. package/src/blocks/container-orchestrator/ContainerOrchestrator.stories.tsx +461 -0
  30. package/src/blocks/dashboard/Dashboard.stories.tsx +559 -0
  31. package/src/blocks/dashboard/index.tsx +68 -27
  32. package/src/blocks/dashboard-kpi/DashboardKPI.stories.tsx +422 -0
  33. package/src/blocks/database-admin/DatabaseAdmin.stories.tsx +393 -0
  34. package/src/blocks/deployment-dashboard/DeploymentDashboard.stories.tsx +457 -0
  35. package/src/blocks/empty-states/EmptyStates.stories.tsx +467 -0
  36. package/src/blocks/empty-states/index.tsx +26 -8
  37. package/src/blocks/error-pages/ErrorPages.stories.tsx +401 -0
  38. package/src/blocks/error-pages/index.tsx +26 -8
  39. package/src/blocks/faq/FAQ.stories.tsx +416 -0
  40. package/src/blocks/file-manager/FileManager.stories.tsx +413 -0
  41. package/src/blocks/footer/Footer.stories.tsx +328 -0
  42. package/src/blocks/gitops-sync-status/GitOpsSyncStatus.stories.tsx +529 -0
  43. package/src/blocks/header/WakaHeader.stories.tsx +455 -0
  44. package/src/blocks/headtab/Headtab.stories.tsx +369 -0
  45. package/src/blocks/i18n-editor/I18nEditor.stories.tsx +451 -0
  46. package/src/blocks/incident-manager/IncidentManager.stories.tsx +464 -0
  47. package/src/blocks/index.ts +1 -1
  48. package/src/blocks/infrastructure-map/InfrastructureMap.stories.tsx +533 -0
  49. package/src/blocks/kanban-board/KanbanBoard.stories.tsx +494 -0
  50. package/src/blocks/landing/WakaLanding.stories.tsx +449 -0
  51. package/src/blocks/landing/index.tsx +125 -66
  52. package/src/blocks/language-selector/LanguageSelector.stories.tsx +345 -0
  53. package/src/blocks/layout/Layout.stories.tsx +373 -0
  54. package/src/blocks/login/Login.stories.tsx +443 -0
  55. package/src/blocks/on-call-schedule/OnCallSchedule.stories.tsx +381 -0
  56. package/src/blocks/player-profile/PlayerProfile.stories.tsx +316 -0
  57. package/src/blocks/pricing/WakaPricing.stories.tsx +530 -0
  58. package/src/blocks/pricing/index.tsx +38 -4
  59. package/src/blocks/profile/Profile.stories.tsx +371 -0
  60. package/src/blocks/release-notes/ReleaseNotes.stories.tsx +431 -0
  61. package/src/blocks/settings/Settings.stories.tsx +520 -0
  62. package/src/blocks/sidebar/WakaSidebar.stories.tsx +513 -0
  63. package/src/blocks/sidebar/index.tsx +49 -20
  64. package/src/blocks/theme-creator-block/ThemeCreatorBlock.stories.tsx +370 -0
  65. package/src/blocks/user-management/UserManagement.stories.tsx +411 -0
  66. package/src/blocks/wizard/Wizard.stories.tsx +683 -0
  67. package/src/components/accordion/Accordion.stories.tsx +93 -0
  68. package/src/components/alert/Alert.stories.tsx +95 -0
  69. package/src/components/alert-dialog/AlertDialog.stories.tsx +126 -0
  70. package/src/components/aspect-ratio/AspectRatio.stories.tsx +118 -0
  71. package/src/components/avatar/Avatar.stories.tsx +104 -0
  72. package/src/components/button/Button.stories.tsx +12 -1
  73. package/src/components/calendar/Calendar.stories.tsx +102 -0
  74. package/src/components/card/Card.stories.tsx +125 -0
  75. package/src/components/checkbox/Checkbox.stories.tsx +100 -0
  76. package/src/components/code/Code.stories.tsx +402 -0
  77. package/src/components/collapsible/Collapsible.stories.tsx +123 -0
  78. package/src/components/command/Command.stories.tsx +207 -0
  79. package/src/components/context-menu/ContextMenu.stories.tsx +220 -0
  80. package/src/components/dialog/Dialog.stories.tsx +157 -0
  81. package/src/components/dropdown-menu/DropdownMenu.stories.tsx +225 -0
  82. package/src/components/form/Form.stories.tsx +413 -0
  83. package/src/components/hover-card/HoverCard.stories.tsx +148 -0
  84. package/src/components/input-otp/InputOTP.stories.tsx +255 -0
  85. package/src/components/label/Label.stories.tsx +68 -0
  86. package/src/components/menubar/Menubar.stories.tsx +278 -0
  87. package/src/components/navigation-menu/NavigationMenu.stories.tsx +202 -0
  88. package/src/components/popover/Popover.stories.tsx +199 -0
  89. package/src/components/progress/Progress.stories.tsx +104 -0
  90. package/src/components/radio-group/RadioGroup.stories.tsx +138 -0
  91. package/src/components/scroll-area/ScrollArea.stories.tsx +153 -0
  92. package/src/components/select/Select.stories.tsx +146 -0
  93. package/src/components/separator/Separator.stories.tsx +117 -0
  94. package/src/components/sheet/Sheet.stories.tsx +195 -0
  95. package/src/components/skeleton/Skeleton.stories.tsx +114 -0
  96. package/src/components/slider/Slider.stories.tsx +157 -0
  97. package/src/components/switch/Switch.stories.tsx +114 -0
  98. package/src/components/table/Table.stories.tsx +153 -0
  99. package/src/components/tabs/Tabs.stories.tsx +155 -0
  100. package/src/components/textarea/Textarea.stories.tsx +116 -0
  101. package/src/components/toast/Toast.stories.tsx +160 -0
  102. package/src/components/toggle/Toggle.stories.tsx +125 -0
  103. package/src/components/tooltip/Tooltip.stories.tsx +133 -0
  104. package/src/components/typography/Typography.stories.tsx +207 -0
  105. package/src/components/waka-3d-pie-chart/Waka3DPieChart.stories.tsx +308 -0
  106. package/src/components/waka-achievement-unlock/WakaAchievementUnlock.stories.tsx +353 -0
  107. package/src/components/waka-artifact-list/WakaArtifactList.stories.tsx +258 -0
  108. package/src/components/waka-autocomplete/WakaAutocomplete.stories.tsx +224 -0
  109. package/src/components/waka-badge-showcase/WakaBadgeShowcase.stories.tsx +269 -0
  110. package/src/components/waka-barcode/WakaBarcode.stories.tsx +227 -0
  111. package/src/components/waka-bottom-sheet/WakaBottomSheet.stories.tsx +408 -0
  112. package/src/components/waka-breadcrumb/WakaBreadcrumb.stories.tsx +237 -0
  113. package/src/components/waka-build-matrix/WakaBuildMatrix.stories.tsx +328 -0
  114. package/src/components/waka-carousel/WakaCarousel.stories.tsx +296 -0
  115. package/src/components/waka-charts/WakaCharts.stories.tsx +519 -0
  116. package/src/components/waka-color-picker/WakaColorPicker.stories.tsx +200 -0
  117. package/src/components/waka-combobox/WakaCombobox.stories.tsx +250 -0
  118. package/src/components/waka-container-list/WakaContainerList.stories.tsx +315 -0
  119. package/src/components/waka-contribution-graph/WakaContributionGraph.stories.tsx +354 -0
  120. package/src/components/waka-cost-breakdown/WakaCostBreakdown.stories.tsx +348 -0
  121. package/src/components/waka-daily-reward/WakaDailyReward.stories.tsx +365 -0
  122. package/src/components/waka-database-card/WakaDatabaseCard.stories.tsx +306 -0
  123. package/src/components/waka-date-range-picker/WakaDateRangePicker.stories.tsx +339 -0
  124. package/src/components/waka-datetime-picker/WakaDateTimePicker.stories.tsx +317 -0
  125. package/src/components/waka-deployment-lane/WakaDeploymentLane.stories.tsx +292 -0
  126. package/src/components/waka-dock/WakaDock.stories.tsx +332 -0
  127. package/src/components/waka-drawer/WakaDrawer.stories.tsx +437 -0
  128. package/src/components/waka-env-var-editor/WakaEnvVarEditor.stories.tsx +263 -0
  129. package/src/components/waka-error-shake/WakaErrorShake.stories.tsx +410 -0
  130. package/src/components/waka-file-upload/WakaFileUpload.stories.tsx +239 -0
  131. package/src/components/waka-flow-diagram/WakaFlowDiagram.stories.tsx +365 -0
  132. package/src/components/waka-funnel-chart/WakaFunnelChart.stories.tsx +281 -0
  133. package/src/components/waka-glow-card/WakaGlowCard.stories.tsx +274 -0
  134. package/src/components/waka-haptic-button/WakaHapticButton.stories.tsx +349 -0
  135. package/src/components/waka-health-pulse/WakaHealthPulse.stories.tsx +293 -0
  136. package/src/components/waka-heatmap/WakaHeatmap.stories.tsx +376 -0
  137. package/src/components/waka-image/WakaImage.stories.tsx +255 -0
  138. package/src/components/waka-incident-timeline/WakaIncidentTimeline.stories.tsx +300 -0
  139. package/src/components/waka-kanban/WakaKanban.stories.tsx +399 -0
  140. package/src/components/waka-kubernetes-overview/WakaKubernetesOverview.stories.tsx +281 -0
  141. package/src/components/waka-leaderboard/WakaLeaderboard.stories.tsx +300 -0
  142. package/src/components/waka-level-progress/WakaLevelProgress.stories.tsx +313 -0
  143. package/src/components/waka-loading-orbit/WakaLoadingOrbit.stories.tsx +413 -0
  144. package/src/components/waka-log-viewer/WakaLogViewer.stories.tsx +312 -0
  145. package/src/components/waka-loot-box/WakaLootBox.stories.tsx +374 -0
  146. package/src/components/waka-metric-sparkline/WakaMetricSparkline.stories.tsx +312 -0
  147. package/src/components/waka-migration-list/WakaMigrationList.stories.tsx +289 -0
  148. package/src/components/waka-modal/WakaModal.stories.tsx +434 -0
  149. package/src/components/waka-morph-button/WakaMorphButton.stories.tsx +405 -0
  150. package/src/components/waka-network-topology/WakaNetworkTopology.stories.tsx +364 -0
  151. package/src/components/waka-notifications/WakaNotifications.stories.tsx +290 -0
  152. package/src/components/waka-number-input/WakaNumberInput.stories.tsx +282 -0
  153. package/src/components/waka-pagination/WakaPagination.stories.tsx +328 -0
  154. package/src/components/waka-password-strength/WakaPasswordStrength.stories.tsx +318 -0
  155. package/src/components/waka-pipeline-view/WakaPipelineView.stories.tsx +386 -0
  156. package/src/components/waka-player-card/WakaPlayerCard.stories.tsx +333 -0
  157. package/src/components/waka-pod-card/WakaPodCard.stories.tsx +435 -0
  158. package/src/components/waka-qrcode/WakaQRCode.stories.tsx +232 -0
  159. package/src/components/waka-query-explain/WakaQueryExplain.stories.tsx +407 -0
  160. package/src/components/waka-quest-card/WakaQuestCard.stories.tsx +394 -0
  161. package/src/components/waka-quota-bar/WakaQuotaBar.stories.tsx +435 -0
  162. package/src/components/waka-radar-score/WakaRadarScore.stories.tsx +372 -0
  163. package/src/components/waka-resource-gauge/WakaResourceGauge.stories.tsx +366 -0
  164. package/src/components/waka-rich-text-editor/WakaRichTextEditor.stories.tsx +238 -0
  165. package/src/components/waka-sankey-diagram/WakaSankeyDiagram.stories.tsx +389 -0
  166. package/src/components/waka-scratch-card/WakaScratchCard.stories.tsx +388 -0
  167. package/src/components/waka-secret-card/WakaSecretCard.stories.tsx +314 -0
  168. package/src/components/waka-segmented-control/WakaSegmentedControl.stories.tsx +309 -0
  169. package/src/components/waka-server-rack/WakaServerRack.stories.tsx +382 -0
  170. package/src/components/waka-service-graph/WakaServiceGraph.stories.tsx +262 -0
  171. package/src/components/waka-skeleton-wave/WakaSkeletonWave.stories.tsx +321 -0
  172. package/src/components/waka-skill-tree/WakaSkillTree.stories.tsx +308 -0
  173. package/src/components/waka-spin-wheel/WakaSpinWheel.stories.tsx +368 -0
  174. package/src/components/waka-spinner/WakaSpinner.stories.tsx +156 -0
  175. package/src/components/waka-stat/WakaStat.stories.tsx +334 -0
  176. package/src/components/waka-status-matrix/WakaStatusMatrix.stories.tsx +331 -0
  177. package/src/components/waka-stepper/WakaStepper.stories.tsx +468 -0
  178. package/src/components/waka-streak-counter/WakaStreakCounter.stories.tsx +235 -0
  179. package/src/components/waka-success-explosion/WakaSuccessExplosion.stories.tsx +389 -0
  180. package/src/components/waka-tabs-morph/WakaTabsMorph.stories.tsx +471 -0
  181. package/src/components/waka-terminal-output/WakaTerminalOutput.stories.tsx +351 -0
  182. package/src/components/waka-test-report/WakaTestReport.stories.tsx +322 -0
  183. package/src/components/waka-tilt-card/WakaTiltCard.stories.tsx +300 -0
  184. package/src/components/waka-time-picker/WakaTimePicker.stories.tsx +227 -0
  185. package/src/components/waka-timeline/WakaTimeline.stories.tsx +383 -0
  186. package/src/components/waka-tournament-bracket/WakaTournamentBracket.stories.tsx +375 -0
  187. package/src/components/waka-trace-viewer/WakaTraceViewer.stories.tsx +445 -0
  188. package/src/components/waka-tree/WakaTree.stories.tsx +359 -0
  189. package/src/components/waka-treemap-chart/WakaTreemapChart.stories.tsx +378 -0
  190. package/src/components/waka-typewriter/WakaTypewriter.stories.tsx +366 -0
  191. package/src/components/waka-versus-card/WakaVersusCard.stories.tsx +530 -0
  192. package/src/components/waka-video/WakaVideo.stories.tsx +203 -0
  193. package/src/components/waka-virtual-list/WakaVirtualList.stories.tsx +273 -0
  194. package/src/components/waka-xp-bar/WakaXPBar.stories.tsx +305 -0
@@ -0,0 +1,308 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { WakaSkillTree, useSkillTree } from './index'
3
+ import type { Skill, SkillBranch } from './index'
4
+ import * as React from 'react'
5
+ import { Sword, Shield, Zap, Heart, Flame, Star, Crown, Target, Eye, Wind } from 'lucide-react'
6
+
7
+ const sampleSkills: Skill[] = [
8
+ // Attack branch
9
+ { id: 'atk-1', name: 'Basic Attack', description: 'Increases base attack damage by 10%', icon: <Sword className="h-6 w-6 text-white" />, unlocked: true, available: true, branch: 'attack', position: { x: 100, y: 100 }, cost: 1 },
10
+ { id: 'atk-2', name: 'Power Strike', description: 'A powerful attack that deals 150% damage', icon: <Sword className="h-6 w-6 text-white" />, unlocked: false, available: true, prerequisites: ['atk-1'], branch: 'attack', position: { x: 100, y: 220 }, cost: 2 },
11
+ { id: 'atk-3', name: 'Critical Mastery', description: 'Increases critical hit chance by 15%', icon: <Target className="h-6 w-6 text-white" />, unlocked: false, available: false, prerequisites: ['atk-2'], branch: 'attack', position: { x: 100, y: 340 }, cost: 3 },
12
+
13
+ // Defense branch
14
+ { id: 'def-1', name: 'Basic Defense', description: 'Increases armor by 10%', icon: <Shield className="h-6 w-6 text-white" />, unlocked: true, available: true, branch: 'defense', position: { x: 300, y: 100 }, cost: 1 },
15
+ { id: 'def-2', name: 'Iron Wall', description: 'Reduces incoming damage by 20%', icon: <Shield className="h-6 w-6 text-white" />, unlocked: false, available: true, prerequisites: ['def-1'], branch: 'defense', position: { x: 300, y: 220 }, cost: 2 },
16
+ { id: 'def-3', name: 'Thorns', description: 'Reflects 10% damage back to attackers', icon: <Flame className="h-6 w-6 text-white" />, unlocked: false, available: false, prerequisites: ['def-2'], branch: 'defense', position: { x: 300, y: 340 }, cost: 3 },
17
+
18
+ // Magic branch
19
+ { id: 'mag-1', name: 'Mana Pool', description: 'Increases max mana by 20%', icon: <Zap className="h-6 w-6 text-white" />, unlocked: false, available: true, branch: 'magic', position: { x: 500, y: 100 }, cost: 1 },
20
+ { id: 'mag-2', name: 'Spell Power', description: 'Increases spell damage by 15%', icon: <Star className="h-6 w-6 text-white" />, unlocked: false, available: false, prerequisites: ['mag-1'], branch: 'magic', position: { x: 500, y: 220 }, cost: 2 },
21
+ { id: 'mag-3', name: 'Arcane Mastery', description: 'Reduces spell cooldowns by 25%', icon: <Crown className="h-6 w-6 text-white" />, unlocked: false, available: false, prerequisites: ['mag-2'], branch: 'magic', position: { x: 500, y: 340 }, cost: 4 },
22
+
23
+ // Health branch
24
+ { id: 'hp-1', name: 'Vitality', description: 'Increases max HP by 15%', icon: <Heart className="h-6 w-6 text-white" />, unlocked: false, available: true, branch: 'health', position: { x: 700, y: 100 }, cost: 1 },
25
+ { id: 'hp-2', name: 'Regeneration', description: 'Recover 1% HP every 5 seconds', icon: <Heart className="h-6 w-6 text-white" />, unlocked: false, available: false, prerequisites: ['hp-1'], branch: 'health', position: { x: 700, y: 220 }, cost: 2 },
26
+ ]
27
+
28
+ const branches: SkillBranch[] = [
29
+ { id: 'attack', name: 'Attack', color: '#ef4444' },
30
+ { id: 'defense', name: 'Defense', color: '#3b82f6' },
31
+ { id: 'magic', name: 'Magic', color: '#8b5cf6' },
32
+ { id: 'health', name: 'Health', color: '#22c55e' },
33
+ ]
34
+
35
+ const meta: Meta<typeof WakaSkillTree> = {
36
+ title: 'Components/Gamification/WakaSkillTree',
37
+ component: WakaSkillTree,
38
+ parameters: {
39
+ layout: 'fullscreen',
40
+ docs: {
41
+ description: {
42
+ component: 'An interactive skill tree with branches, prerequisites, zoom/pan controls, unlock animations, and progress tracking.',
43
+ },
44
+ },
45
+ },
46
+ tags: ['autodocs'],
47
+ argTypes: {
48
+ showConnections: {
49
+ control: 'boolean',
50
+ description: 'Show connection lines between skills',
51
+ },
52
+ zoomable: {
53
+ control: 'boolean',
54
+ description: 'Enable zoom controls',
55
+ },
56
+ animated: {
57
+ control: 'boolean',
58
+ description: 'Enable animations',
59
+ },
60
+ showResetButton: {
61
+ control: 'boolean',
62
+ description: 'Show reset button',
63
+ },
64
+ },
65
+ }
66
+
67
+ export default meta
68
+ type Story = StoryObj<typeof WakaSkillTree>
69
+
70
+ export const Default: Story = {
71
+ args: {
72
+ skills: sampleSkills,
73
+ skillPoints: 10,
74
+ branches: branches,
75
+ },
76
+ render: (args) => (
77
+ <div className="p-6">
78
+ <WakaSkillTree {...args} />
79
+ </div>
80
+ ),
81
+ }
82
+
83
+ export const WithoutBranchPanel: Story = {
84
+ render: () => (
85
+ <div className="p-6">
86
+ <WakaSkillTree
87
+ skills={sampleSkills.slice(0, 6)}
88
+ skillPoints={5}
89
+ branches={branches.slice(0, 2)}
90
+ />
91
+ </div>
92
+ ),
93
+ }
94
+
95
+ export const AllUnlocked: Story = {
96
+ render: () => (
97
+ <div className="p-6">
98
+ <WakaSkillTree
99
+ skills={sampleSkills.map(s => ({ ...s, unlocked: true, available: true }))}
100
+ skillPoints={0}
101
+ branches={branches}
102
+ />
103
+ </div>
104
+ ),
105
+ }
106
+
107
+ export const Interactive: Story = {
108
+ render: () => {
109
+ const {
110
+ skills,
111
+ skillPoints,
112
+ unlockSkill,
113
+ addPoints,
114
+ resetSkills,
115
+ totalUnlocked,
116
+ totalSkills,
117
+ progress,
118
+ } = useSkillTree({
119
+ initialSkills: sampleSkills,
120
+ initialPoints: 10,
121
+ onSkillUnlock: (skill) => console.log('Unlocked:', skill.name),
122
+ })
123
+
124
+ return (
125
+ <div className="p-6">
126
+ <WakaSkillTree
127
+ skills={skills}
128
+ skillPoints={skillPoints}
129
+ branches={branches}
130
+ onUnlock={unlockSkill}
131
+ onReset={resetSkills}
132
+ />
133
+
134
+ <div className="fixed bottom-4 left-4 p-4 rounded-lg border bg-card shadow-lg">
135
+ <div className="text-sm space-y-1">
136
+ <div>Progress: {progress.toFixed(0)}%</div>
137
+ <div>Skills: {totalUnlocked}/{totalSkills}</div>
138
+ </div>
139
+ <div className="mt-3 flex gap-2">
140
+ <button
141
+ onClick={() => addPoints(5)}
142
+ className="px-3 py-1 text-xs rounded bg-primary text-primary-foreground"
143
+ >
144
+ +5 Points
145
+ </button>
146
+ <button
147
+ onClick={resetSkills}
148
+ className="px-3 py-1 text-xs rounded border hover:bg-muted"
149
+ >
150
+ Reset
151
+ </button>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ )
156
+ },
157
+ }
158
+
159
+ export const NoZoom: Story = {
160
+ render: () => (
161
+ <div className="p-6">
162
+ <WakaSkillTree
163
+ skills={sampleSkills}
164
+ skillPoints={5}
165
+ branches={branches}
166
+ zoomable={false}
167
+ />
168
+ </div>
169
+ ),
170
+ }
171
+
172
+ export const NoConnections: Story = {
173
+ render: () => (
174
+ <div className="p-6">
175
+ <WakaSkillTree
176
+ skills={sampleSkills}
177
+ skillPoints={5}
178
+ branches={branches}
179
+ showConnections={false}
180
+ />
181
+ </div>
182
+ ),
183
+ }
184
+
185
+ export const NoAnimations: Story = {
186
+ render: () => (
187
+ <div className="p-6">
188
+ <WakaSkillTree
189
+ skills={sampleSkills}
190
+ skillPoints={5}
191
+ branches={branches}
192
+ animated={false}
193
+ />
194
+ </div>
195
+ ),
196
+ }
197
+
198
+ export const LargeSkillTree: Story = {
199
+ render: () => {
200
+ const largeSkills: Skill[] = [
201
+ // Tier 1
202
+ { id: 's1', name: 'Core Skill', description: 'The foundation', unlocked: true, available: true, branch: 'core', position: { x: 400, y: 50 }, cost: 0 },
203
+
204
+ // Tier 2
205
+ { id: 's2a', name: 'Path A', description: 'First path', unlocked: false, available: true, prerequisites: ['s1'], branch: 'power', position: { x: 200, y: 150 }, cost: 1 },
206
+ { id: 's2b', name: 'Path B', description: 'Second path', unlocked: false, available: true, prerequisites: ['s1'], branch: 'defense', position: { x: 400, y: 150 }, cost: 1 },
207
+ { id: 's2c', name: 'Path C', description: 'Third path', unlocked: false, available: true, prerequisites: ['s1'], branch: 'utility', position: { x: 600, y: 150 }, cost: 1 },
208
+
209
+ // Tier 3
210
+ { id: 's3a1', name: 'Power I', description: 'Power skill 1', unlocked: false, available: false, prerequisites: ['s2a'], branch: 'power', position: { x: 100, y: 250 }, cost: 2 },
211
+ { id: 's3a2', name: 'Power II', description: 'Power skill 2', unlocked: false, available: false, prerequisites: ['s2a'], branch: 'power', position: { x: 300, y: 250 }, cost: 2 },
212
+ { id: 's3b1', name: 'Defense I', description: 'Defense skill 1', unlocked: false, available: false, prerequisites: ['s2b'], branch: 'defense', position: { x: 400, y: 250 }, cost: 2 },
213
+ { id: 's3c1', name: 'Utility I', description: 'Utility skill 1', unlocked: false, available: false, prerequisites: ['s2c'], branch: 'utility', position: { x: 500, y: 250 }, cost: 2 },
214
+ { id: 's3c2', name: 'Utility II', description: 'Utility skill 2', unlocked: false, available: false, prerequisites: ['s2c'], branch: 'utility', position: { x: 700, y: 250 }, cost: 2 },
215
+
216
+ // Tier 4
217
+ { id: 's4a', name: 'Ultimate Power', description: 'Ultimate power skill', unlocked: false, available: false, prerequisites: ['s3a1', 's3a2'], branch: 'power', position: { x: 200, y: 350 }, cost: 4 },
218
+ { id: 's4b', name: 'Ultimate Defense', description: 'Ultimate defense skill', unlocked: false, available: false, prerequisites: ['s3b1'], branch: 'defense', position: { x: 400, y: 350 }, cost: 4 },
219
+ { id: 's4c', name: 'Ultimate Utility', description: 'Ultimate utility skill', unlocked: false, available: false, prerequisites: ['s3c1', 's3c2'], branch: 'utility', position: { x: 600, y: 350 }, cost: 4 },
220
+
221
+ // Final tier
222
+ { id: 's5', name: 'Ascension', description: 'The ultimate skill', unlocked: false, available: false, prerequisites: ['s4a', 's4b', 's4c'], branch: 'core', position: { x: 400, y: 450 }, cost: 10 },
223
+ ]
224
+
225
+ const largeBranches: SkillBranch[] = [
226
+ { id: 'core', name: 'Core', color: '#f59e0b' },
227
+ { id: 'power', name: 'Power', color: '#ef4444' },
228
+ { id: 'defense', name: 'Defense', color: '#3b82f6' },
229
+ { id: 'utility', name: 'Utility', color: '#22c55e' },
230
+ ]
231
+
232
+ return (
233
+ <div className="p-6">
234
+ <WakaSkillTree
235
+ skills={largeSkills}
236
+ skillPoints={20}
237
+ branches={largeBranches}
238
+ />
239
+ </div>
240
+ )
241
+ },
242
+ }
243
+
244
+ export const MultiLevelSkills: Story = {
245
+ render: () => {
246
+ const multiLevelSkills: Skill[] = [
247
+ { id: 'skill-1', name: 'Strength', description: '+10 ATK per level', unlocked: true, available: true, branch: 'attack', position: { x: 200, y: 150 }, cost: 1, maxLevel: 5, level: 3 },
248
+ { id: 'skill-2', name: 'Endurance', description: '+10 DEF per level', unlocked: true, available: true, branch: 'defense', position: { x: 400, y: 150 }, cost: 1, maxLevel: 5, level: 2 },
249
+ { id: 'skill-3', name: 'Intelligence', description: '+10 MP per level', unlocked: true, available: true, branch: 'magic', position: { x: 600, y: 150 }, cost: 1, maxLevel: 5, level: 5 },
250
+ ]
251
+
252
+ return (
253
+ <div className="p-6">
254
+ <p className="text-sm text-muted-foreground text-center mb-4">
255
+ Multi-level skills show current/max level on the node
256
+ </p>
257
+ <WakaSkillTree
258
+ skills={multiLevelSkills}
259
+ skillPoints={5}
260
+ branches={branches.slice(0, 3)}
261
+ />
262
+ </div>
263
+ )
264
+ },
265
+ }
266
+
267
+ export const RPGCharacterBuild: Story = {
268
+ render: () => (
269
+ <div className="p-6">
270
+ <div className="mb-6 p-4 rounded-xl border bg-gradient-to-r from-violet-500/10 to-purple-500/10">
271
+ <div className="flex items-center gap-4">
272
+ <div className="w-16 h-16 rounded-full bg-gradient-to-br from-violet-500 to-purple-600 flex items-center justify-center">
273
+ <Crown className="h-8 w-8 text-white" />
274
+ </div>
275
+ <div>
276
+ <h2 className="text-xl font-bold">Warrior Build</h2>
277
+ <p className="text-sm text-muted-foreground">Level 42 • 3,450 XP to next level</p>
278
+ </div>
279
+ <div className="ml-auto text-right">
280
+ <div className="text-2xl font-bold text-amber-500">12</div>
281
+ <div className="text-xs text-muted-foreground">Skill Points</div>
282
+ </div>
283
+ </div>
284
+ </div>
285
+
286
+ <WakaSkillTree
287
+ skills={sampleSkills}
288
+ skillPoints={12}
289
+ branches={branches}
290
+ />
291
+ </div>
292
+ ),
293
+ }
294
+
295
+ export const LowPoints: Story = {
296
+ render: () => (
297
+ <div className="p-6">
298
+ <p className="text-sm text-muted-foreground text-center mb-4">
299
+ With only 1 point, expensive skills show as locked even when available
300
+ </p>
301
+ <WakaSkillTree
302
+ skills={sampleSkills}
303
+ skillPoints={1}
304
+ branches={branches}
305
+ />
306
+ </div>
307
+ ),
308
+ }
@@ -0,0 +1,368 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { WakaSpinWheel } from './index'
3
+ import type { SpinWheelSegment } from './index'
4
+ import * as React from 'react'
5
+ import { Gift, Star, Zap, Crown, Gem, Coins, Heart, Flame } from 'lucide-react'
6
+
7
+ const sampleSegments: SpinWheelSegment[] = [
8
+ { id: '1', label: '100 Points', color: '#3b82f6', weight: 30 },
9
+ { id: '2', label: '200 Points', color: '#22c55e', weight: 25 },
10
+ { id: '3', label: '500 Points', color: '#f59e0b', weight: 15 },
11
+ { id: '4', label: 'Try Again', color: '#6b7280', weight: 20 },
12
+ { id: '5', label: '1000 Points', color: '#8b5cf6', weight: 8 },
13
+ { id: '6', label: 'JACKPOT!', color: '#ef4444', weight: 2 },
14
+ ]
15
+
16
+ const prizeSegments: SpinWheelSegment[] = [
17
+ { id: 'p1', label: 'Gold Coins', color: '#fbbf24', icon: <Coins className="h-5 w-5 text-white" /> },
18
+ { id: 'p2', label: 'Gems', color: '#8b5cf6', icon: <Gem className="h-5 w-5 text-white" /> },
19
+ { id: 'p3', label: 'Energy', color: '#3b82f6', icon: <Zap className="h-5 w-5 text-white" /> },
20
+ { id: 'p4', label: 'Crown', color: '#f59e0b', icon: <Crown className="h-5 w-5 text-white" /> },
21
+ { id: 'p5', label: 'Heart', color: '#ef4444', icon: <Heart className="h-5 w-5 text-white" /> },
22
+ { id: 'p6', label: 'Star', color: '#22c55e', icon: <Star className="h-5 w-5 text-white" /> },
23
+ ]
24
+
25
+ const meta: Meta<typeof WakaSpinWheel> = {
26
+ title: 'Components/Gamification/WakaSpinWheel',
27
+ component: WakaSpinWheel,
28
+ parameters: {
29
+ layout: 'centered',
30
+ docs: {
31
+ description: {
32
+ component: 'A fortune wheel with weighted probabilities, smooth spin physics, tick sound callbacks, and customizable appearance.',
33
+ },
34
+ },
35
+ },
36
+ tags: ['autodocs'],
37
+ argTypes: {
38
+ size: {
39
+ control: 'select',
40
+ options: ['sm', 'md', 'lg'],
41
+ description: 'Wheel size',
42
+ },
43
+ spinDuration: {
44
+ control: { type: 'number', min: 2000, max: 10000, step: 500 },
45
+ description: 'Spin duration in milliseconds',
46
+ },
47
+ rotations: {
48
+ control: { type: 'number', min: 2, max: 10, step: 1 },
49
+ description: 'Number of full rotations before stopping',
50
+ },
51
+ showLabels: {
52
+ control: 'boolean',
53
+ description: 'Show segment labels',
54
+ },
55
+ disabled: {
56
+ control: 'boolean',
57
+ description: 'Disable spinning',
58
+ },
59
+ pointerPosition: {
60
+ control: 'select',
61
+ options: ['top', 'right', 'bottom', 'left'],
62
+ description: 'Position of the pointer',
63
+ },
64
+ },
65
+ }
66
+
67
+ export default meta
68
+ type Story = StoryObj<typeof WakaSpinWheel>
69
+
70
+ export const Default: Story = {
71
+ args: {
72
+ segments: sampleSegments,
73
+ size: 'md',
74
+ spinDuration: 4000,
75
+ rotations: 5,
76
+ showLabels: true,
77
+ disabled: false,
78
+ pointerPosition: 'top',
79
+ },
80
+ render: (args) => <WakaSpinWheel {...args} />,
81
+ }
82
+
83
+ export const Sizes: Story = {
84
+ render: () => (
85
+ <div className="flex flex-wrap gap-8 items-center">
86
+ <div className="text-center">
87
+ <p className="text-sm text-muted-foreground mb-4">Small</p>
88
+ <WakaSpinWheel segments={sampleSegments} size="sm" />
89
+ </div>
90
+ <div className="text-center">
91
+ <p className="text-sm text-muted-foreground mb-4">Medium (Default)</p>
92
+ <WakaSpinWheel segments={sampleSegments} size="md" />
93
+ </div>
94
+ <div className="text-center">
95
+ <p className="text-sm text-muted-foreground mb-4">Large</p>
96
+ <WakaSpinWheel segments={sampleSegments} size="lg" />
97
+ </div>
98
+ </div>
99
+ ),
100
+ }
101
+
102
+ export const WithIcons: Story = {
103
+ render: () => (
104
+ <WakaSpinWheel segments={prizeSegments} size="lg" />
105
+ ),
106
+ }
107
+
108
+ export const PointerPositions: Story = {
109
+ render: () => (
110
+ <div className="grid grid-cols-2 gap-8">
111
+ <div className="text-center">
112
+ <p className="text-sm text-muted-foreground mb-4">Top (Default)</p>
113
+ <WakaSpinWheel segments={sampleSegments} pointerPosition="top" size="sm" />
114
+ </div>
115
+ <div className="text-center">
116
+ <p className="text-sm text-muted-foreground mb-4">Right</p>
117
+ <WakaSpinWheel segments={sampleSegments} pointerPosition="right" size="sm" />
118
+ </div>
119
+ <div className="text-center">
120
+ <p className="text-sm text-muted-foreground mb-4">Bottom</p>
121
+ <WakaSpinWheel segments={sampleSegments} pointerPosition="bottom" size="sm" />
122
+ </div>
123
+ <div className="text-center">
124
+ <p className="text-sm text-muted-foreground mb-4">Left</p>
125
+ <WakaSpinWheel segments={sampleSegments} pointerPosition="left" size="sm" />
126
+ </div>
127
+ </div>
128
+ ),
129
+ }
130
+
131
+ export const Interactive: Story = {
132
+ render: () => {
133
+ const [result, setResult] = React.useState<SpinWheelSegment | null>(null)
134
+ const [isSpinning, setIsSpinning] = React.useState(false)
135
+ const [spinCount, setSpinCount] = React.useState(0)
136
+
137
+ return (
138
+ <div className="space-y-6">
139
+ <WakaSpinWheel
140
+ segments={sampleSegments}
141
+ onSpin={() => {
142
+ setIsSpinning(true)
143
+ setSpinCount(prev => prev + 1)
144
+ }}
145
+ onResult={(segment) => {
146
+ setIsSpinning(false)
147
+ setResult(segment)
148
+ }}
149
+ size="lg"
150
+ />
151
+
152
+ <div className="p-4 rounded-lg border bg-muted/30 w-[350px]">
153
+ <div className="grid grid-cols-2 gap-2 text-sm mb-3">
154
+ <div>Spin Count: {spinCount}</div>
155
+ <div>Status: {isSpinning ? 'Spinning...' : 'Ready'}</div>
156
+ </div>
157
+
158
+ {result && (
159
+ <div className="pt-3 border-t">
160
+ <h4 className="font-medium mb-1">Last Result</h4>
161
+ <div
162
+ className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-white text-sm font-medium"
163
+ style={{ backgroundColor: result.color }}
164
+ >
165
+ {result.label}
166
+ </div>
167
+ </div>
168
+ )}
169
+ </div>
170
+ </div>
171
+ )
172
+ },
173
+ }
174
+
175
+ export const WithCallbacks: Story = {
176
+ render: () => {
177
+ const [log, setLog] = React.useState<string[]>([])
178
+
179
+ const addLog = (message: string) => {
180
+ setLog(prev => [...prev.slice(-4), `${new Date().toLocaleTimeString()}: ${message}`])
181
+ }
182
+
183
+ return (
184
+ <div className="flex gap-8">
185
+ <WakaSpinWheel
186
+ segments={sampleSegments}
187
+ onSpin={() => addLog('Spin started')}
188
+ onTick={() => addLog('Tick')}
189
+ onSlowDown={() => addLog('Slowing down...')}
190
+ onResult={(segment) => addLog(`Result: ${segment.label}`)}
191
+ />
192
+
193
+ <div className="w-[250px] p-4 rounded-lg border bg-muted/30">
194
+ <h4 className="font-medium mb-2">Event Log</h4>
195
+ <div className="space-y-1 font-mono text-xs">
196
+ {log.map((entry, i) => (
197
+ <div key={i} className="text-muted-foreground">{entry}</div>
198
+ ))}
199
+ {log.length === 0 && (
200
+ <div className="text-muted-foreground italic">Spin to see events...</div>
201
+ )}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ )
206
+ },
207
+ }
208
+
209
+ export const WeightedProbabilities: Story = {
210
+ render: () => {
211
+ const weightedSegments: SpinWheelSegment[] = [
212
+ { id: '1', label: '10 pts', color: '#6b7280', weight: 50 },
213
+ { id: '2', label: '50 pts', color: '#3b82f6', weight: 30 },
214
+ { id: '3', label: '100 pts', color: '#22c55e', weight: 15 },
215
+ { id: '4', label: '500 pts', color: '#f59e0b', weight: 4 },
216
+ { id: '5', label: 'JACKPOT', color: '#ef4444', weight: 1 },
217
+ ]
218
+
219
+ const [results, setResults] = React.useState<Record<string, number>>({})
220
+
221
+ return (
222
+ <div className="space-y-6">
223
+ <WakaSpinWheel
224
+ segments={weightedSegments}
225
+ onResult={(segment) => {
226
+ setResults(prev => ({
227
+ ...prev,
228
+ [segment.id]: (prev[segment.id] || 0) + 1,
229
+ }))
230
+ }}
231
+ size="lg"
232
+ />
233
+
234
+ <div className="w-[350px] p-4 rounded-lg border">
235
+ <h4 className="font-medium mb-3">Probability Distribution</h4>
236
+ <div className="space-y-2">
237
+ {weightedSegments.map((segment) => (
238
+ <div key={segment.id} className="flex items-center gap-2">
239
+ <div
240
+ className="w-3 h-3 rounded-full"
241
+ style={{ backgroundColor: segment.color }}
242
+ />
243
+ <span className="flex-1 text-sm">{segment.label}</span>
244
+ <span className="text-xs text-muted-foreground">
245
+ {segment.weight}% chance
246
+ </span>
247
+ <span className="text-xs font-mono">
248
+ Won: {results[segment.id] || 0}
249
+ </span>
250
+ </div>
251
+ ))}
252
+ </div>
253
+ </div>
254
+ </div>
255
+ )
256
+ },
257
+ }
258
+
259
+ export const CustomSpinSettings: Story = {
260
+ render: () => (
261
+ <div className="flex gap-8">
262
+ <div className="text-center">
263
+ <p className="text-sm text-muted-foreground mb-4">Fast Spin (2s, 3 rotations)</p>
264
+ <WakaSpinWheel
265
+ segments={sampleSegments}
266
+ spinDuration={2000}
267
+ rotations={3}
268
+ size="sm"
269
+ />
270
+ </div>
271
+ <div className="text-center">
272
+ <p className="text-sm text-muted-foreground mb-4">Slow Spin (8s, 8 rotations)</p>
273
+ <WakaSpinWheel
274
+ segments={sampleSegments}
275
+ spinDuration={8000}
276
+ rotations={8}
277
+ size="sm"
278
+ />
279
+ </div>
280
+ </div>
281
+ ),
282
+ }
283
+
284
+ export const CustomCenterContent: Story = {
285
+ render: () => (
286
+ <WakaSpinWheel
287
+ segments={prizeSegments}
288
+ size="lg"
289
+ centerContent={
290
+ <div className="flex flex-col items-center">
291
+ <Flame className="h-6 w-6 text-amber-900" />
292
+ <span className="text-[10px] font-bold text-amber-900">GO!</span>
293
+ </div>
294
+ }
295
+ />
296
+ ),
297
+ }
298
+
299
+ export const DailySpinGame: Story = {
300
+ render: () => {
301
+ const [spinsLeft, setSpinsLeft] = React.useState(3)
302
+ const [totalWon, setTotalWon] = React.useState(0)
303
+
304
+ const dailySegments: SpinWheelSegment[] = [
305
+ { id: '1', label: '50', color: '#6b7280', weight: 30 },
306
+ { id: '2', label: '100', color: '#3b82f6', weight: 25 },
307
+ { id: '3', label: '200', color: '#22c55e', weight: 20 },
308
+ { id: '4', label: '500', color: '#f59e0b', weight: 15 },
309
+ { id: '5', label: '1K', color: '#8b5cf6', weight: 8 },
310
+ { id: '6', label: '5K', color: '#ef4444', weight: 2 },
311
+ ]
312
+
313
+ return (
314
+ <div className="p-6 rounded-xl border bg-gradient-to-br from-violet-500/10 to-purple-500/10">
315
+ <div className="flex justify-between items-center mb-6">
316
+ <div>
317
+ <h3 className="text-xl font-bold">Daily Spin</h3>
318
+ <p className="text-sm text-muted-foreground">Win coins every day!</p>
319
+ </div>
320
+ <div className="text-right">
321
+ <div className="text-2xl font-bold text-amber-500">{totalWon.toLocaleString()}</div>
322
+ <div className="text-xs text-muted-foreground">Coins Won</div>
323
+ </div>
324
+ </div>
325
+
326
+ <WakaSpinWheel
327
+ segments={dailySegments}
328
+ disabled={spinsLeft === 0}
329
+ onSpin={() => setSpinsLeft(prev => prev - 1)}
330
+ onResult={(segment) => {
331
+ const value = parseInt(segment.label.replace('K', '000'))
332
+ setTotalWon(prev => prev + value)
333
+ }}
334
+ />
335
+
336
+ <div className="mt-6 pt-4 border-t flex justify-between items-center">
337
+ <div className="flex items-center gap-2">
338
+ <Flame className="h-4 w-4 text-orange-500" />
339
+ <span className="text-sm">{spinsLeft} spins remaining</span>
340
+ </div>
341
+ <button
342
+ onClick={() => setSpinsLeft(3)}
343
+ className="text-sm text-primary hover:underline"
344
+ >
345
+ Reset (Demo)
346
+ </button>
347
+ </div>
348
+ </div>
349
+ )
350
+ },
351
+ }
352
+
353
+ export const NoLabels: Story = {
354
+ render: () => (
355
+ <WakaSpinWheel segments={prizeSegments} showLabels={false} />
356
+ ),
357
+ }
358
+
359
+ export const Disabled: Story = {
360
+ render: () => (
361
+ <div className="space-y-4">
362
+ <p className="text-sm text-muted-foreground text-center">
363
+ Disabled state - cannot spin
364
+ </p>
365
+ <WakaSpinWheel segments={sampleSegments} disabled />
366
+ </div>
367
+ ),
368
+ }