@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,843 @@
1
+ <script lang="ts">
2
+ /**
3
+ * SDropdown - Advanced Dropdown Menu Component
4
+ * A highly customizable dropdown component for menus, actions, and navigation
5
+ */
6
+ import { type InjectionKey, type Ref } from 'vue'
7
+
8
+ // Types
9
+ export type DropdownTrigger = 'click' | 'hover' | 'context' | 'manual'
10
+ export type DropdownPlacement =
11
+ | 'top' | 'top-start' | 'top-end'
12
+ | 'bottom' | 'bottom-start' | 'bottom-end'
13
+ | 'left' | 'left-start' | 'left-end'
14
+ | 'right' | 'right-start' | 'right-end'
15
+ export type DropdownSize = 'small' | 'medium' | 'large'
16
+ export type DropdownVariant = 'default' | 'filled' | 'glass'
17
+ export type DropdownAnimation = 'fade' | 'slide' | 'scale' | 'reveal'
18
+
19
+ export interface DropdownMenuItem {
20
+ key: string
21
+ label: string
22
+ icon?: string
23
+ trailingIcon?: string
24
+ description?: string
25
+ shortcut?: string
26
+ disabled?: boolean
27
+ danger?: boolean
28
+ checked?: boolean
29
+ children?: DropdownMenuItem[]
30
+ divider?: boolean
31
+ header?: string
32
+ onClick?: () => void
33
+ }
34
+
35
+ export interface SDropdownContext {
36
+ size: DropdownSize
37
+ color: string
38
+ closeOnSelect: boolean
39
+ highlightedIndex: Ref<number>
40
+ registerItem: (item: { key: string; disabled: boolean }) => number
41
+ unregisterItem: (key: string) => void
42
+ selectItem: (key: string) => void
43
+ close: () => void
44
+ }
45
+
46
+ export const SDropdownContextKey: InjectionKey<SDropdownContext> = Symbol('SDropdownContext')
47
+ </script>
48
+
49
+ <script setup lang="ts">
50
+ defineOptions({ inheritAttrs: false })
51
+
52
+ import { ref, computed, provide, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
53
+ import { cn } from '../../../lib/utils'
54
+
55
+ export interface Props {
56
+ /** Menu items (alternative to slots) */
57
+ items?: DropdownMenuItem[]
58
+ /** How to trigger the dropdown */
59
+ trigger?: DropdownTrigger
60
+ /** Placement of the dropdown menu */
61
+ placement?: DropdownPlacement
62
+ /** Size variant */
63
+ size?: DropdownSize
64
+ /** Visual variant */
65
+ variant?: DropdownVariant
66
+ /** Accent color */
67
+ color?: string
68
+ /** Animation type */
69
+ animation?: DropdownAnimation
70
+ /** Close when item is selected */
71
+ closeOnSelect?: boolean
72
+ /** Show arrow pointing to trigger */
73
+ arrow?: boolean
74
+ /** Dropdown is disabled */
75
+ disabled?: boolean
76
+ /** Max height of menu */
77
+ maxHeight?: string
78
+ /** Menu width (number = px, string = CSS value) */
79
+ width?: number | string
80
+ /** Min width of menu */
81
+ minWidth?: string
82
+ /** Teleport target */
83
+ teleport?: boolean | string
84
+ /** Z-index for dropdown */
85
+ zIndex?: number
86
+ /** Delay before showing (hover trigger) */
87
+ showDelay?: number
88
+ /** Delay before hiding (hover trigger) */
89
+ hideDelay?: number
90
+ /** Manual control of visibility */
91
+ visible?: boolean
92
+ /** Enable search filtering */
93
+ searchable?: boolean
94
+ /** Search placeholder */
95
+ searchPlaceholder?: string
96
+ /** Offset from trigger */
97
+ offset?: number
98
+ /** Trigger button text */
99
+ label?: string
100
+ /** Trigger button icon */
101
+ icon?: string
102
+ /** Hide the dropdown arrow on trigger */
103
+ hideArrow?: boolean
104
+ }
105
+
106
+ const props = withDefaults(defineProps<Props>(), {
107
+ items: undefined,
108
+ trigger: 'click',
109
+ placement: 'bottom-start',
110
+ size: 'medium',
111
+ variant: 'default',
112
+ color: 'var(--s-primary)',
113
+ animation: 'scale',
114
+ closeOnSelect: true,
115
+ arrow: false,
116
+ disabled: false,
117
+ maxHeight: '320px',
118
+ width: undefined,
119
+ minWidth: '180px',
120
+ teleport: true,
121
+ zIndex: 1000,
122
+ showDelay: 100,
123
+ hideDelay: 150,
124
+ visible: undefined,
125
+ searchable: false,
126
+ searchPlaceholder: 'Search...',
127
+ offset: 6,
128
+ label: undefined,
129
+ icon: undefined,
130
+ hideArrow: false
131
+ })
132
+
133
+ const emit = defineEmits<{
134
+ 'update:visible': [value: boolean]
135
+ 'open': []
136
+ 'close': []
137
+ 'select': [key: string, item?: DropdownMenuItem]
138
+ }>()
139
+
140
+ // Refs
141
+ const triggerRef = ref<HTMLElement | null>(null)
142
+ const dropdownRef = ref<HTMLElement | null>(null)
143
+ const searchInputRef = ref<HTMLInputElement | null>(null)
144
+ const isOpen = ref(false)
145
+ const searchQuery = ref('')
146
+ const highlightedIndex = ref(-1)
147
+ const registeredItems = ref<{ key: string; disabled: boolean }[]>([])
148
+ const menuPosition = ref<{
149
+ top?: number
150
+ bottom?: number
151
+ left?: number
152
+ right?: number
153
+ placement: DropdownPlacement
154
+ }>({ placement: props.placement })
155
+
156
+ let showTimeout: ReturnType<typeof setTimeout> | null = null
157
+ let hideTimeout: ReturnType<typeof setTimeout> | null = null
158
+
159
+ // Computed
160
+ const isManual = computed(() => props.trigger === 'manual')
161
+
162
+ const filteredItems = computed(() => {
163
+ if (!props.items || !searchQuery.value) return props.items
164
+ const query = searchQuery.value.toLowerCase()
165
+ return props.items.filter(item => {
166
+ if (item.divider || item.header) return true
167
+ return item.label.toLowerCase().includes(query)
168
+ })
169
+ })
170
+
171
+ const teleportTarget = computed(() => {
172
+ if (props.teleport === true) return 'body'
173
+ if (typeof props.teleport === 'string') return props.teleport
174
+ return undefined
175
+ })
176
+
177
+ const menuStyle = computed(() => {
178
+ const style: Record<string, string> = {
179
+ zIndex: props.zIndex.toString(),
180
+ maxHeight: props.maxHeight
181
+ }
182
+
183
+ if (props.width) {
184
+ style.width = typeof props.width === 'number' ? `${props.width}px` : props.width
185
+ }
186
+
187
+ if (props.minWidth) {
188
+ style.minWidth = props.minWidth
189
+ }
190
+
191
+ if (menuPosition.value.top !== undefined) {
192
+ style.top = `${menuPosition.value.top}px`
193
+ }
194
+ if (menuPosition.value.bottom !== undefined) {
195
+ style.bottom = `${menuPosition.value.bottom}px`
196
+ }
197
+ if (menuPosition.value.left !== undefined) {
198
+ style.left = `${menuPosition.value.left}px`
199
+ }
200
+ if (menuPosition.value.right !== undefined) {
201
+ style.right = `${menuPosition.value.right}px`
202
+ }
203
+
204
+ return style
205
+ })
206
+
207
+ // Size configurations
208
+ const sizeConfig = computed(() => ({
209
+ small: {
210
+ trigger: 'px-2 py-0.5 text-xs gap-1.5',
211
+ menu: 'py-1 px-1',
212
+ item: 'px-2 py-0.5 text-xs',
213
+ icon: 'text-sm',
214
+ search: 'text-xs px-2 py-0.5'
215
+ },
216
+ medium: {
217
+ trigger: 'px-2 py-0.5 text-sm gap-2',
218
+ menu: 'py-1 px-1',
219
+ item: 'px-2 py-0.5 text-sm',
220
+ icon: 'text-base',
221
+ search: 'text-sm px-2 py-0.5'
222
+ },
223
+ large: {
224
+ trigger: 'px-2.5 py-0.5 text-base gap-2.5',
225
+ menu: 'py-1.5 px-1.5',
226
+ item: 'px-2.5 py-0.5 text-base',
227
+ icon: 'text-lg',
228
+ search: 'text-base px-2.5 py-0.5'
229
+ }
230
+ }[props.size]))
231
+
232
+ const variantClasses = computed(() => ({
233
+ default: 'bg-background border border-border shadow-xl',
234
+ filled: 'bg-muted border border-border shadow-lg',
235
+ glass: 'bg-background/80 backdrop-blur-xl border border-border/50 shadow-2xl'
236
+ }[props.variant]))
237
+
238
+ // Animation classes
239
+ const animationClasses = computed(() => {
240
+ const placement = menuPosition.value.placement
241
+ const isTop = placement.startsWith('top')
242
+ const isBottom = placement.startsWith('bottom')
243
+ const isLeft = placement.startsWith('left')
244
+ const isRight = placement.startsWith('right')
245
+
246
+ return {
247
+ fade: {
248
+ enter: 'transition-opacity duration-200 ease-out',
249
+ enterFrom: 'opacity-0',
250
+ enterTo: 'opacity-100',
251
+ leave: 'transition-opacity duration-150 ease-in',
252
+ leaveFrom: 'opacity-100',
253
+ leaveTo: 'opacity-0'
254
+ },
255
+ slide: {
256
+ enter: 'transition-all duration-200 ease-out',
257
+ enterFrom: `opacity-0 ${isTop ? 'translate-y-2' : isBottom ? '-translate-y-2' : isLeft ? 'translate-x-2' : '-translate-x-2'}`,
258
+ enterTo: 'opacity-100 translate-x-0 translate-y-0',
259
+ leave: 'transition-all duration-150 ease-in',
260
+ leaveFrom: 'opacity-100 translate-x-0 translate-y-0',
261
+ leaveTo: `opacity-0 ${isTop ? 'translate-y-2' : isBottom ? '-translate-y-2' : isLeft ? 'translate-x-2' : '-translate-x-2'}`
262
+ },
263
+ scale: {
264
+ enter: 'transition-all duration-200 ease-out',
265
+ enterFrom: 'opacity-0 scale-95',
266
+ enterTo: 'opacity-100 scale-100',
267
+ leave: 'transition-all duration-150 ease-in',
268
+ leaveFrom: 'opacity-100 scale-100',
269
+ leaveTo: 'opacity-0 scale-95'
270
+ },
271
+ reveal: {
272
+ enter: 'transition-all duration-250 ease-out',
273
+ enterFrom: 'opacity-0 scale-90 blur-sm',
274
+ enterTo: 'opacity-100 scale-100 blur-0',
275
+ leave: 'transition-all duration-150 ease-in',
276
+ leaveFrom: 'opacity-100 scale-100 blur-0',
277
+ leaveTo: 'opacity-0 scale-90 blur-sm'
278
+ }
279
+ }[props.animation]
280
+ })
281
+
282
+ // Methods
283
+ const calculatePosition = () => {
284
+ if (!triggerRef.value) return
285
+
286
+ const trigger = triggerRef.value.getBoundingClientRect()
287
+ const viewport = { width: window.innerWidth, height: window.innerHeight }
288
+ const offset = props.offset
289
+
290
+ let placement = props.placement
291
+ let top: number | undefined
292
+ let bottom: number | undefined
293
+ let left: number | undefined
294
+ let right: number | undefined
295
+
296
+ // Calculate based on placement
297
+ const positions: Record<DropdownPlacement, () => void> = {
298
+ 'top': () => { bottom = viewport.height - trigger.top + offset; left = trigger.left + trigger.width / 2 },
299
+ 'top-start': () => { bottom = viewport.height - trigger.top + offset; left = trigger.left },
300
+ 'top-end': () => { bottom = viewport.height - trigger.top + offset; right = viewport.width - trigger.right },
301
+ 'bottom': () => { top = trigger.bottom + offset; left = trigger.left + trigger.width / 2 },
302
+ 'bottom-start': () => { top = trigger.bottom + offset; left = trigger.left },
303
+ 'bottom-end': () => { top = trigger.bottom + offset; right = viewport.width - trigger.right },
304
+ 'left': () => { top = trigger.top + trigger.height / 2; right = viewport.width - trigger.left + offset },
305
+ 'left-start': () => { top = trigger.top; right = viewport.width - trigger.left + offset },
306
+ 'left-end': () => { bottom = viewport.height - trigger.bottom; right = viewport.width - trigger.left + offset },
307
+ 'right': () => { top = trigger.top + trigger.height / 2; left = trigger.right + offset },
308
+ 'right-start': () => { top = trigger.top; left = trigger.right + offset },
309
+ 'right-end': () => { bottom = viewport.height - trigger.bottom; left = trigger.right + offset }
310
+ }
311
+
312
+ positions[placement]()
313
+
314
+ // Auto-flip if needed
315
+ const menuHeight = parseInt(props.maxHeight) || 320
316
+ const menuWidth = props.width ? (typeof props.width === 'number' ? props.width : 200) : 200
317
+
318
+ if (placement.startsWith('bottom') && top !== undefined && top + menuHeight > viewport.height - 10) {
319
+ placement = placement.replace('bottom', 'top') as DropdownPlacement
320
+ top = undefined
321
+ bottom = viewport.height - trigger.top + offset
322
+ } else if (placement.startsWith('top') && bottom !== undefined && viewport.height - bottom + menuHeight > viewport.height - 10) {
323
+ placement = placement.replace('top', 'bottom') as DropdownPlacement
324
+ bottom = undefined
325
+ top = trigger.bottom + offset
326
+ }
327
+
328
+ // Constrain to viewport
329
+ if (left !== undefined && left + menuWidth > viewport.width - 10) {
330
+ left = viewport.width - menuWidth - 10
331
+ }
332
+ if (left !== undefined && left < 10) {
333
+ left = 10
334
+ }
335
+
336
+ menuPosition.value = { top, bottom, left, right, placement }
337
+ }
338
+
339
+ const open = async () => {
340
+ if (props.disabled || isOpen.value) return
341
+
342
+ if (hideTimeout) {
343
+ clearTimeout(hideTimeout)
344
+ hideTimeout = null
345
+ }
346
+
347
+ isOpen.value = true
348
+ emit('update:visible', true)
349
+ emit('open')
350
+
351
+ await nextTick()
352
+ calculatePosition()
353
+
354
+ if (props.searchable && searchInputRef.value) {
355
+ searchInputRef.value.focus()
356
+ }
357
+ }
358
+
359
+ const close = () => {
360
+ if (!isOpen.value) return
361
+
362
+ if (showTimeout) {
363
+ clearTimeout(showTimeout)
364
+ showTimeout = null
365
+ }
366
+
367
+ isOpen.value = false
368
+ searchQuery.value = ''
369
+ highlightedIndex.value = -1
370
+ emit('update:visible', false)
371
+ emit('close')
372
+ }
373
+
374
+ const toggle = () => {
375
+ if (isOpen.value) {
376
+ close()
377
+ } else {
378
+ open()
379
+ }
380
+ }
381
+
382
+ const handleTriggerClick = (event: MouseEvent) => {
383
+ if (props.trigger === 'click' && !props.disabled) {
384
+ event.preventDefault()
385
+ toggle()
386
+ }
387
+ }
388
+
389
+ const handleTriggerContextMenu = (event: MouseEvent) => {
390
+ if (props.trigger === 'context' && !props.disabled) {
391
+ event.preventDefault()
392
+ toggle()
393
+ }
394
+ }
395
+
396
+ const handleTriggerMouseEnter = () => {
397
+ if (props.trigger === 'hover' && !props.disabled) {
398
+ showTimeout = setTimeout(open, props.showDelay)
399
+ }
400
+ }
401
+
402
+ const handleTriggerMouseLeave = () => {
403
+ if (props.trigger === 'hover' && !props.disabled) {
404
+ hideTimeout = setTimeout(close, props.hideDelay)
405
+ }
406
+ }
407
+
408
+ const handleMenuMouseEnter = () => {
409
+ if (props.trigger === 'hover') {
410
+ if (hideTimeout) {
411
+ clearTimeout(hideTimeout)
412
+ hideTimeout = null
413
+ }
414
+ }
415
+ }
416
+
417
+ const handleMenuMouseLeave = () => {
418
+ if (props.trigger === 'hover') {
419
+ hideTimeout = setTimeout(close, props.hideDelay)
420
+ }
421
+ }
422
+
423
+ const handleClickOutside = (event: MouseEvent) => {
424
+ const target = event.target as Node
425
+ if (
426
+ triggerRef.value &&
427
+ dropdownRef.value &&
428
+ !triggerRef.value.contains(target) &&
429
+ !dropdownRef.value.contains(target)
430
+ ) {
431
+ close()
432
+ }
433
+ }
434
+
435
+ const handleKeydown = (event: KeyboardEvent) => {
436
+ if (!isOpen.value) {
437
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {
438
+ event.preventDefault()
439
+ open()
440
+ }
441
+ return
442
+ }
443
+
444
+ const enabledItems = registeredItems.value.filter(i => !i.disabled)
445
+
446
+ switch (event.key) {
447
+ case 'Escape':
448
+ event.preventDefault()
449
+ close()
450
+ triggerRef.value?.focus()
451
+ break
452
+ case 'ArrowDown':
453
+ event.preventDefault()
454
+ highlightedIndex.value = Math.min(highlightedIndex.value + 1, enabledItems.length - 1)
455
+ if (highlightedIndex.value < 0) highlightedIndex.value = 0
456
+ break
457
+ case 'ArrowUp':
458
+ event.preventDefault()
459
+ highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0)
460
+ break
461
+ case 'Home':
462
+ event.preventDefault()
463
+ highlightedIndex.value = 0
464
+ break
465
+ case 'End':
466
+ event.preventDefault()
467
+ highlightedIndex.value = enabledItems.length - 1
468
+ break
469
+ case 'Enter':
470
+ case ' ':
471
+ event.preventDefault()
472
+ if (highlightedIndex.value >= 0 && enabledItems[highlightedIndex.value]) {
473
+ selectItem(enabledItems[highlightedIndex.value].key)
474
+ }
475
+ break
476
+ case 'Tab':
477
+ close()
478
+ break
479
+ }
480
+ }
481
+
482
+ const registerItem = (item: { key: string; disabled: boolean }) => {
483
+ const existing = registeredItems.value.findIndex(i => i.key === item.key)
484
+ if (existing >= 0) {
485
+ registeredItems.value[existing] = item
486
+ return existing
487
+ }
488
+ registeredItems.value.push(item)
489
+ return registeredItems.value.length - 1
490
+ }
491
+
492
+ const unregisterItem = (key: string) => {
493
+ const index = registeredItems.value.findIndex(i => i.key === key)
494
+ if (index >= 0) {
495
+ registeredItems.value.splice(index, 1)
496
+ }
497
+ }
498
+
499
+ const selectItem = (key: string) => {
500
+ const item = props.items?.find(i => i.key === key)
501
+ emit('select', key, item)
502
+
503
+ if (item?.onClick) {
504
+ item.onClick()
505
+ }
506
+
507
+ if (props.closeOnSelect) {
508
+ close()
509
+ }
510
+ }
511
+
512
+ // Watchers
513
+ watch(() => props.visible, (val) => {
514
+ if (isManual.value && val !== undefined) {
515
+ if (val) open()
516
+ else close()
517
+ }
518
+ })
519
+
520
+ watch(isOpen, (val) => {
521
+ if (val) {
522
+ window.addEventListener('scroll', calculatePosition, true)
523
+ window.addEventListener('resize', calculatePosition)
524
+ } else {
525
+ window.removeEventListener('scroll', calculatePosition, true)
526
+ window.removeEventListener('resize', calculatePosition)
527
+ }
528
+ })
529
+
530
+ // Lifecycle
531
+ onMounted(() => {
532
+ document.addEventListener('mousedown', handleClickOutside)
533
+ })
534
+
535
+ onBeforeUnmount(() => {
536
+ if (showTimeout) clearTimeout(showTimeout)
537
+ if (hideTimeout) clearTimeout(hideTimeout)
538
+ document.removeEventListener('mousedown', handleClickOutside)
539
+ window.removeEventListener('scroll', calculatePosition, true)
540
+ window.removeEventListener('resize', calculatePosition)
541
+ })
542
+
543
+ // Provide context
544
+ provide(SDropdownContextKey, {
545
+ size: props.size,
546
+ color: props.color,
547
+ closeOnSelect: props.closeOnSelect,
548
+ highlightedIndex,
549
+ registerItem,
550
+ unregisterItem,
551
+ selectItem,
552
+ close
553
+ })
554
+
555
+ // Expose for manual control
556
+ defineExpose({
557
+ open,
558
+ close,
559
+ toggle
560
+ })
561
+ </script>
562
+
563
+ <template>
564
+ <div :class="cn('s-dropdown relative inline-block', $attrs.class ?? '')" v-bind="$attrs">
565
+ <!-- Trigger -->
566
+ <div
567
+ ref="triggerRef"
568
+ class="s-dropdown-trigger outline-none"
569
+ tabindex="0"
570
+ :aria-expanded="isOpen"
571
+ :aria-haspopup="true"
572
+ :aria-disabled="disabled"
573
+ @click="handleTriggerClick"
574
+ @contextmenu="handleTriggerContextMenu"
575
+ @mouseenter="handleTriggerMouseEnter"
576
+ @mouseleave="handleTriggerMouseLeave"
577
+ @keydown="handleKeydown"
578
+ >
579
+ <slot name="trigger">
580
+ <!-- Default trigger button -->
581
+ <button
582
+ type="button"
583
+ class="s-dropdown-btn inline-flex items-center justify-center font-medium rounded-lg border transition-all duration-200 outline-none select-none"
584
+ :class="[
585
+ sizeConfig.trigger,
586
+ disabled
587
+ ? 'opacity-50 cursor-not-allowed bg-accent border-border text-muted-foreground'
588
+ : 'bg-muted border-border text-foreground hover:bg-accent hover:border-input focus:ring-2 focus:ring-primary/20'
589
+ ]"
590
+ :disabled="disabled"
591
+ >
592
+ <span v-if="icon" :class="['mdi', `mdi-${icon}`, sizeConfig.icon]" />
593
+ <span v-if="label">{{ label }}</span>
594
+ <span
595
+ v-if="!hideArrow"
596
+ class="mdi mdi-chevron-down transition-transform duration-200"
597
+ :class="[sizeConfig.icon, { 'rotate-180': isOpen }]"
598
+ />
599
+ </button>
600
+ </slot>
601
+ </div>
602
+
603
+ <!-- Dropdown Menu -->
604
+ <Teleport v-if="teleportTarget" :to="teleportTarget" :disabled="!teleportTarget">
605
+ <Transition
606
+ :enter-active-class="animationClasses.enter"
607
+ :enter-from-class="animationClasses.enterFrom"
608
+ :enter-to-class="animationClasses.enterTo"
609
+ :leave-active-class="animationClasses.leave"
610
+ :leave-from-class="animationClasses.leaveFrom"
611
+ :leave-to-class="animationClasses.leaveTo"
612
+ >
613
+ <div
614
+ v-if="isOpen"
615
+ ref="dropdownRef"
616
+ role="menu"
617
+ class="s-dropdown-menu fixed rounded-xl overflow-hidden"
618
+ :class="[sizeConfig.menu, variantClasses]"
619
+ :style="menuStyle"
620
+ @mouseenter="handleMenuMouseEnter"
621
+ @mouseleave="handleMenuMouseLeave"
622
+ >
623
+ <!-- Arrow pointer -->
624
+ <div
625
+ v-if="arrow"
626
+ class="s-dropdown-arrow absolute w-2.5 h-2.5 rotate-45 bg-background border border-border"
627
+ :class="{
628
+ 'bottom-full left-4 -mb-1 border-b-0 border-r-0': menuPosition.placement.startsWith('bottom'),
629
+ 'top-full left-4 -mt-1 border-t-0 border-l-0': menuPosition.placement.startsWith('top'),
630
+ 'left-full top-4 -ml-1 border-l-0 border-b-0': menuPosition.placement.startsWith('right'),
631
+ 'right-full top-4 -mr-1 border-r-0 border-t-0': menuPosition.placement.startsWith('left')
632
+ }"
633
+ />
634
+
635
+ <!-- Search input -->
636
+ <div v-if="searchable" class="p-2 border-b border-border">
637
+ <div class="relative">
638
+ <span class="absolute left-3 top-1/2 -translate-y-1/2 mdi mdi-magnify text-muted-foreground" />
639
+ <input
640
+ ref="searchInputRef"
641
+ v-model="searchQuery"
642
+ type="text"
643
+ :placeholder="searchPlaceholder"
644
+ :class="sizeConfig.search"
645
+ class="w-full pl-9 pr-3 bg-accent border border-border rounded-lg outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all text-foreground placeholder:text-muted-foreground"
646
+ @keydown="handleKeydown"
647
+ />
648
+ </div>
649
+ </div>
650
+
651
+ <!-- Header slot -->
652
+ <slot name="header" />
653
+
654
+ <!-- Menu content -->
655
+ <div
656
+ class="overflow-y-auto overflow-x-hidden overscroll-contain"
657
+ :style="{ maxHeight: searchable ? `calc(${maxHeight} - 60px)` : maxHeight }"
658
+ >
659
+ <!-- Render from items prop -->
660
+ <template v-if="filteredItems && filteredItems.length > 0">
661
+ <template v-for="(item, index) in filteredItems" :key="item.key || `item-${index}`">
662
+ <!-- Divider -->
663
+ <div
664
+ v-if="item.divider"
665
+ class="my-1 h-px bg-border/60"
666
+ />
667
+
668
+ <!-- Section header -->
669
+ <div
670
+ v-else-if="item.header"
671
+ class="px-2.5 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground"
672
+ >
673
+ {{ item.header }}
674
+ </div>
675
+
676
+ <!-- Menu item -->
677
+ <div
678
+ v-else
679
+ role="menuitem"
680
+ :tabindex="item.disabled ? -1 : 0"
681
+ class="s-dropdown-item relative flex items-center cursor-pointer transition-all duration-150 select-none rounded-lg"
682
+ :class="[
683
+ sizeConfig.item,
684
+ {
685
+ 'opacity-50 cursor-not-allowed': item.disabled,
686
+ 'text-red-500 hover:bg-red-500/10': item.danger && !item.disabled,
687
+ 'text-foreground hover:bg-accent': !item.danger && !item.disabled,
688
+ 'bg-accent': highlightedIndex === index && !item.disabled
689
+ }
690
+ ]"
691
+ @click="!item.disabled && selectItem(item.key)"
692
+ @mouseenter="highlightedIndex = index"
693
+ >
694
+ <!-- Checkbox for checkable items -->
695
+ <span
696
+ v-if="item.checked !== undefined"
697
+ class="mdi mr-2 transition-all duration-150"
698
+ :class="[
699
+ item.checked ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline',
700
+ sizeConfig.icon,
701
+ item.checked ? '' : 'text-muted-foreground'
702
+ ]"
703
+ :style="item.checked ? { color: color } : {}"
704
+ />
705
+
706
+ <!-- Leading icon -->
707
+ <span
708
+ v-else-if="item.icon"
709
+ :class="['mdi', `mdi-${item.icon}`, sizeConfig.icon, 'mr-2.5', item.danger ? '' : 'text-muted-foreground']"
710
+ />
711
+
712
+ <!-- Content -->
713
+ <div class="flex-1 min-w-0">
714
+ <div class="truncate">{{ item.label }}</div>
715
+ <div
716
+ v-if="item.description"
717
+ class="text-xs truncate mt-0.5"
718
+ :class="item.danger ? 'text-red-400' : 'text-muted-foreground'"
719
+ >
720
+ {{ item.description }}
721
+ </div>
722
+ </div>
723
+
724
+ <!-- Trailing content -->
725
+ <div class="flex items-center gap-2 ml-4 shrink-0">
726
+ <!-- Keyboard shortcut -->
727
+ <kbd
728
+ v-if="item.shortcut"
729
+ class="px-1.5 py-0.5 text-[10px] font-mono rounded bg-accent text-muted-foreground border border-border"
730
+ >
731
+ {{ item.shortcut }}
732
+ </kbd>
733
+
734
+ <!-- Trailing icon -->
735
+ <span
736
+ v-if="item.trailingIcon"
737
+ :class="['mdi', `mdi-${item.trailingIcon}`, sizeConfig.icon, 'text-muted-foreground']"
738
+ />
739
+
740
+ <!-- Submenu indicator -->
741
+ <span
742
+ v-if="item.children && item.children.length > 0"
743
+ class="mdi mdi-chevron-right text-muted-foreground"
744
+ :class="sizeConfig.icon"
745
+ />
746
+ </div>
747
+ </div>
748
+ </template>
749
+ </template>
750
+
751
+ <!-- Slots for custom content -->
752
+ <slot v-else />
753
+
754
+ <!-- Empty state -->
755
+ <div
756
+ v-if="searchable && searchQuery && (!filteredItems || filteredItems.length === 0)"
757
+ class="px-4 py-8 text-center"
758
+ >
759
+ <span class="mdi mdi-magnify-close text-3xl text-muted-foreground mb-2 block" />
760
+ <p class="text-sm text-muted-foreground">No results found</p>
761
+ </div>
762
+ </div>
763
+
764
+ <!-- Footer slot -->
765
+ <slot name="footer" />
766
+ </div>
767
+ </Transition>
768
+ </Teleport>
769
+
770
+ <!-- Non-teleported fallback -->
771
+ <template v-else>
772
+ <Transition
773
+ :enter-active-class="animationClasses.enter"
774
+ :enter-from-class="animationClasses.enterFrom"
775
+ :enter-to-class="animationClasses.enterTo"
776
+ :leave-active-class="animationClasses.leave"
777
+ :leave-from-class="animationClasses.leaveFrom"
778
+ :leave-to-class="animationClasses.leaveTo"
779
+ >
780
+ <div
781
+ v-if="isOpen"
782
+ ref="dropdownRef"
783
+ role="menu"
784
+ class="s-dropdown-menu absolute mt-1.5 rounded-xl overflow-hidden"
785
+ :class="[sizeConfig.menu, variantClasses]"
786
+ :style="{ minWidth: minWidth, maxHeight: maxHeight, zIndex: zIndex }"
787
+ @mouseenter="handleMenuMouseEnter"
788
+ @mouseleave="handleMenuMouseLeave"
789
+ >
790
+ <div class="overflow-y-auto overscroll-contain" :style="{ maxHeight: maxHeight }">
791
+ <slot />
792
+ </div>
793
+ </div>
794
+ </Transition>
795
+ </template>
796
+ </div>
797
+ </template>
798
+
799
+ <style scoped>
800
+ .s-dropdown-trigger:focus-visible {
801
+ outline: none;
802
+ }
803
+
804
+ .s-dropdown-menu {
805
+ transform-origin: top left;
806
+ scrollbar-width: thin;
807
+ scrollbar-color: var(--s-border) transparent;
808
+ }
809
+
810
+ .s-dropdown-menu::-webkit-scrollbar {
811
+ width: 6px;
812
+ }
813
+
814
+ .s-dropdown-menu::-webkit-scrollbar-track {
815
+ background: transparent;
816
+ }
817
+
818
+ .s-dropdown-menu::-webkit-scrollbar-thumb {
819
+ background: var(--s-border);
820
+ border-radius: 3px;
821
+ }
822
+
823
+ .s-dropdown-menu::-webkit-scrollbar-thumb:hover {
824
+ background: var(--s-input);
825
+ }
826
+
827
+ /* Submenu positioning for bottom placements */
828
+ [data-placement^="bottom"] .s-dropdown-menu {
829
+ transform-origin: top;
830
+ }
831
+
832
+ [data-placement^="top"] .s-dropdown-menu {
833
+ transform-origin: bottom;
834
+ }
835
+
836
+ [data-placement^="left"] .s-dropdown-menu {
837
+ transform-origin: right;
838
+ }
839
+
840
+ [data-placement^="right"] .s-dropdown-menu {
841
+ transform-origin: left;
842
+ }
843
+ </style>