@mi1y/toast-mi1y 0.0.1
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/components/toast.svelte +138 -0
- package/dist/components/toast.svelte.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/toast.css +2 -0
- package/dist/toasts/interfaces.d.ts +15 -0
- package/dist/toasts/interfaces.js +1 -0
- package/dist/toasts/store.d.ts +22 -0
- package/dist/toasts/store.js +78 -0
- package/package.json +98 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import '../toast.css';
|
|
3
|
+
import { toast } from "../toasts/store.js";
|
|
4
|
+
import type { Toast } from "../toasts/interfaces.js";
|
|
5
|
+
import { fly } from 'svelte/transition';
|
|
6
|
+
|
|
7
|
+
let hoveredToastId= $state<string | null>(null);
|
|
8
|
+
let pausedToasts = $state<Map<string, {pauseTime: number, remainingDuration: number}>>(new Map());
|
|
9
|
+
|
|
10
|
+
const toastsStore = toast.toasts;
|
|
11
|
+
function getToastsStyles(toastType: string) {
|
|
12
|
+
switch (toastType) {
|
|
13
|
+
case 'success':
|
|
14
|
+
return 'bg-emerald-800 border-emerald-700 text-emerald-100';
|
|
15
|
+
case 'error':
|
|
16
|
+
return 'bg-red-800 border-red-700 text-red-100';
|
|
17
|
+
case 'warning':
|
|
18
|
+
return 'bg-orange-800 border-orange-700 text-orange-100';
|
|
19
|
+
case 'info':
|
|
20
|
+
return 'bg-blue-800 border-blue-700 text-blue-100';
|
|
21
|
+
default:
|
|
22
|
+
return 'bg-slate-800 border-slate-700 text-slate-100';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getIconPath(toastType: string) {
|
|
26
|
+
switch (toastType) {
|
|
27
|
+
case 'success':
|
|
28
|
+
return 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z';
|
|
29
|
+
case 'error':
|
|
30
|
+
return 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z';
|
|
31
|
+
case 'warning':
|
|
32
|
+
return 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16c-.77.833.192 2.5 1.732 2.5z';
|
|
33
|
+
case 'info':
|
|
34
|
+
return 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
|
|
35
|
+
default:
|
|
36
|
+
return 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function pauseTimer(toastId: string) {
|
|
41
|
+
hoveredToastId = toastId;
|
|
42
|
+
|
|
43
|
+
if (!pausedToasts.has(toastId)) {
|
|
44
|
+
const toastItem = $toastsStore.find((t: Toast) => t.id === toastId);
|
|
45
|
+
if (toastItem && toastItem.duration > 0) {
|
|
46
|
+
const remainingDuration = toastItem.duration;
|
|
47
|
+
pausedToasts.set(toastId, {
|
|
48
|
+
pauseTime: Date.now(),
|
|
49
|
+
remainingDuration
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
toast.pause(toastId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resumeTimer(toastId: string) {
|
|
58
|
+
hoveredToastId = null;
|
|
59
|
+
|
|
60
|
+
const pausedInfo = pausedToasts.get(toastId);
|
|
61
|
+
if (pausedInfo) {
|
|
62
|
+
toast.resume(toastId, pausedInfo.remainingDuration);
|
|
63
|
+
pausedToasts.delete(toastId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<div class="fixed bottom-4 right-4 z-50 space-y-3 max-w-sm">
|
|
69
|
+
{#each $toastsStore as toastItem (toastItem.id)}
|
|
70
|
+
<div
|
|
71
|
+
class="toast-container border rounded-lg shadow-lg backdrop-blur-sm transition-all duration-300 {getToastsStyles(toastItem.type)}"
|
|
72
|
+
class:hovered={hoveredToastId === toastItem.id}
|
|
73
|
+
in:fly={{ x: 300, duration: 300 }}
|
|
74
|
+
out:fly={{ x: 300, duration: toastItem.isConfirm ? 0 : 300 }}
|
|
75
|
+
role="alert"
|
|
76
|
+
onmouseenter={() => pauseTimer(toastItem.id)}
|
|
77
|
+
onmouseleave={() => resumeTimer(toastItem.id)}
|
|
78
|
+
>
|
|
79
|
+
<div class="flex items-start gap-3 p-4">
|
|
80
|
+
<svg class="w-5 h-5 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
81
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={getIconPath(toastItem.type)}></path>
|
|
82
|
+
</svg>
|
|
83
|
+
|
|
84
|
+
<div class="flex-1 min-w-0">
|
|
85
|
+
<p class="text-sm font-medium wrap-break-words">{toastItem.message}</p>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<button
|
|
89
|
+
class="text-current opacity-70 hover:opacity-100 transition-opacity shrink-0"
|
|
90
|
+
aria-label="Close notification"
|
|
91
|
+
onclick={() => toast.remove(toastItem.id)}
|
|
92
|
+
>
|
|
93
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
94
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
95
|
+
</svg>
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{#if toastItem.isConfirm}
|
|
100
|
+
<div class="flex justify-end gap-2 px-4 pb-4 border-t border-current/20 pt-3">
|
|
101
|
+
<button
|
|
102
|
+
class="px-3 py-1.5 text-xs font-medium bg-white/20 hover:bg-white/30 rounded transition-colors"
|
|
103
|
+
onclick={toastItem.onConfirm}
|
|
104
|
+
>
|
|
105
|
+
Confirm
|
|
106
|
+
</button>
|
|
107
|
+
<button
|
|
108
|
+
class="px-3 py-1.5 text-xs font-medium bg-black/20 hover:bg-black/30 rounded transition-colors"
|
|
109
|
+
onclick={toastItem.onCancel}
|
|
110
|
+
>
|
|
111
|
+
Cancel
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
{/if}
|
|
115
|
+
|
|
116
|
+
{#if toastItem.duration && toastItem.duration > 0 && !toastItem.isConfirm}
|
|
117
|
+
<div class="h-1 bg-black/20 rounded-b-lg overflow-hidden">
|
|
118
|
+
<div
|
|
119
|
+
class="h-full bg-white/40 rounded-b-lg progress-bar"
|
|
120
|
+
class:paused={hoveredToastId === toastItem.id}
|
|
121
|
+
style="animation: shrink {toastItem.duration}ms linear forwards;"
|
|
122
|
+
></div>
|
|
123
|
+
</div>
|
|
124
|
+
{/if}
|
|
125
|
+
</div>
|
|
126
|
+
{/each}
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<style>
|
|
130
|
+
.toast-container.hovered {
|
|
131
|
+
transform: scale(1.02);
|
|
132
|
+
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.3);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.progress-bar {
|
|
136
|
+
transform-origin: left;
|
|
137
|
+
}
|
|
138
|
+
</style>
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/toast.css
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ToastType = "success" | "error" | "info" | "warning";
|
|
2
|
+
export interface Toast {
|
|
3
|
+
id: string;
|
|
4
|
+
message: string;
|
|
5
|
+
type: ToastType;
|
|
6
|
+
duration: number;
|
|
7
|
+
isConfirm?: boolean;
|
|
8
|
+
onConfirm?: () => void;
|
|
9
|
+
onCancel?: () => void;
|
|
10
|
+
}
|
|
11
|
+
export interface CreateToastData {
|
|
12
|
+
message: string;
|
|
13
|
+
type: ToastType;
|
|
14
|
+
duration: number;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Toast, CreateToastData } from './interfaces.js';
|
|
2
|
+
declare class ToastStore {
|
|
3
|
+
private _toasts;
|
|
4
|
+
private activeTimers;
|
|
5
|
+
private _defaultDuration;
|
|
6
|
+
get toasts(): {
|
|
7
|
+
subscribe: (this: void, run: import("svelte/store").Subscriber<Toast[]>, invalidate?: () => void) => import("svelte/store").Unsubscriber;
|
|
8
|
+
set: (this: void, value: Toast[]) => void;
|
|
9
|
+
update: (this: void, updater: import("svelte/store").Updater<Toast[]>) => void;
|
|
10
|
+
};
|
|
11
|
+
show(data: CreateToastData): `${string}-${string}-${string}-${string}-${string}`;
|
|
12
|
+
remove(toastId: string): void;
|
|
13
|
+
success(message: string, duration?: number): `${string}-${string}-${string}-${string}-${string}`;
|
|
14
|
+
error(message: string, duration?: number): `${string}-${string}-${string}-${string}-${string}`;
|
|
15
|
+
info(message: string, duration?: number): `${string}-${string}-${string}-${string}-${string}`;
|
|
16
|
+
warning(message: string, duration?: number): `${string}-${string}-${string}-${string}-${string}`;
|
|
17
|
+
pause(toastId: string): void;
|
|
18
|
+
resume(toastId: string, remainingDuration: number): void;
|
|
19
|
+
confirm(message: string): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
export declare const toast: ToastStore;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
class ToastStore {
|
|
3
|
+
_toasts = writable([]);
|
|
4
|
+
activeTimers = new Map();
|
|
5
|
+
_defaultDuration = 3000;
|
|
6
|
+
get toasts() {
|
|
7
|
+
return {
|
|
8
|
+
subscribe: this._toasts.subscribe,
|
|
9
|
+
set: this._toasts.set,
|
|
10
|
+
update: this._toasts.update
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
show(data) {
|
|
14
|
+
const id = crypto.randomUUID();
|
|
15
|
+
const toast = {
|
|
16
|
+
id,
|
|
17
|
+
message: data.message,
|
|
18
|
+
type: data.type,
|
|
19
|
+
duration: data.duration ?? this._defaultDuration,
|
|
20
|
+
};
|
|
21
|
+
this._toasts.update(toasts => [...toasts, toast]);
|
|
22
|
+
this.activeTimers.set(id, setTimeout(() => this.remove(id), toast.duration));
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
remove(toastId) {
|
|
26
|
+
const timerId = this.activeTimers.get(toastId);
|
|
27
|
+
if (timerId) {
|
|
28
|
+
clearTimeout(timerId);
|
|
29
|
+
this.activeTimers.delete(toastId);
|
|
30
|
+
}
|
|
31
|
+
this._toasts.update(toasts => toasts.filter(toast => toast.id !== toastId));
|
|
32
|
+
}
|
|
33
|
+
success(message, duration) {
|
|
34
|
+
return this.show({ message, type: 'success', duration: duration ?? this._defaultDuration });
|
|
35
|
+
}
|
|
36
|
+
error(message, duration) {
|
|
37
|
+
return this.show({ message, type: 'error', duration: duration ?? this._defaultDuration });
|
|
38
|
+
}
|
|
39
|
+
info(message, duration) {
|
|
40
|
+
return this.show({ message, type: 'info', duration: duration ?? this._defaultDuration });
|
|
41
|
+
}
|
|
42
|
+
warning(message, duration) {
|
|
43
|
+
return this.show({ message, type: 'warning', duration: duration ?? this._defaultDuration });
|
|
44
|
+
}
|
|
45
|
+
pause(toastId) {
|
|
46
|
+
const timerId = this.activeTimers.get(toastId);
|
|
47
|
+
if (timerId) {
|
|
48
|
+
clearTimeout(timerId);
|
|
49
|
+
this.activeTimers.delete(toastId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
resume(toastId, remainingDuration) {
|
|
53
|
+
const newTimerId = setTimeout(() => this.remove(toastId), remainingDuration);
|
|
54
|
+
this.activeTimers.set(toastId, newTimerId);
|
|
55
|
+
}
|
|
56
|
+
confirm(message) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const id = crypto.randomUUID();
|
|
59
|
+
const toast = {
|
|
60
|
+
id,
|
|
61
|
+
message,
|
|
62
|
+
type: 'info',
|
|
63
|
+
duration: 0,
|
|
64
|
+
isConfirm: true,
|
|
65
|
+
onConfirm: () => {
|
|
66
|
+
resolve(true);
|
|
67
|
+
this.remove(id);
|
|
68
|
+
},
|
|
69
|
+
onCancel: () => {
|
|
70
|
+
resolve(false);
|
|
71
|
+
this.remove(id);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
this._toasts.update(toasts => [...toasts, toast]);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export const toast = new ToastStore();
|
package/package.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mi1y/toast-mi1y",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A simple and flexible toast notification library for Svelte 5",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"svelte",
|
|
7
|
+
"svelte5",
|
|
8
|
+
"sveltekit",
|
|
9
|
+
"toast",
|
|
10
|
+
"notification",
|
|
11
|
+
"ui",
|
|
12
|
+
"alert"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/Mi1y/toast-mi1y#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/Mi1y/toast-mi1y/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Mi1y/toast-mi1y.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "mi1y",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"svelte": "./dist/index.js",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./styles": {
|
|
32
|
+
"default": "./dist/toast.css"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"!dist/**/*.test.*",
|
|
39
|
+
"!dist/**/*.spec.*"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"dev": "vite dev",
|
|
43
|
+
"build": "vite build && npm run package",
|
|
44
|
+
"preview": "vite preview",
|
|
45
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
46
|
+
"package": "svelte-kit sync && svelte-package && publint",
|
|
47
|
+
"prepublishOnly": "npm run package",
|
|
48
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
49
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
50
|
+
"lint": "prettier --check . && eslint .",
|
|
51
|
+
"format": "prettier --write ."
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public",
|
|
55
|
+
"registry": "https://registry.npmjs.org"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@eslint/compat": "^2.0.1",
|
|
59
|
+
"@eslint/js": "^9.39.2",
|
|
60
|
+
"@sveltejs/adapter-auto": "^7.0.0",
|
|
61
|
+
"@sveltejs/kit": "^2.50.1",
|
|
62
|
+
"@sveltejs/package": "^2.5.7",
|
|
63
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
64
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
65
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
66
|
+
"@types/node": "^22",
|
|
67
|
+
"eslint": "^9.39.2",
|
|
68
|
+
"eslint-config-prettier": "^10.1.8",
|
|
69
|
+
"eslint-plugin-svelte": "^3.14.0",
|
|
70
|
+
"globals": "^17.1.0",
|
|
71
|
+
"prettier": "^3.8.1",
|
|
72
|
+
"prettier-plugin-svelte": "^3.4.1",
|
|
73
|
+
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
74
|
+
"publint": "^0.3.17",
|
|
75
|
+
"svelte": "^5.48.2",
|
|
76
|
+
"svelte-check": "^4.3.5",
|
|
77
|
+
"tailwindcss": "^4.1.18",
|
|
78
|
+
"typescript": "^5.9.3",
|
|
79
|
+
"typescript-eslint": "^8.53.1",
|
|
80
|
+
"vite": "^7.3.1"
|
|
81
|
+
},
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"svelte": "^5.0.0",
|
|
84
|
+
"tailwindcss": "^4.0.0"
|
|
85
|
+
},
|
|
86
|
+
"peerDependenciesMeta": {
|
|
87
|
+
"tailwindcss": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"sideEffects": [
|
|
92
|
+
"**/*.css"
|
|
93
|
+
],
|
|
94
|
+
"svelte": "./dist/index.js",
|
|
95
|
+
"engines": {
|
|
96
|
+
"node": ">=18.0.0"
|
|
97
|
+
}
|
|
98
|
+
}
|