@teamix-evo/ui 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (511) hide show
  1. package/README.md +46 -50
  2. package/manifest.json +406 -653
  3. package/package.json +15 -34
  4. package/src/_design-system/theme-tokens/stories.tsx +1474 -0
  5. package/src/components/accordion/index.tsx +139 -0
  6. package/src/components/accordion/meta.md +162 -0
  7. package/src/components/accordion/stories.tsx +145 -0
  8. package/src/components/affix/index.tsx +149 -0
  9. package/src/components/affix/meta.md +23 -0
  10. package/src/components/affix/stories.tsx +70 -0
  11. package/src/components/alert/index.tsx +220 -0
  12. package/src/components/alert/meta.md +133 -0
  13. package/src/components/alert/stories.tsx +126 -0
  14. package/src/components/alert-dialog/index.tsx +282 -0
  15. package/src/components/alert-dialog/meta.md +86 -0
  16. package/src/components/alert-dialog/stories.tsx +163 -0
  17. package/src/components/avatar/index.tsx +159 -0
  18. package/src/components/avatar/meta.md +242 -0
  19. package/src/components/avatar/stories.tsx +241 -0
  20. package/src/components/badge/index.tsx +299 -0
  21. package/src/components/badge/meta.md +237 -0
  22. package/src/components/badge/stories.tsx +275 -0
  23. package/src/components/breadcrumb/index.tsx +137 -0
  24. package/src/components/breadcrumb/meta.md +64 -0
  25. package/src/components/breadcrumb/stories.tsx +86 -0
  26. package/src/components/button/index.tsx +202 -0
  27. package/src/components/button/meta.md +209 -0
  28. package/src/components/button/stories.tsx +215 -0
  29. package/src/components/button-group/index.tsx +211 -0
  30. package/src/components/button-group/meta.md +190 -0
  31. package/src/components/button-group/stories.tsx +182 -0
  32. package/src/components/calendar/index.tsx +240 -0
  33. package/src/components/calendar/meta.md +36 -0
  34. package/src/components/calendar/stories.tsx +36 -0
  35. package/src/components/card/index.tsx +262 -0
  36. package/src/components/card/meta.md +194 -0
  37. package/src/components/card/stories.tsx +202 -0
  38. package/src/components/carousel/index.tsx +481 -0
  39. package/src/components/carousel/meta.md +208 -0
  40. package/src/components/carousel/stories.tsx +271 -0
  41. package/src/components/cascader-select/index.tsx +308 -0
  42. package/src/components/cascader-select/meta.md +28 -0
  43. package/src/components/cascader-select/stories.tsx +118 -0
  44. package/src/components/checkbox/index.tsx +208 -0
  45. package/src/components/checkbox/meta.md +166 -0
  46. package/src/components/checkbox/stories.tsx +206 -0
  47. package/src/components/collapsible/index.tsx +72 -0
  48. package/src/components/collapsible/meta.md +87 -0
  49. package/src/components/collapsible/stories.tsx +97 -0
  50. package/src/components/color-picker/index.tsx +677 -0
  51. package/src/components/color-picker/meta.md +83 -0
  52. package/src/components/color-picker/stories.tsx +172 -0
  53. package/src/components/combobox/index.tsx +687 -0
  54. package/src/components/combobox/meta.md +83 -0
  55. package/src/components/combobox/stories.tsx +179 -0
  56. package/src/components/command/index.tsx +238 -0
  57. package/src/components/command/meta.md +48 -0
  58. package/src/components/command/stories.tsx +83 -0
  59. package/src/components/data-table/index.tsx +1537 -0
  60. package/src/components/data-table/meta.md +107 -0
  61. package/src/components/data-table/stories.tsx +1234 -0
  62. package/src/components/date-picker/index.tsx +1492 -0
  63. package/src/components/date-picker/meta.md +103 -0
  64. package/src/components/date-picker/stories.tsx +125 -0
  65. package/src/components/descriptions/index.tsx +535 -0
  66. package/src/components/descriptions/meta.md +104 -0
  67. package/src/components/descriptions/stories.tsx +389 -0
  68. package/src/components/dialog/index.tsx +255 -0
  69. package/src/components/dialog/meta.md +244 -0
  70. package/src/components/dialog/stories.tsx +330 -0
  71. package/src/components/dropdown-menu/index.tsx +301 -0
  72. package/src/components/dropdown-menu/meta.md +93 -0
  73. package/src/components/dropdown-menu/stories.tsx +124 -0
  74. package/src/components/empty/index.tsx +431 -0
  75. package/src/components/empty/meta.md +274 -0
  76. package/src/components/empty/stories.tsx +278 -0
  77. package/src/components/field/index.tsx +374 -0
  78. package/src/components/field/meta.md +283 -0
  79. package/src/components/field/stories.tsx +327 -0
  80. package/src/components/filter-bar/index.tsx +976 -0
  81. package/src/components/filter-bar/meta.md +57 -0
  82. package/src/components/filter-bar/stories.tsx +496 -0
  83. package/src/components/float-button/index.tsx +163 -0
  84. package/src/components/float-button/meta.md +73 -0
  85. package/src/components/float-button/stories.tsx +68 -0
  86. package/src/components/form/index.tsx +218 -0
  87. package/src/components/form/meta.md +16 -0
  88. package/src/components/form/stories.tsx +301 -0
  89. package/src/components/hover-card/index.tsx +103 -0
  90. package/src/components/hover-card/meta.md +71 -0
  91. package/src/components/hover-card/stories.tsx +107 -0
  92. package/src/components/icon/index.tsx +225 -0
  93. package/src/components/icon/meta.md +102 -0
  94. package/src/components/icon/stories.tsx +127 -0
  95. package/src/components/image/index.tsx +147 -0
  96. package/src/components/image/meta.md +55 -0
  97. package/src/components/image/stories.tsx +48 -0
  98. package/src/components/input/index.tsx +63 -0
  99. package/src/components/input/meta.md +57 -0
  100. package/src/components/input/stories.tsx +66 -0
  101. package/src/components/input-group/index.tsx +292 -0
  102. package/src/components/input-group/meta.md +108 -0
  103. package/src/components/input-group/stories.tsx +258 -0
  104. package/src/components/input-ip/index.tsx +288 -0
  105. package/src/components/input-ip/meta.md +48 -0
  106. package/src/components/input-ip/stories.tsx +69 -0
  107. package/src/components/input-number/index.tsx +360 -0
  108. package/src/components/input-number/meta.md +156 -0
  109. package/src/components/input-number/stories.tsx +187 -0
  110. package/src/components/item/index.tsx +368 -0
  111. package/src/components/item/meta.md +395 -0
  112. package/src/components/item/stories.tsx +432 -0
  113. package/src/components/label/index.tsx +44 -0
  114. package/src/components/label/meta.md +68 -0
  115. package/src/components/label/stories.tsx +64 -0
  116. package/src/components/mentions/index.tsx +369 -0
  117. package/src/components/mentions/meta.md +118 -0
  118. package/src/components/mentions/stories.tsx +153 -0
  119. package/src/components/menubar/index.tsx +313 -0
  120. package/src/components/menubar/meta.md +52 -0
  121. package/src/components/menubar/stories.tsx +99 -0
  122. package/src/components/navigation-menu/index.tsx +216 -0
  123. package/src/components/navigation-menu/meta.md +66 -0
  124. package/src/components/navigation-menu/stories.tsx +137 -0
  125. package/src/components/page-header/index.tsx +218 -0
  126. package/src/components/page-header/meta.md +211 -0
  127. package/src/components/page-header/stories.tsx +290 -0
  128. package/src/components/page-shell/index.tsx +119 -0
  129. package/src/components/page-shell/meta.md +115 -0
  130. package/src/components/page-shell/stories.tsx +154 -0
  131. package/src/components/pagination/index.tsx +580 -0
  132. package/src/components/pagination/meta.md +39 -0
  133. package/src/components/pagination/stories.tsx +319 -0
  134. package/src/components/popconfirm/index.tsx +477 -0
  135. package/src/components/popconfirm/meta.md +164 -0
  136. package/src/components/popconfirm/stories.tsx +231 -0
  137. package/src/components/popover/index.tsx +181 -0
  138. package/src/components/popover/meta.md +97 -0
  139. package/src/components/popover/stories.tsx +169 -0
  140. package/src/components/progress/index.tsx +176 -0
  141. package/src/components/progress/meta.md +95 -0
  142. package/src/components/progress/stories.tsx +115 -0
  143. package/src/components/radio-group/index.tsx +78 -0
  144. package/src/components/radio-group/meta.md +137 -0
  145. package/src/components/radio-group/stories.tsx +285 -0
  146. package/src/components/rate/index.tsx +284 -0
  147. package/src/components/rate/meta.md +63 -0
  148. package/src/components/rate/stories.tsx +107 -0
  149. package/src/components/resizable/index.tsx +122 -0
  150. package/src/components/resizable/meta.md +142 -0
  151. package/src/components/resizable/stories.tsx +105 -0
  152. package/src/components/scroll-area/index.tsx +83 -0
  153. package/src/components/scroll-area/meta.md +85 -0
  154. package/src/components/scroll-area/stories.tsx +82 -0
  155. package/src/components/select/index.tsx +325 -0
  156. package/src/components/select/meta.md +183 -0
  157. package/src/components/select/stories.tsx +210 -0
  158. package/src/components/separator/index.tsx +51 -0
  159. package/src/components/separator/meta.md +68 -0
  160. package/src/components/separator/stories.tsx +53 -0
  161. package/src/components/sheet/index.tsx +293 -0
  162. package/src/components/sheet/meta.md +226 -0
  163. package/src/components/sheet/stories.tsx +405 -0
  164. package/src/components/sidebar/index.tsx +715 -0
  165. package/src/components/sidebar/meta.md +14 -0
  166. package/src/components/sidebar/{sidebar.stories.tsx → stories.tsx} +250 -330
  167. package/src/components/skeleton/index.tsx +415 -0
  168. package/src/components/skeleton/meta.md +210 -0
  169. package/src/components/skeleton/stories.tsx +197 -0
  170. package/src/components/slider/index.tsx +222 -0
  171. package/src/components/slider/meta.md +125 -0
  172. package/src/components/slider/stories.tsx +109 -0
  173. package/src/components/sonner/index.tsx +149 -0
  174. package/src/components/sonner/meta.md +156 -0
  175. package/src/components/sonner/stories.tsx +285 -0
  176. package/src/components/spinner/index.tsx +335 -0
  177. package/src/components/spinner/meta.md +172 -0
  178. package/src/components/spinner/stories.tsx +197 -0
  179. package/src/components/statistic/{statistic.tsx → index.tsx} +25 -26
  180. package/src/components/statistic/meta.md +94 -0
  181. package/src/components/statistic/stories.tsx +85 -0
  182. package/src/components/steps/index.tsx +500 -0
  183. package/src/components/steps/meta.md +245 -0
  184. package/src/components/steps/stories.tsx +287 -0
  185. package/src/components/switch/index.tsx +175 -0
  186. package/src/components/switch/meta.md +107 -0
  187. package/src/components/switch/stories.tsx +154 -0
  188. package/src/components/table/index.tsx +350 -0
  189. package/src/components/table/meta.md +148 -0
  190. package/src/components/table/stories.tsx +189 -0
  191. package/src/components/tabs/index.tsx +379 -0
  192. package/src/components/tabs/meta.md +244 -0
  193. package/src/components/tabs/stories.tsx +312 -0
  194. package/src/components/tag/index.tsx +774 -0
  195. package/src/components/tag/meta.md +256 -0
  196. package/src/components/tag/stories.tsx +431 -0
  197. package/src/components/textarea/index.tsx +62 -0
  198. package/src/components/textarea/meta.md +77 -0
  199. package/src/components/textarea/stories.tsx +112 -0
  200. package/src/components/time-picker/index.tsx +887 -0
  201. package/src/components/time-picker/meta.md +102 -0
  202. package/src/components/time-picker/stories.tsx +125 -0
  203. package/src/components/timeline/index.tsx +422 -0
  204. package/src/components/timeline/meta.md +352 -0
  205. package/src/components/timeline/stories.tsx +369 -0
  206. package/src/components/toggle/index.tsx +74 -0
  207. package/src/components/toggle/meta.md +76 -0
  208. package/src/components/toggle/stories.tsx +66 -0
  209. package/src/components/toggle-group/index.tsx +167 -0
  210. package/src/components/toggle-group/meta.md +141 -0
  211. package/src/components/toggle-group/stories.tsx +124 -0
  212. package/src/components/tooltip/index.tsx +144 -0
  213. package/src/components/tooltip/meta.md +186 -0
  214. package/src/components/tooltip/stories.tsx +212 -0
  215. package/src/components/transfer/index.tsx +639 -0
  216. package/src/components/transfer/meta.md +76 -0
  217. package/src/components/transfer/stories.tsx +221 -0
  218. package/src/components/tree/index.tsx +764 -0
  219. package/src/components/tree/meta.md +96 -0
  220. package/src/components/tree/stories.tsx +385 -0
  221. package/src/components/tree/utils.ts +269 -0
  222. package/src/components/tree-select/index.tsx +400 -0
  223. package/src/components/tree-select/meta.md +78 -0
  224. package/src/components/tree-select/stories.tsx +234 -0
  225. package/src/components/typography/index.tsx +290 -0
  226. package/src/components/typography/meta.md +151 -0
  227. package/src/components/typography/stories.tsx +151 -0
  228. package/src/components/upload/index.tsx +858 -0
  229. package/src/components/upload/meta.md +122 -0
  230. package/src/components/upload/stories.tsx +261 -0
  231. package/src/components/watermark/index.tsx +152 -0
  232. package/src/components/watermark/meta.md +67 -0
  233. package/src/components/watermark/stories.tsx +59 -0
  234. package/src/hooks/use-mobile.ts +9 -11
  235. package/src/lib/color.ts +243 -0
  236. package/src/{utils/cn.ts → lib/utils.ts} +1 -1
  237. package/src/components/accordion/accordion.meta.md +0 -88
  238. package/src/components/accordion/accordion.stories.tsx +0 -72
  239. package/src/components/accordion/accordion.tsx +0 -154
  240. package/src/components/affix/affix.meta.md +0 -98
  241. package/src/components/affix/affix.stories.tsx +0 -134
  242. package/src/components/affix/affix.tsx +0 -167
  243. package/src/components/alert/alert.meta.md +0 -132
  244. package/src/components/alert/alert.stories.tsx +0 -138
  245. package/src/components/alert/alert.tsx +0 -179
  246. package/src/components/alert-dialog/alert-dialog.meta.md +0 -152
  247. package/src/components/alert-dialog/alert-dialog.stories.tsx +0 -223
  248. package/src/components/alert-dialog/alert-dialog.tsx +0 -183
  249. package/src/components/anchor/anchor.meta.md +0 -92
  250. package/src/components/anchor/anchor.stories.tsx +0 -74
  251. package/src/components/anchor/anchor.tsx +0 -130
  252. package/src/components/app/app.meta.md +0 -91
  253. package/src/components/app/app.stories.tsx +0 -64
  254. package/src/components/app/app.tsx +0 -58
  255. package/src/components/aspect-ratio/aspect-ratio.meta.md +0 -82
  256. package/src/components/aspect-ratio/aspect-ratio.stories.tsx +0 -59
  257. package/src/components/aspect-ratio/aspect-ratio.tsx +0 -22
  258. package/src/components/auto-complete/auto-complete.meta.md +0 -110
  259. package/src/components/auto-complete/auto-complete.stories.tsx +0 -136
  260. package/src/components/auto-complete/auto-complete.tsx +0 -253
  261. package/src/components/avatar/avatar.meta.md +0 -93
  262. package/src/components/avatar/avatar.stories.tsx +0 -98
  263. package/src/components/avatar/avatar.tsx +0 -127
  264. package/src/components/badge/badge.meta.md +0 -120
  265. package/src/components/badge/badge.stories.tsx +0 -153
  266. package/src/components/badge/badge.tsx +0 -204
  267. package/src/components/breadcrumb/breadcrumb.meta.md +0 -127
  268. package/src/components/breadcrumb/breadcrumb.stories.tsx +0 -207
  269. package/src/components/breadcrumb/breadcrumb.tsx +0 -136
  270. package/src/components/button/button.meta.md +0 -335
  271. package/src/components/button/button.stories.tsx +0 -743
  272. package/src/components/button/button.tsx +0 -466
  273. package/src/components/calendar/calendar.meta.md +0 -128
  274. package/src/components/calendar/calendar.stories.tsx +0 -68
  275. package/src/components/calendar/calendar.tsx +0 -172
  276. package/src/components/card/card.meta.md +0 -139
  277. package/src/components/card/card.stories.tsx +0 -151
  278. package/src/components/card/card.tsx +0 -306
  279. package/src/components/carousel/carousel.meta.md +0 -118
  280. package/src/components/carousel/carousel.stories.tsx +0 -89
  281. package/src/components/carousel/carousel.tsx +0 -224
  282. package/src/components/cascader/cascader.meta.md +0 -140
  283. package/src/components/cascader/cascader.stories.tsx +0 -120
  284. package/src/components/cascader/cascader.tsx +0 -548
  285. package/src/components/checkbox/checkbox.meta.md +0 -167
  286. package/src/components/checkbox/checkbox.stories.tsx +0 -288
  287. package/src/components/checkbox/checkbox.tsx +0 -195
  288. package/src/components/collapsible/collapsible.meta.md +0 -88
  289. package/src/components/collapsible/collapsible.stories.tsx +0 -43
  290. package/src/components/collapsible/collapsible.tsx +0 -105
  291. package/src/components/color-picker/color-picker.meta.md +0 -89
  292. package/src/components/color-picker/color-picker.stories.tsx +0 -159
  293. package/src/components/color-picker/color-picker.tsx +0 -171
  294. package/src/components/command/command.meta.md +0 -120
  295. package/src/components/command/command.stories.tsx +0 -59
  296. package/src/components/command/command.tsx +0 -158
  297. package/src/components/context-menu/context-menu.meta.md +0 -93
  298. package/src/components/context-menu/context-menu.stories.tsx +0 -54
  299. package/src/components/context-menu/context-menu.tsx +0 -204
  300. package/src/components/data-table/data-table.meta.md +0 -150
  301. package/src/components/data-table/data-table.stories.tsx +0 -132
  302. package/src/components/data-table/data-table.tsx +0 -185
  303. package/src/components/date-picker/date-picker.meta.md +0 -175
  304. package/src/components/date-picker/date-picker.stories.tsx +0 -108
  305. package/src/components/date-picker/date-picker.tsx +0 -1554
  306. package/src/components/descriptions/descriptions.meta.md +0 -83
  307. package/src/components/descriptions/descriptions.stories.tsx +0 -60
  308. package/src/components/descriptions/descriptions.tsx +0 -137
  309. package/src/components/dialog/dialog.meta.md +0 -168
  310. package/src/components/dialog/dialog.stories.tsx +0 -255
  311. package/src/components/dialog/dialog.tsx +0 -180
  312. package/src/components/dialog/imperative.tsx +0 -252
  313. package/src/components/drawer/drawer.meta.md +0 -95
  314. package/src/components/drawer/drawer.stories.tsx +0 -71
  315. package/src/components/drawer/drawer.tsx +0 -23
  316. package/src/components/dropdown-menu/dropdown-menu.meta.md +0 -171
  317. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +0 -198
  318. package/src/components/dropdown-menu/dropdown-menu.tsx +0 -209
  319. package/src/components/ellipsis/ellipsis.meta.md +0 -87
  320. package/src/components/ellipsis/ellipsis.stories.tsx +0 -72
  321. package/src/components/ellipsis/ellipsis.tsx +0 -153
  322. package/src/components/empty/empty.meta.md +0 -86
  323. package/src/components/empty/empty.stories.tsx +0 -46
  324. package/src/components/empty/empty.tsx +0 -54
  325. package/src/components/field/field.meta.md +0 -154
  326. package/src/components/field/field.stories.tsx +0 -497
  327. package/src/components/field/field.tsx +0 -392
  328. package/src/components/filter-bar/filter-bar.meta.md +0 -92
  329. package/src/components/filter-bar/filter-bar.stories.tsx +0 -1086
  330. package/src/components/filter-bar/filter-bar.tsx +0 -568
  331. package/src/components/flex/flex.meta.md +0 -142
  332. package/src/components/flex/flex.stories.tsx +0 -199
  333. package/src/components/flex/flex.tsx +0 -145
  334. package/src/components/float-button/float-button.meta.md +0 -92
  335. package/src/components/float-button/float-button.stories.tsx +0 -80
  336. package/src/components/float-button/float-button.tsx +0 -143
  337. package/src/components/form/form.meta.md +0 -153
  338. package/src/components/form/form.stories.tsx +0 -472
  339. package/src/components/form/form.tsx +0 -260
  340. package/src/components/grid/grid.meta.md +0 -92
  341. package/src/components/grid/grid.stories.tsx +0 -101
  342. package/src/components/grid/grid.tsx +0 -130
  343. package/src/components/hover-card/hover-card.meta.md +0 -103
  344. package/src/components/hover-card/hover-card.stories.tsx +0 -97
  345. package/src/components/hover-card/hover-card.tsx +0 -67
  346. package/src/components/icon/DEVELOPMENT.md +0 -809
  347. package/src/components/icon/icon.meta.md +0 -170
  348. package/src/components/icon/icon.stories.tsx +0 -344
  349. package/src/components/icon/icon.tsx +0 -248
  350. package/src/components/image/image.meta.md +0 -99
  351. package/src/components/image/image.stories.tsx +0 -55
  352. package/src/components/image/image.tsx +0 -140
  353. package/src/components/input/input.meta.md +0 -115
  354. package/src/components/input/input.stories.tsx +0 -144
  355. package/src/components/input/input.tsx +0 -212
  356. package/src/components/input-group/input-group.meta.md +0 -124
  357. package/src/components/input-group/input-group.stories.tsx +0 -121
  358. package/src/components/input-group/input-group.tsx +0 -143
  359. package/src/components/input-number/input-number.meta.md +0 -148
  360. package/src/components/input-number/input-number.stories.tsx +0 -125
  361. package/src/components/input-number/input-number.tsx +0 -283
  362. package/src/components/input-otp/input-otp.meta.md +0 -106
  363. package/src/components/input-otp/input-otp.stories.tsx +0 -65
  364. package/src/components/input-otp/input-otp.tsx +0 -97
  365. package/src/components/item/item.meta.md +0 -121
  366. package/src/components/item/item.stories.tsx +0 -116
  367. package/src/components/item/item.tsx +0 -172
  368. package/src/components/kbd/kbd.meta.md +0 -94
  369. package/src/components/kbd/kbd.stories.tsx +0 -70
  370. package/src/components/kbd/kbd.tsx +0 -86
  371. package/src/components/label/label.meta.md +0 -99
  372. package/src/components/label/label.stories.tsx +0 -145
  373. package/src/components/label/label.tsx +0 -138
  374. package/src/components/masonry/masonry.meta.md +0 -90
  375. package/src/components/masonry/masonry.stories.tsx +0 -68
  376. package/src/components/masonry/masonry.tsx +0 -60
  377. package/src/components/mentions/mentions.meta.md +0 -119
  378. package/src/components/mentions/mentions.stories.tsx +0 -189
  379. package/src/components/mentions/mentions.tsx +0 -243
  380. package/src/components/menubar/menubar.meta.md +0 -118
  381. package/src/components/menubar/menubar.stories.tsx +0 -141
  382. package/src/components/menubar/menubar.tsx +0 -232
  383. package/src/components/native-select/native-select.meta.md +0 -93
  384. package/src/components/native-select/native-select.stories.tsx +0 -83
  385. package/src/components/native-select/native-select.tsx +0 -54
  386. package/src/components/navigation-menu/navigation-menu.meta.md +0 -118
  387. package/src/components/navigation-menu/navigation-menu.stories.tsx +0 -215
  388. package/src/components/navigation-menu/navigation-menu.tsx +0 -129
  389. package/src/components/notification/notification.meta.md +0 -133
  390. package/src/components/notification/notification.stories.tsx +0 -98
  391. package/src/components/notification/notification.tsx +0 -99
  392. package/src/components/page-header/DEVELOPMENT.md +0 -842
  393. package/src/components/page-header/page-header.meta.md +0 -210
  394. package/src/components/page-header/page-header.stories.tsx +0 -428
  395. package/src/components/page-header/page-header.tsx +0 -284
  396. package/src/components/page-shell/page-shell.meta.md +0 -116
  397. package/src/components/page-shell/page-shell.stories.tsx +0 -149
  398. package/src/components/page-shell/page-shell.tsx +0 -115
  399. package/src/components/pagination/pagination.meta.md +0 -230
  400. package/src/components/pagination/pagination.stories.tsx +0 -284
  401. package/src/components/pagination/pagination.tsx +0 -567
  402. package/src/components/popconfirm/popconfirm.meta.md +0 -114
  403. package/src/components/popconfirm/popconfirm.stories.tsx +0 -75
  404. package/src/components/popconfirm/popconfirm.tsx +0 -134
  405. package/src/components/popover/popover.meta.md +0 -154
  406. package/src/components/popover/popover.stories.tsx +0 -158
  407. package/src/components/popover/popover.tsx +0 -104
  408. package/src/components/progress/progress.meta.md +0 -118
  409. package/src/components/progress/progress.stories.tsx +0 -75
  410. package/src/components/progress/progress.tsx +0 -203
  411. package/src/components/radio-group/radio-group.meta.md +0 -175
  412. package/src/components/radio-group/radio-group.stories.tsx +0 -113
  413. package/src/components/radio-group/radio-group.tsx +0 -209
  414. package/src/components/rate/rate.meta.md +0 -118
  415. package/src/components/rate/rate.stories.tsx +0 -89
  416. package/src/components/rate/rate.tsx +0 -180
  417. package/src/components/resizable/resizable.meta.md +0 -95
  418. package/src/components/resizable/resizable.stories.tsx +0 -104
  419. package/src/components/resizable/resizable.tsx +0 -56
  420. package/src/components/result/result.meta.md +0 -95
  421. package/src/components/result/result.stories.tsx +0 -67
  422. package/src/components/result/result.tsx +0 -100
  423. package/src/components/scroll-area/scroll-area.meta.md +0 -85
  424. package/src/components/scroll-area/scroll-area.stories.tsx +0 -49
  425. package/src/components/scroll-area/scroll-area.tsx +0 -51
  426. package/src/components/segmented/segmented.meta.md +0 -106
  427. package/src/components/segmented/segmented.stories.tsx +0 -130
  428. package/src/components/segmented/segmented.tsx +0 -146
  429. package/src/components/select/select.meta.md +0 -255
  430. package/src/components/select/select.stories.tsx +0 -275
  431. package/src/components/select/select.tsx +0 -735
  432. package/src/components/separator/separator.meta.md +0 -75
  433. package/src/components/separator/separator.stories.tsx +0 -71
  434. package/src/components/separator/separator.tsx +0 -100
  435. package/src/components/sheet/sheet.meta.md +0 -113
  436. package/src/components/sheet/sheet.stories.tsx +0 -188
  437. package/src/components/sheet/sheet.tsx +0 -226
  438. package/src/components/sidebar/sidebar.meta.md +0 -151
  439. package/src/components/sidebar/sidebar.tsx +0 -853
  440. package/src/components/skeleton/skeleton.meta.md +0 -94
  441. package/src/components/skeleton/skeleton.stories.tsx +0 -79
  442. package/src/components/skeleton/skeleton.tsx +0 -144
  443. package/src/components/slider/slider.meta.md +0 -146
  444. package/src/components/slider/slider.stories.tsx +0 -121
  445. package/src/components/slider/slider.tsx +0 -227
  446. package/src/components/sonner/sonner.meta.md +0 -147
  447. package/src/components/sonner/sonner.stories.tsx +0 -164
  448. package/src/components/sonner/sonner.tsx +0 -169
  449. package/src/components/spinner/spinner.meta.md +0 -125
  450. package/src/components/spinner/spinner.stories.tsx +0 -123
  451. package/src/components/spinner/spinner.tsx +0 -166
  452. package/src/components/statistic/statistic.meta.md +0 -104
  453. package/src/components/statistic/statistic.stories.tsx +0 -67
  454. package/src/components/steps/steps.meta.md +0 -116
  455. package/src/components/steps/steps.stories.tsx +0 -115
  456. package/src/components/steps/steps.tsx +0 -173
  457. package/src/components/switch/switch.meta.md +0 -138
  458. package/src/components/switch/switch.stories.tsx +0 -75
  459. package/src/components/switch/switch.tsx +0 -169
  460. package/src/components/table/table.meta.md +0 -106
  461. package/src/components/table/table.stories.tsx +0 -80
  462. package/src/components/table/table.tsx +0 -124
  463. package/src/components/tabs/tabs.meta.md +0 -111
  464. package/src/components/tabs/tabs.stories.tsx +0 -156
  465. package/src/components/tabs/tabs.tsx +0 -190
  466. package/src/components/tag/tag.meta.md +0 -159
  467. package/src/components/tag/tag.stories.tsx +0 -250
  468. package/src/components/tag/tag.tsx +0 -386
  469. package/src/components/textarea/textarea.meta.md +0 -99
  470. package/src/components/textarea/textarea.stories.tsx +0 -89
  471. package/src/components/textarea/textarea.tsx +0 -137
  472. package/src/components/time-picker/time-picker.meta.md +0 -175
  473. package/src/components/time-picker/time-picker.stories.tsx +0 -129
  474. package/src/components/time-picker/time-picker.tsx +0 -946
  475. package/src/components/timeline/timeline.meta.md +0 -110
  476. package/src/components/timeline/timeline.stories.tsx +0 -134
  477. package/src/components/timeline/timeline.tsx +0 -168
  478. package/src/components/toggle/toggle.meta.md +0 -89
  479. package/src/components/toggle/toggle.stories.tsx +0 -66
  480. package/src/components/toggle/toggle.tsx +0 -54
  481. package/src/components/toggle-group/toggle-group.meta.md +0 -91
  482. package/src/components/toggle-group/toggle-group.stories.tsx +0 -83
  483. package/src/components/toggle-group/toggle-group.tsx +0 -78
  484. package/src/components/tooltip/tooltip.meta.md +0 -149
  485. package/src/components/tooltip/tooltip.stories.tsx +0 -108
  486. package/src/components/tooltip/tooltip.tsx +0 -153
  487. package/src/components/tour/tour.meta.md +0 -121
  488. package/src/components/tour/tour.stories.tsx +0 -66
  489. package/src/components/tour/tour.tsx +0 -242
  490. package/src/components/transfer/transfer.meta.md +0 -95
  491. package/src/components/transfer/transfer.stories.tsx +0 -64
  492. package/src/components/transfer/transfer.tsx +0 -258
  493. package/src/components/tree/tree.meta.md +0 -169
  494. package/src/components/tree/tree.stories.tsx +0 -128
  495. package/src/components/tree/tree.tsx +0 -368
  496. package/src/components/tree-select/tree-select.meta.md +0 -151
  497. package/src/components/tree-select/tree-select.stories.tsx +0 -80
  498. package/src/components/tree-select/tree-select.tsx +0 -206
  499. package/src/components/typography/typography.meta.md +0 -149
  500. package/src/components/typography/typography.stories.tsx +0 -116
  501. package/src/components/typography/typography.tsx +0 -260
  502. package/src/components/upload/upload.meta.md +0 -156
  503. package/src/components/upload/upload.stories.tsx +0 -135
  504. package/src/components/upload/upload.tsx +0 -398
  505. package/src/components/watermark/watermark.meta.md +0 -100
  506. package/src/components/watermark/watermark.stories.tsx +0 -170
  507. package/src/components/watermark/watermark.tsx +0 -166
  508. package/src/hooks/use-breakpoint.ts +0 -117
  509. package/src/hooks/use-debounce-callback.ts +0 -52
  510. package/src/stories/theme-tokens.stories.tsx +0 -747
  511. package/src/utils/trigger-input.ts +0 -57
