@wakastellar/ui 2.0.0 → 2.1.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 (290) hide show
  1. package/dist/cli/commands/add.d.ts +7 -0
  2. package/dist/cli/commands/init.d.ts +6 -0
  3. package/dist/cli/commands/list.d.ts +5 -0
  4. package/dist/cli/commands/search.d.ts +1 -0
  5. package/dist/cli/index.cjs +4844 -0
  6. package/dist/cli/index.d.ts +1 -0
  7. package/dist/cli/utils/config.d.ts +29 -0
  8. package/dist/cli/utils/logger.d.ts +20 -0
  9. package/dist/cli/utils/registry.d.ts +23 -0
  10. package/package.json +14 -3
  11. package/src/blocks/activity-timeline/index.tsx +586 -0
  12. package/src/blocks/calendar-view/index.tsx +756 -0
  13. package/src/blocks/chat/index.tsx +1018 -0
  14. package/src/blocks/chat/widget.tsx +504 -0
  15. package/src/blocks/dashboard/index.tsx +522 -0
  16. package/src/blocks/empty-states/index.tsx +452 -0
  17. package/src/blocks/error-pages/index.tsx +426 -0
  18. package/src/blocks/faq/index.tsx +479 -0
  19. package/src/blocks/file-manager/index.tsx +890 -0
  20. package/src/blocks/footer/index.tsx +133 -0
  21. package/src/blocks/header/index.tsx +357 -0
  22. package/src/blocks/headtab/index.tsx +139 -0
  23. package/src/blocks/i18n-editor/index.tsx +1016 -0
  24. package/src/blocks/index.ts +80 -0
  25. package/src/blocks/kanban-board/index.tsx +779 -0
  26. package/src/blocks/landing/index.tsx +677 -0
  27. package/src/blocks/language-selector/index.tsx +88 -0
  28. package/src/blocks/layout/index.tsx +159 -0
  29. package/src/blocks/login/index.tsx +339 -0
  30. package/src/blocks/login/types.ts +131 -0
  31. package/src/blocks/pricing/index.tsx +564 -0
  32. package/src/blocks/profile/index.tsx +746 -0
  33. package/src/blocks/settings/index.tsx +558 -0
  34. package/src/blocks/sidebar/index.tsx +713 -0
  35. package/src/blocks/theme-creator-block/index.tsx +835 -0
  36. package/src/blocks/user-management/index.tsx +1037 -0
  37. package/src/blocks/wizard/index.tsx +719 -0
  38. package/src/components/DataTable/DataTable.tsx +406 -0
  39. package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
  40. package/src/components/DataTable/DataTableBody.tsx +216 -0
  41. package/src/components/DataTable/DataTableCell.tsx +172 -0
  42. package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
  43. package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
  44. package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
  45. package/src/components/DataTable/DataTableEditCell.tsx +279 -0
  46. package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
  47. package/src/components/DataTable/DataTableFilters.tsx +535 -0
  48. package/src/components/DataTable/DataTableGrouping.tsx +147 -0
  49. package/src/components/DataTable/DataTableHeader.tsx +172 -0
  50. package/src/components/DataTable/DataTablePagination.tsx +125 -0
  51. package/src/components/DataTable/DataTableSelection.tsx +269 -0
  52. package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
  53. package/src/components/DataTable/DataTableToolbar.tsx +262 -0
  54. package/src/components/DataTable/README.md +446 -0
  55. package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
  56. package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
  57. package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
  58. package/src/components/DataTable/examples/EditExample.tsx +166 -0
  59. package/src/components/DataTable/formatters/index.ts +335 -0
  60. package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
  61. package/src/components/DataTable/hooks/useDataTable.ts +145 -0
  62. package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
  63. package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
  64. package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
  65. package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
  66. package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
  67. package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
  68. package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
  69. package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
  70. package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
  71. package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
  72. package/src/components/DataTable/index.ts +81 -0
  73. package/src/components/DataTable/services/IndexedDBService.ts +504 -0
  74. package/src/components/DataTable/templates/index.tsx +803 -0
  75. package/src/components/DataTable/types.ts +504 -0
  76. package/src/components/DataTable/utils.ts +164 -0
  77. package/src/components/DataTable/workers/exportWorker.ts +213 -0
  78. package/src/components/accordion/index.tsx +61 -0
  79. package/src/components/alert/index.tsx +61 -0
  80. package/src/components/alert-dialog/index.tsx +146 -0
  81. package/src/components/aspect-ratio/index.tsx +12 -0
  82. package/src/components/avatar/index.tsx +54 -0
  83. package/src/components/badge/Badge.stories.tsx +64 -0
  84. package/src/components/badge/index.tsx +38 -0
  85. package/src/components/button/Button.stories.tsx +173 -0
  86. package/src/components/button/index.tsx +56 -0
  87. package/src/components/calendar/index.tsx +73 -0
  88. package/src/components/card/index.tsx +78 -0
  89. package/src/components/checkbox/index.tsx +34 -0
  90. package/src/components/code/index.tsx +229 -0
  91. package/src/components/collapsible/index.tsx +16 -0
  92. package/src/components/command/index.tsx +162 -0
  93. package/src/components/context-menu/index.tsx +204 -0
  94. package/src/components/dialog/index.tsx +126 -0
  95. package/src/components/dropdown-menu/index.tsx +204 -0
  96. package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
  97. package/src/components/error-boundary/index.ts +7 -0
  98. package/src/components/form/index.tsx +183 -0
  99. package/src/components/hover-card/index.tsx +33 -0
  100. package/src/components/index.ts +368 -0
  101. package/src/components/input/Input.stories.tsx +100 -0
  102. package/src/components/input/index.tsx +27 -0
  103. package/src/components/input-otp/index.tsx +277 -0
  104. package/src/components/label/index.tsx +30 -0
  105. package/src/components/language-selector/index.tsx +341 -0
  106. package/src/components/menubar/index.tsx +240 -0
  107. package/src/components/navigation-menu/index.tsx +134 -0
  108. package/src/components/popover/index.tsx +35 -0
  109. package/src/components/progress/index.tsx +32 -0
  110. package/src/components/radio-group/index.tsx +48 -0
  111. package/src/components/scroll-area/index.tsx +52 -0
  112. package/src/components/select/index.tsx +164 -0
  113. package/src/components/separator/index.tsx +35 -0
  114. package/src/components/sheet/index.tsx +147 -0
  115. package/src/components/skeleton/index.tsx +22 -0
  116. package/src/components/slider/index.tsx +32 -0
  117. package/src/components/switch/index.tsx +33 -0
  118. package/src/components/table/index.tsx +117 -0
  119. package/src/components/tabs/index.tsx +59 -0
  120. package/src/components/textarea/index.tsx +30 -0
  121. package/src/components/theme-selector/index.tsx +327 -0
  122. package/src/components/toast/index.tsx +133 -0
  123. package/src/components/toaster/index.tsx +34 -0
  124. package/src/components/toggle/index.tsx +49 -0
  125. package/src/components/tooltip/index.tsx +34 -0
  126. package/src/components/typography/index.tsx +276 -0
  127. package/src/components/waka-3d-pie-chart/index.tsx +486 -0
  128. package/src/components/waka-achievement-unlock/index.tsx +716 -0
  129. package/src/components/waka-activity-feed/index.tsx +686 -0
  130. package/src/components/waka-address-autocomplete/index.tsx +1202 -0
  131. package/src/components/waka-admincrumb/index.tsx +349 -0
  132. package/src/components/waka-alert-stack/index.tsx +827 -0
  133. package/src/components/waka-allocation-matrix/index.tsx +1278 -0
  134. package/src/components/waka-approval-chain/index.tsx +766 -0
  135. package/src/components/waka-audit-log/index.tsx +1475 -0
  136. package/src/components/waka-autocomplete/index.tsx +358 -0
  137. package/src/components/waka-badge-showcase/index.tsx +704 -0
  138. package/src/components/waka-barcode/index.tsx +260 -0
  139. package/src/components/waka-biometric-prompt/index.tsx +765 -0
  140. package/src/components/waka-bottom-sheet/index.tsx +495 -0
  141. package/src/components/waka-breadcrumb/index.tsx +376 -0
  142. package/src/components/waka-breadcrumb-path/index.tsx +513 -0
  143. package/src/components/waka-budget-burn/index.tsx +1234 -0
  144. package/src/components/waka-capacity-planner/index.tsx +1107 -0
  145. package/src/components/waka-carousel/index.tsx +893 -0
  146. package/src/components/waka-cart-summary/index.tsx +1055 -0
  147. package/src/components/waka-challenge-timer/index.tsx +1044 -0
  148. package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
  149. package/src/components/waka-charts/WakaBarChart.tsx +222 -0
  150. package/src/components/waka-charts/WakaChart.tsx +124 -0
  151. package/src/components/waka-charts/WakaLineChart.tsx +219 -0
  152. package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
  153. package/src/components/waka-charts/WakaPieChart.tsx +214 -0
  154. package/src/components/waka-charts/WakaSparkline.tsx +229 -0
  155. package/src/components/waka-charts/dataTableHelpers.ts +109 -0
  156. package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
  157. package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
  158. package/src/components/waka-charts/index.ts +90 -0
  159. package/src/components/waka-charts/types.ts +330 -0
  160. package/src/components/waka-chat-bubble/index.tsx +1060 -0
  161. package/src/components/waka-checklist/index.tsx +1067 -0
  162. package/src/components/waka-checkout-stepper/index.tsx +976 -0
  163. package/src/components/waka-cohort-table/index.tsx +1011 -0
  164. package/src/components/waka-color-picker/index.tsx +447 -0
  165. package/src/components/waka-combo-counter/index.tsx +864 -0
  166. package/src/components/waka-combobox/index.tsx +497 -0
  167. package/src/components/waka-command-bar/index.tsx +403 -0
  168. package/src/components/waka-compare-period/index.tsx +1230 -0
  169. package/src/components/waka-connection-matrix/index.tsx +1053 -0
  170. package/src/components/waka-contribution-graph/index.tsx +552 -0
  171. package/src/components/waka-cost-breakdown/index.tsx +1065 -0
  172. package/src/components/waka-coupon-input/index.tsx +592 -0
  173. package/src/components/waka-credit-card-input/index.tsx +982 -0
  174. package/src/components/waka-daily-reward/index.tsx +762 -0
  175. package/src/components/waka-date-range-picker/index.tsx +378 -0
  176. package/src/components/waka-datetime-picker/index.tsx +793 -0
  177. package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
  178. package/src/components/waka-deployment-lane/index.tsx +673 -0
  179. package/src/components/waka-device-trust/index.tsx +1259 -0
  180. package/src/components/waka-dock/index.tsx +285 -0
  181. package/src/components/waka-drawer/index.tsx +319 -0
  182. package/src/components/waka-empty-state/index.tsx +545 -0
  183. package/src/components/waka-error-shake/index.tsx +398 -0
  184. package/src/components/waka-feature-announcement/index.tsx +991 -0
  185. package/src/components/waka-file-upload/index.tsx +437 -0
  186. package/src/components/waka-floating-nav/index.tsx +413 -0
  187. package/src/components/waka-flow-diagram/index.tsx +508 -0
  188. package/src/components/waka-funnel-chart/index.tsx +823 -0
  189. package/src/components/waka-glow-card/index.tsx +246 -0
  190. package/src/components/waka-goal-progress/index.tsx +1025 -0
  191. package/src/components/waka-haptic-button/index.tsx +388 -0
  192. package/src/components/waka-health-pulse/index.tsx +451 -0
  193. package/src/components/waka-heatmap/index.tsx +1026 -0
  194. package/src/components/waka-hotspot/index.tsx +682 -0
  195. package/src/components/waka-image/index.tsx +373 -0
  196. package/src/components/waka-incident-timeline/index.tsx +686 -0
  197. package/src/components/waka-invoice-preview/index.tsx +829 -0
  198. package/src/components/waka-kanban/index.tsx +646 -0
  199. package/src/components/waka-kpi-dashboard/index.tsx +755 -0
  200. package/src/components/waka-leaderboard/index.tsx +746 -0
  201. package/src/components/waka-level-progress/index.tsx +665 -0
  202. package/src/components/waka-liquid-button/index.tsx +520 -0
  203. package/src/components/waka-loading-orbit/index.tsx +478 -0
  204. package/src/components/waka-loot-box/index.tsx +1091 -0
  205. package/src/components/waka-magic-link/index.tsx +321 -0
  206. package/src/components/waka-magnetic-button/index.tsx +567 -0
  207. package/src/components/waka-mention-input/index.tsx +953 -0
  208. package/src/components/waka-metric-sparkline/index.tsx +627 -0
  209. package/src/components/waka-milestone-road/index.tsx +1064 -0
  210. package/src/components/waka-modal/index.tsx +374 -0
  211. package/src/components/waka-morph-button/index.tsx +495 -0
  212. package/src/components/waka-network-topology/index.tsx +801 -0
  213. package/src/components/waka-notifications/index.tsx +414 -0
  214. package/src/components/waka-number-input/index.tsx +373 -0
  215. package/src/components/waka-orbital-menu/index.tsx +445 -0
  216. package/src/components/waka-order-tracker/index.tsx +1041 -0
  217. package/src/components/waka-pagination/index.tsx +393 -0
  218. package/src/components/waka-password-strength/index.tsx +824 -0
  219. package/src/components/waka-payment-method-picker/index.tsx +715 -0
  220. package/src/components/waka-permission-matrix/index.tsx +1302 -0
  221. package/src/components/waka-phone-input/index.tsx +801 -0
  222. package/src/components/waka-pipeline-view/index.tsx +604 -0
  223. package/src/components/waka-player-card/index.tsx +691 -0
  224. package/src/components/waka-points-popup/index.tsx +366 -0
  225. package/src/components/waka-power-up/index.tsx +1155 -0
  226. package/src/components/waka-presence-indicator/index.tsx +1181 -0
  227. package/src/components/waka-pricing-table/index.tsx +755 -0
  228. package/src/components/waka-product-card/index.tsx +786 -0
  229. package/src/components/waka-progress-onboarding/index.tsx +878 -0
  230. package/src/components/waka-pull-to-refresh/index.tsx +451 -0
  231. package/src/components/waka-qrcode/index.tsx +232 -0
  232. package/src/components/waka-quest-card/index.tsx +1275 -0
  233. package/src/components/waka-quota-bar/index.tsx +693 -0
  234. package/src/components/waka-radar-score/index.tsx +512 -0
  235. package/src/components/waka-rank-badge/index.tsx +813 -0
  236. package/src/components/waka-rating-input/index.tsx +560 -0
  237. package/src/components/waka-reaction-picker/index.tsx +1062 -0
  238. package/src/components/waka-region-map/index.tsx +730 -0
  239. package/src/components/waka-resource-gauge/index.tsx +654 -0
  240. package/src/components/waka-resource-pool/index.tsx +1035 -0
  241. package/src/components/waka-rich-text-editor/index.tsx +594 -0
  242. package/src/components/waka-rollback-slider/index.tsx +891 -0
  243. package/src/components/waka-sankey-diagram/index.tsx +1032 -0
  244. package/src/components/waka-schedule-picker/index.tsx +1060 -0
  245. package/src/components/waka-scratch-card/index.tsx +914 -0
  246. package/src/components/waka-season-pass/index.tsx +886 -0
  247. package/src/components/waka-security-score/index.tsx +1126 -0
  248. package/src/components/waka-segmented-control/index.tsx +238 -0
  249. package/src/components/waka-server-rack/index.tsx +764 -0
  250. package/src/components/waka-session-manager/index.tsx +815 -0
  251. package/src/components/waka-signature-pad/index.tsx +744 -0
  252. package/src/components/waka-skeleton-wave/index.tsx +454 -0
  253. package/src/components/waka-skill-tree/index.tsx +1031 -0
  254. package/src/components/waka-sla-tracker/index.tsx +798 -0
  255. package/src/components/waka-slider-range/index.tsx +765 -0
  256. package/src/components/waka-spin-wheel/index.tsx +671 -0
  257. package/src/components/waka-spinner/index.tsx +284 -0
  258. package/src/components/waka-spotlight/index.tsx +410 -0
  259. package/src/components/waka-stat/index.tsx +428 -0
  260. package/src/components/waka-stats-hexagon/index.tsx +824 -0
  261. package/src/components/waka-status-matrix/index.tsx +565 -0
  262. package/src/components/waka-stepper/index.tsx +489 -0
  263. package/src/components/waka-streak-counter/index.tsx +334 -0
  264. package/src/components/waka-success-explosion/index.tsx +453 -0
  265. package/src/components/waka-swipe-card/index.tsx +574 -0
  266. package/src/components/waka-tabs-morph/index.tsx +509 -0
  267. package/src/components/waka-tag-input/index.tsx +877 -0
  268. package/src/components/waka-team-banner/index.tsx +1183 -0
  269. package/src/components/waka-terminal-output/index.tsx +836 -0
  270. package/src/components/waka-theme-creator/index.tsx +762 -0
  271. package/src/components/waka-theme-manager/index.tsx +654 -0
  272. package/src/components/waka-thread-view/index.tsx +874 -0
  273. package/src/components/waka-tilt-card/index.tsx +250 -0
  274. package/src/components/waka-time-picker/index.tsx +479 -0
  275. package/src/components/waka-timeline/index.tsx +385 -0
  276. package/src/components/waka-tooltip-tour/index.tsx +855 -0
  277. package/src/components/waka-tour-guide/index.tsx +920 -0
  278. package/src/components/waka-tournament-bracket/index.tsx +1276 -0
  279. package/src/components/waka-tree/index.tsx +557 -0
  280. package/src/components/waka-treemap-chart/index.tsx +1031 -0
  281. package/src/components/waka-two-factor-setup/index.tsx +995 -0
  282. package/src/components/waka-typewriter/index.tsx +566 -0
  283. package/src/components/waka-typing-indicator/index.tsx +649 -0
  284. package/src/components/waka-versus-card/index.tsx +1026 -0
  285. package/src/components/waka-video/index.tsx +557 -0
  286. package/src/components/waka-video-call/index.tsx +1087 -0
  287. package/src/components/waka-virtual-list/index.tsx +327 -0
  288. package/src/components/waka-voice-message/index.tsx +1019 -0
  289. package/src/components/waka-welcome-modal/index.tsx +790 -0
  290. package/src/components/waka-xp-bar/index.tsx +799 -0
