@boxcustodia/library 2.0.0-alpha.11 → 2.0.0-alpha.12

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 (349) hide show
  1. package/dist/index.css +1 -1
  2. package/package.json +4 -3
  3. package/src/__doc__/Changelog.mdx +6 -0
  4. package/src/__doc__/Components.mdx +73 -0
  5. package/src/__doc__/Examples.tsx +69 -0
  6. package/src/__doc__/Icons.mdx +41 -0
  7. package/src/__doc__/Intro.mdx +138 -0
  8. package/src/__doc__/MCP.mdx +71 -0
  9. package/src/__doc__/Migration.mdx +475 -0
  10. package/src/__doc__/Theme.mdx +132 -0
  11. package/src/__doc__/Types.mdx +252 -0
  12. package/src/components/alert/alert.stories.tsx +142 -0
  13. package/src/components/alert/alert.tsx +109 -0
  14. package/src/components/alert/index.ts +7 -0
  15. package/src/components/alert-dialog/alert-dialog.stories.tsx +173 -0
  16. package/src/components/alert-dialog/alert-dialog.test.tsx +49 -0
  17. package/src/components/alert-dialog/alert-dialog.tsx +265 -0
  18. package/src/components/alert-dialog/index.ts +1 -0
  19. package/src/components/auto-complete/auto-complete-primitives.tsx +155 -0
  20. package/src/components/auto-complete/auto-complete.stories.tsx +241 -0
  21. package/src/components/auto-complete/auto-complete.tsx +82 -0
  22. package/src/components/auto-complete/index.ts +2 -0
  23. package/src/components/avatar/avatar.stories.tsx +84 -0
  24. package/src/components/avatar/avatar.test.tsx +61 -0
  25. package/src/components/avatar/avatar.tsx +104 -0
  26. package/src/components/avatar/index.ts +1 -0
  27. package/src/components/background-image/background-image.stories.tsx +21 -0
  28. package/src/components/background-image/background-image.test.tsx +29 -0
  29. package/src/components/background-image/background-image.tsx +23 -0
  30. package/src/components/background-image/index.ts +1 -0
  31. package/src/components/button/button.stories.tsx +396 -0
  32. package/src/components/button/button.test.tsx +58 -0
  33. package/src/components/button/button.tsx +31 -0
  34. package/src/components/button/button.variants.ts +44 -0
  35. package/src/components/button/components/base-button.tsx +86 -0
  36. package/src/components/button/components/loader-overlay.tsx +21 -0
  37. package/src/components/button/components/loading-icon.tsx +47 -0
  38. package/src/components/button/index.ts +3 -0
  39. package/src/components/calendar/calendar.model.ts +86 -0
  40. package/src/components/calendar/calendar.stories.tsx +155 -0
  41. package/src/components/calendar/calendar.test.tsx +12 -0
  42. package/src/components/calendar/calendar.tsx +185 -0
  43. package/src/components/calendar/components/calendar-navigation.tsx +141 -0
  44. package/src/components/calendar/components/day.tsx +61 -0
  45. package/src/components/calendar/components/decade-view.tsx +45 -0
  46. package/src/components/calendar/components/index.ts +6 -0
  47. package/src/components/calendar/components/month-view.tsx +58 -0
  48. package/src/components/calendar/components/week-days.tsx +27 -0
  49. package/src/components/calendar/components/year-view.tsx +29 -0
  50. package/src/components/calendar/hooks/index.ts +4 -0
  51. package/src/components/calendar/hooks/use-calendar-navigation.ts +79 -0
  52. package/src/components/calendar/hooks/use-calendar.ts +90 -0
  53. package/src/components/calendar/hooks/use-multiple-calendar.ts +34 -0
  54. package/src/components/calendar/hooks/use-range-calendar.ts +91 -0
  55. package/src/components/calendar/hooks/use-single-calendar.ts +18 -0
  56. package/src/components/calendar/index.ts +1 -0
  57. package/src/components/calendar/utils/typeguards.ts +7 -0
  58. package/src/components/card/card.stories.tsx +116 -0
  59. package/src/components/card/card.tsx +74 -0
  60. package/src/components/card/index.ts +1 -0
  61. package/src/components/center/center.stories.tsx +81 -0
  62. package/src/components/center/center.tsx +24 -0
  63. package/src/components/center/index.ts +1 -0
  64. package/src/components/checkbox/checkbox.stories.tsx +307 -0
  65. package/src/components/checkbox/checkbox.tsx +273 -0
  66. package/src/components/checkbox/index.ts +1 -0
  67. package/src/components/checkbox-group/checkbox-group.stories.tsx +104 -0
  68. package/src/components/checkbox-group/checkbox-group.tsx +16 -0
  69. package/src/components/checkbox-group/index.ts +1 -0
  70. package/src/components/combobox/combobox.stories.tsx +339 -0
  71. package/src/components/combobox/combobox.tsx +892 -0
  72. package/src/components/combobox/index.ts +1 -0
  73. package/src/components/date-picker/date-input.stories.tsx +158 -0
  74. package/src/components/date-picker/date-input.tsx +163 -0
  75. package/src/components/date-picker/date-picker.model.ts +90 -0
  76. package/src/components/date-picker/date-picker.stories.tsx +200 -0
  77. package/src/components/date-picker/date-picker.test.tsx +23 -0
  78. package/src/components/date-picker/date-picker.tsx +298 -0
  79. package/src/components/date-picker/date-picker.utils.ts +260 -0
  80. package/src/components/date-picker/index.ts +3 -0
  81. package/src/components/date-picker/use-date-input-popover.ts +48 -0
  82. package/src/components/date-picker/use-date-input.ts +125 -0
  83. package/src/components/dialog/dialog.stories.tsx +171 -0
  84. package/src/components/dialog/dialog.test.tsx +68 -0
  85. package/src/components/dialog/dialog.tsx +277 -0
  86. package/src/components/dialog/index.ts +1 -0
  87. package/src/components/divider/divider.stories.tsx +70 -0
  88. package/src/components/divider/divider.test.tsx +22 -0
  89. package/src/components/divider/divider.tsx +23 -0
  90. package/src/components/divider/index.ts +1 -0
  91. package/src/components/dropzone/dropzone.stories.tsx +210 -0
  92. package/src/components/dropzone/dropzone.tsx +154 -0
  93. package/src/components/dropzone/file-types.ts +64 -0
  94. package/src/components/dropzone/index.ts +3 -0
  95. package/src/components/dropzone/upload-primitives.tsx +310 -0
  96. package/src/components/dropzone/use-dropzone.ts +122 -0
  97. package/src/components/empty-state/empty-state.stories.tsx +56 -0
  98. package/src/components/empty-state/empty-state.tsx +39 -0
  99. package/src/components/empty-state/index.ts +1 -0
  100. package/src/components/field/field.stories.tsx +223 -0
  101. package/src/components/field/field.tsx +229 -0
  102. package/src/components/field/index.ts +1 -0
  103. package/src/components/form/form.stories.tsx +594 -0
  104. package/src/components/form/form.tsx +30 -0
  105. package/src/components/form/index.ts +1 -0
  106. package/src/components/heading/heading.stories.tsx +74 -0
  107. package/src/components/heading/heading.tsx +28 -0
  108. package/src/components/heading/heading.variants.ts +27 -0
  109. package/src/components/heading/index.ts +1 -0
  110. package/src/components/index.ts +46 -0
  111. package/src/components/input/index.ts +1 -0
  112. package/src/components/input/input.stories.tsx +104 -0
  113. package/src/components/input/input.tsx +75 -0
  114. package/src/components/kbd/index.ts +1 -0
  115. package/src/components/kbd/kbd.stories.tsx +40 -0
  116. package/src/components/kbd/kbd.tsx +31 -0
  117. package/src/components/kbd/kbd.variants.ts +26 -0
  118. package/src/components/label/index.ts +1 -0
  119. package/src/components/label/label.stories.tsx +68 -0
  120. package/src/components/label/label.test.tsx +61 -0
  121. package/src/components/label/label.tsx +62 -0
  122. package/src/components/loader/index.ts +1 -0
  123. package/src/components/loader/loader.stories.tsx +60 -0
  124. package/src/components/loader/loader.test.tsx +26 -0
  125. package/src/components/loader/loader.tsx +60 -0
  126. package/src/components/menu/index.ts +2 -0
  127. package/src/components/menu/menu-primitives.tsx +248 -0
  128. package/src/components/menu/menu.stories.tsx +203 -0
  129. package/src/components/menu/menu.tsx +100 -0
  130. package/src/components/menu/util/render-menu-item.tsx +54 -0
  131. package/src/components/multi-select/hooks/use-multi-select.ts +66 -0
  132. package/src/components/multi-select/index.ts +1 -0
  133. package/src/components/multi-select/multi-select.stories.tsx +294 -0
  134. package/src/components/multi-select/multi-select.tsx +300 -0
  135. package/src/components/multi-select/multi-select.variants.ts +22 -0
  136. package/src/components/number-input/index.ts +1 -0
  137. package/src/components/number-input/number-input.stories.tsx +209 -0
  138. package/src/components/number-input/number-input.test.tsx +87 -0
  139. package/src/components/number-input/number-input.tsx +230 -0
  140. package/src/components/pagination/components/pagination-option.tsx +27 -0
  141. package/src/components/pagination/index.ts +1 -0
  142. package/src/components/pagination/pagination.stories.tsx +80 -0
  143. package/src/components/pagination/pagination.test.tsx +76 -0
  144. package/src/components/pagination/pagination.tsx +102 -0
  145. package/src/components/password/index.ts +1 -0
  146. package/src/components/password/password.stories.tsx +104 -0
  147. package/src/components/password/password.tsx +71 -0
  148. package/src/components/popover/index.ts +1 -0
  149. package/src/components/popover/popover.stories.tsx +213 -0
  150. package/src/components/popover/popover.tsx +203 -0
  151. package/src/components/progress/index.ts +1 -0
  152. package/src/components/progress/progress.stories.tsx +124 -0
  153. package/src/components/progress/progress.test.tsx +25 -0
  154. package/src/components/progress/progress.tsx +124 -0
  155. package/src/components/scroll-area/index.ts +1 -0
  156. package/src/components/scroll-area/scroll-area.stories.tsx +166 -0
  157. package/src/components/scroll-area/scroll-area.tsx +64 -0
  158. package/src/components/select/index.ts +1 -0
  159. package/src/components/select/select.stories.tsx +253 -0
  160. package/src/components/select/select.tsx +430 -0
  161. package/src/components/show/index.ts +1 -0
  162. package/src/components/show/show.stories.tsx +197 -0
  163. package/src/components/show/show.test.tsx +41 -0
  164. package/src/components/show/show.tsx +16 -0
  165. package/src/components/skeleton/index.ts +1 -0
  166. package/src/components/skeleton/skeleton.stories.tsx +36 -0
  167. package/src/components/skeleton/skeleton.test.tsx +14 -0
  168. package/src/components/skeleton/skeleton.tsx +15 -0
  169. package/src/components/stack/index.ts +1 -0
  170. package/src/components/stack/stack.stories.tsx +194 -0
  171. package/src/components/stack/stack.tsx +52 -0
  172. package/src/components/stepper/Stepper.tsx +190 -0
  173. package/src/components/stepper/context/stepper-context.tsx +11 -0
  174. package/src/components/stepper/index.ts +1 -0
  175. package/src/components/stepper/stepper.stories.tsx +130 -0
  176. package/src/components/stepper/stepper.test.tsx +91 -0
  177. package/src/components/switch/index.ts +1 -0
  178. package/src/components/switch/switch.stories.tsx +122 -0
  179. package/src/components/switch/switch.test.tsx +30 -0
  180. package/src/components/switch/switch.tsx +86 -0
  181. package/src/components/table/index.ts +3 -0
  182. package/src/components/table/table-primitives.tsx +122 -0
  183. package/src/components/table/table.model.ts +20 -0
  184. package/src/components/table/table.stories.tsx +169 -0
  185. package/src/components/table/table.test.tsx +91 -0
  186. package/src/components/table/table.tsx +109 -0
  187. package/src/components/table-pagination/index.ts +2 -0
  188. package/src/components/table-pagination/table-pagination.model.ts +2 -0
  189. package/src/components/table-pagination/table-pagination.stories.tsx +23 -0
  190. package/src/components/table-pagination/table-pagination.test.tsx +32 -0
  191. package/src/components/table-pagination/table-pagination.tsx +108 -0
  192. package/src/components/tabs/context/tabs-context.tsx +14 -0
  193. package/src/components/tabs/index.ts +1 -0
  194. package/src/components/tabs/tabs.stories.tsx +182 -0
  195. package/src/components/tabs/tabs.test.tsx +61 -0
  196. package/src/components/tabs/tabs.tsx +175 -0
  197. package/src/components/tag/index.ts +2 -0
  198. package/src/components/tag/tag.stories.tsx +170 -0
  199. package/src/components/tag/tag.test.tsx +18 -0
  200. package/src/components/tag/tag.tsx +99 -0
  201. package/src/components/tag/tag.variants.ts +31 -0
  202. package/src/components/textarea/index.ts +1 -0
  203. package/src/components/textarea/textarea.stories.tsx +73 -0
  204. package/src/components/textarea/textarea.tsx +105 -0
  205. package/src/components/timeline/index.ts +1 -0
  206. package/src/components/timeline/timeline-status.ts +5 -0
  207. package/src/components/timeline/timeline.stories.tsx +84 -0
  208. package/src/components/timeline/timeline.tsx +147 -0
  209. package/src/components/toast/index.ts +1 -0
  210. package/src/components/toast/toast.stories.tsx +392 -0
  211. package/src/components/toast/toast.test.tsx +50 -0
  212. package/src/components/toast/toast.tsx +411 -0
  213. package/src/components/tooltip/index.ts +1 -0
  214. package/src/components/tooltip/tooltip.stories.tsx +226 -0
  215. package/src/components/tooltip/tooltip.test.tsx +46 -0
  216. package/src/components/tooltip/tooltip.tsx +171 -0
  217. package/src/components/tree/hooks/use-controllable-tree-state.ts +80 -0
  218. package/src/components/tree/index.ts +2 -0
  219. package/src/components/tree/tree-primitives.tsx +126 -0
  220. package/src/components/tree/tree.stories.tsx +468 -0
  221. package/src/components/tree/tree.tsx +42 -0
  222. package/src/hooks/index.ts +26 -0
  223. package/src/hooks/useArray/__doc__/useArray.stories.tsx +100 -0
  224. package/src/hooks/useArray/__test__/useArray.test.tsx +88 -0
  225. package/src/hooks/useArray/index.ts +1 -0
  226. package/src/hooks/useArray/useArray.ts +76 -0
  227. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +149 -0
  228. package/src/hooks/useAsync/__test__/useAsync.test.tsx +68 -0
  229. package/src/hooks/useAsync/index.ts +1 -0
  230. package/src/hooks/useAsync/useAsync.ts +58 -0
  231. package/src/hooks/useClickOutside/__doc__/useClickOutside.stories.tsx +40 -0
  232. package/src/hooks/useClickOutside/__test__/useClickOutside.test.tsx +33 -0
  233. package/src/hooks/useClickOutside/index.ts +1 -0
  234. package/src/hooks/useClickOutside/useClickOutside.ts +26 -0
  235. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +45 -0
  236. package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +19 -0
  237. package/src/hooks/useClipboard/index.ts +1 -0
  238. package/src/hooks/useClipboard/useClipboard.tsx +28 -0
  239. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +84 -0
  240. package/src/hooks/useDebounceCallback/index.ts +1 -0
  241. package/src/hooks/useDebounceCallback/useDebouncedCallback.ts +23 -0
  242. package/src/hooks/useDebounceValue/__doc__/useDebouncedValue.stories.tsx +75 -0
  243. package/src/hooks/useDebounceValue/index.ts +1 -0
  244. package/src/hooks/useDebounceValue/useDebouncedValue.ts +17 -0
  245. package/src/hooks/useDisclosure/__doc__/useDisclosure.stories.tsx +39 -0
  246. package/src/hooks/useDisclosure/__test__/useDisclosure.test.ts +43 -0
  247. package/src/hooks/useDisclosure/index.ts +1 -0
  248. package/src/hooks/useDisclosure/useDisclosure.ts +37 -0
  249. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +26 -0
  250. package/src/hooks/useDocumentTitle/index.ts +1 -0
  251. package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +11 -0
  252. package/src/hooks/useEventListener/__doc__/useEventListener.stories.tsx +28 -0
  253. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +26 -0
  254. package/src/hooks/useEventListener/index.ts +1 -0
  255. package/src/hooks/useEventListener/useEventListener.ts +25 -0
  256. package/src/hooks/useFocusTrap/__doc__/useFocusTrap.stories.tsx +37 -0
  257. package/src/hooks/useFocusTrap/index.ts +1 -0
  258. package/src/hooks/useFocusTrap/scopeTab.ts +38 -0
  259. package/src/hooks/useFocusTrap/tabbable.ts +70 -0
  260. package/src/hooks/useFocusTrap/useFocusTrap.ts +78 -0
  261. package/src/hooks/useHotkey/__docs__/useHotkey.stories.tsx +116 -0
  262. package/src/hooks/useHotkey/__test__/useHotkey.test.tsx +105 -0
  263. package/src/hooks/useHotkey/__utils__/create-hotkey-listener.ts +25 -0
  264. package/src/hooks/useHotkey/__utils__/index.ts +3 -0
  265. package/src/hooks/useHotkey/__utils__/is-input-field.ts +14 -0
  266. package/src/hooks/useHotkey/__utils__/match-key-modifiers.ts +25 -0
  267. package/src/hooks/useHotkey/index.ts +1 -0
  268. package/src/hooks/useHotkey/useHotkey.ts +34 -0
  269. package/src/hooks/useHover/__doc__/useHover.stories.tsx +41 -0
  270. package/src/hooks/useHover/__test__/useHover.test.tsx +45 -0
  271. package/src/hooks/useHover/index.ts +1 -0
  272. package/src/hooks/useHover/useHover.tsx +40 -0
  273. package/src/hooks/useIsVisible/__doc__/useIsVisible.stories.tsx +60 -0
  274. package/src/hooks/useIsVisible/index.ts +1 -0
  275. package/src/hooks/useIsVisible/useIsVisible.tsx +50 -0
  276. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +86 -0
  277. package/src/hooks/useLocalStorage/__test__/useLocalStorage.test.ts +85 -0
  278. package/src/hooks/useLocalStorage/index.ts +1 -0
  279. package/src/hooks/useLocalStorage/useLocalStorage.ts +57 -0
  280. package/src/hooks/useMediaQuery/__doc__/useMediaQuery.stories.tsx +39 -0
  281. package/src/hooks/useMediaQuery/index.ts +1 -0
  282. package/src/hooks/useMediaQuery/useMediaQuery.ts +22 -0
  283. package/src/hooks/useMemoizedFn/index.ts +1 -0
  284. package/src/hooks/useMemoizedFn/useMemoizedFn.ts +32 -0
  285. package/src/hooks/useMutation/__doc__/useMutation.stories.tsx +111 -0
  286. package/src/hooks/useMutation/__test__/useMutation.test.tsx +83 -0
  287. package/src/hooks/useMutation/index.ts +1 -0
  288. package/src/hooks/useMutation/useMutation.tsx +60 -0
  289. package/src/hooks/useObject/__doc__/useObject.stories.tsx +119 -0
  290. package/src/hooks/useObject/__test__/useObject.test.tsx +87 -0
  291. package/src/hooks/useObject/index.ts +1 -0
  292. package/src/hooks/useObject/useObject.tsx +48 -0
  293. package/src/hooks/usePagination/__doc__/usePagination.stories.tsx +72 -0
  294. package/src/hooks/usePagination/__test__/usePagination.test.tsx +98 -0
  295. package/src/hooks/usePagination/index.ts +2 -0
  296. package/src/hooks/usePagination/usePagination.tsx +74 -0
  297. package/src/hooks/usePortal/__doc__/usePortal.stories.tsx +19 -0
  298. package/src/hooks/usePortal/__test__/usePortal.test.tsx +20 -0
  299. package/src/hooks/usePortal/index.ts +1 -0
  300. package/src/hooks/usePortal/usePortal.ts +40 -0
  301. package/src/hooks/usePreventCloseWindow/__doc__/usePreventCloseWindow.stories.tsx +32 -0
  302. package/src/hooks/usePreventCloseWindow/index.ts +1 -0
  303. package/src/hooks/usePreventCloseWindow/usePreventCloseWindow.ts +33 -0
  304. package/src/hooks/useRangePagination/__test__/useRangePagination.test.tsx +63 -0
  305. package/src/hooks/useRangePagination/index.ts +2 -0
  306. package/src/hooks/useRangePagination/useRangePagination.tsx +72 -0
  307. package/src/hooks/useSelection/__doc__/useSelection.stories.tsx +140 -0
  308. package/src/hooks/useSelection/__test__/useSelection.test.tsx +57 -0
  309. package/src/hooks/useSelection/index.ts +1 -0
  310. package/src/hooks/useSelection/useSelection.ts +121 -0
  311. package/src/hooks/useStep/__doc__/useStep.stories.tsx +98 -0
  312. package/src/hooks/useStep/__test__/useStep.test.ts +51 -0
  313. package/src/hooks/useStep/index.ts +1 -0
  314. package/src/hooks/useStep/useStep.ts +57 -0
  315. package/src/hooks/useToggle/__doc__/useToggle.stories.tsx +25 -0
  316. package/src/hooks/useToggle/__test__/useToggle.test.tsx +43 -0
  317. package/src/hooks/useToggle/index.ts +1 -0
  318. package/src/hooks/useToggle/useToggle.ts +16 -0
  319. package/src/index.ts +6 -0
  320. package/src/lib/cn.ts +8 -0
  321. package/src/lib/index.ts +1 -0
  322. package/src/models/Generic.model.ts +67 -0
  323. package/src/models/index.ts +1 -0
  324. package/src/providers/index.ts +2 -0
  325. package/src/providers/library-provider.tsx +44 -0
  326. package/src/providers/theme/ThemeProvider.tsx +25 -0
  327. package/src/providers/theme/index.ts +3 -0
  328. package/src/providers/theme/types.ts +11 -0
  329. package/src/providers/theme/useThemeProps.ts +25 -0
  330. package/src/stores/theme.store.ts +31 -0
  331. package/src/styles/components.css +4 -0
  332. package/src/styles/index.css +2 -0
  333. package/src/styles/library.css +2 -0
  334. package/src/styles/theme.css +232 -0
  335. package/src/utils/dates/parseDateRange.utility.ts +39 -0
  336. package/src/utils/form.tsx +91 -0
  337. package/src/utils/functions/createSafeContext.ts +17 -0
  338. package/src/utils/functions/ensureReactElement.tsx +30 -0
  339. package/src/utils/functions/getFormData.ts +19 -0
  340. package/src/utils/functions/index.ts +4 -0
  341. package/src/utils/functions/mergeRefs.ts +18 -0
  342. package/src/utils/index.ts +3 -0
  343. package/src/utils/strings/extractInitials.utility.ts +10 -0
  344. package/src/utils/strings/index.ts +1 -0
  345. package/src/utils/tests/click.ts +3 -0
  346. package/src/utils/tests/index.ts +2 -0
  347. package/src/utils/tests/keyboard.ts +21 -0
  348. package/src/utils/tests/type.ts +6 -0
  349. package/dist/components.css +0 -2
