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/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,2 @@
1
+ // Inertia adapter - since Inertia uses Vue, we re-export the Vue adapter
2
+ export * from '../vue';
@@ -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
@@ -0,0 +1,5 @@
1
+ declare module "*.vue" {
2
+ import type { DefineComponent } from "vue";
3
+ const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, any>;
4
+ export default component;
5
+ }
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';