@sakoa/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/README.md +171 -0
  2. package/dist/App.d.ts +2 -0
  3. package/dist/cli/index.js +9243 -0
  4. package/dist/components/DemoSection.d.ts +30 -0
  5. package/dist/components/SApiKeyboard.d.ts +22 -0
  6. package/dist/components/SApiSection.d.ts +21 -0
  7. package/dist/components/SApiTable.d.ts +46 -0
  8. package/dist/components/STableOfContents.d.ts +2 -0
  9. package/dist/components/ui/SAlert.d.ts +76 -0
  10. package/dist/components/ui/SBadge.d.ts +56 -0
  11. package/dist/components/ui/SButton.d.ts +67 -0
  12. package/dist/components/ui/SCheckbox.d.ts +64 -0
  13. package/dist/components/ui/SChip.d.ts +43 -0
  14. package/dist/components/ui/SDatePicker.d.ts +77 -0
  15. package/dist/components/ui/SGlassButton.d.ts +70 -0
  16. package/dist/components/ui/SIcon.d.ts +29 -0
  17. package/dist/components/ui/SInput.d.ts +129 -0
  18. package/dist/components/ui/SKbd.d.ts +24 -0
  19. package/dist/components/ui/SKbdShortcut.d.ts +14 -0
  20. package/dist/components/ui/SSelect.d.ts +148 -0
  21. package/dist/components/ui/SSkeleton.d.ts +37 -0
  22. package/dist/components/ui/SSwitch.d.ts +61 -0
  23. package/dist/components/ui/STooltip.d.ts +82 -0
  24. package/dist/components/ui/accordion/SAccordionContent.d.ts +23 -0
  25. package/dist/components/ui/accordion/SAccordionItem.d.ts +70 -0
  26. package/dist/components/ui/accordion/SAccordionTrigger.d.ts +37 -0
  27. package/dist/components/ui/accordion/index.d.ts +4 -0
  28. package/dist/components/ui/avatar/SAvatar.d.ts +36 -0
  29. package/dist/components/ui/avatar/SAvatarFallback.d.ts +26 -0
  30. package/dist/components/ui/avatar/SAvatarGroup.d.ts +30 -0
  31. package/dist/components/ui/avatar/SAvatarImage.d.ts +23 -0
  32. package/dist/components/ui/avatar/index.d.ts +4 -0
  33. package/dist/components/ui/breadcrumb/SBreadcrumb.d.ts +22 -0
  34. package/dist/components/ui/breadcrumb/SBreadcrumbEllipsis.d.ts +17 -0
  35. package/dist/components/ui/breadcrumb/SBreadcrumbItem.d.ts +17 -0
  36. package/dist/components/ui/breadcrumb/SBreadcrumbLink.d.ts +26 -0
  37. package/dist/components/ui/breadcrumb/SBreadcrumbList.d.ts +17 -0
  38. package/dist/components/ui/breadcrumb/SBreadcrumbPage.d.ts +17 -0
  39. package/dist/components/ui/breadcrumb/SBreadcrumbSeparator.d.ts +17 -0
  40. package/dist/components/ui/breadcrumb/index.d.ts +7 -0
  41. package/dist/components/ui/card/SCard.d.ts +103 -0
  42. package/dist/components/ui/card/SCardActions.d.ts +44 -0
  43. package/dist/components/ui/card/SCardContent.d.ts +35 -0
  44. package/dist/components/ui/card/SCardFooter.d.ts +38 -0
  45. package/dist/components/ui/card/SCardHeader.d.ts +53 -0
  46. package/dist/components/ui/card/SCardMedia.d.ts +83 -0
  47. package/dist/components/ui/card/SGlassCard.d.ts +103 -0
  48. package/dist/components/ui/card/SMorphingCardContent.d.ts +18 -0
  49. package/dist/components/ui/card/index.d.ts +24 -0
  50. package/dist/components/ui/carousel/SCarousel.d.ts +166 -0
  51. package/dist/components/ui/carousel/index.d.ts +2 -0
  52. package/dist/components/ui/color-picker/SColorPickerAlphaSlider.d.ts +4 -0
  53. package/dist/components/ui/color-picker/SColorPickerCopy.d.ts +19 -0
  54. package/dist/components/ui/color-picker/SColorPickerEyeDropper.d.ts +17 -0
  55. package/dist/components/ui/color-picker/SColorPickerHueSlider.d.ts +4 -0
  56. package/dist/components/ui/color-picker/SColorPickerInputs.d.ts +2 -0
  57. package/dist/components/ui/color-picker/SColorPickerPresets.d.ts +9 -0
  58. package/dist/components/ui/color-picker/SColorPickerPreview.d.ts +2 -0
  59. package/dist/components/ui/color-picker/SColorPickerRecent.d.ts +7 -0
  60. package/dist/components/ui/color-picker/SColorPickerSpectrum.d.ts +4 -0
  61. package/dist/components/ui/color-picker/index.d.ts +11 -0
  62. package/dist/components/ui/drawer/index.d.ts +11 -0
  63. package/dist/components/ui/dropdown/SDropdownDivider.d.ts +8 -0
  64. package/dist/components/ui/dropdown/SDropdownGroup.d.ts +25 -0
  65. package/dist/components/ui/dropdown/SDropdownItem.d.ts +56 -0
  66. package/dist/components/ui/dropdown/index.d.ts +4 -0
  67. package/dist/components/ui/form/SForm.d.ts +38 -0
  68. package/dist/components/ui/form/SFormField.d.ts +31 -0
  69. package/dist/components/ui/form/index.d.ts +5 -0
  70. package/dist/components/ui/modal/index.d.ts +19 -0
  71. package/dist/components/ui/option/SOption.d.ts +32 -0
  72. package/dist/components/ui/option/SOptionGroup.d.ts +28 -0
  73. package/dist/components/ui/option/index.d.ts +2 -0
  74. package/dist/components/ui/otp/SOTP.d.ts +122 -0
  75. package/dist/components/ui/otp/SOTPGroup.d.ts +23 -0
  76. package/dist/components/ui/otp/SOTPSeparator.d.ts +17 -0
  77. package/dist/components/ui/otp/SOTPSlot.d.ts +49 -0
  78. package/dist/components/ui/otp/index.d.ts +7 -0
  79. package/dist/components/ui/otp/types.d.ts +26 -0
  80. package/dist/components/ui/otp/useOTPContext.d.ts +42 -0
  81. package/dist/components/ui/pagination/SPagination.d.ts +151 -0
  82. package/dist/components/ui/pagination/index.d.ts +2 -0
  83. package/dist/components/ui/progress/SProgress.d.ts +62 -0
  84. package/dist/components/ui/progress/SProgressRange.d.ts +91 -0
  85. package/dist/components/ui/progress/index.d.ts +4 -0
  86. package/dist/components/ui/radio/SRadio.d.ts +58 -0
  87. package/dist/components/ui/radio/SRadioGroup.d.ts +52 -0
  88. package/dist/components/ui/radio/index.d.ts +2 -0
  89. package/dist/components/ui/stepper/SStepper.d.ts +83 -0
  90. package/dist/components/ui/stepper/SStepperContent.d.ts +24 -0
  91. package/dist/components/ui/stepper/SStepperDescription.d.ts +20 -0
  92. package/dist/components/ui/stepper/SStepperIndicator.d.ts +37 -0
  93. package/dist/components/ui/stepper/SStepperItem.d.ts +37 -0
  94. package/dist/components/ui/stepper/SStepperSeparator.d.ts +5 -0
  95. package/dist/components/ui/stepper/SStepperTitle.d.ts +20 -0
  96. package/dist/components/ui/stepper/SStepperTrigger.d.ts +22 -0
  97. package/dist/components/ui/stepper/index.d.ts +11 -0
  98. package/dist/components/ui/table/STableBody.d.ts +27 -0
  99. package/dist/components/ui/table/STableCell.d.ts +55 -0
  100. package/dist/components/ui/table/STableColumn.d.ts +87 -0
  101. package/dist/components/ui/table/STableEmpty.d.ts +54 -0
  102. package/dist/components/ui/table/STableHeader.d.ts +25 -0
  103. package/dist/components/ui/table/STableRow.d.ts +38 -0
  104. package/dist/components/ui/table/STableSkeleton.d.ts +29 -0
  105. package/dist/components/ui/table/index.d.ts +98 -0
  106. package/dist/components/ui/table/useDataTable.d.ts +80 -0
  107. package/dist/components/ui/tabs/STabPane.d.ts +31 -0
  108. package/dist/components/ui/tabs/STabsContent.d.ts +21 -0
  109. package/dist/components/ui/tabs/STabsIndicator.d.ts +9 -0
  110. package/dist/components/ui/tabs/STabsTrigger.d.ts +28 -0
  111. package/dist/components/ui/tabs/index.d.ts +6 -0
  112. package/dist/components/ui/toast/SToast.d.ts +49 -0
  113. package/dist/components/ui/toast/SToastContainer.d.ts +21 -0
  114. package/dist/components/ui/toast/index.d.ts +2 -0
  115. package/dist/composables/useAsync.d.ts +134 -0
  116. package/dist/composables/useClickOutside.d.ts +69 -0
  117. package/dist/composables/useClipboard.d.ts +46 -0
  118. package/dist/composables/useDebounce.d.ts +150 -0
  119. package/dist/composables/useDialog.d.ts +118 -0
  120. package/dist/composables/useForm.d.ts +204 -0
  121. package/dist/composables/useHotkey.d.ts +128 -0
  122. package/dist/composables/useIntersectionObserver.d.ts +156 -0
  123. package/dist/composables/useLocalStorage.d.ts +120 -0
  124. package/dist/composables/useMediaQuery.d.ts +115 -0
  125. package/dist/composables/useTheme.d.ts +8 -0
  126. package/dist/composables/useToast.d.ts +1619 -0
  127. package/dist/index.d.ts +71 -0
  128. package/dist/layouts/UILayout.d.ts +2 -0
  129. package/dist/lib/utils.d.ts +2 -0
  130. package/dist/main.d.ts +0 -0
  131. package/dist/router.d.ts +2 -0
  132. package/dist/saka-ui.css +1 -0
  133. package/dist/saka-ui.js +18513 -0
  134. package/dist/saka-ui.umd.cjs +38 -0
  135. package/dist/views/docs/CustomizationView.d.ts +2 -0
  136. package/dist/views/docs/FormValidationView.d.ts +2 -0
  137. package/dist/views/docs/StylingGuideView.d.ts +2 -0
  138. package/dist/views/docs/UseAsyncView.d.ts +2 -0
  139. package/dist/views/docs/UseClickOutsideView.d.ts +124 -0
  140. package/dist/views/docs/UseClipboardView.d.ts +4 -0
  141. package/dist/views/docs/UseDebounceView.d.ts +2 -0
  142. package/dist/views/docs/UseHotkeyView.d.ts +205 -0
  143. package/dist/views/docs/UseIntersectionObserverView.d.ts +5 -0
  144. package/dist/views/docs/UseLocalStorageView.d.ts +2 -0
  145. package/dist/views/docs/UseMediaQueryView.d.ts +2 -0
  146. package/dist/views/docs/UseThemeView.d.ts +2 -0
  147. package/dist/views/examples/AuthFormView.d.ts +2 -0
  148. package/dist/views/examples/CreditCardFormView.d.ts +6 -0
  149. package/dist/views/examples/FormFieldExampleView.d.ts +2 -0
  150. package/dist/views/examples/ProjectFormView.d.ts +2 -0
  151. package/dist/views/ui/AccordionView.d.ts +2 -0
  152. package/dist/views/ui/AlertView.d.ts +2 -0
  153. package/dist/views/ui/AvatarView.d.ts +2 -0
  154. package/dist/views/ui/BadgeView.d.ts +2 -0
  155. package/dist/views/ui/BreadcrumbView.d.ts +2 -0
  156. package/dist/views/ui/ButtonView.d.ts +2 -0
  157. package/dist/views/ui/CardView.d.ts +2 -0
  158. package/dist/views/ui/CarouselView.d.ts +274 -0
  159. package/dist/views/ui/CheckboxView.d.ts +2 -0
  160. package/dist/views/ui/ChipView.d.ts +2 -0
  161. package/dist/views/ui/ColorPickerView.d.ts +2 -0
  162. package/dist/views/ui/DatePickerView.d.ts +2 -0
  163. package/dist/views/ui/DialogView.d.ts +2 -0
  164. package/dist/views/ui/DrawerView.d.ts +2 -0
  165. package/dist/views/ui/DropdownView.d.ts +2 -0
  166. package/dist/views/ui/GlassButtonView.d.ts +2 -0
  167. package/dist/views/ui/GlassCardView.d.ts +2 -0
  168. package/dist/views/ui/HomeView.d.ts +2 -0
  169. package/dist/views/ui/IconsView.d.ts +2 -0
  170. package/dist/views/ui/InputView.d.ts +2 -0
  171. package/dist/views/ui/KbdView.d.ts +2 -0
  172. package/dist/views/ui/ModalView.d.ts +2 -0
  173. package/dist/views/ui/MorphingCardView.d.ts +2 -0
  174. package/dist/views/ui/MorphingModalView.d.ts +2 -0
  175. package/dist/views/ui/OTPView.d.ts +206 -0
  176. package/dist/views/ui/PaginationView.d.ts +2 -0
  177. package/dist/views/ui/ProgressView.d.ts +2 -0
  178. package/dist/views/ui/RadioView.d.ts +2 -0
  179. package/dist/views/ui/SelectView.d.ts +2 -0
  180. package/dist/views/ui/SkeletonView.d.ts +2 -0
  181. package/dist/views/ui/StepperView.d.ts +2 -0
  182. package/dist/views/ui/SwitchView.d.ts +2 -0
  183. package/dist/views/ui/TableView.d.ts +2 -0
  184. package/dist/views/ui/TabsView.d.ts +2 -0
  185. package/dist/views/ui/ToastView.d.ts +2 -0
  186. package/dist/views/ui/TooltipView.d.ts +2 -0
  187. package/dist/vite.svg +1 -0
  188. package/package.json +64 -0
  189. package/registry/components/accordion.json +19 -0
  190. package/registry/components/alert.json +17 -0
  191. package/registry/components/avatar.json +18 -0
  192. package/registry/components/badge.json +14 -0
  193. package/registry/components/breadcrumb.json +24 -0
  194. package/registry/components/button.json +17 -0
  195. package/registry/components/card.json +23 -0
  196. package/registry/components/carousel.json +19 -0
  197. package/registry/components/checkbox.json +17 -0
  198. package/registry/components/chip.json +17 -0
  199. package/registry/components/color-picker.json +24 -0
  200. package/registry/components/date-picker.json +17 -0
  201. package/registry/components/drawer.json +26 -0
  202. package/registry/components/dropdown.json +21 -0
  203. package/registry/components/form.json +16 -0
  204. package/registry/components/glass-button.json +17 -0
  205. package/registry/components/icon.json +17 -0
  206. package/registry/components/input.json +17 -0
  207. package/registry/components/kbd.json +16 -0
  208. package/registry/components/modal.json +32 -0
  209. package/registry/components/option.json +16 -0
  210. package/registry/components/otp.json +23 -0
  211. package/registry/components/pagination.json +18 -0
  212. package/registry/components/progress.json +16 -0
  213. package/registry/components/radio.json +19 -0
  214. package/registry/components/select.json +17 -0
  215. package/registry/components/skeleton.json +14 -0
  216. package/registry/components/switch.json +17 -0
  217. package/registry/components/table.json +26 -0
  218. package/registry/components/tabs.json +19 -0
  219. package/registry/components/toast.json +19 -0
  220. package/registry/components/tooltip.json +14 -0
  221. package/registry/index.json +4 -0
  222. package/registry/source/components/ui/SAlert.vue +388 -0
  223. package/registry/source/components/ui/SBadge.vue +243 -0
  224. package/registry/source/components/ui/SButton.vue +387 -0
  225. package/registry/source/components/ui/SCheckbox.vue +310 -0
  226. package/registry/source/components/ui/SChip.vue +130 -0
  227. package/registry/source/components/ui/SDatePicker.vue +1290 -0
  228. package/registry/source/components/ui/SGlassButton.vue +547 -0
  229. package/registry/source/components/ui/SIcon.vue +78 -0
  230. package/registry/source/components/ui/SInput.vue +1054 -0
  231. package/registry/source/components/ui/SKbd.vue +96 -0
  232. package/registry/source/components/ui/SKbdShortcut.vue +36 -0
  233. package/registry/source/components/ui/SSelect.vue +1290 -0
  234. package/registry/source/components/ui/SSkeleton.vue +185 -0
  235. package/registry/source/components/ui/SSwitch.vue +275 -0
  236. package/registry/source/components/ui/STooltip.vue +491 -0
  237. package/registry/source/components/ui/accordion/SAccordion.vue +248 -0
  238. package/registry/source/components/ui/accordion/SAccordionItem.vue +353 -0
  239. package/registry/source/components/ui/accordion/index.ts +5 -0
  240. package/registry/source/components/ui/avatar/SAvatar.vue +169 -0
  241. package/registry/source/components/ui/avatar/SAvatarFallback.vue +66 -0
  242. package/registry/source/components/ui/avatar/SAvatarGroup.vue +69 -0
  243. package/registry/source/components/ui/avatar/SAvatarImage.vue +92 -0
  244. package/registry/source/components/ui/avatar/index.ts +5 -0
  245. package/registry/source/components/ui/breadcrumb/SBreadcrumb.vue +23 -0
  246. package/registry/source/components/ui/breadcrumb/SBreadcrumbEllipsis.vue +17 -0
  247. package/registry/source/components/ui/breadcrumb/SBreadcrumbItem.vue +14 -0
  248. package/registry/source/components/ui/breadcrumb/SBreadcrumbLink.vue +46 -0
  249. package/registry/source/components/ui/breadcrumb/SBreadcrumbList.vue +17 -0
  250. package/registry/source/components/ui/breadcrumb/SBreadcrumbPage.vue +15 -0
  251. package/registry/source/components/ui/breadcrumb/SBreadcrumbSeparator.vue +18 -0
  252. package/registry/source/components/ui/breadcrumb/index.ts +7 -0
  253. package/registry/source/components/ui/card/SCard.vue +517 -0
  254. package/registry/source/components/ui/card/SCardActions.vue +129 -0
  255. package/registry/source/components/ui/card/SCardContent.vue +117 -0
  256. package/registry/source/components/ui/card/SCardFooter.vue +103 -0
  257. package/registry/source/components/ui/card/SCardHeader.vue +163 -0
  258. package/registry/source/components/ui/card/SCardMedia.vue +312 -0
  259. package/registry/source/components/ui/card/index.ts +34 -0
  260. package/registry/source/components/ui/carousel/SCarousel.vue +1069 -0
  261. package/registry/source/components/ui/carousel/SCarouselSlide.vue +107 -0
  262. package/registry/source/components/ui/carousel/index.ts +3 -0
  263. package/registry/source/components/ui/color-picker/SColorPicker.vue +772 -0
  264. package/registry/source/components/ui/color-picker/SColorPickerAlphaSlider.vue +158 -0
  265. package/registry/source/components/ui/color-picker/SColorPickerCopy.vue +76 -0
  266. package/registry/source/components/ui/color-picker/SColorPickerEyeDropper.vue +68 -0
  267. package/registry/source/components/ui/color-picker/SColorPickerHueSlider.vue +138 -0
  268. package/registry/source/components/ui/color-picker/SColorPickerInputs.vue +227 -0
  269. package/registry/source/components/ui/color-picker/SColorPickerPresets.vue +87 -0
  270. package/registry/source/components/ui/color-picker/SColorPickerPreview.vue +46 -0
  271. package/registry/source/components/ui/color-picker/SColorPickerRecent.vue +74 -0
  272. package/registry/source/components/ui/color-picker/SColorPickerSpectrum.vue +149 -0
  273. package/registry/source/components/ui/color-picker/index.ts +11 -0
  274. package/registry/source/components/ui/drawer/SDrawer.vue +797 -0
  275. package/registry/source/components/ui/drawer/SDrawerClose.vue +64 -0
  276. package/registry/source/components/ui/drawer/SDrawerContent.vue +81 -0
  277. package/registry/source/components/ui/drawer/SDrawerDescription.vue +40 -0
  278. package/registry/source/components/ui/drawer/SDrawerFooter.vue +97 -0
  279. package/registry/source/components/ui/drawer/SDrawerHandle.vue +79 -0
  280. package/registry/source/components/ui/drawer/SDrawerHeader.vue +117 -0
  281. package/registry/source/components/ui/drawer/SDrawerTitle.vue +40 -0
  282. package/registry/source/components/ui/drawer/SDrawerTrigger.vue +51 -0
  283. package/registry/source/components/ui/drawer/index.ts +20 -0
  284. package/registry/source/components/ui/dropdown/SDropdown.vue +843 -0
  285. package/registry/source/components/ui/dropdown/SDropdownDivider.vue +23 -0
  286. package/registry/source/components/ui/dropdown/SDropdownGroup.vue +53 -0
  287. package/registry/source/components/ui/dropdown/SDropdownItem.vue +179 -0
  288. package/registry/source/components/ui/dropdown/index.ts +5 -0
  289. package/registry/source/components/ui/form/SForm.vue +84 -0
  290. package/registry/source/components/ui/form/SFormField.vue +78 -0
  291. package/registry/source/components/ui/form/index.ts +8 -0
  292. package/registry/source/components/ui/modal/SModal.vue +648 -0
  293. package/registry/source/components/ui/modal/SModalClose.vue +49 -0
  294. package/registry/source/components/ui/modal/SModalContent.vue +74 -0
  295. package/registry/source/components/ui/modal/SModalDescription.vue +39 -0
  296. package/registry/source/components/ui/modal/SModalFooter.vue +84 -0
  297. package/registry/source/components/ui/modal/SModalHeader.vue +107 -0
  298. package/registry/source/components/ui/modal/SModalTitle.vue +39 -0
  299. package/registry/source/components/ui/modal/SModalTrigger.vue +61 -0
  300. package/registry/source/components/ui/modal/SMorphingModal.vue +429 -0
  301. package/registry/source/components/ui/modal/SMorphingModalClose.vue +42 -0
  302. package/registry/source/components/ui/modal/SMorphingModalDescription.vue +49 -0
  303. package/registry/source/components/ui/modal/SMorphingModalImage.vue +44 -0
  304. package/registry/source/components/ui/modal/SMorphingModalSubtitle.vue +29 -0
  305. package/registry/source/components/ui/modal/SMorphingModalTitle.vue +34 -0
  306. package/registry/source/components/ui/modal/SMorphingModalTrigger.vue +95 -0
  307. package/registry/source/components/ui/modal/index.ts +32 -0
  308. package/registry/source/components/ui/option/SOption.vue +180 -0
  309. package/registry/source/components/ui/option/SOptionGroup.vue +77 -0
  310. package/registry/source/components/ui/option/index.ts +3 -0
  311. package/registry/source/components/ui/otp/SOTP.vue +843 -0
  312. package/registry/source/components/ui/otp/SOTPGroup.vue +29 -0
  313. package/registry/source/components/ui/otp/SOTPSeparator.vue +15 -0
  314. package/registry/source/components/ui/otp/SOTPSlot.vue +462 -0
  315. package/registry/source/components/ui/otp/index.ts +7 -0
  316. package/registry/source/components/ui/otp/types.ts +27 -0
  317. package/registry/source/components/ui/otp/useOTPContext.ts +62 -0
  318. package/registry/source/components/ui/pagination/SPagination.vue +923 -0
  319. package/registry/source/components/ui/pagination/index.ts +8 -0
  320. package/registry/source/components/ui/progress/SProgress.vue +635 -0
  321. package/registry/source/components/ui/progress/SProgressRange.vue +715 -0
  322. package/registry/source/components/ui/progress/index.ts +4 -0
  323. package/registry/source/components/ui/radio/SRadio.vue +407 -0
  324. package/registry/source/components/ui/radio/SRadioGroup.vue +200 -0
  325. package/registry/source/components/ui/radio/index.ts +3 -0
  326. package/registry/source/components/ui/table/SDataTable.vue +828 -0
  327. package/registry/source/components/ui/table/STableBody.vue +70 -0
  328. package/registry/source/components/ui/table/STableCell.vue +147 -0
  329. package/registry/source/components/ui/table/STableColumn.vue +120 -0
  330. package/registry/source/components/ui/table/STableEmpty.vue +159 -0
  331. package/registry/source/components/ui/table/STableHeader.vue +132 -0
  332. package/registry/source/components/ui/table/STableRow.vue +106 -0
  333. package/registry/source/components/ui/table/STableSkeleton.vue +208 -0
  334. package/registry/source/components/ui/table/index.ts +126 -0
  335. package/registry/source/components/ui/table/useDataTable.ts +519 -0
  336. package/registry/source/components/ui/tabs/STabPane.vue +130 -0
  337. package/registry/source/components/ui/tabs/STabs.vue +467 -0
  338. package/registry/source/components/ui/tabs/index.ts +7 -0
  339. package/registry/source/components/ui/toast/SToast.vue +261 -0
  340. package/registry/source/components/ui/toast/SToastContainer.vue +209 -0
  341. package/registry/source/components/ui/toast/index.ts +2 -0
  342. package/registry/source/composables/useForm.ts +960 -0
  343. package/registry/source/composables/useTheme.ts +86 -0
  344. package/registry/source/composables/useToast.ts +440 -0
  345. package/registry/source/lib/utils.ts +6 -0
