@wakastellar/ui 2.0.0 → 2.1.1

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 (291) hide show
  1. package/README.md +71 -8
  2. package/dist/cli/commands/add.d.ts +7 -0
  3. package/dist/cli/commands/init.d.ts +6 -0
  4. package/dist/cli/commands/list.d.ts +5 -0
  5. package/dist/cli/commands/search.d.ts +1 -0
  6. package/dist/cli/index.cjs +6014 -0
  7. package/dist/cli/index.d.ts +1 -0
  8. package/dist/cli/utils/config.d.ts +29 -0
  9. package/dist/cli/utils/logger.d.ts +20 -0
  10. package/dist/cli/utils/registry.d.ts +23 -0
  11. package/package.json +14 -3
  12. package/src/blocks/activity-timeline/index.tsx +586 -0
  13. package/src/blocks/calendar-view/index.tsx +756 -0
  14. package/src/blocks/chat/index.tsx +1018 -0
  15. package/src/blocks/chat/widget.tsx +504 -0
  16. package/src/blocks/dashboard/index.tsx +522 -0
  17. package/src/blocks/empty-states/index.tsx +452 -0
  18. package/src/blocks/error-pages/index.tsx +426 -0
  19. package/src/blocks/faq/index.tsx +479 -0
  20. package/src/blocks/file-manager/index.tsx +890 -0
  21. package/src/blocks/footer/index.tsx +133 -0
  22. package/src/blocks/header/index.tsx +357 -0
  23. package/src/blocks/headtab/index.tsx +139 -0
  24. package/src/blocks/i18n-editor/index.tsx +1016 -0
  25. package/src/blocks/index.ts +80 -0
  26. package/src/blocks/kanban-board/index.tsx +779 -0
  27. package/src/blocks/landing/index.tsx +677 -0
  28. package/src/blocks/language-selector/index.tsx +88 -0
  29. package/src/blocks/layout/index.tsx +159 -0
  30. package/src/blocks/login/index.tsx +339 -0
  31. package/src/blocks/login/types.ts +131 -0
  32. package/src/blocks/pricing/index.tsx +564 -0
  33. package/src/blocks/profile/index.tsx +746 -0
  34. package/src/blocks/settings/index.tsx +558 -0
  35. package/src/blocks/sidebar/index.tsx +713 -0
  36. package/src/blocks/theme-creator-block/index.tsx +835 -0
  37. package/src/blocks/user-management/index.tsx +1037 -0
  38. package/src/blocks/wizard/index.tsx +719 -0
  39. package/src/components/DataTable/DataTable.tsx +406 -0
  40. package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
  41. package/src/components/DataTable/DataTableBody.tsx +216 -0
  42. package/src/components/DataTable/DataTableCell.tsx +172 -0
  43. package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
  44. package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
  45. package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
  46. package/src/components/DataTable/DataTableEditCell.tsx +279 -0
  47. package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
  48. package/src/components/DataTable/DataTableFilters.tsx +535 -0
  49. package/src/components/DataTable/DataTableGrouping.tsx +147 -0
  50. package/src/components/DataTable/DataTableHeader.tsx +172 -0
  51. package/src/components/DataTable/DataTablePagination.tsx +125 -0
  52. package/src/components/DataTable/DataTableSelection.tsx +269 -0
  53. package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
  54. package/src/components/DataTable/DataTableToolbar.tsx +262 -0
  55. package/src/components/DataTable/README.md +446 -0
  56. package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
  57. package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
  58. package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
  59. package/src/components/DataTable/examples/EditExample.tsx +166 -0
  60. package/src/components/DataTable/formatters/index.ts +335 -0
  61. package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
  62. package/src/components/DataTable/hooks/useDataTable.ts +145 -0
  63. package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
  64. package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
  65. package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
  66. package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
  67. package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
  68. package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
  69. package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
  70. package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
  71. package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
  72. package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
  73. package/src/components/DataTable/index.ts +81 -0
  74. package/src/components/DataTable/services/IndexedDBService.ts +504 -0
  75. package/src/components/DataTable/templates/index.tsx +803 -0
  76. package/src/components/DataTable/types.ts +504 -0
  77. package/src/components/DataTable/utils.ts +164 -0
  78. package/src/components/DataTable/workers/exportWorker.ts +213 -0
  79. package/src/components/accordion/index.tsx +61 -0
  80. package/src/components/alert/index.tsx +61 -0
  81. package/src/components/alert-dialog/index.tsx +146 -0
  82. package/src/components/aspect-ratio/index.tsx +12 -0
  83. package/src/components/avatar/index.tsx +54 -0
  84. package/src/components/badge/Badge.stories.tsx +64 -0
  85. package/src/components/badge/index.tsx +38 -0
  86. package/src/components/button/Button.stories.tsx +173 -0
  87. package/src/components/button/index.tsx +56 -0
  88. package/src/components/calendar/index.tsx +73 -0
  89. package/src/components/card/index.tsx +78 -0
  90. package/src/components/checkbox/index.tsx +34 -0
  91. package/src/components/code/index.tsx +229 -0
  92. package/src/components/collapsible/index.tsx +16 -0
  93. package/src/components/command/index.tsx +162 -0
  94. package/src/components/context-menu/index.tsx +204 -0
  95. package/src/components/dialog/index.tsx +126 -0
  96. package/src/components/dropdown-menu/index.tsx +204 -0
  97. package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
  98. package/src/components/error-boundary/index.ts +7 -0
  99. package/src/components/form/index.tsx +183 -0
  100. package/src/components/hover-card/index.tsx +33 -0
  101. package/src/components/index.ts +368 -0
  102. package/src/components/input/Input.stories.tsx +100 -0
  103. package/src/components/input/index.tsx +27 -0
  104. package/src/components/input-otp/index.tsx +277 -0
  105. package/src/components/label/index.tsx +30 -0
  106. package/src/components/language-selector/index.tsx +341 -0
  107. package/src/components/menubar/index.tsx +240 -0
  108. package/src/components/navigation-menu/index.tsx +134 -0
  109. package/src/components/popover/index.tsx +35 -0
  110. package/src/components/progress/index.tsx +32 -0
  111. package/src/components/radio-group/index.tsx +48 -0
  112. package/src/components/scroll-area/index.tsx +52 -0
  113. package/src/components/select/index.tsx +164 -0
  114. package/src/components/separator/index.tsx +35 -0
  115. package/src/components/sheet/index.tsx +147 -0
  116. package/src/components/skeleton/index.tsx +22 -0
  117. package/src/components/slider/index.tsx +32 -0
  118. package/src/components/switch/index.tsx +33 -0
  119. package/src/components/table/index.tsx +117 -0
  120. package/src/components/tabs/index.tsx +59 -0
  121. package/src/components/textarea/index.tsx +30 -0
  122. package/src/components/theme-selector/index.tsx +327 -0
  123. package/src/components/toast/index.tsx +133 -0
  124. package/src/components/toaster/index.tsx +34 -0
  125. package/src/components/toggle/index.tsx +49 -0
  126. package/src/components/tooltip/index.tsx +34 -0
  127. package/src/components/typography/index.tsx +276 -0
  128. package/src/components/waka-3d-pie-chart/index.tsx +486 -0
  129. package/src/components/waka-achievement-unlock/index.tsx +716 -0
  130. package/src/components/waka-activity-feed/index.tsx +686 -0
  131. package/src/components/waka-address-autocomplete/index.tsx +1202 -0
  132. package/src/components/waka-admincrumb/index.tsx +349 -0
  133. package/src/components/waka-alert-stack/index.tsx +827 -0
  134. package/src/components/waka-allocation-matrix/index.tsx +1278 -0
  135. package/src/components/waka-approval-chain/index.tsx +766 -0
  136. package/src/components/waka-audit-log/index.tsx +1475 -0
  137. package/src/components/waka-autocomplete/index.tsx +358 -0
  138. package/src/components/waka-badge-showcase/index.tsx +704 -0
  139. package/src/components/waka-barcode/index.tsx +260 -0
  140. package/src/components/waka-biometric-prompt/index.tsx +765 -0
  141. package/src/components/waka-bottom-sheet/index.tsx +495 -0
  142. package/src/components/waka-breadcrumb/index.tsx +376 -0
  143. package/src/components/waka-breadcrumb-path/index.tsx +513 -0
  144. package/src/components/waka-budget-burn/index.tsx +1234 -0
  145. package/src/components/waka-capacity-planner/index.tsx +1107 -0
  146. package/src/components/waka-carousel/index.tsx +893 -0
  147. package/src/components/waka-cart-summary/index.tsx +1055 -0
  148. package/src/components/waka-challenge-timer/index.tsx +1044 -0
  149. package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
  150. package/src/components/waka-charts/WakaBarChart.tsx +222 -0
  151. package/src/components/waka-charts/WakaChart.tsx +124 -0
  152. package/src/components/waka-charts/WakaLineChart.tsx +219 -0
  153. package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
  154. package/src/components/waka-charts/WakaPieChart.tsx +214 -0
  155. package/src/components/waka-charts/WakaSparkline.tsx +229 -0
  156. package/src/components/waka-charts/dataTableHelpers.ts +109 -0
  157. package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
  158. package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
  159. package/src/components/waka-charts/index.ts +90 -0
  160. package/src/components/waka-charts/types.ts +330 -0
  161. package/src/components/waka-chat-bubble/index.tsx +1060 -0
  162. package/src/components/waka-checklist/index.tsx +1067 -0
  163. package/src/components/waka-checkout-stepper/index.tsx +976 -0
  164. package/src/components/waka-cohort-table/index.tsx +1011 -0
  165. package/src/components/waka-color-picker/index.tsx +447 -0
  166. package/src/components/waka-combo-counter/index.tsx +864 -0
  167. package/src/components/waka-combobox/index.tsx +497 -0
  168. package/src/components/waka-command-bar/index.tsx +403 -0
  169. package/src/components/waka-compare-period/index.tsx +1230 -0
  170. package/src/components/waka-connection-matrix/index.tsx +1053 -0
  171. package/src/components/waka-contribution-graph/index.tsx +552 -0
  172. package/src/components/waka-cost-breakdown/index.tsx +1065 -0
  173. package/src/components/waka-coupon-input/index.tsx +592 -0
  174. package/src/components/waka-credit-card-input/index.tsx +982 -0
  175. package/src/components/waka-daily-reward/index.tsx +762 -0
  176. package/src/components/waka-date-range-picker/index.tsx +378 -0
  177. package/src/components/waka-datetime-picker/index.tsx +793 -0
  178. package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
  179. package/src/components/waka-deployment-lane/index.tsx +673 -0
  180. package/src/components/waka-device-trust/index.tsx +1259 -0
  181. package/src/components/waka-dock/index.tsx +285 -0
  182. package/src/components/waka-drawer/index.tsx +319 -0
  183. package/src/components/waka-empty-state/index.tsx +545 -0
  184. package/src/components/waka-error-shake/index.tsx +398 -0
  185. package/src/components/waka-feature-announcement/index.tsx +991 -0
  186. package/src/components/waka-file-upload/index.tsx +437 -0
  187. package/src/components/waka-floating-nav/index.tsx +413 -0
  188. package/src/components/waka-flow-diagram/index.tsx +508 -0
  189. package/src/components/waka-funnel-chart/index.tsx +823 -0
  190. package/src/components/waka-glow-card/index.tsx +246 -0
  191. package/src/components/waka-goal-progress/index.tsx +1025 -0
  192. package/src/components/waka-haptic-button/index.tsx +388 -0
  193. package/src/components/waka-health-pulse/index.tsx +451 -0
  194. package/src/components/waka-heatmap/index.tsx +1026 -0
  195. package/src/components/waka-hotspot/index.tsx +682 -0
  196. package/src/components/waka-image/index.tsx +373 -0
  197. package/src/components/waka-incident-timeline/index.tsx +686 -0
  198. package/src/components/waka-invoice-preview/index.tsx +829 -0
  199. package/src/components/waka-kanban/index.tsx +646 -0
  200. package/src/components/waka-kpi-dashboard/index.tsx +755 -0
  201. package/src/components/waka-leaderboard/index.tsx +746 -0
  202. package/src/components/waka-level-progress/index.tsx +665 -0
  203. package/src/components/waka-liquid-button/index.tsx +520 -0
  204. package/src/components/waka-loading-orbit/index.tsx +478 -0
  205. package/src/components/waka-loot-box/index.tsx +1091 -0
  206. package/src/components/waka-magic-link/index.tsx +321 -0
  207. package/src/components/waka-magnetic-button/index.tsx +567 -0
  208. package/src/components/waka-mention-input/index.tsx +953 -0
  209. package/src/components/waka-metric-sparkline/index.tsx +627 -0
  210. package/src/components/waka-milestone-road/index.tsx +1064 -0
  211. package/src/components/waka-modal/index.tsx +374 -0
  212. package/src/components/waka-morph-button/index.tsx +495 -0
  213. package/src/components/waka-network-topology/index.tsx +801 -0
  214. package/src/components/waka-notifications/index.tsx +414 -0
  215. package/src/components/waka-number-input/index.tsx +373 -0
  216. package/src/components/waka-orbital-menu/index.tsx +445 -0
  217. package/src/components/waka-order-tracker/index.tsx +1041 -0
  218. package/src/components/waka-pagination/index.tsx +393 -0
  219. package/src/components/waka-password-strength/index.tsx +824 -0
  220. package/src/components/waka-payment-method-picker/index.tsx +715 -0
  221. package/src/components/waka-permission-matrix/index.tsx +1302 -0
  222. package/src/components/waka-phone-input/index.tsx +801 -0
  223. package/src/components/waka-pipeline-view/index.tsx +604 -0
  224. package/src/components/waka-player-card/index.tsx +691 -0
  225. package/src/components/waka-points-popup/index.tsx +366 -0
  226. package/src/components/waka-power-up/index.tsx +1155 -0
  227. package/src/components/waka-presence-indicator/index.tsx +1181 -0
  228. package/src/components/waka-pricing-table/index.tsx +755 -0
  229. package/src/components/waka-product-card/index.tsx +786 -0
  230. package/src/components/waka-progress-onboarding/index.tsx +878 -0
  231. package/src/components/waka-pull-to-refresh/index.tsx +451 -0
  232. package/src/components/waka-qrcode/index.tsx +232 -0
  233. package/src/components/waka-quest-card/index.tsx +1275 -0
  234. package/src/components/waka-quota-bar/index.tsx +693 -0
  235. package/src/components/waka-radar-score/index.tsx +512 -0
  236. package/src/components/waka-rank-badge/index.tsx +813 -0
  237. package/src/components/waka-rating-input/index.tsx +560 -0
  238. package/src/components/waka-reaction-picker/index.tsx +1062 -0
  239. package/src/components/waka-region-map/index.tsx +730 -0
  240. package/src/components/waka-resource-gauge/index.tsx +654 -0
  241. package/src/components/waka-resource-pool/index.tsx +1035 -0
  242. package/src/components/waka-rich-text-editor/index.tsx +594 -0
  243. package/src/components/waka-rollback-slider/index.tsx +891 -0
  244. package/src/components/waka-sankey-diagram/index.tsx +1032 -0
  245. package/src/components/waka-schedule-picker/index.tsx +1060 -0
  246. package/src/components/waka-scratch-card/index.tsx +914 -0
  247. package/src/components/waka-season-pass/index.tsx +886 -0
  248. package/src/components/waka-security-score/index.tsx +1126 -0
  249. package/src/components/waka-segmented-control/index.tsx +238 -0
  250. package/src/components/waka-server-rack/index.tsx +764 -0
  251. package/src/components/waka-session-manager/index.tsx +815 -0
  252. package/src/components/waka-signature-pad/index.tsx +744 -0
  253. package/src/components/waka-skeleton-wave/index.tsx +454 -0
  254. package/src/components/waka-skill-tree/index.tsx +1031 -0
  255. package/src/components/waka-sla-tracker/index.tsx +798 -0
  256. package/src/components/waka-slider-range/index.tsx +765 -0
  257. package/src/components/waka-spin-wheel/index.tsx +671 -0
  258. package/src/components/waka-spinner/index.tsx +284 -0
  259. package/src/components/waka-spotlight/index.tsx +410 -0
  260. package/src/components/waka-stat/index.tsx +428 -0
  261. package/src/components/waka-stats-hexagon/index.tsx +824 -0
  262. package/src/components/waka-status-matrix/index.tsx +565 -0
  263. package/src/components/waka-stepper/index.tsx +489 -0
  264. package/src/components/waka-streak-counter/index.tsx +334 -0
  265. package/src/components/waka-success-explosion/index.tsx +453 -0
  266. package/src/components/waka-swipe-card/index.tsx +574 -0
  267. package/src/components/waka-tabs-morph/index.tsx +509 -0
  268. package/src/components/waka-tag-input/index.tsx +877 -0
  269. package/src/components/waka-team-banner/index.tsx +1183 -0
  270. package/src/components/waka-terminal-output/index.tsx +836 -0
  271. package/src/components/waka-theme-creator/index.tsx +762 -0
  272. package/src/components/waka-theme-manager/index.tsx +654 -0
  273. package/src/components/waka-thread-view/index.tsx +874 -0
  274. package/src/components/waka-tilt-card/index.tsx +250 -0
  275. package/src/components/waka-time-picker/index.tsx +479 -0
  276. package/src/components/waka-timeline/index.tsx +385 -0
  277. package/src/components/waka-tooltip-tour/index.tsx +855 -0
  278. package/src/components/waka-tour-guide/index.tsx +920 -0
  279. package/src/components/waka-tournament-bracket/index.tsx +1276 -0
  280. package/src/components/waka-tree/index.tsx +557 -0
  281. package/src/components/waka-treemap-chart/index.tsx +1031 -0
  282. package/src/components/waka-two-factor-setup/index.tsx +995 -0
  283. package/src/components/waka-typewriter/index.tsx +566 -0
  284. package/src/components/waka-typing-indicator/index.tsx +649 -0
  285. package/src/components/waka-versus-card/index.tsx +1026 -0
  286. package/src/components/waka-video/index.tsx +557 -0
  287. package/src/components/waka-video-call/index.tsx +1087 -0
  288. package/src/components/waka-virtual-list/index.tsx +327 -0
  289. package/src/components/waka-voice-message/index.tsx +1019 -0
  290. package/src/components/waka-welcome-modal/index.tsx +790 -0
  291. package/src/components/waka-xp-bar/index.tsx +799 -0
