@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,296 @@
1
+ <!-- Toast.vue -->
2
+ <script setup lang="ts">
3
+ import tinycolor from "tinycolor2";
4
+ import { computed, ref } from "vue";
5
+ import { icons, type IconKey, XmarkIcon } from "@umbra-ui/icons";
6
+ import type { ToastInstance } from "./types";
7
+ import { IconButton } from "@umbra-ui/core";
8
+ import "./theme.css";
9
+
10
+ interface Props {
11
+ toast: ToastInstance;
12
+ toastStyle?: "bar" | "full" | "notification";
13
+ }
14
+
15
+ const props = withDefaults(defineProps<Props>(), {
16
+ toastStyle: "bar",
17
+ });
18
+ const emit = defineEmits<{
19
+ dismiss: [id: string];
20
+ }>();
21
+
22
+ const containerEl = ref<HTMLElement>();
23
+
24
+ // Fixed icon computation
25
+ const icon = computed(() => {
26
+ const typeIconMap: Record<string, IconKey> = {
27
+ success: "circle-check",
28
+ error: "triangle-warning",
29
+ warning: "triangle-warning",
30
+ info: "circle-info",
31
+ };
32
+
33
+ return (
34
+ props.toast.iconName ||
35
+ typeIconMap[props.toast.toastType as string] ||
36
+ "circle-info"
37
+ );
38
+ });
39
+
40
+ const IconComponent = computed(() => {
41
+ const iconName = icon.value as keyof typeof icons;
42
+ return icons[iconName] || null;
43
+ });
44
+
45
+ // Simplified color system using CSS custom properties
46
+ const toastTypeClass = computed(() => {
47
+ const validTypes = ["success", "error", "warning", "info"];
48
+ return validTypes.includes(props.toast.toastType as string)
49
+ ? props.toast.toastType
50
+ : "custom";
51
+ });
52
+
53
+ const backgroundColor = computed(() => {
54
+ switch (props.toast.toastType) {
55
+ case "success":
56
+ return "var(--toast-success-bg)";
57
+ case "error":
58
+ return "var(--toast-error-bg)";
59
+ case "warning":
60
+ return "var(--toast-warning-bg)";
61
+ case "info":
62
+ return "var(--toast-info-bg)";
63
+ default:
64
+ return props.toast.toastType;
65
+ }
66
+ });
67
+
68
+ const textColor = computed(() => {
69
+ // Get the actual color value from CSS custom property
70
+ const bgColor = backgroundColor.value;
71
+ let actualColor = bgColor;
72
+
73
+ if (bgColor.startsWith("var(")) {
74
+ // Extract the CSS variable name
75
+ const varName = bgColor.slice(4, -1); // removes 'var(' and ')'
76
+ // Get the computed value from the document
77
+ actualColor = getComputedStyle(document.documentElement)
78
+ .getPropertyValue(varName)
79
+ .trim();
80
+ }
81
+
82
+ const color = tinycolor(actualColor);
83
+ return color.isLight() ? "black" : "white";
84
+ });
85
+
86
+ const customColor = computed(() => {
87
+ const validTypes = ["success", "error", "warning", "info"];
88
+ if (!validTypes.includes(props.toast.toastType as string)) {
89
+ return props.toast.toastType;
90
+ }
91
+ return null;
92
+ });
93
+
94
+ // Accessibility: ARIA label
95
+ const ariaLabel = computed(() => {
96
+ const type = props.toast.toastType || "notification";
97
+ return `${type} notification: ${props.toast.title}${
98
+ props.toast.description ? ". " + props.toast.description : ""
99
+ }`;
100
+ });
101
+
102
+ const handleClick = () => {
103
+ if (props.toast.dismissible) {
104
+ emit("dismiss", props.toast.id);
105
+ }
106
+ };
107
+
108
+ const handleDismiss = () => {
109
+ emit("dismiss", props.toast.id);
110
+ };
111
+
112
+ // Keyboard accessibility
113
+ const handleKeyDown = (event: KeyboardEvent) => {
114
+ if (
115
+ props.toast.dismissible &&
116
+ (event.key === "Escape" || event.key === "Enter")
117
+ ) {
118
+ event.preventDefault();
119
+ handleDismiss();
120
+ }
121
+ };
122
+ </script>
123
+
124
+ <template>
125
+ <div
126
+ ref="containerEl"
127
+ :class="[
128
+ $style.container,
129
+ $style[toast.position?.toLowerCase() || 'top'],
130
+ $style[toastTypeClass],
131
+ $style[
132
+ `toastStyle${
133
+ props.toastStyle.charAt(0).toUpperCase() + props.toastStyle.slice(1)
134
+ }`
135
+ ],
136
+ ]"
137
+ :style="{
138
+ '--text-color': textColor,
139
+ ...(customColor && { '--custom-color': customColor }),
140
+ cursor: toast.dismissible ? 'pointer' : 'default',
141
+ }"
142
+ role="alert"
143
+ :aria-label="ariaLabel"
144
+ :aria-live="toast.toastType === 'error' ? 'assertive' : 'polite'"
145
+ :tabindex="toast.dismissible ? 0 : -1"
146
+ @click="handleClick"
147
+ @keydown="handleKeyDown"
148
+ >
149
+ <component
150
+ v-if="IconComponent"
151
+ :is="IconComponent"
152
+ :class="$style.icon"
153
+ aria-hidden="true"
154
+ :color="textColor"
155
+ />
156
+ <div :class="$style.labels">
157
+ <p :class="$style.title">{{ toast.title }}</p>
158
+ <p v-if="toast.description" :class="$style.description">
159
+ {{ toast.description }}
160
+ </p>
161
+ </div>
162
+
163
+ <IconButton
164
+ iconName="xmark"
165
+ :class="$style.closeButton"
166
+ @click.stop="handleDismiss"
167
+ buttonType="plain"
168
+ buttonSize="12"
169
+ :buttonStyle="textColor"
170
+ />
171
+ </div>
172
+ </template>
173
+
174
+ <style module>
175
+ .container {
176
+ display: flex;
177
+ align-items: center;
178
+ gap: 0.706rem;
179
+ padding: 0.706rem;
180
+ width: 100%;
181
+ border-radius: 0.5rem;
182
+ box-shadow: 0 4px 12px var(--toast-shadow);
183
+ transition: scale 0.2s ease-in-out;
184
+ }
185
+
186
+ /* Type variants with proper color system */
187
+ .success {
188
+ background-color: var(--toast-success-bg);
189
+ }
190
+
191
+ .error {
192
+ background-color: var(--toast-error-bg);
193
+ }
194
+
195
+ .warning {
196
+ background-color: var(--toast-warning-bg);
197
+ }
198
+
199
+ .info {
200
+ background-color: var(--toast-info-bg);
201
+ }
202
+
203
+ .custom {
204
+ background-color: var(--custom-color, var(--toast-custom-bg));
205
+ }
206
+
207
+ /* Focus styles for accessibility */
208
+ .container:focus {
209
+ outline: 2px solid currentColor;
210
+ outline-offset: 2px;
211
+ }
212
+
213
+ .container:focus:not(:focus-visible) {
214
+ outline: none;
215
+ }
216
+
217
+ .icon {
218
+ flex-shrink: 0;
219
+ width: 1.5rem;
220
+ height: 1.5rem;
221
+ transition: transform 0.2s ease-in-out;
222
+ }
223
+
224
+ .labels {
225
+ flex: 1;
226
+ display: flex;
227
+ flex-direction: column;
228
+ gap: 0.235rem;
229
+ transition: transform 0.2s ease-in-out;
230
+ }
231
+
232
+ .labels p {
233
+ color: var(--text-color);
234
+ }
235
+
236
+ .toastStyleBar:hover .labels,
237
+ .toastStyleBar:hover .icon {
238
+ transform: translateX(0.25rem);
239
+ }
240
+
241
+ .title {
242
+ font-weight: 600;
243
+ margin: 0;
244
+ }
245
+
246
+ .description {
247
+ font-size: 0.875rem;
248
+ opacity: var(--toast-description-opacity);
249
+ margin: 0;
250
+ }
251
+
252
+ .closeButton {
253
+ transition: transform 0.2s ease-in-out;
254
+ }
255
+
256
+ .toastStyleBar:hover .closeButton {
257
+ transform: translateX(-0.25rem);
258
+ }
259
+
260
+ .closeButton:focus {
261
+ outline: 2px solid currentColor;
262
+ outline-offset: 2px;
263
+ border-radius: 0.25rem;
264
+ }
265
+
266
+ /* Reduced motion support */
267
+ @media (prefers-reduced-motion: reduce) {
268
+ .container {
269
+ transition-duration: 0.15s;
270
+ }
271
+ }
272
+
273
+ /* Toast style variants */
274
+ .toastStyleBar {
275
+ border-radius: 0;
276
+ box-shadow: none;
277
+ border-bottom: 1px solid var(--toast-border);
278
+ }
279
+
280
+ .toastStyleFull {
281
+ border-radius: 0.5rem;
282
+ box-shadow: 0 4px 12px var(--toast-shadow);
283
+ }
284
+
285
+ .toastStyleNotification {
286
+ border-radius: 0.5rem;
287
+ box-shadow: 0 4px 12px var(--toast-shadow);
288
+ }
289
+
290
+ .toastStyleNotification:hover {
291
+ scale: 1.02;
292
+ }
293
+ .toastStyleFull:hover {
294
+ scale: 1.005;
295
+ }
296
+ </style>
@@ -0,0 +1,330 @@
1
+ <!-- ToastContainer.vue -->
2
+ <script setup lang="ts">
3
+ import { computed, ref, watch, nextTick, onMounted, onUnmounted } from "vue";
4
+ import { useToast } from "./useToast";
5
+ import Toast from "./Toast.vue";
6
+ import type { ToastInstance } from "./types";
7
+ import { gsap } from "gsap";
8
+ import { Flip } from "gsap/Flip";
9
+
10
+ // Register GSAP plugin
11
+ gsap.registerPlugin(Flip);
12
+
13
+ const { toasts, removeToast, toastStyle } = useToast();
14
+ const containerRef = ref<HTMLElement>();
15
+
16
+ // Track which toasts have been animated in
17
+ const animatedToasts = new Set<string>();
18
+
19
+ // Track auto-dismiss timeouts
20
+ const autoDismissTimeouts = new Map<string, NodeJS.Timeout>();
21
+
22
+ const handleDismiss = async (id: string) => {
23
+ // Clear any auto-dismiss timeout for this toast
24
+ const existingTimeout = autoDismissTimeouts.get(id);
25
+ if (existingTimeout) {
26
+ clearTimeout(existingTimeout);
27
+ autoDismissTimeouts.delete(id);
28
+ }
29
+
30
+ // Find the toast element that's being removed
31
+ const toastEl = containerRef.value?.querySelector(`[data-toast-id="${id}"]`);
32
+
33
+ if (toastEl) {
34
+ // Get the toast position for animation direction
35
+ const isTop = toastEl.parentElement?.classList.contains("topGroup");
36
+
37
+ // Animate out the specific toast
38
+ await gsap.to(toastEl, {
39
+ opacity: 0,
40
+ y: isTop ? -100 : 100,
41
+ scale: 0.95,
42
+ filter: "blur(8px)",
43
+ duration: 0.4,
44
+ ease: "power2.in",
45
+ });
46
+
47
+ // Capture state of remaining toasts
48
+ const remainingToasts = containerRef.value?.querySelectorAll(
49
+ '.toast-wrapper:not([data-toast-id="' + id + '"])'
50
+ );
51
+ const state = remainingToasts ? Flip.getState(remainingToasts) : null;
52
+
53
+ // Remove the toast
54
+ removeToast(id);
55
+ animatedToasts.delete(id);
56
+
57
+ // Animate remaining toasts to new positions
58
+ await nextTick();
59
+ if (remainingToasts && remainingToasts.length > 0 && state) {
60
+ Flip.from(state, {
61
+ duration: 0.4,
62
+ ease: "power2.inOut",
63
+ stagger: 0.01,
64
+ });
65
+ }
66
+ } else {
67
+ // Fallback if element not found
68
+ removeToast(id);
69
+ animatedToasts.delete(id);
70
+ }
71
+ };
72
+
73
+ // Group toasts by position for proper stacking
74
+ const toastsByPosition = computed(() => {
75
+ const grouped: Record<string, ToastInstance[]> = {
76
+ Top: [],
77
+ Bottom: [],
78
+ };
79
+
80
+ toasts.value.forEach((toast) => {
81
+ const position = toast.position || "Top";
82
+ grouped[position].push(toast);
83
+ });
84
+
85
+ return grouped;
86
+ });
87
+
88
+ // Animate new toasts
89
+ const animateNewToasts = async () => {
90
+ await nextTick();
91
+
92
+ const newToasts = containerRef.value?.querySelectorAll(".toast-wrapper");
93
+ let delay = 0;
94
+
95
+ newToasts?.forEach((toastEl) => {
96
+ const id = toastEl.getAttribute("data-toast-id");
97
+ if (id && !animatedToasts.has(id)) {
98
+ animatedToasts.add(id);
99
+
100
+ // Determine direction based on position
101
+ const isTop = toastEl.parentElement?.classList.contains("topGroup");
102
+
103
+ gsap.fromTo(
104
+ toastEl,
105
+ {
106
+ opacity: 0,
107
+ y: isTop ? -100 : 100,
108
+ scale: 0.9,
109
+ },
110
+ {
111
+ opacity: 1,
112
+ y: 0,
113
+ scale: 1,
114
+ duration: 0.5,
115
+ delay: delay,
116
+ ease: "back.out(1.2)",
117
+ }
118
+ );
119
+
120
+ delay += 0.05;
121
+ }
122
+ });
123
+ };
124
+
125
+ // Setup auto-dismiss for toasts
126
+ const setupAutoDismiss = (toast: ToastInstance) => {
127
+ // Clear any existing timeout for this toast
128
+ const existingTimeout = autoDismissTimeouts.get(toast.id);
129
+ if (existingTimeout) {
130
+ clearTimeout(existingTimeout);
131
+ autoDismissTimeouts.delete(toast.id);
132
+ }
133
+
134
+ // Only setup auto-dismiss if duration is greater than 0
135
+ if (toast.duration && toast.duration > 0) {
136
+ const timeout = setTimeout(() => {
137
+ handleDismiss(toast.id);
138
+ autoDismissTimeouts.delete(toast.id);
139
+ }, toast.duration);
140
+
141
+ autoDismissTimeouts.set(toast.id, timeout);
142
+ }
143
+ };
144
+
145
+ // Watch for changes in toasts
146
+ watch(
147
+ () => toasts.value,
148
+ (newToasts) => {
149
+ animateNewToasts();
150
+
151
+ // Setup auto-dismiss for new toasts
152
+ newToasts.forEach((toast) => {
153
+ if (!autoDismissTimeouts.has(toast.id)) {
154
+ setupAutoDismiss(toast);
155
+ }
156
+ });
157
+ },
158
+ { deep: true }
159
+ );
160
+
161
+ onMounted(() => {
162
+ animateNewToasts();
163
+
164
+ // Setup auto-dismiss for existing toasts
165
+ toasts.value.forEach((toast) => {
166
+ setupAutoDismiss(toast);
167
+ });
168
+ });
169
+
170
+ onUnmounted(() => {
171
+ // Clear all timeouts when component is unmounted
172
+ autoDismissTimeouts.forEach((timeout) => {
173
+ clearTimeout(timeout);
174
+ });
175
+ autoDismissTimeouts.clear();
176
+ });
177
+ </script>
178
+
179
+ <template>
180
+ <Teleport to="body">
181
+ <div
182
+ v-if="toasts.length > 0"
183
+ ref="containerRef"
184
+ :class="[
185
+ $style.container,
186
+ $style[
187
+ `toastStyle${
188
+ toastStyle.charAt(0).toUpperCase() + toastStyle.slice(1)
189
+ }`
190
+ ],
191
+ ]"
192
+ role="region"
193
+ aria-label="Notifications"
194
+ aria-live="polite"
195
+ >
196
+ <!-- Top positioned toasts -->
197
+ <div :class="[$style.toastGroup, $style.topGroup, 'topGroup']">
198
+ <div
199
+ v-for="(toast, index) in toastsByPosition.Top"
200
+ :key="toast.id"
201
+ :data-toast-id="toast.id"
202
+ :class="$style.toastWrapper"
203
+ class="toast-wrapper"
204
+ :style="{
205
+ zIndex: 999 - index,
206
+ }"
207
+ >
208
+ <Toast
209
+ :toast="toast"
210
+ :toastStyle="toastStyle"
211
+ @dismiss="handleDismiss"
212
+ />
213
+ </div>
214
+ </div>
215
+
216
+ <!-- Bottom positioned toasts -->
217
+ <div :class="[$style.toastGroup, $style.bottomGroup, 'bottomGroup']">
218
+ <div
219
+ v-for="(toast, index) in toastsByPosition.Bottom"
220
+ :key="toast.id"
221
+ :data-toast-id="toast.id"
222
+ :class="$style.toastWrapper"
223
+ class="toast-wrapper"
224
+ :style="{
225
+ zIndex: 999 - index,
226
+ }"
227
+ >
228
+ <Toast
229
+ :toast="toast"
230
+ :toastStyle="toastStyle"
231
+ @dismiss="handleDismiss"
232
+ />
233
+ </div>
234
+ </div>
235
+ </div>
236
+ </Teleport>
237
+ </template>
238
+
239
+ <style module>
240
+ .container {
241
+ position: fixed;
242
+ top: 0;
243
+ left: 0;
244
+ right: 0;
245
+ bottom: 0;
246
+ pointer-events: none;
247
+ z-index: 999;
248
+ }
249
+
250
+ .toastGroup {
251
+ position: absolute;
252
+ left: 0;
253
+ right: 0;
254
+ display: flex;
255
+ flex-direction: column;
256
+ align-items: center;
257
+ pointer-events: none;
258
+ }
259
+
260
+ .toastStyleBar .toastGroup {
261
+ padding: 0;
262
+ }
263
+
264
+ .toastStyleFull .toastGroup {
265
+ padding: 1rem;
266
+ gap: 1rem;
267
+ }
268
+
269
+ .toastStyleNotification .toastGroup {
270
+ gap: 1rem;
271
+ padding: 1rem;
272
+ }
273
+
274
+ .topGroup {
275
+ top: 0;
276
+ display: flex;
277
+ }
278
+
279
+ .toastStyleNotification .topGroup {
280
+ justify-content: end;
281
+ align-items: end;
282
+ flex-direction: column;
283
+ }
284
+
285
+ .bottomGroup {
286
+ bottom: 0;
287
+ flex-direction: column-reverse;
288
+ }
289
+
290
+ .toastStyleNotification .bottomGroup {
291
+ justify-content: start;
292
+ align-items: end;
293
+ }
294
+
295
+ .toastWrapper {
296
+ pointer-events: auto;
297
+ width: 100%;
298
+ display: flex;
299
+ }
300
+
301
+ .toastStyleBar .toastWrapper {
302
+ width: 100%;
303
+ max-width: 100%;
304
+ }
305
+
306
+ .toastStyleFull .toastWrapper {
307
+ width: 100%;
308
+ max-width: 100%;
309
+ }
310
+
311
+ .toastStyleNotification .toastWrapper {
312
+ max-width: 360px;
313
+ }
314
+
315
+ /* Focus management */
316
+ .toastWrapper:focus-within {
317
+ z-index: 1000;
318
+ }
319
+
320
+ /* Responsive adjustments */
321
+ @media (max-width: 640px) {
322
+ .toastGroup {
323
+ padding: 0.5rem;
324
+ }
325
+
326
+ .toastWrapper {
327
+ max-width: calc(100vw - 1rem);
328
+ }
329
+ }
330
+ </style>
@@ -0,0 +1,44 @@
1
+ /* Light theme using Colors */
2
+ :root {
3
+ /* Toast type colors */
4
+ --toast-success-bg: #30a46c; /* green9 - success color */
5
+ --toast-error-bg: #e5484d; /* red9 - error color */
6
+ --toast-warning-bg: #ffe629; /* yellow9 - warning color */
7
+ --toast-info-bg: #5b5bd6; /* violet9 - info color */
8
+ --toast-custom-bg: #6b7280; /* gray9 - fallback for custom colors */
9
+
10
+ /* Toast shadow colors */
11
+ --toast-shadow: rgba(
12
+ 0,
13
+ 0,
14
+ 0,
15
+ 0.08
16
+ ); /* blackA6 - lighter shadow for light mode */
17
+ --toast-border: rgba(
18
+ 0,
19
+ 0,
20
+ 0,
21
+ 0.08
22
+ ); /* blackA6 - lighter border for light mode */
23
+
24
+ /* Toast description opacity */
25
+ --toast-description-opacity: 0.8; /* slightly more visible in light mode */
26
+ }
27
+
28
+ /* Dark theme */
29
+ .dark,
30
+ .dark-theme {
31
+ /* Toast type colors */
32
+ --toast-success-bg: #30a46c; /* green9 - success color */
33
+ --toast-error-bg: #e5484d; /* red9 - error color */
34
+ --toast-warning-bg: #ffe629; /* yellow9 - warning color */
35
+ --toast-info-bg: #5b5bd6; /* violet9 - info color */
36
+ --toast-custom-bg: #6b7280; /* gray9 - fallback for custom colors */
37
+
38
+ /* Toast shadow colors */
39
+ --toast-shadow: rgba(0, 0, 0, 0.15); /* Original dark mode value */
40
+ --toast-border: rgba(0, 0, 0, 0.15); /* Original dark mode value */
41
+
42
+ /* Toast description opacity */
43
+ --toast-description-opacity: 0.9; /* Original dark mode value */
44
+ }
@@ -0,0 +1,46 @@
1
+ // types.ts
2
+ export type BarPosition = "Top" | "Bottom";
3
+ export type ToastType = "success" | "error" | "warning" | "info";
4
+
5
+ export interface ToastAction {
6
+ label: string;
7
+ action: () => void;
8
+ style?: "primary" | "secondary";
9
+ }
10
+
11
+ export interface ToastOptions {
12
+ title: string;
13
+ description?: string;
14
+ iconName?: string;
15
+ position?: BarPosition;
16
+ toastType?: ToastType | string; // string allows custom colors
17
+ duration?: number; // Auto dismiss duration in ms (0 = no auto dismiss)
18
+ dismissible?: boolean;
19
+ actions?: ToastAction[]; // Optional action buttons
20
+ onDismiss?: () => void; // Callback when toast is dismissed
21
+ className?: string; // Custom CSS class
22
+ role?: "alert" | "status"; // ARIA role
23
+ }
24
+
25
+ export interface ToastInstance
26
+ extends Required<
27
+ Omit<ToastOptions, "actions" | "onDismiss" | "className" | "role">
28
+ > {
29
+ id: string;
30
+ show: boolean;
31
+ actions?: ToastAction[];
32
+ onDismiss?: () => void;
33
+ className?: string;
34
+ role?: "alert" | "status";
35
+ }
36
+
37
+ // Preset configurations for common scenarios
38
+ export const ToastPresets = {
39
+ quick: { duration: 2000 } as Partial<ToastOptions>,
40
+ persistent: { duration: 0, dismissible: true } as Partial<ToastOptions>,
41
+ important: {
42
+ duration: 0,
43
+ dismissible: false,
44
+ role: "alert",
45
+ } as Partial<ToastOptions>,
46
+ } as const;