@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,713 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils"
5
+ import { Button } from "../../components/button"
6
+ import { Sheet, SheetContent, SheetTitle, SheetDescription } from "../../components/sheet"
7
+ import { Avatar, AvatarFallback, AvatarImage } from "../../components/avatar"
8
+ import { ScrollArea } from "../../components/scroll-area"
9
+ import {
10
+ Collapsible,
11
+ CollapsibleContent,
12
+ CollapsibleTrigger,
13
+ } from "../../components/collapsible"
14
+ import {
15
+ DropdownMenu,
16
+ DropdownMenuContent,
17
+ DropdownMenuItem,
18
+ DropdownMenuSeparator,
19
+ DropdownMenuTrigger,
20
+ } from "../../components/dropdown-menu"
21
+ import { Menu, ChevronDown, LogOut, Settings, User } from "lucide-react"
22
+
23
+ // ============ TYPES ============
24
+
25
+ export interface SidebarMenuItem {
26
+ /** Identifiant unique */
27
+ id: string
28
+ /** Label du menu */
29
+ label: string
30
+ /** Icône (composant React) */
31
+ icon?: React.ReactNode
32
+ /** URL de navigation */
33
+ href?: string
34
+ /** Callback au clic */
35
+ onClick?: () => void
36
+ /** Sous-menus */
37
+ children?: SidebarMenuItem[]
38
+ /** Menu actif */
39
+ active?: boolean
40
+ /** Badge/compteur */
41
+ badge?: string | number
42
+ /** Désactivé */
43
+ disabled?: boolean
44
+ }
45
+
46
+ export interface SidebarUserConfig {
47
+ /** Nom de l'utilisateur */
48
+ name: string
49
+ /** Email ou sous-titre */
50
+ email?: string
51
+ /** URL de l'avatar */
52
+ avatarUrl?: string
53
+ /** Initiales pour le fallback */
54
+ initials?: string
55
+ /** Actions du menu utilisateur */
56
+ actions?: {
57
+ id: string
58
+ label: string
59
+ icon?: React.ReactNode
60
+ onClick?: () => void
61
+ href?: string
62
+ variant?: "default" | "destructive"
63
+ }[]
64
+ }
65
+
66
+ export interface SidebarLogoConfig {
67
+ /** Image du logo */
68
+ src?: string
69
+ /** Texte alternatif */
70
+ alt?: string
71
+ /** Texte à côté du logo */
72
+ title?: string
73
+ /** URL au clic sur le logo */
74
+ href?: string
75
+ /** Callback au clic */
76
+ onClick?: () => void
77
+ /** Composant logo personnalisé */
78
+ component?: React.ReactNode
79
+ }
80
+
81
+ export interface WakaSidebarProps {
82
+ /** Configuration du logo */
83
+ logo?: SidebarLogoConfig
84
+ /** Éléments du menu */
85
+ menu: SidebarMenuItem[]
86
+ /** Configuration utilisateur */
87
+ user?: SidebarUserConfig
88
+ /** Largeur de la sidebar (desktop) */
89
+ width?: number
90
+ /** Breakpoint pour le mode mobile (en px) */
91
+ mobileBreakpoint?: number
92
+ /** Sidebar ouverte (mode mobile) */
93
+ open?: boolean
94
+ /** Callback changement d'état (mode mobile) */
95
+ onOpenChange?: (open: boolean) => void
96
+ /** Classes CSS additionnelles */
97
+ className?: string
98
+ /** Classes CSS du contenu */
99
+ contentClassName?: string
100
+ /** Couleur de fond */
101
+ backgroundColor?: string
102
+ /** Couleur du texte */
103
+ textColor?: string
104
+ /** Couleur de l'élément actif */
105
+ activeColor?: string
106
+ /** Couleur de survol */
107
+ hoverColor?: string
108
+ /** Position du menu utilisateur */
109
+ userPosition?: "top" | "bottom"
110
+ /** Afficher le bouton hamburger */
111
+ showHamburger?: boolean
112
+ /** Position du bouton hamburger (pour usage externe) */
113
+ hamburgerPosition?: "left" | "right"
114
+ /** Rendu personnalisé d'un item */
115
+ renderItem?: (item: SidebarMenuItem, isChild: boolean) => React.ReactNode
116
+ /** Footer personnalisé */
117
+ footer?: React.ReactNode
118
+ /** Header personnalisé (après le logo) */
119
+ header?: React.ReactNode
120
+ /** Mode de positionnement : "fixed" pour app layout, "relative" pour preview/demo */
121
+ position?: "fixed" | "relative"
122
+ }
123
+
124
+ // ============ CONTEXT ============
125
+
126
+ interface SidebarContextValue {
127
+ activeId: string | null
128
+ setActiveId: (id: string | null) => void
129
+ expandedIds: string[]
130
+ toggleExpanded: (id: string) => void
131
+ isMobile: boolean
132
+ closeOnNavigate: boolean
133
+ onClose: () => void
134
+ }
135
+
136
+ const SidebarContext = React.createContext<SidebarContextValue | null>(null)
137
+
138
+ function useSidebarContext() {
139
+ const context = React.useContext(SidebarContext)
140
+ if (!context) {
141
+ throw new Error("Sidebar components must be used within WakaSidebar")
142
+ }
143
+ return context
144
+ }
145
+
146
+ // ============ COMPOSANTS INTERNES ============
147
+
148
+ /**
149
+ * Logo de la sidebar
150
+ */
151
+ function SidebarLogo({ config }: { config: SidebarLogoConfig }) {
152
+ const content = config.component ? (
153
+ config.component
154
+ ) : (
155
+ <div className="flex items-center gap-3">
156
+ {config.src && (
157
+ <img
158
+ src={config.src}
159
+ alt={config.alt || "Logo"}
160
+ className="h-8 w-auto object-contain"
161
+ />
162
+ )}
163
+ {config.title && (
164
+ <span className="text-lg font-bold tracking-wide">{config.title}</span>
165
+ )}
166
+ </div>
167
+ )
168
+
169
+ if (config.href) {
170
+ return (
171
+ <a href={config.href} className="block" onClick={config.onClick}>
172
+ {content}
173
+ </a>
174
+ )
175
+ }
176
+
177
+ if (config.onClick) {
178
+ return (
179
+ <button type="button" onClick={config.onClick} className="block text-left">
180
+ {content}
181
+ </button>
182
+ )
183
+ }
184
+
185
+ return <div>{content}</div>
186
+ }
187
+
188
+ /**
189
+ * Item de menu avec sous-menus collapsibles
190
+ */
191
+ function SidebarMenuItemComponent({
192
+ item,
193
+ isChild = false,
194
+ renderItem,
195
+ }: {
196
+ item: SidebarMenuItem
197
+ isChild?: boolean
198
+ renderItem?: (item: SidebarMenuItem, isChild: boolean) => React.ReactNode
199
+ }) {
200
+ const { activeId, setActiveId, expandedIds, toggleExpanded, isMobile, closeOnNavigate, onClose } =
201
+ useSidebarContext()
202
+
203
+ const hasChildren = item.children && item.children.length > 0
204
+ const isExpanded = expandedIds.includes(item.id)
205
+ const isActive = activeId === item.id || item.active
206
+
207
+ const handleClick = () => {
208
+ if (item.disabled) return
209
+
210
+ if (hasChildren) {
211
+ toggleExpanded(item.id)
212
+ } else {
213
+ setActiveId(item.id)
214
+ item.onClick?.()
215
+ if (isMobile && closeOnNavigate) {
216
+ onClose()
217
+ }
218
+ }
219
+ }
220
+
221
+ // Rendu personnalisé
222
+ if (renderItem) {
223
+ return <>{renderItem(item, isChild)}</>
224
+ }
225
+
226
+ const itemContent = (
227
+ <div
228
+ className={cn(
229
+ "flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200",
230
+ "hover:bg-white/10",
231
+ isActive && !hasChildren && "bg-sidebar-active text-sidebar-active-foreground",
232
+ isChild && "ml-6 text-[13px]",
233
+ item.disabled && "opacity-50 cursor-not-allowed"
234
+ )}
235
+ >
236
+ {item.icon && (
237
+ <span className={cn("flex-shrink-0", isChild ? "h-4 w-4" : "h-5 w-5")}>
238
+ {item.icon}
239
+ </span>
240
+ )}
241
+ <span className="flex-1 truncate">{item.label}</span>
242
+ {item.badge !== undefined && (
243
+ <span className="ml-auto flex h-5 min-w-[20px] items-center justify-center rounded-full bg-primary px-1.5 text-[10px] font-semibold text-primary-foreground">
244
+ {item.badge}
245
+ </span>
246
+ )}
247
+ {hasChildren && (
248
+ <ChevronDown
249
+ className={cn(
250
+ "h-4 w-4 transition-transform duration-200",
251
+ isExpanded && "rotate-180"
252
+ )}
253
+ />
254
+ )}
255
+ </div>
256
+ )
257
+
258
+ // Avec sous-menus
259
+ if (hasChildren) {
260
+ return (
261
+ <Collapsible open={isExpanded} onOpenChange={() => toggleExpanded(item.id)}>
262
+ <CollapsibleTrigger asChild>
263
+ <button
264
+ type="button"
265
+ className="w-full text-left"
266
+ disabled={item.disabled}
267
+ >
268
+ {itemContent}
269
+ </button>
270
+ </CollapsibleTrigger>
271
+ <CollapsibleContent className="space-y-1 pt-1">
272
+ {item.children?.map((child) => (
273
+ <SidebarMenuItemComponent
274
+ key={child.id}
275
+ item={child}
276
+ isChild
277
+ renderItem={renderItem}
278
+ />
279
+ ))}
280
+ </CollapsibleContent>
281
+ </Collapsible>
282
+ )
283
+ }
284
+
285
+ // Lien ou bouton simple
286
+ if (item.href) {
287
+ return (
288
+ <a
289
+ href={item.href}
290
+ onClick={(e) => {
291
+ if (item.disabled) {
292
+ e.preventDefault()
293
+ return
294
+ }
295
+ handleClick()
296
+ }}
297
+ className="block"
298
+ >
299
+ {itemContent}
300
+ </a>
301
+ )
302
+ }
303
+
304
+ return (
305
+ <button
306
+ type="button"
307
+ className="w-full text-left"
308
+ disabled={item.disabled}
309
+ onClick={handleClick}
310
+ >
311
+ {itemContent}
312
+ </button>
313
+ )
314
+ }
315
+
316
+ /**
317
+ * Menu utilisateur
318
+ */
319
+ function SidebarUser({ config }: { config: SidebarUserConfig }) {
320
+ const initials =
321
+ config.initials ||
322
+ config.name
323
+ .split(" ")
324
+ .map((n) => n[0])
325
+ .join("")
326
+ .slice(0, 2)
327
+ .toUpperCase()
328
+
329
+ const defaultActions: SidebarUserConfig["actions"] = [
330
+ { id: "profile", label: "Profil", icon: <User className="h-4 w-4" /> },
331
+ { id: "settings", label: "Paramètres", icon: <Settings className="h-4 w-4" /> },
332
+ { id: "logout", label: "Déconnexion", icon: <LogOut className="h-4 w-4" />, variant: "destructive" },
333
+ ]
334
+
335
+ const actions = config.actions ?? defaultActions
336
+
337
+ return (
338
+ <DropdownMenu>
339
+ <DropdownMenuTrigger asChild>
340
+ <button
341
+ type="button"
342
+ className="flex w-full items-center gap-3 rounded-lg p-2 text-left transition-colors hover:bg-white/10"
343
+ >
344
+ <Avatar className="h-10 w-10 border-2 border-sidebar-active">
345
+ <AvatarImage src={config.avatarUrl} alt={config.name} />
346
+ <AvatarFallback className="bg-sidebar-active text-sidebar-active-foreground text-sm font-medium">
347
+ {initials}
348
+ </AvatarFallback>
349
+ </Avatar>
350
+ <div className="flex-1 min-w-0">
351
+ <p className="text-sm font-medium truncate">{config.name}</p>
352
+ {config.email && (
353
+ <p className="text-xs text-sidebar-foreground/70 truncate">{config.email}</p>
354
+ )}
355
+ </div>
356
+ <ChevronDown className="h-4 w-4 flex-shrink-0 opacity-70" />
357
+ </button>
358
+ </DropdownMenuTrigger>
359
+ <DropdownMenuContent align="end" className="w-56">
360
+ {actions.map((action, index) => (
361
+ <React.Fragment key={action.id}>
362
+ {action.variant === "destructive" && index > 0 && <DropdownMenuSeparator />}
363
+ <DropdownMenuItem
364
+ onClick={action.onClick}
365
+ className={cn(action.variant === "destructive" && "text-destructive focus:text-destructive")}
366
+ asChild={!!action.href}
367
+ >
368
+ {action.href ? (
369
+ <a href={action.href} className="flex items-center gap-2">
370
+ {action.icon}
371
+ {action.label}
372
+ </a>
373
+ ) : (
374
+ <span className="flex items-center gap-2">
375
+ {action.icon}
376
+ {action.label}
377
+ </span>
378
+ )}
379
+ </DropdownMenuItem>
380
+ </React.Fragment>
381
+ ))}
382
+ </DropdownMenuContent>
383
+ </DropdownMenu>
384
+ )
385
+ }
386
+
387
+ /**
388
+ * Contenu de la sidebar
389
+ */
390
+ function SidebarContent({
391
+ logo,
392
+ menu,
393
+ user,
394
+ userPosition = "bottom",
395
+ header,
396
+ footer,
397
+ renderItem,
398
+ }: Pick<WakaSidebarProps, "logo" | "menu" | "user" | "userPosition" | "header" | "footer" | "renderItem">) {
399
+ return (
400
+ <div className="flex h-full flex-col">
401
+ {/* Logo */}
402
+ {logo && (
403
+ <div className="flex-shrink-0 p-4 pb-2">
404
+ <SidebarLogo config={logo} />
405
+ </div>
406
+ )}
407
+
408
+ {/* Header personnalisé */}
409
+ {header && <div className="flex-shrink-0 px-4 py-2">{header}</div>}
410
+
411
+ {/* User en haut */}
412
+ {user && userPosition === "top" && (
413
+ <div className="flex-shrink-0 px-3 py-2 border-b border-white/10">
414
+ <SidebarUser config={user} />
415
+ </div>
416
+ )}
417
+
418
+ {/* Menu scrollable */}
419
+ <ScrollArea className="flex-1 px-3 py-4">
420
+ <nav className="space-y-1">
421
+ {menu.map((item) => (
422
+ <SidebarMenuItemComponent key={item.id} item={item} renderItem={renderItem} />
423
+ ))}
424
+ </nav>
425
+ </ScrollArea>
426
+
427
+ {/* Footer personnalisé */}
428
+ {footer && <div className="flex-shrink-0 px-4 py-2 border-t border-white/10">{footer}</div>}
429
+
430
+ {/* User en bas */}
431
+ {user && userPosition === "bottom" && (
432
+ <div className="flex-shrink-0 p-3 border-t border-white/10">
433
+ <SidebarUser config={user} />
434
+ </div>
435
+ )}
436
+ </div>
437
+ )
438
+ }
439
+
440
+ // ============ COMPOSANT PRINCIPAL ============
441
+
442
+ /**
443
+ * WakaSidebar - Sidebar personnalisable avec menu hamburger responsive
444
+ *
445
+ * @example
446
+ * ```tsx
447
+ * <WakaSidebar
448
+ * logo={{ src: "/logo.svg", title: "WAKASTART" }}
449
+ * menu={[
450
+ * { id: "dashboard", label: "Tableau de bord", icon: <Home /> },
451
+ * {
452
+ * id: "admin",
453
+ * label: "Administration",
454
+ * icon: <Settings />,
455
+ * children: [
456
+ * { id: "users", label: "Utilisateurs" },
457
+ * { id: "roles", label: "Rôles" },
458
+ * ],
459
+ * },
460
+ * ]}
461
+ * user={{
462
+ * name: "John Doe",
463
+ * email: "john@example.com",
464
+ * avatarUrl: "/avatar.jpg",
465
+ * }}
466
+ * />
467
+ * ```
468
+ */
469
+ export function WakaSidebar({
470
+ logo,
471
+ menu,
472
+ user,
473
+ width = 260,
474
+ mobileBreakpoint = 768,
475
+ open,
476
+ onOpenChange,
477
+ className,
478
+ contentClassName,
479
+ backgroundColor,
480
+ textColor,
481
+ activeColor,
482
+ hoverColor,
483
+ userPosition = "bottom",
484
+ showHamburger = true,
485
+ hamburgerPosition = "left",
486
+ renderItem,
487
+ footer,
488
+ header,
489
+ position = "fixed",
490
+ }: WakaSidebarProps) {
491
+ // État interne pour mobile si non contrôlé
492
+ const [internalOpen, setInternalOpen] = React.useState(false)
493
+ const isControlled = open !== undefined
494
+ const isOpen = isControlled ? open : internalOpen
495
+ const setIsOpen = isControlled ? onOpenChange! : setInternalOpen
496
+
497
+ // Détection mobile
498
+ const [isMobile, setIsMobile] = React.useState(false)
499
+
500
+ React.useEffect(() => {
501
+ const checkMobile = () => {
502
+ setIsMobile(window.innerWidth < mobileBreakpoint)
503
+ }
504
+
505
+ checkMobile()
506
+ window.addEventListener("resize", checkMobile)
507
+ return () => window.removeEventListener("resize", checkMobile)
508
+ }, [mobileBreakpoint])
509
+
510
+ // État des menus
511
+ const [activeId, setActiveId] = React.useState<string | null>(() => {
512
+ // Trouver l'élément actif par défaut
513
+ const findActive = (items: SidebarMenuItem[]): string | null => {
514
+ for (const item of items) {
515
+ if (item.active) return item.id
516
+ if (item.children) {
517
+ const childActive = findActive(item.children)
518
+ if (childActive) return childActive
519
+ }
520
+ }
521
+ return null
522
+ }
523
+ return findActive(menu)
524
+ })
525
+
526
+ const [expandedIds, setExpandedIds] = React.useState<string[]>(() => {
527
+ // Ouvrir les parents des éléments actifs
528
+ const findParentsOfActive = (items: SidebarMenuItem[], parentIds: string[] = []): string[] => {
529
+ for (const item of items) {
530
+ if (item.active) return parentIds
531
+ if (item.children) {
532
+ const result = findParentsOfActive(item.children, [...parentIds, item.id])
533
+ if (result.length > parentIds.length) return result
534
+ }
535
+ }
536
+ return []
537
+ }
538
+ return findParentsOfActive(menu)
539
+ })
540
+
541
+ const toggleExpanded = React.useCallback((id: string) => {
542
+ setExpandedIds((prev) =>
543
+ prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
544
+ )
545
+ }, [])
546
+
547
+ const contextValue: SidebarContextValue = {
548
+ activeId,
549
+ setActiveId,
550
+ expandedIds,
551
+ toggleExpanded,
552
+ isMobile,
553
+ closeOnNavigate: true,
554
+ onClose: () => setIsOpen(false),
555
+ }
556
+
557
+ // Styles personnalisés
558
+ const customStyles = {
559
+ "--sidebar-bg": backgroundColor || "hsl(222 47% 11%)",
560
+ "--sidebar-text": textColor || "hsl(210 40% 96%)",
561
+ "--sidebar-active": activeColor || "hsl(187 85% 43%)",
562
+ "--sidebar-active-foreground": "hsl(222 47% 11%)",
563
+ "--sidebar-hover": hoverColor || "rgba(255, 255, 255, 0.1)",
564
+ } as React.CSSProperties
565
+
566
+ const sidebarClasses = cn(
567
+ "bg-[var(--sidebar-bg)] text-[var(--sidebar-text)]",
568
+ "[&_.bg-sidebar-active]:bg-[var(--sidebar-active)]",
569
+ "[&_.text-sidebar-active-foreground]:text-[var(--sidebar-active-foreground)]",
570
+ "[&_.text-sidebar-foreground\\/70]:text-[var(--sidebar-text)]/70",
571
+ "[&_.border-sidebar-active]:border-[var(--sidebar-active)]",
572
+ contentClassName
573
+ )
574
+
575
+ // Contenu de la sidebar
576
+ const sidebarContent = (
577
+ <SidebarContext.Provider value={contextValue}>
578
+ <div className={cn("h-full", sidebarClasses)} style={customStyles}>
579
+ <SidebarContent
580
+ logo={logo}
581
+ menu={menu}
582
+ user={user}
583
+ userPosition={userPosition}
584
+ header={header}
585
+ footer={footer}
586
+ renderItem={renderItem}
587
+ />
588
+ </div>
589
+ </SidebarContext.Provider>
590
+ )
591
+
592
+ return (
593
+ <>
594
+ {/* Bouton hamburger (pour mobile) */}
595
+ {showHamburger && isMobile && (
596
+ <Button
597
+ variant="ghost"
598
+ size="icon"
599
+ className={cn(
600
+ "fixed top-3 z-50",
601
+ hamburgerPosition === "left" ? "left-3" : "right-3"
602
+ )}
603
+ onClick={() => setIsOpen(true)}
604
+ >
605
+ <Menu className="h-6 w-6" />
606
+ <span className="sr-only">Ouvrir le menu</span>
607
+ </Button>
608
+ )}
609
+
610
+ {/* Mode Desktop */}
611
+ {!isMobile && (
612
+ <aside
613
+ className={cn(
614
+ position === "fixed" ? "fixed left-0 top-0 h-screen z-40" : "relative h-full",
615
+ className
616
+ )}
617
+ style={{ width }}
618
+ >
619
+ {sidebarContent}
620
+ </aside>
621
+ )}
622
+
623
+ {/* Mode Mobile (Sheet) */}
624
+ {isMobile && (
625
+ <Sheet open={isOpen} onOpenChange={setIsOpen}>
626
+ <SheetContent
627
+ side="left"
628
+ className={cn("w-[280px] p-0 border-0", className)}
629
+ style={customStyles}
630
+ aria-describedby="sidebar-description"
631
+ >
632
+ <SheetTitle className="sr-only">Menu de navigation</SheetTitle>
633
+ <SheetDescription id="sidebar-description" className="sr-only">
634
+ Menu principal de l&apos;application
635
+ </SheetDescription>
636
+ {sidebarContent}
637
+ </SheetContent>
638
+ </Sheet>
639
+ )}
640
+ </>
641
+ )
642
+ }
643
+
644
+ // ============ HOOK UTILITAIRE ============
645
+
646
+ /**
647
+ * Hook pour gérer l'état de la sidebar externalement
648
+ */
649
+ export function useSidebar() {
650
+ const [isOpen, setIsOpen] = React.useState(false)
651
+
652
+ const open = React.useCallback(() => setIsOpen(true), [])
653
+ const close = React.useCallback(() => setIsOpen(false), [])
654
+ const toggle = React.useCallback(() => setIsOpen((prev) => !prev), [])
655
+
656
+ return {
657
+ isOpen,
658
+ setIsOpen,
659
+ open,
660
+ close,
661
+ toggle,
662
+ }
663
+ }
664
+
665
+ // ============ COMPOSANT LAYOUT AVEC SIDEBAR ============
666
+
667
+ export interface SidebarLayoutProps {
668
+ /** Configuration de la sidebar */
669
+ sidebar: WakaSidebarProps
670
+ /** Contenu principal */
671
+ children: React.ReactNode
672
+ /** Largeur de la sidebar (override) */
673
+ sidebarWidth?: number
674
+ /** Classes CSS du conteneur */
675
+ className?: string
676
+ /** Classes CSS du contenu principal */
677
+ contentClassName?: string
678
+ }
679
+
680
+ /**
681
+ * Layout avec sidebar intégrée
682
+ */
683
+ export function SidebarLayout({
684
+ sidebar,
685
+ children,
686
+ sidebarWidth = 260,
687
+ className,
688
+ contentClassName,
689
+ }: SidebarLayoutProps) {
690
+ const [isMobile, setIsMobile] = React.useState(false)
691
+ const breakpoint = sidebar.mobileBreakpoint || 768
692
+
693
+ React.useEffect(() => {
694
+ const checkMobile = () => setIsMobile(window.innerWidth < breakpoint)
695
+ checkMobile()
696
+ window.addEventListener("resize", checkMobile)
697
+ return () => window.removeEventListener("resize", checkMobile)
698
+ }, [breakpoint])
699
+
700
+ return (
701
+ <div className={cn("min-h-screen bg-background", className)}>
702
+ <WakaSidebar {...sidebar} width={sidebarWidth} />
703
+ <main
704
+ className={cn("min-h-screen transition-all duration-300", contentClassName)}
705
+ style={{ marginLeft: isMobile ? 0 : sidebarWidth }}
706
+ >
707
+ {children}
708
+ </main>
709
+ </div>
710
+ )
711
+ }
712
+
713
+ export default WakaSidebar