@@ -0,0 +1,976 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import {
6
+ Check,
7
+ ShoppingCart,
8
+ Truck,
9
+ CreditCard,
10
+ ClipboardCheck,
11
+ PartyPopper,
12
+ ChevronLeft,
13
+ ChevronRight,
14
+ SkipForward,
15
+ AlertCircle,
16
+ Loader2,
17
+ LucideIcon,
18
+ } from "lucide-react"
19
+
20
+ // ============================================
21
+ // TYPES
22
+ // ============================================
23
+
24
+ export type CheckoutStepId = "cart" | "shipping" | "payment" | "review" | "confirmation"
25
+
26
+ export type CheckoutStepStatus = "pending" | "current" | "completed" | "error" | "skipped"
27
+
28
+ export interface CheckoutStepValidation {
29
+ /** Whether the step is valid */
30
+ isValid: boolean
31
+ /** Error message if invalid */
32
+ errorMessage?: string
33
+ /** Field-level errors */
34
+ fieldErrors?: Record<string, string>
35
+ }
36
+
37
+ export interface CheckoutStep {
38
+ /** Unique step identifier */
39
+ id: CheckoutStepId | string
40
+ /** Step title */
41
+ title: string
42
+ /** Step description */
43
+ description?: string
44
+ /** Custom icon (optional) */
45
+ icon?: LucideIcon
46
+ /** Step content component */
47
+ content?: React.ReactNode
48
+ /** Whether step is optional (can be skipped) */
49
+ optional?: boolean
50
+ /** Whether step is disabled */
51
+ disabled?: boolean
52
+ /** Validation function for the step */
53
+ validate?: () => CheckoutStepValidation | Promise<CheckoutStepValidation>
54
+ /** Summary data for the sidebar */
55
+ summary?: React.ReactNode
56
+ }
57
+
58
+ export interface CheckoutStepperProps {
59
+ /** List of checkout steps */
60
+ steps: CheckoutStep[]
61
+ /** Current step index (0-indexed) */
62
+ currentStep?: number
63
+ /** Callback when step changes */
64
+ onStepChange?: (stepIndex: number, stepId: string) => void
65
+ /** Callback when checkout is completed */
66
+ onComplete?: () => void
67
+ /** Layout orientation */
68
+ orientation?: "horizontal" | "vertical"
69
+ /** Size variant */
70
+ size?: "sm" | "md" | "lg"
71
+ /** Allow clicking on steps to navigate */
72
+ clickable?: boolean
73
+ /** Only allow navigation to previous steps */
74
+ allowPreviousOnly?: boolean
75
+ /** Show step content */
76
+ showContent?: boolean
77
+ /** Show summary sidebar */
78
+ showSummary?: boolean
79
+ /** Summary sidebar position */
80
+ summaryPosition?: "left" | "right"
81
+ /** Enable animated transitions */
82
+ animated?: boolean
83
+ /** Animation direction */
84
+ animationDirection?: "slide" | "fade" | "scale"
85
+ /** Loading state */
86
+ isLoading?: boolean
87
+ /** Custom class name */
88
+ className?: string
89
+ /** Navigation labels */
90
+ labels?: {
91
+ back?: string
92
+ next?: string
93
+ skip?: string
94
+ complete?: string
95
+ processing?: string
96
+ }
97
+ }
98
+
99
+ export interface UseCheckoutStepperOptions {
100
+ /** Initial step index */
101
+ initialStep?: number
102
+ /** List of steps */
103
+ steps: CheckoutStep[]
104
+ /** Callback when step changes */
105
+ onStepChange?: (stepIndex: number, stepId: string) => void
106
+ /** Callback when checkout completes */
107
+ onComplete?: () => void
108
+ /** Validate before proceeding */
109
+ validateOnNext?: boolean
110
+ }
111
+
112
+ export interface UseCheckoutStepperReturn {
113
+ /** Current step index */
114
+ currentStep: number
115
+ /** Current step data */
116
+ currentStepData: CheckoutStep | undefined
117
+ /** All steps with status */
118
+ stepsWithStatus: (CheckoutStep & { status: CheckoutStepStatus })[]
119
+ /** Total number of steps */
120
+ totalSteps: number
121
+ /** Whether on first step */
122
+ isFirstStep: boolean
123
+ /** Whether on last step */
124
+ isLastStep: boolean
125
+ /** Whether checkout is complete */
126
+ isComplete: boolean
127
+ /** Whether currently validating */
128
+ isValidating: boolean
129
+ /** Current validation error */
130
+ validationError: string | null
131
+ /** Go to specific step */
132
+ goToStep: (index: number) => Promise<boolean>
133
+ /** Go to next step */
134
+ nextStep: () => Promise<boolean>
135
+ /** Go to previous step */
136
+ prevStep: () => void
137
+ /** Skip current step (if optional) */
138
+ skipStep: () => void
139
+ /** Complete checkout */
140
+ completeCheckout: () => Promise<boolean>
141
+ /** Reset stepper */
142
+ reset: () => void
143
+ /** Get step status */
144
+ getStepStatus: (index: number) => CheckoutStepStatus
145
+ /** Skipped steps */
146
+ skippedSteps: Set<number>
147
+ /** Mark step as completed manually */
148
+ markStepCompleted: (index: number) => void
149
+ }
150
+
151
+ export interface CheckoutStepperContextValue extends UseCheckoutStepperReturn {
152
+ orientation: "horizontal" | "vertical"
153
+ size: "sm" | "md" | "lg"
154
+ clickable: boolean
155
+ allowPreviousOnly: boolean
156
+ animated: boolean
157
+ animationDirection: "slide" | "fade" | "scale"
158
+ isLoading: boolean
159
+ }
160
+
161
+ // ============================================
162
+ // DEFAULT STEPS
163
+ // ============================================
164
+
165
+ const defaultStepIcons: Record<CheckoutStepId, LucideIcon> = {
166
+ cart: ShoppingCart,
167
+ shipping: Truck,
168
+ payment: CreditCard,
169
+ review: ClipboardCheck,
170
+ confirmation: PartyPopper,
171
+ }
172
+
173
+ // ============================================
174
+ // HOOK: useCheckoutStepper
175
+ // ============================================
176
+
177
+ export function useCheckoutStepper({
178
+ initialStep = 0,
179
+ steps,
180
+ onStepChange,
181
+ onComplete,
182
+ validateOnNext = true,
183
+ }: UseCheckoutStepperOptions): UseCheckoutStepperReturn {
184
+ const [currentStep, setCurrentStep] = React.useState(initialStep)
185
+ const [completedSteps, setCompletedSteps] = React.useState<Set<number>>(new Set())
186
+ const [skippedSteps, setSkippedSteps] = React.useState<Set<number>>(new Set())
187
+ const [isComplete, setIsComplete] = React.useState(false)
188
+ const [isValidating, setIsValidating] = React.useState(false)
189
+ const [validationError, setValidationError] = React.useState<string | null>(null)
190
+
191
+ const totalSteps = steps.length
192
+ const isFirstStep = currentStep === 0
193
+ const isLastStep = currentStep === totalSteps - 1
194
+ const currentStepData = steps[currentStep]
195
+
196
+ const getStepStatus = React.useCallback(
197
+ (index: number): CheckoutStepStatus => {
198
+ if (skippedSteps.has(index)) return "skipped"
199
+ if (completedSteps.has(index)) return "completed"
200
+ if (index === currentStep) return "current"
201
+ if (index < currentStep) return "completed"
202
+ return "pending"
203
+ },
204
+ [currentStep, completedSteps, skippedSteps]
205
+ )
206
+
207
+ const stepsWithStatus = React.useMemo(
208
+ () =>
209
+ steps.map((step, index) => ({
210
+ ...step,
211
+ status: getStepStatus(index),
212
+ })),
213
+ [steps, getStepStatus]
214
+ )
215
+
216
+ const validateCurrentStep = React.useCallback(async (): Promise<boolean> => {
217
+ const step = steps[currentStep]
218
+ if (!step?.validate) return true
219
+
220
+ setIsValidating(true)
221
+ setValidationError(null)
222
+
223
+ try {
224
+ const result = await step.validate()
225
+ if (!result.isValid) {
226
+ setValidationError(result.errorMessage || "Validation failed")
227
+ return false
228
+ }
229
+ return true
230
+ } catch (error) {
231
+ setValidationError("An error occurred during validation")
232
+ return false
233
+ } finally {
234
+ setIsValidating(false)
235
+ }
236
+ }, [currentStep, steps])
237
+
238
+ const goToStep = React.useCallback(
239
+ async (index: number): Promise<boolean> => {
240
+ if (index < 0 || index >= totalSteps) return false
241
+ if (steps[index]?.disabled) return false
242
+
243
+ // If going forward, validate current step
244
+ if (index > currentStep && validateOnNext) {
245
+ const isValid = await validateCurrentStep()
246
+ if (!isValid) return false
247
+ }
248
+
249
+ // Mark current step as completed if moving forward
250
+ if (index > currentStep) {
251
+ setCompletedSteps((prev) => new Set(prev).add(currentStep))
252
+ }
253
+
254
+ setCurrentStep(index)
255
+ setValidationError(null)
256
+ onStepChange?.(index, steps[index].id)
257
+ return true
258
+ },
259
+ [currentStep, totalSteps, steps, validateOnNext, validateCurrentStep, onStepChange]
260
+ )
261
+
262
+ const nextStep = React.useCallback(async (): Promise<boolean> => {
263
+ if (isLastStep) return false
264
+ return goToStep(currentStep + 1)
265
+ }, [isLastStep, currentStep, goToStep])
266
+
267
+ const prevStep = React.useCallback(() => {
268
+ if (isFirstStep) return
269
+ setCurrentStep((prev) => prev - 1)
270
+ setValidationError(null)
271
+ onStepChange?.(currentStep - 1, steps[currentStep - 1].id)
272
+ }, [isFirstStep, currentStep, steps, onStepChange])
273
+
274
+ const skipStep = React.useCallback(() => {
275
+ const step = steps[currentStep]
276
+ if (!step?.optional || isLastStep) return
277
+
278
+ setSkippedSteps((prev) => new Set(prev).add(currentStep))
279
+ setCurrentStep((prev) => prev + 1)
280
+ setValidationError(null)
281
+ onStepChange?.(currentStep + 1, steps[currentStep + 1].id)
282
+ }, [currentStep, steps, isLastStep, onStepChange])
283
+
284
+ const completeCheckout = React.useCallback(async (): Promise<boolean> => {
285
+ if (validateOnNext) {
286
+ const isValid = await validateCurrentStep()
287
+ if (!isValid) return false
288
+ }
289
+
290
+ setCompletedSteps((prev) => new Set(prev).add(currentStep))
291
+ setIsComplete(true)
292
+ onComplete?.()
293
+ return true
294
+ }, [currentStep, validateOnNext, validateCurrentStep, onComplete])
295
+
296
+ const reset = React.useCallback(() => {
297
+ setCurrentStep(initialStep)
298
+ setCompletedSteps(new Set())
299
+ setSkippedSteps(new Set())
300
+ setIsComplete(false)
301
+ setIsValidating(false)
302
+ setValidationError(null)
303
+ }, [initialStep])
304
+
305
+ const markStepCompleted = React.useCallback((index: number) => {
306
+ setCompletedSteps((prev) => new Set(prev).add(index))
307
+ }, [])
308
+
309
+ return {
310
+ currentStep,
311
+ currentStepData,
312
+ stepsWithStatus,
313
+ totalSteps,
314
+ isFirstStep,
315
+ isLastStep,
316
+ isComplete,
317
+ isValidating,
318
+ validationError,
319
+ goToStep,
320
+ nextStep,
321
+ prevStep,
322
+ skipStep,
323
+ completeCheckout,
324
+ reset,
325
+ getStepStatus,
326
+ skippedSteps,
327
+ markStepCompleted,
328
+ }
329
+ }
330
+
331
+ // ============================================
332
+ // CONTEXT
333
+ // ============================================
334
+
335
+ const CheckoutStepperContext = React.createContext<CheckoutStepperContextValue | null>(null)
336
+
337
+ export function useCheckoutStepperContext() {
338
+ const context = React.useContext(CheckoutStepperContext)
339
+ if (!context) {
340
+ throw new Error("useCheckoutStepperContext must be used within a WakaCheckoutStepper")
341
+ }
342
+ return context
343
+ }
344
+
345
+ // ============================================
346
+ // SUB-COMPONENTS
347
+ // ============================================
348
+
349
+ interface StepIndicatorProps {
350
+ step: CheckoutStep & { status: CheckoutStepStatus }
351
+ index: number
352
+ }
353
+
354
+ function StepIndicator({ step, index }: StepIndicatorProps) {
355
+ const { size, animated } = useCheckoutStepperContext()
356
+
357
+ const sizeClasses = {
358
+ sm: "h-8 w-8 text-xs",
359
+ md: "h-10 w-10 text-sm",
360
+ lg: "h-12 w-12 text-base",
361
+ }
362
+
363
+ const iconSizes = {
364
+ sm: "h-4 w-4",
365
+ md: "h-5 w-5",
366
+ lg: "h-6 w-6",
367
+ }
368
+
369
+ const statusClasses = {
370
+ pending: "bg-muted text-muted-foreground border-2 border-muted-foreground/30",
371
+ current: "bg-primary text-primary-foreground border-2 border-primary shadow-lg shadow-primary/25",
372
+ completed: "bg-green-500 text-white border-2 border-green-500",
373
+ error: "bg-destructive text-destructive-foreground border-2 border-destructive",
374
+ skipped: "bg-muted text-muted-foreground border-2 border-dashed border-muted-foreground/50",
375
+ }
376
+
377
+ const Icon = step.icon || defaultStepIcons[step.id as CheckoutStepId]
378
+
379
+ const renderContent = () => {
380
+ if (step.status === "completed") {
381
+ return <Check className={iconSizes[size]} />
382
+ }
383
+
384
+ if (step.status === "error") {
385
+ return <AlertCircle className={iconSizes[size]} />
386
+ }
387
+
388
+ if (step.status === "skipped") {
389
+ return <SkipForward className={iconSizes[size]} />
390
+ }
391
+
392
+ if (Icon) {
393
+ return <Icon className={iconSizes[size]} />
394
+ }
395
+
396
+ return <span className="font-semibold">{index + 1}</span>
397
+ }
398
+
399
+ return (
400
+ <div
401
+ className={cn(
402
+ "rounded-full flex items-center justify-center transition-all duration-300",
403
+ sizeClasses[size],
404
+ statusClasses[step.status],
405
+ animated && step.status === "current" && "animate-pulse"
406
+ )}
407
+ >
408
+ {renderContent()}
409
+ </div>
410
+ )
411
+ }
412
+
413
+ interface StepConnectorProps {
414
+ status: CheckoutStepStatus
415
+ nextStatus: CheckoutStepStatus
416
+ orientation: "horizontal" | "vertical"
417
+ }
418
+
419
+ function StepConnector({ status, nextStatus, orientation }: StepConnectorProps) {
420
+ const isCompleted = status === "completed" || status === "skipped"
421
+
422
+ return (
423
+ <div
424
+ className={cn(
425
+ "transition-all duration-500",
426
+ orientation === "horizontal"
427
+ ? "flex-1 h-0.5 mx-2 min-w-[20px]"
428
+ : "w-0.5 min-h-[32px] mx-auto my-1",
429
+ isCompleted ? "bg-green-500" : "bg-muted-foreground/30"
430
+ )}
431
+ />
432
+ )
433
+ }
434
+
435
+ interface StepItemProps {
436
+ step: CheckoutStep & { status: CheckoutStepStatus }
437
+ index: number
438
+ isLast: boolean
439
+ nextStatus?: CheckoutStepStatus
440
+ }
441
+
442
+ function StepItem({ step, index, isLast, nextStatus }: StepItemProps) {
443
+ const {
444
+ orientation,
445
+ size,
446
+ clickable,
447
+ allowPreviousOnly,
448
+ currentStep,
449
+ goToStep,
450
+ } = useCheckoutStepperContext()
451
+
452
+ const isClickable =
453
+ clickable &&
454
+ !step.disabled &&
455
+ (allowPreviousOnly ? index < currentStep : index !== currentStep)
456
+
457
+ const labelSizes = {
458
+ sm: "text-xs",
459
+ md: "text-sm",
460
+ lg: "text-base",
461
+ }
462
+
463
+ const descSizes = {
464
+ sm: "text-[10px]",
465
+ md: "text-xs",
466
+ lg: "text-sm",
467
+ }
468
+
469
+ const handleClick = async () => {
470
+ if (isClickable) {
471
+ await goToStep(index)
472
+ }
473
+ }
474
+
475
+ if (orientation === "vertical") {
476
+ return (
477
+ <div className="flex">
478
+ <div className="flex flex-col items-center">
479
+ <button
480
+ type="button"
481
+ onClick={handleClick}
482
+ disabled={!isClickable}
483
+ className={cn(
484
+ "flex items-center",
485
+ isClickable && "cursor-pointer hover:opacity-80",
486
+ !isClickable && "cursor-default"
487
+ )}
488
+ aria-current={step.status === "current" ? "step" : undefined}
489
+ >
490
+ <StepIndicator step={step} index={index} />
491
+ </button>
492
+ {!isLast && (
493
+ <StepConnector
494
+ status={step.status}
495
+ nextStatus={nextStatus || "pending"}
496
+ orientation="vertical"
497
+ />
498
+ )}
499
+ </div>
500
+ <div className="ml-4 pb-8">
501
+ <button
502
+ type="button"
503
+ onClick={handleClick}
504
+ disabled={!isClickable}
505
+ className={cn(
506
+ "text-left",
507
+ isClickable && "cursor-pointer hover:opacity-80",
508
+ !isClickable && "cursor-default"
509
+ )}
510
+ >
511
+ <p
512
+ className={cn(
513
+ "font-semibold",
514
+ labelSizes[size],
515
+ step.status === "current"
516
+ ? "text-foreground"
517
+ : step.status === "completed"
518
+ ? "text-green-600 dark:text-green-400"
519
+ : "text-muted-foreground",
520
+ step.disabled && "opacity-50"
521
+ )}
522
+ >
523
+ {step.title}
524
+ {step.optional && (
525
+ <span className="ml-2 text-muted-foreground font-normal text-xs">
526
+ (Optional)
527
+ </span>
528
+ )}
529
+ </p>
530
+ {step.description && (
531
+ <p className={cn("text-muted-foreground mt-0.5", descSizes[size])}>
532
+ {step.description}
533
+ </p>
534
+ )}
535
+ </button>
536
+ </div>
537
+ </div>
538
+ )
539
+ }
540
+
541
+ // Horizontal orientation
542
+ return (
543
+ <>
544
+ <div className="flex flex-col items-center flex-shrink-0">
545
+ <button
546
+ type="button"
547
+ onClick={handleClick}
548
+ disabled={!isClickable}
549
+ className={cn(
550
+ "flex flex-col items-center gap-2",
551
+ isClickable && "cursor-pointer hover:opacity-80",
552
+ !isClickable && "cursor-default"
553
+ )}
554
+ aria-current={step.status === "current" ? "step" : undefined}
555
+ >
556
+ <StepIndicator step={step} index={index} />
557
+ <div className="text-center min-w-[60px] max-w-[100px]">
558
+ <p
559
+ className={cn(
560
+ "font-semibold truncate",
561
+ labelSizes[size],
562
+ step.status === "current"
563
+ ? "text-foreground"
564
+ : step.status === "completed"
565
+ ? "text-green-600 dark:text-green-400"
566
+ : "text-muted-foreground",
567
+ step.disabled && "opacity-50"
568
+ )}
569
+ >
570
+ {step.title}
571
+ </p>
572
+ {step.description && (
573
+ <p
574
+ className={cn(
575
+ "text-muted-foreground mt-0.5 hidden md:block truncate",
576
+ descSizes[size]
577
+ )}
578
+ >
579
+ {step.description}
580
+ </p>
581
+ )}
582
+ </div>
583
+ </button>
584
+ </div>
585
+ {!isLast && (
586
+ <StepConnector
587
+ status={step.status}
588
+ nextStatus={nextStatus || "pending"}
589
+ orientation="horizontal"
590
+ />
591
+ )}
592
+ </>
593
+ )
594
+ }
595
+
596
+ // ============================================
597
+ // STEP CONTENT WRAPPER
598
+ // ============================================
599
+
600
+ interface StepContentProps {
601
+ children: React.ReactNode
602
+ isActive: boolean
603
+ direction: "slide" | "fade" | "scale"
604
+ animated: boolean
605
+ }
606
+
607
+ function StepContent({ children, isActive, direction, animated }: StepContentProps) {
608
+ const [shouldRender, setShouldRender] = React.useState(isActive)
609
+
610
+ React.useEffect(() => {
611
+ if (isActive) {
612
+ setShouldRender(true)
613
+ } else if (animated) {
614
+ const timer = setTimeout(() => setShouldRender(false), 300)
615
+ return () => clearTimeout(timer)
616
+ } else {
617
+ setShouldRender(false)
618
+ }
619
+ }, [isActive, animated])
620
+
621
+ if (!shouldRender) return null
622
+
623
+ const animationClasses = {
624
+ slide: cn(
625
+ "transition-all duration-300",
626
+ isActive
627
+ ? "opacity-100 translate-x-0"
628
+ : "opacity-0 translate-x-8"
629
+ ),
630
+ fade: cn(
631
+ "transition-opacity duration-300",
632
+ isActive ? "opacity-100" : "opacity-0"
633
+ ),
634
+ scale: cn(
635
+ "transition-all duration-300",
636
+ isActive
637
+ ? "opacity-100 scale-100"
638
+ : "opacity-0 scale-95"
639
+ ),
640
+ }
641
+
642
+ return (
643
+ <div className={animated ? animationClasses[direction] : undefined}>
644
+ {children}
645
+ </div>
646
+ )
647
+ }
648
+
649
+ // ============================================
650
+ // SUMMARY SIDEBAR
651
+ // ============================================
652
+
653
+ interface SummarySidebarProps {
654
+ steps: (CheckoutStep & { status: CheckoutStepStatus })[]
655
+ position: "left" | "right"
656
+ className?: string
657
+ }
658
+
659
+ function SummarySidebar({ steps, position, className }: SummarySidebarProps) {
660
+ const completedSteps = steps.filter(
661
+ (s) => s.status === "completed" || s.status === "skipped"
662
+ )
663
+
664
+ return (
665
+ <div
666
+ className={cn(
667
+ "w-full lg:w-80 p-4 bg-muted/50 rounded-lg border",
668
+ position === "left" ? "lg:order-first" : "lg:order-last",
669
+ className
670
+ )}
671
+ >
672
+ <h3 className="font-semibold text-lg mb-4">Order Summary</h3>
673
+ <div className="space-y-4">
674
+ {completedSteps.map((step) => (
675
+ <div key={step.id} className="pb-3 border-b border-border last:border-0">
676
+ <div className="flex items-center gap-2 mb-1">
677
+ <Check className="h-4 w-4 text-green-500" />
678
+ <span className="font-medium text-sm">{step.title}</span>
679
+ </div>
680
+ {step.summary && (
681
+ <div className="text-sm text-muted-foreground ml-6">
682
+ {step.summary}
683
+ </div>
684
+ )}
685
+ </div>
686
+ ))}
687
+ {completedSteps.length === 0 && (
688
+ <p className="text-sm text-muted-foreground">
689
+ Complete steps to see your order summary.
690
+ </p>
691
+ )}
692
+ </div>
693
+ </div>
694
+ )
695
+ }
696
+
697
+ // ============================================
698
+ // NAVIGATION BUTTONS
699
+ // ============================================
700
+
701
+ export interface CheckoutNavigationProps {
702
+ /** Custom class name */
703
+ className?: string
704
+ /** Custom labels */
705
+ labels?: {
706
+ back?: string
707
+ next?: string
708
+ skip?: string
709
+ complete?: string
710
+ processing?: string
711
+ }
712
+ }
713
+
714
+ export function CheckoutNavigation({
715
+ className,
716
+ labels = {},
717
+ }: CheckoutNavigationProps) {
718
+ const {
719
+ currentStep,
720
+ currentStepData,
721
+ isFirstStep,
722
+ isLastStep,
723
+ isValidating,
724
+ isLoading,
725
+ validationError,
726
+ nextStep,
727
+ prevStep,
728
+ skipStep,
729
+ completeCheckout,
730
+ } = useCheckoutStepperContext()
731
+
732
+ const {
733
+ back = "Back",
734
+ next = "Continue",
735
+ skip = "Skip",
736
+ complete = "Complete Order",
737
+ processing = "Processing...",
738
+ } = labels
739
+
740
+ const isProcessing = isValidating || isLoading
741
+
742
+ return (
743
+ <div className={cn("space-y-4", className)}>
744
+ {validationError && (
745
+ <div className="flex items-center gap-2 p-3 bg-destructive/10 text-destructive rounded-lg text-sm">
746
+ <AlertCircle className="h-4 w-4 flex-shrink-0" />
747
+ <span>{validationError}</span>
748
+ </div>
749
+ )}
750
+ <div className="flex items-center justify-between gap-4">
751
+ <button
752
+ type="button"
753
+ onClick={prevStep}
754
+ disabled={isFirstStep || isProcessing}
755
+ className={cn(
756
+ "flex items-center gap-2 px-4 py-2.5 text-sm font-medium rounded-lg border transition-all",
757
+ isFirstStep || isProcessing
758
+ ? "opacity-50 cursor-not-allowed bg-muted"
759
+ : "hover:bg-muted"
760
+ )}
761
+ >
762
+ <ChevronLeft className="h-4 w-4" />
763
+ {back}
764
+ </button>
765
+
766
+ <div className="flex items-center gap-2">
767
+ {currentStepData?.optional && !isLastStep && (
768
+ <button
769
+ type="button"
770
+ onClick={skipStep}
771
+ disabled={isProcessing}
772
+ className={cn(
773
+ "flex items-center gap-2 px-4 py-2.5 text-sm font-medium rounded-lg transition-all",
774
+ "text-muted-foreground hover:text-foreground hover:bg-muted",
775
+ isProcessing && "opacity-50 cursor-not-allowed"
776
+ )}
777
+ >
778
+ <SkipForward className="h-4 w-4" />
779
+ {skip}
780
+ </button>
781
+ )}
782
+
783
+ <button
784
+ type="button"
785
+ onClick={isLastStep ? completeCheckout : nextStep}
786
+ disabled={isProcessing}
787
+ className={cn(
788
+ "flex items-center gap-2 px-6 py-2.5 text-sm font-medium rounded-lg transition-all",
789
+ "bg-primary text-primary-foreground",
790
+ isProcessing
791
+ ? "opacity-70 cursor-not-allowed"
792
+ : "hover:bg-primary/90"
793
+ )}
794
+ >
795
+ {isProcessing ? (
796
+ <>
797
+ <Loader2 className="h-4 w-4 animate-spin" />
798
+ {processing}
799
+ </>
800
+ ) : isLastStep ? (
801
+ <>
802
+ {complete}
803
+ <Check className="h-4 w-4" />
804
+ </>
805
+ ) : (
806
+ <>
807
+ {next}
808
+ <ChevronRight className="h-4 w-4" />
809
+ </>
810
+ )}
811
+ </button>
812
+ </div>
813
+ </div>
814
+ </div>
815
+ )
816
+ }
817
+
818
+ // ============================================
819
+ // PROGRESS BAR
820
+ // ============================================
821
+
822
+ export interface CheckoutProgressBarProps {
823
+ className?: string
824
+ }
825
+
826
+ export function CheckoutProgressBar({ className }: CheckoutProgressBarProps) {
827
+ const { currentStep, totalSteps, stepsWithStatus } = useCheckoutStepperContext()
828
+
829
+ const completedCount = stepsWithStatus.filter(
830
+ (s) => s.status === "completed" || s.status === "skipped"
831
+ ).length
832
+
833
+ const progress = Math.round((completedCount / totalSteps) * 100)
834
+
835
+ return (
836
+ <div className={cn("space-y-2", className)}>
837
+ <div className="flex justify-between text-sm">
838
+ <span className="text-muted-foreground">
839
+ Step {currentStep + 1} of {totalSteps}
840
+ </span>
841
+ <span className="font-medium">{progress}% complete</span>
842
+ </div>
843
+ <div className="h-2 bg-muted rounded-full overflow-hidden">
844
+ <div
845
+ className="h-full bg-primary transition-all duration-500 ease-out rounded-full"
846
+ style={{ width: `${progress}%` }}
847
+ />
848
+ </div>
849
+ </div>
850
+ )
851
+ }
852
+
853
+ // ============================================
854
+ // MAIN COMPONENT
855
+ // ============================================
856
+
857
+ export function WakaCheckoutStepper({
858
+ steps,
859
+ currentStep: controlledStep,
860
+ onStepChange,
861
+ onComplete,
862
+ orientation = "horizontal",
863
+ size = "md",
864
+ clickable = true,
865
+ allowPreviousOnly = true,
866
+ showContent = true,
867
+ showSummary = false,
868
+ summaryPosition = "right",
869
+ animated = true,
870
+ animationDirection = "slide",
871
+ isLoading = false,
872
+ className,
873
+ labels,
874
+ }: CheckoutStepperProps) {
875
+ const stepper = useCheckoutStepper({
876
+ initialStep: controlledStep ?? 0,
877
+ steps,
878
+ onStepChange,
879
+ onComplete,
880
+ validateOnNext: true,
881
+ })
882
+
883
+ // Sync with controlled step if provided
884
+ React.useEffect(() => {
885
+ if (controlledStep !== undefined && controlledStep !== stepper.currentStep) {
886
+ stepper.goToStep(controlledStep)
887
+ }
888
+ }, [controlledStep])
889
+
890
+ const contextValue: CheckoutStepperContextValue = {
891
+ ...stepper,
892
+ orientation,
893
+ size,
894
+ clickable,
895
+ allowPreviousOnly,
896
+ animated,
897
+ animationDirection,
898
+ isLoading,
899
+ }
900
+
901
+ return (
902
+ <CheckoutStepperContext.Provider value={contextValue}>
903
+ <div className={cn("w-full", className)}>
904
+ {/* Step indicators */}
905
+ <nav
906
+ aria-label="Checkout progress"
907
+ className={cn(
908
+ "mb-8",
909
+ orientation === "horizontal"
910
+ ? "flex items-start justify-between overflow-x-auto pb-2"
911
+ : "flex flex-col"
912
+ )}
913
+ >
914
+ {stepper.stepsWithStatus.map((step, index) => (
915
+ <StepItem
916
+ key={step.id}
917
+ step={step}
918
+ index={index}
919
+ isLast={index === steps.length - 1}
920
+ nextStatus={stepper.stepsWithStatus[index + 1]?.status}
921
+ />
922
+ ))}
923
+ </nav>
924
+
925
+ {/* Content area */}
926
+ <div
927
+ className={cn(
928
+ "flex flex-col lg:flex-row gap-6",
929
+ showSummary && summaryPosition === "left" && "lg:flex-row-reverse"
930
+ )}
931
+ >
932
+ {/* Main content */}
933
+ {showContent && (
934
+ <div className="flex-1 min-w-0">
935
+ {steps.map((step, index) => (
936
+ <StepContent
937
+ key={step.id}
938
+ isActive={index === stepper.currentStep}
939
+ direction={animationDirection}
940
+ animated={animated}
941
+ >
942
+ <div className="p-6 rounded-lg border bg-card">
943
+ <h2 className="text-xl font-semibold mb-2">{step.title}</h2>
944
+ {step.description && (
945
+ <p className="text-muted-foreground mb-6">{step.description}</p>
946
+ )}
947
+ {step.content}
948
+ </div>
949
+ </StepContent>
950
+ ))}
951
+
952
+ {/* Navigation */}
953
+ <div className="mt-6">
954
+ <CheckoutNavigation labels={labels} />
955
+ </div>
956
+ </div>
957
+ )}
958
+
959
+ {/* Summary sidebar */}
960
+ {showSummary && (
961
+ <SummarySidebar
962
+ steps={stepper.stepsWithStatus}
963
+ position={summaryPosition}
964
+ />
965
+ )}
966
+ </div>
967
+ </div>
968
+ </CheckoutStepperContext.Provider>
969
+ )
970
+ }
971
+
972
+ // ============================================
973
+ // EXPORTS
974
+ // ============================================
975
+
976
+ export default WakaCheckoutStepper