@aleph-alpha/ui-library 1.9.0 → 1.11.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 (432) hide show
  1. package/README.md +14 -0
  2. package/dist/system/index-CkH7HQaa.js +7 -0
  3. package/dist/system/index-CuHwEAQ_.js +7 -0
  4. package/dist/system/index.d.ts +1322 -318
  5. package/dist/system/lib.js +8839 -6993
  6. package/package.json +2 -1
  7. package/src/@types/shims-vue.d.ts +5 -0
  8. package/src/__tests__/placeholder.test.ts +7 -0
  9. package/src/compositions/UiCompositionPlaceholder/UiCompositionPlaceholder.vue +9 -0
  10. package/src/compositions/UiCompositionPlaceholder/index.ts +1 -0
  11. package/src/compositions/UiCompositionPlaceholder/types.ts +8 -0
  12. package/src/compositions/UiDataTable/UiDataTable.mock.ts +104 -0
  13. package/src/compositions/UiDataTable/UiDataTable.stories.ts +1575 -0
  14. package/src/compositions/UiDataTable/UiDataTable.vue +129 -0
  15. package/src/compositions/UiDataTable/UiDataTableColumnHeader.vue +97 -0
  16. package/src/compositions/UiDataTable/UiDataTablePagination.vue +147 -0
  17. package/src/compositions/UiDataTable/UiDataTableToolbar.vue +85 -0
  18. package/src/compositions/UiDataTable/__tests__/UiDataTable.test.ts +372 -0
  19. package/src/compositions/UiDataTable/__tests__/UiDataTableColumnHeader.test.ts +217 -0
  20. package/src/compositions/UiDataTable/__tests__/UiDataTablePagination.test.ts +274 -0
  21. package/src/compositions/UiDataTable/__tests__/UiDataTableToolbar.test.ts +198 -0
  22. package/src/compositions/UiDataTable/constants.ts +77 -0
  23. package/src/compositions/UiDataTable/index.ts +6 -0
  24. package/src/compositions/UiDataTable/types.ts +39 -0
  25. package/src/compositions/UiDatePicker/UiDatePicker.stories.ts +976 -0
  26. package/src/compositions/UiDatePicker/UiDatePicker.vue +193 -0
  27. package/src/compositions/UiDatePicker/__tests__/UiDatePicker.test.ts +325 -0
  28. package/src/compositions/UiDatePicker/index.ts +14 -0
  29. package/src/compositions/UiDatePicker/types.ts +220 -0
  30. package/src/compositions/index.ts +8 -0
  31. package/src/foundations/UiPlaceholder/UiPlaceholder.vue +9 -0
  32. package/src/foundations/UiPlaceholder/index.ts +1 -0
  33. package/src/foundations/UiPlaceholder/types.ts +8 -0
  34. package/src/foundations/index.ts +6 -0
  35. package/src/index.ts +27 -0
  36. package/src/lib/utils.ts +6 -0
  37. package/src/primitives/UiAccordion/UiAccordion.stories.ts +476 -0
  38. package/src/primitives/UiAccordion/UiAccordion.vue +31 -0
  39. package/src/primitives/UiAccordion/UiAccordionContent.vue +16 -0
  40. package/src/primitives/UiAccordion/UiAccordionItem.vue +16 -0
  41. package/src/primitives/UiAccordion/UiAccordionTrigger.vue +23 -0
  42. package/src/primitives/UiAccordion/__tests__/UiAccordion.test.ts +198 -0
  43. package/src/primitives/UiAccordion/index.ts +6 -0
  44. package/src/primitives/UiAccordion/types.ts +95 -0
  45. package/src/primitives/UiAlert/UiAlert.stories.ts +199 -0
  46. package/src/primitives/UiAlert/UiAlert.vue +27 -0
  47. package/src/primitives/UiAlert/UiAlertDescription.vue +13 -0
  48. package/src/primitives/UiAlert/UiAlertTitle.vue +13 -0
  49. package/src/primitives/UiAlert/__tests__/UiAlert.test.ts +20 -0
  50. package/src/primitives/UiAlert/constants.ts +3 -0
  51. package/src/primitives/UiAlert/index.ts +5 -0
  52. package/src/primitives/UiAlert/types.ts +14 -0
  53. package/src/primitives/UiAlertDialog/UiAlertDialog.stories.ts +186 -0
  54. package/src/primitives/UiAlertDialog/UiAlertDialog.vue +18 -0
  55. package/src/primitives/UiAlertDialog/UiAlertDialogAction.vue +16 -0
  56. package/src/primitives/UiAlertDialog/UiAlertDialogCancel.vue +16 -0
  57. package/src/primitives/UiAlertDialog/UiAlertDialogContent.vue +26 -0
  58. package/src/primitives/UiAlertDialog/UiAlertDialogDescription.vue +16 -0
  59. package/src/primitives/UiAlertDialog/UiAlertDialogFooter.vue +13 -0
  60. package/src/primitives/UiAlertDialog/UiAlertDialogHeader.vue +16 -0
  61. package/src/primitives/UiAlertDialog/UiAlertDialogTitle.vue +16 -0
  62. package/src/primitives/UiAlertDialog/UiAlertDialogTrigger.vue +17 -0
  63. package/src/primitives/UiAlertDialog/__tests__/UiAlertDialog.test.ts +184 -0
  64. package/src/primitives/UiAlertDialog/index.ts +9 -0
  65. package/src/primitives/UiAlertDialog/types.ts +83 -0
  66. package/src/primitives/UiAvatar/UiAvatar.stories.ts +194 -0
  67. package/src/primitives/UiAvatar/UiAvatar.vue +13 -0
  68. package/src/primitives/UiAvatar/UiAvatarFallback.vue +13 -0
  69. package/src/primitives/UiAvatar/UiAvatarImage.vue +14 -0
  70. package/src/primitives/UiAvatar/__tests__/UiAvatar.test.ts +36 -0
  71. package/src/primitives/UiAvatar/index.ts +3 -0
  72. package/src/primitives/UiAvatar/types.ts +17 -0
  73. package/src/primitives/UiBadge/UiBadge.stories.ts +373 -0
  74. package/src/primitives/UiBadge/UiBadge.vue +21 -0
  75. package/src/primitives/UiBadge/__tests__/UiBadge.test.ts +44 -0
  76. package/src/primitives/UiBadge/constants.ts +3 -0
  77. package/src/primitives/UiBadge/index.ts +2 -0
  78. package/src/primitives/UiBadge/types.ts +48 -0
  79. package/src/primitives/UiButton/UiButton.stories.ts +537 -0
  80. package/src/primitives/UiButton/UiButton.vue +72 -0
  81. package/src/primitives/UiButton/__tests__/UiButton.test.ts +133 -0
  82. package/src/primitives/UiButton/index.ts +2 -0
  83. package/src/primitives/UiButton/types.ts +87 -0
  84. package/src/primitives/UiCalendar/UiCalendar.stories.ts +797 -0
  85. package/src/primitives/UiCalendar/UiCalendar.vue +67 -0
  86. package/src/primitives/UiCalendar/__tests__/UiCalendar.test.ts +45 -0
  87. package/src/primitives/UiCalendar/index.ts +15 -0
  88. package/src/primitives/UiCalendar/types.ts +236 -0
  89. package/src/primitives/UiCard/UiCard.stories.ts +197 -0
  90. package/src/primitives/UiCard/UiCard.vue +13 -0
  91. package/src/primitives/UiCard/UiCardAction.vue +13 -0
  92. package/src/primitives/UiCard/UiCardContent.vue +13 -0
  93. package/src/primitives/UiCard/UiCardDescription.vue +13 -0
  94. package/src/primitives/UiCard/UiCardFooter.vue +13 -0
  95. package/src/primitives/UiCard/UiCardHeader.vue +13 -0
  96. package/src/primitives/UiCard/UiCardTitle.vue +13 -0
  97. package/src/primitives/UiCard/__tests__/UiCard.test.ts +19 -0
  98. package/src/primitives/UiCard/__tests__/UiCardAction.test.ts +19 -0
  99. package/src/primitives/UiCard/__tests__/UiCardContent.test.ts +19 -0
  100. package/src/primitives/UiCard/__tests__/UiCardDescription.test.ts +19 -0
  101. package/src/primitives/UiCard/__tests__/UiCardFooter.test.ts +19 -0
  102. package/src/primitives/UiCard/__tests__/UiCardHeader.test.ts +19 -0
  103. package/src/primitives/UiCard/__tests__/UiCardTitle.test.ts +19 -0
  104. package/src/primitives/UiCard/index.ts +7 -0
  105. package/src/primitives/UiCard/types.ts +10 -0
  106. package/src/primitives/UiCheckbox/UiCheckbox.stories.ts +231 -0
  107. package/src/primitives/UiCheckbox/UiCheckbox.vue +19 -0
  108. package/src/primitives/UiCheckbox/__tests__/UiCheckbox.test.ts +29 -0
  109. package/src/primitives/UiCheckbox/index.ts +2 -0
  110. package/src/primitives/UiCheckbox/types.ts +30 -0
  111. package/src/primitives/UiDrawer/UiDrawer.stories.ts +602 -0
  112. package/src/primitives/UiDrawer/UiDrawer.vue +19 -0
  113. package/src/primitives/UiDrawer/UiDrawerClose.vue +16 -0
  114. package/src/primitives/UiDrawer/UiDrawerContent.vue +29 -0
  115. package/src/primitives/UiDrawer/UiDrawerDescription.vue +16 -0
  116. package/src/primitives/UiDrawer/UiDrawerFooter.vue +16 -0
  117. package/src/primitives/UiDrawer/UiDrawerHeader.vue +16 -0
  118. package/src/primitives/UiDrawer/UiDrawerTitle.vue +16 -0
  119. package/src/primitives/UiDrawer/UiDrawerTrigger.vue +16 -0
  120. package/src/primitives/UiDrawer/__tests__/UiDrawer.test.ts +229 -0
  121. package/src/primitives/UiDrawer/index.ts +8 -0
  122. package/src/primitives/UiDrawer/types.ts +96 -0
  123. package/src/primitives/UiDropdownMenu/UiDropdownMenu.stories.ts +760 -0
  124. package/src/primitives/UiDropdownMenu/UiDropdownMenu.vue +25 -0
  125. package/src/primitives/UiDropdownMenu/UiDropdownMenuCheckboxItem.vue +29 -0
  126. package/src/primitives/UiDropdownMenu/UiDropdownMenuContent.vue +27 -0
  127. package/src/primitives/UiDropdownMenu/UiDropdownMenuGroup.vue +13 -0
  128. package/src/primitives/UiDropdownMenu/UiDropdownMenuItem.vue +26 -0
  129. package/src/primitives/UiDropdownMenu/UiDropdownMenuLabel.vue +18 -0
  130. package/src/primitives/UiDropdownMenu/UiDropdownMenuRadioGroup.vue +20 -0
  131. package/src/primitives/UiDropdownMenu/UiDropdownMenuRadioItem.vue +26 -0
  132. package/src/primitives/UiDropdownMenu/UiDropdownMenuSeparator.vue +11 -0
  133. package/src/primitives/UiDropdownMenu/UiDropdownMenuShortcut.vue +13 -0
  134. package/src/primitives/UiDropdownMenu/UiDropdownMenuSub.vue +26 -0
  135. package/src/primitives/UiDropdownMenu/UiDropdownMenuSubContent.vue +23 -0
  136. package/src/primitives/UiDropdownMenu/UiDropdownMenuSubTrigger.vue +24 -0
  137. package/src/primitives/UiDropdownMenu/UiDropdownMenuTrigger.vue +24 -0
  138. package/src/primitives/UiDropdownMenu/__tests__/UiDropdownMenu.test.ts +557 -0
  139. package/src/primitives/UiDropdownMenu/index.ts +16 -0
  140. package/src/primitives/UiDropdownMenu/types.ts +219 -0
  141. package/src/primitives/UiField/UiField.stories.ts +1496 -0
  142. package/src/primitives/UiField/UiField.vue +18 -0
  143. package/src/primitives/UiField/UiFieldContent.vue +13 -0
  144. package/src/primitives/UiField/UiFieldDescription.vue +13 -0
  145. package/src/primitives/UiField/UiFieldError.vue +20 -0
  146. package/src/primitives/UiField/UiFieldGroup.vue +13 -0
  147. package/src/primitives/UiField/UiFieldLabel.vue +16 -0
  148. package/src/primitives/UiField/UiFieldLegend.vue +13 -0
  149. package/src/primitives/UiField/UiFieldSeparator.vue +13 -0
  150. package/src/primitives/UiField/UiFieldSet.vue +13 -0
  151. package/src/primitives/UiField/UiFieldTitle.vue +13 -0
  152. package/src/primitives/UiField/__tests__/UiFieldError.test.ts +35 -0
  153. package/src/primitives/UiField/index.ts +10 -0
  154. package/src/primitives/UiField/types.ts +47 -0
  155. package/src/primitives/UiIcon/UiIcon.stories.ts +95 -0
  156. package/src/primitives/UiIcon/UiIcon.vue +14 -0
  157. package/src/primitives/UiIcon/__tests__/UiIcon.test.ts +24 -0
  158. package/src/primitives/UiIcon/index.ts +1 -0
  159. package/src/primitives/UiIcon/types.ts +23 -0
  160. package/src/primitives/UiIconButton/UiIconButton.stories.ts +446 -0
  161. package/src/primitives/UiIconButton/UiIconButton.vue +63 -0
  162. package/src/primitives/UiIconButton/__tests__/UiIconButton.test.ts +102 -0
  163. package/src/primitives/UiIconButton/index.ts +2 -0
  164. package/src/primitives/UiIconButton/types.ts +67 -0
  165. package/src/primitives/UiInput/UiInput.stories.ts +193 -0
  166. package/src/primitives/UiInput/UiInput.vue +19 -0
  167. package/src/primitives/UiInput/__tests__/UiInput.test.ts +38 -0
  168. package/src/primitives/UiInput/index.ts +2 -0
  169. package/src/primitives/UiInput/types.ts +31 -0
  170. package/src/primitives/UiPopover/UiPopover.stories.ts +394 -0
  171. package/src/primitives/UiPopover/UiPopover.vue +17 -0
  172. package/src/primitives/UiPopover/UiPopoverContent.vue +27 -0
  173. package/src/primitives/UiPopover/UiPopoverTrigger.vue +16 -0
  174. package/src/primitives/UiPopover/__tests__/UiPopover.test.ts +87 -0
  175. package/src/primitives/UiPopover/index.ts +5 -0
  176. package/src/primitives/UiPopover/types.ts +86 -0
  177. package/src/primitives/UiProgress/UiProgress.stories.ts +92 -0
  178. package/src/primitives/UiProgress/UiProgress.vue +25 -0
  179. package/src/primitives/UiProgress/__tests__/UiProgress.test.ts +46 -0
  180. package/src/primitives/UiProgress/index.ts +2 -0
  181. package/src/primitives/UiProgress/types.ts +16 -0
  182. package/src/primitives/UiRadioGroup/UiRadioGroup.stories.ts +291 -0
  183. package/src/primitives/UiRadioGroup/UiRadioGroup.vue +43 -0
  184. package/src/primitives/UiRadioGroup/UiRadioGroupItem.vue +18 -0
  185. package/src/primitives/UiRadioGroup/__tests__/UiRadioGroup.test.ts +404 -0
  186. package/src/primitives/UiRadioGroup/index.ts +4 -0
  187. package/src/primitives/UiRadioGroup/types.ts +66 -0
  188. package/src/primitives/UiRangeCalendar/UiRangeCalendar.stories.ts +609 -0
  189. package/src/primitives/UiRangeCalendar/UiRangeCalendar.vue +50 -0
  190. package/src/primitives/UiRangeCalendar/__tests__/UiRangeCalendar.test.ts +35 -0
  191. package/src/primitives/UiRangeCalendar/index.ts +13 -0
  192. package/src/primitives/UiRangeCalendar/types.ts +184 -0
  193. package/src/primitives/UiSelect/UiSelect.stories.ts +425 -0
  194. package/src/primitives/UiSelect/UiSelect.vue +47 -0
  195. package/src/primitives/UiSelect/UiSelectContent.vue +30 -0
  196. package/src/primitives/UiSelect/UiSelectGroup.vue +13 -0
  197. package/src/primitives/UiSelect/UiSelectItem.vue +19 -0
  198. package/src/primitives/UiSelect/UiSelectLabel.vue +13 -0
  199. package/src/primitives/UiSelect/UiSelectSeparator.vue +11 -0
  200. package/src/primitives/UiSelect/UiSelectTrigger.vue +30 -0
  201. package/src/primitives/UiSelect/UiSelectValue.vue +18 -0
  202. package/src/primitives/UiSelect/__tests__/UiSelect.test.ts +211 -0
  203. package/src/primitives/UiSelect/__tests__/UiSelectContent.test.ts +30 -0
  204. package/src/primitives/UiSelect/__tests__/UiSelectGroup.test.ts +85 -0
  205. package/src/primitives/UiSelect/__tests__/UiSelectItem.test.ts +79 -0
  206. package/src/primitives/UiSelect/__tests__/UiSelectLabel.test.ts +83 -0
  207. package/src/primitives/UiSelect/__tests__/UiSelectSeparator.test.ts +82 -0
  208. package/src/primitives/UiSelect/__tests__/UiSelectTrigger.test.ts +54 -0
  209. package/src/primitives/UiSelect/__tests__/UiSelectValue.test.ts +39 -0
  210. package/src/primitives/UiSelect/index.ts +10 -0
  211. package/src/primitives/UiSelect/types.ts +93 -0
  212. package/src/primitives/UiSlider/UiSlider.stories.ts +226 -0
  213. package/src/primitives/UiSlider/UiSlider.vue +44 -0
  214. package/src/primitives/UiSlider/__tests__/UiSlider.test.ts +76 -0
  215. package/src/primitives/UiSlider/index.ts +1 -0
  216. package/src/primitives/UiSlider/types.ts +101 -0
  217. package/src/primitives/UiSpinner/UiSpinner.stories.ts +143 -0
  218. package/src/primitives/UiSpinner/UiSpinner.vue +16 -0
  219. package/src/primitives/UiSpinner/__tests__/UiSpinner.test.ts +19 -0
  220. package/src/primitives/UiSpinner/index.ts +2 -0
  221. package/src/primitives/UiSpinner/types.ts +16 -0
  222. package/src/primitives/UiSwitch/UiSwitch.stories.ts +120 -0
  223. package/src/primitives/UiSwitch/UiSwitch.vue +21 -0
  224. package/src/primitives/UiSwitch/__tests__/UiSwitch.test.ts +47 -0
  225. package/src/primitives/UiSwitch/index.ts +2 -0
  226. package/src/primitives/UiSwitch/types.ts +25 -0
  227. package/src/primitives/UiTable/UiTable.stories.ts +505 -0
  228. package/src/primitives/UiTable/UiTable.vue +13 -0
  229. package/src/primitives/UiTable/UiTableBody.vue +13 -0
  230. package/src/primitives/UiTable/UiTableCaption.vue +13 -0
  231. package/src/primitives/UiTable/UiTableCell.vue +16 -0
  232. package/src/primitives/UiTable/UiTableEmpty.vue +18 -0
  233. package/src/primitives/UiTable/UiTableFooter.vue +13 -0
  234. package/src/primitives/UiTable/UiTableHead.vue +18 -0
  235. package/src/primitives/UiTable/UiTableHeader.vue +13 -0
  236. package/src/primitives/UiTable/UiTableRow.vue +18 -0
  237. package/src/primitives/UiTable/__tests__/UiTable.test.ts +19 -0
  238. package/src/primitives/UiTable/__tests__/UiTableBody.test.ts +19 -0
  239. package/src/primitives/UiTable/__tests__/UiTableCaption.test.ts +19 -0
  240. package/src/primitives/UiTable/__tests__/UiTableCell.test.ts +26 -0
  241. package/src/primitives/UiTable/__tests__/UiTableEmpty.test.ts +32 -0
  242. package/src/primitives/UiTable/__tests__/UiTableFooter.test.ts +19 -0
  243. package/src/primitives/UiTable/__tests__/UiTableHead.test.ts +43 -0
  244. package/src/primitives/UiTable/__tests__/UiTableHeader.test.ts +19 -0
  245. package/src/primitives/UiTable/__tests__/UiTableRow.test.ts +32 -0
  246. package/src/primitives/UiTable/index.ts +16 -0
  247. package/src/primitives/UiTable/types.ts +68 -0
  248. package/src/primitives/UiTabs/UiTabs.stories.ts +456 -0
  249. package/src/primitives/UiTabs/UiTabs.vue +31 -0
  250. package/src/primitives/UiTabs/UiTabsContent.vue +16 -0
  251. package/src/primitives/UiTabs/UiTabsList.vue +16 -0
  252. package/src/primitives/UiTabs/UiTabsTrigger.vue +16 -0
  253. package/src/primitives/UiTabs/__tests__/UiTabs.test.ts +122 -0
  254. package/src/primitives/UiTabs/index.ts +6 -0
  255. package/src/primitives/UiTabs/types.ts +68 -0
  256. package/src/primitives/UiTextarea/UiTextarea.stories.ts +107 -0
  257. package/src/primitives/UiTextarea/UiTextarea.vue +19 -0
  258. package/src/primitives/UiTextarea/__tests__/UiTextarea.test.ts +40 -0
  259. package/src/primitives/UiTextarea/index.ts +2 -0
  260. package/src/primitives/UiTextarea/types.ts +30 -0
  261. package/src/primitives/UiTooltip/UiTooltip.stories.ts +550 -0
  262. package/src/primitives/UiTooltip/UiTooltip.vue +42 -0
  263. package/src/primitives/UiTooltip/__tests__/UiTooltip.test.ts +78 -0
  264. package/src/primitives/UiTooltip/index.ts +2 -0
  265. package/src/primitives/UiTooltip/types.ts +53 -0
  266. package/src/primitives/index.ts +33 -0
  267. package/src/primitives/shadcn/accordion/Accordion.vue +15 -0
  268. package/src/primitives/shadcn/accordion/AccordionContent.vue +23 -0
  269. package/src/primitives/shadcn/accordion/AccordionItem.vue +24 -0
  270. package/src/primitives/shadcn/accordion/AccordionTrigger.vue +35 -0
  271. package/src/primitives/shadcn/accordion/index.ts +4 -0
  272. package/src/primitives/shadcn/alert/Alert.vue +17 -0
  273. package/src/primitives/shadcn/alert/AlertDescription.vue +22 -0
  274. package/src/primitives/shadcn/alert/AlertTitle.vue +17 -0
  275. package/src/primitives/shadcn/alert/index.ts +24 -0
  276. package/src/primitives/shadcn/alert-dialog/AlertDialog.vue +15 -0
  277. package/src/primitives/shadcn/alert-dialog/AlertDialogAction.vue +18 -0
  278. package/src/primitives/shadcn/alert-dialog/AlertDialogCancel.vue +21 -0
  279. package/src/primitives/shadcn/alert-dialog/AlertDialogContent.vue +44 -0
  280. package/src/primitives/shadcn/alert-dialog/AlertDialogDescription.vue +21 -0
  281. package/src/primitives/shadcn/alert-dialog/AlertDialogFooter.vue +17 -0
  282. package/src/primitives/shadcn/alert-dialog/AlertDialogHeader.vue +17 -0
  283. package/src/primitives/shadcn/alert-dialog/AlertDialogTitle.vue +21 -0
  284. package/src/primitives/shadcn/alert-dialog/AlertDialogTrigger.vue +12 -0
  285. package/src/primitives/shadcn/alert-dialog/index.ts +9 -0
  286. package/src/primitives/shadcn/avatar/Avatar.vue +18 -0
  287. package/src/primitives/shadcn/avatar/AvatarFallback.vue +21 -0
  288. package/src/primitives/shadcn/avatar/AvatarImage.vue +12 -0
  289. package/src/primitives/shadcn/avatar/index.ts +3 -0
  290. package/src/primitives/shadcn/badge/Badge.vue +28 -0
  291. package/src/primitives/shadcn/badge/index.ts +24 -0
  292. package/src/primitives/shadcn/button/Button.vue +29 -0
  293. package/src/primitives/shadcn/button/index.ts +36 -0
  294. package/src/primitives/shadcn/calendar/Calendar.vue +206 -0
  295. package/src/primitives/shadcn/calendar/CalendarCell.vue +28 -0
  296. package/src/primitives/shadcn/calendar/CalendarCellTrigger.vue +44 -0
  297. package/src/primitives/shadcn/calendar/CalendarGrid.vue +23 -0
  298. package/src/primitives/shadcn/calendar/CalendarGridBody.vue +12 -0
  299. package/src/primitives/shadcn/calendar/CalendarGridHead.vue +13 -0
  300. package/src/primitives/shadcn/calendar/CalendarGridRow.vue +23 -0
  301. package/src/primitives/shadcn/calendar/CalendarHeadCell.vue +23 -0
  302. package/src/primitives/shadcn/calendar/CalendarHeader.vue +23 -0
  303. package/src/primitives/shadcn/calendar/CalendarHeading.vue +30 -0
  304. package/src/primitives/shadcn/calendar/CalendarNextButton.vue +33 -0
  305. package/src/primitives/shadcn/calendar/CalendarPrevButton.vue +33 -0
  306. package/src/primitives/shadcn/calendar/index.ts +14 -0
  307. package/src/primitives/shadcn/card/Card.vue +22 -0
  308. package/src/primitives/shadcn/card/CardAction.vue +17 -0
  309. package/src/primitives/shadcn/card/CardContent.vue +14 -0
  310. package/src/primitives/shadcn/card/CardDescription.vue +14 -0
  311. package/src/primitives/shadcn/card/CardFooter.vue +14 -0
  312. package/src/primitives/shadcn/card/CardHeader.vue +22 -0
  313. package/src/primitives/shadcn/card/CardTitle.vue +14 -0
  314. package/src/primitives/shadcn/card/index.ts +7 -0
  315. package/src/primitives/shadcn/checkbox/Checkbox.vue +38 -0
  316. package/src/primitives/shadcn/checkbox/index.ts +1 -0
  317. package/src/primitives/shadcn/drawer/Drawer.vue +15 -0
  318. package/src/primitives/shadcn/drawer/DrawerClose.vue +12 -0
  319. package/src/primitives/shadcn/drawer/DrawerContent.vue +52 -0
  320. package/src/primitives/shadcn/drawer/DrawerDescription.vue +20 -0
  321. package/src/primitives/shadcn/drawer/DrawerFooter.vue +17 -0
  322. package/src/primitives/shadcn/drawer/DrawerHeader.vue +17 -0
  323. package/src/primitives/shadcn/drawer/DrawerTitle.vue +20 -0
  324. package/src/primitives/shadcn/drawer/DrawerTrigger.vue +12 -0
  325. package/src/primitives/shadcn/drawer/index.ts +8 -0
  326. package/src/primitives/shadcn/dropdown-menu/DropdownMenu.vue +15 -0
  327. package/src/primitives/shadcn/dropdown-menu/DropdownMenuCheckboxItem.vue +41 -0
  328. package/src/primitives/shadcn/dropdown-menu/DropdownMenuContent.vue +40 -0
  329. package/src/primitives/shadcn/dropdown-menu/DropdownMenuGroup.vue +12 -0
  330. package/src/primitives/shadcn/dropdown-menu/DropdownMenuItem.vue +41 -0
  331. package/src/primitives/shadcn/dropdown-menu/DropdownMenuLabel.vue +25 -0
  332. package/src/primitives/shadcn/dropdown-menu/DropdownMenuRadioGroup.vue +15 -0
  333. package/src/primitives/shadcn/dropdown-menu/DropdownMenuRadioItem.vue +38 -0
  334. package/src/primitives/shadcn/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  335. package/src/primitives/shadcn/dropdown-menu/DropdownMenuShortcut.vue +17 -0
  336. package/src/primitives/shadcn/dropdown-menu/DropdownMenuSub.vue +15 -0
  337. package/src/primitives/shadcn/dropdown-menu/DropdownMenuSubContent.vue +29 -0
  338. package/src/primitives/shadcn/dropdown-menu/DropdownMenuSubTrigger.vue +31 -0
  339. package/src/primitives/shadcn/dropdown-menu/DropdownMenuTrigger.vue +14 -0
  340. package/src/primitives/shadcn/dropdown-menu/index.ts +16 -0
  341. package/src/primitives/shadcn/field/Field.vue +22 -0
  342. package/src/primitives/shadcn/field/FieldContent.vue +17 -0
  343. package/src/primitives/shadcn/field/FieldDescription.vue +24 -0
  344. package/src/primitives/shadcn/field/FieldError.vue +69 -0
  345. package/src/primitives/shadcn/field/FieldGroup.vue +22 -0
  346. package/src/primitives/shadcn/field/FieldLabel.vue +28 -0
  347. package/src/primitives/shadcn/field/FieldLegend.vue +26 -0
  348. package/src/primitives/shadcn/field/FieldSeparator.vue +28 -0
  349. package/src/primitives/shadcn/field/FieldSet.vue +23 -0
  350. package/src/primitives/shadcn/field/FieldTitle.vue +22 -0
  351. package/src/primitives/shadcn/field/index.ts +39 -0
  352. package/src/primitives/shadcn/icon/Icon.vue +38 -0
  353. package/src/primitives/shadcn/icon/index.ts +1 -0
  354. package/src/primitives/shadcn/index.ts +3 -0
  355. package/src/primitives/shadcn/input/Input.vue +35 -0
  356. package/src/primitives/shadcn/input/index.ts +1 -0
  357. package/src/primitives/shadcn/label/Label.vue +28 -0
  358. package/src/primitives/shadcn/label/index.ts +1 -0
  359. package/src/primitives/shadcn/native-select/NativeSelect.vue +56 -0
  360. package/src/primitives/shadcn/native-select/NativeSelectOptGroup.vue +18 -0
  361. package/src/primitives/shadcn/native-select/NativeSelectOption.vue +18 -0
  362. package/src/primitives/shadcn/native-select/index.ts +3 -0
  363. package/src/primitives/shadcn/popover/Popover.vue +19 -0
  364. package/src/primitives/shadcn/popover/PopoverContent.vue +41 -0
  365. package/src/primitives/shadcn/popover/PopoverTrigger.vue +11 -0
  366. package/src/primitives/shadcn/popover/index.ts +4 -0
  367. package/src/primitives/shadcn/progress/Progress.vue +30 -0
  368. package/src/primitives/shadcn/progress/index.ts +1 -0
  369. package/src/primitives/shadcn/radio-group/RadioGroup.vue +25 -0
  370. package/src/primitives/shadcn/radio-group/RadioGroupItem.vue +38 -0
  371. package/src/primitives/shadcn/radio-group/index.ts +2 -0
  372. package/src/primitives/shadcn/range-calendar/RangeCalendar.vue +73 -0
  373. package/src/primitives/shadcn/range-calendar/RangeCalendarCell.vue +28 -0
  374. package/src/primitives/shadcn/range-calendar/RangeCalendarCellTrigger.vue +46 -0
  375. package/src/primitives/shadcn/range-calendar/RangeCalendarGrid.vue +23 -0
  376. package/src/primitives/shadcn/range-calendar/RangeCalendarGridBody.vue +12 -0
  377. package/src/primitives/shadcn/range-calendar/RangeCalendarGridHead.vue +12 -0
  378. package/src/primitives/shadcn/range-calendar/RangeCalendarGridRow.vue +23 -0
  379. package/src/primitives/shadcn/range-calendar/RangeCalendarHeadCell.vue +23 -0
  380. package/src/primitives/shadcn/range-calendar/RangeCalendarHeader.vue +23 -0
  381. package/src/primitives/shadcn/range-calendar/RangeCalendarHeading.vue +30 -0
  382. package/src/primitives/shadcn/range-calendar/RangeCalendarNextButton.vue +34 -0
  383. package/src/primitives/shadcn/range-calendar/RangeCalendarPrevButton.vue +34 -0
  384. package/src/primitives/shadcn/range-calendar/index.ts +12 -0
  385. package/src/primitives/shadcn/select/Select.vue +15 -0
  386. package/src/primitives/shadcn/select/SelectContent.vue +55 -0
  387. package/src/primitives/shadcn/select/SelectGroup.vue +12 -0
  388. package/src/primitives/shadcn/select/SelectItem.vue +39 -0
  389. package/src/primitives/shadcn/select/SelectItemText.vue +12 -0
  390. package/src/primitives/shadcn/select/SelectLabel.vue +17 -0
  391. package/src/primitives/shadcn/select/SelectScrollDownButton.vue +26 -0
  392. package/src/primitives/shadcn/select/SelectScrollUpButton.vue +26 -0
  393. package/src/primitives/shadcn/select/SelectSeparator.vue +19 -0
  394. package/src/primitives/shadcn/select/SelectTrigger.vue +37 -0
  395. package/src/primitives/shadcn/select/SelectValue.vue +12 -0
  396. package/src/primitives/shadcn/select/index.ts +11 -0
  397. package/src/primitives/shadcn/separator/Separator.vue +27 -0
  398. package/src/primitives/shadcn/separator/index.ts +1 -0
  399. package/src/primitives/shadcn/slider/Slider.vue +45 -0
  400. package/src/primitives/shadcn/slider/index.ts +1 -0
  401. package/src/primitives/shadcn/spinner/Spinner.vue +18 -0
  402. package/src/primitives/shadcn/spinner/index.ts +1 -0
  403. package/src/primitives/shadcn/switch/Switch.vue +40 -0
  404. package/src/primitives/shadcn/switch/index.ts +1 -0
  405. package/src/primitives/shadcn/table/Table.vue +16 -0
  406. package/src/primitives/shadcn/table/TableBody.vue +14 -0
  407. package/src/primitives/shadcn/table/TableCaption.vue +14 -0
  408. package/src/primitives/shadcn/table/TableCell.vue +26 -0
  409. package/src/primitives/shadcn/table/TableEmpty.vue +29 -0
  410. package/src/primitives/shadcn/table/TableFooter.vue +17 -0
  411. package/src/primitives/shadcn/table/TableHead.vue +28 -0
  412. package/src/primitives/shadcn/table/TableHeader.vue +14 -0
  413. package/src/primitives/shadcn/table/TableRow.vue +21 -0
  414. package/src/primitives/shadcn/table/index.ts +9 -0
  415. package/src/primitives/shadcn/table/utils.ts +8 -0
  416. package/src/primitives/shadcn/tabs/Tabs.vue +24 -0
  417. package/src/primitives/shadcn/tabs/TabsContent.vue +21 -0
  418. package/src/primitives/shadcn/tabs/TabsList.vue +26 -0
  419. package/src/primitives/shadcn/tabs/TabsTrigger.vue +28 -0
  420. package/src/primitives/shadcn/tabs/index.ts +4 -0
  421. package/src/primitives/shadcn/textarea/Textarea.vue +33 -0
  422. package/src/primitives/shadcn/textarea/index.ts +1 -0
  423. package/src/primitives/shadcn/tooltip/Tooltip.vue +15 -0
  424. package/src/primitives/shadcn/tooltip/TooltipContent.vue +40 -0
  425. package/src/primitives/shadcn/tooltip/TooltipProvider.vue +12 -0
  426. package/src/primitives/shadcn/tooltip/TooltipTrigger.vue +12 -0
  427. package/src/primitives/shadcn/tooltip/index.ts +4 -0
  428. package/src/styles/global.css +1 -0
  429. package/src/templates/UiTemplatePlaceholder/UiTemplatePlaceholder.vue +9 -0
  430. package/src/templates/UiTemplatePlaceholder/index.ts +1 -0
  431. package/src/templates/UiTemplatePlaceholder/types.ts +8 -0
  432. package/src/templates/index.ts +6 -0