@@ -0,0 +1,719 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils"
5
+ import { Button } from "../../components/button"
6
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../../components/card"
7
+ import { Progress } from "../../components/progress"
8
+ import {
9
+ AlertDialog,
10
+ AlertDialogAction,
11
+ AlertDialogCancel,
12
+ AlertDialogContent,
13
+ AlertDialogDescription,
14
+ AlertDialogFooter,
15
+ AlertDialogHeader,
16
+ AlertDialogTitle,
17
+ } from "../../components/alert-dialog"
18
+ import {
19
+ ArrowLeft,
20
+ ArrowRight,
21
+ Check,
22
+ X,
23
+ Loader2,
24
+ AlertCircle,
25
+ Circle,
26
+ } from "lucide-react"
27
+
28
+ // ============================================
29
+ // TYPES
30
+ // ============================================
31
+
32
+ export type WizardStepStatus = "pending" | "current" | "completed" | "error" | "skipped"
33
+
34
+ export interface WizardStep {
35
+ id: string
36
+ title: string
37
+ description?: string
38
+ icon?: React.ReactNode
39
+ content: React.ReactNode
40
+ /** Étape optionnelle (peut être sautée) */
41
+ optional?: boolean
42
+ /** Validation de l'étape */
43
+ validate?: () => boolean | Promise<boolean>
44
+ /** Callback avant de quitter l'étape */
45
+ onLeave?: () => void | Promise<void>
46
+ /** Callback en entrant dans l'étape */
47
+ onEnter?: () => void | Promise<void>
48
+ }
49
+
50
+ export interface WizardSubmitData {
51
+ steps: { id: string; status: WizardStepStatus }[]
52
+ completedSteps: string[]
53
+ skippedSteps: string[]
54
+ }
55
+
56
+ export interface WakaWizardProps {
57
+ /** Étapes du wizard */
58
+ steps: WizardStep[]
59
+ /** Étape courante (contrôlé) */
60
+ currentStep?: number
61
+ /** Callback changement d'étape */
62
+ onStepChange?: (step: number) => void
63
+ /** Callback de soumission */
64
+ onSubmit?: (data: WizardSubmitData) => void | Promise<void>
65
+ /** Callback d'annulation */
66
+ onCancel?: () => void
67
+ /** Titre du wizard */
68
+ title?: string
69
+ /** Description */
70
+ description?: string
71
+ /** Texte du bouton précédent */
72
+ prevButtonText?: string
73
+ /** Texte du bouton suivant */
74
+ nextButtonText?: string
75
+ /** Texte du bouton terminer */
76
+ finishButtonText?: string
77
+ /** Texte du bouton annuler */
78
+ cancelButtonText?: string
79
+ /** Texte du bouton passer */
80
+ skipButtonText?: string
81
+ /** Afficher le bouton annuler */
82
+ showCancelButton?: boolean
83
+ /** Afficher la progression */
84
+ showProgress?: boolean
85
+ /** Afficher les numéros d'étapes */
86
+ showStepNumbers?: boolean
87
+ /** Afficher le stepper */
88
+ showStepper?: boolean
89
+ /** Position du stepper */
90
+ stepperPosition?: "top" | "left"
91
+ /** Permettre la navigation vers les étapes précédentes */
92
+ allowBackNavigation?: boolean
93
+ /** Confirmer l'annulation */
94
+ confirmCancel?: boolean
95
+ /** Message de confirmation d'annulation */
96
+ cancelConfirmMessage?: string
97
+ /** En cours de soumission */
98
+ submitting?: boolean
99
+ /** Layout */
100
+ layout?: "card" | "page" | "minimal"
101
+ /** Classes CSS additionnelles */
102
+ className?: string
103
+ }
104
+
105
+ // ============================================
106
+ // CONTEXT
107
+ // ============================================
108
+
109
+ interface WizardContextValue {
110
+ currentStep: number
111
+ totalSteps: number
112
+ stepStatuses: WizardStepStatus[]
113
+ goToStep: (step: number) => void
114
+ nextStep: () => void
115
+ prevStep: () => void
116
+ skipStep: () => void
117
+ isFirstStep: boolean
118
+ isLastStep: boolean
119
+ }
120
+
121
+ const WizardContext = React.createContext<WizardContextValue | null>(null)
122
+
123
+ export function useWizard() {
124
+ const context = React.useContext(WizardContext)
125
+ if (!context) {
126
+ throw new Error("useWizard must be used within a WakaWizard")
127
+ }
128
+ return context
129
+ }
130
+
131
+ // ============================================
132
+ // SUB-COMPONENTS
133
+ // ============================================
134
+
135
+ interface WizardStepperProps {
136
+ steps: WizardStep[]
137
+ currentStep: number
138
+ stepStatuses: WizardStepStatus[]
139
+ showStepNumbers: boolean
140
+ orientation: "horizontal" | "vertical"
141
+ onStepClick?: (step: number) => void
142
+ allowBackNavigation: boolean
143
+ }
144
+
145
+ function WizardStepper({
146
+ steps,
147
+ currentStep,
148
+ stepStatuses,
149
+ showStepNumbers,
150
+ orientation,
151
+ onStepClick,
152
+ allowBackNavigation,
153
+ }: WizardStepperProps) {
154
+ const getStepIcon = (index: number, status: WizardStepStatus) => {
155
+ if (status === "completed") {
156
+ return <Check className="h-4 w-4" />
157
+ }
158
+ if (status === "error") {
159
+ return <AlertCircle className="h-4 w-4" />
160
+ }
161
+ if (status === "skipped") {
162
+ return <X className="h-4 w-4 text-muted-foreground" />
163
+ }
164
+ if (showStepNumbers) {
165
+ return <span className="text-xs font-medium">{index + 1}</span>
166
+ }
167
+ return <Circle className="h-3 w-3" />
168
+ }
169
+
170
+ const getStepClasses = (status: WizardStepStatus) => {
171
+ switch (status) {
172
+ case "completed":
173
+ return "bg-primary text-primary-foreground border-primary"
174
+ case "current":
175
+ return "bg-primary text-primary-foreground border-primary"
176
+ case "error":
177
+ return "bg-destructive text-destructive-foreground border-destructive"
178
+ case "skipped":
179
+ return "bg-muted text-muted-foreground border-muted"
180
+ default:
181
+ return "bg-background text-muted-foreground border-border"
182
+ }
183
+ }
184
+
185
+ if (orientation === "vertical") {
186
+ return (
187
+ <div className="flex flex-col gap-0">
188
+ {steps.map((step, index) => {
189
+ const status = stepStatuses[index]
190
+ const isClickable = allowBackNavigation && index < currentStep
191
+
192
+ return (
193
+ <div key={step.id} className="flex gap-3">
194
+ {/* Indicator column */}
195
+ <div className="flex flex-col items-center">
196
+ <button
197
+ type="button"
198
+ disabled={!isClickable}
199
+ onClick={() => isClickable && onStepClick?.(index)}
200
+ className={cn(
201
+ "flex h-8 w-8 items-center justify-center rounded-full border-2 transition-colors",
202
+ getStepClasses(status),
203
+ isClickable && "cursor-pointer hover:opacity-80"
204
+ )}
205
+ >
206
+ {step.icon || getStepIcon(index, status)}
207
+ </button>
208
+ {index < steps.length - 1 && (
209
+ <div
210
+ className={cn(
211
+ "w-0.5 flex-1 min-h-[24px]",
212
+ stepStatuses[index] === "completed" ? "bg-primary" : "bg-border"
213
+ )}
214
+ />
215
+ )}
216
+ </div>
217
+
218
+ {/* Content column */}
219
+ <div className="pb-8">
220
+ <div
221
+ className={cn(
222
+ "font-medium",
223
+ status === "current" ? "text-foreground" : "text-muted-foreground"
224
+ )}
225
+ >
226
+ {step.title}
227
+ {step.optional && (
228
+ <span className="ml-2 text-xs text-muted-foreground">(optionnel)</span>
229
+ )}
230
+ </div>
231
+ {step.description && (
232
+ <div className="text-sm text-muted-foreground mt-0.5">
233
+ {step.description}
234
+ </div>
235
+ )}
236
+ </div>
237
+ </div>
238
+ )
239
+ })}
240
+ </div>
241
+ )
242
+ }
243
+
244
+ return (
245
+ <div className="flex items-center gap-0">
246
+ {steps.map((step, index) => {
247
+ const status = stepStatuses[index]
248
+ const isClickable = allowBackNavigation && index < currentStep
249
+
250
+ return (
251
+ <React.Fragment key={step.id}>
252
+ <div className="flex flex-col items-center gap-2">
253
+ <button
254
+ type="button"
255
+ disabled={!isClickable}
256
+ onClick={() => isClickable && onStepClick?.(index)}
257
+ className={cn(
258
+ "flex h-8 w-8 items-center justify-center rounded-full border-2 transition-colors",
259
+ getStepClasses(status),
260
+ isClickable && "cursor-pointer hover:opacity-80"
261
+ )}
262
+ >
263
+ {step.icon || getStepIcon(index, status)}
264
+ </button>
265
+ <div className="text-center">
266
+ <div
267
+ className={cn(
268
+ "text-sm font-medium",
269
+ status === "current" ? "text-foreground" : "text-muted-foreground"
270
+ )}
271
+ >
272
+ {step.title}
273
+ </div>
274
+ </div>
275
+ </div>
276
+ {index < steps.length - 1 && (
277
+ <div
278
+ className={cn(
279
+ "flex-1 h-0.5 mx-2 mb-6",
280
+ stepStatuses[index] === "completed" ? "bg-primary" : "bg-border"
281
+ )}
282
+ />
283
+ )}
284
+ </React.Fragment>
285
+ )
286
+ })}
287
+ </div>
288
+ )
289
+ }
290
+
291
+ interface WizardFooterProps {
292
+ onPrev: () => void
293
+ onNext: () => void
294
+ onSkip?: () => void
295
+ onCancel?: () => void
296
+ isFirstStep: boolean
297
+ isLastStep: boolean
298
+ isOptional: boolean
299
+ prevButtonText: string
300
+ nextButtonText: string
301
+ finishButtonText: string
302
+ skipButtonText: string
303
+ cancelButtonText: string
304
+ showCancelButton: boolean
305
+ submitting: boolean
306
+ }
307
+
308
+ function WizardFooter({
309
+ onPrev,
310
+ onNext,
311
+ onSkip,
312
+ onCancel,
313
+ isFirstStep,
314
+ isLastStep,
315
+ isOptional,
316
+ prevButtonText,
317
+ nextButtonText,
318
+ finishButtonText,
319
+ skipButtonText,
320
+ cancelButtonText,
321
+ showCancelButton,
322
+ submitting,
323
+ }: WizardFooterProps) {
324
+ return (
325
+ <div className="flex items-center justify-between">
326
+ <div className="flex gap-2">
327
+ {showCancelButton && onCancel && (
328
+ <Button type="button" variant="ghost" onClick={onCancel} disabled={submitting}>
329
+ {cancelButtonText}
330
+ </Button>
331
+ )}
332
+ </div>
333
+ <div className="flex gap-2">
334
+ {!isFirstStep && (
335
+ <Button type="button" variant="outline" onClick={onPrev} disabled={submitting}>
336
+ <ArrowLeft className="mr-2 h-4 w-4" />
337
+ {prevButtonText}
338
+ </Button>
339
+ )}
340
+ {isOptional && onSkip && !isLastStep && (
341
+ <Button type="button" variant="ghost" onClick={onSkip} disabled={submitting}>
342
+ {skipButtonText}
343
+ </Button>
344
+ )}
345
+ <Button type="button" onClick={onNext} disabled={submitting}>
346
+ {submitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
347
+ {isLastStep ? finishButtonText : nextButtonText}
348
+ {!isLastStep && !submitting && <ArrowRight className="ml-2 h-4 w-4" />}
349
+ </Button>
350
+ </div>
351
+ </div>
352
+ )
353
+ }
354
+
355
+ // ============================================
356
+ // MAIN COMPONENT
357
+ // ============================================
358
+
359
+ export function WakaWizard({
360
+ steps,
361
+ currentStep: externalCurrentStep,
362
+ onStepChange,
363
+ onSubmit,
364
+ onCancel,
365
+ title,
366
+ description,
367
+ prevButtonText = "Précédent",
368
+ nextButtonText = "Suivant",
369
+ finishButtonText = "Terminer",
370
+ cancelButtonText = "Annuler",
371
+ skipButtonText = "Passer",
372
+ showCancelButton = true,
373
+ showProgress = true,
374
+ showStepNumbers = true,
375
+ showStepper = true,
376
+ stepperPosition = "top",
377
+ allowBackNavigation = true,
378
+ confirmCancel = true,
379
+ cancelConfirmMessage = "Êtes-vous sûr de vouloir annuler ? Toutes les modifications seront perdues.",
380
+ submitting = false,
381
+ layout = "card",
382
+ className,
383
+ }: WakaWizardProps) {
384
+ const [internalCurrentStep, setInternalCurrentStep] = React.useState(0)
385
+ const [stepStatuses, setStepStatuses] = React.useState<WizardStepStatus[]>(
386
+ steps.map((_, index) => (index === 0 ? "current" : "pending"))
387
+ )
388
+ const [showCancelDialog, setShowCancelDialog] = React.useState(false)
389
+
390
+ const currentStep = externalCurrentStep ?? internalCurrentStep
391
+ const isFirstStep = currentStep === 0
392
+ const isLastStep = currentStep === steps.length - 1
393
+ const currentStepData = steps[currentStep]
394
+ const progress = ((currentStep + 1) / steps.length) * 100
395
+
396
+ const updateStepStatuses = (newStep: number, action: "next" | "prev" | "skip" | "goto") => {
397
+ setStepStatuses((prev) => {
398
+ const newStatuses = [...prev]
399
+
400
+ if (action === "next" && currentStep < steps.length) {
401
+ newStatuses[currentStep] = "completed"
402
+ } else if (action === "skip") {
403
+ newStatuses[currentStep] = "skipped"
404
+ } else if (action === "goto" && newStep < currentStep) {
405
+ // Reset statuses for steps after the target
406
+ for (let i = newStep + 1; i < steps.length; i++) {
407
+ newStatuses[i] = "pending"
408
+ }
409
+ }
410
+
411
+ newStatuses[newStep] = "current"
412
+ return newStatuses
413
+ })
414
+ }
415
+
416
+ const goToStep = async (step: number) => {
417
+ if (step < 0 || step >= steps.length) return
418
+ if (step > currentStep && !allowBackNavigation) return
419
+
420
+ if (currentStepData.onLeave) {
421
+ await currentStepData.onLeave()
422
+ }
423
+
424
+ updateStepStatuses(step, "goto")
425
+
426
+ if (onStepChange) {
427
+ onStepChange(step)
428
+ } else {
429
+ setInternalCurrentStep(step)
430
+ }
431
+
432
+ const targetStep = steps[step]
433
+ if (targetStep.onEnter) {
434
+ await targetStep.onEnter()
435
+ }
436
+ }
437
+
438
+ const nextStep = async () => {
439
+ // Validate current step
440
+ if (currentStepData.validate) {
441
+ const isValid = await currentStepData.validate()
442
+ if (!isValid) {
443
+ setStepStatuses((prev) => {
444
+ const newStatuses = [...prev]
445
+ newStatuses[currentStep] = "error"
446
+ return newStatuses
447
+ })
448
+ return
449
+ }
450
+ }
451
+
452
+ if (currentStepData.onLeave) {
453
+ await currentStepData.onLeave()
454
+ }
455
+
456
+ if (isLastStep) {
457
+ // Submit
458
+ const submitData: WizardSubmitData = {
459
+ steps: steps.map((step, index) => ({
460
+ id: step.id,
461
+ status: index === currentStep ? "completed" : stepStatuses[index],
462
+ })),
463
+ completedSteps: steps
464
+ .filter((_, index) => stepStatuses[index] === "completed" || index === currentStep)
465
+ .map((step) => step.id),
466
+ skippedSteps: steps
467
+ .filter((_, index) => stepStatuses[index] === "skipped")
468
+ .map((step) => step.id),
469
+ }
470
+ await onSubmit?.(submitData)
471
+ } else {
472
+ const nextStepIndex = currentStep + 1
473
+ updateStepStatuses(nextStepIndex, "next")
474
+
475
+ if (onStepChange) {
476
+ onStepChange(nextStepIndex)
477
+ } else {
478
+ setInternalCurrentStep(nextStepIndex)
479
+ }
480
+
481
+ const nextStepData = steps[nextStepIndex]
482
+ if (nextStepData.onEnter) {
483
+ await nextStepData.onEnter()
484
+ }
485
+ }
486
+ }
487
+
488
+ const prevStep = async () => {
489
+ if (isFirstStep) return
490
+
491
+ if (currentStepData.onLeave) {
492
+ await currentStepData.onLeave()
493
+ }
494
+
495
+ const prevStepIndex = currentStep - 1
496
+ updateStepStatuses(prevStepIndex, "prev")
497
+
498
+ if (onStepChange) {
499
+ onStepChange(prevStepIndex)
500
+ } else {
501
+ setInternalCurrentStep(prevStepIndex)
502
+ }
503
+
504
+ const prevStepData = steps[prevStepIndex]
505
+ if (prevStepData.onEnter) {
506
+ await prevStepData.onEnter()
507
+ }
508
+ }
509
+
510
+ const skipStep = async () => {
511
+ if (!currentStepData.optional || isLastStep) return
512
+
513
+ if (currentStepData.onLeave) {
514
+ await currentStepData.onLeave()
515
+ }
516
+
517
+ const nextStepIndex = currentStep + 1
518
+ updateStepStatuses(nextStepIndex, "skip")
519
+
520
+ if (onStepChange) {
521
+ onStepChange(nextStepIndex)
522
+ } else {
523
+ setInternalCurrentStep(nextStepIndex)
524
+ }
525
+
526
+ const nextStepData = steps[nextStepIndex]
527
+ if (nextStepData.onEnter) {
528
+ await nextStepData.onEnter()
529
+ }
530
+ }
531
+
532
+ const handleCancel = () => {
533
+ if (confirmCancel) {
534
+ setShowCancelDialog(true)
535
+ } else {
536
+ onCancel?.()
537
+ }
538
+ }
539
+
540
+ const contextValue: WizardContextValue = {
541
+ currentStep,
542
+ totalSteps: steps.length,
543
+ stepStatuses,
544
+ goToStep,
545
+ nextStep,
546
+ prevStep,
547
+ skipStep,
548
+ isFirstStep,
549
+ isLastStep,
550
+ }
551
+
552
+ const stepperComponent = showStepper && (
553
+ <WizardStepper
554
+ steps={steps}
555
+ currentStep={currentStep}
556
+ stepStatuses={stepStatuses}
557
+ showStepNumbers={showStepNumbers}
558
+ orientation={stepperPosition === "left" ? "vertical" : "horizontal"}
559
+ onStepClick={goToStep}
560
+ allowBackNavigation={allowBackNavigation}
561
+ />
562
+ )
563
+
564
+ const footerComponent = (
565
+ <WizardFooter
566
+ onPrev={prevStep}
567
+ onNext={nextStep}
568
+ onSkip={currentStepData.optional ? skipStep : undefined}
569
+ onCancel={onCancel ? handleCancel : undefined}
570
+ isFirstStep={isFirstStep}
571
+ isLastStep={isLastStep}
572
+ isOptional={currentStepData.optional || false}
573
+ prevButtonText={prevButtonText}
574
+ nextButtonText={nextButtonText}
575
+ finishButtonText={finishButtonText}
576
+ skipButtonText={skipButtonText}
577
+ cancelButtonText={cancelButtonText}
578
+ showCancelButton={showCancelButton}
579
+ submitting={submitting}
580
+ />
581
+ )
582
+
583
+ const content = (
584
+ <WizardContext.Provider value={contextValue}>
585
+ {stepperPosition === "left" ? (
586
+ <div className="flex gap-8">
587
+ <div className="w-64 flex-shrink-0">{stepperComponent}</div>
588
+ <div className="flex-1">
589
+ {showProgress && (
590
+ <div className="mb-6">
591
+ <div className="flex justify-between text-sm text-muted-foreground mb-2">
592
+ <span>Étape {currentStep + 1} sur {steps.length}</span>
593
+ <span>{Math.round(progress)}%</span>
594
+ </div>
595
+ <Progress value={progress} />
596
+ </div>
597
+ )}
598
+ <div className="min-h-[300px]">{currentStepData.content}</div>
599
+ <div className="mt-8">{footerComponent}</div>
600
+ </div>
601
+ </div>
602
+ ) : (
603
+ <>
604
+ {stepperComponent && <div className="mb-8">{stepperComponent}</div>}
605
+ {showProgress && (
606
+ <div className="mb-6">
607
+ <div className="flex justify-between text-sm text-muted-foreground mb-2">
608
+ <span>Étape {currentStep + 1} sur {steps.length}</span>
609
+ <span>{Math.round(progress)}%</span>
610
+ </div>
611
+ <Progress value={progress} />
612
+ </div>
613
+ )}
614
+ <div className="min-h-[300px]">{currentStepData.content}</div>
615
+ <div className="mt-8">{footerComponent}</div>
616
+ </>
617
+ )}
618
+
619
+ {/* Cancel confirmation dialog */}
620
+ <AlertDialog open={showCancelDialog} onOpenChange={setShowCancelDialog}>
621
+ <AlertDialogContent>
622
+ <AlertDialogHeader>
623
+ <AlertDialogTitle>Annuler ?</AlertDialogTitle>
624
+ <AlertDialogDescription>{cancelConfirmMessage}</AlertDialogDescription>
625
+ </AlertDialogHeader>
626
+ <AlertDialogFooter>
627
+ <AlertDialogCancel>Non, continuer</AlertDialogCancel>
628
+ <AlertDialogAction
629
+ onClick={() => {
630
+ setShowCancelDialog(false)
631
+ onCancel?.()
632
+ }}
633
+ >
634
+ Oui, annuler
635
+ </AlertDialogAction>
636
+ </AlertDialogFooter>
637
+ </AlertDialogContent>
638
+ </AlertDialog>
639
+ </WizardContext.Provider>
640
+ )
641
+
642
+ if (layout === "minimal") {
643
+ return <div className={cn("space-y-6", className)}>{content}</div>
644
+ }
645
+
646
+ if (layout === "page") {
647
+ return (
648
+ <div className={cn("container py-8 max-w-4xl", className)}>
649
+ {(title || description) && (
650
+ <div className="mb-8">
651
+ {title && <h1 className="text-2xl font-bold tracking-tight">{title}</h1>}
652
+ {description && (
653
+ <p className="text-muted-foreground mt-1">{description}</p>
654
+ )}
655
+ </div>
656
+ )}
657
+ {content}
658
+ </div>
659
+ )
660
+ }
661
+
662
+ return (
663
+ <Card className={className}>
664
+ {(title || description) && (
665
+ <CardHeader>
666
+ {title && <CardTitle>{title}</CardTitle>}
667
+ {description && <CardDescription>{description}</CardDescription>}
668
+ </CardHeader>
669
+ )}
670
+ <CardContent>{content}</CardContent>
671
+ </Card>
672
+ )
673
+ }
674
+
675
+ // ============================================
676
+ // PRESETS
677
+ // ============================================
678
+
679
+ export const defaultWizardSteps: WizardStep[] = [
680
+ {
681
+ id: "step-1",
682
+ title: "Informations de base",
683
+ description: "Renseignez vos informations personnelles",
684
+ content: (
685
+ <div className="space-y-4">
686
+ <p className="text-muted-foreground">
687
+ Contenu de l'étape 1 - Informations de base
688
+ </p>
689
+ </div>
690
+ ),
691
+ },
692
+ {
693
+ id: "step-2",
694
+ title: "Configuration",
695
+ description: "Configurez vos préférences",
696
+ optional: true,
697
+ content: (
698
+ <div className="space-y-4">
699
+ <p className="text-muted-foreground">
700
+ Contenu de l'étape 2 - Configuration (optionnel)
701
+ </p>
702
+ </div>
703
+ ),
704
+ },
705
+ {
706
+ id: "step-3",
707
+ title: "Confirmation",
708
+ description: "Vérifiez et confirmez",
709
+ content: (
710
+ <div className="space-y-4">
711
+ <p className="text-muted-foreground">
712
+ Contenu de l'étape 3 - Confirmation
713
+ </p>
714
+ </div>
715
+ ),
716
+ },
717
+ ]
718
+
719
+ export default WakaWizard