@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,704 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+
6
+ // ============================================================================
7
+ // Types
8
+ // ============================================================================
9
+
10
+ export type BadgeRarity = "common" | "rare" | "epic" | "legendary"
11
+
12
+ export interface Badge {
13
+ /** Unique identifier */
14
+ id: string
15
+ /** Badge name */
16
+ name: string
17
+ /** Badge description */
18
+ description: string
19
+ /** Badge icon (React node) */
20
+ icon: React.ReactNode
21
+ /** Rarity tier */
22
+ rarity: BadgeRarity
23
+ /** Whether the badge is unlocked */
24
+ unlocked: boolean
25
+ /** When the badge was unlocked */
26
+ unlockedAt?: Date
27
+ /** Progress towards unlocking (0-100) */
28
+ progress?: number
29
+ /** Badge category */
30
+ category?: string
31
+ /** Whether this is a newly unlocked badge */
32
+ isNew?: boolean
33
+ }
34
+
35
+ export interface WakaBadgeShowcaseProps {
36
+ /** Array of badges to display */
37
+ badges: Badge[]
38
+ /** Callback when a badge is clicked */
39
+ onBadgeClick?: (id: string) => void
40
+ /** Display variant */
41
+ variant?: "grid" | "list" | "showcase"
42
+ /** Whether to show locked badges */
43
+ showLocked?: boolean
44
+ /** Whether to show progress indicators */
45
+ showProgress?: boolean
46
+ /** Number of columns for grid layout */
47
+ columns?: number
48
+ /** Size of badge icons */
49
+ size?: "sm" | "md" | "lg"
50
+ /** Additional CSS classes */
51
+ className?: string
52
+ }
53
+
54
+ // ============================================================================
55
+ // Rarity Configuration
56
+ // ============================================================================
57
+
58
+ const rarityConfig = {
59
+ common: {
60
+ gradient: "from-slate-400 to-slate-500",
61
+ glow: "#94a3b8",
62
+ border: "border-slate-400",
63
+ bg: "bg-slate-100 dark:bg-slate-800",
64
+ text: "text-slate-600 dark:text-slate-300",
65
+ shine: "rgba(148, 163, 184, 0.6)",
66
+ label: "Common",
67
+ },
68
+ rare: {
69
+ gradient: "from-blue-400 to-blue-600",
70
+ glow: "#3b82f6",
71
+ border: "border-blue-400",
72
+ bg: "bg-blue-100 dark:bg-blue-900",
73
+ text: "text-blue-600 dark:text-blue-300",
74
+ shine: "rgba(59, 130, 246, 0.6)",
75
+ label: "Rare",
76
+ },
77
+ epic: {
78
+ gradient: "from-purple-400 to-purple-600",
79
+ glow: "#a855f7",
80
+ border: "border-purple-400",
81
+ bg: "bg-purple-100 dark:bg-purple-900",
82
+ text: "text-purple-600 dark:text-purple-300",
83
+ shine: "rgba(168, 85, 247, 0.6)",
84
+ label: "Epic",
85
+ },
86
+ legendary: {
87
+ gradient: "from-amber-400 via-orange-500 to-red-500",
88
+ glow: "#f59e0b",
89
+ border: "border-amber-400",
90
+ bg: "bg-amber-100 dark:bg-amber-900",
91
+ text: "text-amber-600 dark:text-amber-300",
92
+ shine: "rgba(245, 158, 11, 0.8)",
93
+ label: "Legendary",
94
+ },
95
+ }
96
+
97
+ // ============================================================================
98
+ // Size Configuration
99
+ // ============================================================================
100
+
101
+ const sizeConfig = {
102
+ sm: {
103
+ badge: "h-12 w-12",
104
+ icon: "h-6 w-6",
105
+ text: "text-xs",
106
+ gap: "gap-2",
107
+ },
108
+ md: {
109
+ badge: "h-16 w-16",
110
+ icon: "h-8 w-8",
111
+ text: "text-sm",
112
+ gap: "gap-3",
113
+ },
114
+ lg: {
115
+ badge: "h-24 w-24",
116
+ icon: "h-12 w-12",
117
+ text: "text-base",
118
+ gap: "gap-4",
119
+ },
120
+ }
121
+
122
+ // ============================================================================
123
+ // Shine Effect Component
124
+ // ============================================================================
125
+
126
+ function ShineEffect({ active, color }: { active: boolean; color: string }) {
127
+ if (!active) return null
128
+
129
+ return (
130
+ <>
131
+ <div className="absolute inset-0 overflow-hidden rounded-full">
132
+ <div
133
+ className="absolute inset-0 animate-badge-shine"
134
+ style={{
135
+ background: `linear-gradient(
136
+ 90deg,
137
+ transparent 0%,
138
+ ${color} 50%,
139
+ transparent 100%
140
+ )`,
141
+ }}
142
+ />
143
+ </div>
144
+ <style>{`
145
+ @keyframes badge-shine {
146
+ 0% {
147
+ transform: translateX(-100%) rotate(45deg);
148
+ }
149
+ 100% {
150
+ transform: translateX(200%) rotate(45deg);
151
+ }
152
+ }
153
+ .animate-badge-shine {
154
+ animation: badge-shine 3s ease-in-out infinite;
155
+ }
156
+ `}</style>
157
+ </>
158
+ )
159
+ }
160
+
161
+ // ============================================================================
162
+ // Glow Effect Component
163
+ // ============================================================================
164
+
165
+ function GlowEffect({ active, color }: { active: boolean; color: string }) {
166
+ if (!active) return null
167
+
168
+ return (
169
+ <>
170
+ <div
171
+ className="absolute inset-0 rounded-full animate-badge-glow"
172
+ style={{
173
+ boxShadow: `0 0 20px 4px ${color}60, 0 0 40px 8px ${color}30`,
174
+ }}
175
+ />
176
+ <style>{`
177
+ @keyframes badge-glow {
178
+ 0%, 100% { opacity: 0.6; }
179
+ 50% { opacity: 1; }
180
+ }
181
+ .animate-badge-glow {
182
+ animation: badge-glow 2s ease-in-out infinite;
183
+ }
184
+ `}</style>
185
+ </>
186
+ )
187
+ }
188
+
189
+ // ============================================================================
190
+ // New Badge Highlight Component
191
+ // ============================================================================
192
+
193
+ function NewBadgeIndicator() {
194
+ return (
195
+ <>
196
+ <div className="absolute -top-1 -right-1 z-20">
197
+ <span className="relative flex h-4 w-4">
198
+ <span className="animate-badge-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
199
+ <span className="relative inline-flex rounded-full h-4 w-4 bg-green-500 items-center justify-center">
200
+ <span className="text-[8px] font-bold text-white">!</span>
201
+ </span>
202
+ </span>
203
+ </div>
204
+ <style>{`
205
+ @keyframes badge-ping {
206
+ 75%, 100% {
207
+ transform: scale(2);
208
+ opacity: 0;
209
+ }
210
+ }
211
+ .animate-badge-ping {
212
+ animation: badge-ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
213
+ }
214
+ `}</style>
215
+ </>
216
+ )
217
+ }
218
+
219
+ // ============================================================================
220
+ // Progress Ring Component
221
+ // ============================================================================
222
+
223
+ function ProgressRing({
224
+ progress,
225
+ color,
226
+ size,
227
+ }: {
228
+ progress: number
229
+ color: string
230
+ size: "sm" | "md" | "lg"
231
+ }) {
232
+ const strokeWidth = size === "sm" ? 3 : size === "md" ? 4 : 5
233
+ const radius = 46
234
+
235
+ return (
236
+ <svg className="absolute inset-0 -rotate-90" viewBox="0 0 100 100">
237
+ <circle
238
+ cx="50"
239
+ cy="50"
240
+ r={radius}
241
+ fill="none"
242
+ stroke="currentColor"
243
+ strokeWidth={strokeWidth}
244
+ className="text-muted-foreground/20"
245
+ />
246
+ <circle
247
+ cx="50"
248
+ cy="50"
249
+ r={radius}
250
+ fill="none"
251
+ stroke={color}
252
+ strokeWidth={strokeWidth}
253
+ strokeLinecap="round"
254
+ strokeDasharray={`${(progress / 100) * 2 * Math.PI * radius} ${2 * Math.PI * radius}`}
255
+ className="transition-all duration-500"
256
+ />
257
+ </svg>
258
+ )
259
+ }
260
+
261
+ // ============================================================================
262
+ // Badge Item Component
263
+ // ============================================================================
264
+
265
+ interface BadgeItemProps {
266
+ badge: Badge
267
+ size: "sm" | "md" | "lg"
268
+ showProgress: boolean
269
+ onClick?: () => void
270
+ variant: "grid" | "list" | "showcase"
271
+ }
272
+
273
+ function BadgeItem({ badge, size, showProgress, onClick, variant }: BadgeItemProps) {
274
+ const [isHovered, setIsHovered] = React.useState(false)
275
+ const config = rarityConfig[badge.rarity]
276
+ const sizes = sizeConfig[size]
277
+
278
+ const isLocked = !badge.unlocked
279
+ const hasProgress = isLocked && showProgress && badge.progress !== undefined && badge.progress > 0
280
+
281
+ return (
282
+ <div
283
+ className={cn(
284
+ "group relative cursor-pointer transition-transform duration-200",
285
+ variant === "list" && "flex items-center gap-4",
286
+ variant === "showcase" && "flex flex-col items-center",
287
+ onClick && "cursor-pointer"
288
+ )}
289
+ onClick={onClick}
290
+ onMouseEnter={() => setIsHovered(true)}
291
+ onMouseLeave={() => setIsHovered(false)}
292
+ >
293
+ {/* Badge Container */}
294
+ <div className="relative">
295
+ {/* Glow effect for unlocked badges */}
296
+ {!isLocked && <GlowEffect active={isHovered} color={config.glow} />}
297
+
298
+ {/* New badge indicator */}
299
+ {badge.isNew && !isLocked && <NewBadgeIndicator />}
300
+
301
+ {/* Main badge circle */}
302
+ <div
303
+ className={cn(
304
+ "relative flex items-center justify-center rounded-full border-2 transition-all duration-300",
305
+ sizes.badge,
306
+ isLocked
307
+ ? "bg-muted/50 border-muted-foreground/30 grayscale"
308
+ : cn("bg-gradient-to-br", config.gradient, config.border),
309
+ !isLocked && isHovered && "scale-110",
310
+ !isLocked && "shadow-lg"
311
+ )}
312
+ style={
313
+ !isLocked && isHovered
314
+ ? { boxShadow: `0 0 30px 8px ${config.glow}50` }
315
+ : {}
316
+ }
317
+ >
318
+ {/* Progress ring for locked badges */}
319
+ {hasProgress && (
320
+ <ProgressRing
321
+ progress={badge.progress!}
322
+ color={config.glow}
323
+ size={size}
324
+ />
325
+ )}
326
+
327
+ {/* Icon or silhouette */}
328
+ <div
329
+ className={cn(
330
+ sizes.icon,
331
+ "transition-all duration-200 flex items-center justify-center",
332
+ isLocked
333
+ ? "text-muted-foreground/40 opacity-50 blur-[1px]"
334
+ : "text-white"
335
+ )}
336
+ >
337
+ {badge.icon}
338
+ </div>
339
+
340
+ {/* Lock overlay for locked badges */}
341
+ {isLocked && (
342
+ <div className="absolute inset-0 flex items-center justify-center rounded-full bg-black/20">
343
+ <svg
344
+ className="h-5 w-5 text-muted-foreground/60"
345
+ fill="none"
346
+ viewBox="0 0 24 24"
347
+ stroke="currentColor"
348
+ >
349
+ <path
350
+ strokeLinecap="round"
351
+ strokeLinejoin="round"
352
+ strokeWidth={2}
353
+ d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
354
+ />
355
+ </svg>
356
+ </div>
357
+ )}
358
+
359
+ {/* Shine effect for unlocked badges */}
360
+ {!isLocked && <ShineEffect active={true} color={config.shine} />}
361
+ </div>
362
+ </div>
363
+
364
+ {/* Badge info for list/showcase variants */}
365
+ {(variant === "list" || variant === "showcase") && (
366
+ <div
367
+ className={cn(
368
+ "transition-opacity duration-200",
369
+ variant === "showcase" && "text-center mt-2",
370
+ isLocked && "opacity-60"
371
+ )}
372
+ >
373
+ <div className={cn("font-semibold", sizes.text)}>{badge.name}</div>
374
+ {variant === "list" && (
375
+ <div className={cn("text-muted-foreground", sizes.text, "text-xs")}>
376
+ {badge.description}
377
+ </div>
378
+ )}
379
+ <div
380
+ className={cn(
381
+ "inline-flex items-center rounded-full px-2 py-0.5 mt-1",
382
+ sizes.text,
383
+ "text-[10px] font-medium uppercase tracking-wider",
384
+ config.bg,
385
+ config.text
386
+ )}
387
+ >
388
+ {config.label}
389
+ </div>
390
+ {hasProgress && (
391
+ <div className={cn("text-muted-foreground mt-1", "text-xs")}>
392
+ {Math.round(badge.progress!)}% complete
393
+ </div>
394
+ )}
395
+ </div>
396
+ )}
397
+
398
+ {/* Hover tooltip for grid variant */}
399
+ {variant === "grid" && isHovered && (
400
+ <div
401
+ className={cn(
402
+ "absolute left-1/2 top-full z-30 mt-2 -translate-x-1/2",
403
+ "whitespace-nowrap rounded-lg bg-popover px-3 py-2 text-sm shadow-lg border",
404
+ "animate-badge-tooltip"
405
+ )}
406
+ >
407
+ <div className="font-semibold">{badge.name}</div>
408
+ <div className="text-xs text-muted-foreground max-w-[200px] whitespace-normal">
409
+ {badge.description}
410
+ </div>
411
+ <div
412
+ className={cn(
413
+ "inline-flex items-center rounded-full px-2 py-0.5 mt-1",
414
+ "text-[10px] font-medium uppercase tracking-wider",
415
+ config.bg,
416
+ config.text
417
+ )}
418
+ >
419
+ {config.label}
420
+ </div>
421
+ {badge.unlockedAt && !isLocked && (
422
+ <div className="text-xs text-muted-foreground mt-1">
423
+ Unlocked: {badge.unlockedAt.toLocaleDateString()}
424
+ </div>
425
+ )}
426
+ {hasProgress && (
427
+ <div className="text-xs text-muted-foreground mt-1">
428
+ Progress: {Math.round(badge.progress!)}%
429
+ </div>
430
+ )}
431
+ <style>{`
432
+ @keyframes badge-tooltip {
433
+ from { opacity: 0; transform: translate(-50%, 10px); }
434
+ to { opacity: 1; transform: translate(-50%, 0); }
435
+ }
436
+ .animate-badge-tooltip {
437
+ animation: badge-tooltip 0.2s ease-out;
438
+ }
439
+ `}</style>
440
+ </div>
441
+ )}
442
+ </div>
443
+ )
444
+ }
445
+
446
+ // ============================================================================
447
+ // Category Header Component
448
+ // ============================================================================
449
+
450
+ function CategoryHeader({
451
+ category,
452
+ count,
453
+ unlockedCount,
454
+ }: {
455
+ category: string
456
+ count: number
457
+ unlockedCount: number
458
+ }) {
459
+ return (
460
+ <div className="flex items-center justify-between mb-4">
461
+ <h3 className="text-lg font-semibold capitalize">{category}</h3>
462
+ <span className="text-sm text-muted-foreground">
463
+ {unlockedCount}/{count}
464
+ </span>
465
+ </div>
466
+ )
467
+ }
468
+
469
+ // ============================================================================
470
+ // Main Component
471
+ // ============================================================================
472
+
473
+ export function WakaBadgeShowcase({
474
+ badges,
475
+ onBadgeClick,
476
+ variant = "grid",
477
+ showLocked = true,
478
+ showProgress = true,
479
+ columns = 4,
480
+ size = "md",
481
+ className,
482
+ }: WakaBadgeShowcaseProps) {
483
+ // Filter badges based on showLocked
484
+ const displayBadges = showLocked ? badges : badges.filter((b) => b.unlocked)
485
+
486
+ // Group badges by category if categories exist
487
+ const categories = React.useMemo(() => {
488
+ const grouped: Record<string, Badge[]> = {}
489
+ displayBadges.forEach((badge) => {
490
+ const cat = badge.category || "uncategorized"
491
+ if (!grouped[cat]) grouped[cat] = []
492
+ grouped[cat].push(badge)
493
+ })
494
+ return grouped
495
+ }, [displayBadges])
496
+
497
+ const hasCategories = Object.keys(categories).length > 1 || !categories["uncategorized"]
498
+
499
+ // Grid column classes
500
+ const getGridCols = () => {
501
+ switch (columns) {
502
+ case 1:
503
+ return "grid-cols-1"
504
+ case 2:
505
+ return "grid-cols-2"
506
+ case 3:
507
+ return "grid-cols-3"
508
+ case 4:
509
+ return "grid-cols-4"
510
+ case 5:
511
+ return "grid-cols-5"
512
+ case 6:
513
+ return "grid-cols-6"
514
+ default:
515
+ return "grid-cols-4"
516
+ }
517
+ }
518
+
519
+ // Render badges without categories
520
+ const renderBadges = (badgesToRender: Badge[]) => {
521
+ if (variant === "list") {
522
+ return (
523
+ <div className="flex flex-col gap-4">
524
+ {badgesToRender.map((badge) => (
525
+ <BadgeItem
526
+ key={badge.id}
527
+ badge={badge}
528
+ size={size}
529
+ showProgress={showProgress}
530
+ onClick={onBadgeClick ? () => onBadgeClick(badge.id) : undefined}
531
+ variant={variant}
532
+ />
533
+ ))}
534
+ </div>
535
+ )
536
+ }
537
+
538
+ if (variant === "showcase") {
539
+ return (
540
+ <div className="flex flex-wrap justify-center gap-8">
541
+ {badgesToRender.map((badge) => (
542
+ <BadgeItem
543
+ key={badge.id}
544
+ badge={badge}
545
+ size={size}
546
+ showProgress={showProgress}
547
+ onClick={onBadgeClick ? () => onBadgeClick(badge.id) : undefined}
548
+ variant={variant}
549
+ />
550
+ ))}
551
+ </div>
552
+ )
553
+ }
554
+
555
+ // Grid variant (default)
556
+ return (
557
+ <div className={cn("grid gap-6", getGridCols())}>
558
+ {badgesToRender.map((badge) => (
559
+ <div key={badge.id} className="flex justify-center">
560
+ <BadgeItem
561
+ badge={badge}
562
+ size={size}
563
+ showProgress={showProgress}
564
+ onClick={onBadgeClick ? () => onBadgeClick(badge.id) : undefined}
565
+ variant={variant}
566
+ />
567
+ </div>
568
+ ))}
569
+ </div>
570
+ )
571
+ }
572
+
573
+ return (
574
+ <div className={cn("relative", className)}>
575
+ {hasCategories ? (
576
+ <div className="space-y-8">
577
+ {Object.entries(categories).map(([category, categoryBadges]) => (
578
+ <div key={category}>
579
+ <CategoryHeader
580
+ category={category}
581
+ count={categoryBadges.length}
582
+ unlockedCount={categoryBadges.filter((b) => b.unlocked).length}
583
+ />
584
+ {renderBadges(categoryBadges)}
585
+ </div>
586
+ ))}
587
+ </div>
588
+ ) : (
589
+ renderBadges(displayBadges)
590
+ )}
591
+
592
+ {/* Global animations */}
593
+ <style>{`
594
+ @keyframes legendary-shimmer {
595
+ 0% { background-position: 0% 50%; }
596
+ 50% { background-position: 100% 50%; }
597
+ 100% { background-position: 0% 50%; }
598
+ }
599
+ `}</style>
600
+ </div>
601
+ )
602
+ }
603
+
604
+ // ============================================================================
605
+ // Preset Showcase Variants
606
+ // ============================================================================
607
+
608
+ export function WakaBadgeShowcaseCompact(
609
+ props: Omit<WakaBadgeShowcaseProps, "variant" | "size" | "columns">
610
+ ) {
611
+ return <WakaBadgeShowcase variant="grid" size="sm" columns={6} {...props} />
612
+ }
613
+
614
+ export function WakaBadgeShowcaseList(
615
+ props: Omit<WakaBadgeShowcaseProps, "variant">
616
+ ) {
617
+ return <WakaBadgeShowcase variant="list" {...props} />
618
+ }
619
+
620
+ export function WakaBadgeShowcaseHero(
621
+ props: Omit<WakaBadgeShowcaseProps, "variant" | "size">
622
+ ) {
623
+ return <WakaBadgeShowcase variant="showcase" size="lg" {...props} />
624
+ }
625
+
626
+ // ============================================================================
627
+ // Badge Stats Component
628
+ // ============================================================================
629
+
630
+ export interface WakaBadgeStatsProps {
631
+ badges: Badge[]
632
+ className?: string
633
+ }
634
+
635
+ export function WakaBadgeStats({ badges, className }: WakaBadgeStatsProps) {
636
+ const stats = React.useMemo(() => {
637
+ const total = badges.length
638
+ const unlocked = badges.filter((b) => b.unlocked).length
639
+ const byRarity = {
640
+ common: badges.filter((b) => b.rarity === "common"),
641
+ rare: badges.filter((b) => b.rarity === "rare"),
642
+ epic: badges.filter((b) => b.rarity === "epic"),
643
+ legendary: badges.filter((b) => b.rarity === "legendary"),
644
+ }
645
+
646
+ return {
647
+ total,
648
+ unlocked,
649
+ percentage: Math.round((unlocked / total) * 100),
650
+ byRarity,
651
+ }
652
+ }, [badges])
653
+
654
+ return (
655
+ <div
656
+ className={cn(
657
+ "flex flex-wrap gap-4 p-4 rounded-lg bg-muted/50 border",
658
+ className
659
+ )}
660
+ >
661
+ <div className="flex-1 min-w-[100px]">
662
+ <div className="text-2xl font-bold">
663
+ {stats.unlocked}/{stats.total}
664
+ </div>
665
+ <div className="text-sm text-muted-foreground">Badges Unlocked</div>
666
+ <div className="mt-2 h-2 rounded-full bg-muted overflow-hidden">
667
+ <div
668
+ className="h-full bg-primary rounded-full transition-all duration-500"
669
+ style={{ width: `${stats.percentage}%` }}
670
+ />
671
+ </div>
672
+ </div>
673
+
674
+ {(["common", "rare", "epic", "legendary"] as const).map((rarity) => {
675
+ const rarityBadges = stats.byRarity[rarity]
676
+ const unlockedCount = rarityBadges.filter((b) => b.unlocked).length
677
+ const config = rarityConfig[rarity]
678
+
679
+ return (
680
+ <div key={rarity} className="flex items-center gap-2">
681
+ <div
682
+ className={cn(
683
+ "h-8 w-8 rounded-full bg-gradient-to-br flex items-center justify-center",
684
+ config.gradient
685
+ )}
686
+ >
687
+ <span className="text-white text-xs font-bold">
688
+ {unlockedCount}
689
+ </span>
690
+ </div>
691
+ <div>
692
+ <div className={cn("text-xs font-medium", config.text)}>
693
+ {config.label}
694
+ </div>
695
+ <div className="text-xs text-muted-foreground">
696
+ of {rarityBadges.length}
697
+ </div>
698
+ </div>
699
+ </div>
700
+ )
701
+ })}
702
+ </div>
703
+ )
704
+ }