@umbra.ui/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/dist/components/controls/Dropdown/types.d.ts +5 -0
  2. package/dist/components/controls/Dropdown/types.d.ts.map +1 -0
  3. package/dist/components/controls/Dropdown/types.js +1 -0
  4. package/dist/components/controls/SegmentedControl/types.d.ts +6 -0
  5. package/dist/components/controls/SegmentedControl/types.d.ts.map +1 -0
  6. package/dist/components/controls/SegmentedControl/types.js +1 -0
  7. package/dist/components/dialogs/Alert/types.d.ts +7 -0
  8. package/dist/components/dialogs/Alert/types.d.ts.map +1 -0
  9. package/dist/components/dialogs/Alert/types.js +1 -0
  10. package/dist/components/dialogs/Toast/types.d.ts +34 -0
  11. package/dist/components/dialogs/Toast/types.d.ts.map +1 -0
  12. package/dist/components/dialogs/Toast/types.js +10 -0
  13. package/dist/components/dialogs/Toast/useToast.d.ts +36 -0
  14. package/dist/components/dialogs/Toast/useToast.d.ts.map +1 -0
  15. package/dist/components/dialogs/Toast/useToast.js +90 -0
  16. package/dist/components/indicators/Tooltip/tooltip.d.ts +3 -0
  17. package/dist/components/indicators/Tooltip/tooltip.d.ts.map +1 -0
  18. package/dist/components/indicators/Tooltip/tooltip.js +33 -0
  19. package/dist/components/indicators/Tooltip/types.d.ts +14 -0
  20. package/dist/components/indicators/Tooltip/types.d.ts.map +1 -0
  21. package/dist/components/indicators/Tooltip/types.js +1 -0
  22. package/dist/components/indicators/Tooltip/useTooltip.d.ts +18 -0
  23. package/dist/components/indicators/Tooltip/useTooltip.d.ts.map +1 -0
  24. package/dist/components/indicators/Tooltip/useTooltip.js +57 -0
  25. package/dist/components/inputs/Tags/tag-bar-styles.d.ts +14 -0
  26. package/dist/components/inputs/Tags/tag-bar-styles.d.ts.map +1 -0
  27. package/dist/components/inputs/Tags/tag-bar-styles.js +313 -0
  28. package/dist/components/inputs/Tags/types.d.ts +93 -0
  29. package/dist/components/inputs/Tags/types.d.ts.map +1 -0
  30. package/dist/components/inputs/Tags/types.js +216 -0
  31. package/dist/components/inputs/search/types.d.ts +9 -0
  32. package/dist/components/inputs/search/types.d.ts.map +1 -0
  33. package/dist/components/inputs/search/types.js +1 -0
  34. package/dist/components/navigation/adaptive/types.d.ts +16 -0
  35. package/dist/components/navigation/adaptive/types.d.ts.map +1 -0
  36. package/dist/components/navigation/adaptive/types.js +1 -0
  37. package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts +27 -0
  38. package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts.map +1 -0
  39. package/dist/components/navigation/adaptive/useAdaptiveLayout.js +40 -0
  40. package/dist/components/navigation/adaptive/useBreakpoints.d.ts +6 -0
  41. package/dist/components/navigation/adaptive/useBreakpoints.d.ts.map +1 -0
  42. package/dist/components/navigation/adaptive/useBreakpoints.js +37 -0
  43. package/dist/components/navigation/adaptive/useContainerMonitor.d.ts +93 -0
  44. package/dist/components/navigation/adaptive/useContainerMonitor.d.ts.map +1 -0
  45. package/dist/components/navigation/adaptive/useContainerMonitor.js +145 -0
  46. package/dist/components/navigation/adaptive/useViewAnimation.d.ts +31 -0
  47. package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -0
  48. package/dist/components/navigation/adaptive/useViewAnimation.js +591 -0
  49. package/dist/components/navigation/adaptive/useViewResize.d.ts +52 -0
  50. package/dist/components/navigation/adaptive/useViewResize.d.ts.map +1 -0
  51. package/dist/components/navigation/adaptive/useViewResize.js +146 -0
  52. package/dist/components/navigation/navstack/useNavigationStack.d.ts +25 -0
  53. package/dist/components/navigation/navstack/useNavigationStack.d.ts.map +1 -0
  54. package/dist/components/navigation/navstack/useNavigationStack.js +133 -0
  55. package/dist/components/navigation/slideover/useSlideoverController.d.ts +20 -0
  56. package/dist/components/navigation/slideover/useSlideoverController.d.ts.map +1 -0
  57. package/dist/components/navigation/slideover/useSlideoverController.js +267 -0
  58. package/dist/components/navigation/splitview/useSplitViewController.d.ts +20 -0
  59. package/dist/components/navigation/splitview/useSplitViewController.d.ts.map +1 -0
  60. package/dist/components/navigation/splitview/useSplitViewController.js +325 -0
  61. package/dist/components/navigation/tabcontroller/types.d.ts +21 -0
  62. package/dist/components/navigation/tabcontroller/types.d.ts.map +1 -0
  63. package/dist/components/navigation/tabcontroller/types.js +1 -0
  64. package/dist/components/navigation/tabcontroller/useTabController.d.ts +5 -0
  65. package/dist/components/navigation/tabcontroller/useTabController.d.ts.map +1 -0
  66. package/dist/components/navigation/tabcontroller/useTabController.js +10 -0
  67. package/dist/components/navigation/types.d.ts +8 -0
  68. package/dist/components/navigation/types.d.ts.map +1 -0
  69. package/dist/components/navigation/types.js +1 -0
  70. package/dist/components/pickers/CollectionPicker/types.d.ts +11 -0
  71. package/dist/components/pickers/CollectionPicker/types.d.ts.map +1 -0
  72. package/dist/components/pickers/CollectionPicker/types.js +1 -0
  73. package/dist/components/pickers/ColorPicker/colors.d.ts +13 -0
  74. package/dist/components/pickers/ColorPicker/colors.d.ts.map +1 -0
  75. package/dist/components/pickers/ColorPicker/colors.js +266 -0
  76. package/dist/components/pickers/FilePicker/types.d.ts +10 -0
  77. package/dist/components/pickers/FilePicker/types.d.ts.map +1 -0
  78. package/dist/components/pickers/FilePicker/types.js +1 -0
  79. package/dist/index.d.ts +91 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +196 -0
  82. package/dist/theme.d.ts +73 -0
  83. package/dist/theme.d.ts.map +1 -0
  84. package/dist/theme.js +279 -0
  85. package/dist/themes/blank.d.ts +7 -0
  86. package/dist/themes/blank.d.ts.map +1 -0
  87. package/dist/themes/blank.js +543 -0
  88. package/dist/themes/crimson-dark.d.ts +4 -0
  89. package/dist/themes/crimson-dark.d.ts.map +1 -0
  90. package/dist/themes/crimson-dark.js +552 -0
  91. package/dist/themes/cyan-light.d.ts +4 -0
  92. package/dist/themes/cyan-light.d.ts.map +1 -0
  93. package/dist/themes/cyan-light.js +552 -0
  94. package/dist/themes/dark.d.ts +4 -0
  95. package/dist/themes/dark.d.ts.map +1 -0
  96. package/dist/themes/dark.js +551 -0
  97. package/dist/themes/gold-dark.d.ts +4 -0
  98. package/dist/themes/gold-dark.d.ts.map +1 -0
  99. package/dist/themes/gold-dark.js +552 -0
  100. package/dist/themes/grass-dark.d.ts +4 -0
  101. package/dist/themes/grass-dark.d.ts.map +1 -0
  102. package/dist/themes/grass-dark.js +552 -0
  103. package/dist/themes/indigo.d.ts +4 -0
  104. package/dist/themes/indigo.d.ts.map +1 -0
  105. package/dist/themes/indigo.js +552 -0
  106. package/dist/themes/light.d.ts +4 -0
  107. package/dist/themes/light.d.ts.map +1 -0
  108. package/dist/themes/light.js +551 -0
  109. package/dist/themes/orange-dark.d.ts +4 -0
  110. package/dist/themes/orange-dark.d.ts.map +1 -0
  111. package/dist/themes/orange-dark.js +551 -0
  112. package/dist/themes/orange-light.d.ts +4 -0
  113. package/dist/themes/orange-light.d.ts.map +1 -0
  114. package/dist/themes/orange-light.js +551 -0
  115. package/package.json +62 -0
  116. package/src/components/controls/Button/Button.vue +417 -0
  117. package/src/components/controls/Button/README.md +348 -0
  118. package/src/components/controls/Button/theme.css +200 -0
  119. package/src/components/controls/Checkbox/Checkbox.vue +164 -0
  120. package/src/components/controls/Checkbox/README.md +441 -0
  121. package/src/components/controls/Checkbox/theme.css +36 -0
  122. package/src/components/controls/Dropdown/Dropdown.vue +476 -0
  123. package/src/components/controls/Dropdown/README.md +370 -0
  124. package/src/components/controls/Dropdown/theme.css +50 -0
  125. package/src/components/controls/Dropdown/types.ts +6 -0
  126. package/src/components/controls/IconButton/IconButton.vue +267 -0
  127. package/src/components/controls/IconButton/README.md +502 -0
  128. package/src/components/controls/IconButton/theme.css +89 -0
  129. package/src/components/controls/Radio/README.md +591 -0
  130. package/src/components/controls/Radio/Radio.vue +89 -0
  131. package/src/components/controls/Radio/theme.css +14 -0
  132. package/src/components/controls/RangeSlider/README.md +608 -0
  133. package/src/components/controls/RangeSlider/RangeSlider.vue +535 -0
  134. package/src/components/controls/RangeSlider/theme.css +80 -0
  135. package/src/components/controls/SegmentedControl/README.md +587 -0
  136. package/src/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
  137. package/src/components/controls/SegmentedControl/theme.css +60 -0
  138. package/src/components/controls/SegmentedControl/types.ts +5 -0
  139. package/src/components/controls/Slider/README.md +627 -0
  140. package/src/components/controls/Slider/Slider.vue +260 -0
  141. package/src/components/controls/Slider/theme.css +74 -0
  142. package/src/components/controls/Stepper/README.md +601 -0
  143. package/src/components/controls/Stepper/Stepper.vue +103 -0
  144. package/src/components/controls/Stepper/theme.css +53 -0
  145. package/src/components/controls/Switch/README.md +667 -0
  146. package/src/components/controls/Switch/Switch.vue +127 -0
  147. package/src/components/controls/Switch/theme.css +42 -0
  148. package/src/components/dialogs/Alert/Alert.vue +218 -0
  149. package/src/components/dialogs/Alert/README.md +450 -0
  150. package/src/components/dialogs/Alert/theme.css +44 -0
  151. package/src/components/dialogs/Alert/types.ts +11 -0
  152. package/src/components/dialogs/Toast/README.md +522 -0
  153. package/src/components/dialogs/Toast/Toast.vue +296 -0
  154. package/src/components/dialogs/Toast/ToastContainer.vue +330 -0
  155. package/src/components/dialogs/Toast/theme.css +44 -0
  156. package/src/components/dialogs/Toast/types.ts +46 -0
  157. package/src/components/dialogs/Toast/useToast.ts +127 -0
  158. package/src/components/indicators/ProgressBar/ProgressBar.vue +98 -0
  159. package/src/components/indicators/ProgressBar/README.md +744 -0
  160. package/src/components/indicators/ProgressBar/theme.css +36 -0
  161. package/src/components/indicators/Tooltip/README.md +723 -0
  162. package/src/components/indicators/Tooltip/TooltipProvider.vue +142 -0
  163. package/src/components/indicators/Tooltip/theme.css +18 -0
  164. package/src/components/indicators/Tooltip/tooltip.ts +48 -0
  165. package/src/components/indicators/Tooltip/types.ts +15 -0
  166. package/src/components/indicators/Tooltip/useTooltip.ts +71 -0
  167. package/src/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
  168. package/src/components/inputs/AutogrowTextView/README.md +643 -0
  169. package/src/components/inputs/AutogrowTextView/theme.css +28 -0
  170. package/src/components/inputs/InputCard/InputCard.vue +600 -0
  171. package/src/components/inputs/InputCard/README.md +636 -0
  172. package/src/components/inputs/InputEmail/InputEmail.vue +698 -0
  173. package/src/components/inputs/InputEmail/README.md +764 -0
  174. package/src/components/inputs/InputNumber/InputNumber.vue +300 -0
  175. package/src/components/inputs/InputNumber/README.md +749 -0
  176. package/src/components/inputs/InputPhone/InputPhone.vue +645 -0
  177. package/src/components/inputs/InputPhone/README.md +636 -0
  178. package/src/components/inputs/InputSecure/InputSecure.vue +646 -0
  179. package/src/components/inputs/InputSecure/README.md +771 -0
  180. package/src/components/inputs/InputText/InputText.vue +225 -0
  181. package/src/components/inputs/InputText/README.md +844 -0
  182. package/src/components/inputs/OTP/OTP.vue +349 -0
  183. package/src/components/inputs/OTP/README.md +736 -0
  184. package/src/components/inputs/OTP/theme.css +50 -0
  185. package/src/components/inputs/StringCapture/README.md +718 -0
  186. package/src/components/inputs/StringCapture/StringCapture.vue +315 -0
  187. package/src/components/inputs/StringCapture/theme.css +86 -0
  188. package/src/components/inputs/Tags/README.md +897 -0
  189. package/src/components/inputs/Tags/TagBar.vue +793 -0
  190. package/src/components/inputs/Tags/TagCreation.vue +219 -0
  191. package/src/components/inputs/Tags/TagPicker.vue +380 -0
  192. package/src/components/inputs/Tags/tag-bar-styles.ts +354 -0
  193. package/src/components/inputs/Tags/theme.css +121 -0
  194. package/src/components/inputs/Tags/types.ts +346 -0
  195. package/src/components/inputs/search/README.md +759 -0
  196. package/src/components/inputs/search/SearchBar.vue +394 -0
  197. package/src/components/inputs/search/SearchResults.vue +310 -0
  198. package/src/components/inputs/search/theme.css +187 -0
  199. package/src/components/inputs/search/types.ts +8 -0
  200. package/src/components/inputs/theme.css +102 -0
  201. package/src/components/menus/ActionMenu/ActionMenu.vue +383 -0
  202. package/src/components/menus/ActionMenu/README.md +825 -0
  203. package/src/components/menus/ActionMenu/theme.css +93 -0
  204. package/src/components/models/Popover/Popover.vue +551 -0
  205. package/src/components/models/Popover/README.md +885 -0
  206. package/src/components/models/Popover/theme.css +52 -0
  207. package/src/components/models/Sheet/README.md +1159 -0
  208. package/src/components/models/Sheet/Sheet.vue +465 -0
  209. package/src/components/models/Sheet/theme.css +72 -0
  210. package/src/components/models/Sidebar/README.md +1228 -0
  211. package/src/components/models/Sidebar/Sidebar.vue +480 -0
  212. package/src/components/models/Sidebar/theme.css +90 -0
  213. package/src/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
  214. package/src/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
  215. package/src/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
  216. package/src/components/navigation/adaptive/README.md +768 -0
  217. package/src/components/navigation/adaptive/types.ts +19 -0
  218. package/src/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
  219. package/src/components/navigation/adaptive/useBreakpoints.ts +41 -0
  220. package/src/components/navigation/adaptive/useContainerMonitor.ts +214 -0
  221. package/src/components/navigation/adaptive/useViewAnimation.ts +721 -0
  222. package/src/components/navigation/adaptive/useViewResize.ts +211 -0
  223. package/src/components/navigation/navstack/NavigationStack.vue +180 -0
  224. package/src/components/navigation/navstack/README.md +994 -0
  225. package/src/components/navigation/navstack/useNavigationStack.ts +164 -0
  226. package/src/components/navigation/slideover/README.md +1275 -0
  227. package/src/components/navigation/slideover/SlideoverController.vue +287 -0
  228. package/src/components/navigation/slideover/useSlideoverController.ts +320 -0
  229. package/src/components/navigation/splitview/README.md +1115 -0
  230. package/src/components/navigation/splitview/SplitViewController.vue +176 -0
  231. package/src/components/navigation/splitview/useSplitViewController.ts +388 -0
  232. package/src/components/navigation/tabcontroller/README.md +919 -0
  233. package/src/components/navigation/tabcontroller/TabController.vue +307 -0
  234. package/src/components/navigation/tabcontroller/TabItem.vue +57 -0
  235. package/src/components/navigation/tabcontroller/types.ts +24 -0
  236. package/src/components/navigation/tabcontroller/useTabController.ts +18 -0
  237. package/src/components/navigation/theme.css +91 -0
  238. package/src/components/navigation/types.ts +7 -0
  239. package/src/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
  240. package/src/components/pickers/CollectionPicker/README.md +1115 -0
  241. package/src/components/pickers/CollectionPicker/theme.css +14 -0
  242. package/src/components/pickers/CollectionPicker/types.ts +11 -0
  243. package/src/components/pickers/ColorPicker/ColorPicker.vue +376 -0
  244. package/src/components/pickers/ColorPicker/README.md +1439 -0
  245. package/src/components/pickers/ColorPicker/colors.ts +299 -0
  246. package/src/components/pickers/ColorPicker/theme.css +32 -0
  247. package/src/components/pickers/DatePicker/DatePicker.vue +660 -0
  248. package/src/components/pickers/DatePicker/README.md +1195 -0
  249. package/src/components/pickers/DatePicker/theme.css +22 -0
  250. package/src/components/pickers/FilePicker/FilePicker.vue +534 -0
  251. package/src/components/pickers/FilePicker/README.md +1542 -0
  252. package/src/components/pickers/FilePicker/theme.css +48 -0
  253. package/src/components/pickers/FilePicker/types.ts +10 -0
  254. package/src/components/pickers/IconPicker/IconPicker.vue +327 -0
  255. package/src/components/pickers/IconPicker/README.md +1161 -0
  256. package/src/components/pickers/IconPicker/theme.css +28 -0
  257. package/src/components/pickers/theme.css +82 -0
  258. package/src/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
  259. package/src/components/views/MarkdownViewer/README.md +833 -0
  260. package/src/components/views/MarkdownViewer/theme.css +130 -0
  261. package/src/index.ts +263 -0
  262. package/src/theme.ts +378 -0
  263. package/src/themes/crimson-dark.ts +556 -0
  264. package/src/themes/cyan-light.ts +556 -0
  265. package/src/themes/dark.ts +557 -0
  266. package/src/themes/gold-dark.ts +556 -0
  267. package/src/themes/grass-dark.ts +556 -0
  268. package/src/themes/indigo.ts +556 -0
  269. package/src/themes/light.ts +557 -0
  270. package/src/themes/orange-dark.ts +557 -0
  271. package/src/themes/orange-light.ts +557 -0
  272. package/src/vue.d.ts +45 -0
