@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,410 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { WakaErrorShake, WakaFormFieldShake, useErrorShake } from './index'
3
+ import type { WakaErrorShakeRef } from './index'
4
+ import * as React from 'react'
5
+ import { Button } from '../button'
6
+ import { AlertCircle } from 'lucide-react'
7
+
8
+ const meta: Meta<typeof WakaErrorShake> = {
9
+ title: 'Components/Effects/WakaErrorShake',
10
+ component: WakaErrorShake,
11
+ parameters: {
12
+ layout: 'centered',
13
+ docs: {
14
+ description: {
15
+ component: 'An error shake animation component for visual feedback on errors or invalid input.',
16
+ },
17
+ },
18
+ },
19
+ tags: ['autodocs'],
20
+ argTypes: {
21
+ intensity: {
22
+ control: 'select',
23
+ options: ['light', 'medium', 'strong'],
24
+ description: 'Shake intensity',
25
+ },
26
+ duration: {
27
+ control: { type: 'range', min: 200, max: 1000, step: 50 },
28
+ description: 'Animation duration in ms',
29
+ },
30
+ glitchEffect: {
31
+ control: 'boolean',
32
+ description: 'Enable glitch/distortion effect',
33
+ },
34
+ flashEffect: {
35
+ control: 'boolean',
36
+ description: 'Enable red flash effect',
37
+ },
38
+ flashColor: {
39
+ control: 'color',
40
+ description: 'Flash color',
41
+ },
42
+ },
43
+ }
44
+
45
+ export default meta
46
+ type Story = StoryObj<typeof WakaErrorShake>
47
+
48
+ export const Default: Story = {
49
+ args: {},
50
+ render: (args) => {
51
+ const [trigger, setTrigger] = React.useState(false)
52
+
53
+ const handleShake = () => {
54
+ setTrigger(true)
55
+ setTimeout(() => setTrigger(false), 100)
56
+ }
57
+
58
+ return (
59
+ <div className="space-y-6">
60
+ <WakaErrorShake trigger={trigger}>
61
+ <div className="p-6 border rounded-lg bg-card w-[300px]">
62
+ <h3 className="font-semibold mb-2">Error Content</h3>
63
+ <p className="text-sm text-muted-foreground">
64
+ This content will shake when triggered.
65
+ </p>
66
+ </div>
67
+ </WakaErrorShake>
68
+ <Button variant="destructive" onClick={handleShake}>
69
+ Trigger Shake
70
+ </Button>
71
+ </div>
72
+ )
73
+ },
74
+ }
75
+
76
+ export const Intensities: Story = {
77
+ render: () => {
78
+ const [triggers, setTriggers] = React.useState({
79
+ light: false,
80
+ medium: false,
81
+ strong: false,
82
+ })
83
+
84
+ const handleTrigger = (intensity: keyof typeof triggers) => {
85
+ setTriggers((prev) => ({ ...prev, [intensity]: true }))
86
+ setTimeout(() => setTriggers((prev) => ({ ...prev, [intensity]: false })), 100)
87
+ }
88
+
89
+ return (
90
+ <div className="flex gap-6">
91
+ {(['light', 'medium', 'strong'] as const).map((intensity) => (
92
+ <div key={intensity} className="flex flex-col items-center gap-4">
93
+ <WakaErrorShake trigger={triggers[intensity]} intensity={intensity}>
94
+ <div className="p-4 border rounded-lg bg-card w-[150px] text-center">
95
+ <span className="capitalize">{intensity}</span>
96
+ </div>
97
+ </WakaErrorShake>
98
+ <Button size="sm" variant="outline" onClick={() => handleTrigger(intensity)}>
99
+ Shake
100
+ </Button>
101
+ </div>
102
+ ))}
103
+ </div>
104
+ )
105
+ },
106
+ }
107
+
108
+ export const WithGlitchEffect: Story = {
109
+ render: () => {
110
+ const [trigger, setTrigger] = React.useState(false)
111
+
112
+ return (
113
+ <div className="space-y-6">
114
+ <WakaErrorShake trigger={trigger} glitchEffect intensity="strong">
115
+ <div className="p-6 border rounded-lg bg-card w-[300px]">
116
+ <h3 className="font-semibold mb-2 text-destructive">Critical Error</h3>
117
+ <p className="text-sm text-muted-foreground">
118
+ This includes a glitch/distortion effect.
119
+ </p>
120
+ </div>
121
+ </WakaErrorShake>
122
+ <Button
123
+ variant="destructive"
124
+ onClick={() => {
125
+ setTrigger(true)
126
+ setTimeout(() => setTrigger(false), 100)
127
+ }}
128
+ >
129
+ Trigger Glitch
130
+ </Button>
131
+ </div>
132
+ )
133
+ },
134
+ }
135
+
136
+ export const NoFlashEffect: Story = {
137
+ render: () => {
138
+ const [trigger, setTrigger] = React.useState(false)
139
+
140
+ return (
141
+ <div className="space-y-6">
142
+ <WakaErrorShake trigger={trigger} flashEffect={false}>
143
+ <div className="p-6 border rounded-lg bg-card w-[300px]">
144
+ <h3 className="font-semibold mb-2">No Flash</h3>
145
+ <p className="text-sm text-muted-foreground">
146
+ Only shake animation, no red flash.
147
+ </p>
148
+ </div>
149
+ </WakaErrorShake>
150
+ <Button
151
+ variant="outline"
152
+ onClick={() => {
153
+ setTrigger(true)
154
+ setTimeout(() => setTrigger(false), 100)
155
+ }}
156
+ >
157
+ Shake Only
158
+ </Button>
159
+ </div>
160
+ )
161
+ },
162
+ }
163
+
164
+ export const CustomFlashColor: Story = {
165
+ render: () => {
166
+ const [trigger, setTrigger] = React.useState(false)
167
+
168
+ return (
169
+ <div className="space-y-6">
170
+ <WakaErrorShake trigger={trigger} flashColor="rgb(251, 146, 60)">
171
+ <div className="p-6 border rounded-lg bg-card w-[300px]">
172
+ <h3 className="font-semibold mb-2">Warning Flash</h3>
173
+ <p className="text-sm text-muted-foreground">
174
+ Orange flash color for warnings.
175
+ </p>
176
+ </div>
177
+ </WakaErrorShake>
178
+ <Button
179
+ className="bg-orange-500 hover:bg-orange-600"
180
+ onClick={() => {
181
+ setTrigger(true)
182
+ setTimeout(() => setTrigger(false), 100)
183
+ }}
184
+ >
185
+ Trigger Warning
186
+ </Button>
187
+ </div>
188
+ )
189
+ },
190
+ }
191
+
192
+ export const FormFieldShake: Story = {
193
+ render: () => {
194
+ const [error, setError] = React.useState(false)
195
+ const [value, setValue] = React.useState('')
196
+
197
+ const handleSubmit = (e: React.FormEvent) => {
198
+ e.preventDefault()
199
+ if (!value.includes('@')) {
200
+ setError(true)
201
+ } else {
202
+ setError(false)
203
+ alert('Submitted!')
204
+ }
205
+ }
206
+
207
+ return (
208
+ <form onSubmit={handleSubmit} className="w-[300px] space-y-4">
209
+ <div>
210
+ <label className="text-sm font-medium">Email</label>
211
+ <WakaFormFieldShake hasError={error}>
212
+ <input
213
+ type="text"
214
+ value={value}
215
+ onChange={(e) => {
216
+ setValue(e.target.value)
217
+ if (error) setError(false)
218
+ }}
219
+ className={`w-full mt-1 px-3 py-2 border rounded-md ${
220
+ error ? 'border-destructive' : ''
221
+ }`}
222
+ placeholder="Enter your email"
223
+ />
224
+ </WakaFormFieldShake>
225
+ {error && (
226
+ <p className="text-sm text-destructive mt-1 flex items-center gap-1">
227
+ <AlertCircle className="h-3 w-3" />
228
+ Please enter a valid email
229
+ </p>
230
+ )}
231
+ </div>
232
+ <Button type="submit" className="w-full">
233
+ Submit
234
+ </Button>
235
+ </form>
236
+ )
237
+ },
238
+ }
239
+
240
+ export const WithRef: Story = {
241
+ render: () => {
242
+ const ref = React.useRef<WakaErrorShakeRef>(null)
243
+
244
+ return (
245
+ <div className="space-y-6">
246
+ <WakaErrorShake ref={ref}>
247
+ <div className="p-6 border rounded-lg bg-card w-[300px]">
248
+ <h3 className="font-semibold mb-2">Controlled via Ref</h3>
249
+ <p className="text-sm text-muted-foreground">
250
+ Use the ref to trigger shake programmatically.
251
+ </p>
252
+ </div>
253
+ </WakaErrorShake>
254
+ <div className="flex gap-2">
255
+ <Button variant="destructive" onClick={() => ref.current?.shake()}>
256
+ Shake
257
+ </Button>
258
+ <Button variant="outline" onClick={() => ref.current?.reset()}>
259
+ Reset
260
+ </Button>
261
+ </div>
262
+ </div>
263
+ )
264
+ },
265
+ }
266
+
267
+ export const WithHook: Story = {
268
+ render: () => {
269
+ const { shake, ShakeWrapper } = useErrorShake({
270
+ intensity: 'medium',
271
+ onComplete: () => console.log('Shake complete!'),
272
+ })
273
+
274
+ return (
275
+ <div className="space-y-6">
276
+ <ShakeWrapper>
277
+ <div className="p-6 border rounded-lg bg-card w-[300px]">
278
+ <h3 className="font-semibold mb-2">Using Hook</h3>
279
+ <p className="text-sm text-muted-foreground">
280
+ Controlled via useErrorShake hook.
281
+ </p>
282
+ </div>
283
+ </ShakeWrapper>
284
+ <Button variant="destructive" onClick={shake}>
285
+ Trigger via Hook
286
+ </Button>
287
+ </div>
288
+ )
289
+ },
290
+ }
291
+
292
+ export const LoginFormExample: Story = {
293
+ render: () => {
294
+ const [username, setUsername] = React.useState('')
295
+ const [password, setPassword] = React.useState('')
296
+ const [errors, setErrors] = React.useState({ username: false, password: false })
297
+ const [loginError, setLoginError] = React.useState(false)
298
+
299
+ const handleSubmit = (e: React.FormEvent) => {
300
+ e.preventDefault()
301
+
302
+ const newErrors = {
303
+ username: !username,
304
+ password: !password,
305
+ }
306
+ setErrors(newErrors)
307
+
308
+ if (!newErrors.username && !newErrors.password) {
309
+ if (username !== 'admin' || password !== 'password') {
310
+ setLoginError(true)
311
+ setTimeout(() => setLoginError(false), 100)
312
+ } else {
313
+ alert('Login successful!')
314
+ }
315
+ }
316
+ }
317
+
318
+ return (
319
+ <WakaErrorShake trigger={loginError} intensity="medium">
320
+ <form onSubmit={handleSubmit} className="w-[320px] p-6 border rounded-lg bg-card">
321
+ <h2 className="text-xl font-bold mb-6 text-center">Login</h2>
322
+ <div className="space-y-4">
323
+ <div>
324
+ <label className="text-sm font-medium">Username</label>
325
+ <WakaFormFieldShake hasError={errors.username}>
326
+ <input
327
+ type="text"
328
+ value={username}
329
+ onChange={(e) => {
330
+ setUsername(e.target.value)
331
+ setErrors((p) => ({ ...p, username: false }))
332
+ }}
333
+ className={`w-full mt-1 px-3 py-2 border rounded-md ${
334
+ errors.username ? 'border-destructive' : ''
335
+ }`}
336
+ placeholder="Enter username"
337
+ />
338
+ </WakaFormFieldShake>
339
+ {errors.username && (
340
+ <p className="text-xs text-destructive mt-1">Username is required</p>
341
+ )}
342
+ </div>
343
+ <div>
344
+ <label className="text-sm font-medium">Password</label>
345
+ <WakaFormFieldShake hasError={errors.password}>
346
+ <input
347
+ type="password"
348
+ value={password}
349
+ onChange={(e) => {
350
+ setPassword(e.target.value)
351
+ setErrors((p) => ({ ...p, password: false }))
352
+ }}
353
+ className={`w-full mt-1 px-3 py-2 border rounded-md ${
354
+ errors.password ? 'border-destructive' : ''
355
+ }`}
356
+ placeholder="Enter password"
357
+ />
358
+ </WakaFormFieldShake>
359
+ {errors.password && (
360
+ <p className="text-xs text-destructive mt-1">Password is required</p>
361
+ )}
362
+ </div>
363
+ <Button type="submit" className="w-full">
364
+ Sign In
365
+ </Button>
366
+ </div>
367
+ <p className="text-xs text-muted-foreground mt-4 text-center">
368
+ Try: admin / password
369
+ </p>
370
+ </form>
371
+ </WakaErrorShake>
372
+ )
373
+ },
374
+ }
375
+
376
+ export const WithCallback: Story = {
377
+ render: () => {
378
+ const [trigger, setTrigger] = React.useState(false)
379
+ const [message, setMessage] = React.useState('')
380
+
381
+ return (
382
+ <div className="space-y-6">
383
+ <WakaErrorShake
384
+ trigger={trigger}
385
+ onComplete={() => setMessage('Shake animation completed!')}
386
+ >
387
+ <div className="p-6 border rounded-lg bg-card w-[300px]">
388
+ <h3 className="font-semibold mb-2">With Callback</h3>
389
+ <p className="text-sm text-muted-foreground">
390
+ Callback fires when animation ends.
391
+ </p>
392
+ </div>
393
+ </WakaErrorShake>
394
+ <Button
395
+ variant="destructive"
396
+ onClick={() => {
397
+ setMessage('')
398
+ setTrigger(true)
399
+ setTimeout(() => setTrigger(false), 100)
400
+ }}
401
+ >
402
+ Trigger
403
+ </Button>
404
+ {message && (
405
+ <p className="text-sm text-muted-foreground">{message}</p>
406
+ )}
407
+ </div>
408
+ )
409
+ },
410
+ }
@@ -0,0 +1,239 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { WakaFileUpload } from './index'
3
+ import * as React from 'react'
4
+
5
+ const meta: Meta<typeof WakaFileUpload> = {
6
+ title: 'Components/Forms/WakaFileUpload',
7
+ component: WakaFileUpload,
8
+ parameters: {
9
+ layout: 'centered',
10
+ docs: {
11
+ description: {
12
+ component: 'A file upload component with drag & drop, preview, and progress tracking.',
13
+ },
14
+ },
15
+ },
16
+ tags: ['autodocs'],
17
+ argTypes: {
18
+ variant: {
19
+ control: 'select',
20
+ options: ['dropzone', 'button', 'inline'],
21
+ description: 'Display variant',
22
+ },
23
+ multiple: {
24
+ control: 'boolean',
25
+ description: 'Allow multiple files',
26
+ },
27
+ showPreview: {
28
+ control: 'boolean',
29
+ description: 'Show file preview',
30
+ },
31
+ showProgress: {
32
+ control: 'boolean',
33
+ description: 'Show upload progress',
34
+ },
35
+ disabled: {
36
+ control: 'boolean',
37
+ description: 'Disable upload',
38
+ },
39
+ },
40
+ }
41
+
42
+ export default meta
43
+ type Story = StoryObj<typeof WakaFileUpload>
44
+
45
+ // Mock upload function
46
+ const mockUpload = (file: File, onProgress: (progress: number) => void): Promise<string> => {
47
+ return new Promise((resolve) => {
48
+ let progress = 0
49
+ const interval = setInterval(() => {
50
+ progress += 10
51
+ onProgress(progress)
52
+ if (progress >= 100) {
53
+ clearInterval(interval)
54
+ resolve(`https://example.com/uploads/${file.name}`)
55
+ }
56
+ }, 200)
57
+ })
58
+ }
59
+
60
+ export const Default: Story = {
61
+ args: {
62
+ label: 'Upload Files',
63
+ description: 'Drag and drop files here or click to browse',
64
+ },
65
+ render: (args) => (
66
+ <div className="w-[400px]">
67
+ <WakaFileUpload
68
+ {...args}
69
+ onUpload={mockUpload}
70
+ onComplete={(files) => console.log('Uploaded:', files)}
71
+ />
72
+ </div>
73
+ ),
74
+ }
75
+
76
+ export const Dropzone: Story = {
77
+ render: () => (
78
+ <div className="w-[400px]">
79
+ <WakaFileUpload
80
+ variant="dropzone"
81
+ label="Documents"
82
+ placeholder="Drop your documents here"
83
+ onUpload={mockUpload}
84
+ />
85
+ </div>
86
+ ),
87
+ }
88
+
89
+ export const ButtonVariant: Story = {
90
+ render: () => (
91
+ <WakaFileUpload
92
+ variant="button"
93
+ placeholder="Upload File"
94
+ onUpload={mockUpload}
95
+ />
96
+ ),
97
+ }
98
+
99
+ export const InlineVariant: Story = {
100
+ render: () => (
101
+ <div className="w-[300px]">
102
+ <WakaFileUpload
103
+ variant="inline"
104
+ placeholder="Add attachment"
105
+ onUpload={mockUpload}
106
+ />
107
+ </div>
108
+ ),
109
+ }
110
+
111
+ export const ImagesOnly: Story = {
112
+ render: () => (
113
+ <div className="w-[400px]">
114
+ <WakaFileUpload
115
+ label="Upload Images"
116
+ accept="image/*"
117
+ placeholder="Drop images here (PNG, JPG, GIF)"
118
+ onUpload={mockUpload}
119
+ />
120
+ </div>
121
+ ),
122
+ }
123
+
124
+ export const PDFOnly: Story = {
125
+ render: () => (
126
+ <div className="w-[400px]">
127
+ <WakaFileUpload
128
+ label="Upload PDF"
129
+ accept=".pdf"
130
+ placeholder="Drop PDF files here"
131
+ onUpload={mockUpload}
132
+ />
133
+ </div>
134
+ ),
135
+ }
136
+
137
+ export const MultipleFiles: Story = {
138
+ render: () => (
139
+ <div className="w-[400px]">
140
+ <WakaFileUpload
141
+ label="Upload Multiple Files"
142
+ multiple
143
+ maxFiles={5}
144
+ placeholder="Drop up to 5 files here"
145
+ onUpload={mockUpload}
146
+ />
147
+ </div>
148
+ ),
149
+ }
150
+
151
+ export const WithSizeLimit: Story = {
152
+ render: () => (
153
+ <div className="w-[400px]">
154
+ <WakaFileUpload
155
+ label="Upload (Max 2MB)"
156
+ maxSize={2 * 1024 * 1024}
157
+ onUpload={mockUpload}
158
+ onError={(error) => console.error('Error:', error)}
159
+ />
160
+ </div>
161
+ ),
162
+ }
163
+
164
+ export const NoPreview: Story = {
165
+ render: () => (
166
+ <div className="w-[400px]">
167
+ <WakaFileUpload
168
+ label="Upload"
169
+ showPreview={false}
170
+ onUpload={mockUpload}
171
+ />
172
+ </div>
173
+ ),
174
+ }
175
+
176
+ export const NoProgress: Story = {
177
+ render: () => (
178
+ <div className="w-[400px]">
179
+ <WakaFileUpload
180
+ label="Quick Upload"
181
+ showProgress={false}
182
+ onUpload={mockUpload}
183
+ />
184
+ </div>
185
+ ),
186
+ }
187
+
188
+ export const Disabled: Story = {
189
+ render: () => (
190
+ <div className="w-[400px]">
191
+ <WakaFileUpload
192
+ label="Upload (Disabled)"
193
+ disabled
194
+ />
195
+ </div>
196
+ ),
197
+ }
198
+
199
+ export const ProfilePicture: Story = {
200
+ render: () => (
201
+ <div className="w-[400px] space-y-4">
202
+ <h3 className="font-semibold">Profile Picture</h3>
203
+ <WakaFileUpload
204
+ accept="image/*"
205
+ maxSize={5 * 1024 * 1024}
206
+ maxFiles={1}
207
+ placeholder="Upload a profile picture (max 5MB)"
208
+ onUpload={mockUpload}
209
+ />
210
+ </div>
211
+ ),
212
+ }
213
+
214
+ export const DocumentUpload: Story = {
215
+ render: () => (
216
+ <div className="w-[400px] space-y-4 p-4 border rounded-lg">
217
+ <div>
218
+ <h3 className="font-semibold">Document Submission</h3>
219
+ <p className="text-sm text-muted-foreground">
220
+ Upload your required documents
221
+ </p>
222
+ </div>
223
+ <WakaFileUpload
224
+ label="ID Document"
225
+ accept=".pdf,.jpg,.png"
226
+ maxSize={10 * 1024 * 1024}
227
+ placeholder="Upload ID (PDF, JPG, PNG)"
228
+ onUpload={mockUpload}
229
+ />
230
+ <WakaFileUpload
231
+ label="Proof of Address"
232
+ accept=".pdf,.jpg,.png"
233
+ maxSize={10 * 1024 * 1024}
234
+ placeholder="Upload proof of address"
235
+ onUpload={mockUpload}
236
+ />
237
+ </div>
238
+ ),
239
+ }