@@ -0,0 +1,193 @@
1
+ <script setup lang="ts">
2
+ import { Calendar } from 'lucide-vue-next';
3
+ import { DateFormatter, getLocalTimeZone } from '@internationalized/date';
4
+ import type { DateRange, DateValue } from 'reka-ui';
5
+ import { computed, defineAsyncComponent, ref } from 'vue';
6
+ import { cn } from '@/lib/utils';
7
+ import { UiButton } from '@/primitives/UiButton';
8
+ import { UiPopover, UiPopoverContent, UiPopoverTrigger } from '@/primitives/UiPopover';
9
+ import type { UiDatePickerProps } from './types';
10
+
11
+ defineOptions({
12
+ name: 'UiDatePicker',
13
+ });
14
+
15
+ // Lazy-load calendar components to reduce bundle size.
16
+ // Using v-if/v-else ensures only the needed component is loaded,
17
+ // while maintaining full TypeScript type safety.
18
+ const UiCalendar = defineAsyncComponent(() =>
19
+ import('@/primitives/UiCalendar').then((m) => m.UiCalendar),
20
+ );
21
+ const UiRangeCalendar = defineAsyncComponent(() =>
22
+ import('@/primitives/UiRangeCalendar').then((m) => m.UiRangeCalendar),
23
+ );
24
+
25
+ const props = withDefaults(defineProps<UiDatePickerProps>(), {
26
+ mode: 'single',
27
+ disabled: false,
28
+ readonly: false,
29
+ locale: 'en',
30
+ weekStartsOn: 0,
31
+ numberOfMonths: 1,
32
+ pagedNavigation: false,
33
+ weekdayFormat: 'narrow',
34
+ fixedWeeks: false,
35
+ preventDeselect: false,
36
+ closeOnSelect: true,
37
+ dateFormat: () => ({ dateStyle: 'long' }),
38
+ rangeSeparator: ' - ',
39
+ });
40
+
41
+ const modelValue = defineModel<UiDatePickerProps['modelValue']>();
42
+ const open = defineModel<boolean>('open');
43
+
44
+ // Internal state for uncontrolled mode (when defaultValue is provided without v-model)
45
+ const internalValue = ref<UiDatePickerProps['modelValue']>(props.defaultValue);
46
+ const internalOpen = ref(false);
47
+
48
+ // Uncontrolled mode: defaultValue provided, use internal state
49
+ // Controlled mode: no defaultValue, use v-model
50
+ const isUncontrolled = props.defaultValue !== undefined;
51
+
52
+ const effectiveValue = computed(() => (isUncontrolled ? internalValue.value : modelValue.value));
53
+ const effectiveOpen = computed({
54
+ get: () => open.value ?? internalOpen.value,
55
+ set: (value: boolean) => {
56
+ open.value = value;
57
+ internalOpen.value = value;
58
+ },
59
+ });
60
+
61
+ const isDateRange = (value: unknown): value is DateRange => {
62
+ return value !== null && typeof value === 'object' && 'start' in value && 'end' in value;
63
+ };
64
+
65
+ // Type-safe computed values for each mode (avoids type assertions in template)
66
+ // Cast needed because TypeScript can't narrow the DateValue | DateRange union
67
+ const singleValue = computed((): DateValue | undefined => {
68
+ const value = effectiveValue.value as DateValue | DateRange | undefined;
69
+ return props.mode === 'single' && value && !isDateRange(value) ? value : undefined;
70
+ });
71
+
72
+ const rangeValue = computed((): DateRange | undefined => {
73
+ const value = effectiveValue.value as DateValue | DateRange | undefined;
74
+ return props.mode === 'range' && value && isDateRange(value) ? value : undefined;
75
+ });
76
+
77
+ const dateFormatter = computed(() => new DateFormatter(props.locale, props.dateFormat));
78
+
79
+ const computedPlaceholder = computed(() => {
80
+ if (props.placeholder) return props.placeholder;
81
+ return props.mode === 'range' ? 'Pick a date range' : 'Pick a date';
82
+ });
83
+
84
+ const displayValue = computed(() => {
85
+ if (props.mode === 'range') {
86
+ const range = rangeValue.value;
87
+ if (!range?.start) return computedPlaceholder.value;
88
+
89
+ const startStr = dateFormatter.value.format(range.start.toDate(getLocalTimeZone()));
90
+ if (!range.end) return startStr;
91
+
92
+ const endStr = dateFormatter.value.format(range.end.toDate(getLocalTimeZone()));
93
+ return `${startStr}${props.rangeSeparator}${endStr}`;
94
+ }
95
+
96
+ const single = singleValue.value;
97
+ if (!single) return computedPlaceholder.value;
98
+ return dateFormatter.value.format(single.toDate(getLocalTimeZone()));
99
+ });
100
+
101
+ const updateValue = (value: UiDatePickerProps['modelValue']) => {
102
+ if (isUncontrolled) {
103
+ internalValue.value = value;
104
+ } else {
105
+ modelValue.value = value;
106
+ }
107
+ };
108
+
109
+ const handleSingleDateUpdate = (value: DateValue | DateValue[] | undefined) => {
110
+ // Handle array case (shouldn't happen in single mode, but type-safe)
111
+ const newValue = Array.isArray(value) ? value[0] : value;
112
+ updateValue(newValue);
113
+
114
+ if (props.closeOnSelect) {
115
+ effectiveOpen.value = false;
116
+ }
117
+ };
118
+
119
+ const handleRangeDateUpdate = (value: DateRange | undefined) => {
120
+ updateValue(value);
121
+ // Close only when both start and end dates are selected
122
+ if (props.closeOnSelect && value?.start && value?.end) {
123
+ effectiveOpen.value = false;
124
+ }
125
+ };
126
+ </script>
127
+
128
+ <template>
129
+ <UiPopover v-model:open="effectiveOpen">
130
+ <UiPopoverTrigger as-child>
131
+ <UiButton
132
+ variant="outline"
133
+ :class="
134
+ cn(
135
+ props.mode === 'range' ? 'w-[300px]' : 'w-[240px]',
136
+ 'justify-start text-left font-normal',
137
+ !effectiveValue && 'text-muted-foreground',
138
+ props.class,
139
+ )
140
+ "
141
+ :disabled="props.disabled"
142
+ :aria-label="props.ariaLabel"
143
+ >
144
+ <Calendar class="mr-2 size-4 shrink-0" />
145
+ <span class="truncate">{{ displayValue }}</span>
146
+ </UiButton>
147
+ </UiPopoverTrigger>
148
+ <UiPopoverContent class="w-auto p-0" align="start">
149
+ <Suspense>
150
+ <UiCalendar
151
+ v-if="props.mode === 'single'"
152
+ :model-value="singleValue"
153
+ :min-value="props.minValue"
154
+ :max-value="props.maxValue"
155
+ :disabled="props.disabled"
156
+ :readonly="props.readonly"
157
+ :locale="props.locale"
158
+ :week-starts-on="props.weekStartsOn"
159
+ :number-of-months="props.numberOfMonths"
160
+ :weekday-format="props.weekdayFormat"
161
+ :fixed-weeks="props.fixedWeeks"
162
+ :layout="props.layout"
163
+ :prevent-deselect="props.preventDeselect"
164
+ :calendar-label="props.calendarLabel"
165
+ :is-date-disabled="props.isDateDisabled"
166
+ :is-date-unavailable="props.isDateUnavailable"
167
+ initial-focus
168
+ @update:model-value="handleSingleDateUpdate"
169
+ />
170
+ <UiRangeCalendar
171
+ v-else
172
+ :model-value="rangeValue"
173
+ :min-value="props.minValue"
174
+ :max-value="props.maxValue"
175
+ :disabled="props.disabled"
176
+ :readonly="props.readonly"
177
+ :locale="props.locale"
178
+ :week-starts-on="props.weekStartsOn"
179
+ :number-of-months="props.numberOfMonths"
180
+ :paged-navigation="props.pagedNavigation"
181
+ :weekday-format="props.weekdayFormat"
182
+ :fixed-weeks="props.fixedWeeks"
183
+ :prevent-deselect="props.preventDeselect"
184
+ :calendar-label="props.calendarLabel"
185
+ :is-date-disabled="props.isDateDisabled"
186
+ :is-date-unavailable="props.isDateUnavailable"
187
+ initial-focus
188
+ @update:model-value="handleRangeDateUpdate"
189
+ />
190
+ </Suspense>
191
+ </UiPopoverContent>
192
+ </UiPopover>
193
+ </template>
@@ -0,0 +1,325 @@
1
+ import { CalendarDate } from '@internationalized/date';
2
+ import { render } from '@testing-library/vue';
3
+ import userEvent from '@testing-library/user-event';
4
+ import type { DateRange } from 'reka-ui';
5
+ import { describe, expect, test } from 'vitest';
6
+ import UiDatePicker from '../UiDatePicker.vue';
7
+
8
+ describe('UiDatePicker', () => {
9
+ const testDate = new CalendarDate(2025, 1, 15);
10
+
11
+ test('renders with custom class on trigger button', () => {
12
+ const { getByRole } = render(UiDatePicker, {
13
+ props: { class: 'w-full custom-picker' },
14
+ });
15
+ const button = getByRole('button');
16
+ expect(button).toHaveClass('w-full', 'custom-picker');
17
+ });
18
+
19
+ test('displays placeholder when no date is selected', () => {
20
+ const { getByRole } = render(UiDatePicker, {
21
+ props: { placeholder: 'Select a date' },
22
+ });
23
+ expect(getByRole('button')).toHaveTextContent('Select a date');
24
+ });
25
+
26
+ test('displays formatted date when modelValue is set', () => {
27
+ const { getByRole } = render(UiDatePicker, {
28
+ props: { modelValue: testDate, locale: 'en' },
29
+ });
30
+ expect(getByRole('button')).toHaveTextContent('January 15, 2025');
31
+ });
32
+
33
+ test('applies disabled state to trigger button', () => {
34
+ const { getByRole } = render(UiDatePicker, {
35
+ props: { disabled: true },
36
+ });
37
+ expect(getByRole('button')).toBeDisabled();
38
+ });
39
+
40
+ test('emits update:modelValue with a DateValue when date is selected', async () => {
41
+ const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
42
+ props: { modelValue: testDate },
43
+ });
44
+
45
+ // Open the popover
46
+ await userEvent.click(getByRole('button'));
47
+
48
+ // Find and click a date cell (use findAllByRole to wait for async component)
49
+ const cells = await findAllByRole('gridcell');
50
+ const clickableCell = cells.find(
51
+ (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
52
+ );
53
+ const dateButton = clickableCell?.querySelector('button');
54
+ expect(dateButton).toBeInTheDocument();
55
+
56
+ await userEvent.click(dateButton!);
57
+
58
+ const emittedEvents = emitted('update:modelValue');
59
+ expect(emittedEvents).toHaveLength(1);
60
+ const emittedValue = emittedEvents[0][0];
61
+ expect(emittedValue).toHaveProperty('year');
62
+ expect(emittedValue).toHaveProperty('month');
63
+ expect(emittedValue).toHaveProperty('day');
64
+ });
65
+
66
+ test('displays defaultValue immediately in uncontrolled mode', () => {
67
+ const { getByRole } = render(UiDatePicker, {
68
+ props: { defaultValue: testDate, locale: 'en' },
69
+ });
70
+ // Should display the default value, not the placeholder
71
+ expect(getByRole('button')).toHaveTextContent('January 15, 2025');
72
+ });
73
+
74
+ test('updates display after selection in uncontrolled mode', async () => {
75
+ const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
76
+ props: { defaultValue: testDate, locale: 'en' },
77
+ });
78
+
79
+ // Initially shows the default value
80
+ expect(getByRole('button')).toHaveTextContent('January 15, 2025');
81
+
82
+ // Open and select a different date (use findAllByRole to wait for async component)
83
+ await userEvent.click(getByRole('button'));
84
+ const cells = await findAllByRole('gridcell');
85
+ const clickableCell = cells.find(
86
+ (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
87
+ );
88
+ await userEvent.click(clickableCell!.querySelector('button')!);
89
+
90
+ // Display should update to show the newly selected date (not the placeholder)
91
+ const buttonText = getByRole('button').textContent;
92
+ expect(buttonText).not.toBe('Pick a date');
93
+ expect(buttonText).not.toBe('January 15, 2025'); // Should be different from initial
94
+
95
+ // In uncontrolled mode, update:modelValue should NOT be emitted
96
+ expect(emitted('update:modelValue')).toBeUndefined();
97
+ });
98
+
99
+ test('closes popover after date selection when closeOnSelect is true', async () => {
100
+ const { getByRole, findAllByRole, queryByRole } = render(UiDatePicker, {
101
+ props: { closeOnSelect: true },
102
+ });
103
+
104
+ // Open the popover
105
+ await userEvent.click(getByRole('button'));
106
+ expect(queryByRole('dialog')).toBeInTheDocument();
107
+
108
+ // Select a date (use findAllByRole to wait for async component)
109
+ const cells = await findAllByRole('gridcell');
110
+ const clickableCell = cells.find(
111
+ (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
112
+ );
113
+ await userEvent.click(clickableCell!.querySelector('button')!);
114
+
115
+ // Popover should be closed
116
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
117
+ });
118
+
119
+ test('keeps popover open after date selection when closeOnSelect is false', async () => {
120
+ const { getByRole, findAllByRole, queryByRole } = render(UiDatePicker, {
121
+ props: { closeOnSelect: false },
122
+ });
123
+
124
+ // Open the popover
125
+ await userEvent.click(getByRole('button'));
126
+ expect(queryByRole('dialog')).toBeInTheDocument();
127
+
128
+ // Select a date (use findAllByRole to wait for async component)
129
+ const cells = await findAllByRole('gridcell');
130
+ const clickableCell = cells.find(
131
+ (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
132
+ );
133
+ await userEvent.click(clickableCell!.querySelector('button')!);
134
+
135
+ // Popover should remain open
136
+ expect(queryByRole('dialog')).toBeInTheDocument();
137
+ });
138
+
139
+ test('formats date according to locale', () => {
140
+ const { getByRole } = render(UiDatePicker, {
141
+ props: { modelValue: testDate, locale: 'de' },
142
+ });
143
+ // German format: "15. Januar 2025"
144
+ expect(getByRole('button')).toHaveTextContent('15. Januar 2025');
145
+ });
146
+
147
+ test('defaults to "Pick a date" placeholder in single mode', () => {
148
+ const { getByRole } = render(UiDatePicker, {
149
+ props: { mode: 'single' },
150
+ });
151
+ expect(getByRole('button')).toHaveTextContent('Pick a date');
152
+ });
153
+
154
+ test('defaults to "Pick a date range" placeholder in range mode', () => {
155
+ const { getByRole } = render(UiDatePicker, {
156
+ props: { mode: 'range' },
157
+ });
158
+ expect(getByRole('button')).toHaveTextContent('Pick a date range');
159
+ });
160
+
161
+ test('uses custom placeholder in range mode', () => {
162
+ const { getByRole } = render(UiDatePicker, {
163
+ props: { mode: 'range', placeholder: 'Select travel dates' },
164
+ });
165
+ expect(getByRole('button')).toHaveTextContent('Select travel dates');
166
+ });
167
+
168
+ test('displays formatted date range when modelValue is set in range mode', () => {
169
+ const dateRange: DateRange = {
170
+ start: new CalendarDate(2025, 1, 15),
171
+ end: new CalendarDate(2025, 1, 20),
172
+ };
173
+ const { getByRole } = render(UiDatePicker, {
174
+ props: { mode: 'range', modelValue: dateRange, locale: 'en' },
175
+ });
176
+ expect(getByRole('button')).toHaveTextContent('January 15, 2025 - January 20, 2025');
177
+ });
178
+
179
+ test('displays partial date range when only start date is selected', () => {
180
+ const dateRange: DateRange = {
181
+ start: new CalendarDate(2025, 1, 15),
182
+ end: undefined,
183
+ };
184
+ const { getByRole } = render(UiDatePicker, {
185
+ props: { mode: 'range', modelValue: dateRange, locale: 'en' },
186
+ });
187
+ expect(getByRole('button')).toHaveTextContent('January 15, 2025');
188
+ });
189
+
190
+ test('emits update:modelValue with a DateRange when range is selected', async () => {
191
+ const initialRange: DateRange = { start: undefined, end: undefined };
192
+ const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
193
+ props: { mode: 'range', modelValue: initialRange },
194
+ });
195
+
196
+ // Open the popover
197
+ await userEvent.click(getByRole('button'));
198
+
199
+ // Find and click two date cells (use findAllByRole to wait for async component)
200
+ const cells = await findAllByRole('gridcell');
201
+ const clickableCells = cells.filter(
202
+ (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
203
+ );
204
+
205
+ // Click first date (start)
206
+ await userEvent.click(clickableCells[5]?.querySelector('button')!);
207
+
208
+ // Click second date (end)
209
+ await userEvent.click(clickableCells[10]?.querySelector('button')!);
210
+
211
+ const emittedEvents = emitted('update:modelValue');
212
+ expect(emittedEvents.length).toBeGreaterThanOrEqual(1);
213
+ const lastEmittedValue = emittedEvents[emittedEvents.length - 1][0];
214
+ expect(lastEmittedValue).toHaveProperty('start');
215
+ expect(lastEmittedValue).toHaveProperty('end');
216
+ expect(lastEmittedValue.start).toHaveProperty('year');
217
+ expect(lastEmittedValue.end).toHaveProperty('year');
218
+ });
219
+
220
+ test('keeps popover open in range mode until both dates are selected when closeOnSelect is true', async () => {
221
+ const { getByRole, findAllByRole, queryByRole } = render(UiDatePicker, {
222
+ props: { mode: 'range', closeOnSelect: true },
223
+ });
224
+
225
+ // Open the popover
226
+ await userEvent.click(getByRole('button'));
227
+ expect(queryByRole('dialog')).toBeInTheDocument();
228
+
229
+ // Select first date (use findAllByRole to wait for async component)
230
+ const cells = await findAllByRole('gridcell');
231
+ const clickableCells = cells.filter(
232
+ (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
233
+ );
234
+ await userEvent.click(clickableCells[5]?.querySelector('button')!);
235
+
236
+ // Popover should still be open (only start date selected)
237
+ expect(queryByRole('dialog')).toBeInTheDocument();
238
+
239
+ // Select second date
240
+ await userEvent.click(clickableCells[10]?.querySelector('button')!);
241
+
242
+ // Now popover should close (both dates selected)
243
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
244
+ });
245
+
246
+ describe('lazy loading optimization', () => {
247
+ test('lazily loads UiCalendar in single mode when popover opens', async () => {
248
+ const { getByRole, queryByRole, findByRole } = render(UiDatePicker, {
249
+ props: { mode: 'single', modelValue: new CalendarDate(2025, 1, 15) },
250
+ });
251
+
252
+ // Before opening, popover content is not rendered
253
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
254
+
255
+ // Open the popover
256
+ await userEvent.click(getByRole('button'));
257
+
258
+ // Calendar should be rendered after async load (findByRole waits for element)
259
+ await findByRole('dialog');
260
+ await findByRole('grid');
261
+ });
262
+
263
+ test('lazily loads UiRangeCalendar in range mode when popover opens', async () => {
264
+ const { getByRole, queryByRole, findByRole } = render(UiDatePicker, {
265
+ props: { mode: 'range' },
266
+ });
267
+
268
+ // Before opening, popover content is not rendered
269
+ expect(queryByRole('dialog')).not.toBeInTheDocument();
270
+
271
+ // Open the popover
272
+ await userEvent.click(getByRole('button'));
273
+
274
+ // RangeCalendar should be rendered after async load (findByRole waits for element)
275
+ await findByRole('dialog');
276
+ await findByRole('grid');
277
+ });
278
+
279
+ test('single mode calendar is fully functional after lazy load', async () => {
280
+ const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
281
+ props: { mode: 'single', modelValue: new CalendarDate(2025, 1, 15) },
282
+ });
283
+
284
+ // Open popover and wait for async component
285
+ await userEvent.click(getByRole('button'));
286
+
287
+ // Wait for calendar grid cells to load (findAllByRole waits for elements)
288
+ const cells = await findAllByRole('gridcell');
289
+ const clickableCell = cells.find(
290
+ (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
291
+ );
292
+ await userEvent.click(clickableCell!.querySelector('button')!);
293
+
294
+ // Should emit the selected date
295
+ const emittedEvents = emitted('update:modelValue');
296
+ expect(emittedEvents).toHaveLength(1);
297
+ expect(emittedEvents[0][0]).toHaveProperty('year');
298
+ });
299
+
300
+ test('range mode calendar is fully functional after lazy load', async () => {
301
+ const { getByRole, findAllByRole, emitted } = render(UiDatePicker, {
302
+ props: { mode: 'range' },
303
+ });
304
+
305
+ // Open popover and wait for async component
306
+ await userEvent.click(getByRole('button'));
307
+
308
+ // Wait for calendar grid cells to load (findAllByRole waits for elements)
309
+ const cells = await findAllByRole('gridcell');
310
+ const clickableCells = cells.filter(
311
+ (cell) => !cell.querySelector('button')?.hasAttribute('data-disabled'),
312
+ );
313
+
314
+ await userEvent.click(clickableCells[5]?.querySelector('button')!);
315
+ await userEvent.click(clickableCells[10]?.querySelector('button')!);
316
+
317
+ // Should emit the date range
318
+ const emittedEvents = emitted('update:modelValue');
319
+ expect(emittedEvents.length).toBeGreaterThanOrEqual(1);
320
+ const lastEmitted = emittedEvents[emittedEvents.length - 1][0];
321
+ expect(lastEmitted).toHaveProperty('start');
322
+ expect(lastEmitted).toHaveProperty('end');
323
+ });
324
+ });
325
+ });
@@ -0,0 +1,14 @@
1
+ export { default as UiDatePicker } from './UiDatePicker.vue';
2
+
3
+ export type {
4
+ UiDatePickerProps,
5
+ UiDatePickerMode,
6
+ UiDatePickerLayout,
7
+ UiDatePickerWeekdayFormat,
8
+ UiDatePickerWeekStartsOn,
9
+ DateRange,
10
+ DateValue,
11
+ Matcher,
12
+ } from './types';
13
+
14
+ export { CalendarDate, getLocalTimeZone, today } from '@internationalized/date';