mgv-backoffice 1.0.13 → 1.2.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 (83) hide show
  1. package/README.md +735 -70
  2. package/dist/index.d.ts +1 -27
  3. package/dist/{components → src/components}/BaseAlert.vue.d.ts +2 -2
  4. package/dist/src/components/BaseAppLayout.vue.d.ts +31 -0
  5. package/dist/{components → src/components}/BaseButton.vue.d.ts +5 -2
  6. package/dist/src/components/BaseCollapsibleSection.vue.d.ts +35 -0
  7. package/dist/src/components/BaseConfirmModal.vue.d.ts +23 -0
  8. package/dist/src/components/BaseEntityPickerModal.vue.d.ts +58 -0
  9. package/dist/{components → src/components}/BaseLogo.vue.d.ts +3 -3
  10. package/dist/{components → src/components}/BaseModal.vue.d.ts +4 -3
  11. package/dist/src/components/BaseModalShell.vue.d.ts +43 -0
  12. package/dist/src/components/BaseNotFoundPage.vue.d.ts +17 -0
  13. package/dist/src/components/BaseSidebar.vue.d.ts +46 -0
  14. package/dist/src/components/BaseTextInputModal.vue.d.ts +46 -0
  15. package/dist/{components → src/components}/BaseToast.vue.d.ts +14 -2
  16. package/dist/{components → src/components}/Pagination.vue.d.ts +0 -3
  17. package/dist/src/composables/useDebounce.d.ts +10 -0
  18. package/dist/src/composables/useEscapeKey.d.ts +6 -0
  19. package/dist/src/composables/useMobileSidebar.d.ts +6 -0
  20. package/dist/src/composables/useTheme.d.ts +19 -0
  21. package/dist/src/composables/useThemeClasses.d.ts +62 -0
  22. package/dist/src/composables/useToast.d.ts +19 -0
  23. package/dist/{enums → src/enums}/AlertEnum.d.ts +1 -1
  24. package/dist/{enums → src/enums}/BaseButtonSizeEnum.d.ts +1 -1
  25. package/dist/src/enums/BaseLogoEnum.d.ts +7 -0
  26. package/dist/src/index.d.ts +46 -0
  27. package/dist/src/types/entityPicker.d.ts +11 -0
  28. package/dist/src/types/sidebar.d.ts +22 -0
  29. package/dist/src/utils/httpColors.d.ts +19 -0
  30. package/dist/ui-lib.css +3 -0
  31. package/dist/ui-lib.js +1624 -1011
  32. package/dist/ui-lib.umd.cjs +1 -1
  33. package/package.json +14 -14
  34. package/src/components/BaseAlert.vue +2 -2
  35. package/src/components/BaseAppLayout.vue +65 -0
  36. package/src/components/BaseBreadcrumb.vue +2 -1
  37. package/src/components/BaseButton.vue +152 -132
  38. package/src/components/BaseCollapsibleSection.vue +107 -0
  39. package/src/components/BaseConfirmModal.vue +110 -0
  40. package/src/components/BaseEntityPickerModal.vue +330 -0
  41. package/src/components/BaseLogo.vue +5 -5
  42. package/src/components/BaseModal.vue +95 -75
  43. package/src/components/BaseModalShell.vue +115 -0
  44. package/src/components/BaseNotFoundPage.vue +51 -0
  45. package/src/components/BaseSidebar.vue +223 -0
  46. package/src/components/BaseTextInputModal.vue +144 -0
  47. package/src/components/BaseToast.vue +41 -20
  48. package/src/components/ColoredSquares.vue +2 -4
  49. package/src/components/Pagination.vue +137 -136
  50. package/src/components/TrendArrow.vue +1 -1
  51. package/src/composables/useDebounce.ts +32 -0
  52. package/src/composables/useEscapeKey.ts +15 -0
  53. package/src/composables/useMobileSidebar.ts +23 -0
  54. package/src/composables/useTheme.ts +69 -0
  55. package/src/composables/useThemeClasses.ts +133 -0
  56. package/src/composables/useToast.ts +56 -0
  57. package/src/enums/AlertEnum.ts +1 -1
  58. package/src/enums/BaseButtonSizeEnum.ts +1 -1
  59. package/src/enums/BaseLogoEnum.ts +4 -1
  60. package/src/index.ts +64 -36
  61. package/src/types/entityPicker.ts +11 -0
  62. package/src/types/sidebar.ts +24 -0
  63. package/src/utils/httpColors.ts +68 -0
  64. package/src/utils/util.ts +61 -53
  65. package/dist/enums/BaseLogoEnum.d.ts +0 -5
  66. package/dist/style.css +0 -1
  67. /package/dist/{components → src/components}/BaseBadge.vue.d.ts +0 -0
  68. /package/dist/{components → src/components}/BaseBreadcrumb.vue.d.ts +0 -0
  69. /package/dist/{components → src/components}/BaseLine.vue.d.ts +0 -0
  70. /package/dist/{components → src/components}/BaseRow.vue.d.ts +0 -0
  71. /package/dist/{components → src/components}/BaseSpinner.vue.d.ts +0 -0
  72. /package/dist/{components → src/components}/ColoredSquares.vue.d.ts +0 -0
  73. /package/dist/{components → src/components}/EarningsCard.vue.d.ts +0 -0
  74. /package/dist/{components → src/components}/EuroAmount.vue.d.ts +0 -0
  75. /package/dist/{components → src/components}/TrendArrow.vue.d.ts +0 -0
  76. /package/dist/{enums → src/enums}/BaseBadgeEnum.d.ts +0 -0
  77. /package/dist/{enums → src/enums}/BaseButtonEnum.d.ts +0 -0
  78. /package/dist/{enums → src/enums}/BaseModalEnum.d.ts +0 -0
  79. /package/dist/{enums → src/enums}/BaseToastEnum.d.ts +0 -0
  80. /package/dist/{enums → src/enums}/ColorsEnums.d.ts +0 -0
  81. /package/dist/{enums → src/enums}/LineEnum.d.ts +0 -0
  82. /package/dist/{enums → src/enums}/PositioningEnum.d.ts +0 -0
  83. /package/dist/{utils → src/utils}/util.d.ts +0 -0
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <div class="flex items-center justify-center min-h-[60vh]">
3
+ <div class="text-center px-6">
4
+ <ExclamationTriangleIcon class="w-16 h-16 mx-auto mb-4" :class="t.illustration" />
5
+ <h1 class="text-4xl font-bold mb-2" :class="isDark ? 'text-gray-200' : 'text-gray-800'">
6
+ {{ code }}
7
+ </h1>
8
+ <p class="text-lg mb-6" :class="t.dimTextAlt">{{ message }}</p>
9
+ <button
10
+ @click="goHome"
11
+ class="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium text-white transition-colors cursor-pointer"
12
+ :class="isDark ? 'bg-emerald-600 hover:bg-emerald-500' : 'bg-emerald-600 hover:bg-emerald-700'"
13
+ >
14
+ {{ homeLabel }}
15
+ </button>
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import { useRouter } from 'vue-router'
22
+ import { ExclamationTriangleIcon } from '@heroicons/vue/24/outline'
23
+ import { useTheme } from '../composables/useTheme'
24
+ import { useThemeClasses } from '../composables/useThemeClasses'
25
+
26
+ interface Props {
27
+ /** Big heading. Defaults to '404'. */
28
+ code?: string
29
+ /** Subtitle text. Defaults to 'Page not found'. */
30
+ message?: string
31
+ /** Vue Router named route to redirect to when the button is clicked. */
32
+ homeRouteName?: string
33
+ /** Button label. Defaults to 'Go home'. */
34
+ homeLabel?: string
35
+ }
36
+
37
+ const props = withDefaults(defineProps<Props>(), {
38
+ code: '404',
39
+ message: 'Page not found',
40
+ homeRouteName: 'home',
41
+ homeLabel: 'Go home',
42
+ })
43
+
44
+ const router = useRouter()
45
+ const { isDark } = useTheme()
46
+ const t = useThemeClasses()
47
+
48
+ function goHome() {
49
+ router.push({ name: props.homeRouteName })
50
+ }
51
+ </script>
@@ -0,0 +1,223 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
3
+ import { useRouter, useRoute } from 'vue-router'
4
+ import { Bars3Icon, XMarkIcon } from '@heroicons/vue/24/outline'
5
+ import { useTheme } from '../composables/useTheme'
6
+ import { useMobileSidebar } from '../composables/useMobileSidebar'
7
+ import type { NavSection } from '../types/sidebar'
8
+
9
+ interface Props {
10
+ /** Nav sections (groups of items). */
11
+ sections: NavSection[]
12
+ /** Route name to navigate to when the logo / home button is clicked. */
13
+ homeRouteName?: string
14
+ /** Optional app name shown in the footer next to the version. */
15
+ appName?: string
16
+ /** Optional version string shown in the footer. */
17
+ version?: string
18
+ /** Whether to render the dark/light toggle in the footer. */
19
+ showThemeToggle?: boolean
20
+ }
21
+
22
+ const props = withDefaults(defineProps<Props>(), {
23
+ homeRouteName: 'home',
24
+ appName: '',
25
+ version: '',
26
+ showThemeToggle: true,
27
+ })
28
+
29
+ const router = useRouter()
30
+ const route = useRoute()
31
+ const { isDark, toggleTheme } = useTheme()
32
+ const { mobileOpen, close: closeMobile, toggle: toggleMobile } = useMobileSidebar()
33
+
34
+ // Refs needed for the mobile focus management.
35
+ const closeMobileButton = ref<HTMLButtonElement | null>(null)
36
+ let lastFocusedBeforeOpen: HTMLElement | null = null
37
+
38
+ // When the mobile sidebar opens, move focus into it so keyboard users
39
+ // don't have to Tab back from <main> (which the surrounding layout
40
+ // marks `inert`); when it closes, restore focus to whatever opened the
41
+ // menu.
42
+ watch(mobileOpen, async (open) => {
43
+ if (open) {
44
+ lastFocusedBeforeOpen = document.activeElement instanceof HTMLElement
45
+ ? document.activeElement
46
+ : null
47
+ await nextTick()
48
+ closeMobileButton.value?.focus()
49
+ } else {
50
+ lastFocusedBeforeOpen?.focus()
51
+ lastFocusedBeforeOpen = null
52
+ }
53
+ })
54
+
55
+ onMounted(() => {
56
+ // The visibility-change health-check hook that lived in WireMate's
57
+ // AppSidebar was application-specific; consumers can wire any
58
+ // periodic work in the `#status` slot themselves.
59
+ })
60
+
61
+ onUnmounted(() => {})
62
+
63
+ function goHome() {
64
+ router.push({ name: props.homeRouteName })
65
+ closeMobile()
66
+ }
67
+
68
+ function isActive(name: string): boolean {
69
+ return route.name === name
70
+ }
71
+
72
+ function navigate(name: string) {
73
+ router.push({ name })
74
+ closeMobile()
75
+ }
76
+ </script>
77
+
78
+ <template>
79
+ <div>
80
+ <!-- Mobile top bar (hidden on desktop via scoped CSS to avoid flex/hidden conflict) -->
81
+ <div class="mobile-topbar fixed top-0 inset-x-0 z-30 flex items-center justify-between px-4 py-3 border-b transition-colors"
82
+ :class="isDark ? 'bg-gray-900 border-gray-800' : 'bg-white border-gray-200'">
83
+ <button type="button" @click="goHome" class="cursor-pointer" aria-label="Go to home">
84
+ <!--
85
+ `logo` slot: consumers drop their brand logo in here. Receives
86
+ the current size hint so a logo component can scale to match.
87
+ -->
88
+ <slot name="logo" :size="28" />
89
+ </button>
90
+ <button
91
+ type="button"
92
+ @click="toggleMobile"
93
+ class="p-2 rounded-lg cursor-pointer"
94
+ :class="isDark ? 'text-gray-400 hover:bg-gray-800' : 'text-gray-600 hover:bg-gray-100'"
95
+ :aria-label="mobileOpen ? 'Close navigation menu' : 'Open navigation menu'"
96
+ :aria-expanded="mobileOpen"
97
+ >
98
+ <Bars3Icon v-if="!mobileOpen" class="w-6 h-6" aria-hidden="true" />
99
+ <XMarkIcon v-else class="w-6 h-6" aria-hidden="true" />
100
+ </button>
101
+ </div>
102
+
103
+ <!-- Mobile overlay (decorative). -->
104
+ <div
105
+ v-if="mobileOpen"
106
+ class="mobile-overlay fixed inset-0 z-40 bg-black/50"
107
+ aria-hidden="true"
108
+ @click="closeMobile"
109
+ />
110
+
111
+ <aside
112
+ role="navigation"
113
+ aria-label="Main navigation"
114
+ class="fixed top-0 left-0 h-screen w-60 flex flex-col border-r transition-transform duration-300 lg:pointer-events-auto"
115
+ :class="[
116
+ isDark ? 'bg-gray-900 border-gray-800' : 'bg-white border-gray-200',
117
+ mobileOpen ? 'translate-x-0 z-50' : '-translate-x-full max-lg:pointer-events-none lg:translate-x-0 z-50',
118
+ ]"
119
+ >
120
+ <div class="flex flex-col h-full">
121
+ <!-- Logo -->
122
+ <div class="px-5 pt-5 pb-4 flex items-center justify-center relative">
123
+ <button type="button" @click="goHome" class="cursor-pointer" aria-label="Go to home">
124
+ <slot name="logo" :size="52" />
125
+ </button>
126
+ <button
127
+ ref="closeMobileButton"
128
+ type="button"
129
+ @click="closeMobile"
130
+ class="lg:hidden p-1.5 rounded-lg cursor-pointer absolute right-3 top-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500"
131
+ :class="isDark ? 'text-gray-400 hover:bg-gray-800' : 'text-gray-500 hover:bg-gray-100'"
132
+ aria-label="Close navigation menu"
133
+ >
134
+ <XMarkIcon class="w-5 h-5" aria-hidden="true" />
135
+ </button>
136
+ </div>
137
+
138
+ <!-- Nav -->
139
+ <nav class="flex-1 overflow-y-auto px-3 space-y-6">
140
+ <div v-for="section in sections" :key="section.title">
141
+ <p class="px-2 mb-2 text-[10px] font-semibold uppercase tracking-wider text-gray-500">
142
+ {{ section.title }}
143
+ </p>
144
+ <ul class="space-y-0.5">
145
+ <li v-for="item in section.items" :key="item.name">
146
+ <button
147
+ type="button"
148
+ :aria-current="isActive(item.name) ? 'page' : undefined"
149
+ @click="navigate(item.name)"
150
+ class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors w-full text-left cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500"
151
+ :class="isActive(item.name)
152
+ ? (isDark
153
+ ? 'bg-emerald-500/15 text-emerald-300'
154
+ : 'bg-emerald-50 text-emerald-700')
155
+ : (isDark
156
+ ? 'text-gray-400 hover:text-gray-100 hover:bg-gray-800'
157
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100')"
158
+ >
159
+ <component :is="item.icon" class="w-[18px] h-[18px] shrink-0" />
160
+ {{ item.label }}
161
+ </button>
162
+ </li>
163
+ </ul>
164
+ </div>
165
+ </nav>
166
+
167
+ <!-- Bottom: Theme toggle, status slot, footer slot -->
168
+ <div class="px-4 py-4 border-t transition-colors space-y-3"
169
+ :class="isDark ? 'border-gray-800' : 'border-gray-200'">
170
+ <button
171
+ v-if="showThemeToggle"
172
+ type="button"
173
+ @click="toggleTheme"
174
+ :title="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
175
+ :aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
176
+ :aria-pressed="isDark"
177
+ class="flex items-center gap-3 w-full px-3 py-2.5 rounded-lg text-sm font-medium transition-colors cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500"
178
+ :class="isDark
179
+ ? 'text-gray-400 hover:text-gray-100 hover:bg-gray-800'
180
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'"
181
+ >
182
+ {{ isDark ? 'Switch to light' : 'Switch to dark' }}
183
+ <span class="ml-auto relative w-9 h-5 rounded-full transition-colors"
184
+ :class="isDark ? 'bg-emerald-600' : 'bg-gray-300'">
185
+ <span class="absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white shadow-sm transition-transform duration-300"
186
+ :class="isDark ? 'translate-x-4' : 'translate-x-0'" />
187
+ </span>
188
+ </button>
189
+
190
+ <!--
191
+ `status` slot: consumers drop in their own indicator (e.g.
192
+ health check, sync status). The wrapper handles spacing only.
193
+ -->
194
+ <slot name="status" />
195
+
196
+ <!--
197
+ `footer` slot: links / version / etc. If not provided and
198
+ either `appName` or `version` is set, we render a default
199
+ "appName vVersion" line.
200
+ -->
201
+ <slot name="footer">
202
+ <div
203
+ v-if="appName || version"
204
+ class="flex items-center justify-center gap-2 text-xs"
205
+ :class="isDark ? 'text-gray-500' : 'text-gray-500'"
206
+ >
207
+ <span>{{ appName }}<template v-if="appName && version"> </template><template v-if="version">v{{ version }}</template></span>
208
+ </div>
209
+ </slot>
210
+ </div>
211
+ </div>
212
+ </aside>
213
+ </div>
214
+ </template>
215
+
216
+ <style scoped>
217
+ @media (min-width: 1024px) {
218
+ /* Hide mobile top bar on desktop */
219
+ .mobile-topbar {
220
+ display: none !important;
221
+ }
222
+ }
223
+ </style>
@@ -0,0 +1,144 @@
1
+ <template>
2
+ <!--
3
+ Generic "ask the user for a single string and confirm" dialog.
4
+ Used for clone-name prompts, rename dialogs, etc.
5
+
6
+ Slots:
7
+ • icon — replace the default emerald-tinted document icon.
8
+
9
+ Preserves typed input on stray backdrop clicks (only treats the
10
+ click as cancel if the user hasn't modified the initial value).
11
+ Escape is always treated as an explicit dismissal.
12
+ -->
13
+ <BaseModalShell
14
+ :title="title"
15
+ manual-close
16
+ @backdrop="handleBackdropClick"
17
+ >
18
+ <template #icon>
19
+ <slot name="icon">
20
+ <div
21
+ class="flex-shrink-0 flex h-12 w-12 items-center justify-center rounded-full"
22
+ :class="isDark ? 'bg-emerald-900/30' : 'bg-emerald-50'"
23
+ >
24
+ <DocumentDuplicateIcon class="h-6 w-6 text-emerald-600" aria-hidden="true" />
25
+ </div>
26
+ </slot>
27
+ </template>
28
+
29
+ <p class="text-sm" :class="isDark ? 'text-gray-300' : 'text-gray-600'">
30
+ {{ message }}
31
+ </p>
32
+
33
+ <div class="mt-4">
34
+ <label
35
+ class="block text-sm font-medium mb-1"
36
+ :class="t.label"
37
+ >
38
+ {{ inputLabel }}
39
+ </label>
40
+ <input
41
+ ref="nameInput"
42
+ v-model="inputValue"
43
+ type="text"
44
+ :placeholder="placeholder"
45
+ class="w-full px-3 py-2 rounded-md border text-sm transition-colors"
46
+ :class="isDark
47
+ ? 'bg-gray-800 border-gray-600 text-gray-100 placeholder-gray-500 focus:outline-none focus:border-emerald-500'
48
+ : 'bg-white border-gray-300 text-gray-900 placeholder-gray-400 focus:outline-none focus:border-emerald-500'"
49
+ @keyup.enter="handleConfirm"
50
+ />
51
+ </div>
52
+
53
+ <template #footer>
54
+ <button
55
+ type="button"
56
+ class="inline-flex items-center justify-center px-4 py-2 border rounded-md shadow-sm text-sm font-medium transition-colors duration-150"
57
+ :class="t.ghostButton"
58
+ @click="handleCancel"
59
+ >
60
+ {{ cancelText }}
61
+ </button>
62
+ <button
63
+ type="button"
64
+ :disabled="!inputValue.trim() || submitting"
65
+ class="inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white transition-colors duration-150"
66
+ :class="[
67
+ !inputValue.trim() || submitting
68
+ ? 'bg-emerald-400 cursor-not-allowed'
69
+ : 'bg-emerald-600 hover:bg-emerald-700 cursor-pointer'
70
+ ]"
71
+ @click="handleConfirm"
72
+ >
73
+ {{ submitting ? submittingText : confirmText }}
74
+ </button>
75
+ </template>
76
+ </BaseModalShell>
77
+ </template>
78
+
79
+ <script setup lang="ts">
80
+ import { ref, onMounted, nextTick } from 'vue'
81
+ import { DocumentDuplicateIcon } from '@heroicons/vue/24/outline'
82
+ import BaseModalShell from './BaseModalShell.vue'
83
+ import { useTheme } from '../composables/useTheme'
84
+ import { useThemeClasses } from '../composables/useThemeClasses'
85
+ import { useEscapeKey } from '../composables/useEscapeKey'
86
+
87
+ interface Props {
88
+ title: string
89
+ message: string
90
+ initialValue?: string
91
+ placeholder?: string
92
+ inputLabel?: string
93
+ confirmText?: string
94
+ cancelText?: string
95
+ submittingText?: string
96
+ submitting?: boolean
97
+ }
98
+
99
+ const props = withDefaults(defineProps<Props>(), {
100
+ initialValue: '',
101
+ placeholder: 'Enter value...',
102
+ inputLabel: 'Name',
103
+ confirmText: 'Confirm',
104
+ cancelText: 'Cancel',
105
+ submittingText: 'Working...',
106
+ submitting: false,
107
+ })
108
+
109
+ const emit = defineEmits<{
110
+ confirm: [value: string]
111
+ cancel: []
112
+ }>()
113
+
114
+ const { isDark } = useTheme()
115
+ const t = useThemeClasses()
116
+ const inputValue = ref(props.initialValue)
117
+ const nameInput = ref<HTMLInputElement | null>(null)
118
+
119
+ onMounted(async () => {
120
+ await nextTick()
121
+ nameInput.value?.focus()
122
+ })
123
+
124
+ function handleConfirm() {
125
+ if (!inputValue.value.trim() || props.submitting) return
126
+ emit('confirm', inputValue.value.trim())
127
+ }
128
+
129
+ function handleCancel() {
130
+ emit('cancel')
131
+ }
132
+
133
+ // Backdrop click: only treat it as cancel if the user hasn't modified
134
+ // the value. Otherwise a stray click outside the dialog would silently
135
+ // throw away the typed input.
136
+ function handleBackdropClick() {
137
+ if (inputValue.value.trim() === (props.initialValue ?? '').trim()) {
138
+ handleCancel()
139
+ }
140
+ }
141
+
142
+ // Escape always cancels.
143
+ useEscapeKey(handleCancel)
144
+ </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div v-if="isLoading" id="toast-top-right" :class="computeMainComponentCss()" role="alert">
2
+ <div v-if="isVisible" id="toast-top-right" :class="computeMainComponentCss()" role="alert">
3
3
 
4
4
  <div class="flex items-center w-full max-w-md p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800">
5
5
  <div v-if="mode == BaseToastEnum.ERROR" :class="computeModeCss()">
@@ -21,7 +21,7 @@
21
21
  <span class="sr-only">Close</span>
22
22
  <XMarkIcon
23
23
  class="h-6 w-6 text-gray-500 cursor-pointer"
24
- @click="isLoading = !isLoading"
24
+ @click="close"
25
25
  />
26
26
  </button>
27
27
  </div>
@@ -31,7 +31,7 @@
31
31
 
32
32
  <script lang="ts" setup>
33
33
  import { ExclamationCircleIcon, CheckCircleIcon, ExclamationTriangleIcon, XMarkIcon } from '@heroicons/vue/24/outline';
34
- import { ref, PropType } from "vue";
34
+ import { ref, onMounted, onUnmounted, PropType } from "vue";
35
35
  import { BaseToastEnum } from "../enums/BaseToastEnum";
36
36
  import { PositioningEnum } from "../enums/PositioningEnum";
37
37
 
@@ -52,34 +52,63 @@ const props = defineProps({
52
52
  positioning: {
53
53
  type: String,
54
54
  required: false,
55
- default: 'right'
55
+ default: PositioningEnum.TOP_RIGHT
56
+ },
57
+ duration: {
58
+ type: Number,
59
+ required: false,
60
+ default: 5000
61
+ }
62
+ })
63
+
64
+ const isVisible = ref(true);
65
+ let timer: ReturnType<typeof setTimeout> | null = null;
66
+
67
+ function close() {
68
+ isVisible.value = false;
69
+ if (timer) {
70
+ clearTimeout(timer);
71
+ timer = null;
72
+ }
73
+ }
74
+
75
+ onMounted(() => {
76
+ timer = setTimeout(() => isVisible.value = false, props.duration);
77
+ })
78
+
79
+ onUnmounted(() => {
80
+ if (timer) {
81
+ clearTimeout(timer);
56
82
  }
57
83
  })
58
84
 
59
85
  function computeModeCss() {
60
- let basePositioning = "";
86
+ let modeCss = "";
61
87
  switch (props.mode) {
62
88
  case (BaseToastEnum.ERROR):
63
- basePositioning = "text-red-500 bg-red-100 dark:bg-red-800 dark:text-red-200"
89
+ modeCss = "text-red-500 bg-red-100 dark:bg-red-800 dark:text-red-200"
64
90
  break;
65
91
  case (BaseToastEnum.SUCCESS):
66
- basePositioning = "text-green-500 bg-green-100 dark:bg-green-800 dark:text-green-200"
92
+ modeCss = "text-green-500 bg-green-100 dark:bg-green-800 dark:text-green-200"
67
93
  break;
68
94
  case (BaseToastEnum.WARNING):
69
- basePositioning = "text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200"
95
+ modeCss = "text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200"
96
+ break;
97
+ default:
98
+ modeCss = "text-gray-500 bg-gray-100 dark:bg-gray-800 dark:text-gray-200"
70
99
  break;
71
100
  }
72
- return 'inline-flex items-center justify-center flex-shrink-0 w-8 h-8 rounded-lg ' + basePositioning;
101
+ return 'inline-flex items-center justify-center flex-shrink-0 w-8 h-8 rounded-lg ' + modeCss;
73
102
  }
74
103
 
75
104
  function computeMainComponentCss() {
76
105
  let basePositioning = "top-5 right-5";
77
106
  switch (props.positioning) {
78
107
  case (PositioningEnum.BOTTOM_LEFT):
79
- basePositioning = "bottom-5"
108
+ basePositioning = "bottom-5 left-5"
80
109
  break;
81
110
  case (PositioningEnum.TOP_LEFT):
82
- basePositioning = "top-5"
111
+ basePositioning = "top-5 left-5"
83
112
  break;
84
113
  case (PositioningEnum.TOP_RIGHT):
85
114
  basePositioning = "top-5 right-5"
@@ -88,14 +117,6 @@ function computeMainComponentCss() {
88
117
  basePositioning = "bottom-5 right-5"
89
118
  break;
90
119
  }
91
- return 'fixed flex items-center w-full max-w-md p-4 space-x-4 divide-x rounded-lg ' + basePositioning + ' space-x';
120
+ return 'fixed flex items-center w-full max-w-md p-4 space-x-4 divide-x rounded-lg ' + basePositioning;
92
121
  }
93
-
94
- const isLoading = ref(true);
95
-
96
- function disable() {
97
- setTimeout(() => isLoading.value = false, 1000000);
98
- }
99
-
100
- disable()
101
122
  </script>
@@ -11,7 +11,7 @@
11
11
  </template>
12
12
 
13
13
  <script setup lang="ts">
14
- import { computed } from 'vue'
14
+ import { ref } from 'vue'
15
15
  import { ColorsEnums } from "../enums/ColorsEnums";
16
16
 
17
17
  const props = defineProps({
@@ -33,9 +33,7 @@ const pastelColors = [
33
33
  'bg-pink-300'
34
34
  ]
35
35
 
36
- const bgColor = computed(() => {
37
- return pastelColors[Math.floor(Math.random() * pastelColors.length)]
38
- })
36
+ const bgColor = ref(pastelColors[Math.floor(Math.random() * pastelColors.length)])
39
37
 
40
38
  function computeCss() {
41
39
  let color = 'gray';