@@ -0,0 +1,411 @@
1
+ import {
2
+ type ToastManagerAddOptions as BaseToastManagerAddOptions,
3
+ Toast,
4
+ type ToastObject,
5
+ } from "@base-ui/react/toast";
6
+ import { CircleCheck, CircleX, Info, TriangleAlert, X } from "lucide-react";
7
+ import {
8
+ type ComponentProps,
9
+ type ComponentType,
10
+ type ReactNode,
11
+ useEffect,
12
+ useMemo,
13
+ } from "react";
14
+ import { cn } from "../../lib";
15
+ import { Button } from "../button";
16
+
17
+ // ─── Types ────────────────────────────────────────────────────────────────────
18
+
19
+ export type ToastVariant = "default" | "success" | "error" | "warning" | "info";
20
+
21
+ type ButtonProps = ComponentProps<typeof Button>;
22
+
23
+ // ─── Variant configuration ────────────────────────────────────────────────────
24
+
25
+ type VariantConfig = {
26
+ icon: ComponentType<{ className?: string }> | null;
27
+ ringClass: string;
28
+ titleClass: string;
29
+ bgClass: string;
30
+ };
31
+
32
+ const TOAST_VARIANT_CONFIG: Record<ToastVariant, VariantConfig> = {
33
+ default: {
34
+ icon: null,
35
+ ringClass: "ring-input",
36
+ titleClass: "",
37
+ bgClass: "",
38
+ },
39
+ success: {
40
+ icon: CircleCheck,
41
+ ringClass: "ring-success",
42
+ titleClass: "text-success",
43
+ bgClass: "bg-success/5",
44
+ },
45
+ error: {
46
+ icon: CircleX,
47
+ ringClass: "ring-error",
48
+ titleClass: "text-error",
49
+ bgClass: "bg-error/5",
50
+ },
51
+ warning: {
52
+ icon: TriangleAlert,
53
+ ringClass: "ring-warning",
54
+ titleClass: "text-warning",
55
+ bgClass: "bg-warning/5",
56
+ },
57
+ info: {
58
+ icon: Info,
59
+ ringClass: "ring-info",
60
+ titleClass: "text-info",
61
+ bgClass: "bg-info/5",
62
+ },
63
+ };
64
+
65
+ export function toastVariants({
66
+ variant = "default",
67
+ }: {
68
+ variant?: ToastVariant;
69
+ } = {}) {
70
+ return cn(
71
+ "rounded-xl ring-1 bg-clip-padding p-4 shadow-lg bg-background",
72
+ TOAST_VARIANT_CONFIG[variant].ringClass,
73
+ );
74
+ }
75
+
76
+ // ─── Option types ─────────────────────────────────────────────────────────────
77
+
78
+ type ToastOptionsBase = {
79
+ variant?: ToastVariant;
80
+ content?: ReactNode;
81
+ actions?: ButtonProps[];
82
+ bump?: boolean;
83
+ };
84
+
85
+ export type ToastOptions<Data extends object = object> = ToastObject<Data> &
86
+ ToastOptionsBase;
87
+ export type ToastManagerAddOptions<Data extends object = object> =
88
+ BaseToastManagerAddOptions<Data> & ToastOptionsBase;
89
+
90
+ // ─── Manager wrapping ─────────────────────────────────────────────────────────
91
+
92
+ function wrapManagerMethods<
93
+ T extends {
94
+ add: Function;
95
+ update: Function;
96
+ promise: Function;
97
+ close: Function;
98
+ },
99
+ >(manager: T) {
100
+ return {
101
+ ...manager,
102
+
103
+ close: (id?: string) => manager.close(id),
104
+
105
+ add: (options: ToastManagerAddOptions<any>) => {
106
+ if (options.id) {
107
+ const toasts = (manager as any).toasts as
108
+ | Array<ToastObject<any>>
109
+ | undefined;
110
+
111
+ if (toasts) {
112
+ const existing = toasts.find((t) => t.id === options.id);
113
+
114
+ if (existing && existing.transitionStatus !== "ending") {
115
+ manager.update(options.id, { bump: false });
116
+ requestAnimationFrame(() => {
117
+ manager.update(options.id, {
118
+ bump: true,
119
+ ...(options.timeout !== undefined && {
120
+ timeout: options.timeout,
121
+ }),
122
+ });
123
+ });
124
+ return options.id;
125
+ }
126
+
127
+ if (existing && existing.transitionStatus === "ending") {
128
+ return options.id;
129
+ }
130
+ }
131
+ }
132
+
133
+ return manager.add({ ...options });
134
+ },
135
+
136
+ update: (id: string, options: Partial<ToastManagerAddOptions<any>>) => {
137
+ return manager.update(id, { ...options });
138
+ },
139
+
140
+ promise: <T,>(
141
+ promise: Promise<T>,
142
+ options: {
143
+ loading: ToastManagerAddOptions<any>;
144
+ success:
145
+ | ToastManagerAddOptions<any>
146
+ | ((data: T) => ToastManagerAddOptions<any>);
147
+ error:
148
+ | ToastManagerAddOptions<any>
149
+ | ((error: Error) => ToastManagerAddOptions<any>);
150
+ },
151
+ ) => {
152
+ return manager.promise(promise, {
153
+ loading: { ...options.loading },
154
+ success:
155
+ typeof options.success === "function"
156
+ ? (data: T) => ({
157
+ ...(options.success as (d: T) => ToastManagerAddOptions<any>)(
158
+ data,
159
+ ),
160
+ })
161
+ : { ...options.success },
162
+ error:
163
+ typeof options.error === "function"
164
+ ? (err: Error) => ({
165
+ ...(options.error as (e: Error) => ToastManagerAddOptions<any>)(
166
+ err,
167
+ ),
168
+ })
169
+ : { ...options.error },
170
+ });
171
+ },
172
+ };
173
+ }
174
+
175
+ export function useToastManager() {
176
+ const manager = Toast.useToastManager();
177
+ return {
178
+ ...wrapManagerMethods(manager),
179
+ toasts: manager.toasts as ToastOptions<any>[],
180
+ };
181
+ }
182
+
183
+ export function createToastManager() {
184
+ return wrapManagerMethods(Toast.createToastManager());
185
+ }
186
+
187
+ // ─── Singleton + convenience API ─────────────────────────────────────────────
188
+
189
+ // Captured by ToastManagerBridge on provider mount — points to the internal
190
+ // Base UI manager so that toast.* calls update React state synchronously,
191
+ // avoiding the extra async tick of the subscription-based external manager.
192
+ let _activeManager: ReturnType<
193
+ typeof wrapManagerMethods<ReturnType<typeof Toast.useToastManager>>
194
+ > | null = null;
195
+
196
+ type ToastArg = string | Omit<ToastManagerAddOptions<any>, "variant">;
197
+
198
+ function resolveArgs(
199
+ arg: ToastArg,
200
+ ): Omit<ToastManagerAddOptions<any>, "variant"> {
201
+ return typeof arg === "string" ? { title: arg } : arg;
202
+ }
203
+
204
+ /**
205
+ * Imperative toast API. Works anywhere — no hook needed.
206
+ *
207
+ * Requires `<ToastProvider>` somewhere in the tree (no extra props needed).
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * toast("Notificación"); // default variant
212
+ * toast.success("Guardado correctamente");
213
+ * toast.error({ title: "Error", description: "Algo salió mal" });
214
+ * toast.promise(saveData(), {
215
+ * loading: { description: "Guardando..." },
216
+ * success: () => ({ variant: "success", description: "Guardado!" }),
217
+ * error: (e) => ({ variant: "error", description: e.message }),
218
+ * });
219
+ * ```
220
+ */
221
+ export const toast = Object.assign(
222
+ (arg: ToastArg) => _activeManager?.add({ ...resolveArgs(arg) }),
223
+ {
224
+ success: (arg: ToastArg) =>
225
+ _activeManager?.add({ variant: "success", ...resolveArgs(arg) }),
226
+ error: (arg: ToastArg) =>
227
+ _activeManager?.add({ variant: "error", ...resolveArgs(arg) }),
228
+ warning: (arg: ToastArg) =>
229
+ _activeManager?.add({ variant: "warning", ...resolveArgs(arg) }),
230
+ info: (arg: ToastArg) =>
231
+ _activeManager?.add({ variant: "info", ...resolveArgs(arg) }),
232
+ promise: <T,>(
233
+ promise: Promise<T>,
234
+ options: {
235
+ loading: ToastManagerAddOptions<any>;
236
+ success:
237
+ | ToastManagerAddOptions<any>
238
+ | ((data: T) => ToastManagerAddOptions<any>);
239
+ error:
240
+ | ToastManagerAddOptions<any>
241
+ | ((error: Error) => ToastManagerAddOptions<any>);
242
+ },
243
+ ) => _activeManager?.promise(promise, options),
244
+ },
245
+ );
246
+
247
+ // ─── Provider ─────────────────────────────────────────────────────────────────
248
+
249
+ export interface ToastProviderProps {
250
+ children: ReactNode;
251
+ /**
252
+ * External toast manager created with `createToastManager()`.
253
+ * Only needed for isolated instances (tests, microfrontends).
254
+ * The default `<ToastProvider>` (no props) is all you need for `toast.*` to work.
255
+ */
256
+ toastManager?: ReturnType<typeof Toast.createToastManager>;
257
+ /**
258
+ * Container element for the portal.
259
+ * @default document.body
260
+ */
261
+ container?: HTMLElement | null;
262
+ }
263
+
264
+ /**
265
+ * Captures the internal Base UI manager so that `toast.*` calls update
266
+ * React state synchronously, avoiding async subscription lag.
267
+ */
268
+ function ToastManagerBridge() {
269
+ const manager = Toast.useToastManager();
270
+ const wrapped = useMemo(() => wrapManagerMethods(manager), [manager]);
271
+
272
+ // Set during render so it's available immediately on first interaction.
273
+ // Safe for a module-level singleton — does not trigger re-renders.
274
+ _activeManager = wrapped;
275
+
276
+ useEffect(
277
+ () => () => {
278
+ _activeManager = null;
279
+ },
280
+ [],
281
+ );
282
+
283
+ return null;
284
+ }
285
+
286
+ /**
287
+ * ToastProvider — wrap your app once to enable toast notifications.
288
+ *
289
+ * Toasts stack in the bottom-right corner with swipe-to-dismiss and expand-on-hover.
290
+ * Once mounted, call `toast.success()` / `toast.error()` etc. from anywhere.
291
+ *
292
+ * @example
293
+ * ```tsx
294
+ * // app.tsx — one-time setup, no extra props needed
295
+ * <ToastProvider>
296
+ * <App />
297
+ * </ToastProvider>
298
+ *
299
+ * // Anywhere in the codebase
300
+ * import { toast } from "@boxcustodia/library";
301
+ * toast.success("Cambios guardados");
302
+ * toast.error({ title: "Error", description: "Algo salió mal" });
303
+ * ```
304
+ */
305
+ export function ToastProvider({
306
+ children,
307
+ toastManager,
308
+ container,
309
+ }: ToastProviderProps) {
310
+ return (
311
+ <Toast.Provider toastManager={toastManager}>
312
+ <ToastManagerBridge />
313
+ {children}
314
+ <Toast.Portal container={container}>
315
+ <Toast.Viewport className="fixed top-auto right-4 bottom-4 mx-auto flex w-[calc(100%-2rem)] sm:right-8 sm:bottom-8 sm:w-[340px]">
316
+ <ToastList />
317
+ </Toast.Viewport>
318
+ </Toast.Portal>
319
+ </Toast.Provider>
320
+ );
321
+ }
322
+
323
+ // ─── Internal render helpers ──────────────────────────────────────────────────
324
+
325
+ function ToastList() {
326
+ const { toasts } = useToastManager();
327
+ return toasts.map((toast) => (
328
+ <Toast.Root
329
+ key={toast.id}
330
+ toast={toast}
331
+ data-slot="toast"
332
+ className={cn(
333
+ "absolute right-0 bottom-0 left-auto z-[calc(var(--z-toast)-var(--toast-index))] mr-0 h-[var(--height)] w-full origin-bottom select-none",
334
+ toastVariants({ variant: toast.variant }),
335
+ "[--gap:0.75rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))]",
336
+ "[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]",
337
+ "after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
338
+ "data-[ending-style]:opacity-0 data-[expanded]:h-[var(--toast-height)] data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--offset-y)))] data-[limited]:opacity-0 data-[starting-style]:[transform:translateY(150%)]",
339
+ "data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]",
340
+ "data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]",
341
+ "data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]",
342
+ "data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
343
+ "[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]",
344
+ toast.bump && "animate-toast-bump",
345
+ )}
346
+ >
347
+ <ToastBackground variant={toast.variant} />
348
+ <Toast.Content className="isolate flex flex-col gap-1 transition-opacity [transition-duration:250ms] data-[behind]:pointer-events-none data-[behind]:opacity-0 data-[expanded]:pointer-events-auto data-[expanded]:opacity-100">
349
+ {toast.content ?? (
350
+ <div className="flex items-start gap-2">
351
+ <ToastIcon variant={toast.variant} />
352
+ <div className="flex flex-col gap-1 overflow-hidden">
353
+ <Toast.Title
354
+ data-slot="toast-title"
355
+ className={cn(
356
+ "text-[0.975rem] leading-5 font-medium text-foreground",
357
+ TOAST_VARIANT_CONFIG[toast.variant ?? "default"].titleClass,
358
+ )}
359
+ />
360
+ <Toast.Description
361
+ data-slot="toast-description"
362
+ className="text-[0.925rem] leading-5 text-muted-foreground"
363
+ />
364
+ {!!toast.actions?.length && (
365
+ <div className="mt-2 flex min-w-0 flex-nowrap gap-2 overflow-x-auto p-px">
366
+ {toast.actions.map((actionProps, idx) => (
367
+ <Button key={idx} {...actionProps} />
368
+ ))}
369
+ </div>
370
+ )}
371
+ </div>
372
+ </div>
373
+ )}
374
+ <Toast.Close
375
+ className="absolute top-2 right-2 flex h-4 w-4 items-center justify-center rounded border-none bg-transparent text-current/50 hover:bg-foreground/10 hover:text-current"
376
+ aria-label="Close"
377
+ >
378
+ <X className="h-3 w-3" />
379
+ </Toast.Close>
380
+ </Toast.Content>
381
+ </Toast.Root>
382
+ ));
383
+ }
384
+
385
+ function ToastBackground({ variant }: { variant?: ToastVariant }) {
386
+ const bgClass = variant ? TOAST_VARIANT_CONFIG[variant].bgClass : "";
387
+ return (
388
+ <div
389
+ className={cn(
390
+ "absolute inset-0 rounded-[11px] bg-background/90",
391
+ bgClass,
392
+ )}
393
+ />
394
+ );
395
+ }
396
+
397
+ function ToastIcon({ variant }: { variant?: ToastVariant }) {
398
+ if (!variant || variant === "default") return null;
399
+ const { icon: Icon, titleClass } = TOAST_VARIANT_CONFIG[variant];
400
+ if (!Icon) return null;
401
+ return (
402
+ <Icon
403
+ data-slot="toast-icon"
404
+ className={cn("mt-0.5 h-4 w-4 shrink-0", titleClass)}
405
+ />
406
+ );
407
+ }
408
+
409
+ // ─── Primitive escape hatch ───────────────────────────────────────────────────
410
+
411
+ export { Toast as ToastPrimitive };
@@ -0,0 +1 @@
1
+ export * from "./tooltip";
@@ -0,0 +1,226 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Info } from "lucide-react";
3
+ import {
4
+ AlertDialogClose,
5
+ AlertDialogPopup,
6
+ AlertDialogRoot,
7
+ AlertDialogTrigger,
8
+ Button,
9
+ Tooltip,
10
+ TooltipArrow,
11
+ TooltipPopup,
12
+ TooltipPrimitive,
13
+ TooltipRoot,
14
+ TooltipTrigger,
15
+ } from "../../components";
16
+
17
+ /**
18
+ * Accessible tooltip built on Base UI. Shows supplementary information on hover or focus.
19
+ * Children become the trigger via Base UI's `render` prop — no wrapper element is added.
20
+ * Accepts `open`/`defaultOpen`/`onOpenChange` for controlled and uncontrolled usage.
21
+ *
22
+ * **Requires `<LibraryProvider>` at the app root.** It wires up `TooltipProvider` globally,
23
+ * enabling shared hover intent: once one tooltip opens, subsequent ones skip `delay` until
24
+ * the cursor leaves all triggers.
25
+ */
26
+ const meta: Meta<typeof Tooltip> = {
27
+ title: "Components/Tooltip",
28
+ component: Tooltip,
29
+ parameters: {
30
+ layout: "centered",
31
+ },
32
+ args: {
33
+ content: "Tooltip content",
34
+ children: (
35
+ <Button variant="outline">
36
+ <Info />
37
+ Hover me
38
+ </Button>
39
+ ),
40
+ },
41
+ argTypes: {
42
+ children: { control: false },
43
+ content: { control: "text" },
44
+ side: {
45
+ control: "select",
46
+ options: ["top", "bottom", "left", "right"],
47
+ },
48
+ align: {
49
+ control: "select",
50
+ options: ["start", "center", "end"],
51
+ },
52
+ },
53
+ };
54
+
55
+ export default meta;
56
+ type Story = StoryObj<typeof Tooltip>;
57
+
58
+ export const Default: Story = {};
59
+
60
+ /**
61
+ * All four `side` values side by side. The arrow and popup reposition automatically.
62
+ */
63
+ export const Sides: Story = {
64
+ render: () => (
65
+ <div className="grid grid-cols-2 gap-x-12 gap-y-10 p-12">
66
+ {(["top", "right", "bottom", "left"] as const).map((side) => (
67
+ <Tooltip key={side} content={`side="${side}"`} side={side} defaultOpen>
68
+ <Button variant="outline" className="w-24">
69
+ {side}
70
+ </Button>
71
+ </Tooltip>
72
+ ))}
73
+ </div>
74
+ ),
75
+ };
76
+
77
+ /**
78
+ * All three `align` values with `side="right"` for clarity.
79
+ * `align` controls placement along the axis perpendicular to `side`.
80
+ */
81
+ export const Align: Story = {
82
+ render: () => (
83
+ <div className="flex flex-col gap-2 py-8">
84
+ {(["start", "center", "end"] as const).map((align) => (
85
+ <Tooltip
86
+ key={align}
87
+ content={`align="${align}"`}
88
+ side="right"
89
+ align={align}
90
+ defaultOpen
91
+ >
92
+ <Button variant="outline" className="w-28 h-28">
93
+ {align}
94
+ </Button>
95
+ </Tooltip>
96
+ ))}
97
+ </div>
98
+ ),
99
+ };
100
+
101
+ /**
102
+ * `offset` controls the gap in pixels between the tooltip and the trigger. Default is `10`.
103
+ */
104
+ export const Offset: Story = {
105
+ args: {
106
+ offset: 24,
107
+ defaultOpen: true,
108
+ },
109
+ };
110
+
111
+ /**
112
+ * `delay` sets how long (ms) the user must hover before the tooltip opens.
113
+ * `closeDelay` sets how long it stays visible after the cursor leaves.
114
+ * Both default to Base UI's built-in values (600ms open, 0ms close).
115
+ */
116
+ export const Delay: Story = {
117
+ render: () => (
118
+ <div className="flex items-center gap-6">
119
+ <Tooltip content="Opens instantly" delay={0}>
120
+ <Button variant="outline">delay=0</Button>
121
+ </Tooltip>
122
+ <Tooltip content="Opens after 1s" delay={1000}>
123
+ <Button variant="outline">delay=1000</Button>
124
+ </Tooltip>
125
+ <Tooltip content="Stays open 1s after blur" closeDelay={1000}>
126
+ <Button variant="outline">closeDelay=1000</Button>
127
+ </Tooltip>
128
+ </div>
129
+ ),
130
+ };
131
+
132
+ /**
133
+ * `className` overrides the popup styles. `arrowClassName` accepts a `text-*` class
134
+ * to recolor the arrow — it uses `currentColor`, so `text-primary` turns the arrow primary.
135
+ */
136
+ export const CustomColor: Story = {
137
+ args: {
138
+ content: "Custom color",
139
+ className: "bg-primary text-primary-foreground",
140
+ arrowClassName: "text-primary",
141
+ defaultOpen: true,
142
+ },
143
+ };
144
+
145
+ /**
146
+ * `defaultOpen` renders the tooltip open without external state.
147
+ * Use `open` + `onOpenChange` for fully controlled behavior.
148
+ */
149
+ export const Controlled: Story = {
150
+ args: {
151
+ open: true,
152
+ content: "Always visible",
153
+ },
154
+ };
155
+
156
+ /**
157
+ * Direct composition with primitives for full structural control.
158
+ * `TooltipPrimitive` exposes every Base UI subcomponent not wrapped by the library.
159
+ *
160
+ * ```tsx
161
+ * <TooltipRoot>
162
+ * <TooltipTrigger render={<Button />} />
163
+ * <TooltipPrimitive.Portal>
164
+ * <TooltipPrimitive.Positioner side="top" sideOffset={10}>
165
+ * <TooltipPopup>
166
+ * <TooltipArrow />
167
+ * Custom content
168
+ * </TooltipPopup>
169
+ * </TooltipPrimitive.Positioner>
170
+ * </TooltipPrimitive.Portal>
171
+ * </TooltipRoot>
172
+ * ```
173
+ */
174
+ export const Primitive: Story = {
175
+ render: () => (
176
+ <TooltipRoot>
177
+ <TooltipTrigger
178
+ render={
179
+ <Button variant="outline">
180
+ <Info />
181
+ Primitives
182
+ </Button>
183
+ }
184
+ />
185
+ <TooltipPrimitive.Portal>
186
+ <TooltipPrimitive.Positioner side="top" sideOffset={10}>
187
+ <TooltipPopup>
188
+ <TooltipArrow />
189
+ Built with primitives
190
+ </TooltipPopup>
191
+ </TooltipPrimitive.Positioner>
192
+ </TooltipPrimitive.Portal>
193
+ </TooltipRoot>
194
+ ),
195
+ };
196
+
197
+ /**
198
+ * Tooltip + AlertDialog composed on the same trigger.
199
+ * Base UI's `render` prop chains both triggers into a single `<Button>` —
200
+ * hover shows the tooltip, click opens the dialog.
201
+ */
202
+ export const WithAlertDialog: Story = {
203
+ render: () => (
204
+ <AlertDialogRoot>
205
+ <Tooltip content="Esta acción no se puede deshacer">
206
+ <AlertDialogTrigger
207
+ render={<Button variant="error">Eliminar</Button>}
208
+ />
209
+ </Tooltip>
210
+ <AlertDialogPopup>
211
+ <p className="font-semibold">¿Estás seguro?</p>
212
+ <p className="text-sm text-muted-foreground">
213
+ Esta acción no se puede deshacer.
214
+ </p>
215
+ <div className="flex justify-end gap-2">
216
+ <AlertDialogClose
217
+ render={<Button variant="secondary">Cancelar</Button>}
218
+ />
219
+ <AlertDialogClose
220
+ render={<Button variant="error">Eliminar</Button>}
221
+ />
222
+ </div>
223
+ </AlertDialogPopup>
224
+ </AlertDialogRoot>
225
+ ),
226
+ };
@@ -0,0 +1,46 @@
1
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import { Button } from "../../components/button";
4
+ import { Tooltip } from ".";
5
+
6
+ describe("Tooltip component", () => {
7
+ const content = "Tooltip content";
8
+
9
+ it("should toggle the tooltip onFocus", async () => {
10
+ render(
11
+ <Tooltip content={content}>
12
+ <Button>Focus me</Button>
13
+ </Tooltip>,
14
+ );
15
+
16
+ const button = screen.getByText("Focus me");
17
+ fireEvent.focus(button);
18
+
19
+ const toooltip = screen.getByText(content);
20
+ expect(toooltip).toBeInTheDocument();
21
+
22
+ fireEvent.blur(button);
23
+ waitFor(() => {
24
+ expect(toooltip).not.toBeInTheDocument();
25
+ });
26
+ });
27
+
28
+ it("should toggle the tooltip onHover", async () => {
29
+ render(
30
+ <Tooltip content={content}>
31
+ <Button>Hover me</Button>
32
+ </Tooltip>,
33
+ );
34
+
35
+ const button = screen.getByText("Hover me");
36
+ fireEvent.mouseEnter(button);
37
+
38
+ const toooltip = screen.getByText(content);
39
+ expect(toooltip).toBeInTheDocument();
40
+
41
+ fireEvent.mouseLeave(button);
42
+ waitFor(() => {
43
+ expect(toooltip).not.toBeInTheDocument();
44
+ });
45
+ });
46
+ });