@weni/unnnic-system 3.10.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/assets/tokens/colors.json.d.ts +376 -0
  3. package/dist/components/Accordion/Accordion.vue.d.ts +1 -1
  4. package/dist/components/Alert/Alert.vue.d.ts +17 -116
  5. package/dist/components/Alert/Alert.vue.d.ts.map +1 -1
  6. package/dist/components/Alert/Version1dot1.vue.d.ts +2 -38
  7. package/dist/components/Alert/Version1dot1.vue.d.ts.map +1 -1
  8. package/dist/components/AudioRecorder/AudioHandler.vue.d.ts +2 -2
  9. package/dist/components/AudioRecorder/AudioPlayer.vue.d.ts +1 -1
  10. package/dist/components/AudioRecorder/AudioRecorder.vue.d.ts +5 -5
  11. package/dist/components/AvatarIcon/AvatarIcon.vue.d.ts +3 -3
  12. package/dist/components/Banner/Banner.vue.d.ts +1 -1
  13. package/dist/components/Banner/InfoBanner.vue.d.ts +1 -1
  14. package/dist/components/Breadcrumb/Breadcrumb.vue.d.ts +1 -1
  15. package/dist/components/Button/Button.vue.d.ts +1 -1
  16. package/dist/components/Button/Button.vue.d.ts.map +1 -1
  17. package/dist/components/Button/ButtonIcon.vue.d.ts +1 -1
  18. package/dist/components/Button/types.d.ts +1 -1
  19. package/dist/components/Button/types.d.ts.map +1 -1
  20. package/dist/components/Card/AccountCard.vue.d.ts +5 -5
  21. package/dist/components/Card/BlankCard.vue.d.ts +1 -1
  22. package/dist/components/Card/Card.vue.d.ts +27 -27
  23. package/dist/components/Card/CardCompany.vue.d.ts +11 -414
  24. package/dist/components/Card/CardData.vue.d.ts +1 -1
  25. package/dist/components/Card/CardStatusesContainer.vue.d.ts +5 -5
  26. package/dist/components/Card/ContentCard.vue.d.ts +3 -3
  27. package/dist/components/Card/DashCard.vue.d.ts +5 -5
  28. package/dist/components/Card/DefaultCard.vue.d.ts +1 -1
  29. package/dist/components/Card/MarketplaceCard.vue.d.ts +2 -2
  30. package/dist/components/Card/SimpleCard.vue.d.ts +3 -3
  31. package/dist/components/Card/StatusCard.vue.d.ts +2 -2
  32. package/dist/components/Card/TitleCard.vue.d.ts +3 -3
  33. package/dist/components/CardImage/CardImage.vue.d.ts +24 -31
  34. package/dist/components/CardInformation/CardInformation.vue.d.ts +5 -5
  35. package/dist/components/CardProject/CardProject.vue.d.ts +3 -3
  36. package/dist/components/Carousel/Carousel.vue.d.ts +13 -416
  37. package/dist/components/Carousel/TagCarousel.vue.d.ts +12 -415
  38. package/dist/components/ChartBar/ChartBar.vue.d.ts +5 -5
  39. package/dist/components/ChartLine/ChartLine.vue.d.ts +1 -1
  40. package/dist/components/ChatText/ChatText.vue.d.ts +2 -2
  41. package/dist/components/ChatsContact/ChatsContact.vue.d.ts +21 -446
  42. package/dist/components/ChatsDashboardTagLive/ChatsDashboardTagLive.vue.d.ts +1 -1
  43. package/dist/components/ChatsHeader/ChatsHeader.vue.d.ts +1 -1
  44. package/dist/components/ChatsHeader/ChatsHeader.vue.d.ts.map +1 -1
  45. package/dist/components/ChatsMessage/ChatsMessage.vue.d.ts +5 -5
  46. package/dist/components/ChatsMessage/ChatsMessageStatusBackdrop.vue.d.ts +2 -2
  47. package/dist/components/ChatsNavbar/ChatsNavbar.vue.d.ts +1 -1
  48. package/dist/components/ChatsUserAvatar/ChatsUserAvatar.vue.d.ts +2 -2
  49. package/dist/components/Checkbox/Checkbox.vue.d.ts +19 -26
  50. package/dist/components/Checkbox/Checkbox.vue.d.ts.map +1 -1
  51. package/dist/components/CheckboxGroup/CheckboxGroup.vue.d.ts +28 -0
  52. package/dist/components/CheckboxGroup/CheckboxGroup.vue.d.ts.map +1 -0
  53. package/dist/components/Comment/Comment.vue.d.ts +1 -1
  54. package/dist/components/DataArea/DataArea.vue.d.ts +2 -2
  55. package/dist/components/DataTable/index.vue.d.ts +1 -1
  56. package/dist/components/DataTable/index.vue.d.ts.map +1 -1
  57. package/dist/components/DateFilter/DateFilter.vue.d.ts +170 -39
  58. package/dist/components/DatePicker/DatePicker.vue.d.ts +4 -4
  59. package/dist/components/Drawer/Drawer.vue.d.ts +4 -4
  60. package/dist/components/Dropdown/Dropdown.vue.d.ts +1 -1
  61. package/dist/components/Dropdown/LanguageSelect.vue.d.ts +3 -3
  62. package/dist/components/Flag.vue.d.ts +2 -2
  63. package/dist/components/FormElement/FormElement.vue.d.ts +51 -28
  64. package/dist/components/FormElement/FormElement.vue.d.ts.map +1 -1
  65. package/dist/components/Icon.vue.d.ts +1 -1
  66. package/dist/components/Icon.vue.d.ts.map +1 -1
  67. package/dist/components/IconLoading/IconLoading.vue.d.ts +1 -1
  68. package/dist/components/ImportCard/ImportCard.vue.d.ts +4 -4
  69. package/dist/components/Input/BaseInput.vue.d.ts +11 -2
  70. package/dist/components/Input/BaseInput.vue.d.ts.map +1 -1
  71. package/dist/components/Input/Input.vue.d.ts +170 -39
  72. package/dist/components/Input/Input.vue.d.ts.map +1 -1
  73. package/dist/components/Input/TextInput.vue.d.ts +33 -24
  74. package/dist/components/Input/TextInput.vue.d.ts.map +1 -1
  75. package/dist/components/InputDatePicker/InputDatePicker.vue.d.ts +175 -44
  76. package/dist/components/InputNext/InputNext.vue.d.ts +4 -4
  77. package/dist/components/Label/Label.vue.d.ts +9 -15
  78. package/dist/components/Label/Label.vue.d.ts.map +1 -1
  79. package/dist/components/Modal/Modal.vue.d.ts +2 -2
  80. package/dist/components/ModalDialog/ModalDialog.vue.d.ts +6 -6
  81. package/dist/components/ModalNext/ModalNext.vue.d.ts +175 -44
  82. package/dist/components/ModalUpload/ModalUpload.vue.d.ts +9 -9
  83. package/dist/components/MoodRating/MoodRating.vue.d.ts +1 -1
  84. package/dist/components/MultiSelect/MultiSelect.vue.d.ts +26 -14
  85. package/dist/components/PageHeader/PageHeader.vue.d.ts +28 -0
  86. package/dist/components/PageHeader/PageHeader.vue.d.ts.map +1 -0
  87. package/dist/components/PageHeader/index.d.ts +3 -0
  88. package/dist/components/PageHeader/index.d.ts.map +1 -0
  89. package/dist/components/PageHeader/types.d.ts +9 -0
  90. package/dist/components/PageHeader/types.d.ts.map +1 -0
  91. package/dist/components/Pagination/Pagination.vue.d.ts +3 -3
  92. package/dist/components/ProgressBar/ProgressBar.vue.d.ts +1 -1
  93. package/dist/components/Radio/Radio.vue.d.ts +10 -6
  94. package/dist/components/Radio/Radio.vue.d.ts.map +1 -1
  95. package/dist/components/SelectSmart/SelectSmart.vue.d.ts +68 -469
  96. package/dist/components/SelectSmart/SelectSmartMultipleHeader.vue.d.ts +11 -414
  97. package/dist/components/SelectSmart/SelectSmartOption.vue.d.ts +21 -28
  98. package/dist/components/SelectSmart/SelectSmartOption.vue.d.ts.map +1 -1
  99. package/dist/components/SelectTime/index.vue.d.ts +33 -24
  100. package/dist/components/SkeletonLoading/skeletonTheme.vue.d.ts +1 -1
  101. package/dist/components/Slider/Slider.vue.d.ts +2 -2
  102. package/dist/components/StarRating/StarRating.vue.d.ts +1 -1
  103. package/dist/components/Switch/Switch.vue.d.ts +55 -21
  104. package/dist/components/Switch/Switch.vue.d.ts.map +1 -1
  105. package/dist/components/Tab/Tab.vue.d.ts +13 -2
  106. package/dist/components/TableNext/TableBodyCell.vue.d.ts +2 -2
  107. package/dist/components/TableNext/TablePagination.vue.d.ts +3 -3
  108. package/dist/components/TabsExpanded/TabsExpanded.vue.d.ts +1 -1
  109. package/dist/components/Tag/DefaultTag.vue.d.ts +4 -83
  110. package/dist/components/Tag/DefaultTag.vue.d.ts.map +1 -1
  111. package/dist/components/Tag/Tag.vue.d.ts +12 -414
  112. package/dist/components/Tag/Tag.vue.d.ts.map +1 -1
  113. package/dist/components/Tag/types.d.ts +18 -0
  114. package/dist/components/Tag/types.d.ts.map +1 -0
  115. package/dist/components/TextArea/TextArea.vue.d.ts +78 -33
  116. package/dist/components/TextArea/TextArea.vue.d.ts.map +1 -1
  117. package/dist/components/Toast/Toast.vue.d.ts +16 -0
  118. package/dist/components/Toast/Toast.vue.d.ts.map +1 -0
  119. package/dist/components/Toast/ToastManager.d.ts +14 -0
  120. package/dist/components/Toast/ToastManager.d.ts.map +1 -0
  121. package/dist/components/Toast/types.d.ts +35 -0
  122. package/dist/components/Toast/types.d.ts.map +1 -0
  123. package/dist/components/ToolTip/ToolTip.vue.d.ts +1 -1
  124. package/dist/components/Tour/Tour.vue.d.ts +3 -3
  125. package/dist/components/Tour/TourPopover.vue.d.ts +3 -3
  126. package/dist/components/UploadArea/UploadArea.vue.d.ts +4 -4
  127. package/dist/components/index.d.ts +8910 -10904
  128. package/dist/components/index.d.ts.map +1 -1
  129. package/dist/components/ui/popover/PopoverContent.vue.d.ts +1 -1
  130. package/dist/components/ui/popover/PopoverContent.vue.d.ts.map +1 -1
  131. package/dist/{es-4b899f97.mjs → es-e3248052.mjs} +1 -1
  132. package/dist/{index-23489897.mjs → index-f67d5b30.mjs} +9289 -8806
  133. package/dist/{pt-br-5a914a63.mjs → pt-br-f6f53acd.mjs} +1 -1
  134. package/dist/style.css +1 -1
  135. package/dist/unnnic.mjs +181 -173
  136. package/dist/unnnic.umd.js +35 -36
  137. package/dist/utils/call.d.ts +2 -1
  138. package/dist/utils/call.d.ts.map +1 -1
  139. package/package.json +2 -2
  140. package/src/assets/icons/checkbox-checked-disabled.svg +3 -0
  141. package/src/assets/icons/checkbox-checked.svg +3 -0
  142. package/src/assets/icons/checkbox-less-disabled.svg +3 -0
  143. package/src/assets/icons/checkbox-less.svg +3 -0
  144. package/src/assets/icons/radio-checked.svg +3 -0
  145. package/src/assets/icons/switch-checked-disabled.svg +3 -0
  146. package/src/assets/icons/switch-checked.svg +3 -0
  147. package/src/components/Alert/Alert.vue +26 -135
  148. package/src/components/Alert/Version1dot1.vue +0 -36
  149. package/src/components/Alert/__tests__/Alert.spec.js +2 -45
  150. package/src/components/Alert/__tests__/Version1dot1.spec.js +0 -21
  151. package/src/components/Alert/__tests__/__snapshots__/Alert.spec.js.snap +11 -7
  152. package/src/components/Alert/__tests__/__snapshots__/AlertBanner.spec.js.snap +2 -2
  153. package/src/components/Alert/__tests__/__snapshots__/Version1dot1.spec.js.snap +1 -1
  154. package/src/components/Button/Button.vue +67 -117
  155. package/src/components/Button/types.ts +0 -1
  156. package/src/components/ChatsContact/ChatsContact.vue +10 -6
  157. package/src/components/Checkbox/Checkbox.vue +117 -65
  158. package/src/components/Checkbox/__tests__/Checkbox.spec.js +6 -21
  159. package/src/components/CheckboxGroup/CheckboxGroup.vue +96 -0
  160. package/src/components/FormElement/FormElement.vue +63 -93
  161. package/src/components/Icon.vue +2 -0
  162. package/src/components/Input/BaseInput.vue +12 -12
  163. package/src/components/Input/Input.scss +19 -20
  164. package/src/components/Input/Input.vue +60 -55
  165. package/src/components/Input/TextInput.vue +25 -54
  166. package/src/components/Input/__test__/Input.spec.js +13 -33
  167. package/src/components/Input/__test__/TextInput.spec.js +6 -8
  168. package/src/components/Input/__test__/__snapshots__/Input.spec.js.snap +14 -5
  169. package/src/components/Input/__test__/__snapshots__/TextInput.spec.js.snap +1 -1
  170. package/src/components/Label/Label.vue +52 -21
  171. package/src/components/Label/__tests__/Label.spec.js +1 -1
  172. package/src/components/Label/__tests__/__snapshots__/Label.spec.js.snap +1 -1
  173. package/src/components/PageHeader/PageHeader.vue +148 -0
  174. package/src/components/PageHeader/index.ts +2 -0
  175. package/src/components/PageHeader/types.ts +10 -0
  176. package/src/components/Radio/Radio.vue +118 -66
  177. package/src/components/Radio/__test__/Radio.spec.js +14 -20
  178. package/src/components/Radio/__test__/__snapshots__/Radio.spec.js.snap +4 -3
  179. package/src/components/RadioGroup/RadioGroup.vue +142 -0
  180. package/src/components/SelectSmart/SelectSmart.vue +4 -3
  181. package/src/components/Switch/Switch.vue +132 -91
  182. package/src/components/Switch/__tests__/Switch.spec.js +8 -75
  183. package/src/components/Switch/__tests__/__snapshots__/Switch.spec.js.snap +5 -6
  184. package/src/components/Tab/Tab.vue +37 -23
  185. package/src/components/Tab/__test__/__snapshots__/Tab.spec.js.snap +1 -1
  186. package/src/components/TableNext/__test__/__snapshots__/TableNext.spec.js.snap +2 -2
  187. package/src/components/TableNext/__test__/__snapshots__/TablePagination.spec.js.snap +2 -2
  188. package/src/components/Tag/DefaultTag.vue +51 -107
  189. package/src/components/Tag/Tag.vue +32 -79
  190. package/src/components/Tag/types.ts +19 -0
  191. package/src/components/TextArea/TextArea.vue +41 -12
  192. package/src/components/TextArea/__test__/__snapshots__/TextArea.spec.js.snap +11 -3
  193. package/src/components/Toast/Toast.vue +246 -0
  194. package/src/components/Toast/ToastManager.ts +110 -0
  195. package/src/components/Toast/__tests__/Toast.spec.js +291 -0
  196. package/src/components/Toast/__tests__/ToastManager.spec.js +294 -0
  197. package/src/components/Toast/types.ts +57 -0
  198. package/src/components/index.ts +33 -17
  199. package/src/stories/Alert.stories.js +6 -67
  200. package/src/stories/Button.stories.js +29 -39
  201. package/src/stories/Checkbox.stories.js +11 -4
  202. package/src/stories/CheckboxGroup.stories.js +105 -0
  203. package/src/stories/Input.stories.js +71 -76
  204. package/src/stories/Label.stories.js +7 -0
  205. package/src/stories/PageHeader.stories.js +330 -0
  206. package/src/stories/Radio.stories.js +28 -1
  207. package/src/stories/RadioGroup.stories.js +144 -0
  208. package/src/stories/Switch.stories.js +10 -5
  209. package/src/stories/Tab.stories.js +11 -4
  210. package/src/stories/Tag.stories.js +24 -43
  211. package/src/stories/TextArea.stories.js +14 -2
  212. package/src/stories/Toast.mdx +123 -0
  213. package/src/stories/Toast.stories.js +126 -0
  214. package/src/types/scheme-colors.d.ts +1 -0
  215. package/src/utils/call.js +46 -18
  216. package/dist/components/Tag/BrandTag.vue.d.ts +0 -51
  217. package/dist/components/Tag/BrandTag.vue.d.ts.map +0 -1
  218. package/dist/components/Tag/IndicatorTag.vue.d.ts +0 -151
  219. package/dist/components/Tag/IndicatorTag.vue.d.ts.map +0 -1
  220. package/dist/components/Tag/TagNext.vue.d.ts +0 -24
  221. package/dist/components/Tag/TagNext.vue.d.ts.map +0 -1
  222. package/src/components/Alert/AlertBanner.vue +0 -182
  223. package/src/components/Alert/AlertCaller.vue +0 -49
  224. package/src/components/Alert/__tests__/AlertBanner.spec.js +0 -89
  225. package/src/components/Alert/__tests__/AlertCaller.spec.js +0 -98
  226. package/src/components/Tag/BrandTag.vue +0 -96
  227. package/src/components/Tag/IndicatorTag.vue +0 -107
  228. package/src/components/Tag/TagNext.vue +0 -60
