@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,388 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { WakaScratchCard, useScratchCard } from './index'
3
+ import type { Prize, PrizeRarity } from './index'
4
+ import * as React from 'react'
5
+ import { Gift, Star, Crown, Gem, Coins, Zap, Trophy } from 'lucide-react'
6
+
7
+ const samplePrize: Prize = {
8
+ id: '1',
9
+ name: 'Grand Prize',
10
+ description: 'You won the jackpot!',
11
+ rarity: 'legendary',
12
+ icon: <Crown className="h-12 w-12" />,
13
+ value: '$1,000',
14
+ }
15
+
16
+ const prizesByRarity: Record<PrizeRarity, Prize> = {
17
+ common: {
18
+ id: 'common',
19
+ name: 'Participation Reward',
20
+ description: 'Thanks for playing!',
21
+ rarity: 'common',
22
+ icon: <Star className="h-10 w-10" />,
23
+ value: '10 Points',
24
+ },
25
+ rare: {
26
+ id: 'rare',
27
+ name: 'Silver Prize',
28
+ description: 'Nice scratch!',
29
+ rarity: 'rare',
30
+ icon: <Coins className="h-10 w-10" />,
31
+ value: '50 Coins',
32
+ },
33
+ epic: {
34
+ id: 'epic',
35
+ name: 'Gold Prize',
36
+ description: 'Amazing luck!',
37
+ rarity: 'epic',
38
+ icon: <Gem className="h-10 w-10" />,
39
+ value: '200 Gems',
40
+ },
41
+ legendary: samplePrize,
42
+ }
43
+
44
+ const meta: Meta<typeof WakaScratchCard> = {
45
+ title: 'Components/Gamification/WakaScratchCard',
46
+ component: WakaScratchCard,
47
+ parameters: {
48
+ layout: 'centered',
49
+ docs: {
50
+ description: {
51
+ component: 'An interactive scratch card with canvas-based scratching, reveal effects, confetti celebration, and customizable overlay patterns.',
52
+ },
53
+ },
54
+ },
55
+ tags: ['autodocs'],
56
+ argTypes: {
57
+ width: {
58
+ control: { type: 'number', min: 200, max: 500, step: 50 },
59
+ description: 'Card width in pixels',
60
+ },
61
+ height: {
62
+ control: { type: 'number', min: 150, max: 400, step: 50 },
63
+ description: 'Card height in pixels',
64
+ },
65
+ revealThreshold: {
66
+ control: { type: 'range', min: 0.3, max: 0.9, step: 0.1 },
67
+ description: 'Percentage of scratching needed to reveal (0-1)',
68
+ },
69
+ brushSize: {
70
+ control: { type: 'number', min: 20, max: 80, step: 10 },
71
+ description: 'Scratch brush size',
72
+ },
73
+ overlayPattern: {
74
+ control: 'select',
75
+ options: ['solid', 'stripes', 'dots', 'gradient'],
76
+ description: 'Overlay pattern style',
77
+ },
78
+ showProgress: {
79
+ control: 'boolean',
80
+ description: 'Show scratch progress indicator',
81
+ },
82
+ disabled: {
83
+ control: 'boolean',
84
+ description: 'Disable scratching',
85
+ },
86
+ },
87
+ }
88
+
89
+ export default meta
90
+ type Story = StoryObj<typeof WakaScratchCard>
91
+
92
+ export const Default: Story = {
93
+ args: {
94
+ prize: samplePrize,
95
+ width: 300,
96
+ height: 200,
97
+ revealThreshold: 0.5,
98
+ brushSize: 40,
99
+ overlayPattern: 'solid',
100
+ },
101
+ render: (args) => <WakaScratchCard {...args} />,
102
+ }
103
+
104
+ export const Rarities: Story = {
105
+ render: () => (
106
+ <div className="grid grid-cols-2 gap-6">
107
+ {(['common', 'rare', 'epic', 'legendary'] as PrizeRarity[]).map((rarity) => (
108
+ <div key={rarity} className="text-center">
109
+ <p className="text-sm text-muted-foreground mb-2 capitalize">{rarity}</p>
110
+ <WakaScratchCard prize={prizesByRarity[rarity]} width={250} height={180} />
111
+ </div>
112
+ ))}
113
+ </div>
114
+ ),
115
+ }
116
+
117
+ export const OverlayPatterns: Story = {
118
+ render: () => (
119
+ <div className="grid grid-cols-2 gap-6">
120
+ <div className="text-center">
121
+ <p className="text-sm text-muted-foreground mb-2">Gradient (Default)</p>
122
+ <WakaScratchCard
123
+ prize={prizesByRarity.epic}
124
+ overlayPattern="gradient"
125
+ width={250}
126
+ height={180}
127
+ />
128
+ </div>
129
+ <div className="text-center">
130
+ <p className="text-sm text-muted-foreground mb-2">Solid</p>
131
+ <WakaScratchCard
132
+ prize={prizesByRarity.rare}
133
+ overlayPattern="solid"
134
+ overlayColor="#4f46e5"
135
+ width={250}
136
+ height={180}
137
+ />
138
+ </div>
139
+ <div className="text-center">
140
+ <p className="text-sm text-muted-foreground mb-2">Stripes</p>
141
+ <WakaScratchCard
142
+ prize={prizesByRarity.common}
143
+ overlayPattern="stripes"
144
+ overlayColor="#22c55e"
145
+ width={250}
146
+ height={180}
147
+ />
148
+ </div>
149
+ <div className="text-center">
150
+ <p className="text-sm text-muted-foreground mb-2">Dots</p>
151
+ <WakaScratchCard
152
+ prize={prizesByRarity.legendary}
153
+ overlayPattern="dots"
154
+ overlayColor="#f59e0b"
155
+ width={250}
156
+ height={180}
157
+ />
158
+ </div>
159
+ </div>
160
+ ),
161
+ }
162
+
163
+ export const Sizes: Story = {
164
+ render: () => (
165
+ <div className="flex flex-wrap gap-6 items-start">
166
+ <div className="text-center">
167
+ <p className="text-sm text-muted-foreground mb-2">Small</p>
168
+ <WakaScratchCard prize={prizesByRarity.common} width={200} height={150} />
169
+ </div>
170
+ <div className="text-center">
171
+ <p className="text-sm text-muted-foreground mb-2">Medium (Default)</p>
172
+ <WakaScratchCard prize={prizesByRarity.rare} width={300} height={200} />
173
+ </div>
174
+ <div className="text-center">
175
+ <p className="text-sm text-muted-foreground mb-2">Large</p>
176
+ <WakaScratchCard prize={prizesByRarity.epic} width={400} height={280} />
177
+ </div>
178
+ </div>
179
+ ),
180
+ }
181
+
182
+ export const WithProgress: Story = {
183
+ render: () => (
184
+ <div className="space-y-4">
185
+ <p className="text-sm text-muted-foreground text-center">
186
+ Scratch to see the progress indicator
187
+ </p>
188
+ <WakaScratchCard prize={samplePrize} showProgress />
189
+ </div>
190
+ ),
191
+ }
192
+
193
+ export const CustomRevealThreshold: Story = {
194
+ render: () => (
195
+ <div className="flex gap-6">
196
+ <div className="text-center">
197
+ <p className="text-sm text-muted-foreground mb-2">30% threshold</p>
198
+ <WakaScratchCard
199
+ prize={prizesByRarity.rare}
200
+ revealThreshold={0.3}
201
+ showProgress
202
+ width={250}
203
+ height={180}
204
+ />
205
+ </div>
206
+ <div className="text-center">
207
+ <p className="text-sm text-muted-foreground mb-2">80% threshold</p>
208
+ <WakaScratchCard
209
+ prize={prizesByRarity.epic}
210
+ revealThreshold={0.8}
211
+ showProgress
212
+ width={250}
213
+ height={180}
214
+ />
215
+ </div>
216
+ </div>
217
+ ),
218
+ }
219
+
220
+ export const Interactive: Story = {
221
+ render: () => {
222
+ const [revealed, setRevealed] = React.useState(false)
223
+ const [percentage, setPercentage] = React.useState(0)
224
+ const [revealedPrize, setRevealedPrize] = React.useState<Prize | null>(null)
225
+
226
+ return (
227
+ <div className="space-y-6 w-[350px]">
228
+ <WakaScratchCard
229
+ prize={samplePrize}
230
+ onScratch={(pct) => setPercentage(pct)}
231
+ onReveal={(prize) => {
232
+ setRevealed(true)
233
+ setRevealedPrize(prize)
234
+ }}
235
+ onReset={() => {
236
+ setRevealed(false)
237
+ setPercentage(0)
238
+ setRevealedPrize(null)
239
+ }}
240
+ />
241
+
242
+ <div className="p-4 rounded-lg border bg-muted/30">
243
+ <div className="grid grid-cols-2 gap-2 text-sm">
244
+ <div>Scratched: {percentage.toFixed(0)}%</div>
245
+ <div>Revealed: {revealed ? 'Yes' : 'No'}</div>
246
+ </div>
247
+ {revealedPrize && (
248
+ <div className="mt-2 pt-2 border-t text-sm">
249
+ <span className="text-muted-foreground">Prize: </span>
250
+ <span className="font-medium">{revealedPrize.name}</span>
251
+ </div>
252
+ )}
253
+ </div>
254
+ </div>
255
+ )
256
+ },
257
+ }
258
+
259
+ export const WithHook: Story = {
260
+ render: () => {
261
+ const scratchCard = useScratchCard({
262
+ threshold: 60,
263
+ onReveal: () => console.log('Revealed!'),
264
+ })
265
+
266
+ return (
267
+ <div className="space-y-6 w-[350px]">
268
+ <WakaScratchCard
269
+ prize={prizesByRarity.legendary}
270
+ onScratch={(pct) => scratchCard.setPercentage(pct)}
271
+ onReveal={() => scratchCard.reveal()}
272
+ onReset={() => scratchCard.reset()}
273
+ />
274
+
275
+ <div className="p-4 rounded-lg border">
276
+ <h4 className="font-medium mb-2">Hook State</h4>
277
+ <div className="grid grid-cols-2 gap-2 text-sm">
278
+ <div>Percentage: {scratchCard.percentage.toFixed(0)}%</div>
279
+ <div>Revealed: {scratchCard.isRevealed ? 'Yes' : 'No'}</div>
280
+ <div>Scratching: {scratchCard.isScratching ? 'Yes' : 'No'}</div>
281
+ </div>
282
+
283
+ <div className="mt-3 flex gap-2">
284
+ <button
285
+ onClick={() => scratchCard.reveal()}
286
+ disabled={scratchCard.isRevealed}
287
+ className="px-3 py-1.5 text-xs rounded bg-primary text-primary-foreground disabled:opacity-50"
288
+ >
289
+ Force Reveal
290
+ </button>
291
+ <button
292
+ onClick={() => scratchCard.reset()}
293
+ className="px-3 py-1.5 text-xs rounded border hover:bg-muted"
294
+ >
295
+ Reset
296
+ </button>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ )
301
+ },
302
+ }
303
+
304
+ export const LotteryTicket: Story = {
305
+ render: () => (
306
+ <div className="p-6 rounded-xl border bg-gradient-to-br from-amber-50 to-orange-50 dark:from-amber-950/30 dark:to-orange-950/30">
307
+ <div className="text-center mb-4">
308
+ <h3 className="text-xl font-bold text-amber-600 dark:text-amber-400">Lucky Lottery</h3>
309
+ <p className="text-sm text-muted-foreground">Scratch to reveal your prize!</p>
310
+ </div>
311
+ <WakaScratchCard
312
+ prize={samplePrize}
313
+ overlayColor="#d97706"
314
+ overlayPattern="stripes"
315
+ showProgress
316
+ />
317
+ <p className="text-xs text-center text-muted-foreground mt-4">
318
+ Ticket #8472-9123 • Valid until Dec 31, 2024
319
+ </p>
320
+ </div>
321
+ ),
322
+ }
323
+
324
+ export const PromoCard: Story = {
325
+ render: () => {
326
+ const promoPrize: Prize = {
327
+ id: 'promo',
328
+ name: '50% OFF',
329
+ description: 'On your next purchase',
330
+ rarity: 'epic',
331
+ icon: <Gift className="h-12 w-12" />,
332
+ value: 'SAVE50',
333
+ }
334
+
335
+ return (
336
+ <div className="p-6 rounded-xl border bg-gradient-to-br from-violet-500/10 to-purple-500/10">
337
+ <div className="text-center mb-4">
338
+ <h3 className="text-lg font-bold">Special Offer</h3>
339
+ <p className="text-sm text-muted-foreground">Scratch to reveal your discount!</p>
340
+ </div>
341
+ <WakaScratchCard
342
+ prize={promoPrize}
343
+ overlayColor="#8b5cf6"
344
+ overlayPattern="dots"
345
+ width={280}
346
+ height={180}
347
+ />
348
+ </div>
349
+ )
350
+ },
351
+ }
352
+
353
+ export const CustomPrizeRenderer: Story = {
354
+ render: () => (
355
+ <WakaScratchCard
356
+ prize={prizesByRarity.legendary}
357
+ renderPrize={(prize, revealed) => (
358
+ <div
359
+ className={`h-full w-full flex flex-col items-center justify-center bg-gradient-to-br from-amber-900 to-orange-900 p-4 transition-all ${
360
+ revealed ? 'opacity-100' : 'opacity-0'
361
+ }`}
362
+ >
363
+ <Trophy className="h-16 w-16 text-amber-400 mb-3" />
364
+ <div className="text-2xl font-bold text-white">{prize.value}</div>
365
+ <div className="text-amber-300 text-center mt-2">
366
+ {prize.name}
367
+ </div>
368
+ {revealed && (
369
+ <button className="mt-4 px-4 py-2 bg-amber-500 text-white rounded-lg font-medium hover:bg-amber-400 transition-colors">
370
+ Claim Prize
371
+ </button>
372
+ )}
373
+ </div>
374
+ )}
375
+ />
376
+ ),
377
+ }
378
+
379
+ export const Disabled: Story = {
380
+ render: () => (
381
+ <div className="space-y-4">
382
+ <p className="text-sm text-muted-foreground text-center">
383
+ Disabled state - cannot scratch
384
+ </p>
385
+ <WakaScratchCard prize={samplePrize} disabled />
386
+ </div>
387
+ ),
388
+ }
@@ -0,0 +1,314 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { WakaSecretCard, defaultSecret } from './index'
3
+ import type { Secret } from './index'
4
+ import * as React from 'react'
5
+
6
+ const expiredSecret: Secret = {
7
+ id: '2',
8
+ name: 'Expired Certificate',
9
+ description: 'SSL certificate that has expired',
10
+ type: 'certificate',
11
+ value: '-----BEGIN CERTIFICATE-----...',
12
+ createdAt: new Date(Date.now() - 365 * 24 * 3600000),
13
+ updatedAt: new Date(Date.now() - 30 * 24 * 3600000),
14
+ expiresAt: new Date(Date.now() - 7 * 24 * 3600000),
15
+ tags: ['ssl', 'critical'],
16
+ }
17
+
18
+ const expiringSoonSecret: Secret = {
19
+ id: '3',
20
+ name: 'Database Password',
21
+ description: 'Password for production database',
22
+ type: 'password',
23
+ value: 'super-secret-password-123',
24
+ createdAt: new Date(Date.now() - 180 * 24 * 3600000),
25
+ updatedAt: new Date(Date.now() - 5 * 24 * 3600000),
26
+ expiresAt: new Date(Date.now() + 3 * 24 * 3600000),
27
+ rotatedAt: new Date(Date.now() - 5 * 24 * 3600000),
28
+ rotationPeriod: 90,
29
+ lastAccessedAt: new Date(Date.now() - 12 * 3600000),
30
+ lastAccessedBy: 'postgres-service',
31
+ tags: ['database', 'production'],
32
+ }
33
+
34
+ const needsRotationSecret: Secret = {
35
+ id: '4',
36
+ name: 'AWS Secret Key',
37
+ description: 'AWS access credentials',
38
+ type: 'api-key',
39
+ value: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
40
+ createdAt: new Date(Date.now() - 120 * 24 * 3600000),
41
+ updatedAt: new Date(Date.now() - 45 * 24 * 3600000),
42
+ rotatedAt: new Date(Date.now() - 45 * 24 * 3600000),
43
+ rotationPeriod: 30,
44
+ lastAccessedAt: new Date(Date.now() - 1 * 3600000),
45
+ lastAccessedBy: 's3-backup-job',
46
+ tags: ['aws', 'cloud'],
47
+ }
48
+
49
+ const tokenSecret: Secret = {
50
+ id: '5',
51
+ name: 'GitHub Personal Access Token',
52
+ type: 'token',
53
+ value: 'ghp_1234567890abcdefghijklmnopqrstuvwxyz',
54
+ createdAt: new Date(Date.now() - 60 * 24 * 3600000),
55
+ updatedAt: new Date(Date.now() - 60 * 24 * 3600000),
56
+ tags: ['github', 'ci-cd'],
57
+ }
58
+
59
+ const simpleSecret: Secret = {
60
+ id: '6',
61
+ name: 'Simple API Key',
62
+ type: 'api-key',
63
+ createdAt: new Date(Date.now() - 30 * 24 * 3600000),
64
+ updatedAt: new Date(Date.now() - 30 * 24 * 3600000),
65
+ }
66
+
67
+ const meta: Meta<typeof WakaSecretCard> = {
68
+ title: 'Components/DevOps/WakaSecretCard',
69
+ component: WakaSecretCard,
70
+ parameters: {
71
+ layout: 'centered',
72
+ docs: {
73
+ description: {
74
+ component: 'A secret management card with expiration warnings, rotation indicators, access history, and secure value display.',
75
+ },
76
+ },
77
+ },
78
+ tags: ['autodocs'],
79
+ argTypes: {
80
+ isValueVisible: {
81
+ control: 'boolean',
82
+ description: 'Whether the secret value is visible',
83
+ },
84
+ showHistory: {
85
+ control: 'boolean',
86
+ description: 'Show access history section',
87
+ },
88
+ compact: {
89
+ control: 'boolean',
90
+ description: 'Compact row mode',
91
+ },
92
+ },
93
+ }
94
+
95
+ export default meta
96
+ type Story = StoryObj<typeof WakaSecretCard>
97
+
98
+ export const Default: Story = {
99
+ args: {
100
+ secret: defaultSecret,
101
+ },
102
+ render: (args) => (
103
+ <div className="w-[500px]">
104
+ <WakaSecretCard
105
+ {...args}
106
+ onRotate={() => console.log('Rotate')}
107
+ onDelete={() => console.log('Delete')}
108
+ onCopy={() => console.log('Copy')}
109
+ onView={() => console.log('View')}
110
+ />
111
+ </div>
112
+ ),
113
+ }
114
+
115
+ export const WithAccessHistory: Story = {
116
+ render: () => (
117
+ <div className="w-[500px]">
118
+ <WakaSecretCard
119
+ secret={defaultSecret}
120
+ showHistory
121
+ onRotate={() => console.log('Rotate')}
122
+ onDelete={() => console.log('Delete')}
123
+ onCopy={() => console.log('Copy')}
124
+ onView={() => console.log('View')}
125
+ />
126
+ </div>
127
+ ),
128
+ }
129
+
130
+ export const Expired: Story = {
131
+ render: () => (
132
+ <div className="w-[500px]">
133
+ <WakaSecretCard
134
+ secret={expiredSecret}
135
+ onRotate={() => console.log('Rotate')}
136
+ onDelete={() => console.log('Delete')}
137
+ />
138
+ </div>
139
+ ),
140
+ }
141
+
142
+ export const ExpiringSoon: Story = {
143
+ render: () => (
144
+ <div className="w-[500px]">
145
+ <WakaSecretCard
146
+ secret={expiringSoonSecret}
147
+ onRotate={() => console.log('Rotate')}
148
+ onDelete={() => console.log('Delete')}
149
+ onCopy={() => console.log('Copy')}
150
+ onView={() => console.log('View')}
151
+ />
152
+ </div>
153
+ ),
154
+ }
155
+
156
+ export const NeedsRotation: Story = {
157
+ render: () => (
158
+ <div className="w-[500px]">
159
+ <WakaSecretCard
160
+ secret={needsRotationSecret}
161
+ onRotate={() => console.log('Rotate')}
162
+ onDelete={() => console.log('Delete')}
163
+ onCopy={() => console.log('Copy')}
164
+ onView={() => console.log('View')}
165
+ />
166
+ </div>
167
+ ),
168
+ }
169
+
170
+ export const AllTypes: Story = {
171
+ render: () => {
172
+ const types: Secret['type'][] = ['api-key', 'password', 'certificate', 'token', 'other']
173
+
174
+ return (
175
+ <div className="space-y-4 w-[500px]">
176
+ {types.map((type) => (
177
+ <WakaSecretCard
178
+ key={type}
179
+ secret={{
180
+ id: type,
181
+ name: `${type.replace('-', ' ')} Secret`,
182
+ type,
183
+ createdAt: new Date(),
184
+ updatedAt: new Date(),
185
+ }}
186
+ compact
187
+ />
188
+ ))}
189
+ </div>
190
+ )
191
+ },
192
+ }
193
+
194
+ export const ValueVisible: Story = {
195
+ render: () => {
196
+ const [visible, setVisible] = React.useState(false)
197
+
198
+ return (
199
+ <div className="w-[500px]">
200
+ <WakaSecretCard
201
+ secret={defaultSecret}
202
+ isValueVisible={visible}
203
+ onView={() => setVisible(!visible)}
204
+ onCopy={() => {
205
+ navigator.clipboard?.writeText(defaultSecret.value || '')
206
+ alert('Copied to clipboard!')
207
+ }}
208
+ onRotate={() => console.log('Rotate')}
209
+ />
210
+ </div>
211
+ )
212
+ },
213
+ }
214
+
215
+ export const Compact: Story = {
216
+ render: () => (
217
+ <div className="space-y-2 w-[500px]">
218
+ <WakaSecretCard
219
+ secret={defaultSecret}
220
+ compact
221
+ onCopy={() => console.log('Copy')}
222
+ onView={() => console.log('View')}
223
+ onRotate={() => console.log('Rotate')}
224
+ />
225
+ <WakaSecretCard
226
+ secret={expiredSecret}
227
+ compact
228
+ onCopy={() => console.log('Copy')}
229
+ onRotate={() => console.log('Rotate')}
230
+ />
231
+ <WakaSecretCard
232
+ secret={expiringSoonSecret}
233
+ compact
234
+ onCopy={() => console.log('Copy')}
235
+ onRotate={() => console.log('Rotate')}
236
+ />
237
+ <WakaSecretCard
238
+ secret={needsRotationSecret}
239
+ compact
240
+ onCopy={() => console.log('Copy')}
241
+ onRotate={() => console.log('Rotate')}
242
+ />
243
+ </div>
244
+ ),
245
+ }
246
+
247
+ export const NoActions: Story = {
248
+ render: () => (
249
+ <div className="w-[500px]">
250
+ <p className="text-sm text-muted-foreground mb-4">
251
+ Read-only view without action buttons
252
+ </p>
253
+ <WakaSecretCard secret={tokenSecret} />
254
+ </div>
255
+ ),
256
+ }
257
+
258
+ export const MinimalSecret: Story = {
259
+ render: () => (
260
+ <div className="w-[500px]">
261
+ <p className="text-sm text-muted-foreground mb-4">
262
+ Secret with minimal information
263
+ </p>
264
+ <WakaSecretCard
265
+ secret={simpleSecret}
266
+ onRotate={() => console.log('Rotate')}
267
+ onDelete={() => console.log('Delete')}
268
+ />
269
+ </div>
270
+ ),
271
+ }
272
+
273
+ export const SecretVault: Story = {
274
+ render: () => {
275
+ const secrets: Secret[] = [
276
+ defaultSecret,
277
+ expiredSecret,
278
+ expiringSoonSecret,
279
+ needsRotationSecret,
280
+ tokenSecret,
281
+ ]
282
+
283
+ return (
284
+ <div className="p-6 rounded-xl border bg-card">
285
+ <div className="flex items-center justify-between mb-6">
286
+ <div>
287
+ <h2 className="text-xl font-bold">Secret Vault</h2>
288
+ <p className="text-sm text-muted-foreground">Production Environment</p>
289
+ </div>
290
+ <div className="flex items-center gap-2 text-sm">
291
+ <span className="text-red-500 font-medium">1 expired</span>
292
+ <span className="text-muted-foreground">•</span>
293
+ <span className="text-yellow-500 font-medium">1 expiring soon</span>
294
+ <span className="text-muted-foreground">•</span>
295
+ <span className="text-orange-500 font-medium">1 needs rotation</span>
296
+ </div>
297
+ </div>
298
+
299
+ <div className="space-y-2 w-[500px]">
300
+ {secrets.map((secret) => (
301
+ <WakaSecretCard
302
+ key={secret.id}
303
+ secret={secret}
304
+ compact
305
+ onCopy={() => console.log('Copy:', secret.name)}
306
+ onView={() => console.log('View:', secret.name)}
307
+ onRotate={() => console.log('Rotate:', secret.name)}
308
+ />
309
+ ))}
310
+ </div>
311
+ </div>
312
+ )
313
+ },
314
+ }