@@ -0,0 +1,93 @@
1
+ /* Light theme using Colors */
2
+ :root {
3
+ /* ActionMenu popup colors */
4
+ --actionmenu-popup-bg: #ffffff; /* white - light background for light mode */
5
+ --actionmenu-popup-border: 1px solid rgba(0, 0, 0, 0.1); /* border for light mode */
6
+ --actionmenu-popup-shadow: transparent;
7
+ --actionmenu-popup-inset-shadow: rgba(
8
+ 255,
9
+ 255,
10
+ 255,
11
+ 0.8
12
+ ); /* whiteA8 - inset highlight for light mode */
13
+
14
+ /* ActionMenu item colors */
15
+ --actionmenu-item-bg: #ffffff; /* white - light background for light mode */
16
+ --actionmenu-item-hover-bg: rgba(
17
+ 0,
18
+ 0,
19
+ 0,
20
+ 0.05
21
+ ); /* blackA6 - hover background for light mode */
22
+ --actionmenu-item-border: rgba(
23
+ 0,
24
+ 0,
25
+ 0,
26
+ 0.1
27
+ ); /* blackA6 - border for light mode */
28
+ --actionmenu-item-text: #1f2937; /* gray8 - dark text for light mode */
29
+ --actionmenu-item-icon: #6b7280; /* gray5 - icon color for light mode */
30
+
31
+ /* ActionMenu destructive colors */
32
+ --actionmenu-destructive-text: #dc2626; /* red6 - destructive text for light mode */
33
+ --actionmenu-destructive-icon: #dc2626; /* red6 - destructive icon for light mode */
34
+
35
+ /* ActionMenu overlay colors */
36
+ --actionmenu-overlay-bg: rgba(
37
+ 0,
38
+ 0,
39
+ 0,
40
+ 0.1
41
+ ); /* blackA8 - lighter overlay for light mode */
42
+ --actionmenu-overlay-blur-bg: rgba(
43
+ 0,
44
+ 0,
45
+ 0,
46
+ 0.1
47
+ ); /* blackA6 - blur overlay for light mode */
48
+ }
49
+
50
+ /* Dark theme */
51
+ .dark,
52
+ .dark-theme {
53
+ /* ActionMenu popup colors */
54
+ --actionmenu-popup-bg: #484848; /* Original dark mode value */
55
+ --actionmenu-popup-border: none; /* no border for dark mode */
56
+ --actionmenu-popup-shadow: rgba(0, 0, 0, 0.21); /* Original dark mode value */
57
+ --actionmenu-popup-inset-shadow: rgba(
58
+ 255,
59
+ 255,
60
+ 255,
61
+ 0.1
62
+ ); /* Original dark mode value */
63
+
64
+ /* ActionMenu item colors */
65
+ --actionmenu-item-bg: #484848; /* Original dark mode value */
66
+ --actionmenu-item-hover-bg: rgba(
67
+ 255,
68
+ 255,
69
+ 255,
70
+ 0.05
71
+ ); /* Original dark mode value */
72
+ --actionmenu-item-border: rgba(
73
+ 255,
74
+ 255,
75
+ 255,
76
+ 0.1
77
+ ); /* Original dark mode value */
78
+ --actionmenu-item-text: #eeeeee; /* Original dark mode value */
79
+ --actionmenu-item-icon: #eeeeee; /* Original dark mode value */
80
+
81
+ /* ActionMenu destructive colors */
82
+ --actionmenu-destructive-text: #e5484d; /* Original dark mode value */
83
+ --actionmenu-destructive-icon: #e5484d; /* Original dark mode value */
84
+
85
+ /* ActionMenu overlay colors */
86
+ --actionmenu-overlay-bg: rgba(0, 0, 0, 0.7); /* Original dark mode value */
87
+ --actionmenu-overlay-blur-bg: rgba(
88
+ 0,
89
+ 0,
90
+ 0,
91
+ 0.1
92
+ ); /* Original dark mode value */
93
+ }
@@ -0,0 +1,551 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ ref,
4
+ onMounted,
5
+ nextTick,
6
+ onUnmounted,
7
+ watch,
8
+ Teleport,
9
+ computed,
10
+ } from "vue";
11
+ import {
12
+ offset,
13
+ flip,
14
+ shift,
15
+ arrow,
16
+ computePosition,
17
+ autoUpdate,
18
+ type Placement,
19
+ type Middleware,
20
+ } from "@floating-ui/vue";
21
+ import gsap from "gsap";
22
+ import "./theme.css";
23
+
24
+ export interface PopoverProps {
25
+ /**
26
+ * Controls whether the popover is visible
27
+ */
28
+ modelValue: boolean;
29
+ /**
30
+ * Placement of the popover relative to the trigger
31
+ * @default 'bottom'
32
+ */
33
+ placement?: Placement;
34
+ /**
35
+ * Offset from the trigger element
36
+ * @default 8
37
+ */
38
+ offsetDistance?: number;
39
+ /**
40
+ * Whether clicking outside closes the popover
41
+ * @default true
42
+ */
43
+ dismissOnClickOutside?: boolean;
44
+ /**
45
+ * Whether to show an arrow pointing to the trigger
46
+ * @default true
47
+ */
48
+ showArrow?: boolean;
49
+ /**
50
+ * Whether to show an overlay backdrop
51
+ * @default false
52
+ */
53
+ showOverlay?: boolean;
54
+ /**
55
+ * Overlay opacity (0-1)
56
+ * @default 0.5
57
+ */
58
+ overlayOpacity?: number;
59
+ /**
60
+ * Animation duration in seconds
61
+ * @default 0.2
62
+ */
63
+ animationDuration?: number;
64
+ /**
65
+ * Z-index for the popover
66
+ * @default 1000
67
+ */
68
+ zIndex?: number;
69
+ /**
70
+ * Padding from viewport edges
71
+ * @default 16
72
+ */
73
+ viewportPadding?: number;
74
+ /**
75
+ * Maximum width of the popover
76
+ * @default 'none'
77
+ */
78
+ maxWidth?: string;
79
+ /**
80
+ * Maximum height of the popover
81
+ * @default 'none'
82
+ */
83
+ maxHeight?: string;
84
+ /**
85
+ * Trigger event: 'click' or 'hover'
86
+ * @default 'click'
87
+ */
88
+ trigger?: "click" | "hover";
89
+ /**
90
+ * Delay before showing on hover (ms)
91
+ * @default 0
92
+ */
93
+ hoverShowDelay?: number;
94
+ /**
95
+ * Delay before hiding on hover (ms)
96
+ * @default 0
97
+ */
98
+ hoverHideDelay?: number;
99
+ }
100
+
101
+ const props = withDefaults(defineProps<PopoverProps>(), {
102
+ placement: "bottom",
103
+ offsetDistance: 8,
104
+ dismissOnClickOutside: true,
105
+ showArrow: true,
106
+ showOverlay: false,
107
+ overlayOpacity: 0.5,
108
+ animationDuration: 0.2,
109
+ zIndex: 1000,
110
+ viewportPadding: 16,
111
+ maxWidth: "none",
112
+ maxHeight: "none",
113
+ trigger: "click",
114
+ hoverShowDelay: 0,
115
+ hoverHideDelay: 0,
116
+ });
117
+
118
+ const emit = defineEmits<{
119
+ "update:modelValue": [value: boolean];
120
+ open: [];
121
+ close: [];
122
+ "after-enter": [];
123
+ "after-leave": [];
124
+ }>();
125
+
126
+ // Refs
127
+ const isVisible = ref(false);
128
+ const isAnimating = ref(false);
129
+ const triggerRef = ref<HTMLElement>();
130
+ const popoverRef = ref<HTMLElement>();
131
+ const arrowRef = ref<HTMLElement>();
132
+ const overlayRef = ref<HTMLElement>();
133
+
134
+ // Store cleanup function
135
+ let cleanupAutoUpdate: (() => void) | null = null;
136
+ let hoverTimeout: number | null = null;
137
+
138
+ // Computed styles
139
+ const popoverStyles = computed(() => ({
140
+ maxWidth: props.maxWidth,
141
+ maxHeight: props.maxHeight,
142
+ zIndex: props.zIndex + 1,
143
+ }));
144
+
145
+ const overlayStyles = computed(() => ({
146
+ opacity: props.overlayOpacity,
147
+ zIndex: props.zIndex,
148
+ }));
149
+
150
+ // Positioning
151
+ const updatePosition = async () => {
152
+ if (!triggerRef.value || !popoverRef.value) return;
153
+
154
+ const middleware: Middleware[] = [
155
+ offset(props.offsetDistance),
156
+ flip(),
157
+ shift({ padding: props.viewportPadding }),
158
+ ];
159
+
160
+ if (props.showArrow && arrowRef.value) {
161
+ middleware.push(arrow({ element: arrowRef.value }));
162
+ }
163
+
164
+ const { x, y, placement, middlewareData } = await computePosition(
165
+ triggerRef.value,
166
+ popoverRef.value,
167
+ {
168
+ placement: props.placement,
169
+ middleware,
170
+ }
171
+ );
172
+
173
+ Object.assign(popoverRef.value.style, {
174
+ left: `${x}px`,
175
+ top: `${y}px`,
176
+ });
177
+
178
+ // Position arrow
179
+ if (props.showArrow && arrowRef.value && middlewareData.arrow) {
180
+ const { x: arrowX, y: arrowY } = middlewareData.arrow;
181
+
182
+ const staticSide = {
183
+ top: "bottom",
184
+ right: "left",
185
+ bottom: "top",
186
+ left: "right",
187
+ }[placement.split("-")[0]];
188
+
189
+ Object.assign(arrowRef.value.style, {
190
+ left: arrowX != null ? `${arrowX}px` : "",
191
+ top: arrowY != null ? `${arrowY}px` : "",
192
+ right: "",
193
+ bottom: "",
194
+ [staticSide!]: "-4px",
195
+ });
196
+ }
197
+ };
198
+
199
+ // Animation functions
200
+ const open = async () => {
201
+ if (isAnimating.value || isVisible.value) return;
202
+
203
+ isAnimating.value = true;
204
+ isVisible.value = true;
205
+ emit("open");
206
+
207
+ await nextTick();
208
+
209
+ if (!popoverRef.value) return;
210
+
211
+ // First, position the popover to determine actual placement
212
+ let actualPlacement = props.placement;
213
+
214
+ if (triggerRef.value && popoverRef.value) {
215
+ const middleware: Middleware[] = [
216
+ offset(props.offsetDistance),
217
+ flip(),
218
+ shift({ padding: props.viewportPadding }),
219
+ ];
220
+
221
+ if (props.showArrow && arrowRef.value) {
222
+ middleware.push(arrow({ element: arrowRef.value }));
223
+ }
224
+
225
+ const result = await computePosition(triggerRef.value, popoverRef.value, {
226
+ placement: props.placement,
227
+ middleware,
228
+ });
229
+
230
+ actualPlacement = result.placement;
231
+
232
+ // Apply the position
233
+ Object.assign(popoverRef.value.style, {
234
+ left: `${result.x}px`,
235
+ top: `${result.y}px`,
236
+ });
237
+
238
+ // Position arrow if needed
239
+ if (props.showArrow && arrowRef.value && result.middlewareData.arrow) {
240
+ const { x: arrowX, y: arrowY } = result.middlewareData.arrow;
241
+ const staticSide = {
242
+ top: "bottom",
243
+ right: "left",
244
+ bottom: "top",
245
+ left: "right",
246
+ }[actualPlacement.split("-")[0]];
247
+
248
+ Object.assign(arrowRef.value.style, {
249
+ left: arrowX != null ? `${arrowX}px` : "",
250
+ top: arrowY != null ? `${arrowY}px` : "",
251
+ right: "",
252
+ bottom: "",
253
+ [staticSide!]: "-4px",
254
+ });
255
+ }
256
+ }
257
+
258
+ // NOW set initial state based on actual placement
259
+ const side = actualPlacement.split("-")[0];
260
+ const initialOffset = {
261
+ top: { x: 0, y: 8 }, // slides up from below
262
+ bottom: { x: 0, y: -8 }, // slides down from above
263
+ left: { x: 8, y: 0 }, // slides left from right
264
+ right: { x: -8, y: 0 }, // slides right from left
265
+ }[side] || { x: 0, y: -8 };
266
+
267
+ gsap.set(popoverRef.value, {
268
+ opacity: 0,
269
+ scale: 0.95,
270
+ ...initialOffset,
271
+ });
272
+
273
+ if (overlayRef.value) {
274
+ gsap.set(overlayRef.value, { opacity: 0 });
275
+ }
276
+
277
+ // Start auto-update
278
+ if (triggerRef.value && popoverRef.value) {
279
+ cleanupAutoUpdate = autoUpdate(
280
+ triggerRef.value,
281
+ popoverRef.value,
282
+ updatePosition
283
+ );
284
+ }
285
+
286
+ // Animate in
287
+ const tl = gsap.timeline({
288
+ onComplete: () => {
289
+ isAnimating.value = false;
290
+ emit("after-enter");
291
+ },
292
+ });
293
+
294
+ tl.to(popoverRef.value, {
295
+ opacity: 1,
296
+ scale: 1,
297
+ x: 0,
298
+ y: 0,
299
+ duration: props.animationDuration,
300
+ ease: "back.out(1.7)",
301
+ });
302
+
303
+ if (overlayRef.value) {
304
+ tl.to(
305
+ overlayRef.value,
306
+ {
307
+ opacity: 1,
308
+ duration: props.animationDuration,
309
+ ease: "power2.out",
310
+ },
311
+ 0
312
+ );
313
+ }
314
+ };
315
+
316
+ const close = async () => {
317
+ if (isAnimating.value || !isVisible.value) return;
318
+
319
+ isAnimating.value = true;
320
+ emit("update:modelValue", false);
321
+ emit("close");
322
+
323
+ if (!popoverRef.value) return;
324
+
325
+ // Animate out
326
+ const tl = gsap.timeline({
327
+ onComplete: () => {
328
+ isVisible.value = false;
329
+ isAnimating.value = false;
330
+ if (cleanupAutoUpdate) {
331
+ cleanupAutoUpdate();
332
+ cleanupAutoUpdate = null;
333
+ }
334
+ emit("after-leave");
335
+ },
336
+ });
337
+
338
+ tl.to(popoverRef.value, {
339
+ opacity: 0,
340
+ scale: 0.95,
341
+ y: -4,
342
+ duration: props.animationDuration * 0.8,
343
+ ease: "power2.in",
344
+ });
345
+
346
+ if (overlayRef.value) {
347
+ tl.to(
348
+ overlayRef.value,
349
+ {
350
+ opacity: 0,
351
+ duration: props.animationDuration * 0.8,
352
+ ease: "power2.in",
353
+ },
354
+ 0
355
+ );
356
+ }
357
+ };
358
+
359
+ // Event handlers
360
+ const handleTriggerClick = () => {
361
+ if (props.trigger === "click") {
362
+ if (isVisible.value) {
363
+ close();
364
+ } else {
365
+ open();
366
+ }
367
+ }
368
+ };
369
+
370
+ const handleTriggerMouseEnter = () => {
371
+ if (props.trigger === "hover") {
372
+ if (hoverTimeout) {
373
+ clearTimeout(hoverTimeout);
374
+ }
375
+ hoverTimeout = window.setTimeout(() => {
376
+ open();
377
+ }, props.hoverShowDelay);
378
+ }
379
+ };
380
+
381
+ const handleTriggerMouseLeave = () => {
382
+ if (props.trigger === "hover") {
383
+ if (hoverTimeout) {
384
+ clearTimeout(hoverTimeout);
385
+ }
386
+ hoverTimeout = window.setTimeout(() => {
387
+ close();
388
+ }, props.hoverHideDelay);
389
+ }
390
+ };
391
+
392
+ const handlePopoverMouseEnter = () => {
393
+ if (props.trigger === "hover" && hoverTimeout) {
394
+ clearTimeout(hoverTimeout);
395
+ }
396
+ };
397
+
398
+ const handlePopoverMouseLeave = () => {
399
+ if (props.trigger === "hover") {
400
+ hoverTimeout = window.setTimeout(() => {
401
+ close();
402
+ }, props.hoverHideDelay);
403
+ }
404
+ };
405
+
406
+ const handleClickOutside = (event: MouseEvent) => {
407
+ if (!props.dismissOnClickOutside || !isVisible.value) return;
408
+
409
+ const target = event.target as Node;
410
+ if (
411
+ triggerRef.value?.contains(target) ||
412
+ popoverRef.value?.contains(target)
413
+ ) {
414
+ return;
415
+ }
416
+
417
+ close();
418
+ };
419
+
420
+ const handleEscape = (event: KeyboardEvent) => {
421
+ if (
422
+ event.key === "Escape" &&
423
+ isVisible.value &&
424
+ props.dismissOnClickOutside
425
+ ) {
426
+ close();
427
+ }
428
+ };
429
+
430
+ // Watchers
431
+ watch(
432
+ () => props.modelValue,
433
+ (newValue) => {
434
+ if (newValue) {
435
+ open();
436
+ } else {
437
+ close();
438
+ }
439
+ }
440
+ );
441
+
442
+ // Lifecycle
443
+ onMounted(() => {
444
+ document.addEventListener("click", handleClickOutside, true);
445
+ document.addEventListener("keydown", handleEscape);
446
+ });
447
+
448
+ onUnmounted(() => {
449
+ document.removeEventListener("click", handleClickOutside, true);
450
+ document.removeEventListener("keydown", handleEscape);
451
+ if (cleanupAutoUpdate) {
452
+ cleanupAutoUpdate();
453
+ }
454
+ if (hoverTimeout) {
455
+ clearTimeout(hoverTimeout);
456
+ }
457
+ });
458
+
459
+ // Expose API
460
+ defineExpose({
461
+ open,
462
+ close,
463
+ isVisible,
464
+ isAnimating,
465
+ updatePosition,
466
+ });
467
+ </script>
468
+
469
+ <template>
470
+ <div :class="$style.container">
471
+ <!-- Trigger slot -->
472
+ <div
473
+ ref="triggerRef"
474
+ :class="$style.trigger"
475
+ @click="handleTriggerClick"
476
+ @mouseenter="handleTriggerMouseEnter"
477
+ @mouseleave="handleTriggerMouseLeave"
478
+ >
479
+ <slot name="trigger" />
480
+ </div>
481
+
482
+ <!-- Popover -->
483
+ <Teleport to="body">
484
+ <!-- Overlay -->
485
+ <div
486
+ v-if="showOverlay && isVisible"
487
+ ref="overlayRef"
488
+ :class="$style.overlay"
489
+ :style="overlayStyles"
490
+ @click="handleClickOutside"
491
+ />
492
+
493
+ <!-- Popover content -->
494
+ <div
495
+ v-if="isVisible"
496
+ ref="popoverRef"
497
+ :class="$style.popover"
498
+ :style="popoverStyles"
499
+ @mouseenter="handlePopoverMouseEnter"
500
+ @mouseleave="handlePopoverMouseLeave"
501
+ >
502
+ <!-- Arrow -->
503
+ <div v-if="showArrow" ref="arrowRef" :class="$style.arrow" />
504
+
505
+ <!-- Content -->
506
+ <slot />
507
+ </div>
508
+ </Teleport>
509
+ </div>
510
+ </template>
511
+
512
+ <style module>
513
+ .container {
514
+ display: inline-block;
515
+ }
516
+
517
+ .trigger {
518
+ display: inline-block;
519
+ cursor: pointer;
520
+ }
521
+
522
+ .overlay {
523
+ position: fixed;
524
+ inset: 0;
525
+ background-color: var(--popover-overlay-bg);
526
+ will-change: opacity;
527
+ }
528
+
529
+ .popover {
530
+ position: absolute;
531
+ top: 0;
532
+ left: 0;
533
+ background-color: var(--popover-bg);
534
+ border-radius: 0.353rem;
535
+ box-shadow: 0px 1px 0px 0px var(--popover-shadow),
536
+ inset 0px 1px 0px 0px var(--popover-inset-shadow);
537
+ border: var(--popover-border);
538
+ will-change: transform, opacity;
539
+ transform-origin: top center;
540
+ overflow: hidden;
541
+ }
542
+
543
+ .arrow {
544
+ position: absolute;
545
+ width: 8px;
546
+ height: 8px;
547
+ background-color: var(--popover-arrow-bg);
548
+ transform: rotate(45deg);
549
+ box-shadow: -1px -1px 1px var(--popover-arrow-shadow);
550
+ }
551
+ </style>