@@ -0,0 +1,246 @@
1
+ <template>
2
+ <Transition
3
+ name="toast-slide"
4
+ appear
5
+ data-testid="toast-transition"
6
+ @after-leave="$emit('destroy')"
7
+ >
8
+ <aside
9
+ v-if="isVisible"
10
+ :class="['unnnic-toast', `unnnic-toast--${type}`]"
11
+ :role="type === 'error' ? 'alert' : 'status'"
12
+ :aria-live="type === 'error' ? 'assertive' : 'polite'"
13
+ data-testid="toast"
14
+ >
15
+ <section
16
+ class="unnnic-toast__content"
17
+ data-testid="toast-content"
18
+ >
19
+ <header
20
+ class="unnnic-toast__header"
21
+ data-testid="toast-header"
22
+ >
23
+ <UnnnicIcon
24
+ :icon="typeConfig.icon"
25
+ :scheme="typeConfig.scheme"
26
+ size="ant"
27
+ data-testid="toast-type-icon"
28
+ />
29
+
30
+ <h3
31
+ class="unnnic-toast__title"
32
+ data-testid="toast-title"
33
+ >
34
+ {{ title }}
35
+ </h3>
36
+
37
+ <UnnnicIcon
38
+ icon="close"
39
+ scheme="neutral-dark"
40
+ size="ant"
41
+ clickable
42
+ class="unnnic-toast__close"
43
+ data-testid="toast-close-icon"
44
+ @click="handleClose"
45
+ @keydown.enter="handleClose"
46
+ />
47
+ </header>
48
+
49
+ <p
50
+ v-if="description"
51
+ data-testid="toast-text"
52
+ class="unnnic-toast__text"
53
+ >
54
+ {{ description }}
55
+ </p>
56
+ </section>
57
+
58
+ <UnnnicButton
59
+ v-if="button"
60
+ type="tertiary"
61
+ :text="button.text"
62
+ class="unnnic-toast__action-button"
63
+ data-testid="toast-action-button"
64
+ @click="handleAction"
65
+ @keydown.enter="handleAction"
66
+ @keydown.space.prevent="handleAction"
67
+ />
68
+ </aside>
69
+ </Transition>
70
+ </template>
71
+
72
+ <script setup lang="ts">
73
+ import { ref, computed, onMounted, onUnmounted } from 'vue';
74
+
75
+ import UnnnicIcon from '@/components/Icon.vue';
76
+ import UnnnicButton from '@/components/Button/Button.vue';
77
+
78
+ import type { ToastProps, ToastEmits } from './types';
79
+ import type { SchemeColor } from '@/types/scheme-colors';
80
+
81
+ defineOptions({
82
+ name: 'UnnnicToast',
83
+ });
84
+
85
+ const props = withDefaults(defineProps<ToastProps>(), {
86
+ title: '',
87
+ description: '',
88
+ button: undefined,
89
+ timeout: 5000,
90
+ type: 'informational',
91
+ });
92
+
93
+ const emit = defineEmits<ToastEmits>();
94
+
95
+ const isVisible = ref(false);
96
+ let timeoutId: number | null = null;
97
+
98
+ const typeConfig = computed(() => {
99
+ const configMap = {
100
+ informational: { icon: 'info', scheme: 'blue-500' },
101
+ attention: { icon: 'error', scheme: 'yellow-500' },
102
+ success: { icon: 'check_circle', scheme: 'green-500' },
103
+ error: { icon: 'cancel', scheme: 'red-500' },
104
+ };
105
+
106
+ return configMap[props.type || 'informational'] as {
107
+ icon: string;
108
+ scheme: SchemeColor;
109
+ };
110
+ });
111
+
112
+ const handleClose = () => {
113
+ isVisible.value = false;
114
+ emit('close');
115
+ };
116
+
117
+ const handleAction = () => {
118
+ if (props.button?.action) {
119
+ props.button.action();
120
+ }
121
+ };
122
+
123
+ const startTimeout = () => {
124
+ if (props.timeout > 0) {
125
+ timeoutId = window.setTimeout(() => {
126
+ handleClose();
127
+ }, props.timeout);
128
+ }
129
+ };
130
+
131
+ const stopTimeout = () => {
132
+ if (timeoutId) {
133
+ window.clearTimeout(timeoutId);
134
+ timeoutId = null;
135
+ }
136
+ };
137
+
138
+ onMounted(() => {
139
+ isVisible.value = true;
140
+ startTimeout();
141
+ });
142
+
143
+ onUnmounted(() => {
144
+ stopTimeout();
145
+ });
146
+ </script>
147
+
148
+ <style lang="scss" scoped>
149
+ @use '@/assets/scss/unnnic' as *;
150
+
151
+ .unnnic-toast {
152
+ position: fixed;
153
+ bottom: $unnnic-space-4;
154
+ right: $unnnic-space-4;
155
+ z-index: 9999;
156
+
157
+ display: flex;
158
+ align-items: flex-end;
159
+ flex-direction: column;
160
+ gap: $unnnic-space-2;
161
+
162
+ min-width: 250px;
163
+ max-width: 450px;
164
+ padding: $unnnic-space-4;
165
+
166
+ border-radius: $unnnic-radius-2;
167
+ box-shadow: $unnnic-shadow-2;
168
+ border: 1px solid $unnnic-color-border-base;
169
+
170
+ &--informational {
171
+ border-color: $unnnic-color-border-info;
172
+ background-color: $unnnic-color-bg-info;
173
+ }
174
+
175
+ &--attention {
176
+ border-color: $unnnic-color-border-warning;
177
+ background-color: $unnnic-color-bg-warning;
178
+ }
179
+
180
+ &--success {
181
+ border-color: $unnnic-color-border-success;
182
+ background-color: $unnnic-color-bg-success;
183
+ }
184
+
185
+ &--error {
186
+ border-color: $unnnic-color-border-critical;
187
+ background-color: $unnnic-color-bg-critical;
188
+ }
189
+
190
+ &__content,
191
+ &__header {
192
+ width: 100%;
193
+
194
+ display: flex;
195
+ }
196
+
197
+ &__content {
198
+ flex-direction: column;
199
+ gap: $unnnic-space-1;
200
+ }
201
+
202
+ &__header {
203
+ align-items: center;
204
+ gap: $unnnic-space-2;
205
+ }
206
+
207
+ &__title {
208
+ flex: 1;
209
+
210
+ margin: 0;
211
+
212
+ font: $unnnic-font-action;
213
+ color: $unnnic-color-fg-emphasized;
214
+ }
215
+
216
+ &__text {
217
+ margin: 0;
218
+ margin-left: $unnnic-space-7;
219
+
220
+ font: $unnnic-font-caption-2;
221
+ color: $unnnic-color-fg-emphasized;
222
+ }
223
+ }
224
+
225
+ // Animations
226
+ .toast-slide-enter-active,
227
+ .toast-slide-leave-active {
228
+ transition: all 0.3s ease;
229
+ }
230
+
231
+ .toast-slide-enter-from {
232
+ transform: translateY(100%);
233
+ opacity: 0;
234
+ }
235
+
236
+ .toast-slide-leave-to {
237
+ transform: translateY(100%);
238
+ opacity: 0;
239
+ }
240
+
241
+ .toast-slide-enter-to,
242
+ .toast-slide-leave-from {
243
+ transform: translateY(0);
244
+ opacity: 1;
245
+ }
246
+ </style>
@@ -0,0 +1,110 @@
1
+ import { createApp } from 'vue';
2
+ import Toast from './Toast.vue';
3
+ import type {
4
+ ToastProps,
5
+ ToastInstance,
6
+ ToastOptions,
7
+ ToastCall,
8
+ } from './types';
9
+
10
+ class ToastManager implements ToastManager {
11
+ private toasts: Map<string, ToastInstance> = new Map();
12
+ private container: HTMLElement | null = null;
13
+ private nextId = 0;
14
+
15
+ private createContainer(): HTMLElement {
16
+ if (this.container) {
17
+ return this.container;
18
+ }
19
+
20
+ this.container = document.createElement('div');
21
+ this.container.setAttribute('unnnic-toast-container', 'true');
22
+
23
+ document.body.appendChild(this.container);
24
+ return this.container;
25
+ }
26
+
27
+ private generateId(): string {
28
+ return `toast-${++this.nextId}`;
29
+ }
30
+
31
+ show(props: ToastProps): ToastInstance {
32
+ const id = this.generateId();
33
+ const container = this.createContainer();
34
+
35
+ const toastWrapper = document.createElement('div');
36
+
37
+ // Create promise that resolves when toast is destroyed
38
+ let resolvePromise: () => void;
39
+ const promise = new Promise<void>((resolve) => {
40
+ resolvePromise = resolve;
41
+ });
42
+
43
+ const app = createApp(Toast, {
44
+ ...props,
45
+ onClose: () => {
46
+ this.close(id);
47
+ },
48
+ onDestroy: () => {
49
+ app.unmount();
50
+ toastWrapper.remove();
51
+ resolvePromise(); // Resolve the promise when toast is destroyed
52
+ },
53
+ });
54
+
55
+ app.mount(toastWrapper);
56
+ container.appendChild(toastWrapper);
57
+
58
+ const toastInstance: ToastInstance = {
59
+ id,
60
+ props,
61
+ close: () => this.close(id),
62
+ promise,
63
+ };
64
+
65
+ this.toasts.set(id, toastInstance);
66
+ return toastInstance;
67
+ }
68
+
69
+ close(id: string): void {
70
+ const toast = this.toasts.get(id);
71
+ if (toast) {
72
+ this.toasts.delete(id);
73
+ }
74
+ }
75
+ }
76
+
77
+ const toastManager = new ToastManager();
78
+
79
+ export const toast: ToastCall = {
80
+ info: (title: string, description?: string, options?: ToastOptions) =>
81
+ toastManager.show({
82
+ title,
83
+ description,
84
+ ...options,
85
+ type: 'informational',
86
+ }).promise,
87
+ success: (title: string, description?: string, options?: ToastOptions) =>
88
+ toastManager.show({
89
+ title,
90
+ description,
91
+ ...options,
92
+ type: 'success',
93
+ }).promise,
94
+ attention: (title: string, description?: string, options?: ToastOptions) =>
95
+ toastManager.show({
96
+ title,
97
+ description,
98
+ ...options,
99
+ type: 'attention',
100
+ }).promise,
101
+ error: (title: string, description?: string, options?: ToastOptions) =>
102
+ toastManager.show({
103
+ title,
104
+ description,
105
+ ...options,
106
+ type: 'error',
107
+ }).promise,
108
+ };
109
+
110
+ export default toastManager;
@@ -0,0 +1,291 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { beforeEach, describe, expect, afterEach, test, vi } from 'vitest';
3
+ import Toast from '../Toast.vue';
4
+
5
+ describe('UnnnicToast', () => {
6
+ let wrapper;
7
+ let mockAction;
8
+
9
+ beforeEach(() => {
10
+ mockAction = vi.fn();
11
+ vi.useFakeTimers();
12
+
13
+ wrapper = shallowMount(Toast, {
14
+ props: {
15
+ title: 'Test Toast',
16
+ },
17
+ global: {
18
+ stubs: {
19
+ Transition: {
20
+ template: '<div data-testid="toast-transition"><slot /></div>',
21
+ },
22
+ },
23
+ },
24
+ });
25
+ });
26
+
27
+ afterEach(() => {
28
+ if (wrapper) {
29
+ wrapper.unmount();
30
+ }
31
+ vi.useRealTimers();
32
+ vi.clearAllMocks();
33
+ });
34
+
35
+ const transition = () =>
36
+ wrapper.findComponent('[data-testid="toast-transition"]');
37
+ const toast = () => wrapper.find('[data-testid="toast"]');
38
+ const content = () => wrapper.find('[data-testid="toast-content"]');
39
+ const header = () => wrapper.find('[data-testid="toast-header"]');
40
+ const title = () => wrapper.find('[data-testid="toast-title"]');
41
+ const text = () => wrapper.find('[data-testid="toast-text"]');
42
+ const actionButton = () =>
43
+ wrapper.findComponent('[data-testid="toast-action-button"]');
44
+ const closeIcon = () =>
45
+ wrapper.findComponent('[data-testid="toast-close-icon"]');
46
+ const typeIcon = () =>
47
+ wrapper.findComponent('[data-testid="toast-type-icon"]');
48
+
49
+ describe('Rendering', () => {
50
+ test('renders correctly with required props', () => {
51
+ expect(transition().exists()).toBe(true);
52
+ expect(header().exists()).toBe(true);
53
+
54
+ expect(typeIcon().exists()).toBe(true);
55
+ expect(typeIcon().props('icon')).toBe('info');
56
+ expect(typeIcon().props('scheme')).toBe('blue-500');
57
+ expect(typeIcon().props('size')).toBe('ant');
58
+
59
+ expect(title().exists()).toBe(true);
60
+ expect(title().text()).toBe('Test Toast');
61
+
62
+ expect(text().exists()).toBe(false);
63
+
64
+ expect(toast().exists()).toBe(true);
65
+ expect(content().exists()).toBe(true);
66
+ expect(actionButton().exists()).toBe(false);
67
+
68
+ expect(closeIcon().exists()).toBe(true);
69
+ expect(closeIcon().props('icon')).toBe('close');
70
+ expect(closeIcon().props('scheme')).toBe('neutral-dark');
71
+ expect(closeIcon().props('size')).toBe('ant');
72
+ expect(closeIcon().props('clickable')).toBe(true);
73
+ });
74
+
75
+ test('renders description when provided', async () => {
76
+ await wrapper.setProps({ description: 'Test description' });
77
+
78
+ expect(text().exists()).toBe(true);
79
+ expect(text().text()).toBe('Test description');
80
+ });
81
+
82
+ test('applies correct CSS class based on type', async () => {
83
+ const types = ['informational', 'attention', 'success', 'error'];
84
+
85
+ for (const type of types) {
86
+ await wrapper.setProps({ type });
87
+
88
+ expect(toast().classes()).toContain(`unnnic-toast--${type}`);
89
+ }
90
+ });
91
+
92
+ test('sets correct ARIA attributes for different types', async () => {
93
+ // Error type
94
+ await wrapper.setProps({ type: 'error' });
95
+ expect(toast().attributes('role')).toBe('alert');
96
+ expect(toast().attributes('aria-live')).toBe('assertive');
97
+
98
+ // Other types
99
+ await wrapper.setProps({ type: 'informational' });
100
+ expect(toast().attributes('role')).toBe('status');
101
+ expect(toast().attributes('aria-live')).toBe('polite');
102
+ });
103
+ });
104
+
105
+ describe('Icon Configuration', () => {
106
+ test('displays correct icon for each type', async () => {
107
+ const typeConfigs = [
108
+ { type: 'informational', expectedIcon: 'info' },
109
+ { type: 'attention', expectedIcon: 'error' },
110
+ { type: 'success', expectedIcon: 'check_circle' },
111
+ { type: 'error', expectedIcon: 'cancel' },
112
+ ];
113
+
114
+ for (const { type, expectedIcon } of typeConfigs) {
115
+ await wrapper.setProps({ type });
116
+ const icon = typeIcon();
117
+ expect(icon.props('icon')).toBe(expectedIcon);
118
+ }
119
+ });
120
+
121
+ test('displays correct scheme color for each type', async () => {
122
+ const typeConfigs = [
123
+ { type: 'informational', expectedScheme: 'blue-500' },
124
+ { type: 'attention', expectedScheme: 'yellow-500' },
125
+ { type: 'success', expectedScheme: 'green-500' },
126
+ { type: 'error', expectedScheme: 'red-500' },
127
+ ];
128
+
129
+ for (const { type, expectedScheme } of typeConfigs) {
130
+ await wrapper.setProps({ type });
131
+ const icon = typeIcon();
132
+ expect(icon.props('scheme')).toBe(expectedScheme);
133
+ }
134
+ });
135
+ });
136
+
137
+ describe('Close Functionality', () => {
138
+ test('emits close event when close button is clicked', async () => {
139
+ const closeButton = closeIcon();
140
+ await closeButton.trigger('click');
141
+
142
+ expect(wrapper.emitted('close')).toBeTruthy();
143
+ expect(wrapper.emitted('close')).toHaveLength(1);
144
+
145
+ expect(wrapper.vm.isVisible).toBe(false);
146
+ });
147
+
148
+ test('emits close event when close button is activated with Enter key', async () => {
149
+ const closeButton = closeIcon();
150
+ await closeButton.trigger('keydown.enter');
151
+
152
+ expect(wrapper.emitted('close')).toBeTruthy();
153
+ expect(wrapper.emitted('close')).toHaveLength(1);
154
+
155
+ expect(wrapper.vm.isVisible).toBe(false);
156
+ });
157
+
158
+ test('hides toast when close is triggered', async () => {
159
+ expect(toast().exists()).toBe(true);
160
+
161
+ await wrapper.vm.handleClose();
162
+ await wrapper.vm.$nextTick();
163
+
164
+ expect(toast().exists()).toBe(false);
165
+ });
166
+ });
167
+
168
+ describe('Action Button', () => {
169
+ test('renders action button when provided', async () => {
170
+ await wrapper.setProps({
171
+ button: {
172
+ text: 'Action',
173
+ action: mockAction,
174
+ },
175
+ });
176
+
177
+ expect(actionButton().exists()).toBe(true);
178
+ expect(actionButton().props('text')).toBe('Action');
179
+ });
180
+
181
+ test('does not render action button when not provided', async () => {
182
+ expect(actionButton().exists()).toBe(false);
183
+ });
184
+
185
+ test('calls action function when action button is clicked', async () => {
186
+ await wrapper.setProps({
187
+ button: {
188
+ text: 'Action',
189
+ action: mockAction,
190
+ },
191
+ });
192
+
193
+ await actionButton().trigger('click');
194
+
195
+ expect(mockAction).toHaveBeenCalledTimes(1);
196
+ });
197
+
198
+ test('calls action function when action button is activated with Enter key', async () => {
199
+ await wrapper.setProps({
200
+ button: {
201
+ text: 'Action',
202
+ action: mockAction,
203
+ },
204
+ });
205
+
206
+ await actionButton().trigger('keydown.enter');
207
+
208
+ expect(mockAction).toHaveBeenCalledTimes(1);
209
+ });
210
+
211
+ test('does not throw error when action button has no action function', async () => {
212
+ await wrapper.setProps({
213
+ button: {
214
+ text: 'Action',
215
+ action: undefined,
216
+ },
217
+ });
218
+
219
+ expect(async () => {
220
+ await actionButton().trigger('click');
221
+ }).not.toThrow();
222
+ });
223
+ });
224
+
225
+ describe('Timeout Functionality', () => {
226
+ const createTimeoutWrapper = (timeout = 1000) => {
227
+ return shallowMount(Toast, {
228
+ props: { title: 'Test Toast', timeout },
229
+ });
230
+ };
231
+
232
+ test('auto-closes after timeout period', async () => {
233
+ wrapper = createTimeoutWrapper();
234
+ vi.advanceTimersByTime(1000);
235
+
236
+ await wrapper.vm.$nextTick();
237
+ expect(wrapper.emitted('close')).toBeTruthy();
238
+ });
239
+
240
+ test('does not auto-close when timeout is 0', async () => {
241
+ wrapper = createTimeoutWrapper(0);
242
+ vi.advanceTimersByTime(5000);
243
+
244
+ await wrapper.vm.$nextTick();
245
+ expect(wrapper.emitted('close')).toBeFalsy();
246
+ });
247
+
248
+ test('does not auto-close when timeout is negative', async () => {
249
+ wrapper = createTimeoutWrapper(-1);
250
+ vi.advanceTimersByTime(5000);
251
+
252
+ await wrapper.vm.$nextTick();
253
+ expect(wrapper.emitted('close')).toBeFalsy();
254
+ });
255
+
256
+ test('clears timeout when component is unmounted', () => {
257
+ vi.useRealTimers();
258
+ const clearTimeoutSpy = vi.spyOn(window, 'clearTimeout');
259
+
260
+ const localWrapper = shallowMount(Toast, {
261
+ props: { title: 'Test Toast', timeout: 1000 },
262
+ });
263
+
264
+ localWrapper.unmount();
265
+ expect(clearTimeoutSpy).toHaveBeenCalled();
266
+
267
+ clearTimeoutSpy.mockRestore();
268
+ vi.useFakeTimers();
269
+ });
270
+ });
271
+
272
+ describe('Transitions', () => {
273
+ test('emits destroy event after leave transition', async () => {
274
+ await wrapper.vm.handleClose();
275
+ await wrapper.vm.$nextTick();
276
+
277
+ await transition().vm.$emit('after-leave');
278
+
279
+ expect(wrapper.emitted('destroy')).toBeTruthy();
280
+ });
281
+ });
282
+
283
+ describe('Default Props', () => {
284
+ test('uses correct default values', () => {
285
+ expect(wrapper.props('description')).toBe('');
286
+ expect(wrapper.props('button')).toBeUndefined();
287
+ expect(wrapper.props('timeout')).toBe(5000);
288
+ expect(wrapper.props('type')).toBe('informational');
289
+ });
290
+ });
291
+ });