@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,1054 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, watch, nextTick, onMounted, type CSSProperties } from 'vue'
3
+ import { cn } from '../../lib/utils'
4
+
5
+ defineOptions({ inheritAttrs: false })
6
+
7
+ // Props interface
8
+ export interface Props {
9
+ // Core
10
+ modelValue?: string | number
11
+ type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search' | 'textarea'
12
+
13
+ // Visual Design
14
+ variant?: 'outlined' | 'filled' | 'underlined' | 'ghost'
15
+ size?: 'small' | 'medium' | 'large'
16
+ color?: string
17
+ rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full'
18
+
19
+ // Label & Placeholder
20
+ label?: string
21
+ placeholder?: string
22
+ labelPlacement?: 'top' | 'top-left' | 'top-center' | 'top-right' | 'bottom' | 'left' | 'right' | 'floating' | 'inside'
23
+ labelAnimation?: 'morph' | 'slide' | 'fade' | 'none'
24
+
25
+ // Icons
26
+ iconLeft?: string
27
+ iconRight?: string
28
+ iconColor?: string
29
+
30
+ // States
31
+ disabled?: boolean
32
+ readonly?: boolean
33
+ loading?: boolean
34
+ error?: string | boolean
35
+ success?: string | boolean
36
+ warning?: string | boolean
37
+ hint?: string
38
+
39
+ // Validation
40
+ required?: boolean
41
+ minLength?: number
42
+ maxLength?: number
43
+ pattern?: string | RegExp
44
+ validator?: (value: string | number) => string | boolean | Promise<string | boolean>
45
+ validateOn?: 'blur' | 'input' | 'submit' | 'never'
46
+
47
+ // Input Behavior
48
+ autocomplete?: string
49
+ autofocus?: boolean
50
+ spellcheck?: boolean
51
+ inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
52
+ max?: number | string
53
+ min?: number | string
54
+ step?: number | string
55
+
56
+ // Advanced Features
57
+ clearable?: boolean
58
+ showPasswordToggle?: boolean
59
+ counter?: boolean
60
+ prefix?: string
61
+ suffix?: string
62
+ rows?: number
63
+ resize?: 'none' | 'vertical' | 'horizontal' | 'both'
64
+
65
+ // Autocomplete Suggestions
66
+ suggestions?: string[] // e.g., ['@gmail.com', '@outlook.com'] for email
67
+ showSuggestionsOnFocus?: boolean
68
+
69
+ // Input Filtering/Masking
70
+ allowOnly?: 'digits' | 'letters' | 'alphanumeric' | RegExp | ((char: string) => boolean)
71
+ decimalPlaces?: number // For number type, max decimal places allowed
72
+
73
+ // Accessibility
74
+ name?: string
75
+ id?: string
76
+ ariaLabel?: string
77
+ ariaDescribedBy?: string
78
+
79
+ // Styling Overrides
80
+ inputClass?: string
81
+ labelClass?: string
82
+ wrapperClass?: string
83
+ }
84
+
85
+ const props = withDefaults(defineProps<Props>(), {
86
+ modelValue: '',
87
+ type: 'text',
88
+ variant: 'outlined',
89
+ size: 'medium',
90
+ color: undefined,
91
+ rounded: 'md',
92
+ labelPlacement: 'top',
93
+ labelAnimation: 'morph',
94
+ disabled: false,
95
+ readonly: false,
96
+ loading: false,
97
+ required: false,
98
+ validateOn: 'blur',
99
+ clearable: false,
100
+ showPasswordToggle: false,
101
+ counter: false,
102
+ rows: 3,
103
+ resize: 'vertical',
104
+ autofocus: false,
105
+ spellcheck: true,
106
+ suggestions: undefined,
107
+ showSuggestionsOnFocus: true,
108
+ allowOnly: undefined,
109
+ decimalPlaces: undefined
110
+ })
111
+
112
+ const emit = defineEmits<{
113
+ 'update:modelValue': [value: string | number]
114
+ 'update:error': [error: string | null]
115
+ 'focus': [event: FocusEvent]
116
+ 'blur': [event: FocusEvent]
117
+ 'input': [event: Event]
118
+ 'change': [value: string | number, event: Event]
119
+ 'clear': []
120
+ 'enter': [event: KeyboardEvent]
121
+ 'validate': [isValid: boolean, error: string | null]
122
+ 'select-suggestion': [suggestion: string]
123
+ }>()
124
+
125
+ // Refs
126
+ const inputRef = ref<HTMLInputElement | HTMLTextAreaElement | null>(null)
127
+ const isFocused = ref(false)
128
+ const showPassword = ref(false)
129
+ const internalError = ref<string | null>(null)
130
+ const isValidating = ref(false)
131
+ const inputId = computed(() => props.id || `s-input-${Math.random().toString(36).slice(2, 9)}`)
132
+ const messageId = computed(() => `${inputId.value}-message`)
133
+
134
+ // Suggestions state
135
+ const showSuggestions = ref(false)
136
+ const selectedSuggestionIndex = ref(-1)
137
+ const suggestionsRef = ref<HTMLUListElement | null>(null)
138
+
139
+ // Debounce timer
140
+ let validateTimer: ReturnType<typeof setTimeout> | null = null
141
+
142
+ // Computed
143
+ const hasValue = computed(() => {
144
+ return props.modelValue !== null && props.modelValue !== undefined && String(props.modelValue).length > 0
145
+ })
146
+
147
+ const currentLength = computed(() => String(props.modelValue || '').length)
148
+
149
+ const computedType = computed(() => {
150
+ if (props.type === 'password' && showPassword.value) {
151
+ return 'text'
152
+ }
153
+ return props.type
154
+ })
155
+
156
+ const isFloatingLabel = computed(() => {
157
+ return props.labelPlacement === 'floating' || props.labelPlacement === 'inside'
158
+ })
159
+
160
+ const isLabelFloated = computed(() => {
161
+ return isFocused.value || hasValue.value
162
+ })
163
+
164
+ const displayError = computed(() => {
165
+ if (typeof props.error === 'string') return props.error
166
+ if (props.error === true) return 'Invalid value'
167
+ return internalError.value
168
+ })
169
+
170
+ const displaySuccess = computed(() => {
171
+ if (typeof props.success === 'string') return props.success
172
+ return null
173
+ })
174
+
175
+ const displayWarning = computed(() => {
176
+ if (typeof props.warning === 'string') return props.warning
177
+ return null
178
+ })
179
+
180
+ const validationState = computed<'error' | 'success' | 'warning' | null>(() => {
181
+ if (displayError.value || props.error) return 'error'
182
+ if (displaySuccess.value || props.success) return 'success'
183
+ if (displayWarning.value || props.warning) return 'warning'
184
+ return null
185
+ })
186
+
187
+ const validationIcon = computed(() => {
188
+ switch (validationState.value) {
189
+ case 'error': return 'alert-circle'
190
+ case 'success': return 'check-circle'
191
+ case 'warning': return 'alert'
192
+ default: return null
193
+ }
194
+ })
195
+
196
+ // Filtered suggestions based on current value
197
+ const filteredSuggestions = computed(() => {
198
+ if (!props.suggestions || !props.suggestions.length) return []
199
+ const currentValue = String(props.modelValue || '')
200
+
201
+ // For email type, show suggestions if @ is typed
202
+ if (props.type === 'email') {
203
+ const atIndex = currentValue.indexOf('@')
204
+ if (atIndex === -1) return [] // No @ yet, don't show suggestions
205
+
206
+ const afterAt = currentValue.slice(atIndex)
207
+ // Filter suggestions that match what's typed after @
208
+ return props.suggestions.filter(s =>
209
+ s.toLowerCase().startsWith(afterAt.toLowerCase()) && s !== afterAt
210
+ )
211
+ }
212
+
213
+ // For other types, filter by value
214
+ return props.suggestions.filter(s =>
215
+ s.toLowerCase().includes(currentValue.toLowerCase()) && s !== currentValue
216
+ )
217
+ })
218
+
219
+ // Should show suggestions dropdown
220
+ const shouldShowSuggestions = computed(() => {
221
+ return showSuggestions.value &&
222
+ filteredSuggestions.value.length > 0 &&
223
+ !props.disabled &&
224
+ !props.readonly
225
+ })
226
+
227
+ // Size configurations
228
+ const sizeConfig = computed(() => {
229
+ const sizes = {
230
+ small: {
231
+ input: 'min-h-8 text-xs',
232
+ padding: 'px-2.5 py-1.5',
233
+ paddingWithIcon: 'px-2.5',
234
+ label: 'text-xs',
235
+ icon: 'text-sm',
236
+ floatLabel: 'text-xs',
237
+ floatLabelActive: '-top-2 text-[10px]'
238
+ },
239
+ medium: {
240
+ input: 'min-h-10 text-sm',
241
+ padding: 'px-3 py-2',
242
+ paddingWithIcon: 'px-3',
243
+ label: 'text-sm',
244
+ icon: 'text-base',
245
+ floatLabel: 'text-sm',
246
+ floatLabelActive: '-top-2.5 text-xs'
247
+ },
248
+ large: {
249
+ input: 'min-h-12 text-base',
250
+ padding: 'px-4 py-2.5',
251
+ paddingWithIcon: 'px-4',
252
+ label: 'text-base',
253
+ icon: 'text-lg',
254
+ floatLabel: 'text-base',
255
+ floatLabelActive: '-top-3 text-sm'
256
+ }
257
+ }
258
+ return sizes[props.size]
259
+ })
260
+
261
+ // Rounded classes
262
+ const roundedConfig = computed(() => {
263
+ const radii = {
264
+ none: 'rounded-none',
265
+ sm: 'rounded',
266
+ md: 'rounded-lg',
267
+ lg: 'rounded-xl',
268
+ full: 'rounded-full'
269
+ }
270
+ return radii[props.rounded]
271
+ })
272
+
273
+ // Variant styles
274
+ const variantClasses = computed(() => {
275
+ const base = {
276
+ outlined: 'border bg-background border-border hover:border-input',
277
+ filled: 'border-transparent bg-accent',
278
+ underlined: 'border-b border-t-0 border-l-0 border-r-0 rounded-none! bg-transparent border-border',
279
+ ghost: 'border-transparent bg-transparent hover:bg-accent'
280
+ }
281
+ return base[props.variant]
282
+ })
283
+
284
+ // Focus classes
285
+ const focusClasses = computed(() => {
286
+ if (!isFocused.value) return ''
287
+ if (props.color) return '' // handled by inline style
288
+ if (props.variant === 'underlined') {
289
+ return 'border-primary'
290
+ }
291
+ return 'ring-2 ring-ring/20 border-primary'
292
+ })
293
+
294
+ // Focus inline style when custom color is set
295
+ const focusStyle = computed<CSSProperties | undefined>(() => {
296
+ if (!isFocused.value || !props.color) return undefined
297
+ if (props.variant === 'underlined') {
298
+ return { borderColor: props.color }
299
+ }
300
+ return {
301
+ borderColor: props.color,
302
+ boxShadow: `0 0 0 2px color-mix(in srgb, ${props.color} 20%, transparent)`
303
+ }
304
+ })
305
+
306
+ // Validation state border colors
307
+ const validationBorderClasses = computed(() => {
308
+ if (!validationState.value) return ''
309
+ const colors = {
310
+ error: 'border-red-500 hover:border-red-500',
311
+ success: 'border-green-500 hover:border-green-500',
312
+ warning: 'border-amber-500 hover:border-amber-500'
313
+ }
314
+ return colors[validationState.value]
315
+ })
316
+
317
+ // Character counter color
318
+ const counterColorClass = computed(() => {
319
+ if (!props.maxLength) return 'text-muted-foreground'
320
+ const ratio = currentLength.value / props.maxLength
321
+ if (ratio >= 1) return 'text-red-500'
322
+ if (ratio >= 0.9) return 'text-amber-500'
323
+ if (ratio >= 0.75) return 'text-amber-400'
324
+ return 'text-muted-foreground'
325
+ })
326
+
327
+ // Icon color classes
328
+ const iconColorClass = computed(() => {
329
+ if (props.iconColor) return ''
330
+ if (isFocused.value) return props.color ? '' : 'text-primary'
331
+ return 'text-muted-foreground'
332
+ })
333
+
334
+ // Icon color inline style when custom color is set
335
+ const iconFocusStyle = computed<CSSProperties | undefined>(() => {
336
+ if (!isFocused.value || props.iconColor || !props.color) return undefined
337
+ return { color: props.color }
338
+ })
339
+
340
+ // Label layout classes for non-floating labels
341
+ const labelLayoutClasses = computed(() => {
342
+ if (isFloatingLabel.value) return 'flex flex-col w-full'
343
+
344
+ const [side, align] = props.labelPlacement.split('-') as [string, string | undefined]
345
+ const classes = ['flex', 'w-full']
346
+
347
+ if (side === 'top' || side === 'bottom') {
348
+ classes.push('flex-col', 'gap-1.5')
349
+ if (side === 'bottom') classes.push('flex-col-reverse')
350
+ } else {
351
+ classes.push('gap-3')
352
+ if (side === 'right') classes.push('flex-row-reverse')
353
+ else classes.push('flex-row')
354
+ if (align === 'center') classes.push('items-center')
355
+ else if (align === 'bottom') classes.push('items-end')
356
+ else classes.push('items-start')
357
+ }
358
+
359
+ return classes.join(' ')
360
+ })
361
+
362
+ // Label text alignment
363
+ const labelClasses = computed(() => {
364
+ const base = `font-medium text-muted-foreground ${sizeConfig.value.label}`
365
+ if (isFloatingLabel.value) return base
366
+
367
+ const [side, align] = props.labelPlacement.split('-')
368
+ if (side === 'top' || side === 'bottom') {
369
+ if (align === 'center') return `${base} text-center`
370
+ if (align === 'right') return `${base} text-right`
371
+ }
372
+ return base
373
+ })
374
+
375
+ // Input padding based on icons
376
+ const inputPaddingClasses = computed(() => {
377
+ const hasLeft = !!props.iconLeft || !!props.prefix
378
+ const hasRight = !!props.iconRight || !!props.suffix || props.clearable || props.showPasswordToggle || props.loading || validationIcon.value
379
+
380
+ let classes = sizeConfig.value.input
381
+
382
+ if (props.size === 'small') {
383
+ classes += hasLeft ? ' pl-8' : ' pl-2.5'
384
+ classes += hasRight ? ' pr-8' : ' pr-2.5'
385
+ } else if (props.size === 'large') {
386
+ classes += hasLeft ? ' pl-12' : ' pl-4'
387
+ classes += hasRight ? ' pr-12' : ' pr-4'
388
+ } else {
389
+ classes += hasLeft ? ' pl-10' : ' pl-3'
390
+ classes += hasRight ? ' pr-10' : ' pr-3'
391
+ }
392
+
393
+ return classes
394
+ })
395
+
396
+ // Validation
397
+ const isValidEmail = (email: string): boolean => {
398
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
399
+ }
400
+
401
+ const isValidUrl = (url: string): boolean => {
402
+ try {
403
+ new URL(url)
404
+ return true
405
+ } catch {
406
+ return false
407
+ }
408
+ }
409
+
410
+ const validate = async (value: string | number): Promise<string | null> => {
411
+ const strValue = String(value)
412
+
413
+ // Required validation
414
+ if (props.required && strValue.trim() === '') {
415
+ return 'This field is required'
416
+ }
417
+
418
+ // Skip other validations if empty and not required
419
+ if (strValue === '') return null
420
+
421
+ // Min length
422
+ if (props.minLength && strValue.length < props.minLength) {
423
+ return `Minimum ${props.minLength} characters required`
424
+ }
425
+
426
+ // Max length
427
+ if (props.maxLength && strValue.length > props.maxLength) {
428
+ return `Maximum ${props.maxLength} characters allowed`
429
+ }
430
+
431
+ // Pattern
432
+ if (props.pattern) {
433
+ const regex = typeof props.pattern === 'string' ? new RegExp(props.pattern) : props.pattern
434
+ if (!regex.test(strValue)) {
435
+ return 'Invalid format'
436
+ }
437
+ }
438
+
439
+ // Type-specific validation
440
+ if (props.type === 'email' && strValue && !isValidEmail(strValue)) {
441
+ return 'Please enter a valid email address'
442
+ }
443
+
444
+ if (props.type === 'url' && strValue && !isValidUrl(strValue)) {
445
+ return 'Please enter a valid URL'
446
+ }
447
+
448
+ // Number range validation
449
+ if (props.type === 'number') {
450
+ const numValue = Number(value)
451
+ if (props.min !== undefined && numValue < Number(props.min)) {
452
+ return `Minimum value is ${props.min}`
453
+ }
454
+ if (props.max !== undefined && numValue > Number(props.max)) {
455
+ return `Maximum value is ${props.max}`
456
+ }
457
+ }
458
+
459
+ // Custom validator
460
+ if (props.validator) {
461
+ isValidating.value = true
462
+ try {
463
+ const result = await props.validator(value)
464
+ if (typeof result === 'string') return result
465
+ if (result === false) return 'Invalid value'
466
+ } finally {
467
+ isValidating.value = false
468
+ }
469
+ }
470
+
471
+ return null
472
+ }
473
+
474
+ const runValidation = async () => {
475
+ if (props.validateOn === 'never') return
476
+
477
+ const error = await validate(props.modelValue ?? '')
478
+ internalError.value = error
479
+ emit('update:error', error)
480
+ emit('validate', error === null, error)
481
+ }
482
+
483
+ // Input character filtering
484
+ const isCharacterAllowed = (char: string, newValue: string): boolean => {
485
+ const allowOnly = props.allowOnly
486
+ if (!allowOnly) return true
487
+
488
+ if (allowOnly === 'digits') {
489
+ // Allow digits and one decimal point for numbers with decimalPlaces
490
+ if (props.decimalPlaces !== undefined) {
491
+ return /[\d.]/.test(char) && !(char === '.' && newValue.includes('.'))
492
+ }
493
+ return /\d/.test(char)
494
+ }
495
+ if (allowOnly === 'letters') return /[a-zA-Z]/.test(char)
496
+ if (allowOnly === 'alphanumeric') return /[a-zA-Z0-9]/.test(char)
497
+ if (allowOnly instanceof RegExp) return allowOnly.test(char)
498
+ if (typeof allowOnly === 'function') return allowOnly(char)
499
+ return true
500
+ }
501
+
502
+ const enforceDecimalPlaces = (value: string): string => {
503
+ if (props.decimalPlaces === undefined) return value
504
+
505
+ const parts = value.split('.')
506
+ if (parts.length === 2 && parts[1].length > props.decimalPlaces) {
507
+ return parts[0] + '.' + parts[1].slice(0, props.decimalPlaces)
508
+ }
509
+ return value
510
+ }
511
+
512
+ // Handle keypress for character filtering (beforeinput)
513
+ const handleBeforeInput = (event: InputEvent) => {
514
+ if (!props.allowOnly && props.decimalPlaces === undefined) return
515
+
516
+ const char = event.data
517
+ if (!char) return // Allow control keys
518
+
519
+ const target = event.target as HTMLInputElement
520
+ const newValue = target.value + char
521
+
522
+ // Check if character is allowed
523
+ if (!isCharacterAllowed(char, target.value)) {
524
+ event.preventDefault()
525
+ return
526
+ }
527
+
528
+ // Check decimal places
529
+ if (props.decimalPlaces !== undefined && char !== '.') {
530
+ const parts = newValue.split('.')
531
+ if (parts.length === 2 && parts[1].length > props.decimalPlaces) {
532
+ event.preventDefault()
533
+ return
534
+ }
535
+ }
536
+ }
537
+
538
+ // Event handlers
539
+ const handleInput = (event: Event) => {
540
+ const target = event.target as HTMLInputElement | HTMLTextAreaElement
541
+ let value: string | number = target.value
542
+
543
+ // Enforce decimal places after paste
544
+ if (props.decimalPlaces !== undefined) {
545
+ value = enforceDecimalPlaces(String(value))
546
+ target.value = value
547
+ }
548
+
549
+ if (props.type === 'number' && value !== '') {
550
+ value = Number(value)
551
+ }
552
+
553
+ emit('update:modelValue', value)
554
+ emit('input', event)
555
+
556
+ // Show suggestions on input
557
+ if (props.suggestions?.length) {
558
+ showSuggestions.value = true
559
+ selectedSuggestionIndex.value = -1
560
+ }
561
+
562
+ // Clear error on input if it was set
563
+ if (internalError.value && props.validateOn === 'input') {
564
+ if (validateTimer) clearTimeout(validateTimer)
565
+ validateTimer = setTimeout(() => {
566
+ runValidation()
567
+ }, 500)
568
+ } else if (props.validateOn === 'input') {
569
+ if (validateTimer) clearTimeout(validateTimer)
570
+ validateTimer = setTimeout(() => {
571
+ runValidation()
572
+ }, 350000)
573
+ }
574
+ }
575
+
576
+ const handleFocus = (event: FocusEvent) => {
577
+ isFocused.value = true
578
+ emit('focus', event)
579
+
580
+ // Show suggestions on focus if configured
581
+ if (props.showSuggestionsOnFocus && props.suggestions?.length) {
582
+ showSuggestions.value = true
583
+ }
584
+ }
585
+
586
+ const handleBlur = (event: FocusEvent) => {
587
+ isFocused.value = false
588
+ emit('blur', event)
589
+
590
+ // Delay hiding suggestions to allow click on suggestion
591
+ setTimeout(() => {
592
+ showSuggestions.value = false
593
+ selectedSuggestionIndex.value = -1
594
+ }, 300)
595
+
596
+ if (props.validateOn === 'blur') {
597
+ runValidation()
598
+ }
599
+ }
600
+
601
+ const handleChange = (event: Event) => {
602
+ emit('change', props.modelValue ?? '', event)
603
+ }
604
+
605
+ const handleKeydown = (event: KeyboardEvent) => {
606
+ // Handle keyboard navigation for suggestions
607
+ if (shouldShowSuggestions.value) {
608
+ if (event.key === 'ArrowDown') {
609
+ event.preventDefault()
610
+ selectedSuggestionIndex.value = Math.min(
611
+ selectedSuggestionIndex.value + 1,
612
+ filteredSuggestions.value.length - 1
613
+ )
614
+ return
615
+ }
616
+ if (event.key === 'ArrowUp') {
617
+ event.preventDefault()
618
+ selectedSuggestionIndex.value = Math.max(selectedSuggestionIndex.value - 1, -1)
619
+ return
620
+ }
621
+ if (event.key === 'Enter' && selectedSuggestionIndex.value >= 0) {
622
+ event.preventDefault()
623
+ selectSuggestion(filteredSuggestions.value[selectedSuggestionIndex.value])
624
+ return
625
+ }
626
+ if (event.key === 'Escape') {
627
+ showSuggestions.value = false
628
+ selectedSuggestionIndex.value = -1
629
+ return
630
+ }
631
+ if (event.key === 'Tab' && filteredSuggestions.value.length > 0) {
632
+ // Auto-complete with first suggestion on Tab
633
+ event.preventDefault()
634
+ selectSuggestion(filteredSuggestions.value[0])
635
+ return
636
+ }
637
+ }
638
+
639
+ if (event.key === 'Enter' && props.type !== 'textarea') {
640
+ emit('enter', event)
641
+ }
642
+ }
643
+
644
+ // Select a suggestion
645
+ const selectSuggestion = (suggestion: string) => {
646
+ let newValue = suggestion
647
+
648
+ // For email type, append suggestion to the part before @
649
+ if (props.type === 'email') {
650
+ const currentValue = String(props.modelValue || '')
651
+ const atIndex = currentValue.indexOf('@')
652
+ if (atIndex >= 0) {
653
+ newValue = currentValue.slice(0, atIndex) + suggestion
654
+ }
655
+ }
656
+
657
+ emit('update:modelValue', newValue)
658
+ emit('select-suggestion', suggestion)
659
+ showSuggestions.value = false
660
+ selectedSuggestionIndex.value = -1
661
+
662
+ // Focus input after selection
663
+ nextTick(() => {
664
+ inputRef.value?.focus()
665
+ })
666
+ }
667
+
668
+ const clear = () => {
669
+ emit('update:modelValue', '')
670
+ emit('clear')
671
+ internalError.value = null
672
+ nextTick(() => {
673
+ inputRef.value?.focus()
674
+ })
675
+ }
676
+
677
+ const togglePassword = () => {
678
+ showPassword.value = !showPassword.value
679
+ }
680
+
681
+ // Focus method
682
+ const focus = () => {
683
+ inputRef.value?.focus()
684
+ }
685
+
686
+ const blur = () => {
687
+ inputRef.value?.blur()
688
+ }
689
+
690
+ // Expose methods
691
+ defineExpose({
692
+ focus,
693
+ blur,
694
+ validate: runValidation,
695
+ inputElement: inputRef
696
+ })
697
+
698
+ // Lifecycle
699
+ onMounted(() => {
700
+ if (props.autofocus) {
701
+ nextTick(() => {
702
+ inputRef.value?.focus()
703
+ })
704
+ }
705
+ })
706
+
707
+ // Watch for external error changes
708
+ watch(() => props.error, (newError) => {
709
+ if (newError) {
710
+ internalError.value = null
711
+ }
712
+ })
713
+ </script>
714
+
715
+ <template>
716
+ <div
717
+ v-bind="$attrs"
718
+ :class="cn('s-input-wrapper relative w-full', labelLayoutClasses, wrapperClass, $attrs.class as string)"
719
+ >
720
+ <!-- Static Label (non-floating) -->
721
+ <label
722
+ v-if="label && !isFloatingLabel"
723
+ :for="inputId"
724
+ class="shrink-0"
725
+ :class="[labelClasses, labelClass]"
726
+ >
727
+ {{ label }}
728
+ <span v-if="required" class="text-red-500 ml-0.5">*</span>
729
+ </label>
730
+
731
+ <!-- Input Container -->
732
+ <div class="flex-1 min-w-0">
733
+ <div class="s-input-container relative">
734
+ <!-- Prefix / Left Icon -->
735
+ <span
736
+ v-if="iconLeft || prefix"
737
+ class="absolute left-0 top-1/2 -translate-y-1/2 flex items-center gap-1 pointer-events-none transition-colors duration-200"
738
+ :class="[
739
+ sizeConfig.icon,
740
+ iconColorClass,
741
+ size === 'small' ? 'pl-2.5' : size === 'large' ? 'pl-4' : 'pl-3'
742
+ ]"
743
+ :style="iconColor ? { color: iconColor } : iconFocusStyle"
744
+ >
745
+ <slot name="prefix">
746
+ <span v-if="iconLeft" :class="['mdi', `mdi-${iconLeft}`]" />
747
+ <span v-if="prefix" class="text-muted-foreground text-sm">{{ prefix }}</span>
748
+ </slot>
749
+ </span>
750
+
751
+ <!-- Input Element -->
752
+ <input
753
+ v-if="type !== 'textarea'"
754
+ ref="inputRef"
755
+ :id="inputId"
756
+ :name="name"
757
+ :type="computedType"
758
+ :value="modelValue"
759
+ :placeholder="isFloatingLabel && !isLabelFloated ? '' : placeholder"
760
+ :disabled="disabled"
761
+ :readonly="readonly"
762
+ :required="required"
763
+ :autocomplete="autocomplete"
764
+ :spellcheck="spellcheck"
765
+ :inputmode="inputmode"
766
+ :min="min"
767
+ :max="max"
768
+ :step="step"
769
+ :maxlength="maxLength"
770
+ :aria-label="ariaLabel || label"
771
+ :aria-describedby="(displayError || displaySuccess || displayWarning || hint) ? messageId : ariaDescribedBy"
772
+ :aria-invalid="!!displayError"
773
+ :aria-required="required"
774
+ class="s-input w-full outline-none transition-all duration-200 text-foreground placeholder:text-muted-foreground"
775
+ :class="[
776
+ inputPaddingClasses,
777
+ roundedConfig,
778
+ variantClasses,
779
+ focusClasses,
780
+ validationBorderClasses,
781
+ inputClass,
782
+ {
783
+ 'opacity-50 cursor-not-allowed': disabled,
784
+ 'cursor-wait': loading,
785
+ 'py-2': isFloatingLabel
786
+ }
787
+ ]"
788
+ :style="focusStyle"
789
+ @input="handleInput"
790
+ @focus="handleFocus"
791
+ @blur="handleBlur"
792
+ @change="handleChange"
793
+ @keydown="handleKeydown"
794
+ @beforeinput="handleBeforeInput"
795
+ />
796
+
797
+ <!-- Textarea Element -->
798
+ <textarea
799
+ v-else
800
+ ref="inputRef"
801
+ :id="inputId"
802
+ :name="name"
803
+ :value="modelValue"
804
+ :placeholder="isFloatingLabel && !isLabelFloated ? '' : placeholder"
805
+ :disabled="disabled"
806
+ :readonly="readonly"
807
+ :required="required"
808
+ :autocomplete="autocomplete"
809
+ :spellcheck="spellcheck"
810
+ :rows="rows"
811
+ :maxlength="maxLength"
812
+ :aria-label="ariaLabel || label"
813
+ :aria-describedby="(displayError || displaySuccess || displayWarning || hint) ? messageId : ariaDescribedBy"
814
+ :aria-invalid="!!displayError"
815
+ :aria-required="required"
816
+ class="s-input w-full outline-none transition-all duration-200 text-foreground placeholder:text-muted-foreground"
817
+ :class="[
818
+ sizeConfig.input,
819
+ size === 'small' ? 'px-2.5 py-1.5' : size === 'large' ? 'px-4 py-2.5' : 'px-3 py-2',
820
+ roundedConfig,
821
+ variantClasses,
822
+ focusClasses,
823
+ validationBorderClasses,
824
+ inputClass,
825
+ {
826
+ 'opacity-50 cursor-not-allowed': disabled,
827
+ 'cursor-wait': loading,
828
+ 'resize-none': resize === 'none',
829
+ 'resize-y': resize === 'vertical',
830
+ 'resize-x': resize === 'horizontal',
831
+ 'resize': resize === 'both'
832
+ }
833
+ ]"
834
+ :style="{ ...focusStyle, minHeight: type === 'textarea' ? `${rows * 1.5 + 1}rem` : undefined }"
835
+ @input="handleInput"
836
+ @focus="handleFocus"
837
+ @blur="handleBlur"
838
+ @change="handleChange"
839
+ />
840
+
841
+ <!-- Floating/Inside Label -->
842
+ <label
843
+ v-if="label && isFloatingLabel"
844
+ :for="inputId"
845
+ class="s-input-label-floating absolute left-0 pointer-events-none transition-all duration-200 ease-out origin-left"
846
+ :class="[
847
+ labelClass,
848
+ size === 'small' ? 'left-2.5' : size === 'large' ? 'left-4' : 'left-3',
849
+ iconLeft || prefix ? (size === 'small' ? 'left-8' : size === 'large' ? 'left-12' : 'left-10') : '',
850
+ isLabelFloated
851
+ ? [sizeConfig.floatLabelActive, color ? '' : 'text-primary', 'bg-background px-1 -ml-1']
852
+ : ['top-1/2 -translate-y-1/2', sizeConfig.floatLabel, 'text-muted-foreground']
853
+ ]"
854
+ :style="isLabelFloated && color ? { color } : undefined"
855
+ >
856
+ {{ label }}
857
+ <span v-if="required" class="text-red-500 ml-0.5">*</span>
858
+ </label>
859
+
860
+ <!-- Suffix / Right Icon / Actions -->
861
+ <span
862
+ v-if="iconRight || suffix || clearable || showPasswordToggle || loading || validationIcon"
863
+ class="absolute right-0 top-1/2 -translate-y-1/2 flex items-center gap-1 transition-colors duration-200"
864
+ :class="[
865
+ sizeConfig.icon,
866
+ size === 'small' ? 'pr-2.5' : size === 'large' ? 'pr-4' : 'pr-3'
867
+ ]"
868
+ >
869
+ <!-- Loading spinner -->
870
+ <span
871
+ v-if="loading"
872
+ class="mdi mdi-loading animate-spin text-muted-foreground"
873
+ />
874
+
875
+ <!-- Clear button -->
876
+ <button
877
+ v-else-if="clearable && hasValue && !disabled && !readonly"
878
+ type="button"
879
+ class="mdi mdi-close-circle text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
880
+ tabindex="-1"
881
+ @click="clear"
882
+ />
883
+
884
+ <!-- Validation icon -->
885
+ <span
886
+ v-else-if="validationIcon && !iconRight"
887
+ :class="[
888
+ 'mdi',
889
+ `mdi-${validationIcon}`,
890
+ validationState === 'error' ? 'text-red-500' : '',
891
+ validationState === 'success' ? 'text-green-500' : '',
892
+ validationState === 'warning' ? 'text-amber-500' : ''
893
+ ]"
894
+ />
895
+
896
+ <!-- Password toggle -->
897
+ <button
898
+ v-if="type === 'password' && showPasswordToggle && !loading"
899
+ type="button"
900
+ class="mdi transition-colors cursor-pointer text-muted-foreground hover:text-foreground"
901
+ :class="showPassword ? 'mdi-eye-off' : 'mdi-eye'"
902
+ tabindex="-1"
903
+ @click="togglePassword"
904
+ />
905
+
906
+ <!-- Custom suffix -->
907
+ <slot name="suffix">
908
+ <span
909
+ v-if="iconRight"
910
+ :class="['mdi', `mdi-${iconRight}`, iconColorClass]"
911
+ :style="iconColor ? { color: iconColor } : iconFocusStyle"
912
+ />
913
+ <span v-if="suffix" class="text-muted-foreground text-sm">{{ suffix }}</span>
914
+ </slot>
915
+ </span>
916
+
917
+ <!-- Animated border line (underlined variant) -->
918
+ <div
919
+ v-if="variant === 'underlined'"
920
+ class="s-input-border-animated absolute bottom-0 left-1/2 h-0.5 transition-all duration-200 ease-out"
921
+ :class="[color ? '' : 'bg-primary', isFocused ? 'w-full -translate-x-1/2' : 'w-0 -translate-x-1/2']"
922
+ :style="color ? { backgroundColor: color } : undefined"
923
+ />
924
+
925
+ <!-- Suggestions Dropdown -->
926
+ <Transition
927
+ enter-active-class="transition-all duration-150 ease-out"
928
+ enter-from-class="opacity-0 -translate-y-2 scale-95"
929
+ enter-to-class="opacity-100 translate-y-0 scale-100"
930
+ leave-active-class="transition-all duration-100 ease-in"
931
+ leave-from-class="opacity-100 translate-y-0 scale-100"
932
+ leave-to-class="opacity-0 -translate-y-2 scale-95"
933
+ >
934
+ <ul
935
+ v-if="shouldShowSuggestions"
936
+ ref="suggestionsRef"
937
+ class="s-input-suggestions absolute left-0 right-0 top-full mt-1 z-50 rounded-lg border border-border bg-background shadow-lg overflow-hidden"
938
+ role="listbox"
939
+ :aria-label="`Suggestions for ${label || 'input'}`"
940
+ >
941
+ <li
942
+ v-for="(suggestion, index) in filteredSuggestions"
943
+ :key="suggestion"
944
+ role="option"
945
+ :aria-selected="index === selectedSuggestionIndex"
946
+ class="px-3 py-2 text-sm cursor-pointer transition-colors"
947
+ :class="[
948
+ index === selectedSuggestionIndex
949
+ ? 'bg-primary text-primary-foreground'
950
+ : 'text-foreground hover:bg-accent'
951
+ ]"
952
+ @mousedown.prevent="selectSuggestion(suggestion)"
953
+ @mouseenter="selectedSuggestionIndex = index"
954
+ >
955
+ <template v-if="type === 'email'">
956
+ <span class="text-muted-foreground">{{ String(modelValue || '').split('@')[0] }}</span>
957
+ <span :class="index === selectedSuggestionIndex ? 'text-primary-foreground' : 'text-primary font-medium'">{{ suggestion }}</span>
958
+ </template>
959
+ <template v-else>
960
+ {{ suggestion }}
961
+ </template>
962
+ </li>
963
+ </ul>
964
+ </Transition>
965
+ </div>
966
+
967
+ <!-- Messages Row -->
968
+ <div
969
+ v-if="displayError || displaySuccess || displayWarning || hint || (counter && maxLength)"
970
+ :id="messageId"
971
+ class="s-input-messages flex items-start justify-between gap-2 mt-1.5"
972
+ >
973
+ <!-- Message -->
974
+ <Transition
975
+ enter-active-class="transition-all duration-200 ease-out"
976
+ enter-from-class="opacity-0 -translate-y-1"
977
+ enter-to-class="opacity-100 translate-y-0"
978
+ leave-active-class="transition-all duration-150 ease-in"
979
+ leave-from-class="opacity-100 translate-y-0"
980
+ leave-to-class="opacity-0 -translate-y-1"
981
+ mode="out-in"
982
+ >
983
+ <p
984
+ v-if="displayError"
985
+ key="error"
986
+ class="text-xs text-red-500 flex items-center gap-1"
987
+ >
988
+ <span class="mdi mdi-alert-circle text-sm" />
989
+ {{ displayError }}
990
+ </p>
991
+ <p
992
+ v-else-if="displaySuccess"
993
+ key="success"
994
+ class="text-xs text-green-500 flex items-center gap-1"
995
+ >
996
+ <span class="mdi mdi-check-circle text-sm" />
997
+ {{ displaySuccess }}
998
+ </p>
999
+ <p
1000
+ v-else-if="displayWarning"
1001
+ key="warning"
1002
+ class="text-xs text-amber-500 flex items-center gap-1"
1003
+ >
1004
+ <span class="mdi mdi-alert text-sm" />
1005
+ {{ displayWarning }}
1006
+ </p>
1007
+ <p
1008
+ v-else-if="hint"
1009
+ key="hint"
1010
+ class="text-xs text-muted-foreground"
1011
+ >
1012
+ {{ hint }}
1013
+ </p>
1014
+ <span v-else key="empty" />
1015
+ </Transition>
1016
+
1017
+ <!-- Character counter -->
1018
+ <span
1019
+ v-if="counter && maxLength"
1020
+ class="text-xs shrink-0 tabular-nums transition-colors duration-200"
1021
+ :class="counterColorClass"
1022
+ >
1023
+ {{ currentLength }} / {{ maxLength }}
1024
+ </span>
1025
+ </div>
1026
+ </div>
1027
+ </div>
1028
+ </template>
1029
+
1030
+ <style scoped>
1031
+ /* Ensure floating label background matches input */
1032
+ .s-input-label-floating {
1033
+ z-index: 1;
1034
+ }
1035
+
1036
+ /* Remove default browser styling for search inputs */
1037
+ .s-input[type="search"]::-webkit-search-cancel-button,
1038
+ .s-input[type="search"]::-webkit-search-decoration {
1039
+ -webkit-appearance: none;
1040
+ appearance: none;
1041
+ }
1042
+
1043
+ /* Remove number input spinners */
1044
+ .s-input[type="number"]::-webkit-inner-spin-button,
1045
+ .s-input[type="number"]::-webkit-outer-spin-button {
1046
+ -webkit-appearance: none;
1047
+ margin: 0;
1048
+ }
1049
+
1050
+ .s-input[type="number"] {
1051
+ -moz-appearance: textfield;
1052
+ appearance: textfield;
1053
+ }
1054
+ </style>