vue-notifyr 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.
- package/LICENCE +21 -0
- package/README.md +226 -0
- package/dist/index.cjs +355 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.js +281 -0
- package/dist/index.js.map +1 -0
- package/dist/style.css +245 -0
- package/package.json +71 -0
- package/src/adapters/inertia/index.ts +2 -0
- package/src/adapters/nuxt/index.ts +24 -0
- package/src/adapters/vue/NotificationContainer.vue +78 -0
- package/src/adapters/vue/index.ts +70 -0
- package/src/composables/useNotification.ts +11 -0
- package/src/core/notification-manager.ts +151 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +27 -0
- package/src/styles/style.css +245 -0
- package/src/types/index.ts +23 -0
package/dist/style.css
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
@import url(https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap);
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--notification-info: #385bbb;
|
|
5
|
+
--notification-info-rgb: 56, 91, 187;
|
|
6
|
+
--notification-info-darker: hsl(224, 54%, 35%);
|
|
7
|
+
--notification-warning: #f3950d;
|
|
8
|
+
--notification-warning-rgb: 243, 149, 13;
|
|
9
|
+
--notification-warning-darker: hsl(35, 91%, 40%);
|
|
10
|
+
--notification-error: #d32f2f;
|
|
11
|
+
--notification-error-rgb: 211, 47, 47;
|
|
12
|
+
--notification-error-darker: hsl(0, 68%, 43%);
|
|
13
|
+
--notification-success: #388e3c;
|
|
14
|
+
--notification-success-rgb: 56, 142, 60;
|
|
15
|
+
--notification-success-darker: hsl(120, 54%, 35%);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@keyframes notificationSlideFromTop {
|
|
19
|
+
0% {
|
|
20
|
+
opacity: 0;
|
|
21
|
+
translate: 0 calc(-100% - 24px);
|
|
22
|
+
}
|
|
23
|
+
100% {
|
|
24
|
+
opacity: 1;
|
|
25
|
+
translate: 0 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@keyframes notificationSlideFromBottom {
|
|
30
|
+
0% {
|
|
31
|
+
opacity: 0;
|
|
32
|
+
translate: 0 calc(100% + 24px);
|
|
33
|
+
}
|
|
34
|
+
100% {
|
|
35
|
+
opacity: 1;
|
|
36
|
+
translate: 0 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@keyframes notificationFadeOut {
|
|
41
|
+
0% {
|
|
42
|
+
opacity: 1;
|
|
43
|
+
}
|
|
44
|
+
100% {
|
|
45
|
+
opacity: 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@keyframes progressBar {
|
|
50
|
+
0% {
|
|
51
|
+
transform: scaleX(1);
|
|
52
|
+
}
|
|
53
|
+
100% {
|
|
54
|
+
transform: scaleX(0);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.notification-list {
|
|
59
|
+
position: fixed;
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 16px;
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
z-index: 99999;
|
|
65
|
+
width: max-content;
|
|
66
|
+
max-width: calc(100vw - 48px);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.notification-list > * {
|
|
70
|
+
pointer-events: auto;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.notification-list[data-position^='top'] {
|
|
74
|
+
top: 24px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.notification-list[data-position^='bottom'] {
|
|
78
|
+
bottom: 24px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.notification-list[data-position$='left'] {
|
|
82
|
+
left: 24px;
|
|
83
|
+
right: auto;
|
|
84
|
+
transform: none;
|
|
85
|
+
align-items: flex-start;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.notification-list[data-position$='center'] {
|
|
89
|
+
left: 50%;
|
|
90
|
+
right: auto;
|
|
91
|
+
transform: translateX(-50%);
|
|
92
|
+
align-items: center;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.notification-list[data-position$='right'] {
|
|
96
|
+
right: 24px;
|
|
97
|
+
left: auto;
|
|
98
|
+
transform: none;
|
|
99
|
+
align-items: flex-end;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.notification {
|
|
103
|
+
position: relative;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 12px;
|
|
107
|
+
padding: 16px 20px;
|
|
108
|
+
padding-right: 50px;
|
|
109
|
+
min-width: 280px;
|
|
110
|
+
max-width: 400px;
|
|
111
|
+
background-color: #fff;
|
|
112
|
+
border-radius: 8px;
|
|
113
|
+
border: 2px solid transparent;
|
|
114
|
+
font-family: 'Nunito Sans', system-ui, helvetica, sans-serif;
|
|
115
|
+
font-size: 15px;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
line-height: 1.4;
|
|
118
|
+
box-shadow:
|
|
119
|
+
0 2px 4px -1px rgba(0, 0, 0, 0.2),
|
|
120
|
+
0 4px 5px 0 rgba(0, 0, 0, 0.14),
|
|
121
|
+
0 1px 10px 0 rgba(0, 0, 0, 0.12);
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
color: var(--notification-info-darker);
|
|
124
|
+
background-clip: padding-box;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.notification-list[data-position^='top'] .notification {
|
|
128
|
+
animation: notificationSlideFromTop 0.5s ease-out forwards;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.notification-list[data-position^='bottom'] .notification {
|
|
132
|
+
animation: notificationSlideFromBottom 0.5s ease-out forwards;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.notification.notification-success {
|
|
136
|
+
color: var(--notification-success);
|
|
137
|
+
border-color: var(--notification-success);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.notification.notification-error {
|
|
141
|
+
color: var(--notification-error);
|
|
142
|
+
border-color: var(--notification-error);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.notification.notification-warning {
|
|
146
|
+
color: var(--notification-warning);
|
|
147
|
+
border-color: var(--notification-warning);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.notification.notification-info {
|
|
151
|
+
color: var(--notification-info);
|
|
152
|
+
border-color: var(--notification-info);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.notification-content {
|
|
156
|
+
display: flex;
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
flex: 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.notification-title {
|
|
162
|
+
font-weight: 600;
|
|
163
|
+
font-size: 15px;
|
|
164
|
+
line-height: 1.4;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.notification-progress {
|
|
168
|
+
position: absolute;
|
|
169
|
+
left: 0;
|
|
170
|
+
right: 0;
|
|
171
|
+
bottom: 0;
|
|
172
|
+
height: 3px;
|
|
173
|
+
border-radius: 50px;
|
|
174
|
+
transform-origin: left;
|
|
175
|
+
background-color: currentColor;
|
|
176
|
+
animation: progressBar linear forwards;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.notification-close {
|
|
180
|
+
display: flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: center;
|
|
183
|
+
position: absolute;
|
|
184
|
+
right: 8px;
|
|
185
|
+
top: 50%;
|
|
186
|
+
transform: translateY(-50%);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.notification-close button {
|
|
190
|
+
padding: 8px;
|
|
191
|
+
margin: 0;
|
|
192
|
+
border: none;
|
|
193
|
+
background-color: transparent;
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
justify-content: center;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
border-radius: 4px;
|
|
199
|
+
transition: background-color 0.2s ease;
|
|
200
|
+
color: inherit;
|
|
201
|
+
font-size: 16px;
|
|
202
|
+
line-height: 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.notification-close button:focus-visible {
|
|
206
|
+
outline: 2px solid currentColor;
|
|
207
|
+
outline-offset: 2px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.notification-close button:hover {
|
|
211
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.notification-enter-active,
|
|
215
|
+
.notification-leave-active {
|
|
216
|
+
transition:
|
|
217
|
+
opacity 0.2s ease,
|
|
218
|
+
transform 0.2s ease;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.notification-enter-from,
|
|
222
|
+
.notification-leave-to {
|
|
223
|
+
opacity: 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.notification-list[data-position^='top'] .notification-enter-from,
|
|
227
|
+
.notification-list[data-position^='top'] .notification-leave-to {
|
|
228
|
+
transform: translateY(-16px);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.notification-list[data-position^='bottom'] .notification-enter-from,
|
|
232
|
+
.notification-list[data-position^='bottom'] .notification-leave-to {
|
|
233
|
+
transform: translateY(16px);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.notification-move {
|
|
237
|
+
transition: transform 0.2s ease;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@media (max-width: 480px) {
|
|
241
|
+
.notification {
|
|
242
|
+
min-width: calc(100vw - 48px);
|
|
243
|
+
max-width: calc(100vw - 48px);
|
|
244
|
+
}
|
|
245
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-notifyr",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic notification library with Vue, Nuxt, and Inertia support",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./style.css": "./dist/style.css",
|
|
16
|
+
"./vue": "./dist/adapters/vue/index.js",
|
|
17
|
+
"./nuxt": "./dist/adapters/nuxt/index.js",
|
|
18
|
+
"./inertia": "./dist/adapters/inertia/index.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "vite build && node scripts/copy-css.js",
|
|
26
|
+
"dev": "vite build --watch",
|
|
27
|
+
"lint": "eslint src --ext .ts,.vue",
|
|
28
|
+
"lint:fix": "eslint src --ext .ts,.vue --fix",
|
|
29
|
+
"format": "prettier --write src/**/*.{ts,vue,css}",
|
|
30
|
+
"type-check": "tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"vue": "^3.3.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"vue": {
|
|
37
|
+
"optional": true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"author": "Rayhan Bapari <mdrayhanbapari02@gmail.com>",
|
|
42
|
+
"keywords": [
|
|
43
|
+
"vue",
|
|
44
|
+
"nuxt",
|
|
45
|
+
"inertia",
|
|
46
|
+
"notification",
|
|
47
|
+
"toast",
|
|
48
|
+
"typescript",
|
|
49
|
+
"framework-agnostic"
|
|
50
|
+
],
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/rayhan-bapari/vue-notifyr"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/rayhan-bapari/vue-notifyr/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/rayhan-bapari/vue-notifyr#readme",
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^20.0.0",
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
62
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
63
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
64
|
+
"eslint": "^8.0.0",
|
|
65
|
+
"prettier": "^3.0.0",
|
|
66
|
+
"typescript": "^5.0.0",
|
|
67
|
+
"vite": "^5.0.0",
|
|
68
|
+
"vite-plugin-dts": "^3.0.0",
|
|
69
|
+
"vue": "^3.3.0"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { notificationManager } from '../../core/notification-manager';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Nuxt plugin for notification management.
|
|
5
|
+
* Integrates the notification manager into Nuxt applications.
|
|
6
|
+
*/
|
|
7
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
8
|
+
nuxtApp.vueApp.use(createVueNotificationPlugin(notificationManager));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create Vue plugin for Nuxt (internal use).
|
|
13
|
+
*/
|
|
14
|
+
function createVueNotificationPlugin(manager: any) {
|
|
15
|
+
return {
|
|
16
|
+
install(app: any) {
|
|
17
|
+
app.config.globalProperties.$notificationManager = manager;
|
|
18
|
+
app.provide('notificationManager', manager);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Re-export Vue adapter functions for convenience
|
|
24
|
+
export { useNotificationManager, createReactiveNotificationStore, notificationStore } from '../vue';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue';
|
|
3
|
+
import { notificationStore } from './index';
|
|
4
|
+
import { notificationManager } from '../../core/notification-manager';
|
|
5
|
+
import type { NotificationItem, NotificationPosition } from '../../types';
|
|
6
|
+
|
|
7
|
+
const isClient = ref(false);
|
|
8
|
+
|
|
9
|
+
if (typeof window !== 'undefined') {
|
|
10
|
+
isClient.value = true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const groups = computed<Record<NotificationPosition, NotificationItem[]>>(() => {
|
|
14
|
+
const map: Record<NotificationPosition, NotificationItem[]> = {
|
|
15
|
+
'top-left': [],
|
|
16
|
+
'top-center': [],
|
|
17
|
+
'top-right': [],
|
|
18
|
+
'bottom-left': [],
|
|
19
|
+
'bottom-center': [],
|
|
20
|
+
'bottom-right': [],
|
|
21
|
+
};
|
|
22
|
+
for (const notification of notificationStore.notifications) {
|
|
23
|
+
map[notification.options.position].push(notification);
|
|
24
|
+
}
|
|
25
|
+
return map;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function removeNotification(id: number) {
|
|
29
|
+
notificationManager.remove(id);
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<Teleport v-if="isClient" to="body">
|
|
35
|
+
<TransitionGroup
|
|
36
|
+
v-for="(items, pos) in groups"
|
|
37
|
+
:key="pos"
|
|
38
|
+
tag="div"
|
|
39
|
+
name="notification"
|
|
40
|
+
class="notification-list"
|
|
41
|
+
:data-position="pos"
|
|
42
|
+
>
|
|
43
|
+
<article
|
|
44
|
+
v-for="notification in items"
|
|
45
|
+
:key="notification.id"
|
|
46
|
+
class="notification"
|
|
47
|
+
:class="[
|
|
48
|
+
`notification-${notification.type}`,
|
|
49
|
+
{
|
|
50
|
+
'notification-auto-close':
|
|
51
|
+
notification.options.progress && notification.options.autoClose !== false,
|
|
52
|
+
},
|
|
53
|
+
]"
|
|
54
|
+
:data-position="pos"
|
|
55
|
+
>
|
|
56
|
+
<div class="notification-content">
|
|
57
|
+
<span class="notification-title">{{ notification.title }}</span>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div
|
|
61
|
+
v-if="notification.options.progress && notification.options.autoClose !== false"
|
|
62
|
+
class="notification-progress"
|
|
63
|
+
:style="{ animationDuration: (notification.options.autoClose || 0) + 'ms' }"
|
|
64
|
+
></div>
|
|
65
|
+
|
|
66
|
+
<div class="notification-close">
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
@click="removeNotification(notification.id)"
|
|
70
|
+
aria-label="Dismiss notification"
|
|
71
|
+
>
|
|
72
|
+
×
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
</article>
|
|
76
|
+
</TransitionGroup>
|
|
77
|
+
</Teleport>
|
|
78
|
+
</template>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { reactive, watchEffect } from 'vue';
|
|
2
|
+
import { NotificationManager, notificationManager } from '../../core/notification-manager';
|
|
3
|
+
import type { NotificationItem, NotificationOptions } from '../../types';
|
|
4
|
+
|
|
5
|
+
export interface ReactiveNotificationStore {
|
|
6
|
+
notifications: NotificationItem[];
|
|
7
|
+
options: NotificationOptions;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a reactive store for Vue that syncs with the NotificationManager.
|
|
12
|
+
* @param manager - The NotificationManager instance (optional, uses default if not provided)
|
|
13
|
+
* @returns Reactive store
|
|
14
|
+
*/
|
|
15
|
+
export function createReactiveNotificationStore(
|
|
16
|
+
manager: NotificationManager = notificationManager
|
|
17
|
+
): ReactiveNotificationStore {
|
|
18
|
+
const store = reactive({
|
|
19
|
+
notifications: manager.getNotifications(),
|
|
20
|
+
options: manager.getOptions(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Sync notifications
|
|
24
|
+
manager.on('notification-added', () => {
|
|
25
|
+
store.notifications = manager.getNotifications();
|
|
26
|
+
});
|
|
27
|
+
manager.on('notification-removed', () => {
|
|
28
|
+
store.notifications = manager.getNotifications();
|
|
29
|
+
});
|
|
30
|
+
manager.on('notifications-cleared', () => {
|
|
31
|
+
store.notifications = manager.getNotifications();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Sync options
|
|
35
|
+
manager.on('options-changed', () => {
|
|
36
|
+
store.options = manager.getOptions();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return store;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Default reactive store
|
|
43
|
+
export const notificationStore = createReactiveNotificationStore();
|
|
44
|
+
|
|
45
|
+
// Vue composable
|
|
46
|
+
export function useNotificationManager(manager: NotificationManager = notificationManager) {
|
|
47
|
+
return {
|
|
48
|
+
success: (title: string, opts?: NotificationOptions) => manager.success(title, opts),
|
|
49
|
+
error: (title: string, opts?: NotificationOptions) => manager.error(title, opts),
|
|
50
|
+
warning: (title: string, opts?: NotificationOptions) => manager.warning(title, opts),
|
|
51
|
+
info: (title: string, opts?: NotificationOptions) => manager.info(title, opts),
|
|
52
|
+
show: (type: string, title: string, opts?: NotificationOptions) =>
|
|
53
|
+
manager.show(type as any, title, opts),
|
|
54
|
+
remove: (id: number) => manager.remove(id),
|
|
55
|
+
clear: () => manager.clear(),
|
|
56
|
+
setDefaults: (opts: NotificationOptions) => manager.setDefaults(opts),
|
|
57
|
+
getNotifications: () => manager.getNotifications(),
|
|
58
|
+
getOptions: () => manager.getOptions(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Vue plugin
|
|
63
|
+
export function createVueNotificationPlugin(manager: NotificationManager = notificationManager) {
|
|
64
|
+
return {
|
|
65
|
+
install(app: any) {
|
|
66
|
+
app.config.globalProperties.$notificationManager = manager;
|
|
67
|
+
app.provide('notificationManager', manager);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useNotificationManager } from '../adapters/vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vue composable for notification management.
|
|
5
|
+
* Provides a clean API to show notifications in Vue components.
|
|
6
|
+
*
|
|
7
|
+
* @returns Notification manager API
|
|
8
|
+
*/
|
|
9
|
+
export function useNotification() {
|
|
10
|
+
return useNotificationManager();
|
|
11
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { NotificationType, NotificationOptions, NotificationItem } from '../types';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_OPTIONS: Required<NotificationOptions> = {
|
|
4
|
+
position: 'top-center',
|
|
5
|
+
autoClose: 3000,
|
|
6
|
+
progress: true,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class NotificationManager {
|
|
10
|
+
private notifications: NotificationItem[] = [];
|
|
11
|
+
private options: Required<NotificationOptions> = { ...DEFAULT_OPTIONS };
|
|
12
|
+
private listeners: { [event: string]: Function[] } = {};
|
|
13
|
+
private idCounter = 1;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Subscribe to an event.
|
|
17
|
+
* @param event - The event name ('notification-added', 'notification-removed', 'notifications-cleared', 'options-changed')
|
|
18
|
+
* @param callback - The callback function
|
|
19
|
+
*/
|
|
20
|
+
on(event: string, callback: Function): void {
|
|
21
|
+
if (!this.listeners[event]) {
|
|
22
|
+
this.listeners[event] = [];
|
|
23
|
+
}
|
|
24
|
+
this.listeners[event].push(callback);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Unsubscribe from an event.
|
|
29
|
+
* @param event - The event name
|
|
30
|
+
* @param callback - The callback function to remove
|
|
31
|
+
*/
|
|
32
|
+
off(event: string, callback: Function): void {
|
|
33
|
+
if (this.listeners[event]) {
|
|
34
|
+
this.listeners[event] = this.listeners[event].filter((cb) => cb !== callback);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private emit(event: string, data?: any): void {
|
|
39
|
+
if (this.listeners[event]) {
|
|
40
|
+
this.listeners[event].forEach((callback) => callback(data));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Set default options for notifications.
|
|
46
|
+
* @param opts - The options to set as defaults
|
|
47
|
+
*/
|
|
48
|
+
setDefaults(opts: NotificationOptions): void {
|
|
49
|
+
this.options = { ...this.options, ...opts };
|
|
50
|
+
this.emit('options-changed', this.options);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Show a notification.
|
|
55
|
+
* @param type - The type of notification
|
|
56
|
+
* @param title - The title/message of the notification
|
|
57
|
+
* @param opts - Optional override options
|
|
58
|
+
*/
|
|
59
|
+
show(type: NotificationType, title: string, opts?: NotificationOptions): void {
|
|
60
|
+
const options = { ...this.options, ...(opts || {}) };
|
|
61
|
+
const item: NotificationItem = {
|
|
62
|
+
id: this.idCounter++,
|
|
63
|
+
type,
|
|
64
|
+
title,
|
|
65
|
+
options,
|
|
66
|
+
createdAt: Date.now(),
|
|
67
|
+
};
|
|
68
|
+
this.notifications.push(item);
|
|
69
|
+
this.emit('notification-added', item);
|
|
70
|
+
|
|
71
|
+
if (options.autoClose !== false && typeof window !== 'undefined') {
|
|
72
|
+
setTimeout(() => this.remove(item.id), options.autoClose);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Remove a notification by ID.
|
|
78
|
+
* @param id - The ID of the notification to remove
|
|
79
|
+
*/
|
|
80
|
+
remove(id: number): void {
|
|
81
|
+
const index = this.notifications.findIndex((n) => n.id === id);
|
|
82
|
+
if (index !== -1) {
|
|
83
|
+
const removed = this.notifications.splice(index, 1)[0];
|
|
84
|
+
this.emit('notification-removed', removed);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Clear all notifications.
|
|
90
|
+
*/
|
|
91
|
+
clear(): void {
|
|
92
|
+
const removed = [...this.notifications];
|
|
93
|
+
this.notifications.splice(0);
|
|
94
|
+
this.emit('notifications-cleared', removed);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get a copy of the current notifications.
|
|
99
|
+
* @returns Array of notification items
|
|
100
|
+
*/
|
|
101
|
+
getNotifications(): NotificationItem[] {
|
|
102
|
+
return [...this.notifications];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the current default options.
|
|
107
|
+
* @returns The default options
|
|
108
|
+
*/
|
|
109
|
+
getOptions(): Required<NotificationOptions> {
|
|
110
|
+
return { ...this.options };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Success notification shorthand.
|
|
115
|
+
* @param title - The title/message
|
|
116
|
+
* @param opts - Optional options
|
|
117
|
+
*/
|
|
118
|
+
success(title: string, opts?: NotificationOptions): void {
|
|
119
|
+
this.show('success', title, opts);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Error notification shorthand.
|
|
124
|
+
* @param title - The title/message
|
|
125
|
+
* @param opts - Optional options
|
|
126
|
+
*/
|
|
127
|
+
error(title: string, opts?: NotificationOptions): void {
|
|
128
|
+
this.show('error', title, opts);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Warning notification shorthand.
|
|
133
|
+
* @param title - The title/message
|
|
134
|
+
* @param opts - Optional options
|
|
135
|
+
*/
|
|
136
|
+
warning(title: string, opts?: NotificationOptions): void {
|
|
137
|
+
this.show('warning', title, opts);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Info notification shorthand.
|
|
142
|
+
* @param title - The title/message
|
|
143
|
+
* @param opts - Optional options
|
|
144
|
+
*/
|
|
145
|
+
info(title: string, opts?: NotificationOptions): void {
|
|
146
|
+
this.show('info', title, opts);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Default instance for convenience
|
|
151
|
+
export const notificationManager = new NotificationManager();
|
package/src/env.d.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Core exports
|
|
2
|
+
export { NotificationManager, notificationManager } from './core/notification-manager';
|
|
3
|
+
export type {
|
|
4
|
+
NotificationType,
|
|
5
|
+
NotificationPosition,
|
|
6
|
+
NotificationOptions,
|
|
7
|
+
NotificationItem,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
// Vue adapter
|
|
11
|
+
export {
|
|
12
|
+
useNotificationManager,
|
|
13
|
+
createReactiveNotificationStore,
|
|
14
|
+
notificationStore,
|
|
15
|
+
createVueNotificationPlugin,
|
|
16
|
+
} from './adapters/vue';
|
|
17
|
+
export { default as NotificationContainer } from './adapters/vue/NotificationContainer.vue';
|
|
18
|
+
|
|
19
|
+
// Composables
|
|
20
|
+
export { useNotification } from './composables/useNotification';
|
|
21
|
+
|
|
22
|
+
// Adapters for other frameworks
|
|
23
|
+
export * from './adapters/nuxt';
|
|
24
|
+
export * from './adapters/inertia';
|
|
25
|
+
|
|
26
|
+
// Styles
|
|
27
|
+
import './styles/style.css';
|