@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.
- package/dist/blocks/dashboard/index.d.ts +4 -1
- package/dist/blocks/empty-states/index.d.ts +4 -1
- package/dist/blocks/error-pages/index.d.ts +4 -1
- package/dist/blocks/index.d.ts +1 -1
- package/dist/blocks/landing/index.d.ts +4 -1
- package/dist/blocks/pricing/index.d.ts +5 -1
- package/dist/blocks/sidebar/index.d.ts +5 -1
- package/dist/index.cjs.js +130 -130
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +7905 -7856
- package/dist/stories/Button.d.ts +14 -0
- package/dist/stories/Button.stories.d.ts +8 -0
- package/dist/stories/Header.d.ts +11 -0
- package/dist/stories/Header.stories.d.ts +6 -0
- package/dist/stories/Page.d.ts +2 -0
- package/dist/stories/Page.stories.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/link.d.ts +23 -0
- package/package.json +11 -3
- package/src/blocks/activity-timeline/ActivityTimeline.stories.tsx +460 -0
- package/src/blocks/apm-overview/APMOverview.stories.tsx +435 -0
- package/src/blocks/auth-2fa/Auth2FA.stories.tsx +308 -0
- package/src/blocks/calendar-view/CalendarView.stories.tsx +398 -0
- package/src/blocks/chat/Chat.stories.tsx +466 -0
- package/src/blocks/chat-interface/ChatInterface.stories.tsx +412 -0
- package/src/blocks/checkout-flow/CheckoutFlow.stories.tsx +408 -0
- package/src/blocks/cicd-builder/CICDBuilder.stories.tsx +499 -0
- package/src/blocks/cloud-cost-dashboard/CloudCostDashboard.stories.tsx +356 -0
- package/src/blocks/container-orchestrator/ContainerOrchestrator.stories.tsx +461 -0
- package/src/blocks/dashboard/Dashboard.stories.tsx +559 -0
- package/src/blocks/dashboard/index.tsx +68 -27
- package/src/blocks/dashboard-kpi/DashboardKPI.stories.tsx +422 -0
- package/src/blocks/database-admin/DatabaseAdmin.stories.tsx +393 -0
- package/src/blocks/deployment-dashboard/DeploymentDashboard.stories.tsx +457 -0
- package/src/blocks/empty-states/EmptyStates.stories.tsx +467 -0
- package/src/blocks/empty-states/index.tsx +26 -8
- package/src/blocks/error-pages/ErrorPages.stories.tsx +401 -0
- package/src/blocks/error-pages/index.tsx +26 -8
- package/src/blocks/faq/FAQ.stories.tsx +416 -0
- package/src/blocks/file-manager/FileManager.stories.tsx +413 -0
- package/src/blocks/footer/Footer.stories.tsx +328 -0
- package/src/blocks/gitops-sync-status/GitOpsSyncStatus.stories.tsx +529 -0
- package/src/blocks/header/WakaHeader.stories.tsx +455 -0
- package/src/blocks/headtab/Headtab.stories.tsx +369 -0
- package/src/blocks/i18n-editor/I18nEditor.stories.tsx +451 -0
- package/src/blocks/incident-manager/IncidentManager.stories.tsx +464 -0
- package/src/blocks/index.ts +1 -1
- package/src/blocks/infrastructure-map/InfrastructureMap.stories.tsx +533 -0
- package/src/blocks/kanban-board/KanbanBoard.stories.tsx +494 -0
- package/src/blocks/landing/WakaLanding.stories.tsx +449 -0
- package/src/blocks/landing/index.tsx +125 -66
- package/src/blocks/language-selector/LanguageSelector.stories.tsx +345 -0
- package/src/blocks/layout/Layout.stories.tsx +373 -0
- package/src/blocks/login/Login.stories.tsx +443 -0
- package/src/blocks/on-call-schedule/OnCallSchedule.stories.tsx +381 -0
- package/src/blocks/player-profile/PlayerProfile.stories.tsx +316 -0
- package/src/blocks/pricing/WakaPricing.stories.tsx +530 -0
- package/src/blocks/pricing/index.tsx +38 -4
- package/src/blocks/profile/Profile.stories.tsx +371 -0
- package/src/blocks/release-notes/ReleaseNotes.stories.tsx +431 -0
- package/src/blocks/settings/Settings.stories.tsx +520 -0
- package/src/blocks/sidebar/WakaSidebar.stories.tsx +513 -0
- package/src/blocks/sidebar/index.tsx +49 -20
- package/src/blocks/theme-creator-block/ThemeCreatorBlock.stories.tsx +370 -0
- package/src/blocks/user-management/UserManagement.stories.tsx +411 -0
- package/src/blocks/wizard/Wizard.stories.tsx +683 -0
- package/src/components/accordion/Accordion.stories.tsx +93 -0
- package/src/components/alert/Alert.stories.tsx +95 -0
- package/src/components/alert-dialog/AlertDialog.stories.tsx +126 -0
- package/src/components/aspect-ratio/AspectRatio.stories.tsx +118 -0
- package/src/components/avatar/Avatar.stories.tsx +104 -0
- package/src/components/button/Button.stories.tsx +12 -1
- package/src/components/calendar/Calendar.stories.tsx +102 -0
- package/src/components/card/Card.stories.tsx +125 -0
- package/src/components/checkbox/Checkbox.stories.tsx +100 -0
- package/src/components/code/Code.stories.tsx +402 -0
- package/src/components/collapsible/Collapsible.stories.tsx +123 -0
- package/src/components/command/Command.stories.tsx +207 -0
- package/src/components/context-menu/ContextMenu.stories.tsx +220 -0
- package/src/components/dialog/Dialog.stories.tsx +157 -0
- package/src/components/dropdown-menu/DropdownMenu.stories.tsx +225 -0
- package/src/components/form/Form.stories.tsx +413 -0
- package/src/components/hover-card/HoverCard.stories.tsx +148 -0
- package/src/components/input-otp/InputOTP.stories.tsx +255 -0
- package/src/components/label/Label.stories.tsx +68 -0
- package/src/components/menubar/Menubar.stories.tsx +278 -0
- package/src/components/navigation-menu/NavigationMenu.stories.tsx +202 -0
- package/src/components/popover/Popover.stories.tsx +199 -0
- package/src/components/progress/Progress.stories.tsx +104 -0
- package/src/components/radio-group/RadioGroup.stories.tsx +138 -0
- package/src/components/scroll-area/ScrollArea.stories.tsx +153 -0
- package/src/components/select/Select.stories.tsx +146 -0
- package/src/components/separator/Separator.stories.tsx +117 -0
- package/src/components/sheet/Sheet.stories.tsx +195 -0
- package/src/components/skeleton/Skeleton.stories.tsx +114 -0
- package/src/components/slider/Slider.stories.tsx +157 -0
- package/src/components/switch/Switch.stories.tsx +114 -0
- package/src/components/table/Table.stories.tsx +153 -0
- package/src/components/tabs/Tabs.stories.tsx +155 -0
- package/src/components/textarea/Textarea.stories.tsx +116 -0
- package/src/components/toast/Toast.stories.tsx +160 -0
- package/src/components/toggle/Toggle.stories.tsx +125 -0
- package/src/components/tooltip/Tooltip.stories.tsx +133 -0
- package/src/components/typography/Typography.stories.tsx +207 -0
- package/src/components/waka-3d-pie-chart/Waka3DPieChart.stories.tsx +308 -0
- package/src/components/waka-achievement-unlock/WakaAchievementUnlock.stories.tsx +353 -0
- package/src/components/waka-artifact-list/WakaArtifactList.stories.tsx +258 -0
- package/src/components/waka-autocomplete/WakaAutocomplete.stories.tsx +224 -0
- package/src/components/waka-badge-showcase/WakaBadgeShowcase.stories.tsx +269 -0
- package/src/components/waka-barcode/WakaBarcode.stories.tsx +227 -0
- package/src/components/waka-bottom-sheet/WakaBottomSheet.stories.tsx +408 -0
- package/src/components/waka-breadcrumb/WakaBreadcrumb.stories.tsx +237 -0
- package/src/components/waka-build-matrix/WakaBuildMatrix.stories.tsx +328 -0
- package/src/components/waka-carousel/WakaCarousel.stories.tsx +296 -0
- package/src/components/waka-charts/WakaCharts.stories.tsx +519 -0
- package/src/components/waka-color-picker/WakaColorPicker.stories.tsx +200 -0
- package/src/components/waka-combobox/WakaCombobox.stories.tsx +250 -0
- package/src/components/waka-container-list/WakaContainerList.stories.tsx +315 -0
- package/src/components/waka-contribution-graph/WakaContributionGraph.stories.tsx +354 -0
- package/src/components/waka-cost-breakdown/WakaCostBreakdown.stories.tsx +348 -0
- package/src/components/waka-daily-reward/WakaDailyReward.stories.tsx +365 -0
- package/src/components/waka-database-card/WakaDatabaseCard.stories.tsx +306 -0
- package/src/components/waka-date-range-picker/WakaDateRangePicker.stories.tsx +339 -0
- package/src/components/waka-datetime-picker/WakaDateTimePicker.stories.tsx +317 -0
- package/src/components/waka-deployment-lane/WakaDeploymentLane.stories.tsx +292 -0
- package/src/components/waka-dock/WakaDock.stories.tsx +332 -0
- package/src/components/waka-drawer/WakaDrawer.stories.tsx +437 -0
- package/src/components/waka-env-var-editor/WakaEnvVarEditor.stories.tsx +263 -0
- package/src/components/waka-error-shake/WakaErrorShake.stories.tsx +410 -0
- package/src/components/waka-file-upload/WakaFileUpload.stories.tsx +239 -0
- package/src/components/waka-flow-diagram/WakaFlowDiagram.stories.tsx +365 -0
- package/src/components/waka-funnel-chart/WakaFunnelChart.stories.tsx +281 -0
- package/src/components/waka-glow-card/WakaGlowCard.stories.tsx +274 -0
- package/src/components/waka-haptic-button/WakaHapticButton.stories.tsx +349 -0
- package/src/components/waka-health-pulse/WakaHealthPulse.stories.tsx +293 -0
- package/src/components/waka-heatmap/WakaHeatmap.stories.tsx +376 -0
- package/src/components/waka-image/WakaImage.stories.tsx +255 -0
- package/src/components/waka-incident-timeline/WakaIncidentTimeline.stories.tsx +300 -0
- package/src/components/waka-kanban/WakaKanban.stories.tsx +399 -0
- package/src/components/waka-kubernetes-overview/WakaKubernetesOverview.stories.tsx +281 -0
- package/src/components/waka-leaderboard/WakaLeaderboard.stories.tsx +300 -0
- package/src/components/waka-level-progress/WakaLevelProgress.stories.tsx +313 -0
- package/src/components/waka-loading-orbit/WakaLoadingOrbit.stories.tsx +413 -0
- package/src/components/waka-log-viewer/WakaLogViewer.stories.tsx +312 -0
- package/src/components/waka-loot-box/WakaLootBox.stories.tsx +374 -0
- package/src/components/waka-metric-sparkline/WakaMetricSparkline.stories.tsx +312 -0
- package/src/components/waka-migration-list/WakaMigrationList.stories.tsx +289 -0
- package/src/components/waka-modal/WakaModal.stories.tsx +434 -0
- package/src/components/waka-morph-button/WakaMorphButton.stories.tsx +405 -0
- package/src/components/waka-network-topology/WakaNetworkTopology.stories.tsx +364 -0
- package/src/components/waka-notifications/WakaNotifications.stories.tsx +290 -0
- package/src/components/waka-number-input/WakaNumberInput.stories.tsx +282 -0
- package/src/components/waka-pagination/WakaPagination.stories.tsx +328 -0
- package/src/components/waka-password-strength/WakaPasswordStrength.stories.tsx +318 -0
- package/src/components/waka-pipeline-view/WakaPipelineView.stories.tsx +386 -0
- package/src/components/waka-player-card/WakaPlayerCard.stories.tsx +333 -0
- package/src/components/waka-pod-card/WakaPodCard.stories.tsx +435 -0
- package/src/components/waka-qrcode/WakaQRCode.stories.tsx +232 -0
- package/src/components/waka-query-explain/WakaQueryExplain.stories.tsx +407 -0
- package/src/components/waka-quest-card/WakaQuestCard.stories.tsx +394 -0
- package/src/components/waka-quota-bar/WakaQuotaBar.stories.tsx +435 -0
- package/src/components/waka-radar-score/WakaRadarScore.stories.tsx +372 -0
- package/src/components/waka-resource-gauge/WakaResourceGauge.stories.tsx +366 -0
- package/src/components/waka-rich-text-editor/WakaRichTextEditor.stories.tsx +238 -0
- package/src/components/waka-sankey-diagram/WakaSankeyDiagram.stories.tsx +389 -0
- package/src/components/waka-scratch-card/WakaScratchCard.stories.tsx +388 -0
- package/src/components/waka-secret-card/WakaSecretCard.stories.tsx +314 -0
- package/src/components/waka-segmented-control/WakaSegmentedControl.stories.tsx +309 -0
- package/src/components/waka-server-rack/WakaServerRack.stories.tsx +382 -0
- package/src/components/waka-service-graph/WakaServiceGraph.stories.tsx +262 -0
- package/src/components/waka-skeleton-wave/WakaSkeletonWave.stories.tsx +321 -0
- package/src/components/waka-skill-tree/WakaSkillTree.stories.tsx +308 -0
- package/src/components/waka-spin-wheel/WakaSpinWheel.stories.tsx +368 -0
- package/src/components/waka-spinner/WakaSpinner.stories.tsx +156 -0
- package/src/components/waka-stat/WakaStat.stories.tsx +334 -0
- package/src/components/waka-status-matrix/WakaStatusMatrix.stories.tsx +331 -0
- package/src/components/waka-stepper/WakaStepper.stories.tsx +468 -0
- package/src/components/waka-streak-counter/WakaStreakCounter.stories.tsx +235 -0
- package/src/components/waka-success-explosion/WakaSuccessExplosion.stories.tsx +389 -0
- package/src/components/waka-tabs-morph/WakaTabsMorph.stories.tsx +471 -0
- package/src/components/waka-terminal-output/WakaTerminalOutput.stories.tsx +351 -0
- package/src/components/waka-test-report/WakaTestReport.stories.tsx +322 -0
- package/src/components/waka-tilt-card/WakaTiltCard.stories.tsx +300 -0
- package/src/components/waka-time-picker/WakaTimePicker.stories.tsx +227 -0
- package/src/components/waka-timeline/WakaTimeline.stories.tsx +383 -0
- package/src/components/waka-tournament-bracket/WakaTournamentBracket.stories.tsx +375 -0
- package/src/components/waka-trace-viewer/WakaTraceViewer.stories.tsx +445 -0
- package/src/components/waka-tree/WakaTree.stories.tsx +359 -0
- package/src/components/waka-treemap-chart/WakaTreemapChart.stories.tsx +378 -0
- package/src/components/waka-typewriter/WakaTypewriter.stories.tsx +366 -0
- package/src/components/waka-versus-card/WakaVersusCard.stories.tsx +530 -0
- package/src/components/waka-video/WakaVideo.stories.tsx +203 -0
- package/src/components/waka-virtual-list/WakaVirtualList.stories.tsx +273 -0
- 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
|
+
}
|