@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,765 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import {
6
+ Fingerprint,
7
+ ScanFace,
8
+ Check,
9
+ X,
10
+ KeyRound,
11
+ AlertCircle,
12
+ RefreshCw,
13
+ Loader2,
14
+ } from "lucide-react"
15
+
16
+ // ============================================================================
17
+ // Types
18
+ // ============================================================================
19
+
20
+ export type BiometricType = "fingerprint" | "face" | "iris" | "voice"
21
+
22
+ export type BiometricState = "idle" | "scanning" | "success" | "error"
23
+
24
+ export interface BiometricPromptConfig {
25
+ /** Type of biometric authentication */
26
+ type: BiometricType
27
+ /** Custom label for the biometric type */
28
+ label?: string
29
+ /** Custom icon component */
30
+ icon?: React.ReactNode
31
+ }
32
+
33
+ export interface WakaBiometricPromptProps {
34
+ /** Available biometric types */
35
+ biometricTypes?: BiometricPromptConfig[]
36
+ /** Currently selected biometric type */
37
+ selectedType?: BiometricType
38
+ /** Callback when biometric type changes */
39
+ onTypeChange?: (type: BiometricType) => void
40
+ /** Title text */
41
+ title?: string
42
+ /** Description text */
43
+ description?: string
44
+ /** Current authentication state */
45
+ state?: BiometricState
46
+ /** Error message when state is "error" */
47
+ errorMessage?: string
48
+ /** Callback when authentication is initiated */
49
+ onAuthenticate?: () => void | Promise<void>
50
+ /** Callback when retry is clicked */
51
+ onRetry?: () => void
52
+ /** Callback when cancel is clicked */
53
+ onCancel?: () => void
54
+ /** Callback when fallback to password is clicked */
55
+ onFallbackToPassword?: () => void
56
+ /** Show fallback to password option */
57
+ showPasswordFallback?: boolean
58
+ /** Show cancel button */
59
+ showCancel?: boolean
60
+ /** Custom scanning duration in ms (for demo purposes) */
61
+ scanningDuration?: number
62
+ /** Success message */
63
+ successMessage?: string
64
+ /** Additional CSS classes */
65
+ className?: string
66
+ /** Size variant */
67
+ size?: "sm" | "md" | "lg"
68
+ /** Color scheme */
69
+ colorScheme?: "default" | "primary" | "dark"
70
+ }
71
+
72
+ export interface UseBiometricPromptOptions {
73
+ /** Initial biometric type */
74
+ initialType?: BiometricType
75
+ /** Simulated scanning duration */
76
+ scanningDuration?: number
77
+ /** Simulated success rate (0-1, for demo) */
78
+ successRate?: number
79
+ /** Callback on successful authentication */
80
+ onSuccess?: () => void
81
+ /** Callback on authentication error */
82
+ onError?: (error: string) => void
83
+ /** Actual authentication function */
84
+ authenticate?: () => Promise<boolean>
85
+ }
86
+
87
+ export interface UseBiometricPromptReturn {
88
+ /** Current state */
89
+ state: BiometricState
90
+ /** Current biometric type */
91
+ selectedType: BiometricType
92
+ /** Error message if any */
93
+ errorMessage: string | null
94
+ /** Set biometric type */
95
+ setSelectedType: (type: BiometricType) => void
96
+ /** Start authentication */
97
+ startAuthentication: () => Promise<void>
98
+ /** Retry authentication */
99
+ retry: () => void
100
+ /** Reset to idle state */
101
+ reset: () => void
102
+ /** Cancel authentication */
103
+ cancel: () => void
104
+ }
105
+
106
+ // ============================================================================
107
+ // Hook
108
+ // ============================================================================
109
+
110
+ export function useBiometricPrompt(
111
+ options: UseBiometricPromptOptions = {}
112
+ ): UseBiometricPromptReturn {
113
+ const {
114
+ initialType = "fingerprint",
115
+ scanningDuration = 2000,
116
+ successRate = 0.9,
117
+ onSuccess,
118
+ onError,
119
+ authenticate,
120
+ } = options
121
+
122
+ const [state, setState] = React.useState<BiometricState>("idle")
123
+ const [selectedType, setSelectedType] = React.useState<BiometricType>(initialType)
124
+ const [errorMessage, setErrorMessage] = React.useState<string | null>(null)
125
+ const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)
126
+
127
+ const clearTimeouts = React.useCallback(() => {
128
+ if (timeoutRef.current) {
129
+ clearTimeout(timeoutRef.current)
130
+ timeoutRef.current = null
131
+ }
132
+ }, [])
133
+
134
+ const reset = React.useCallback(() => {
135
+ clearTimeouts()
136
+ setState("idle")
137
+ setErrorMessage(null)
138
+ }, [clearTimeouts])
139
+
140
+ const cancel = React.useCallback(() => {
141
+ clearTimeouts()
142
+ setState("idle")
143
+ setErrorMessage(null)
144
+ }, [clearTimeouts])
145
+
146
+ const startAuthentication = React.useCallback(async () => {
147
+ clearTimeouts()
148
+ setState("scanning")
149
+ setErrorMessage(null)
150
+
151
+ try {
152
+ if (authenticate) {
153
+ // Use actual authentication function
154
+ const success = await authenticate()
155
+ if (success) {
156
+ setState("success")
157
+ onSuccess?.()
158
+ } else {
159
+ setState("error")
160
+ const message = "Authentication failed. Please try again."
161
+ setErrorMessage(message)
162
+ onError?.(message)
163
+ }
164
+ } else {
165
+ // Simulated authentication for demo
166
+ await new Promise<void>((resolve) => {
167
+ timeoutRef.current = setTimeout(() => {
168
+ const success = Math.random() < successRate
169
+ if (success) {
170
+ setState("success")
171
+ onSuccess?.()
172
+ } else {
173
+ setState("error")
174
+ const message = "Authentication failed. Please try again."
175
+ setErrorMessage(message)
176
+ onError?.(message)
177
+ }
178
+ resolve()
179
+ }, scanningDuration)
180
+ })
181
+ }
182
+ } catch (error) {
183
+ setState("error")
184
+ const message = error instanceof Error ? error.message : "An unexpected error occurred."
185
+ setErrorMessage(message)
186
+ onError?.(message)
187
+ }
188
+ }, [authenticate, clearTimeouts, onError, onSuccess, scanningDuration, successRate])
189
+
190
+ const retry = React.useCallback(() => {
191
+ startAuthentication()
192
+ }, [startAuthentication])
193
+
194
+ React.useEffect(() => {
195
+ return () => {
196
+ clearTimeouts()
197
+ }
198
+ }, [clearTimeouts])
199
+
200
+ return {
201
+ state,
202
+ selectedType,
203
+ errorMessage,
204
+ setSelectedType,
205
+ startAuthentication,
206
+ retry,
207
+ reset,
208
+ cancel,
209
+ }
210
+ }
211
+
212
+ // ============================================================================
213
+ // Default Configs
214
+ // ============================================================================
215
+
216
+ const defaultBiometricConfigs: BiometricPromptConfig[] = [
217
+ { type: "fingerprint", label: "Fingerprint" },
218
+ { type: "face", label: "Face ID" },
219
+ ]
220
+
221
+ const biometricIcons: Record<BiometricType, React.ReactNode> = {
222
+ fingerprint: <Fingerprint className="h-full w-full" />,
223
+ face: <ScanFace className="h-full w-full" />,
224
+ iris: <ScanFace className="h-full w-full" />,
225
+ voice: <ScanFace className="h-full w-full" />,
226
+ }
227
+
228
+ // ============================================================================
229
+ // Size Configs
230
+ // ============================================================================
231
+
232
+ const sizeConfig = {
233
+ sm: {
234
+ container: "p-4 max-w-xs",
235
+ icon: "h-12 w-12",
236
+ iconInner: "h-8 w-8",
237
+ title: "text-base",
238
+ description: "text-xs",
239
+ button: "h-8 px-3 text-xs",
240
+ typeButton: "h-8 w-8",
241
+ },
242
+ md: {
243
+ container: "p-6 max-w-sm",
244
+ icon: "h-20 w-20",
245
+ iconInner: "h-12 w-12",
246
+ title: "text-lg",
247
+ description: "text-sm",
248
+ button: "h-10 px-4 text-sm",
249
+ typeButton: "h-10 w-10",
250
+ },
251
+ lg: {
252
+ container: "p-8 max-w-md",
253
+ icon: "h-28 w-28",
254
+ iconInner: "h-16 w-16",
255
+ title: "text-xl",
256
+ description: "text-base",
257
+ button: "h-12 px-6 text-base",
258
+ typeButton: "h-12 w-12",
259
+ },
260
+ }
261
+
262
+ // ============================================================================
263
+ // Color Scheme Configs
264
+ // ============================================================================
265
+
266
+ const colorSchemeConfig = {
267
+ default: {
268
+ background: "bg-background",
269
+ text: "text-foreground",
270
+ muted: "text-muted-foreground",
271
+ primary: "text-primary",
272
+ success: "text-green-500",
273
+ error: "text-destructive",
274
+ iconBg: "bg-muted",
275
+ iconBgScanning: "bg-primary/10",
276
+ iconBgSuccess: "bg-green-500/10",
277
+ iconBgError: "bg-destructive/10",
278
+ button: "bg-primary text-primary-foreground hover:bg-primary/90",
279
+ buttonOutline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
280
+ buttonGhost: "hover:bg-accent hover:text-accent-foreground",
281
+ },
282
+ primary: {
283
+ background: "bg-primary",
284
+ text: "text-primary-foreground",
285
+ muted: "text-primary-foreground/70",
286
+ primary: "text-primary-foreground",
287
+ success: "text-green-300",
288
+ error: "text-red-300",
289
+ iconBg: "bg-primary-foreground/10",
290
+ iconBgScanning: "bg-primary-foreground/20",
291
+ iconBgSuccess: "bg-green-400/20",
292
+ iconBgError: "bg-red-400/20",
293
+ button: "bg-primary-foreground text-primary hover:bg-primary-foreground/90",
294
+ buttonOutline: "border border-primary-foreground/30 bg-transparent hover:bg-primary-foreground/10",
295
+ buttonGhost: "hover:bg-primary-foreground/10",
296
+ },
297
+ dark: {
298
+ background: "bg-zinc-900",
299
+ text: "text-zinc-100",
300
+ muted: "text-zinc-400",
301
+ primary: "text-blue-400",
302
+ success: "text-green-400",
303
+ error: "text-red-400",
304
+ iconBg: "bg-zinc-800",
305
+ iconBgScanning: "bg-blue-500/20",
306
+ iconBgSuccess: "bg-green-500/20",
307
+ iconBgError: "bg-red-500/20",
308
+ button: "bg-blue-500 text-white hover:bg-blue-600",
309
+ buttonOutline: "border border-zinc-700 bg-transparent hover:bg-zinc-800",
310
+ buttonGhost: "hover:bg-zinc-800",
311
+ },
312
+ }
313
+
314
+ // ============================================================================
315
+ // Scanning Animation Component
316
+ // ============================================================================
317
+
318
+ interface ScanningAnimationProps {
319
+ type: BiometricType
320
+ size: "sm" | "md" | "lg"
321
+ colorScheme: "default" | "primary" | "dark"
322
+ }
323
+
324
+ function ScanningAnimation({ type, size, colorScheme }: ScanningAnimationProps) {
325
+ const colors = colorSchemeConfig[colorScheme]
326
+ const sizes = sizeConfig[size]
327
+
328
+ return (
329
+ <div className="absolute inset-0 flex items-center justify-center">
330
+ {/* Pulsing rings */}
331
+ <div className={cn(
332
+ "absolute rounded-full animate-ping",
333
+ colors.iconBgScanning,
334
+ sizes.icon
335
+ )} style={{ animationDuration: "1.5s" }} />
336
+ <div className={cn(
337
+ "absolute rounded-full animate-ping",
338
+ colors.iconBgScanning,
339
+ sizes.icon
340
+ )} style={{ animationDuration: "1.5s", animationDelay: "0.5s" }} />
341
+
342
+ {/* Scanning line for fingerprint */}
343
+ {type === "fingerprint" && (
344
+ <div
345
+ className={cn(
346
+ "absolute h-0.5 rounded-full animate-scan-line",
347
+ colorScheme === "default" ? "bg-primary" :
348
+ colorScheme === "primary" ? "bg-primary-foreground" : "bg-blue-400"
349
+ )}
350
+ style={{ width: "70%" }}
351
+ />
352
+ )}
353
+
354
+ {/* Scanning frame for face */}
355
+ {type === "face" && (
356
+ <div className="absolute inset-2 border-2 border-dashed rounded-lg animate-pulse"
357
+ style={{
358
+ borderColor: colorScheme === "default" ? "hsl(var(--primary))" :
359
+ colorScheme === "primary" ? "hsl(var(--primary-foreground))" : "#60a5fa"
360
+ }}
361
+ />
362
+ )}
363
+ </div>
364
+ )
365
+ }
366
+
367
+ // ============================================================================
368
+ // Success Animation Component
369
+ // ============================================================================
370
+
371
+ interface SuccessAnimationProps {
372
+ size: "sm" | "md" | "lg"
373
+ colorScheme: "default" | "primary" | "dark"
374
+ }
375
+
376
+ function SuccessAnimation({ size, colorScheme }: SuccessAnimationProps) {
377
+ const colors = colorSchemeConfig[colorScheme]
378
+ const sizes = sizeConfig[size]
379
+
380
+ return (
381
+ <div className={cn(
382
+ "flex items-center justify-center rounded-full animate-success-pop",
383
+ colors.iconBgSuccess,
384
+ sizes.icon
385
+ )}>
386
+ <Check className={cn(sizes.iconInner, colors.success)} strokeWidth={3} />
387
+ </div>
388
+ )
389
+ }
390
+
391
+ // ============================================================================
392
+ // Error Animation Component
393
+ // ============================================================================
394
+
395
+ interface ErrorAnimationProps {
396
+ size: "sm" | "md" | "lg"
397
+ colorScheme: "default" | "primary" | "dark"
398
+ }
399
+
400
+ function ErrorAnimation({ size, colorScheme }: ErrorAnimationProps) {
401
+ const colors = colorSchemeConfig[colorScheme]
402
+ const sizes = sizeConfig[size]
403
+
404
+ return (
405
+ <div className={cn(
406
+ "flex items-center justify-center rounded-full animate-error-shake",
407
+ colors.iconBgError,
408
+ sizes.icon
409
+ )}>
410
+ <AlertCircle className={cn(sizes.iconInner, colors.error)} strokeWidth={2} />
411
+ </div>
412
+ )
413
+ }
414
+
415
+ // ============================================================================
416
+ // Biometric Type Selector
417
+ // ============================================================================
418
+
419
+ interface BiometricTypeSelectorProps {
420
+ types: BiometricPromptConfig[]
421
+ selectedType: BiometricType
422
+ onTypeChange: (type: BiometricType) => void
423
+ disabled?: boolean
424
+ size: "sm" | "md" | "lg"
425
+ colorScheme: "default" | "primary" | "dark"
426
+ }
427
+
428
+ function BiometricTypeSelector({
429
+ types,
430
+ selectedType,
431
+ onTypeChange,
432
+ disabled,
433
+ size,
434
+ colorScheme,
435
+ }: BiometricTypeSelectorProps) {
436
+ const colors = colorSchemeConfig[colorScheme]
437
+ const sizes = sizeConfig[size]
438
+
439
+ if (types.length <= 1) return null
440
+
441
+ return (
442
+ <div className="flex items-center justify-center gap-2 mt-4" role="radiogroup" aria-label="Biometric type selection">
443
+ {types.map((config) => {
444
+ const isSelected = config.type === selectedType
445
+ const icon = config.icon || biometricIcons[config.type]
446
+
447
+ return (
448
+ <button
449
+ key={config.type}
450
+ type="button"
451
+ role="radio"
452
+ aria-checked={isSelected}
453
+ aria-label={config.label || config.type}
454
+ disabled={disabled}
455
+ onClick={() => onTypeChange(config.type)}
456
+ className={cn(
457
+ "flex items-center justify-center rounded-full transition-all",
458
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
459
+ sizes.typeButton,
460
+ isSelected
461
+ ? cn(colors.button)
462
+ : cn(colors.buttonGhost, colors.muted),
463
+ disabled && "opacity-50 cursor-not-allowed"
464
+ )}
465
+ >
466
+ <div className={cn(
467
+ size === "sm" ? "h-4 w-4" : size === "md" ? "h-5 w-5" : "h-6 w-6"
468
+ )}>
469
+ {icon}
470
+ </div>
471
+ </button>
472
+ )
473
+ })}
474
+ </div>
475
+ )
476
+ }
477
+
478
+ // ============================================================================
479
+ // Main Component
480
+ // ============================================================================
481
+
482
+ export const WakaBiometricPrompt = React.forwardRef<
483
+ HTMLDivElement,
484
+ WakaBiometricPromptProps
485
+ >(
486
+ (
487
+ {
488
+ biometricTypes = defaultBiometricConfigs,
489
+ selectedType: controlledSelectedType,
490
+ onTypeChange,
491
+ title = "Use biometrics",
492
+ description = "Authenticate using your biometric data",
493
+ state = "idle",
494
+ errorMessage,
495
+ onAuthenticate,
496
+ onRetry,
497
+ onCancel,
498
+ onFallbackToPassword,
499
+ showPasswordFallback = true,
500
+ showCancel = true,
501
+ successMessage = "Authentication successful",
502
+ className,
503
+ size = "md",
504
+ colorScheme = "default",
505
+ },
506
+ ref
507
+ ) => {
508
+ const [internalSelectedType, setInternalSelectedType] = React.useState<BiometricType>(
509
+ biometricTypes[0]?.type || "fingerprint"
510
+ )
511
+
512
+ const selectedType = controlledSelectedType ?? internalSelectedType
513
+ const handleTypeChange = onTypeChange ?? setInternalSelectedType
514
+
515
+ const colors = colorSchemeConfig[colorScheme]
516
+ const sizes = sizeConfig[size]
517
+
518
+ const currentConfig = biometricTypes.find((c) => c.type === selectedType) || biometricTypes[0]
519
+ const icon = currentConfig?.icon || biometricIcons[selectedType]
520
+
521
+ const isDisabled = state === "scanning" || state === "success"
522
+
523
+ // Handle keyboard navigation
524
+ const handleKeyDown = (event: React.KeyboardEvent) => {
525
+ if (event.key === "Enter" || event.key === " ") {
526
+ event.preventDefault()
527
+ if (state === "idle") {
528
+ onAuthenticate?.()
529
+ } else if (state === "error") {
530
+ onRetry?.()
531
+ }
532
+ } else if (event.key === "Escape") {
533
+ event.preventDefault()
534
+ onCancel?.()
535
+ }
536
+ }
537
+
538
+ return (
539
+ <div
540
+ ref={ref}
541
+ className={cn(
542
+ "flex flex-col items-center rounded-2xl shadow-lg",
543
+ colors.background,
544
+ sizes.container,
545
+ className
546
+ )}
547
+ role="dialog"
548
+ aria-labelledby="biometric-title"
549
+ aria-describedby="biometric-description"
550
+ onKeyDown={handleKeyDown}
551
+ >
552
+ {/* Icon Container */}
553
+ <div className="relative flex items-center justify-center mb-4">
554
+ {state === "idle" && (
555
+ <button
556
+ type="button"
557
+ onClick={onAuthenticate}
558
+ disabled={isDisabled}
559
+ className={cn(
560
+ "flex items-center justify-center rounded-full transition-all",
561
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
562
+ "hover:scale-105 active:scale-95",
563
+ colors.iconBg,
564
+ colors.primary,
565
+ sizes.icon
566
+ )}
567
+ aria-label={`Authenticate with ${currentConfig?.label || selectedType}`}
568
+ >
569
+ <div className={sizes.iconInner}>{icon}</div>
570
+ </button>
571
+ )}
572
+
573
+ {state === "scanning" && (
574
+ <div className={cn(
575
+ "relative flex items-center justify-center rounded-full",
576
+ colors.iconBgScanning,
577
+ colors.primary,
578
+ sizes.icon
579
+ )}>
580
+ <div className={cn(sizes.iconInner, "animate-pulse")}>{icon}</div>
581
+ <ScanningAnimation type={selectedType} size={size} colorScheme={colorScheme} />
582
+ </div>
583
+ )}
584
+
585
+ {state === "success" && (
586
+ <SuccessAnimation size={size} colorScheme={colorScheme} />
587
+ )}
588
+
589
+ {state === "error" && (
590
+ <ErrorAnimation size={size} colorScheme={colorScheme} />
591
+ )}
592
+ </div>
593
+
594
+ {/* Title */}
595
+ <h2
596
+ id="biometric-title"
597
+ className={cn("font-semibold text-center", colors.text, sizes.title)}
598
+ >
599
+ {state === "success" ? successMessage : state === "error" ? "Authentication failed" : title}
600
+ </h2>
601
+
602
+ {/* Description */}
603
+ <p
604
+ id="biometric-description"
605
+ className={cn("text-center mt-1", colors.muted, sizes.description)}
606
+ >
607
+ {state === "scanning" && `Scanning ${currentConfig?.label || selectedType}...`}
608
+ {state === "error" && (errorMessage || "Please try again")}
609
+ {state === "success" && "You can now proceed"}
610
+ {state === "idle" && description}
611
+ </p>
612
+
613
+ {/* Biometric Type Selector */}
614
+ {state === "idle" && (
615
+ <BiometricTypeSelector
616
+ types={biometricTypes}
617
+ selectedType={selectedType}
618
+ onTypeChange={handleTypeChange}
619
+ disabled={isDisabled}
620
+ size={size}
621
+ colorScheme={colorScheme}
622
+ />
623
+ )}
624
+
625
+ {/* Loading indicator for scanning state */}
626
+ {state === "scanning" && (
627
+ <div className={cn("flex items-center gap-2 mt-4", colors.muted)}>
628
+ <Loader2 className="h-4 w-4 animate-spin" />
629
+ <span className={sizes.description}>Processing...</span>
630
+ </div>
631
+ )}
632
+
633
+ {/* Action Buttons */}
634
+ <div className="flex flex-col items-center gap-2 mt-6 w-full">
635
+ {state === "error" && onRetry && (
636
+ <button
637
+ type="button"
638
+ onClick={onRetry}
639
+ className={cn(
640
+ "inline-flex items-center justify-center gap-2 rounded-lg font-medium transition-colors",
641
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
642
+ colors.button,
643
+ sizes.button,
644
+ "w-full"
645
+ )}
646
+ aria-label="Retry authentication"
647
+ >
648
+ <RefreshCw className="h-4 w-4" />
649
+ Try again
650
+ </button>
651
+ )}
652
+
653
+ {state === "idle" && showPasswordFallback && onFallbackToPassword && (
654
+ <button
655
+ type="button"
656
+ onClick={onFallbackToPassword}
657
+ className={cn(
658
+ "inline-flex items-center justify-center gap-2 rounded-lg font-medium transition-colors",
659
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
660
+ colors.buttonOutline,
661
+ sizes.button,
662
+ "w-full"
663
+ )}
664
+ aria-label="Use password instead"
665
+ >
666
+ <KeyRound className="h-4 w-4" />
667
+ Use password instead
668
+ </button>
669
+ )}
670
+
671
+ {(state === "idle" || state === "error") && showCancel && onCancel && (
672
+ <button
673
+ type="button"
674
+ onClick={onCancel}
675
+ className={cn(
676
+ "inline-flex items-center justify-center gap-2 rounded-lg font-medium transition-colors",
677
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
678
+ colors.buttonGhost,
679
+ colors.muted,
680
+ sizes.button
681
+ )}
682
+ aria-label="Cancel authentication"
683
+ >
684
+ <X className="h-4 w-4" />
685
+ Cancel
686
+ </button>
687
+ )}
688
+ </div>
689
+ </div>
690
+ )
691
+ }
692
+ )
693
+
694
+ WakaBiometricPrompt.displayName = "WakaBiometricPrompt"
695
+
696
+ // ============================================================================
697
+ // CSS Animations (injected dynamically)
698
+ // ============================================================================
699
+
700
+ if (typeof document !== "undefined") {
701
+ const styleId = "waka-biometric-prompt-styles"
702
+ if (!document.getElementById(styleId)) {
703
+ const style = document.createElement("style")
704
+ style.id = styleId
705
+ style.textContent = `
706
+ @keyframes scan-line {
707
+ 0%, 100% {
708
+ transform: translateY(-200%);
709
+ opacity: 0;
710
+ }
711
+ 10%, 90% {
712
+ opacity: 1;
713
+ }
714
+ 50% {
715
+ transform: translateY(200%);
716
+ }
717
+ }
718
+
719
+ @keyframes success-pop {
720
+ 0% {
721
+ transform: scale(0);
722
+ opacity: 0;
723
+ }
724
+ 50% {
725
+ transform: scale(1.2);
726
+ }
727
+ 100% {
728
+ transform: scale(1);
729
+ opacity: 1;
730
+ }
731
+ }
732
+
733
+ @keyframes error-shake {
734
+ 0%, 100% {
735
+ transform: translateX(0);
736
+ }
737
+ 10%, 30%, 50%, 70%, 90% {
738
+ transform: translateX(-4px);
739
+ }
740
+ 20%, 40%, 60%, 80% {
741
+ transform: translateX(4px);
742
+ }
743
+ }
744
+
745
+ .animate-scan-line {
746
+ animation: scan-line 2s ease-in-out infinite;
747
+ }
748
+
749
+ .animate-success-pop {
750
+ animation: success-pop 0.4s ease-out forwards;
751
+ }
752
+
753
+ .animate-error-shake {
754
+ animation: error-shake 0.5s ease-in-out;
755
+ }
756
+ `
757
+ document.head.appendChild(style)
758
+ }
759
+ }
760
+
761
+ // ============================================================================
762
+ // Exports
763
+ // ============================================================================
764
+
765
+ export default WakaBiometricPrompt