@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,312 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { WakaLogViewer, defaultLogs } from './index'
|
|
3
|
+
import type { LogEntry, LogLevel } from './index'
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
|
|
6
|
+
const generateLogs = (count: number): LogEntry[] => {
|
|
7
|
+
const sources = ['api', 'db', 'cache', 'auth', 'worker', 'main']
|
|
8
|
+
const levels: LogLevel[] = ['info', 'debug', 'warn', 'error', 'trace']
|
|
9
|
+
const messages = [
|
|
10
|
+
'Request processed successfully',
|
|
11
|
+
'Loading configuration from environment',
|
|
12
|
+
'Connection established',
|
|
13
|
+
'Cache miss, fetching from database',
|
|
14
|
+
'User authentication successful',
|
|
15
|
+
'Processing batch job',
|
|
16
|
+
'Deprecated API version detected',
|
|
17
|
+
'High memory usage warning',
|
|
18
|
+
'Failed to connect to service',
|
|
19
|
+
'Query execution timeout',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
23
|
+
id: `log-${i}`,
|
|
24
|
+
timestamp: new Date(Date.now() - (count - i) * 1000),
|
|
25
|
+
level: levels[Math.floor(Math.random() * levels.length)],
|
|
26
|
+
message: messages[Math.floor(Math.random() * messages.length)],
|
|
27
|
+
source: sources[Math.floor(Math.random() * sources.length)],
|
|
28
|
+
}))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const logsWithMetadata: LogEntry[] = [
|
|
32
|
+
{
|
|
33
|
+
id: '1',
|
|
34
|
+
timestamp: new Date(),
|
|
35
|
+
level: 'info',
|
|
36
|
+
message: 'Application started successfully',
|
|
37
|
+
source: 'main',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: '2',
|
|
41
|
+
timestamp: new Date(),
|
|
42
|
+
level: 'debug',
|
|
43
|
+
message: 'Loading configuration from /etc/app/config.yaml',
|
|
44
|
+
source: 'config',
|
|
45
|
+
metadata: { path: '/etc/app/config.yaml', format: 'yaml', size: '2.4KB' },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: '3',
|
|
49
|
+
timestamp: new Date(),
|
|
50
|
+
level: 'info',
|
|
51
|
+
message: 'Database connection established',
|
|
52
|
+
source: 'db',
|
|
53
|
+
metadata: { host: 'postgres.example.com', port: 5432, database: 'production' },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: '4',
|
|
57
|
+
timestamp: new Date(),
|
|
58
|
+
level: 'warn',
|
|
59
|
+
message: 'Deprecated API version detected, please upgrade to v2',
|
|
60
|
+
source: 'api',
|
|
61
|
+
metadata: { currentVersion: 'v1', recommendedVersion: 'v2', deprecationDate: '2024-06-01' },
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: '5',
|
|
65
|
+
timestamp: new Date(),
|
|
66
|
+
level: 'error',
|
|
67
|
+
message: 'Failed to connect to Redis cache',
|
|
68
|
+
source: 'cache',
|
|
69
|
+
metadata: { host: 'redis.example.com', port: 6379 },
|
|
70
|
+
stackTrace: `Error: Connection refused
|
|
71
|
+
at RedisClient.connect (/app/node_modules/redis/lib/client.js:123:15)
|
|
72
|
+
at async CacheService.init (/app/src/services/cache.ts:45:5)
|
|
73
|
+
at async bootstrap (/app/src/main.ts:22:3)`,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: '6',
|
|
77
|
+
timestamp: new Date(),
|
|
78
|
+
level: 'info',
|
|
79
|
+
message: 'Retry connection attempt 1/3',
|
|
80
|
+
source: 'cache',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: '7',
|
|
84
|
+
timestamp: new Date(),
|
|
85
|
+
level: 'debug',
|
|
86
|
+
message: 'Request: GET /api/users/123',
|
|
87
|
+
source: 'api',
|
|
88
|
+
metadata: { method: 'GET', path: '/api/users/123', statusCode: 200, duration: '45ms' },
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: '8',
|
|
92
|
+
timestamp: new Date(),
|
|
93
|
+
level: 'trace',
|
|
94
|
+
message: 'SQL query executed',
|
|
95
|
+
source: 'db',
|
|
96
|
+
metadata: { query: 'SELECT * FROM users WHERE id = $1', params: [123], duration: '12ms' },
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
const meta: Meta<typeof WakaLogViewer> = {
|
|
101
|
+
title: 'Components/DevOps/WakaLogViewer',
|
|
102
|
+
component: WakaLogViewer,
|
|
103
|
+
parameters: {
|
|
104
|
+
layout: 'centered',
|
|
105
|
+
docs: {
|
|
106
|
+
description: {
|
|
107
|
+
component: 'A log viewer with level filtering, search, expandable metadata, stack traces, streaming support, and export functionality.',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
tags: ['autodocs'],
|
|
112
|
+
argTypes: {
|
|
113
|
+
showTimestamp: {
|
|
114
|
+
control: 'boolean',
|
|
115
|
+
description: 'Show timestamps',
|
|
116
|
+
},
|
|
117
|
+
showSource: {
|
|
118
|
+
control: 'boolean',
|
|
119
|
+
description: 'Show log source',
|
|
120
|
+
},
|
|
121
|
+
autoScroll: {
|
|
122
|
+
control: 'boolean',
|
|
123
|
+
description: 'Enable auto-scroll to bottom',
|
|
124
|
+
},
|
|
125
|
+
maxLogs: {
|
|
126
|
+
control: { type: 'number', min: 100, max: 5000, step: 100 },
|
|
127
|
+
description: 'Maximum number of logs to display',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export default meta
|
|
133
|
+
type Story = StoryObj<typeof WakaLogViewer>
|
|
134
|
+
|
|
135
|
+
export const Default: Story = {
|
|
136
|
+
args: {
|
|
137
|
+
logs: defaultLogs,
|
|
138
|
+
showTimestamp: true,
|
|
139
|
+
showSource: true,
|
|
140
|
+
autoScroll: true,
|
|
141
|
+
maxLogs: 1000,
|
|
142
|
+
},
|
|
143
|
+
render: (args) => (
|
|
144
|
+
<div className="w-[800px] h-[500px]">
|
|
145
|
+
<WakaLogViewer {...args} />
|
|
146
|
+
</div>
|
|
147
|
+
),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const WithMetadata: Story = {
|
|
151
|
+
render: () => (
|
|
152
|
+
<div className="w-[800px] h-[500px]">
|
|
153
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
154
|
+
Click on log entries to expand metadata and stack traces
|
|
155
|
+
</p>
|
|
156
|
+
<WakaLogViewer logs={logsWithMetadata} />
|
|
157
|
+
</div>
|
|
158
|
+
),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const LiveStreaming: Story = {
|
|
162
|
+
render: () => {
|
|
163
|
+
const [logs, setLogs] = React.useState<LogEntry[]>(defaultLogs)
|
|
164
|
+
const [isStreaming, setIsStreaming] = React.useState(true)
|
|
165
|
+
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (!isStreaming) return
|
|
168
|
+
|
|
169
|
+
const levels: LogLevel[] = ['info', 'debug', 'warn', 'error']
|
|
170
|
+
const sources = ['api', 'db', 'cache', 'worker']
|
|
171
|
+
const messages = [
|
|
172
|
+
'Request processed',
|
|
173
|
+
'Cache hit',
|
|
174
|
+
'Query executed',
|
|
175
|
+
'Task completed',
|
|
176
|
+
'High latency detected',
|
|
177
|
+
'Connection timeout',
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
const interval = setInterval(() => {
|
|
181
|
+
const newLog: LogEntry = {
|
|
182
|
+
id: `log-${Date.now()}`,
|
|
183
|
+
timestamp: new Date(),
|
|
184
|
+
level: levels[Math.floor(Math.random() * levels.length)],
|
|
185
|
+
message: messages[Math.floor(Math.random() * messages.length)],
|
|
186
|
+
source: sources[Math.floor(Math.random() * sources.length)],
|
|
187
|
+
}
|
|
188
|
+
setLogs(prev => [...prev.slice(-99), newLog])
|
|
189
|
+
}, 500)
|
|
190
|
+
|
|
191
|
+
return () => clearInterval(interval)
|
|
192
|
+
}, [isStreaming])
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div className="w-[800px] h-[500px]">
|
|
196
|
+
<WakaLogViewer
|
|
197
|
+
logs={logs}
|
|
198
|
+
isStreaming={isStreaming}
|
|
199
|
+
onToggleStreaming={() => setIsStreaming(!isStreaming)}
|
|
200
|
+
onClear={() => setLogs([])}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const ManyLogs: Story = {
|
|
208
|
+
render: () => (
|
|
209
|
+
<div className="w-[800px] h-[500px]">
|
|
210
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
211
|
+
500 logs with virtualization
|
|
212
|
+
</p>
|
|
213
|
+
<WakaLogViewer logs={generateLogs(500)} />
|
|
214
|
+
</div>
|
|
215
|
+
),
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export const NoTimestamps: Story = {
|
|
219
|
+
render: () => (
|
|
220
|
+
<div className="w-[800px] h-[400px]">
|
|
221
|
+
<WakaLogViewer logs={logsWithMetadata} showTimestamp={false} />
|
|
222
|
+
</div>
|
|
223
|
+
),
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export const NoSource: Story = {
|
|
227
|
+
render: () => (
|
|
228
|
+
<div className="w-[800px] h-[400px]">
|
|
229
|
+
<WakaLogViewer logs={logsWithMetadata} showSource={false} />
|
|
230
|
+
</div>
|
|
231
|
+
),
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const WithCallbacks: Story = {
|
|
235
|
+
render: () => {
|
|
236
|
+
const [logs, setLogs] = React.useState<LogEntry[]>(logsWithMetadata)
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<div className="w-[800px] h-[500px]">
|
|
240
|
+
<WakaLogViewer
|
|
241
|
+
logs={logs}
|
|
242
|
+
onClear={() => setLogs([])}
|
|
243
|
+
onExport={(filteredLogs) => {
|
|
244
|
+
console.log('Exporting logs:', filteredLogs)
|
|
245
|
+
alert(`Exporting ${filteredLogs.length} logs`)
|
|
246
|
+
}}
|
|
247
|
+
onLoadMore={() => {
|
|
248
|
+
console.log('Loading more logs...')
|
|
249
|
+
setLogs(prev => [...generateLogs(10), ...prev])
|
|
250
|
+
}}
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
)
|
|
254
|
+
},
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const ErrorsOnly: Story = {
|
|
258
|
+
render: () => {
|
|
259
|
+
const errorLogs: LogEntry[] = [
|
|
260
|
+
{
|
|
261
|
+
id: '1',
|
|
262
|
+
timestamp: new Date(Date.now() - 5 * 60 * 1000),
|
|
263
|
+
level: 'error',
|
|
264
|
+
message: 'TypeError: Cannot read property "id" of undefined',
|
|
265
|
+
source: 'api',
|
|
266
|
+
stackTrace: `TypeError: Cannot read property 'id' of undefined
|
|
267
|
+
at UserController.getUser (/app/src/controllers/user.ts:45:15)
|
|
268
|
+
at processRequest (/app/src/middleware/router.ts:89:12)`,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: '2',
|
|
272
|
+
timestamp: new Date(Date.now() - 10 * 60 * 1000),
|
|
273
|
+
level: 'error',
|
|
274
|
+
message: 'ECONNREFUSED: Connection refused to database',
|
|
275
|
+
source: 'db',
|
|
276
|
+
metadata: { host: 'db.example.com', port: 5432 },
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: '3',
|
|
280
|
+
timestamp: new Date(Date.now() - 15 * 60 * 1000),
|
|
281
|
+
level: 'error',
|
|
282
|
+
message: 'OutOfMemoryError: Heap space exceeded',
|
|
283
|
+
source: 'worker',
|
|
284
|
+
stackTrace: `java.lang.OutOfMemoryError: Java heap space
|
|
285
|
+
at java.util.Arrays.copyOf(Arrays.java:3210)
|
|
286
|
+
at java.util.ArrayList.grow(ArrayList.java:265)`,
|
|
287
|
+
},
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<div className="w-[800px] h-[400px]">
|
|
292
|
+
<WakaLogViewer logs={errorLogs} title="Error Logs" />
|
|
293
|
+
</div>
|
|
294
|
+
)
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export const CustomTitle: Story = {
|
|
299
|
+
render: () => (
|
|
300
|
+
<div className="w-[800px] h-[400px]">
|
|
301
|
+
<WakaLogViewer logs={logsWithMetadata} title="Application Logs - Production" />
|
|
302
|
+
</div>
|
|
303
|
+
),
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export const Empty: Story = {
|
|
307
|
+
render: () => (
|
|
308
|
+
<div className="w-[800px] h-[300px]">
|
|
309
|
+
<WakaLogViewer logs={[]} />
|
|
310
|
+
</div>
|
|
311
|
+
),
|
|
312
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { WakaLootBox, useLootBox } from './index'
|
|
3
|
+
import type { LootBoxItem, LootBoxRarity } from './index'
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
import { Sword, Shield, Crown, Gem, Star, Zap, Heart, Coins } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
const sampleItems: LootBoxItem[] = [
|
|
8
|
+
{
|
|
9
|
+
id: '1',
|
|
10
|
+
name: 'Legendary Sword',
|
|
11
|
+
description: 'A blade forged in dragon fire',
|
|
12
|
+
rarity: 'legendary',
|
|
13
|
+
icon: <Sword className="h-12 w-12" />,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: '2',
|
|
17
|
+
name: 'Epic Shield',
|
|
18
|
+
description: 'Unbreakable defense',
|
|
19
|
+
rarity: 'epic',
|
|
20
|
+
icon: <Shield className="h-12 w-12" />,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: '3',
|
|
24
|
+
name: 'Rare Gem',
|
|
25
|
+
description: 'A precious stone',
|
|
26
|
+
rarity: 'rare',
|
|
27
|
+
icon: <Gem className="h-12 w-12" />,
|
|
28
|
+
quantity: 5,
|
|
29
|
+
},
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const commonLoot: LootBoxItem[] = [
|
|
33
|
+
{ id: 'c1', name: 'Gold Coins', description: '+100 gold', rarity: 'common', icon: <Coins className="h-10 w-10" />, quantity: 100 },
|
|
34
|
+
{ id: 'c2', name: 'Health Potion', description: 'Restore 50 HP', rarity: 'common', icon: <Heart className="h-10 w-10" />, quantity: 3 },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const rareLoot: LootBoxItem[] = [
|
|
38
|
+
{ id: 'r1', name: 'Energy Crystal', description: '+50 Energy', rarity: 'rare', icon: <Zap className="h-10 w-10" /> },
|
|
39
|
+
{ id: 'r2', name: 'Sapphire', description: 'A rare gemstone', rarity: 'rare', icon: <Gem className="h-10 w-10" /> },
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
const epicLoot: LootBoxItem[] = [
|
|
43
|
+
{ id: 'e1', name: 'Phoenix Feather', description: 'Grants revival', rarity: 'epic', icon: <Star className="h-10 w-10" /> },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
const legendaryLoot: LootBoxItem[] = [
|
|
47
|
+
{ id: 'l1', name: 'Crown of Kings', description: 'Ultimate power', rarity: 'legendary', icon: <Crown className="h-10 w-10" /> },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
const meta: Meta<typeof WakaLootBox> = {
|
|
51
|
+
title: 'Components/Gamification/WakaLootBox',
|
|
52
|
+
component: WakaLootBox,
|
|
53
|
+
parameters: {
|
|
54
|
+
layout: 'centered',
|
|
55
|
+
docs: {
|
|
56
|
+
description: {
|
|
57
|
+
component: 'A loot box opening experience with shake animation, glow effects, particle bursts, and item reveal sequence.',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
tags: ['autodocs'],
|
|
62
|
+
argTypes: {
|
|
63
|
+
variant: {
|
|
64
|
+
control: 'select',
|
|
65
|
+
options: ['chest', 'crate', 'gift', 'orb'],
|
|
66
|
+
description: 'Visual style of the loot box',
|
|
67
|
+
},
|
|
68
|
+
size: {
|
|
69
|
+
control: 'select',
|
|
70
|
+
options: ['sm', 'default', 'lg'],
|
|
71
|
+
description: 'Size of the loot box',
|
|
72
|
+
},
|
|
73
|
+
autoReveal: {
|
|
74
|
+
control: 'boolean',
|
|
75
|
+
description: 'Auto-reveal items without navigation',
|
|
76
|
+
},
|
|
77
|
+
autoRevealDelay: {
|
|
78
|
+
control: { type: 'number', min: 500, max: 5000, step: 500 },
|
|
79
|
+
description: 'Delay between auto-reveal items (ms)',
|
|
80
|
+
},
|
|
81
|
+
disabled: {
|
|
82
|
+
control: 'boolean',
|
|
83
|
+
description: 'Disable interaction',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default meta
|
|
89
|
+
type Story = StoryObj<typeof WakaLootBox>
|
|
90
|
+
|
|
91
|
+
export const Default: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
items: sampleItems,
|
|
94
|
+
variant: 'chest',
|
|
95
|
+
size: 'default',
|
|
96
|
+
autoReveal: false,
|
|
97
|
+
autoRevealDelay: 1500,
|
|
98
|
+
disabled: false,
|
|
99
|
+
},
|
|
100
|
+
render: (args) => <WakaLootBox {...args} />,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const Variants: Story = {
|
|
104
|
+
render: () => (
|
|
105
|
+
<div className="flex flex-wrap gap-8 items-end">
|
|
106
|
+
<div className="text-center">
|
|
107
|
+
<p className="text-sm text-muted-foreground mb-2">Chest</p>
|
|
108
|
+
<WakaLootBox items={sampleItems} variant="chest" />
|
|
109
|
+
</div>
|
|
110
|
+
<div className="text-center">
|
|
111
|
+
<p className="text-sm text-muted-foreground mb-2">Crate</p>
|
|
112
|
+
<WakaLootBox items={rareLoot} variant="crate" />
|
|
113
|
+
</div>
|
|
114
|
+
<div className="text-center">
|
|
115
|
+
<p className="text-sm text-muted-foreground mb-2">Gift</p>
|
|
116
|
+
<WakaLootBox items={epicLoot} variant="gift" />
|
|
117
|
+
</div>
|
|
118
|
+
<div className="text-center">
|
|
119
|
+
<p className="text-sm text-muted-foreground mb-2">Orb</p>
|
|
120
|
+
<WakaLootBox items={legendaryLoot} variant="orb" />
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const Sizes: Story = {
|
|
127
|
+
render: () => (
|
|
128
|
+
<div className="flex flex-wrap gap-8 items-end">
|
|
129
|
+
<div className="text-center">
|
|
130
|
+
<p className="text-sm text-muted-foreground mb-2">Small</p>
|
|
131
|
+
<WakaLootBox items={commonLoot} size="sm" />
|
|
132
|
+
</div>
|
|
133
|
+
<div className="text-center">
|
|
134
|
+
<p className="text-sm text-muted-foreground mb-2">Default</p>
|
|
135
|
+
<WakaLootBox items={rareLoot} size="default" />
|
|
136
|
+
</div>
|
|
137
|
+
<div className="text-center">
|
|
138
|
+
<p className="text-sm text-muted-foreground mb-2">Large</p>
|
|
139
|
+
<WakaLootBox items={epicLoot} size="lg" />
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const RarityEffects: Story = {
|
|
146
|
+
render: () => (
|
|
147
|
+
<div className="flex flex-wrap gap-8 items-end">
|
|
148
|
+
<div className="text-center">
|
|
149
|
+
<p className="text-sm text-muted-foreground mb-2">Common Loot</p>
|
|
150
|
+
<WakaLootBox items={commonLoot} variant="crate" />
|
|
151
|
+
</div>
|
|
152
|
+
<div className="text-center">
|
|
153
|
+
<p className="text-sm text-muted-foreground mb-2">Rare Loot</p>
|
|
154
|
+
<WakaLootBox items={rareLoot} variant="gift" />
|
|
155
|
+
</div>
|
|
156
|
+
<div className="text-center">
|
|
157
|
+
<p className="text-sm text-muted-foreground mb-2">Epic Loot</p>
|
|
158
|
+
<WakaLootBox items={epicLoot} variant="chest" />
|
|
159
|
+
</div>
|
|
160
|
+
<div className="text-center">
|
|
161
|
+
<p className="text-sm text-muted-foreground mb-2">Legendary Loot</p>
|
|
162
|
+
<WakaLootBox items={legendaryLoot} variant="orb" />
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
),
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const AutoReveal: Story = {
|
|
169
|
+
render: () => (
|
|
170
|
+
<div className="space-y-4">
|
|
171
|
+
<p className="text-sm text-muted-foreground text-center">
|
|
172
|
+
Items will automatically cycle through after opening
|
|
173
|
+
</p>
|
|
174
|
+
<WakaLootBox items={sampleItems} autoReveal autoRevealDelay={2000} />
|
|
175
|
+
</div>
|
|
176
|
+
),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const Interactive: Story = {
|
|
180
|
+
render: () => {
|
|
181
|
+
const [openedBoxes, setOpenedBoxes] = React.useState(0)
|
|
182
|
+
const [totalItems, setTotalItems] = React.useState<LootBoxItem[]>([])
|
|
183
|
+
|
|
184
|
+
const handleRevealComplete = (items: LootBoxItem[]) => {
|
|
185
|
+
setOpenedBoxes(prev => prev + 1)
|
|
186
|
+
setTotalItems(prev => [...prev, ...items])
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div className="space-y-6">
|
|
191
|
+
<div className="flex gap-8">
|
|
192
|
+
<WakaLootBox
|
|
193
|
+
items={commonLoot}
|
|
194
|
+
variant="crate"
|
|
195
|
+
onRevealComplete={handleRevealComplete}
|
|
196
|
+
/>
|
|
197
|
+
<WakaLootBox
|
|
198
|
+
items={rareLoot}
|
|
199
|
+
variant="gift"
|
|
200
|
+
onRevealComplete={handleRevealComplete}
|
|
201
|
+
/>
|
|
202
|
+
<WakaLootBox
|
|
203
|
+
items={epicLoot}
|
|
204
|
+
variant="chest"
|
|
205
|
+
onRevealComplete={handleRevealComplete}
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div className="p-4 rounded-lg border bg-muted/30">
|
|
210
|
+
<div className="flex justify-between items-center">
|
|
211
|
+
<span className="text-sm text-muted-foreground">Boxes Opened: {openedBoxes}</span>
|
|
212
|
+
<span className="text-sm text-muted-foreground">Items Collected: {totalItems.length}</span>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{totalItems.length > 0 && (
|
|
216
|
+
<div className="mt-3 flex flex-wrap gap-2">
|
|
217
|
+
{totalItems.map((item, i) => (
|
|
218
|
+
<div
|
|
219
|
+
key={`${item.id}-${i}`}
|
|
220
|
+
className={`px-2 py-1 rounded text-xs font-medium ${
|
|
221
|
+
item.rarity === 'legendary' ? 'bg-amber-500/20 text-amber-500' :
|
|
222
|
+
item.rarity === 'epic' ? 'bg-purple-500/20 text-purple-500' :
|
|
223
|
+
item.rarity === 'rare' ? 'bg-blue-500/20 text-blue-500' :
|
|
224
|
+
'bg-slate-500/20 text-slate-500'
|
|
225
|
+
}`}
|
|
226
|
+
>
|
|
227
|
+
{item.name}
|
|
228
|
+
</div>
|
|
229
|
+
))}
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
)
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const WithHook: Story = {
|
|
239
|
+
render: () => {
|
|
240
|
+
const [inventory, setInventory] = React.useState<LootBoxItem[]>([])
|
|
241
|
+
|
|
242
|
+
const lootBox = useLootBox({
|
|
243
|
+
items: sampleItems,
|
|
244
|
+
onRevealComplete: (items) => {
|
|
245
|
+
setInventory(prev => [...prev, ...items])
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div className="space-y-6 w-[400px]">
|
|
251
|
+
<div className="flex justify-between items-center">
|
|
252
|
+
<h3 className="font-semibold">Loot Box Demo</h3>
|
|
253
|
+
<span className="text-sm text-muted-foreground">
|
|
254
|
+
Phase: {lootBox.phase}
|
|
255
|
+
</span>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<WakaLootBox
|
|
259
|
+
items={sampleItems}
|
|
260
|
+
isOpen={lootBox.isOpen}
|
|
261
|
+
onOpen={lootBox.open}
|
|
262
|
+
onClose={lootBox.close}
|
|
263
|
+
/>
|
|
264
|
+
|
|
265
|
+
<div className="flex gap-2">
|
|
266
|
+
<button
|
|
267
|
+
onClick={() => lootBox.open()}
|
|
268
|
+
disabled={lootBox.isOpen}
|
|
269
|
+
className="px-3 py-1.5 text-sm rounded bg-primary text-primary-foreground disabled:opacity-50"
|
|
270
|
+
>
|
|
271
|
+
Open Box
|
|
272
|
+
</button>
|
|
273
|
+
<button
|
|
274
|
+
onClick={() => lootBox.reset()}
|
|
275
|
+
className="px-3 py-1.5 text-sm rounded border hover:bg-muted"
|
|
276
|
+
>
|
|
277
|
+
Reset
|
|
278
|
+
</button>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{inventory.length > 0 && (
|
|
282
|
+
<div className="p-4 rounded-lg border">
|
|
283
|
+
<h4 className="text-sm font-medium mb-2">Inventory ({inventory.length})</h4>
|
|
284
|
+
<div className="flex flex-wrap gap-2">
|
|
285
|
+
{inventory.map((item, i) => (
|
|
286
|
+
<div key={`${item.id}-${i}`} className="text-xs px-2 py-1 bg-muted rounded">
|
|
287
|
+
{item.name} {item.quantity && `x${item.quantity}`}
|
|
288
|
+
</div>
|
|
289
|
+
))}
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
</div>
|
|
294
|
+
)
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export const MixedRarityBox: Story = {
|
|
299
|
+
render: () => {
|
|
300
|
+
const mixedItems: LootBoxItem[] = [
|
|
301
|
+
...commonLoot,
|
|
302
|
+
...rareLoot,
|
|
303
|
+
...epicLoot,
|
|
304
|
+
...legendaryLoot,
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div className="space-y-4">
|
|
309
|
+
<p className="text-sm text-muted-foreground text-center">
|
|
310
|
+
Contains items of all rarities - the box glows based on the highest rarity inside
|
|
311
|
+
</p>
|
|
312
|
+
<WakaLootBox items={mixedItems} variant="chest" size="lg" />
|
|
313
|
+
</div>
|
|
314
|
+
)
|
|
315
|
+
},
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export const GachaSimulator: Story = {
|
|
319
|
+
render: () => {
|
|
320
|
+
const [pulls, setPulls] = React.useState(0)
|
|
321
|
+
const [legendary, setLegendary] = React.useState(0)
|
|
322
|
+
const [currentItems, setCurrentItems] = React.useState<LootBoxItem[]>(commonLoot)
|
|
323
|
+
|
|
324
|
+
const getRandomLoot = (): LootBoxItem[] => {
|
|
325
|
+
const roll = Math.random()
|
|
326
|
+
if (roll < 0.01) return legendaryLoot
|
|
327
|
+
if (roll < 0.10) return epicLoot
|
|
328
|
+
if (roll < 0.30) return rareLoot
|
|
329
|
+
return commonLoot
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const handleOpen = () => {
|
|
333
|
+
setPulls(prev => prev + 1)
|
|
334
|
+
const loot = getRandomLoot()
|
|
335
|
+
setCurrentItems(loot)
|
|
336
|
+
if (loot[0]?.rarity === 'legendary') {
|
|
337
|
+
setLegendary(prev => prev + 1)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<div className="space-y-6 w-[400px]">
|
|
343
|
+
<div className="p-4 rounded-lg border bg-gradient-to-r from-amber-500/10 to-orange-500/10">
|
|
344
|
+
<h3 className="font-bold text-center mb-2">Gacha Simulator</h3>
|
|
345
|
+
<div className="flex justify-between text-sm">
|
|
346
|
+
<span>Total Pulls: {pulls}</span>
|
|
347
|
+
<span className="text-amber-500">Legendaries: {legendary}</span>
|
|
348
|
+
</div>
|
|
349
|
+
<div className="text-xs text-muted-foreground text-center mt-2">
|
|
350
|
+
1% Legendary | 10% Epic | 30% Rare | 59% Common
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<WakaLootBox
|
|
355
|
+
key={pulls}
|
|
356
|
+
items={currentItems}
|
|
357
|
+
variant="orb"
|
|
358
|
+
onOpen={handleOpen}
|
|
359
|
+
/>
|
|
360
|
+
</div>
|
|
361
|
+
)
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export const Disabled: Story = {
|
|
366
|
+
render: () => (
|
|
367
|
+
<div className="space-y-4">
|
|
368
|
+
<p className="text-sm text-muted-foreground text-center">
|
|
369
|
+
Disabled state - cannot be opened
|
|
370
|
+
</p>
|
|
371
|
+
<WakaLootBox items={sampleItems} disabled />
|
|
372
|
+
</div>
|
|
373
|
+
),
|
|
374
|
+
}
|