@@ -1,1554 +0,0 @@
1
- import * as React from 'react';
2
- import {
3
- endOfMonth,
4
- endOfWeek,
5
- format as formatDate,
6
- startOfDay,
7
- startOfMonth,
8
- startOfWeek,
9
- subDays,
10
- subMonths,
11
- } from 'date-fns';
12
- import { zhCN } from 'date-fns/locale';
13
- import {
14
- Calendar as CalendarIcon,
15
- ChevronLeft,
16
- ChevronRight,
17
- ChevronsLeft,
18
- ChevronsRight,
19
- X,
20
- } from 'lucide-react';
21
- import {
22
- useDayPicker,
23
- type DateRange,
24
- type DayPickerProps,
25
- } from 'react-day-picker';
26
-
27
- import { cn } from '@/utils/cn';
28
- import {
29
- datesEqual,
30
- inputElementClass,
31
- triggerSizeClass,
32
- triggerWrapperClass,
33
- tryParseDate,
34
- } from '@/utils/trigger-input';
35
- import { Button, buttonVariants } from '@/components/button/button';
36
- import { Calendar } from '@/components/calendar/calendar';
37
- import {
38
- Popover,
39
- PopoverAnchor,
40
- PopoverContent,
41
- } from '@/components/popover/popover';
42
- import {
43
- TimePickerPanel,
44
- parseTime,
45
- formatTime,
46
- type TimeParts,
47
- } from '@/components/time-picker/time-picker';
48
-
49
- // ─── showTime 配置类型 ───────────────────────────────────────────────────
50
-
51
- export interface ShowTimeConfig {
52
- /** 时间格式(默认 "HH:mm:ss")。 */
53
- format?: string;
54
- /** 默认时间(未选择时显示的时间值,格式同 `format`)。 */
55
- defaultValue?: string;
56
- /** 小时步长。 */
57
- hourStep?: number;
58
- /** 分钟步长。 */
59
- minuteStep?: number;
60
- /** 秒步长。 */
61
- secondStep?: number;
62
- /** 禁用小时。 */
63
- disabledHours?: () => number[];
64
- /** 禁用分钟。 */
65
- disabledMinutes?: (hour: number) => number[];
66
- /** 禁用秒。 */
67
- disabledSeconds?: (hour: number, minute: number) => number[];
68
- }
69
-
70
- const DEFAULT_TIME_FORMAT = 'HH:mm:ss';
71
-
72
- // ─── Preset 类型 ────────────────────────────────────────────────────────
73
-
74
- /** DatePicker(单日)preset 项 —— `value` 可以是 Date 或返回 Date 的函数。 */
75
- export interface DatePickerPreset {
76
- label: string;
77
- value: Date | (() => Date);
78
- }
79
-
80
- /** DateRangePicker(范围)preset 项 —— `value` 可以是 DateRange 或返回 DateRange 的函数。 */
81
- export interface DateRangePreset {
82
- label: string;
83
- value: DateRange | (() => DateRange);
84
- }
85
-
86
- /** 内置单日 presets:今天 / 昨天。 */
87
- const DEFAULT_DATE_PRESETS: DatePickerPreset[] = [
88
- { label: '今天', value: () => new Date() },
89
- { label: '昨天', value: () => subDays(new Date(), 1) },
90
- ];
91
-
92
- /** 内置范围 presets:今天 / 昨天 / 本周 / 上周 / 本月 / 上月。 */
93
- const DEFAULT_RANGE_PRESETS: DateRangePreset[] = [
94
- { label: '今天', value: () => ({ from: new Date(), to: new Date() }) },
95
- {
96
- label: '昨天',
97
- value: () => {
98
- const y = subDays(new Date(), 1);
99
- return { from: y, to: y };
100
- },
101
- },
102
- {
103
- label: '本周',
104
- value: () => ({
105
- from: startOfWeek(new Date(), { weekStartsOn: 1 }),
106
- to: endOfWeek(new Date(), { weekStartsOn: 1 }),
107
- }),
108
- },
109
- {
110
- label: '上周',
111
- value: () => {
112
- const lastWeek = subDays(new Date(), 7);
113
- return {
114
- from: startOfWeek(lastWeek, { weekStartsOn: 1 }),
115
- to: endOfWeek(lastWeek, { weekStartsOn: 1 }),
116
- };
117
- },
118
- },
119
- {
120
- label: '本月',
121
- value: () => ({
122
- from: startOfMonth(new Date()),
123
- to: endOfMonth(new Date()),
124
- }),
125
- },
126
- {
127
- label: '上月',
128
- value: () => {
129
- const lastMonth = subMonths(new Date(), 1);
130
- return {
131
- from: startOfMonth(lastMonth),
132
- to: endOfMonth(lastMonth),
133
- };
134
- },
135
- },
136
- ];
137
-
138
- const resolveDatePresets = (
139
- presets: boolean | DatePickerPreset[] | undefined,
140
- ): DatePickerPreset[] => {
141
- if (!presets) return [];
142
- if (presets === true) return DEFAULT_DATE_PRESETS;
143
- return presets;
144
- };
145
-
146
- const resolveRangePresets = (
147
- presets: boolean | DateRangePreset[] | undefined,
148
- ): DateRangePreset[] => {
149
- if (!presets) return [];
150
- if (presets === true) return DEFAULT_RANGE_PRESETS;
151
- return presets;
152
- };
153
-
154
- const resolvePresetValue = <T,>(v: T | (() => T)): T =>
155
- typeof v === 'function' ? (v as () => T)() : v;
156
-
157
- /**
158
- * Preview range 虚线视觉 —— 中性灰 dashed border。
159
- *
160
- * 实现关键:day cell(td)默认带 1px transparent dashed border 占位(见 calendar.tsx day slot),
161
- * preview 时仅切换 border-color 不改尺寸 → 避免布局跳变。配合 month_grid 的 `border-collapse`,
162
- * 相邻 cell border 自然合并为连续虚线。
163
- *
164
- * 三段 modifier:
165
- * - `previewRange`:行首 / 行末(`first` / `last`)颜色给到 left / right border + 圆角
166
- * - `previewStart` / `previewEnd`:range 端点强制 left / right 边 + 圆角(端点不在行首/末时也闭合)
167
- * - selected 端点(`[data-selected]`)border 还原透明,避免与蓝色填充叠加虚线
168
- */
169
- const PREVIEW_RANGE_CLASS =
170
- '!border-y-foreground/40 first:!border-l-foreground/40 first:rounded-l-md last:!border-r-foreground/40 last:rounded-r-md [&[data-selected]]:!border-transparent';
171
- const PREVIEW_START_CLASS = '!border-l-foreground/40 rounded-l-md';
172
- const PREVIEW_END_CLASS = '!border-r-foreground/40 rounded-r-md';
173
-
174
- // ─── 日历面板本地化(DatePicker 内部覆盖,不影响 Calendar 直接使用者) ───
175
-
176
- const navButtonClass = cn(
177
- buttonVariants({ variant: 'ghost', size: 'icon' }),
178
- 'size-7 bg-transparent opacity-60 hover:opacity-100 disabled:pointer-events-none disabled:opacity-30',
179
- );
180
-
181
- /**
182
- * 自定义 Nav:左右各两枚按钮(年/月双向导航)。
183
- * 透传 react-day-picker 的 nav className(absolute 定位)。
184
- */
185
- function DatePickerNav({
186
- className,
187
- }: React.HTMLAttributes<HTMLElement> & {
188
- onPreviousClick?: React.MouseEventHandler<HTMLButtonElement>;
189
- onNextClick?: React.MouseEventHandler<HTMLButtonElement>;
190
- previousMonth?: Date;
191
- nextMonth?: Date;
192
- }): React.JSX.Element {
193
- const { goToMonth, nextMonth, previousMonth, months } = useDayPicker();
194
- const currentMonth = months[0]?.date;
195
- const prevYear = currentMonth
196
- ? new Date(currentMonth.getFullYear() - 1, currentMonth.getMonth(), 1)
197
- : undefined;
198
- const nextYear = currentMonth
199
- ? new Date(currentMonth.getFullYear() + 1, currentMonth.getMonth(), 1)
200
- : undefined;
201
-
202
- return (
203
- <nav className={className}>
204
- <div className="flex items-center gap-0.5">
205
- <button
206
- type="button"
207
- aria-label="上一年"
208
- className={navButtonClass}
209
- disabled={!prevYear}
210
- onClick={() => prevYear && goToMonth(prevYear)}
211
- >
212
- <ChevronsLeft className="size-4" />
213
- </button>
214
- <button
215
- type="button"
216
- aria-label="上个月"
217
- className={navButtonClass}
218
- disabled={!previousMonth}
219
- onClick={() => previousMonth && goToMonth(previousMonth)}
220
- >
221
- <ChevronLeft className="size-4" />
222
- </button>
223
- </div>
224
- <div className="flex items-center gap-0.5">
225
- <button
226
- type="button"
227
- aria-label="下个月"
228
- className={navButtonClass}
229
- disabled={!nextMonth}
230
- onClick={() => nextMonth && goToMonth(nextMonth)}
231
- >
232
- <ChevronRight className="size-4" />
233
- </button>
234
- <button
235
- type="button"
236
- aria-label="下一年"
237
- className={navButtonClass}
238
- disabled={!nextYear}
239
- onClick={() => nextYear && goToMonth(nextYear)}
240
- >
241
- <ChevronsRight className="size-4" />
242
- </button>
243
- </div>
244
- </nav>
245
- );
246
- }
247
-
248
- /** 中文化 + 视觉对齐 cloud-design DatePicker2 的 Calendar 透传 props。 */
249
- const datePickerCalendarProps = {
250
- locale: zhCN,
251
- weekStartsOn: 1,
252
- formatters: {
253
- formatCaption: (month: Date) =>
254
- `${month.getFullYear()}年 ${month.getMonth() + 1}月`,
255
- },
256
- classNames: {
257
- // 今日仅蓝色文字,不填充背景(选中态保留 bg-primary)
258
- today: 'text-primary font-medium',
259
- },
260
- components: {
261
- Nav: DatePickerNav,
262
- },
263
- } satisfies Pick<
264
- DayPickerProps,
265
- 'locale' | 'weekStartsOn' | 'formatters' | 'classNames' | 'components'
266
- >;
267
-
268
- // ─── MonthPanel / YearPanel(picker='month'|'year' 时替代 Calendar 日期网格) ─────
269
-
270
- const MONTH_LABELS = [
271
- '1月',
272
- '2月',
273
- '3月',
274
- '4月',
275
- '5月',
276
- '6月',
277
- '7月',
278
- '8月',
279
- '9月',
280
- '10月',
281
- '11月',
282
- '12月',
283
- ] as const;
284
-
285
- function MonthPanel({
286
- value,
287
- onSelect,
288
- disabledDates,
289
- }: {
290
- value?: Date;
291
- onSelect: (d: Date) => void;
292
- disabledDates?: (d: Date) => boolean;
293
- }) {
294
- const [year, setYear] = React.useState(
295
- () => value?.getFullYear() ?? new Date().getFullYear(),
296
- );
297
- const selectedMonth =
298
- value && value.getFullYear() === year ? value.getMonth() : -1;
299
-
300
- return (
301
- <div className="flex flex-col gap-3 p-3">
302
- <div className="flex items-center justify-between">
303
- <button
304
- type="button"
305
- className={navButtonClass}
306
- onClick={() => setYear(year - 1)}
307
- >
308
- <ChevronLeft className="size-4" />
309
- </button>
310
- <span className="text-xs font-medium">{year}年</span>
311
- <button
312
- type="button"
313
- className={navButtonClass}
314
- onClick={() => setYear(year + 1)}
315
- >
316
- <ChevronRight className="size-4" />
317
- </button>
318
- </div>
319
- <div className="grid grid-cols-3 gap-2">
320
- {MONTH_LABELS.map((label, i) => {
321
- const d = new Date(year, i, 1);
322
- const isDisabled = disabledDates?.(d) ?? false;
323
- return (
324
- <button
325
- key={i}
326
- type="button"
327
- disabled={isDisabled}
328
- onClick={() => onSelect(d)}
329
- className={cn(
330
- 'h-8 cursor-pointer rounded-md text-xs transition-colors',
331
- 'hover:bg-accent hover:text-accent-foreground',
332
- 'disabled:pointer-events-none disabled:opacity-50',
333
- selectedMonth === i &&
334
- 'bg-primary text-primary-foreground hover:bg-primary/90',
335
- )}
336
- >
337
- {label}
338
- </button>
339
- );
340
- })}
341
- </div>
342
- </div>
343
- );
344
- }
345
-
346
- function YearPanel({
347
- value,
348
- onSelect,
349
- disabledDates,
350
- }: {
351
- value?: Date;
352
- onSelect: (d: Date) => void;
353
- disabledDates?: (d: Date) => boolean;
354
- }) {
355
- const baseYear = value?.getFullYear() ?? new Date().getFullYear();
356
- const [decadeStart, setDecadeStart] = React.useState(
357
- () => Math.floor(baseYear / 10) * 10,
358
- );
359
- const selectedYear = value?.getFullYear() ?? -1;
360
- const years = Array.from({ length: 12 }, (_, i) => decadeStart - 1 + i);
361
-
362
- return (
363
- <div className="flex flex-col gap-3 p-3">
364
- <div className="flex items-center justify-between">
365
- <button
366
- type="button"
367
- className={navButtonClass}
368
- onClick={() => setDecadeStart(decadeStart - 10)}
369
- >
370
- <ChevronLeft className="size-4" />
371
- </button>
372
- <span className="text-xs font-medium">
373
- {decadeStart} – {decadeStart + 9}
374
- </span>
375
- <button
376
- type="button"
377
- className={navButtonClass}
378
- onClick={() => setDecadeStart(decadeStart + 10)}
379
- >
380
- <ChevronRight className="size-4" />
381
- </button>
382
- </div>
383
- <div className="grid grid-cols-3 gap-2">
384
- {years.map((y) => {
385
- const d = new Date(y, 0, 1);
386
- const isDisabled = disabledDates?.(d) ?? false;
387
- const isOutside = y < decadeStart || y > decadeStart + 9;
388
- return (
389
- <button
390
- key={y}
391
- type="button"
392
- disabled={isDisabled}
393
- onClick={() => onSelect(d)}
394
- className={cn(
395
- 'h-8 cursor-pointer rounded-md text-xs transition-colors',
396
- 'hover:bg-accent hover:text-accent-foreground',
397
- 'disabled:pointer-events-none disabled:opacity-50',
398
- isOutside && 'text-muted-foreground/60',
399
- selectedYear === y &&
400
- 'bg-primary text-primary-foreground hover:bg-primary/90',
401
- )}
402
- >
403
- {y}
404
- </button>
405
- );
406
- })}
407
- </div>
408
- </div>
409
- );
410
- }
411
-
412
- const dateToParts = (d: Date | undefined): TimeParts =>
413
- d
414
- ? { h: d.getHours(), m: d.getMinutes(), s: d.getSeconds() }
415
- : { h: 0, m: 0, s: 0 };
416
-
417
- const mergeDateAndTime = (date: Date, parts: TimeParts): Date => {
418
- const next = new Date(date);
419
- next.setHours(parts.h, parts.m, parts.s, 0);
420
- return next;
421
- };
422
-
423
- const resolveShowTime = (
424
- showTime: boolean | ShowTimeConfig | undefined,
425
- ): ShowTimeConfig | null => {
426
- if (!showTime) return null;
427
- if (showTime === true) return { format: DEFAULT_TIME_FORMAT };
428
- return { format: DEFAULT_TIME_FORMAT, ...showTime };
429
- };
430
-
431
- // ─── DatePicker(单日,可选 showTime) ────────────────────────────────────
432
-
433
- export interface DatePickerProps {
434
- /**
435
- * 选择粒度(antd `picker` 并集) — 决定弹出面板类型:
436
- * - `'date'`(默认) — 日历面板(选择具体日期)
437
- * - `'month'` — 月份网格(选择到月)
438
- * - `'year'` — 年份网格(选择到年)
439
- * @default "date"
440
- */
441
- picker?: 'date' | 'month' | 'year';
442
- /** 受控值 — 当前选中日期(`showTime` 时含时间)。 */
443
- value?: Date;
444
- /**
445
- * 非受控初始值(对齐 antd / cd `defaultValue`)。仅在未传 `value` 时生效。
446
- */
447
- defaultValue?: Date;
448
- /** 选中变化回调。`showTime` 时点击「确定」才触发,否则点击日期立即触发。 */
449
- onChange?: (value: Date | undefined) => void;
450
- /** 占位文本。 @default "选择日期" */
451
- placeholder?: string;
452
- /**
453
- * 显示格式(date-fns 语法)。`showTime` 时建议含时间 token,如 `yyyy-MM-dd HH:mm:ss`。
454
- * @default "yyyy-MM-dd"
455
- */
456
- format?: string;
457
- /** 触发器尺寸。 @default "md" */
458
- size?: 'sm' | 'md' | 'default' | 'lg';
459
- /** 触发器 className。 */
460
- className?: string;
461
- /** 是否禁用。 */
462
- disabled?: boolean;
463
- /** 不可选日期 predicate。 */
464
- disabledDates?: (date: Date) => boolean;
465
- /**
466
- * 显示时间选择器(antd `showTime` 并集)。
467
- * - `true` — 启用,使用默认 `HH:mm:ss` 格式
468
- * - `ShowTimeConfig` — 详细配置(format / 默认值 / 步长 / 禁用回调)
469
- * - `false` / 缺省 — 仅日期(默认行为,向后兼容)
470
- * @default false
471
- */
472
- showTime?: boolean | ShowTimeConfig;
473
- /**
474
- * 弹层底部快捷 preset 链接(对齐 antd / cloud-design DatePicker2 的 `presets` 能力)。
475
- * - `true` — 启用内置默认集(今天 / 昨天)
476
- * - `DatePickerPreset[]` — 自定义集
477
- * - `false` / 缺省 — 不显示
478
- * @default false
479
- */
480
- presets?: boolean | DatePickerPreset[];
481
- /**
482
- * 显示清除按钮(antd `allowClear` 并集);有值时显示 X 替代日历图标,点击清空 value。
483
- * @default true
484
- */
485
- hasClear?: boolean;
486
- /**
487
- * 校验状态(对齐 antd `status`) — 控制触发器边框 / 字体颜色:
488
- * - `'error'` — 红色 destructive 边框 + `aria-invalid="true"`
489
- * - `'warning'` — 警告色边框
490
- * - `'default'` / 缺省 — 中性边框
491
- * @default "default"
492
- */
493
- status?: 'default' | 'error' | 'warning';
494
- /**
495
- * 受控弹层显隐(对齐 antd `open`)。
496
- * 业务侧需要"程序化打开 / 关闭"时使用;不传则组件内部自管。
497
- */
498
- open?: boolean;
499
- /** 弹层显隐变化回调(对齐 antd `onOpenChange`)。 */
500
- onOpenChange?: (open: boolean) => void;
501
- /**
502
- * 弹层底部自定义 footer(对齐 antd `footer / renderExtraFooter`)。
503
- * 与 `presets` 共存时,footer 渲染在 presets 之下。
504
- */
505
- footer?: React.ReactNode;
506
- /**
507
- * 输入框只读 — 仅能通过点击弹层选择,不能直接键入(对齐 antd `inputReadOnly`)。
508
- * @default false
509
- */
510
- inputReadOnly?: boolean;
511
- }
512
-
513
- const triggerWidth = (showTime: ShowTimeConfig | null) =>
514
- showTime ? 'w-64' : 'w-60';
515
-
516
- // trigger 样式 / parse 工具迁移至 `@/utils/trigger-input`,4 个 picker 共享。
517
-
518
- const DatePicker = React.forwardRef<HTMLInputElement, DatePickerProps>(
519
- (
520
- {
521
- value: valueProp,
522
- defaultValue,
523
- onChange,
524
- picker = 'date',
525
- placeholder: placeholderProp,
526
- format: fmt,
527
- size = 'md',
528
- className,
529
- disabled,
530
- disabledDates,
531
- showTime,
532
- presets,
533
- hasClear = true,
534
- status = 'default',
535
- open: openProp,
536
- onOpenChange,
537
- footer,
538
- inputReadOnly = false,
539
- },
540
- ref,
541
- ) => {
542
- const timeCfg = picker === 'date' ? resolveShowTime(showTime) : null;
543
- const displayFormat =
544
- fmt ??
545
- (picker === 'year'
546
- ? 'yyyy'
547
- : picker === 'month'
548
- ? 'yyyy-MM'
549
- : timeCfg
550
- ? 'yyyy-MM-dd HH:mm:ss'
551
- : 'yyyy-MM-dd');
552
- const placeholder =
553
- placeholderProp ??
554
- (picker === 'year'
555
- ? '选择年份'
556
- : picker === 'month'
557
- ? '选择月份'
558
- : '选择日期');
559
- const presetList = resolveDatePresets(presets);
560
-
561
- // value 受控 / 非受控 — 优先使用 valueProp,缺省时回退到内部 state(初值 defaultValue)
562
- const isValueControlled = valueProp !== undefined;
563
- const [internalValue, setInternalValue] = React.useState<Date | undefined>(
564
- defaultValue,
565
- );
566
- const value = isValueControlled ? valueProp : internalValue;
567
-
568
- // open 受控 / 非受控 — 优先 openProp,缺省时内部自管
569
- const isOpenControlled = openProp !== undefined;
570
- const [openInternal, setOpenInternal] = React.useState(false);
571
- const open = isOpenControlled ? openProp : openInternal;
572
- const setOpen = (next: boolean) => {
573
- if (!isOpenControlled) setOpenInternal(next);
574
- onOpenChange?.(next);
575
- };
576
-
577
- const [draftDate, setDraftDate] = React.useState<Date | undefined>(value);
578
- const [draftTime, setDraftTime] = React.useState<TimeParts>(() =>
579
- dateToParts(value),
580
- );
581
-
582
- // ─── input 状态 ──────────────────────────────────────────────
583
- // inputText 是用户可编辑的文本草稿,与 value 双向同步:
584
- // - value 变化(外部 / 选择 / preset)→ inputText 同步(仅非 focus 时)
585
- // - input blur → parse inputText → 合法更新 value;非法清空
586
- const inputRef = React.useRef<HTMLInputElement>(null);
587
- const anchorRef = React.useRef<HTMLDivElement>(null);
588
- React.useImperativeHandle(ref, () => inputRef.current!);
589
- const [inputText, setInputText] = React.useState<string>(() =>
590
- value ? formatDate(value, displayFormat) : '',
591
- );
592
- React.useEffect(() => {
593
- if (document.activeElement !== inputRef.current) {
594
- setInputText(value ? formatDate(value, displayFormat) : '');
595
- }
596
- }, [value, displayFormat]);
597
-
598
- // 同步外部 value → draft(showTime 模式才用,纯日期模式直接走 value)
599
- React.useEffect(() => {
600
- if (open) {
601
- setDraftDate(value);
602
- if (value) {
603
- setDraftTime(dateToParts(value));
604
- } else if (timeCfg?.defaultValue) {
605
- const parsed = parseTime(timeCfg.defaultValue);
606
- if (parsed) setDraftTime(parsed);
607
- }
608
- }
609
- // 仅在 popover 开关瞬间同步 draft,故意只依赖 open。
610
- }, [open]);
611
-
612
- const commitValue = (next: Date | undefined) => {
613
- // 立即同步 input 文本 —— 不能只靠 useEffect,因为选择时 input 仍 focused,
614
- // useEffect 的 `activeElement !== input` 条件会跳过同步。
615
- setInputText(next ? formatDate(next, displayFormat) : '');
616
- if (datesEqual(value, next)) return;
617
- if (!isValueControlled) setInternalValue(next);
618
- onChange?.(next);
619
- };
620
-
621
- const handleInputBlur = () => {
622
- const parsed = tryParseDate(inputText, displayFormat);
623
- if (parsed) {
624
- commitValue(parsed);
625
- } else if (inputText.trim() === '') {
626
- commitValue(undefined);
627
- } else {
628
- // 非法 → 清空
629
- setInputText('');
630
- commitValue(undefined);
631
- }
632
- };
633
-
634
- const handleSelectDate = (d: Date | undefined) => {
635
- if (!timeCfg) {
636
- commitValue(d);
637
- setOpen(false);
638
- return;
639
- }
640
- setDraftDate(d);
641
- // showTime:实时同步 input text(merge date + draftTime)
642
- if (d) {
643
- setInputText(formatDate(mergeDateAndTime(d, draftTime), displayFormat));
644
- }
645
- };
646
-
647
- const handleSelectTime = (next: TimeParts) => {
648
- setDraftTime(next);
649
- // showTime:实时同步 input text(merge draftDate + new time)
650
- if (draftDate) {
651
- setInputText(
652
- formatDate(mergeDateAndTime(draftDate, next), displayFormat),
653
- );
654
- }
655
- };
656
-
657
- const handleConfirm = () => {
658
- if (!draftDate) {
659
- setOpen(false);
660
- return;
661
- }
662
- commitValue(mergeDateAndTime(draftDate, draftTime));
663
- setOpen(false);
664
- };
665
-
666
- const handlePresetClick = (preset: DatePickerPreset) => {
667
- const d = resolvePresetValue(preset.value);
668
- if (!timeCfg) {
669
- commitValue(d);
670
- setOpen(false);
671
- return;
672
- }
673
- setDraftDate(d);
674
- setDraftTime(dateToParts(d));
675
- setInputText(formatDate(d, displayFormat));
676
- };
677
-
678
- const handleClear = (e: React.MouseEvent) => {
679
- e.preventDefault(); // 不触发 input blur
680
- commitValue(undefined);
681
- inputRef.current?.focus();
682
- };
683
-
684
- const hasValue = value != null;
685
- // hasClear:有值显示 X(可清除),空值还原为日历 icon。默认 true,可传 hasClear={false} 关闭。
686
- const showClear = hasClear && hasValue && !disabled;
687
-
688
- return (
689
- <Popover open={open} onOpenChange={setOpen}>
690
- <PopoverAnchor asChild>
691
- <div
692
- ref={anchorRef}
693
- aria-invalid={status === 'error' ? true : undefined}
694
- className={cn(
695
- triggerWrapperClass,
696
- triggerSizeClass[size],
697
- triggerWidth(timeCfg),
698
- status === 'error' &&
699
- 'border-destructive aria-invalid:border-destructive',
700
- status === 'warning' && 'border-warning',
701
- className,
702
- )}
703
- >
704
- <input
705
- ref={inputRef}
706
- type="text"
707
- value={inputText}
708
- onChange={(e) => setInputText(e.target.value)}
709
- onFocus={() => setOpen(true)}
710
- onBlur={handleInputBlur}
711
- placeholder={placeholder}
712
- disabled={disabled}
713
- readOnly={inputReadOnly}
714
- className={inputElementClass}
715
- />
716
- {showClear ? (
717
- <button
718
- type="button"
719
- aria-label="清除日期"
720
- tabIndex={-1}
721
- onMouseDown={handleClear}
722
- className="flex shrink-0 cursor-pointer items-center text-muted-foreground transition-colors hover:text-foreground"
723
- >
724
- <X className="size-4" />
725
- </button>
726
- ) : (
727
- <CalendarIcon
728
- aria-hidden
729
- className="size-4 shrink-0 text-muted-foreground"
730
- />
731
- )}
732
- </div>
733
- </PopoverAnchor>
734
- <PopoverContent
735
- className="w-auto rounded-md p-0 shadow-sm"
736
- onOpenAutoFocus={(e) => e.preventDefault()}
737
- onInteractOutside={(e) => {
738
- // 点击 / focus 落在 anchor(trigger wrapper)内时,popover 不关闭。
739
- // anchor 是我们自管的 trigger,Radix 默认仅认 PopoverTrigger,
740
- // 不加这层判断会在 input focus 触发 open 后立即被 outside 关闭。
741
- const target = e.detail.originalEvent.target as Node | null;
742
- if (target && anchorRef.current?.contains(target)) {
743
- e.preventDefault();
744
- }
745
- }}
746
- >
747
- {timeCfg ? (
748
- <>
749
- <div className="flex items-stretch">
750
- <Calendar
751
- mode="single"
752
- selected={draftDate}
753
- onSelect={handleSelectDate}
754
- disabled={disabledDates}
755
- defaultMonth={value}
756
- {...datePickerCalendarProps}
757
- />
758
- <TimePickerPanel
759
- value={draftTime}
760
- onChange={handleSelectTime}
761
- format={timeCfg.format ?? DEFAULT_TIME_FORMAT}
762
- hourStep={timeCfg.hourStep}
763
- minuteStep={timeCfg.minuteStep}
764
- secondStep={timeCfg.secondStep}
765
- disabledHours={timeCfg.disabledHours}
766
- disabledMinutes={timeCfg.disabledMinutes}
767
- disabledSeconds={timeCfg.disabledSeconds}
768
- hasSelection={draftDate != null}
769
- padZero={false}
770
- className="border-l border-border"
771
- />
772
- </div>
773
- <div className="flex items-center justify-between border-t border-border px-3 py-2">
774
- <div className="flex flex-wrap items-center gap-3">
775
- {presetList.map((p) => (
776
- <button
777
- key={p.label}
778
- type="button"
779
- className="cursor-pointer text-xs text-primary transition-colors hover:text-primary/80"
780
- onClick={() => handlePresetClick(p)}
781
- >
782
- {p.label}
783
- </button>
784
- ))}
785
- </div>
786
- <Button size="sm" onClick={handleConfirm} disabled={!draftDate}>
787
- 确定
788
- </Button>
789
- </div>
790
- </>
791
- ) : (
792
- <>
793
- {picker === 'month' ? (
794
- <MonthPanel
795
- value={value}
796
- onSelect={handleSelectDate}
797
- disabledDates={disabledDates}
798
- />
799
- ) : picker === 'year' ? (
800
- <YearPanel
801
- value={value}
802
- onSelect={handleSelectDate}
803
- disabledDates={disabledDates}
804
- />
805
- ) : (
806
- <Calendar
807
- mode="single"
808
- selected={value}
809
- onSelect={handleSelectDate}
810
- disabled={disabledDates}
811
- defaultMonth={value}
812
- {...datePickerCalendarProps}
813
- />
814
- )}
815
- {presetList.length > 0 && (
816
- <div className="flex flex-wrap items-center gap-3 border-t border-border px-3 py-2">
817
- {presetList.map((p) => (
818
- <button
819
- key={p.label}
820
- type="button"
821
- className="cursor-pointer text-xs text-primary transition-colors hover:text-primary/80"
822
- onClick={() => handlePresetClick(p)}
823
- >
824
- {p.label}
825
- </button>
826
- ))}
827
- </div>
828
- )}
829
- </>
830
- )}
831
- {footer ? (
832
- <div className="border-t border-border p-2 text-xs">{footer}</div>
833
- ) : null}
834
- </PopoverContent>
835
- </Popover>
836
- );
837
- },
838
- );
839
- DatePicker.displayName = 'DatePicker';
840
-
841
- // ─── DateRangePicker(范围,可选 showTime) ──────────────────────────────
842
-
843
- export interface DateRangePickerProps {
844
- /** 受控值 — 当前选中范围。 */
845
- value?: DateRange;
846
- /** 非受控初始值(对齐 antd / cd `defaultValue`)。仅在未传 `value` 时生效。 */
847
- defaultValue?: DateRange;
848
- /** 范围变化回调。 */
849
- onChange?: (value: DateRange | undefined) => void;
850
- /**
851
- * 显示格式(date-fns 语法)。
852
- * @default "yyyy-MM-dd"
853
- */
854
- format?: string;
855
- /** 触发器尺寸。 @default "md" */
856
- size?: 'sm' | 'md' | 'default' | 'lg';
857
- /** 触发器 className。 */
858
- className?: string;
859
- /** 是否禁用。 */
860
- disabled?: boolean;
861
- /** 不可选日期 predicate。 */
862
- disabledDates?: (date: Date) => boolean;
863
- /** 同时显示几个月历。 @default 2 */
864
- numberOfMonths?: number;
865
- /**
866
- * 起始 / 结束占位文本(`showTime` 时使用双段触发器)。
867
- * @default ["起始日期","结束日期"]
868
- */
869
- placeholders?: [string, string];
870
- /**
871
- * 显示时间选择器(antd `showTime` 并集)。
872
- * @default false
873
- */
874
- showTime?: boolean | ShowTimeConfig;
875
- /**
876
- * 弹层底部快捷 preset 链接(对齐 antd / cloud-design DatePicker2 的 `presets` 能力)。
877
- * - `true` — 启用内置默认集(今天 / 昨天 / 本周 / 上周 / 本月 / 上月)
878
- * - `DateRangePreset[]` — 自定义集
879
- * - `false` / 缺省 — 不显示
880
- * @default false
881
- */
882
- presets?: boolean | DateRangePreset[];
883
- /**
884
- * 显示清除按钮(antd `allowClear` 并集)。有值时显示 X,空值还原为日历 icon。
885
- * @default true
886
- */
887
- hasClear?: boolean;
888
- /**
889
- * 校验状态(对齐 antd `status`)— `'error'` / `'warning'` / `'default'`。
890
- * @default "default"
891
- */
892
- status?: 'default' | 'error' | 'warning';
893
- /** 受控弹层显隐(对齐 antd `open`)。 */
894
- open?: boolean;
895
- /** 弹层显隐变化回调。 */
896
- onOpenChange?: (open: boolean) => void;
897
- /** 弹层底部自定义 footer(渲染在 presets 下方)。 */
898
- footer?: React.ReactNode;
899
- /**
900
- * 输入框只读 — 仅能通过点击弹层选择,不能直接键入。
901
- * @default false
902
- */
903
- inputReadOnly?: boolean;
904
- }
905
-
906
- const DateRangePicker = React.forwardRef<
907
- HTMLInputElement,
908
- DateRangePickerProps
909
- >(
910
- (
911
- {
912
- value: valueProp,
913
- defaultValue,
914
- onChange,
915
- format: fmt,
916
- size = 'md',
917
- className,
918
- disabled,
919
- disabledDates,
920
- numberOfMonths = 2,
921
- placeholders = ['起始日期', '结束日期'],
922
- showTime,
923
- presets,
924
- hasClear = true,
925
- status = 'default',
926
- open: openProp,
927
- onOpenChange,
928
- footer,
929
- inputReadOnly = false,
930
- },
931
- ref,
932
- ) => {
933
- const timeCfg = resolveShowTime(showTime);
934
- const displayFormat =
935
- fmt ?? (timeCfg ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd');
936
- const presetList = resolveRangePresets(presets);
937
-
938
- // value 受控 / 非受控
939
- const isValueControlled = valueProp !== undefined;
940
- const [internalValue, setInternalValue] = React.useState<
941
- DateRange | undefined
942
- >(defaultValue);
943
- const value = isValueControlled ? valueProp : internalValue;
944
-
945
- // open 受控 / 非受控
946
- const isOpenControlled = openProp !== undefined;
947
- const [openInternal, setOpenInternal] = React.useState(false);
948
- const open = isOpenControlled ? openProp : openInternal;
949
- const setOpen = (next: boolean) => {
950
- if (!isOpenControlled) setOpenInternal(next);
951
- onOpenChange?.(next);
952
- };
953
-
954
- // ─── draft state ──────────────────────────
955
- const [draftRange, setDraftRange] = React.useState<DateRange | undefined>(
956
- value,
957
- );
958
- const [draftStartTime, setDraftStartTime] = React.useState<TimeParts>(() =>
959
- dateToParts(value?.from),
960
- );
961
- const [draftEndTime, setDraftEndTime] = React.useState<TimeParts>(() =>
962
- dateToParts(value?.to),
963
- );
964
- // touched:跟踪 from / to 是否被用户主动选过(影响 time panel hasSelection 与 onChange tick)
965
- const [startTouched, setStartTouched] = React.useState<boolean>(
966
- value?.from != null,
967
- );
968
- const [endTouched, setEndTouched] = React.useState<boolean>(
969
- value?.to != null,
970
- );
971
- // hover preview:from 已选 / to 未选中间态时显示虚线 preview
972
- const [hoveredDate, setHoveredDate] = React.useState<Date | undefined>();
973
- // 哪个 input 是 active(showTime 决定显示 from / to 单面板;纯日期决定下次点击填哪段)
974
- const [activeField, setActiveField] = React.useState<'from' | 'to'>('from');
975
-
976
- // ─── 双 input refs + text state ───────────────────────
977
- const fromInputRef = React.useRef<HTMLInputElement>(null);
978
- const toInputRef = React.useRef<HTMLInputElement>(null);
979
- const anchorRef = React.useRef<HTMLDivElement>(null);
980
- React.useImperativeHandle(ref, () => fromInputRef.current!);
981
- // confirm 标志:避免 popover 关闭兜底 commit 与 confirm commit 重复
982
- const confirmedRef = React.useRef(false);
983
-
984
- const [fromText, setFromText] = React.useState<string>(() =>
985
- value?.from ? formatDate(value.from, displayFormat) : '',
986
- );
987
- const [toText, setToText] = React.useState<string>(() =>
988
- value?.to ? formatDate(value.to, displayFormat) : '',
989
- );
990
-
991
- // ─── helpers ──────────────────────────────────────
992
- // 计算最终值:仅 touched 的段才输出,未 touched 段返回 undefined。
993
- // 这样允许"只选 from 不选 to"的半 range 提交。
994
- const finalize = (
995
- range: DateRange | undefined,
996
- startTime: TimeParts,
997
- endTime: TimeParts,
998
- fromTouched: boolean,
999
- toTouched: boolean,
1000
- ): DateRange | undefined => {
1001
- const fromOut =
1002
- fromTouched && range?.from
1003
- ? timeCfg
1004
- ? mergeDateAndTime(range.from, startTime)
1005
- : range.from
1006
- : undefined;
1007
- const toOut =
1008
- toTouched && range?.to
1009
- ? timeCfg
1010
- ? mergeDateAndTime(range.to, endTime)
1011
- : range.to
1012
- : undefined;
1013
- if (!fromOut && !toOut) return undefined;
1014
- return { from: fromOut, to: toOut };
1015
- };
1016
-
1017
- // input text 实时同步(根据 date + time + 当前 displayFormat)
1018
- const updateFromText = (date: Date | undefined, time: TimeParts) => {
1019
- if (!date) return setFromText('');
1020
- const merged = timeCfg ? mergeDateAndTime(date, time) : date;
1021
- setFromText(formatDate(merged, displayFormat));
1022
- };
1023
- const updateToText = (date: Date | undefined, time: TimeParts) => {
1024
- if (!date) return setToText('');
1025
- const merged = timeCfg ? mergeDateAndTime(date, time) : date;
1026
- setToText(formatDate(merged, displayFormat));
1027
- };
1028
-
1029
- // 计算 range 是否相等
1030
- const rangeEqual = (a: DateRange | undefined, b: DateRange | undefined) => {
1031
- if (!a && !b) return true;
1032
- if (!a || !b) return false;
1033
- return datesEqual(a.from, b.from) && datesEqual(a.to, b.to);
1034
- };
1035
-
1036
- // 外部 value → input text(仅 input 非 focus 时同步,避免覆盖编辑)
1037
- React.useEffect(() => {
1038
- const active = document.activeElement;
1039
- if (active !== fromInputRef.current) {
1040
- setFromText(value?.from ? formatDate(value.from, displayFormat) : '');
1041
- }
1042
- if (active !== toInputRef.current) {
1043
- setToText(value?.to ? formatDate(value.to, displayFormat) : '');
1044
- }
1045
- }, [value?.from, value?.to, displayFormat]);
1046
-
1047
- // popover open 同步 draft / touched / 重置 hover / confirm 标志;close 兜底 commit
1048
- React.useEffect(() => {
1049
- if (open) {
1050
- setDraftRange(value);
1051
- setDraftStartTime(dateToParts(value?.from));
1052
- setDraftEndTime(dateToParts(value?.to));
1053
- setStartTouched(value?.from != null);
1054
- setEndTouched(value?.to != null);
1055
- setHoveredDate(undefined);
1056
- confirmedRef.current = false;
1057
- } else if (!confirmedRef.current) {
1058
- // 关闭兜底:若 draft 与 value 不同 → commit(未 touched 段会被 finalize 丢成 undefined)
1059
- const finalRange = finalize(
1060
- draftRange,
1061
- draftStartTime,
1062
- draftEndTime,
1063
- startTouched,
1064
- endTouched,
1065
- );
1066
- if (!rangeEqual(value, finalRange)) {
1067
- if (!isValueControlled) setInternalValue(finalRange);
1068
- onChange?.(finalRange);
1069
- }
1070
- }
1071
- // 仅在 popover 开关瞬间同步 draft / 兜底 commit,故意只依赖 open。
1072
- }, [open]);
1073
-
1074
- // ─── handlers ──────────────────────────────────────
1075
- // calendar 选择 —— 用 react-day-picker onSelect(_newRange, triggerDate),自管 + activeField 决定填 from / to
1076
- const handleSelect = (
1077
- _newRange: DateRange | undefined,
1078
- triggerDate: Date | undefined,
1079
- ) => {
1080
- if (!triggerDate) return;
1081
- const triggerDay = startOfDay(triggerDate).getTime();
1082
- let nextFrom = draftRange?.from;
1083
- let nextTo = draftRange?.to;
1084
- if (activeField === 'from') {
1085
- nextFrom = triggerDate;
1086
- // clamp:若新 from > 当前 to(按日比较)→ to 一并设为 from
1087
- if (nextTo && startOfDay(nextTo).getTime() < triggerDay) {
1088
- nextTo = triggerDate;
1089
- }
1090
- setStartTouched(true);
1091
- if (nextTo !== draftRange?.to) setEndTouched(true);
1092
- } else {
1093
- nextTo = triggerDate;
1094
- // clamp:若新 to < 当前 from(按日比较)→ from 一并设为 to
1095
- if (nextFrom && startOfDay(nextFrom).getTime() > triggerDay) {
1096
- nextFrom = triggerDate;
1097
- }
1098
- setEndTouched(true);
1099
- if (nextFrom !== draftRange?.from) setStartTouched(true);
1100
- }
1101
- const next: DateRange = { from: nextFrom, to: nextTo };
1102
- setDraftRange(next);
1103
- // 实时同步 input text
1104
- updateFromText(nextFrom, draftStartTime);
1105
- updateToText(nextTo, draftEndTime);
1106
- // 行为分流
1107
- if (!timeCfg) {
1108
- if (nextFrom && nextTo) {
1109
- // range 完整 → 关闭(useEffect 兜底 commit)
1110
- setOpen(false);
1111
- } else if (activeField === 'from' && !nextTo) {
1112
- // 选完 from,自动切到 to 等待用户继续
1113
- setActiveField('to');
1114
- toInputRef.current?.focus();
1115
- } else if (activeField === 'to' && !nextFrom) {
1116
- setActiveField('from');
1117
- fromInputRef.current?.focus();
1118
- }
1119
- }
1120
- // showTime:不关 popover,等用户调时间或切 input + 点确定
1121
- };
1122
-
1123
- // showTime 时间面板选择 —— 按 activeField 更新对应段时间
1124
- const handleTimeChange = (time: TimeParts) => {
1125
- if (activeField === 'from') {
1126
- setDraftStartTime(time);
1127
- setStartTouched(true);
1128
- updateFromText(draftRange?.from, time);
1129
- } else {
1130
- setDraftEndTime(time);
1131
- setEndTouched(true);
1132
- updateToText(draftRange?.to, time);
1133
- }
1134
- };
1135
-
1136
- // input blur parse(同时支持 date 和 datetime 文本)
1137
- const handleFromBlur = () => {
1138
- const trimmed = fromText.trim();
1139
- if (trimmed === '') {
1140
- setDraftRange({ from: undefined, to: draftRange?.to });
1141
- setStartTouched(false);
1142
- return;
1143
- }
1144
- const parsed = tryParseDate(trimmed, displayFormat);
1145
- if (parsed) {
1146
- // clamp 按 datetime 完整时间比(input parse 出的值含时间,不用 startOfDay)
1147
- const curTo = draftRange?.to;
1148
- const nextTo =
1149
- curTo && curTo.getTime() < parsed.getTime() ? parsed : curTo;
1150
- setDraftRange({ from: parsed, to: nextTo });
1151
- setStartTouched(true);
1152
- if (nextTo !== curTo) setEndTouched(true);
1153
- if (timeCfg) setDraftStartTime(dateToParts(parsed));
1154
- updateFromText(parsed, timeCfg ? dateToParts(parsed) : draftStartTime);
1155
- if (nextTo)
1156
- updateToText(nextTo, timeCfg ? dateToParts(nextTo) : draftEndTime);
1157
- } else {
1158
- setFromText('');
1159
- setDraftRange({ from: undefined, to: draftRange?.to });
1160
- setStartTouched(false);
1161
- }
1162
- };
1163
-
1164
- const handleToBlur = () => {
1165
- const trimmed = toText.trim();
1166
- if (trimmed === '') {
1167
- setDraftRange({ from: draftRange?.from, to: undefined });
1168
- setEndTouched(false);
1169
- return;
1170
- }
1171
- const parsed = tryParseDate(trimmed, displayFormat);
1172
- if (parsed) {
1173
- const curFrom = draftRange?.from;
1174
- const nextFrom =
1175
- curFrom && curFrom.getTime() > parsed.getTime() ? parsed : curFrom;
1176
- setDraftRange({ from: nextFrom, to: parsed });
1177
- setEndTouched(true);
1178
- if (nextFrom !== curFrom) setStartTouched(true);
1179
- if (timeCfg) setDraftEndTime(dateToParts(parsed));
1180
- updateToText(parsed, timeCfg ? dateToParts(parsed) : draftEndTime);
1181
- if (nextFrom)
1182
- updateFromText(
1183
- nextFrom,
1184
- timeCfg ? dateToParts(nextFrom) : draftStartTime,
1185
- );
1186
- } else {
1187
- setToText('');
1188
- setDraftRange({ from: draftRange?.from, to: undefined });
1189
- setEndTouched(false);
1190
- }
1191
- };
1192
-
1193
- const handleConfirm = () => {
1194
- const finalRange = finalize(
1195
- draftRange,
1196
- draftStartTime,
1197
- draftEndTime,
1198
- startTouched,
1199
- endTouched,
1200
- );
1201
- confirmedRef.current = true;
1202
- onChange?.(finalRange);
1203
- setOpen(false);
1204
- };
1205
-
1206
- const handlePresetClick = (preset: DateRangePreset) => {
1207
- const range = resolvePresetValue(preset.value);
1208
- setDraftRange(range);
1209
- setDraftStartTime(dateToParts(range.from));
1210
- setDraftEndTime(dateToParts(range.to));
1211
- setStartTouched(range.from != null);
1212
- setEndTouched(range.to != null);
1213
- updateFromText(range.from, dateToParts(range.from));
1214
- updateToText(range.to, dateToParts(range.to));
1215
- if (!timeCfg) {
1216
- confirmedRef.current = true;
1217
- if (!isValueControlled) setInternalValue(range);
1218
- onChange?.(range);
1219
- setOpen(false);
1220
- }
1221
- };
1222
-
1223
- const handleClear = (e: React.MouseEvent) => {
1224
- e.preventDefault();
1225
- setDraftRange(undefined);
1226
- setStartTouched(false);
1227
- setEndTouched(false);
1228
- setFromText('');
1229
- setToText('');
1230
- confirmedRef.current = true;
1231
- if (!isValueControlled) setInternalValue(undefined);
1232
- onChange?.(undefined);
1233
- fromInputRef.current?.focus();
1234
- };
1235
-
1236
- // ─── 范围约束:disable 越界日期 ────────────────────
1237
- // 用 startOfDay 比较"日"维度,避免 draftRange.from/to 携带的时间分量(showTime 模式)
1238
- // 让同一天的 cell 被误判越界。例如 from=16 00:07 时,calendar 给 to 面板的 16(00:00)
1239
- // 若用 getTime() 比较会 < from(00:07) → 误 disable;startOfDay 后两者相等 → 允许选同日。
1240
- const combinedDisabled = React.useCallback(
1241
- (date: Date) => {
1242
- if (disabledDates?.(date)) return true;
1243
- const day = startOfDay(date).getTime();
1244
- if (activeField === 'from' && draftRange?.to) {
1245
- return day > startOfDay(draftRange.to).getTime();
1246
- }
1247
- if (activeField === 'to' && draftRange?.from) {
1248
- return day < startOfDay(draftRange.from).getTime();
1249
- }
1250
- return false;
1251
- },
1252
- [disabledDates, activeField, draftRange?.from, draftRange?.to],
1253
- );
1254
-
1255
- // ─── 时间约束(showTime 同日时):disable 越界时分秒 ──
1256
- const sameDayAsOpposite = React.useMemo(() => {
1257
- if (!timeCfg) return false;
1258
- if (activeField === 'from' && draftRange?.to && draftRange?.from) {
1259
- return datesEqual(
1260
- new Date(
1261
- draftRange.from.getFullYear(),
1262
- draftRange.from.getMonth(),
1263
- draftRange.from.getDate(),
1264
- ),
1265
- new Date(
1266
- draftRange.to.getFullYear(),
1267
- draftRange.to.getMonth(),
1268
- draftRange.to.getDate(),
1269
- ),
1270
- );
1271
- }
1272
- if (activeField === 'to' && draftRange?.from && draftRange?.to) {
1273
- return datesEqual(
1274
- new Date(
1275
- draftRange.from.getFullYear(),
1276
- draftRange.from.getMonth(),
1277
- draftRange.from.getDate(),
1278
- ),
1279
- new Date(
1280
- draftRange.to.getFullYear(),
1281
- draftRange.to.getMonth(),
1282
- draftRange.to.getDate(),
1283
- ),
1284
- );
1285
- }
1286
- return false;
1287
- }, [timeCfg, activeField, draftRange?.from, draftRange?.to]);
1288
-
1289
- const oppositeTime: TimeParts | undefined = sameDayAsOpposite
1290
- ? activeField === 'from'
1291
- ? draftEndTime
1292
- : draftStartTime
1293
- : undefined;
1294
-
1295
- const timeDisabledHours = () => {
1296
- const set = new Set(timeCfg?.disabledHours?.() ?? []);
1297
- if (oppositeTime) {
1298
- if (activeField === 'from') {
1299
- // from time disable > opposite (end time)
1300
- for (let h = oppositeTime.h + 1; h <= 23; h++) set.add(h);
1301
- } else {
1302
- // to time disable < opposite (start time)
1303
- for (let h = 0; h < oppositeTime.h; h++) set.add(h);
1304
- }
1305
- }
1306
- return [...set];
1307
- };
1308
- const timeDisabledMinutes = (hour: number) => {
1309
- const set = new Set(timeCfg?.disabledMinutes?.(hour) ?? []);
1310
- if (oppositeTime && hour === oppositeTime.h) {
1311
- if (activeField === 'from') {
1312
- for (let m = oppositeTime.m + 1; m <= 59; m++) set.add(m);
1313
- } else {
1314
- for (let m = 0; m < oppositeTime.m; m++) set.add(m);
1315
- }
1316
- }
1317
- return [...set];
1318
- };
1319
- const timeDisabledSeconds = (hour: number, minute: number) => {
1320
- const set = new Set(timeCfg?.disabledSeconds?.(hour, minute) ?? []);
1321
- if (
1322
- oppositeTime &&
1323
- hour === oppositeTime.h &&
1324
- minute === oppositeTime.m
1325
- ) {
1326
- if (activeField === 'from') {
1327
- for (let s = oppositeTime.s + 1; s <= 59; s++) set.add(s);
1328
- } else {
1329
- for (let s = 0; s < oppositeTime.s; s++) set.add(s);
1330
- }
1331
- }
1332
- return [...set];
1333
- };
1334
-
1335
- // ─── preview range ────────────────────────────────
1336
- // 任何 hover 都展示"若点击 hovered 会形成的 range"(基于 activeField 模拟点击),
1337
- // 不再限制为"from 已选 / to 未选"中间态。
1338
- const previewRange = React.useMemo<DateRange | undefined>(() => {
1339
- if (!hoveredDate) return undefined;
1340
- let pFrom: Date | undefined;
1341
- let pTo: Date | undefined;
1342
- if (activeField === 'from') {
1343
- pFrom = hoveredDate;
1344
- pTo = draftRange?.to;
1345
- // clamp:若新 from > 当前 to,把 to 也设为 hovered(模拟 handleSelect clamp 行为)
1346
- if (pTo && pTo.getTime() < hoveredDate.getTime()) pTo = hoveredDate;
1347
- } else {
1348
- pFrom = draftRange?.from;
1349
- pTo = hoveredDate;
1350
- if (pFrom && pFrom.getTime() > hoveredDate.getTime())
1351
- pFrom = hoveredDate;
1352
- }
1353
- if (!pFrom && !pTo) return undefined;
1354
- return { from: pFrom, to: pTo };
1355
- }, [activeField, draftRange?.from, draftRange?.to, hoveredDate]);
1356
-
1357
- const rangeHoverProps = {
1358
- onDayMouseEnter: (day: Date) => setHoveredDate(day),
1359
- onDayMouseLeave: () => setHoveredDate(undefined),
1360
- modifiers:
1361
- previewRange && previewRange.from && previewRange.to
1362
- ? {
1363
- previewRange,
1364
- previewStart: previewRange.from,
1365
- previewEnd: previewRange.to,
1366
- }
1367
- : undefined,
1368
- modifiersClassNames: {
1369
- previewRange: PREVIEW_RANGE_CLASS,
1370
- previewStart: PREVIEW_START_CLASS,
1371
- previewEnd: PREVIEW_END_CLASS,
1372
- },
1373
- };
1374
-
1375
- // hasClear:有值显示 X,空值还原为日历 icon。默认 true,可传 hasClear={false} 关闭。
1376
- const hasAny = value?.from != null || value?.to != null;
1377
- const showClear = hasClear && hasAny && !disabled;
1378
- // calendar 锚定:showTime 单月 + 按 activeField 锚;纯日期双月以 from 起锚
1379
- const calendarDefaultMonth =
1380
- activeField === 'to'
1381
- ? draftRange?.to ?? draftRange?.from
1382
- : draftRange?.from ?? draftRange?.to;
1383
-
1384
- return (
1385
- <Popover open={open} onOpenChange={setOpen}>
1386
- <PopoverAnchor asChild>
1387
- <div
1388
- ref={anchorRef}
1389
- aria-invalid={status === 'error' ? true : undefined}
1390
- style={timeCfg ? { width: '26rem' } : undefined}
1391
- className={cn(
1392
- triggerWrapperClass,
1393
- triggerSizeClass[size],
1394
- !timeCfg && 'w-72',
1395
- status === 'error' &&
1396
- 'border-destructive aria-invalid:border-destructive',
1397
- status === 'warning' && 'border-warning',
1398
- className,
1399
- )}
1400
- >
1401
- <input
1402
- ref={fromInputRef}
1403
- type="text"
1404
- value={fromText}
1405
- onChange={(e) => setFromText(e.target.value)}
1406
- onFocus={() => {
1407
- setOpen(true);
1408
- setActiveField('from');
1409
- }}
1410
- onBlur={handleFromBlur}
1411
- placeholder={placeholders[0]}
1412
- disabled={disabled}
1413
- readOnly={inputReadOnly}
1414
- className={cn(inputElementClass, 'text-center')}
1415
- />
1416
- <span aria-hidden className="shrink-0 text-muted-foreground">
1417
- -
1418
- </span>
1419
- <input
1420
- ref={toInputRef}
1421
- type="text"
1422
- value={toText}
1423
- onChange={(e) => setToText(e.target.value)}
1424
- onFocus={() => {
1425
- setOpen(true);
1426
- setActiveField('to');
1427
- }}
1428
- onBlur={handleToBlur}
1429
- placeholder={placeholders[1]}
1430
- disabled={disabled}
1431
- readOnly={inputReadOnly}
1432
- className={cn(inputElementClass, 'text-center')}
1433
- />
1434
- {showClear ? (
1435
- <button
1436
- type="button"
1437
- aria-label="清除范围"
1438
- tabIndex={-1}
1439
- onMouseDown={handleClear}
1440
- className="flex shrink-0 cursor-pointer items-center text-muted-foreground transition-colors hover:text-foreground"
1441
- >
1442
- <X className="size-4" />
1443
- </button>
1444
- ) : (
1445
- <CalendarIcon
1446
- aria-hidden
1447
- className="size-4 shrink-0 text-muted-foreground"
1448
- />
1449
- )}
1450
- </div>
1451
- </PopoverAnchor>
1452
- <PopoverContent
1453
- className="w-auto rounded-md p-0 shadow-sm"
1454
- onOpenAutoFocus={(e) => e.preventDefault()}
1455
- onInteractOutside={(e) => {
1456
- const target = e.detail.originalEvent.target as Node | null;
1457
- if (target && anchorRef.current?.contains(target)) {
1458
- e.preventDefault();
1459
- }
1460
- }}
1461
- >
1462
- {timeCfg ? (
1463
- <>
1464
- {/* showTime 模式:单月 + 单时间面板,按 activeField 切换内容 */}
1465
- <div className="flex items-stretch">
1466
- <Calendar
1467
- mode="range"
1468
- selected={draftRange}
1469
- onSelect={handleSelect}
1470
- disabled={combinedDisabled}
1471
- numberOfMonths={1}
1472
- defaultMonth={calendarDefaultMonth}
1473
- {...datePickerCalendarProps}
1474
- {...rangeHoverProps}
1475
- />
1476
- <TimePickerPanel
1477
- value={activeField === 'to' ? draftEndTime : draftStartTime}
1478
- onChange={handleTimeChange}
1479
- format={timeCfg.format ?? DEFAULT_TIME_FORMAT}
1480
- hourStep={timeCfg.hourStep}
1481
- minuteStep={timeCfg.minuteStep}
1482
- secondStep={timeCfg.secondStep}
1483
- disabledHours={timeDisabledHours}
1484
- disabledMinutes={timeDisabledMinutes}
1485
- disabledSeconds={timeDisabledSeconds}
1486
- hasSelection={
1487
- activeField === 'to' ? endTouched : startTouched
1488
- }
1489
- padZero={false}
1490
- className="border-l border-border"
1491
- />
1492
- </div>
1493
- <div className="flex items-center justify-between gap-3 border-t border-border px-3 py-2">
1494
- <div className="flex flex-wrap items-center gap-3">
1495
- {presetList.map((p) => (
1496
- <button
1497
- key={p.label}
1498
- type="button"
1499
- className="cursor-pointer text-xs text-primary transition-colors hover:text-primary/80"
1500
- onClick={() => handlePresetClick(p)}
1501
- >
1502
- {p.label}
1503
- </button>
1504
- ))}
1505
- </div>
1506
- <Button
1507
- size="sm"
1508
- onClick={handleConfirm}
1509
- disabled={!startTouched && !endTouched}
1510
- >
1511
- 确定
1512
- </Button>
1513
- </div>
1514
- </>
1515
- ) : (
1516
- <>
1517
- {/* 纯日期:双月并排 + activeField 控制 + hover preview */}
1518
- <Calendar
1519
- mode="range"
1520
- selected={draftRange}
1521
- onSelect={handleSelect}
1522
- disabled={combinedDisabled}
1523
- numberOfMonths={numberOfMonths}
1524
- defaultMonth={calendarDefaultMonth}
1525
- {...datePickerCalendarProps}
1526
- {...rangeHoverProps}
1527
- />
1528
- {presetList.length > 0 && (
1529
- <div className="flex flex-wrap items-center gap-3 border-t border-border px-3 py-2">
1530
- {presetList.map((p) => (
1531
- <button
1532
- key={p.label}
1533
- type="button"
1534
- className="cursor-pointer text-xs text-primary transition-colors hover:text-primary/80"
1535
- onClick={() => handlePresetClick(p)}
1536
- >
1537
- {p.label}
1538
- </button>
1539
- ))}
1540
- </div>
1541
- )}
1542
- </>
1543
- )}
1544
- {footer ? (
1545
- <div className="border-t border-border p-2 text-xs">{footer}</div>
1546
- ) : null}
1547
- </PopoverContent>
1548
- </Popover>
1549
- );
1550
- },
1551
- );
1552
- DateRangePicker.displayName = 'DateRangePicker';
1553
-
1554
- export { DatePicker, DateRangePicker };