@@ -0,0 +1,1290 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
3
+ import { cn } from '../../lib/utils'
4
+
5
+ defineOptions({ inheritAttrs: false })
6
+
7
+ export interface Props {
8
+ // Core
9
+ modelValue?: Date | Date[] | [Date, Date] | null
10
+ mode?: 'single' | 'range' | 'multiple'
11
+
12
+ // Display
13
+ variant?: 'outlined' | 'filled' | 'ghost'
14
+ size?: 'small' | 'medium' | 'large'
15
+ color?: string
16
+ rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full'
17
+
18
+ // Formatting
19
+ format?: string
20
+ placeholder?: string
21
+
22
+ // Constraints
23
+ minDate?: Date
24
+ maxDate?: Date
25
+ disabledDates?: Date[] | ((date: Date) => boolean)
26
+ disabledWeekdays?: number[]
27
+
28
+ // Behavior
29
+ disabled?: boolean
30
+ loading?: boolean
31
+ readonly?: boolean
32
+ clearable?: boolean
33
+ closeOnSelect?: boolean
34
+ teleport?: boolean | string
35
+
36
+ // Calendar Options
37
+ showWeekNumbers?: boolean
38
+ firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6
39
+ monthsToShow?: 1 | 2
40
+ showToday?: boolean
41
+
42
+ // Time Picker
43
+ enableTime?: boolean
44
+ timeFormat?: '12h' | '24h'
45
+ minuteStep?: number
46
+
47
+ // Labels
48
+ label?: string
49
+ labelPlacement?: 'top' | 'left'
50
+ }
51
+
52
+ const props = withDefaults(defineProps<Props>(), {
53
+ modelValue: null,
54
+ mode: 'single',
55
+ variant: 'outlined',
56
+ size: 'medium',
57
+ color: 'var(--s-primary)',
58
+ rounded: 'md',
59
+ format: 'MMM dd, yyyy',
60
+ placeholder: 'Select date...',
61
+ minDate: undefined,
62
+ maxDate: undefined,
63
+ disabledDates: undefined,
64
+ disabledWeekdays: () => [],
65
+ disabled: false,
66
+ loading: false,
67
+ readonly: false,
68
+ clearable: true,
69
+ closeOnSelect: true,
70
+ teleport: true,
71
+ showWeekNumbers: false,
72
+ firstDayOfWeek: 0,
73
+ monthsToShow: 1,
74
+ showToday: true,
75
+ enableTime: false,
76
+ timeFormat: '24h',
77
+ minuteStep: 5,
78
+ label: undefined,
79
+ labelPlacement: 'top'
80
+ })
81
+
82
+ const emit = defineEmits<{
83
+ 'update:modelValue': [value: Date | Date[] | [Date, Date] | null]
84
+ open: []
85
+ close: []
86
+ clear: []
87
+ monthChange: [month: number, year: number]
88
+ yearChange: [year: number]
89
+ }>()
90
+
91
+ // Active text class: use primary-foreground for default primary, white for custom colors
92
+ const activeTextClass = computed(() => props.color === 'var(--s-primary)' ? 'text-primary-foreground' : 'text-white')
93
+
94
+ // Refs
95
+ const triggerRef = ref<HTMLElement | null>(null)
96
+ const calendarRef = ref<HTMLElement | null>(null)
97
+ const isOpen = ref(false)
98
+ const isFocused = ref(false)
99
+
100
+ // Calendar state
101
+ const viewDate = ref(new Date())
102
+ const hoverDate = ref<Date | null>(null)
103
+ const rangeStart = ref<Date | null>(null)
104
+
105
+ // Month/Year picker state
106
+ const showMonthPicker = ref(false)
107
+ const showYearPicker = ref(false)
108
+ const yearPickerRef = ref<HTMLElement | null>(null)
109
+
110
+ // Time state
111
+ const selectedHour = ref(0)
112
+ const selectedMinute = ref(0)
113
+ const isAM = ref(true)
114
+
115
+ // Ripple state
116
+ const ripples = ref<{ id: number; x: number; y: number; size: number }[]>([])
117
+ let rippleId = 0
118
+
119
+ // Dropdown position
120
+ const dropdownPosition = ref<{
121
+ top?: number
122
+ bottom?: number
123
+ left: number
124
+ width: number
125
+ placement: 'top' | 'bottom'
126
+ }>({ top: 0, left: 0, width: 0, placement: 'bottom' })
127
+
128
+ // Month and weekday names
129
+ const monthNames = [
130
+ 'January', 'February', 'March', 'April', 'May', 'June',
131
+ 'July', 'August', 'September', 'October', 'November', 'December'
132
+ ]
133
+
134
+ const monthNamesShort = [
135
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
136
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
137
+ ]
138
+
139
+ const weekdayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
140
+
141
+ // Computed
142
+ const weekdaysOrdered = computed(() => {
143
+ const days = [...weekdayNames]
144
+ for (let i = 0; i < props.firstDayOfWeek; i++) {
145
+ days.push(days.shift()!)
146
+ }
147
+ return days
148
+ })
149
+
150
+ const displayValue = computed(() => {
151
+ if (!props.modelValue) return ''
152
+
153
+ if (props.mode === 'single' && props.modelValue instanceof Date) {
154
+ return formatDate(props.modelValue)
155
+ }
156
+
157
+ if (props.mode === 'range' && Array.isArray(props.modelValue) && props.modelValue.length === 2) {
158
+ const [start, end] = props.modelValue as [Date, Date]
159
+ return `${formatDate(start)} - ${formatDate(end)}`
160
+ }
161
+
162
+ if (props.mode === 'multiple' && Array.isArray(props.modelValue)) {
163
+ const dates = props.modelValue as Date[]
164
+ if (dates.length <= 2) {
165
+ return dates.map(d => formatDate(d)).join(', ')
166
+ }
167
+ return `${dates.length} dates selected`
168
+ }
169
+
170
+ return ''
171
+ })
172
+
173
+ const hasValue = computed(() => {
174
+ if (!props.modelValue) return false
175
+ if (Array.isArray(props.modelValue)) return props.modelValue.length > 0
176
+ return true
177
+ })
178
+
179
+ const calendarDays = computed(() => {
180
+ const year = viewDate.value.getFullYear()
181
+ const month = viewDate.value.getMonth()
182
+
183
+ const firstDay = new Date(year, month, 1)
184
+ const lastDay = new Date(year, month + 1, 0)
185
+
186
+ const days: { date: Date; isCurrentMonth: boolean; isToday: boolean; isSelected: boolean; isInRange: boolean; isRangeStart: boolean; isRangeEnd: boolean; isDisabled: boolean }[] = []
187
+
188
+ // Previous month days
189
+ let startDayOfWeek = firstDay.getDay() - props.firstDayOfWeek
190
+ if (startDayOfWeek < 0) startDayOfWeek += 7
191
+
192
+ for (let i = startDayOfWeek - 1; i >= 0; i--) {
193
+ const date = new Date(year, month, -i)
194
+ days.push({
195
+ date,
196
+ isCurrentMonth: false,
197
+ isToday: isToday(date),
198
+ isSelected: isSelected(date),
199
+ isInRange: isInRange(date),
200
+ isRangeStart: isRangeStart(date),
201
+ isRangeEnd: isRangeEnd(date),
202
+ isDisabled: isDateDisabled(date)
203
+ })
204
+ }
205
+
206
+ // Current month days
207
+ for (let i = 1; i <= lastDay.getDate(); i++) {
208
+ const date = new Date(year, month, i)
209
+ days.push({
210
+ date,
211
+ isCurrentMonth: true,
212
+ isToday: isToday(date),
213
+ isSelected: isSelected(date),
214
+ isInRange: isInRange(date),
215
+ isRangeStart: isRangeStart(date),
216
+ isRangeEnd: isRangeEnd(date),
217
+ isDisabled: isDateDisabled(date)
218
+ })
219
+ }
220
+
221
+ // Next month days
222
+ const remainingDays = 42 - days.length
223
+ for (let i = 1; i <= remainingDays; i++) {
224
+ const date = new Date(year, month + 1, i)
225
+ days.push({
226
+ date,
227
+ isCurrentMonth: false,
228
+ isToday: isToday(date),
229
+ isSelected: isSelected(date),
230
+ isInRange: isInRange(date),
231
+ isRangeStart: isRangeStart(date),
232
+ isRangeEnd: isRangeEnd(date),
233
+ isDisabled: isDateDisabled(date)
234
+ })
235
+ }
236
+
237
+ return days
238
+ })
239
+
240
+ const weeks = computed(() => {
241
+ const result: typeof calendarDays.value[] = []
242
+ for (let i = 0; i < calendarDays.value.length; i += 7) {
243
+ result.push(calendarDays.value.slice(i, i + 7))
244
+ }
245
+ return result
246
+ })
247
+
248
+ const years = computed(() => {
249
+ const currentYear = new Date().getFullYear()
250
+ const years: number[] = []
251
+
252
+ // Default range: 100 years before, 50 years after
253
+ let startYear = currentYear - 100
254
+ let endYear = currentYear + 50
255
+
256
+ // Respect maxDate if provided
257
+ if (props.maxDate) {
258
+ endYear = Math.min(endYear, props.maxDate.getFullYear())
259
+ }
260
+
261
+ // Respect minDate if provided
262
+ if (props.minDate) {
263
+ startYear = Math.max(startYear, props.minDate.getFullYear())
264
+ }
265
+
266
+ for (let i = startYear; i <= endYear; i++) {
267
+ years.push(i)
268
+ }
269
+ return years
270
+ })
271
+
272
+ // Helper functions
273
+ function formatDate(date: Date): string {
274
+ if (props.enableTime) {
275
+ const hours = date.getHours()
276
+ const minutes = date.getMinutes()
277
+ const timeStr = props.timeFormat === '12h'
278
+ ? `${hours % 12 || 12}:${String(minutes).padStart(2, '0')} ${hours >= 12 ? 'PM' : 'AM'}`
279
+ : `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`
280
+ return `${monthNamesShort[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()} ${timeStr}`
281
+ }
282
+ return `${monthNamesShort[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`
283
+ }
284
+
285
+ function isSameDay(date1: Date, date2: Date): boolean {
286
+ return date1.getFullYear() === date2.getFullYear() &&
287
+ date1.getMonth() === date2.getMonth() &&
288
+ date1.getDate() === date2.getDate()
289
+ }
290
+
291
+ function isToday(date: Date): boolean {
292
+ return isSameDay(date, new Date())
293
+ }
294
+
295
+ function isSelected(date: Date): boolean {
296
+ if (!props.modelValue) return false
297
+
298
+ if (props.mode === 'single' && props.modelValue instanceof Date) {
299
+ return isSameDay(date, props.modelValue)
300
+ }
301
+
302
+ if (props.mode === 'range' && Array.isArray(props.modelValue)) {
303
+ const [start, end] = props.modelValue as [Date, Date]
304
+ if (start && isSameDay(date, start)) return true
305
+ if (end && isSameDay(date, end)) return true
306
+ return false
307
+ }
308
+
309
+ if (props.mode === 'multiple' && Array.isArray(props.modelValue)) {
310
+ return (props.modelValue as Date[]).some(d => isSameDay(date, d))
311
+ }
312
+
313
+ return false
314
+ }
315
+
316
+ function isInRange(date: Date): boolean {
317
+ if (props.mode !== 'range') return false
318
+
319
+ let start: Date | null = null
320
+ let end: Date | null = null
321
+
322
+ if (rangeStart.value && hoverDate.value) {
323
+ start = rangeStart.value < hoverDate.value ? rangeStart.value : hoverDate.value
324
+ end = rangeStart.value < hoverDate.value ? hoverDate.value : rangeStart.value
325
+ } else if (Array.isArray(props.modelValue) && props.modelValue.length === 2) {
326
+ [start, end] = props.modelValue as [Date, Date]
327
+ }
328
+
329
+ if (!start || !end) return false
330
+
331
+ return date > start && date < end
332
+ }
333
+
334
+ function isRangeStart(date: Date): boolean {
335
+ if (props.mode !== 'range') return false
336
+
337
+ if (rangeStart.value && !hoverDate.value) {
338
+ return isSameDay(date, rangeStart.value)
339
+ }
340
+
341
+ if (Array.isArray(props.modelValue) && props.modelValue.length >= 1) {
342
+ const start = props.modelValue[0] as Date
343
+ return isSameDay(date, start)
344
+ }
345
+
346
+ return false
347
+ }
348
+
349
+ function isRangeEnd(date: Date): boolean {
350
+ if (props.mode !== 'range') return false
351
+
352
+ if (Array.isArray(props.modelValue) && props.modelValue.length === 2) {
353
+ const end = props.modelValue[1] as Date
354
+ return isSameDay(date, end)
355
+ }
356
+
357
+ return false
358
+ }
359
+
360
+ function isDateDisabled(date: Date): boolean {
361
+ // Check weekday
362
+ if (props.disabledWeekdays?.includes(date.getDay())) {
363
+ return true
364
+ }
365
+
366
+ // Check min date
367
+ if (props.minDate) {
368
+ const min = new Date(props.minDate)
369
+ min.setHours(0, 0, 0, 0)
370
+ const checkDate = new Date(date)
371
+ checkDate.setHours(0, 0, 0, 0)
372
+ if (checkDate < min) return true
373
+ }
374
+
375
+ // Check max date
376
+ if (props.maxDate) {
377
+ const max = new Date(props.maxDate)
378
+ max.setHours(0, 0, 0, 0)
379
+ const checkDate = new Date(date)
380
+ checkDate.setHours(0, 0, 0, 0)
381
+ if (checkDate > max) return true
382
+ }
383
+
384
+ // Check disabled dates
385
+ if (props.disabledDates) {
386
+ if (typeof props.disabledDates === 'function') {
387
+ return props.disabledDates(date)
388
+ }
389
+ return props.disabledDates.some(d => isSameDay(date, d))
390
+ }
391
+
392
+ return false
393
+ }
394
+
395
+ // Actions
396
+ function createRipple(event: MouseEvent, element: HTMLElement) {
397
+ const rect = element.getBoundingClientRect()
398
+ const size = Math.max(rect.width, rect.height) * 2
399
+ const x = event.clientX - rect.left - size / 2
400
+ const y = event.clientY - rect.top - size / 2
401
+
402
+ const id = rippleId++
403
+ ripples.value.push({ id, x, y, size })
404
+
405
+ setTimeout(() => {
406
+ ripples.value = ripples.value.filter(r => r.id !== id)
407
+ }, 600)
408
+ }
409
+
410
+ function selectDate(date: Date, event?: MouseEvent) {
411
+ if (isDateDisabled(date)) return
412
+
413
+ if (event?.currentTarget) {
414
+ createRipple(event, event.currentTarget as HTMLElement)
415
+ }
416
+
417
+ const selectedDate = new Date(date)
418
+ if (props.enableTime) {
419
+ selectedDate.setHours(selectedHour.value, selectedMinute.value)
420
+ }
421
+
422
+ if (props.mode === 'single') {
423
+ emit('update:modelValue', selectedDate)
424
+ if (props.closeOnSelect && !props.enableTime) {
425
+ close()
426
+ }
427
+ } else if (props.mode === 'range') {
428
+ if (!rangeStart.value) {
429
+ rangeStart.value = selectedDate
430
+ emit('update:modelValue', [selectedDate] as any)
431
+ } else {
432
+ const start = rangeStart.value
433
+ const end = selectedDate
434
+ const sortedRange: [Date, Date] = start < end ? [start, end] : [end, start]
435
+ emit('update:modelValue', sortedRange)
436
+ rangeStart.value = null
437
+ if (props.closeOnSelect && !props.enableTime) {
438
+ close()
439
+ }
440
+ }
441
+ } else if (props.mode === 'multiple') {
442
+ const current = (props.modelValue as Date[]) || []
443
+ const existingIndex = current.findIndex(d => isSameDay(d, selectedDate))
444
+
445
+ if (existingIndex >= 0) {
446
+ const newDates = [...current]
447
+ newDates.splice(existingIndex, 1)
448
+ emit('update:modelValue', newDates)
449
+ } else {
450
+ emit('update:modelValue', [...current, selectedDate])
451
+ }
452
+ }
453
+ }
454
+
455
+ function previousMonth() {
456
+ const newDate = new Date(viewDate.value)
457
+ newDate.setMonth(newDate.getMonth() - 1)
458
+ viewDate.value = newDate
459
+ emit('monthChange', newDate.getMonth(), newDate.getFullYear())
460
+ }
461
+
462
+ function nextMonth() {
463
+ const newDate = new Date(viewDate.value)
464
+ newDate.setMonth(newDate.getMonth() + 1)
465
+ viewDate.value = newDate
466
+ emit('monthChange', newDate.getMonth(), newDate.getFullYear())
467
+ }
468
+
469
+ function setMonth(month: number) {
470
+ const newDate = new Date(viewDate.value)
471
+ newDate.setMonth(month)
472
+ viewDate.value = newDate
473
+ emit('monthChange', month, newDate.getFullYear())
474
+ }
475
+
476
+ function setYear(year: number) {
477
+ const newDate = new Date(viewDate.value)
478
+ newDate.setFullYear(year)
479
+ viewDate.value = newDate
480
+ emit('yearChange', year)
481
+ }
482
+
483
+ function goToToday() {
484
+ viewDate.value = new Date()
485
+ }
486
+
487
+ function clear(event?: Event) {
488
+ event?.stopPropagation()
489
+ emit('update:modelValue', null)
490
+ rangeStart.value = null
491
+ emit('clear')
492
+ }
493
+
494
+ function updateDropdownPosition() {
495
+ if (!triggerRef.value) return
496
+
497
+ const rect = triggerRef.value.getBoundingClientRect()
498
+ const viewportHeight = window.innerHeight
499
+ const spaceBelow = viewportHeight - rect.bottom
500
+ const spaceAbove = rect.top
501
+ const dropdownHeight = 380
502
+
503
+ if (spaceBelow >= dropdownHeight || spaceBelow >= spaceAbove) {
504
+ dropdownPosition.value = {
505
+ top: rect.bottom + 8,
506
+ left: rect.left,
507
+ width: Math.max(rect.width, 320),
508
+ placement: 'bottom'
509
+ }
510
+ } else {
511
+ dropdownPosition.value = {
512
+ bottom: viewportHeight - rect.top + 8,
513
+ left: rect.left,
514
+ width: Math.max(rect.width, 320),
515
+ placement: 'top'
516
+ }
517
+ }
518
+ }
519
+
520
+ function open() {
521
+ if (props.disabled || props.readonly) return
522
+ isOpen.value = true
523
+ updateDropdownPosition()
524
+ emit('open')
525
+
526
+ // Set view to current selection or today
527
+ if (props.modelValue instanceof Date) {
528
+ viewDate.value = new Date(props.modelValue)
529
+ if (props.enableTime) {
530
+ selectedHour.value = props.modelValue.getHours()
531
+ selectedMinute.value = props.modelValue.getMinutes()
532
+ }
533
+ } else if (Array.isArray(props.modelValue) && props.modelValue.length > 0) {
534
+ viewDate.value = new Date(props.modelValue[0])
535
+ if (props.enableTime && props.modelValue[0] instanceof Date) {
536
+ selectedHour.value = props.modelValue[0].getHours()
537
+ selectedMinute.value = props.modelValue[0].getMinutes()
538
+ }
539
+ } else {
540
+ viewDate.value = new Date()
541
+ }
542
+
543
+ nextTick(() => {
544
+ window.addEventListener('scroll', updateDropdownPosition, true)
545
+ window.addEventListener('resize', updateDropdownPosition)
546
+ })
547
+ }
548
+
549
+ function close() {
550
+ isOpen.value = false
551
+ if (props.mode === 'range' && rangeStart.value) {
552
+ // If we only selected start, clear the selection
553
+ rangeStart.value = null
554
+ }
555
+ emit('close')
556
+ window.removeEventListener('scroll', updateDropdownPosition, true)
557
+ window.removeEventListener('resize', updateDropdownPosition)
558
+ }
559
+
560
+ function toggle() {
561
+ if (isOpen.value) {
562
+ close()
563
+ } else {
564
+ open()
565
+ }
566
+ }
567
+
568
+ function handleFocus() {
569
+ isFocused.value = true
570
+ }
571
+
572
+ function handleBlur(event: FocusEvent) {
573
+ const relatedTarget = event.relatedTarget as HTMLElement
574
+ if (calendarRef.value?.contains(relatedTarget)) return
575
+ isFocused.value = false
576
+ }
577
+
578
+ function handleClickOutside(event: MouseEvent) {
579
+ const target = event.target as HTMLElement
580
+ if (triggerRef.value?.contains(target)) return
581
+ if (calendarRef.value?.contains(target)) return
582
+ close()
583
+ }
584
+
585
+ function handleKeydown(event: KeyboardEvent) {
586
+ if (event.key === 'Escape') {
587
+ close()
588
+ triggerRef.value?.focus()
589
+ } else if (event.key === 'Enter' || event.key === ' ') {
590
+ if (!isOpen.value) {
591
+ event.preventDefault()
592
+ open()
593
+ }
594
+ }
595
+ }
596
+
597
+ function handleDayHover(date: Date) {
598
+ if (props.mode === 'range' && rangeStart.value) {
599
+ hoverDate.value = date
600
+ }
601
+ }
602
+
603
+ function handleDayLeave() {
604
+ hoverDate.value = null
605
+ }
606
+
607
+ // Time picker
608
+ function adjustHour(delta: number) {
609
+ selectedHour.value = (selectedHour.value + delta + 24) % 24
610
+ isAM.value = selectedHour.value < 12
611
+ updateTimeOnSelection()
612
+ }
613
+
614
+ function adjustMinute(delta: number) {
615
+ selectedMinute.value = (selectedMinute.value + delta + 60) % 60
616
+ updateTimeOnSelection()
617
+ }
618
+
619
+ function handleHourInput(event: Event) {
620
+ const input = event.target as HTMLInputElement
621
+ const rawValue = input.value.replace(/\D/g, '') // Only digits
622
+
623
+ if (!rawValue) {
624
+ return
625
+ }
626
+
627
+ let value = parseInt(rawValue, 10)
628
+
629
+ if (props.timeFormat === '12h') {
630
+ // For 12h format, input is 1-12
631
+ value = Math.max(1, Math.min(12, value))
632
+ // Convert to 24h format for internal storage
633
+ if (isAM.value) {
634
+ selectedHour.value = value === 12 ? 0 : value
635
+ } else {
636
+ selectedHour.value = value === 12 ? 12 : value + 12
637
+ }
638
+ } else {
639
+ // For 24h format, input is 0-23
640
+ value = Math.max(0, Math.min(23, value))
641
+ selectedHour.value = value
642
+ }
643
+
644
+ updateTimeOnSelection()
645
+ }
646
+
647
+ function handleMinuteInput(event: Event) {
648
+ const input = event.target as HTMLInputElement
649
+ const rawValue = input.value.replace(/\D/g, '') // Only digits
650
+
651
+ if (!rawValue) {
652
+ return
653
+ }
654
+
655
+ let value = parseInt(rawValue, 10)
656
+ value = Math.max(0, Math.min(59, value))
657
+ selectedMinute.value = value
658
+ updateTimeOnSelection()
659
+ }
660
+
661
+ function handleHourWheel(event: WheelEvent) {
662
+ event.preventDefault()
663
+ const delta = event.deltaY < 0 ? 1 : -1
664
+ adjustHour(delta)
665
+ }
666
+
667
+ function handleMinuteWheel(event: WheelEvent) {
668
+ event.preventDefault()
669
+ const delta = event.deltaY < 0 ? props.minuteStep : -props.minuteStep
670
+ adjustMinute(delta)
671
+ }
672
+
673
+ function toggleAMPM() {
674
+ isAM.value = !isAM.value
675
+ if (isAM.value && selectedHour.value >= 12) {
676
+ selectedHour.value -= 12
677
+ } else if (!isAM.value && selectedHour.value < 12) {
678
+ selectedHour.value += 12
679
+ }
680
+ updateTimeOnSelection()
681
+ }
682
+
683
+ function displayHour(): string {
684
+ if (props.timeFormat === '12h') {
685
+ const h = selectedHour.value % 12 || 12
686
+ return String(h).padStart(2, '0')
687
+ }
688
+ return String(selectedHour.value).padStart(2, '0')
689
+ }
690
+
691
+ // Month/Year picker methods
692
+ function selectMonthFromPicker(month: number) {
693
+ setMonth(month)
694
+ showMonthPicker.value = false
695
+ }
696
+
697
+ function selectYearFromPicker(year: number) {
698
+ setYear(year)
699
+ showYearPicker.value = false
700
+ }
701
+
702
+ function toggleMonthPicker() {
703
+ showYearPicker.value = false
704
+ showMonthPicker.value = !showMonthPicker.value
705
+ }
706
+
707
+ function toggleYearPicker() {
708
+ showMonthPicker.value = false
709
+ showYearPicker.value = !showYearPicker.value
710
+
711
+ // Scroll to current year when opening
712
+ if (showYearPicker.value) {
713
+ nextTick(() => {
714
+ if (yearPickerRef.value) {
715
+ const currentYearBtn = yearPickerRef.value.querySelector('[data-current-year]') as HTMLElement
716
+ if (currentYearBtn) {
717
+ currentYearBtn.scrollIntoView({ block: 'center' })
718
+ }
719
+ }
720
+ })
721
+ }
722
+ }
723
+
724
+ function updateTimeOnSelection() {
725
+ if (props.mode === 'single' && props.modelValue instanceof Date) {
726
+ const newDate = new Date(props.modelValue)
727
+ newDate.setHours(selectedHour.value, selectedMinute.value)
728
+ emit('update:modelValue', newDate)
729
+ }
730
+ }
731
+
732
+ // Watchers
733
+ watch(isOpen, (val) => {
734
+ if (val) {
735
+ nextTick(() => {
736
+ document.addEventListener('mousedown', handleClickOutside)
737
+ })
738
+ } else {
739
+ document.removeEventListener('mousedown', handleClickOutside)
740
+ }
741
+ })
742
+
743
+ // Lifecycle
744
+ onMounted(() => {
745
+ if (props.modelValue instanceof Date) {
746
+ viewDate.value = new Date(props.modelValue)
747
+ } else if (Array.isArray(props.modelValue) && props.modelValue.length > 0 && props.modelValue[0] instanceof Date) {
748
+ viewDate.value = new Date(props.modelValue[0])
749
+ }
750
+ })
751
+
752
+ onBeforeUnmount(() => {
753
+ document.removeEventListener('mousedown', handleClickOutside)
754
+ window.removeEventListener('scroll', updateDropdownPosition, true)
755
+ window.removeEventListener('resize', updateDropdownPosition)
756
+ })
757
+
758
+ // Size configurations
759
+ const sizeConfig = computed(() => {
760
+ const sizes = {
761
+ small: {
762
+ trigger: 'min-h-8 text-xs',
763
+ padding: 'px-2 py-0.5',
764
+ icon: 'text-sm',
765
+ day: 'w-7 h-7 text-xs',
766
+ label: 'text-xs mb-1'
767
+ },
768
+ medium: {
769
+ trigger: 'min-h-10 text-sm',
770
+ padding: 'px-2 py-0.5',
771
+ icon: 'text-base',
772
+ day: 'w-9 h-9 text-sm',
773
+ label: 'text-sm mb-1.5'
774
+ },
775
+ large: {
776
+ trigger: 'min-h-12 text-base',
777
+ padding: 'px-2.5 py-0.5',
778
+ icon: 'text-lg',
779
+ day: 'w-10 h-10 text-base',
780
+ label: 'text-base mb-2'
781
+ }
782
+ }
783
+ return sizes[props.size]
784
+ })
785
+
786
+ const roundedConfig = computed(() => {
787
+ const radii = {
788
+ none: 'rounded-none',
789
+ sm: 'rounded',
790
+ md: 'rounded-lg',
791
+ lg: 'rounded-xl',
792
+ full: 'rounded-full'
793
+ }
794
+ return radii[props.rounded]
795
+ })
796
+
797
+ const variantStyles = computed(() => {
798
+ const color = props.color
799
+
800
+ if (props.variant === 'filled') {
801
+ return {
802
+ '--dp-bg': 'var(--s-accent)',
803
+ '--dp-bg-focus': 'var(--s-accent)',
804
+ '--dp-border': 'transparent',
805
+ '--dp-border-focus': color
806
+ }
807
+ }
808
+
809
+ if (props.variant === 'ghost') {
810
+ return {
811
+ '--dp-bg': 'transparent',
812
+ '--dp-bg-focus': 'var(--s-accent)',
813
+ '--dp-border': 'transparent',
814
+ '--dp-border-focus': color
815
+ }
816
+ }
817
+
818
+ // outlined (default)
819
+ return {
820
+ '--dp-bg': 'var(--s-background)',
821
+ '--dp-bg-focus': 'var(--s-background)',
822
+ '--dp-border': 'var(--s-border)',
823
+ '--dp-border-focus': color
824
+ }
825
+ })
826
+
827
+ const layoutClasses = computed(() => {
828
+ if (props.labelPlacement === 'left') {
829
+ return 'flex items-center gap-3'
830
+ }
831
+ return 'flex flex-col'
832
+ })
833
+
834
+ const teleportTarget = computed(() => {
835
+ if (props.teleport === true) return 'body'
836
+ if (typeof props.teleport === 'string') return props.teleport
837
+ return undefined
838
+ })
839
+ </script>
840
+
841
+ <template>
842
+ <div v-bind="$attrs" :class="cn(layoutClasses)">
843
+ <!-- Label -->
844
+ <label
845
+ v-if="label"
846
+ :class="[
847
+ 'font-medium text-foreground shrink-0',
848
+ sizeConfig.label,
849
+ labelPlacement === 'left' ? 'mb-0' : ''
850
+ ]"
851
+ >
852
+ {{ label }}
853
+ </label>
854
+
855
+ <!-- Trigger -->
856
+ <div
857
+ ref="triggerRef"
858
+ tabindex="0"
859
+ role="combobox"
860
+ :aria-expanded="isOpen"
861
+ :aria-disabled="disabled"
862
+ class="s-datepicker-trigger relative flex items-center cursor-pointer transition-all duration-200 w-full"
863
+ :class="[
864
+ sizeConfig.trigger,
865
+ sizeConfig.padding,
866
+ roundedConfig,
867
+ {
868
+ 'opacity-50 cursor-not-allowed': disabled,
869
+ 'cursor-default': readonly
870
+ }
871
+ ]"
872
+ :style="variantStyles"
873
+ @click="toggle"
874
+ @focus="handleFocus"
875
+ @blur="handleBlur"
876
+ @keydown="handleKeydown"
877
+ >
878
+ <!-- Calendar icon -->
879
+ <span
880
+ class="mdi mdi-calendar-blank-outline mr-2 text-muted-foreground"
881
+ :class="sizeConfig.icon"
882
+ />
883
+
884
+ <!-- Display value -->
885
+ <span
886
+ v-if="hasValue"
887
+ class="flex-1 truncate text-foreground"
888
+ >
889
+ {{ displayValue }}
890
+ </span>
891
+ <span
892
+ v-else
893
+ class="flex-1 text-muted-foreground"
894
+ >
895
+ {{ placeholder }}
896
+ </span>
897
+
898
+ <!-- Loading spinner -->
899
+ <span
900
+ v-if="loading"
901
+ class="mdi mdi-loading animate-spin text-muted-foreground"
902
+ :class="sizeConfig.icon"
903
+ />
904
+
905
+ <!-- Clear button -->
906
+ <button
907
+ v-else-if="clearable && hasValue && !disabled && !readonly"
908
+ type="button"
909
+ class="p-1 -mr-1 rounded-full hover:bg-accent transition-colors text-muted-foreground hover:text-foreground"
910
+ @click="clear"
911
+ >
912
+ <span class="mdi mdi-close text-sm" />
913
+ </button>
914
+
915
+ <!-- Dropdown arrow -->
916
+ <span
917
+ v-else
918
+ class="mdi transition-transform duration-200 text-muted-foreground"
919
+ :class="[sizeConfig.icon, isOpen ? 'mdi-chevron-up' : 'mdi-chevron-down']"
920
+ />
921
+ </div>
922
+
923
+ <!-- Calendar Dropdown -->
924
+ <Teleport :to="teleportTarget" :disabled="!teleport">
925
+ <Transition
926
+ enter-active-class="transition duration-200 ease-out"
927
+ enter-from-class="opacity-0 scale-95"
928
+ enter-to-class="opacity-100 scale-100"
929
+ leave-active-class="transition duration-150 ease-in"
930
+ leave-from-class="opacity-100 scale-100"
931
+ leave-to-class="opacity-0 scale-95"
932
+ >
933
+ <div
934
+ v-if="isOpen"
935
+ ref="calendarRef"
936
+ class="s-datepicker-calendar fixed z-50 bg-background border border-border rounded-xl shadow-2xl overflow-hidden"
937
+ :style="{
938
+ top: dropdownPosition.top !== undefined ? `${dropdownPosition.top}px` : 'auto',
939
+ bottom: dropdownPosition.bottom !== undefined ? `${dropdownPosition.bottom}px` : 'auto',
940
+ left: `${dropdownPosition.left}px`,
941
+ width: `${dropdownPosition.width}px`,
942
+ minWidth: '320px',
943
+ transformOrigin: dropdownPosition.placement === 'bottom' ? 'top' : 'bottom'
944
+ }"
945
+ >
946
+ <!-- Header -->
947
+ <div class="flex items-center justify-between px-4 py-3 border-b border-border bg-muted">
948
+ <button
949
+ type="button"
950
+ class="p-1.5 rounded-lg hover:bg-accent transition-colors text-muted-foreground hover:text-foreground"
951
+ @click="previousMonth"
952
+ >
953
+ <span class="mdi mdi-chevron-left text-lg" />
954
+ </button>
955
+
956
+ <div class="flex items-center gap-1 relative">
957
+ <!-- Month Picker Button -->
958
+ <button
959
+ type="button"
960
+ class="px-2 py-1 rounded-lg hover:bg-accent transition-colors text-foreground font-semibold flex items-center gap-1"
961
+ @click="toggleMonthPicker"
962
+ >
963
+ {{ monthNames[viewDate.getMonth()] }}
964
+ <span class="mdi mdi-chevron-down text-sm text-muted-foreground" />
965
+ </button>
966
+
967
+ <!-- Month Picker Dropdown -->
968
+ <Transition
969
+ enter-active-class="transition duration-150 ease-out"
970
+ enter-from-class="opacity-0 translate-y-1"
971
+ enter-to-class="opacity-100 translate-y-0"
972
+ leave-active-class="transition duration-100 ease-in"
973
+ leave-from-class="opacity-100 translate-y-0"
974
+ leave-to-class="opacity-0 translate-y-1"
975
+ >
976
+ <div
977
+ v-if="showMonthPicker"
978
+ class="absolute top-full left-0 mt-1 z-10 bg-background border border-border rounded-lg shadow-xl p-2 grid grid-cols-3 gap-1 w-48"
979
+ >
980
+ <button
981
+ v-for="(month, index) in monthNamesShort"
982
+ :key="index"
983
+ type="button"
984
+ class="px-2 py-1.5 text-sm rounded-sm transition-colors"
985
+ :class="[
986
+ viewDate.getMonth() === index
987
+ ? `${activeTextClass} font-semibold`
988
+ : 'text-foreground hover:bg-accent'
989
+ ]"
990
+ :style="viewDate.getMonth() === index ? { backgroundColor: color } : undefined"
991
+ @click="selectMonthFromPicker(index)"
992
+ >
993
+ {{ month }}
994
+ </button>
995
+ </div>
996
+ </Transition>
997
+
998
+ <!-- Year Picker Button -->
999
+ <button
1000
+ type="button"
1001
+ class="px-2 py-1 rounded-lg hover:bg-accent transition-colors text-foreground font-semibold flex items-center gap-1"
1002
+ @click="toggleYearPicker"
1003
+ >
1004
+ {{ viewDate.getFullYear() }}
1005
+ <span class="mdi mdi-chevron-down text-sm text-muted-foreground" />
1006
+ </button>
1007
+
1008
+ <!-- Year Picker Dropdown -->
1009
+ <Transition
1010
+ enter-active-class="transition duration-150 ease-out"
1011
+ enter-from-class="opacity-0 translate-y-1"
1012
+ enter-to-class="opacity-100 translate-y-0"
1013
+ leave-active-class="transition duration-100 ease-in"
1014
+ leave-from-class="opacity-100 translate-y-0"
1015
+ leave-to-class="opacity-0 translate-y-1"
1016
+ >
1017
+ <div
1018
+ v-if="showYearPicker"
1019
+ ref="yearPickerRef"
1020
+ class="absolute top-full right-0 mt-1 z-10 bg-background border border-border rounded-lg shadow-xl p-2 max-h-48 overflow-y-auto w-24 scrollbar-thin"
1021
+ >
1022
+ <button
1023
+ v-for="year in years"
1024
+ :key="year"
1025
+ type="button"
1026
+ class="w-full px-2 py-1.5 text-sm rounded-sm transition-colors text-left"
1027
+ :class="[
1028
+ viewDate.getFullYear() === year
1029
+ ? `${activeTextClass} font-semibold`
1030
+ : 'text-foreground hover:bg-accent'
1031
+ ]"
1032
+ :style="viewDate.getFullYear() === year ? { backgroundColor: color } : undefined"
1033
+ :data-current-year="viewDate.getFullYear() === year ? true : undefined"
1034
+ @click="selectYearFromPicker(year)"
1035
+ >
1036
+ {{ year }}
1037
+ </button>
1038
+ </div>
1039
+ </Transition>
1040
+ </div>
1041
+
1042
+ <button
1043
+ type="button"
1044
+ class="p-1.5 rounded-lg hover:bg-accent transition-colors text-muted-foreground hover:text-foreground"
1045
+ @click="nextMonth"
1046
+ >
1047
+ <span class="mdi mdi-chevron-right text-lg" />
1048
+ </button>
1049
+ </div>
1050
+
1051
+ <!-- Weekday headers -->
1052
+ <div class="grid grid-cols-7 px-3 py-2 border-b border-border">
1053
+ <div
1054
+ v-for="day in weekdaysOrdered"
1055
+ :key="day"
1056
+ class="text-center text-xs font-medium text-muted-foreground py-1"
1057
+ >
1058
+ {{ day }}
1059
+ </div>
1060
+ </div>
1061
+
1062
+ <!-- Calendar grid -->
1063
+ <div class="p-3">
1064
+ <div
1065
+ v-for="(week, weekIndex) in weeks"
1066
+ :key="weekIndex"
1067
+ class="grid grid-cols-7 gap-0.5"
1068
+ >
1069
+ <button
1070
+ v-for="day in week"
1071
+ :key="day.date.toISOString()"
1072
+ type="button"
1073
+ class="relative flex items-center justify-center rounded-lg transition-all duration-150 overflow-hidden"
1074
+ :class="[
1075
+ sizeConfig.day,
1076
+ {
1077
+ 'text-muted-foreground': !day.isCurrentMonth && !day.isSelected,
1078
+ 'text-foreground': day.isCurrentMonth && !day.isSelected && !day.isDisabled,
1079
+ 'font-bold ring-2 ring-inset': day.isToday && !day.isSelected,
1080
+ 'opacity-40 cursor-not-allowed': day.isDisabled,
1081
+ 'cursor-pointer hover:bg-accent': !day.isDisabled && !day.isSelected,
1082
+ [activeTextClass + ' font-semibold']: day.isSelected,
1083
+ 'bg-primary/15': day.isInRange && !day.isSelected,
1084
+ 'rounded-l-lg': day.isRangeStart,
1085
+ 'rounded-r-lg': day.isRangeEnd
1086
+ }
1087
+ ]"
1088
+ :style="{
1089
+ backgroundColor: day.isSelected ? color : undefined,
1090
+ '--tw-ring-color': day.isToday && !day.isSelected ? color : undefined
1091
+ }"
1092
+ :disabled="day.isDisabled"
1093
+ @click="selectDate(day.date, $event)"
1094
+ @mouseenter="handleDayHover(day.date)"
1095
+ @mouseleave="handleDayLeave"
1096
+ >
1097
+ <!-- Ripple -->
1098
+ <span
1099
+ v-for="ripple in ripples"
1100
+ :key="ripple.id"
1101
+ class="absolute rounded-full bg-white/30 animate-ripple pointer-events-none"
1102
+ :style="{
1103
+ left: `${ripple.x}px`,
1104
+ top: `${ripple.y}px`,
1105
+ width: `${ripple.size}px`,
1106
+ height: `${ripple.size}px`
1107
+ }"
1108
+ />
1109
+ {{ day.date.getDate() }}
1110
+ </button>
1111
+ </div>
1112
+ </div>
1113
+
1114
+ <!-- Time picker section -->
1115
+ <div
1116
+ v-if="enableTime"
1117
+ class="px-4 py-3 border-t border-border bg-muted"
1118
+ >
1119
+ <div class="flex items-center justify-center gap-3">
1120
+ <!-- Hour input with arrows -->
1121
+ <div class="flex flex-col items-center" @wheel="handleHourWheel">
1122
+ <button
1123
+ type="button"
1124
+ class="p-0.5 rounded hover:bg-accent text-muted-foreground"
1125
+ @click="adjustHour(1)"
1126
+ >
1127
+ <span class="mdi mdi-chevron-up text-lg" />
1128
+ </button>
1129
+ <input
1130
+ type="text"
1131
+ inputmode="numeric"
1132
+ :value="displayHour()"
1133
+ class="w-12 h-10 text-center font-mono text-xl bg-background text-foreground border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
1134
+ maxlength="2"
1135
+ @keydown="(e: KeyboardEvent) => { if (!/^[0-9]$/.test(e.key) && !['Backspace', 'Delete', 'Tab', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) e.preventDefault() }"
1136
+ @change="handleHourInput"
1137
+ @focus="($event.target as HTMLInputElement).select()"
1138
+ />
1139
+ <button
1140
+ type="button"
1141
+ class="p-0.5 rounded hover:bg-accent text-muted-foreground"
1142
+ @click="adjustHour(-1)"
1143
+ >
1144
+ <span class="mdi mdi-chevron-down text-lg" />
1145
+ </button>
1146
+ </div>
1147
+
1148
+ <span class="text-2xl font-bold text-foreground pb-1">:</span>
1149
+
1150
+ <!-- Minute input with arrows -->
1151
+ <div class="flex flex-col items-center" @wheel="handleMinuteWheel">
1152
+ <button
1153
+ type="button"
1154
+ class="p-0.5 rounded hover:bg-accent text-muted-foreground"
1155
+ @click="adjustMinute(minuteStep)"
1156
+ >
1157
+ <span class="mdi mdi-chevron-up text-lg" />
1158
+ </button>
1159
+ <input
1160
+ type="text"
1161
+ inputmode="numeric"
1162
+ :value="String(selectedMinute).padStart(2, '0')"
1163
+ class="w-12 h-10 text-center font-mono text-xl bg-background text-foreground border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
1164
+ maxlength="2"
1165
+ @keydown="(e: KeyboardEvent) => { if (!/^[0-9]$/.test(e.key) && !['Backspace', 'Delete', 'Tab', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) e.preventDefault() }"
1166
+ @change="handleMinuteInput"
1167
+ @focus="($event.target as HTMLInputElement).select()"
1168
+ />
1169
+ <button
1170
+ type="button"
1171
+ class="p-0.5 rounded hover:bg-accent text-muted-foreground"
1172
+ @click="adjustMinute(-minuteStep)"
1173
+ >
1174
+ <span class="mdi mdi-chevron-down text-lg" />
1175
+ </button>
1176
+ </div>
1177
+
1178
+ <!-- AM/PM Toggle for 12h format -->
1179
+ <div v-if="timeFormat === '12h'" class="flex flex-col gap-1 ml-2">
1180
+ <button
1181
+ type="button"
1182
+ class="px-3 py-1.5 text-sm font-medium rounded-lg transition-colors"
1183
+ :class="[
1184
+ isAM
1185
+ ? activeTextClass
1186
+ : 'text-muted-foreground hover:bg-accent'
1187
+ ]"
1188
+ :style="isAM ? { backgroundColor: color } : undefined"
1189
+ @click="isAM || toggleAMPM()"
1190
+ >
1191
+ AM
1192
+ </button>
1193
+ <button
1194
+ type="button"
1195
+ class="px-3 py-1.5 text-sm font-medium rounded-lg transition-colors"
1196
+ :class="[
1197
+ !isAM
1198
+ ? activeTextClass
1199
+ : 'text-muted-foreground hover:bg-accent'
1200
+ ]"
1201
+ :style="!isAM ? { backgroundColor: color } : undefined"
1202
+ @click="isAM && toggleAMPM()"
1203
+ >
1204
+ PM
1205
+ </button>
1206
+ </div>
1207
+ </div>
1208
+ </div>
1209
+
1210
+ <!-- Footer -->
1211
+ <div
1212
+ v-if="showToday || enableTime"
1213
+ class="flex items-center justify-between px-4 py-2 border-t border-border"
1214
+ >
1215
+ <button
1216
+ v-if="showToday"
1217
+ type="button"
1218
+ class="text-sm font-medium transition-colors hover:underline"
1219
+ :style="{ color }"
1220
+ @click="goToToday"
1221
+ >
1222
+ Today
1223
+ </button>
1224
+ <div v-else />
1225
+
1226
+ <button
1227
+ v-if="enableTime"
1228
+ type="button"
1229
+ :class="['px-4 py-1.5 text-sm font-medium rounded-lg transition-colors', activeTextClass]"
1230
+ :style="{ backgroundColor: color }"
1231
+ @click="close"
1232
+ >
1233
+ Done
1234
+ </button>
1235
+ </div>
1236
+ </div>
1237
+ </Transition>
1238
+ </Teleport>
1239
+ </div>
1240
+ </template>
1241
+
1242
+ <style scoped>
1243
+ .s-datepicker-trigger {
1244
+ background-color: var(--dp-bg);
1245
+ border: 1.5px solid var(--dp-border);
1246
+ }
1247
+
1248
+ .s-datepicker-trigger:focus {
1249
+ background-color: var(--dp-bg-focus);
1250
+ border-color: var(--dp-border-focus);
1251
+ outline: none;
1252
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--dp-border-focus) 20%, transparent);
1253
+ }
1254
+
1255
+ /* Ripple animation */
1256
+ @keyframes ripple {
1257
+ 0% {
1258
+ transform: scale(0);
1259
+ opacity: 0.5;
1260
+ }
1261
+ 100% {
1262
+ transform: scale(1);
1263
+ opacity: 0;
1264
+ }
1265
+ }
1266
+
1267
+ .animate-ripple {
1268
+ animation: ripple 0.6s ease-out forwards;
1269
+ }
1270
+
1271
+ /* Custom scrollbar for dropdowns */
1272
+ .s-datepicker-calendar select {
1273
+ scrollbar-width: thin;
1274
+ scrollbar-color: var(--s-border) transparent;
1275
+ }
1276
+
1277
+ .s-datepicker-calendar select::-webkit-scrollbar {
1278
+ width: 6px;
1279
+ }
1280
+
1281
+ .s-datepicker-calendar select::-webkit-scrollbar-track {
1282
+ background: transparent;
1283
+ }
1284
+
1285
+ .s-datepicker-calendar select::-webkit-scrollbar-thumb {
1286
+ background-color: var(--s-border);
1287
+ border-radius: 3px;
1288
+ }
1289
+ </style>
1290
+