@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,793 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import DatePicker, { registerLocale } from "react-datepicker"
5
+ import {
6
+ format,
7
+ parse,
8
+ startOfDay,
9
+ endOfDay,
10
+ startOfWeek,
11
+ endOfWeek,
12
+ startOfMonth,
13
+ endOfMonth,
14
+ startOfYear,
15
+ endOfYear,
16
+ subDays,
17
+ subWeeks,
18
+ subMonths,
19
+ addDays,
20
+ addWeeks,
21
+ addMonths,
22
+ isAfter,
23
+ isBefore,
24
+ isEqual,
25
+ isWithinInterval,
26
+ setHours,
27
+ setMinutes,
28
+ setSeconds,
29
+ getHours,
30
+ getMinutes,
31
+ getSeconds,
32
+ } from "date-fns"
33
+ import { fr, enUS, es, de, it, pt, ja, zhCN } from "date-fns/locale"
34
+ import {
35
+ Calendar as CalendarIcon,
36
+ Clock,
37
+ X,
38
+ Check,
39
+ ChevronLeft,
40
+ ChevronRight,
41
+ ChevronsLeft,
42
+ ChevronsRight,
43
+ Plus,
44
+ Minus,
45
+ AlertCircle,
46
+ Info,
47
+ } from "lucide-react"
48
+ import { cn } from "../../utils/cn"
49
+ import { Button } from "../button"
50
+ import { Input } from "../input"
51
+ import { Label } from "../label"
52
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover"
53
+ import { Badge } from "../badge"
54
+ import { Separator } from "../separator"
55
+ import { ScrollArea } from "../scroll-area"
56
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../tooltip"
57
+ import { useTranslation } from "../../hooks/use-translation"
58
+
59
+ // Enregistrer les locales
60
+ registerLocale('fr', fr)
61
+ registerLocale('en', enUS)
62
+ registerLocale('es', es)
63
+ registerLocale('de', de)
64
+ registerLocale('it', it)
65
+ registerLocale('pt', pt)
66
+ registerLocale('ja', ja)
67
+ registerLocale('zh', zhCN)
68
+
69
+ /**
70
+ * Configuration des presets de dates rapides
71
+ */
72
+ export interface DatePreset {
73
+ label: string
74
+ value?: Date | (() => Date)
75
+ range?: { from: Date; to: Date } | (() => { from: Date; to: Date })
76
+ }
77
+
78
+ /**
79
+ * Configuration de validation
80
+ */
81
+ export interface DateValidation {
82
+ /** Validation personnalisée */
83
+ validate?: (date: Date) => boolean | string
84
+ /** Message d'erreur personnalisé */
85
+ message?: string
86
+ /** Dates désactivées (patterns) */
87
+ disabledDates?: Date[]
88
+ /** Fonction pour désactiver des dates */
89
+ disabledDatesFn?: (date: Date) => boolean
90
+ /** Jours de la semaine désactivés (0-6, 0 = Dimanche) */
91
+ disabledDaysOfWeek?: number[]
92
+ /** Dates highlightées */
93
+ highlightedDates?: Date[]
94
+ /** Fonction pour highlighter des dates */
95
+ highlightedDatesFn?: (date: Date) => boolean
96
+ }
97
+
98
+ /**
99
+ * Range de dates
100
+ */
101
+ export interface DateRange {
102
+ from: Date | undefined
103
+ to: Date | undefined
104
+ }
105
+
106
+ /**
107
+ * Props du WakaDateTimePicker
108
+ */
109
+ export interface WakaDateTimePickerProps {
110
+ /** Valeur actuelle (Date simple ou DateRange) */
111
+ value?: Date | DateRange | Date[] | null
112
+ /** Callback de changement */
113
+ onChange?: (date: Date | DateRange | Date[] | null) => void
114
+ /** Placeholder du champ */
115
+ placeholder?: string
116
+ /** Désactiver le picker */
117
+ disabled?: boolean
118
+ /** Classe CSS supplémentaire */
119
+ className?: string
120
+ /** Variant du picker */
121
+ variant?: "date" | "time" | "datetime" | "range" | "multiple"
122
+ /** Format d'affichage personnalisé */
123
+ format?: string
124
+ /** Date minimum */
125
+ minDate?: Date
126
+ /** Date maximum */
127
+ maxDate?: Date
128
+ /** Afficher les secondes */
129
+ showSeconds?: boolean
130
+ /** Afficher le bouton clear */
131
+ showClear?: boolean
132
+ /** Afficher les presets rapides */
133
+ showPresets?: boolean
134
+ /** Presets personnalisés */
135
+ presets?: DatePreset[]
136
+ /** Locale (fr, en, es, etc.) */
137
+ locale?: string
138
+ /** Format d'heure (12h ou 24h) */
139
+ timeFormat?: "12h" | "24h"
140
+ /** Taille du composant */
141
+ size?: "sm" | "md" | "lg"
142
+ /** Message d'erreur */
143
+ error?: string
144
+ /** Label du champ */
145
+ label?: string
146
+ /** Champ requis */
147
+ required?: boolean
148
+ /** Mode inline (sans popover) */
149
+ inline?: boolean
150
+ /** Nombre de mois à afficher */
151
+ monthsShown?: number
152
+ /** Intervalle de temps (en minutes) */
153
+ timeIntervals?: number
154
+ /** Validation */
155
+ validation?: DateValidation
156
+ /** Afficher les numéros de semaine */
157
+ showWeekNumbers?: boolean
158
+ /** Afficher le bouton "Aujourd'hui" */
159
+ showTodayButton?: boolean
160
+ /** Afficher les infos de sélection */
161
+ showSelectionInfo?: boolean
162
+ /** Autoriser la sélection de plage de temps */
163
+ allowTimeRange?: boolean
164
+ /** Timezone (pour affichage avancé) */
165
+ timezone?: string
166
+ /** Callback avant changement (peut annuler) */
167
+ onBeforeChange?: (date: Date | DateRange | Date[] | null) => boolean
168
+ /** Callback après changement */
169
+ onAfterChange?: (date: Date | DateRange | Date[] | null) => void
170
+ /** Render personnalisé pour une date */
171
+ renderDay?: (date: Date) => React.ReactNode
172
+ /** Classe pour le container du popover */
173
+ popoverClassName?: string
174
+ /** Désactiver les animations */
175
+ disableAnimations?: boolean
176
+ /** Mode lecture seule */
177
+ readOnly?: boolean
178
+ /** ID du champ (pour les tests) */
179
+ id?: string
180
+ /** Name du champ (pour les formulaires) */
181
+ name?: string
182
+ /** Auto-focus */
183
+ autoFocus?: boolean
184
+ }
185
+
186
+ /**
187
+ * WakaDateTimePicker
188
+ *
189
+ * Composant datetime picker ultra-complet avec support de :
190
+ * - Date, time, datetime, range, multiple selection
191
+ * - Presets rapides
192
+ * - Validation avancée
193
+ * - I18n complet
194
+ * - Thème dynamique
195
+ * - Keyboard navigation
196
+ * - Accessibilité (ARIA)
197
+ * - Time zones
198
+ * - Custom renders
199
+ */
200
+ export const WakaDateTimePicker = React.forwardRef<HTMLDivElement, WakaDateTimePickerProps>(
201
+ ({
202
+ value,
203
+ onChange,
204
+ placeholder,
205
+ disabled = false,
206
+ className,
207
+ variant = "date",
208
+ format: customFormat,
209
+ minDate,
210
+ maxDate,
211
+ showSeconds = false,
212
+ showClear = true,
213
+ showPresets = false,
214
+ presets: customPresets,
215
+ locale = "fr",
216
+ timeFormat = "24h",
217
+ size = "md",
218
+ error,
219
+ label,
220
+ required = false,
221
+ inline = false,
222
+ monthsShown = 1,
223
+ timeIntervals = 15,
224
+ validation,
225
+ showWeekNumbers = false,
226
+ showTodayButton = true,
227
+ showSelectionInfo = false,
228
+ allowTimeRange = false,
229
+ timezone,
230
+ onBeforeChange,
231
+ onAfterChange,
232
+ renderDay,
233
+ popoverClassName,
234
+ disableAnimations = false,
235
+ readOnly = false,
236
+ id,
237
+ name,
238
+ autoFocus = false,
239
+ }, ref) => {
240
+ const { t } = useTranslation()
241
+ const [open, setOpen] = React.useState(false)
242
+ const [internalValue, setInternalValue] = React.useState<Date | DateRange | Date[] | null>(
243
+ variant === "range" && value && !('from' in value) ? { from: value as Date, to: undefined } : value as any
244
+ )
245
+ const [validationError, setValidationError] = React.useState<string | null>(null)
246
+ const [hoveredDate, setHoveredDate] = React.useState<Date | null>(null)
247
+
248
+ // Locales supportées
249
+ const localeMap: Record<string, any> = {
250
+ fr,
251
+ en: enUS,
252
+ es,
253
+ de,
254
+ it,
255
+ pt,
256
+ ja,
257
+ zh: zhCN,
258
+ }
259
+
260
+ const currentLocale = localeMap[locale] || fr
261
+
262
+ // Presets par défaut
263
+ const defaultPresets: DatePreset[] = [
264
+ {
265
+ label: t?.("today") || "Aujourd'hui",
266
+ value: () => new Date(),
267
+ },
268
+ {
269
+ label: t?.("yesterday") || "Hier",
270
+ value: () => subDays(new Date(), 1),
271
+ },
272
+ {
273
+ label: t?.("thisWeek") || "Cette semaine",
274
+ range: () => ({
275
+ from: startOfWeek(new Date(), { locale: currentLocale }),
276
+ to: endOfWeek(new Date(), { locale: currentLocale }),
277
+ }),
278
+ },
279
+ {
280
+ label: t?.("lastWeek") || "Semaine dernière",
281
+ range: () => ({
282
+ from: startOfWeek(subWeeks(new Date(), 1), { locale: currentLocale }),
283
+ to: endOfWeek(subWeeks(new Date(), 1), { locale: currentLocale }),
284
+ }),
285
+ },
286
+ {
287
+ label: t?.("thisMonth") || "Ce mois",
288
+ range: () => ({
289
+ from: startOfMonth(new Date()),
290
+ to: endOfMonth(new Date()),
291
+ }),
292
+ },
293
+ {
294
+ label: t?.("lastMonth") || "Mois dernier",
295
+ range: () => ({
296
+ from: startOfMonth(subMonths(new Date(), 1)),
297
+ to: endOfMonth(subMonths(new Date(), 1)),
298
+ }),
299
+ },
300
+ {
301
+ label: t?.("last7Days") || "7 derniers jours",
302
+ range: () => ({
303
+ from: subDays(new Date(), 7),
304
+ to: new Date(),
305
+ }),
306
+ },
307
+ {
308
+ label: t?.("last30Days") || "30 derniers jours",
309
+ range: () => ({
310
+ from: subDays(new Date(), 30),
311
+ to: new Date(),
312
+ }),
313
+ },
314
+ {
315
+ label: t?.("thisYear") || "Cette année",
316
+ range: () => ({
317
+ from: startOfYear(new Date()),
318
+ to: endOfYear(new Date()),
319
+ }),
320
+ },
321
+ ]
322
+
323
+ const activePresets = customPresets || (showPresets ? defaultPresets : [])
324
+
325
+ // Gestion des tailles
326
+ const sizeClasses = {
327
+ sm: "h-8 px-3 text-xs",
328
+ md: "h-10 px-4 text-sm",
329
+ lg: "h-12 px-5 text-base"
330
+ }
331
+
332
+ // Format par défaut selon le variant
333
+ const getDefaultFormat = (): string => {
334
+ switch (variant) {
335
+ case "time":
336
+ return showSeconds
337
+ ? (timeFormat === "12h" ? "h:mm:ss a" : "HH:mm:ss")
338
+ : (timeFormat === "12h" ? "h:mm a" : "HH:mm")
339
+ case "datetime":
340
+ return showSeconds
341
+ ? (timeFormat === "12h" ? "PPP h:mm:ss a" : "PPP HH:mm:ss")
342
+ : (timeFormat === "12h" ? "PPP h:mm a" : "PPP HH:mm")
343
+ case "range":
344
+ return "PPP"
345
+ case "multiple":
346
+ return "PPP"
347
+ default:
348
+ return "PPP"
349
+ }
350
+ }
351
+
352
+ const displayFormat = customFormat || getDefaultFormat()
353
+
354
+ // Validation d'une date
355
+ const validateDate = (date: Date): string | null => {
356
+ if (!validation) return null
357
+
358
+ // Validation min/max
359
+ if (minDate && isBefore(date, startOfDay(minDate))) {
360
+ return t?.("dateBeforeMin") || `Date antérieure au ${format(minDate, displayFormat, { locale: currentLocale })}`
361
+ }
362
+ if (maxDate && isAfter(date, endOfDay(maxDate))) {
363
+ return t?.("dateAfterMax") || `Date postérieure au ${format(maxDate, displayFormat, { locale: currentLocale })}`
364
+ }
365
+
366
+ // Dates désactivées
367
+ if (validation.disabledDates?.some(d => isEqual(startOfDay(d), startOfDay(date)))) {
368
+ return validation.message || t?.("dateDisabled") || "Date désactivée"
369
+ }
370
+
371
+ // Fonction de désactivation
372
+ if (validation.disabledDatesFn?.(date)) {
373
+ return validation.message || t?.("dateDisabled") || "Date désactivée"
374
+ }
375
+
376
+ // Jours de semaine désactivés
377
+ if (validation.disabledDaysOfWeek?.includes(date.getDay())) {
378
+ return validation.message || t?.("dayOfWeekDisabled") || "Jour de la semaine désactivé"
379
+ }
380
+
381
+ // Validation personnalisée
382
+ if (validation.validate) {
383
+ const result = validation.validate(date)
384
+ if (result === false) {
385
+ return validation.message || t?.("invalidDate") || "Date invalide"
386
+ }
387
+ if (typeof result === "string") {
388
+ return result
389
+ }
390
+ }
391
+
392
+ return null
393
+ }
394
+
395
+ // Gestion du changement de date
396
+ const handleDateChange = (date: Date | [Date, Date] | null) => {
397
+ if (readOnly) return
398
+
399
+ let newValue: Date | DateRange | Date[] | null = null
400
+
401
+ if (variant === "range") {
402
+ if (Array.isArray(date) && date[0] && date[1]) {
403
+ newValue = { from: date[0], to: date[1] }
404
+ } else if (date) {
405
+ // Si une seule date, commence une range
406
+ const currentRange = internalValue as DateRange
407
+ if (currentRange?.from && !currentRange.to) {
408
+ newValue = { from: currentRange.from, to: date as Date }
409
+ } else {
410
+ newValue = { from: date as Date, to: undefined }
411
+ }
412
+ }
413
+ } else if (variant === "multiple") {
414
+ if (Array.isArray(internalValue)) {
415
+ const dates = [...internalValue]
416
+ const exists = dates.findIndex(d => isEqual(startOfDay(d), startOfDay(date as Date)))
417
+ if (exists >= 0) {
418
+ dates.splice(exists, 1)
419
+ } else {
420
+ dates.push(date as Date)
421
+ }
422
+ newValue = dates
423
+ } else {
424
+ newValue = date ? [date as Date] : []
425
+ }
426
+ } else {
427
+ newValue = date as Date | null
428
+ }
429
+
430
+ // Validation avant changement
431
+ if (onBeforeChange && !onBeforeChange(newValue)) {
432
+ return
433
+ }
434
+
435
+ // Validation
436
+ if (newValue && !(newValue as any).from && !Array.isArray(newValue)) {
437
+ const error = validateDate(newValue as Date)
438
+ if (error) {
439
+ setValidationError(error)
440
+ return
441
+ }
442
+ }
443
+
444
+ setValidationError(null)
445
+ setInternalValue(newValue)
446
+ onChange?.(newValue)
447
+ onAfterChange?.(newValue)
448
+
449
+ // Fermer le popover pour les sélections simples
450
+ if (variant !== "range" && variant !== "multiple" && !inline) {
451
+ setOpen(false)
452
+ }
453
+ }
454
+
455
+ // Appliquer un preset
456
+ const applyPreset = (preset: DatePreset) => {
457
+ if (preset.range) {
458
+ const range = typeof preset.range === "function" ? preset.range() : preset.range
459
+ handleDateChange([range.from, range.to])
460
+ } else if (preset.value) {
461
+ const date = typeof preset.value === "function" ? preset.value() : preset.value
462
+ handleDateChange(date)
463
+ }
464
+ if (!inline) {
465
+ setOpen(false)
466
+ }
467
+ }
468
+
469
+ // Effacer la sélection
470
+ const handleClear = (e?: React.MouseEvent) => {
471
+ if (e) {
472
+ e.stopPropagation()
473
+ }
474
+ if (readOnly) return
475
+
476
+ setInternalValue(null)
477
+ setValidationError(null)
478
+ onChange?.(null)
479
+ onAfterChange?.(null)
480
+ }
481
+
482
+ // Sélectionner aujourd'hui
483
+ const handleToday = () => {
484
+ const today = new Date()
485
+ handleDateChange(today)
486
+ }
487
+
488
+ // Obtenir le texte affiché
489
+ const getDisplayText = (): string => {
490
+ if (!internalValue) return placeholder || t?.("selectDate") || "Sélectionner une date"
491
+
492
+ try {
493
+ if (variant === "range") {
494
+ const range = internalValue as DateRange
495
+ if (range.from && range.to) {
496
+ return `${format(range.from, displayFormat, { locale: currentLocale })} - ${format(range.to, displayFormat, { locale: currentLocale })}`
497
+ }
498
+ if (range.from) {
499
+ return `${format(range.from, displayFormat, { locale: currentLocale })} - ...`
500
+ }
501
+ return placeholder || t?.("selectDateRange") || "Sélectionner une période"
502
+ }
503
+
504
+ if (variant === "multiple") {
505
+ const dates = internalValue as Date[]
506
+ if (dates.length === 0) {
507
+ return placeholder || t?.("selectDates") || "Sélectionner des dates"
508
+ }
509
+ if (dates.length === 1) {
510
+ return format(dates[0], displayFormat, { locale: currentLocale })
511
+ }
512
+ return `${dates.length} ${t?.("datesSelected") || "dates sélectionnées"}`
513
+ }
514
+
515
+ return format(internalValue as Date, displayFormat, { locale: currentLocale })
516
+ } catch (e) {
517
+ console.error("Error formatting date:", e)
518
+ return placeholder || t?.("selectDate") || "Sélectionner une date"
519
+ }
520
+ }
521
+
522
+ // Vérifier si une date est désactivée
523
+ const isDateDisabled = (date: Date): boolean => {
524
+ if (minDate && isBefore(date, startOfDay(minDate))) return true
525
+ if (maxDate && isAfter(date, endOfDay(maxDate))) return true
526
+ if (validation?.disabledDates?.some(d => isEqual(startOfDay(d), startOfDay(date)))) return true
527
+ if (validation?.disabledDatesFn?.(date)) return true
528
+ if (validation?.disabledDaysOfWeek?.includes(date.getDay())) return true
529
+ return false
530
+ }
531
+
532
+ // Vérifier si une date est highlightée
533
+ const isDateHighlighted = (date: Date): boolean => {
534
+ if (validation?.highlightedDates?.some(d => isEqual(startOfDay(d), startOfDay(date)))) return true
535
+ if (validation?.highlightedDatesFn?.(date)) return true
536
+ return false
537
+ }
538
+
539
+ // Render du composant principal
540
+ const renderDatePicker = () => {
541
+ const pickerProps: any = {
542
+ selected: variant === "range"
543
+ ? (internalValue as DateRange)?.from
544
+ : variant === "multiple"
545
+ ? undefined
546
+ : (internalValue as Date),
547
+ onChange: handleDateChange,
548
+ startDate: variant === "range" ? (internalValue as DateRange)?.from : undefined,
549
+ endDate: variant === "range" ? (internalValue as DateRange)?.to : undefined,
550
+ selectsRange: variant === "range",
551
+ selectsMultiple: variant === "multiple",
552
+ highlightDates: variant === "multiple" ? (internalValue as Date[]) : undefined,
553
+ showTimeSelect: variant === "time" || variant === "datetime",
554
+ showTimeSelectOnly: variant === "time",
555
+ timeFormat: showSeconds
556
+ ? (timeFormat === "12h" ? "h:mm:ss aa" : "HH:mm:ss")
557
+ : (timeFormat === "12h" ? "h:mm aa" : "HH:mm"),
558
+ timeIntervals,
559
+ timeCaption: t?.("time") || "Heure",
560
+ dateFormat: displayFormat,
561
+ locale: locale,
562
+ placeholderText: placeholder,
563
+ disabled,
564
+ minDate,
565
+ maxDate,
566
+ inline,
567
+ monthsShown: variant === "range" ? Math.max(2, monthsShown) : monthsShown,
568
+ showWeekNumbers,
569
+ filterDate: (date: Date) => !isDateDisabled(date),
570
+ dayClassName: (date: Date) => {
571
+ const classes = []
572
+ if (isDateHighlighted(date)) {
573
+ classes.push("highlighted-date")
574
+ }
575
+ if (hoveredDate && variant === "range") {
576
+ const range = internalValue as DateRange
577
+ if (range?.from && !range.to && isWithinInterval(date, {
578
+ start: range.from < hoveredDate ? range.from : hoveredDate,
579
+ end: range.from > hoveredDate ? range.from : hoveredDate
580
+ })) {
581
+ classes.push("in-selecting-range")
582
+ }
583
+ }
584
+ return classes.join(" ")
585
+ },
586
+ onDayMouseEnter: (date: Date) => setHoveredDate(date),
587
+ calendarClassName: cn(
588
+ "waka-datepicker",
589
+ disableAnimations && "no-animations"
590
+ ),
591
+ wrapperClassName: "w-full",
592
+ autoFocus,
593
+ }
594
+
595
+ return (
596
+ <div className="flex gap-3">
597
+ {showPresets && activePresets.length > 0 && (
598
+ <div className="flex flex-col gap-1 min-w-[150px] border-r border-border pr-3">
599
+ <Label className="text-xs font-semibold text-muted-foreground mb-1">
600
+ {t?.("quickSelection") || "Sélection rapide"}
601
+ </Label>
602
+ <ScrollArea className="h-[300px]">
603
+ <div className="space-y-1">
604
+ {activePresets.map((preset, index) => (
605
+ <Button
606
+ key={index}
607
+ variant="ghost"
608
+ size="sm"
609
+ className="w-full justify-start text-xs"
610
+ onClick={() => applyPreset(preset)}
611
+ >
612
+ {preset.label}
613
+ </Button>
614
+ ))}
615
+ </div>
616
+ </ScrollArea>
617
+ </div>
618
+ )}
619
+
620
+ <div className="flex-1">
621
+ <DatePicker {...pickerProps} />
622
+
623
+ {showSelectionInfo && internalValue && (
624
+ <div className="mt-3 p-2 bg-muted rounded-md text-xs space-y-1">
625
+ <div className="flex items-center gap-2">
626
+ <Info className="h-3 w-3 text-muted-foreground" />
627
+ <span className="font-medium">{t?.("selection") || "Sélection"} :</span>
628
+ </div>
629
+ <div className="text-muted-foreground pl-5">
630
+ {getDisplayText()}
631
+ </div>
632
+ </div>
633
+ )}
634
+ </div>
635
+ </div>
636
+ )
637
+ }
638
+
639
+ // Render inline
640
+ if (inline) {
641
+ return (
642
+ <div ref={ref} className={cn("space-y-2", className)} id={id}>
643
+ {label && (
644
+ <Label className="text-sm font-medium text-foreground">
645
+ {label}
646
+ {required && <span className="text-destructive ml-1">*</span>}
647
+ </Label>
648
+ )}
649
+
650
+ <div className="border border-border rounded-lg p-4 bg-card">
651
+ {renderDatePicker()}
652
+ </div>
653
+
654
+ {(error || validationError) && (
655
+ <div className="flex items-center gap-1.5 text-sm text-destructive">
656
+ <AlertCircle className="h-3.5 w-3.5" />
657
+ <span>{error || validationError}</span>
658
+ </div>
659
+ )}
660
+ </div>
661
+ )
662
+ }
663
+
664
+ // Render avec Popover
665
+ return (
666
+ <div ref={ref} className={cn("space-y-2", className)} id={id}>
667
+ {label && (
668
+ <Label htmlFor={`${id}-input`} className="text-sm font-medium text-foreground">
669
+ {label}
670
+ {required && <span className="text-destructive ml-1">*</span>}
671
+ </Label>
672
+ )}
673
+
674
+ <Popover open={open} onOpenChange={setOpen}>
675
+ <PopoverTrigger asChild>
676
+ <Button
677
+ id={`${id}-input`}
678
+ name={name}
679
+ variant="outline"
680
+ disabled={disabled || readOnly}
681
+ className={cn(
682
+ "w-full justify-between text-left font-normal",
683
+ !internalValue && "text-muted-foreground",
684
+ sizeClasses[size],
685
+ (error || validationError) && "border-destructive focus-visible:ring-destructive",
686
+ readOnly && "cursor-default opacity-70"
687
+ )}
688
+ aria-label={label || placeholder}
689
+ aria-invalid={!!(error || validationError)}
690
+ aria-required={required}
691
+ >
692
+ <span className="flex items-center gap-2 flex-1 truncate">
693
+ {variant === "time" ? (
694
+ <Clock className="h-4 w-4 flex-shrink-0" />
695
+ ) : (
696
+ <CalendarIcon className="h-4 w-4 flex-shrink-0" />
697
+ )}
698
+ <span className="truncate">{getDisplayText()}</span>
699
+ </span>
700
+
701
+ <div className="flex items-center gap-1 flex-shrink-0 ml-2">
702
+ {internalValue && showClear && !readOnly && (
703
+ <TooltipProvider>
704
+ <Tooltip>
705
+ <TooltipTrigger asChild>
706
+ <div
707
+ className="h-5 w-5 rounded hover:bg-destructive/10 flex items-center justify-center cursor-pointer"
708
+ onClick={handleClear}
709
+ role="button"
710
+ aria-label={t?.("clear") || "Effacer"}
711
+ >
712
+ <X className="h-3 w-3 text-muted-foreground hover:text-destructive" />
713
+ </div>
714
+ </TooltipTrigger>
715
+ <TooltipContent>
716
+ <p>{t?.("clear") || "Effacer"}</p>
717
+ </TooltipContent>
718
+ </Tooltip>
719
+ </TooltipProvider>
720
+ )}
721
+
722
+ {validationError && (
723
+ <TooltipProvider>
724
+ <Tooltip>
725
+ <TooltipTrigger asChild>
726
+ <AlertCircle className="h-4 w-4 text-destructive" />
727
+ </TooltipTrigger>
728
+ <TooltipContent>
729
+ <p>{validationError}</p>
730
+ </TooltipContent>
731
+ </Tooltip>
732
+ </TooltipProvider>
733
+ )}
734
+ </div>
735
+ </Button>
736
+ </PopoverTrigger>
737
+
738
+ <PopoverContent
739
+ className={cn("w-auto p-0", popoverClassName)}
740
+ align="start"
741
+ sideOffset={4}
742
+ >
743
+ <div className="p-4">
744
+ {renderDatePicker()}
745
+ </div>
746
+
747
+ {(showTodayButton || showClear) && (
748
+ <>
749
+ <Separator />
750
+ <div className="flex items-center justify-between p-3 gap-2">
751
+ {showTodayButton && (
752
+ <Button
753
+ variant="outline"
754
+ size="sm"
755
+ onClick={handleToday}
756
+ disabled={disabled || readOnly}
757
+ >
758
+ <CalendarIcon className="h-3.5 w-3.5 mr-1.5" />
759
+ {t?.("today") || "Aujourd'hui"}
760
+ </Button>
761
+ )}
762
+
763
+ {showClear && internalValue && (
764
+ <Button
765
+ variant="outline"
766
+ size="sm"
767
+ onClick={handleClear}
768
+ disabled={disabled || readOnly}
769
+ className="ml-auto"
770
+ >
771
+ <X className="h-3.5 w-3.5 mr-1.5" />
772
+ {t?.("clear") || "Effacer"}
773
+ </Button>
774
+ )}
775
+ </div>
776
+ </>
777
+ )}
778
+ </PopoverContent>
779
+ </Popover>
780
+
781
+ {(error || validationError) && (
782
+ <div className="flex items-center gap-1.5 text-sm text-destructive">
783
+ <AlertCircle className="h-3.5 w-3.5" />
784
+ <span>{error || validationError}</span>
785
+ </div>
786
+ )}
787
+ </div>
788
+ )
789
+ }
790
+ )
791
+
792
+ WakaDateTimePicker.displayName = "WakaDateTimePicker"
793
+