@samline/notify 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/CHANGELOG.md +9 -0
- package/README.md +128 -0
- package/dist/core/index.d.ts +47 -0
- package/dist/core/index.test.d.ts +1 -0
- package/dist/index.cjs.js +1131 -0
- package/dist/index.esm.js +1124 -0
- package/dist/notifications.umd.js +1133 -0
- package/dist/notify.umd.js +1137 -0
- package/dist/react/Toaster.d.ts +6 -0
- package/dist/react/index.d.ts +10 -0
- package/dist/samline.notifications.umd.js +1131 -0
- package/dist/styles.css +49 -0
- package/dist/svelte/store.d.ts +3 -0
- package/dist/vanilla/index.d.ts +13 -0
- package/dist/vanilla/toasterManager.d.ts +6 -0
- package/dist/vue/index.d.ts +20 -0
- package/docs/browser.md +44 -0
- package/docs/react.md +64 -0
- package/docs/svelte.md +50 -0
- package/docs/vanilla.md +79 -0
- package/docs/vue.md +41 -0
- package/examples/no-bundler/index.html +25 -0
- package/package.json +55 -0
- package/rollup.config.js +39 -0
- package/src/core/index.test.ts +26 -0
- package/src/core/index.ts +122 -0
- package/src/react/Toaster.tsx +67 -0
- package/src/react/index.ts +13 -0
- package/src/styles/sileo.css +49 -0
- package/src/svelte/Toaster.svelte +69 -0
- package/src/svelte/store.ts +9 -0
- package/src/vanilla/index.ts +22 -0
- package/src/vanilla/toasterManager.ts +132 -0
- package/src/vue/index.ts +61 -0
- package/test/smoke/no-bundler.test.ts +10 -0
- package/test/smoke/svelte.test.ts +12 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import type { ToastItem } from '../core/index';
|
|
3
|
+
import { sileo } from '../core/index';
|
|
4
|
+
import { animate } from 'motion';
|
|
5
|
+
|
|
6
|
+
export interface ReactToasterProps {
|
|
7
|
+
position?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const Toaster: React.FC<ReactToasterProps> = ({ position = 'top-right' }) => {
|
|
11
|
+
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
|
12
|
+
const lastSnapshot = React.useRef<string[]>([]);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const unsub = sileo.subscribe((items) => setToasts(items.filter((t) => (t.options.position || 'top-right') === position)));
|
|
16
|
+
return () => { unsub(); };
|
|
17
|
+
}, [position]);
|
|
18
|
+
|
|
19
|
+
const handleDismiss = async (id: string) => {
|
|
20
|
+
const el = document.querySelector(`[data-id="${id}"]`) as HTMLElement | null;
|
|
21
|
+
if (el) {
|
|
22
|
+
try {
|
|
23
|
+
const a = animate(el, { opacity: [1, 0], transform: ['translateY(0px)', 'translateY(-8px)'] }, { duration: 0.15 });
|
|
24
|
+
await (a as any).finished;
|
|
25
|
+
} catch (e) { /* ignore */ }
|
|
26
|
+
}
|
|
27
|
+
sileo.dismiss(id);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// animate in newly added toasts
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const ids = toasts.map(t => t.id);
|
|
33
|
+
const added = ids.filter(id => !lastSnapshot.current.includes(id));
|
|
34
|
+
added.forEach(id => {
|
|
35
|
+
const el = document.querySelector(`[data-id="${id}"]`) as HTMLElement | null;
|
|
36
|
+
if (el) {
|
|
37
|
+
// set initial state then animate
|
|
38
|
+
el.style.opacity = '0';
|
|
39
|
+
el.style.transform = 'translateY(-6px)';
|
|
40
|
+
try {
|
|
41
|
+
const a = animate(el, { opacity: [0, 1], transform: ['translateY(-6px)', 'translateY(0px)'] }, { duration: 0.22 });
|
|
42
|
+
// ignore finished
|
|
43
|
+
} catch (e) {}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
lastSnapshot.current = ids;
|
|
47
|
+
}, [toasts]);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="sileo-toaster" data-position={position}>
|
|
51
|
+
{toasts.map((t) => (
|
|
52
|
+
<div key={t.id} className="sileo-toast" data-type={t.options.type} data-id={t.id}>
|
|
53
|
+
<div style={{ flex: 1 }}>
|
|
54
|
+
<div className="sileo-toast-header">{t.options.title}</div>
|
|
55
|
+
{t.options.description && <div className="sileo-toast-desc">{t.options.description}</div>}
|
|
56
|
+
</div>
|
|
57
|
+
{t.options.button && (
|
|
58
|
+
<button className="sileo-toast-btn" onClick={(e) => { e.stopPropagation(); t.options.button!.onClick(); }}>{t.options.button.title}</button>
|
|
59
|
+
)}
|
|
60
|
+
<button className="sileo-toast-close" onClick={() => handleDismiss(t.id)}>×</button>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default Toaster;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { notify, sileo } from '../core/index';
|
|
2
|
+
export { Toaster } from './Toaster';
|
|
3
|
+
|
|
4
|
+
import { notify as coreNotify, sileo as coreSileo } from '../core/index';
|
|
5
|
+
|
|
6
|
+
type ReactAPI = {
|
|
7
|
+
notify: typeof coreNotify;
|
|
8
|
+
sileo: typeof coreSileo;
|
|
9
|
+
Toaster?: any;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const defaultExport: ReactAPI = { notify: coreNotify, sileo: coreSileo };
|
|
13
|
+
export default defaultExport;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
:root{
|
|
2
|
+
--sileo-bg: #0f1724;
|
|
3
|
+
--sileo-foreground: #ffffff;
|
|
4
|
+
--sileo-success: #16a34a;
|
|
5
|
+
--sileo-error: #ef4444;
|
|
6
|
+
--sileo-info: #2563eb;
|
|
7
|
+
--sileo-warning: #f59e0b;
|
|
8
|
+
--sileo-gap: 12px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.sileo-toaster{
|
|
12
|
+
position: fixed;
|
|
13
|
+
z-index: 9999;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: var(--sileo-gap);
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.sileo-toaster[data-position="top-right"]{ top: 16px; right: 16px; align-items:flex-end; }
|
|
21
|
+
.sileo-toaster[data-position="top-left"]{ top: 16px; left: 16px; align-items:flex-start; }
|
|
22
|
+
.sileo-toaster[data-position="bottom-right"]{ bottom: 16px; right: 16px; align-items:flex-end; }
|
|
23
|
+
.sileo-toaster[data-position="bottom-left"]{ bottom: 16px; left: 16px; align-items:flex-start; }
|
|
24
|
+
|
|
25
|
+
.sileo-toast{
|
|
26
|
+
pointer-events: auto;
|
|
27
|
+
min-width: 220px;
|
|
28
|
+
max-width: 360px;
|
|
29
|
+
background: var(--sileo-bg);
|
|
30
|
+
color: var(--sileo-foreground);
|
|
31
|
+
padding: 12px 14px;
|
|
32
|
+
border-radius: 10px;
|
|
33
|
+
box-shadow: 0 6px 18px rgba(2,6,23,0.6);
|
|
34
|
+
display: flex;
|
|
35
|
+
gap: 8px;
|
|
36
|
+
align-items: center;
|
|
37
|
+
position: relative;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sileo-toast[data-type="success"]{ border-left: 4px solid var(--sileo-success); }
|
|
42
|
+
.sileo-toast[data-type="error"]{ border-left: 4px solid var(--sileo-error); }
|
|
43
|
+
.sileo-toast[data-type="info"]{ border-left: 4px solid var(--sileo-info); }
|
|
44
|
+
.sileo-toast[data-type="warning"]{ border-left: 4px solid var(--sileo-warning); }
|
|
45
|
+
|
|
46
|
+
.sileo-toast-header{ font-weight: 600; font-size: 14px; }
|
|
47
|
+
.sileo-toast-desc{ font-size: 13px; opacity: 0.85; margin-top: 4px; }
|
|
48
|
+
.sileo-toast-btn{ margin-left: 8px; background: transparent; color: inherit; border: 1px solid rgba(255,255,255,0.08); padding: 6px 8px; border-radius: 6px; cursor: pointer; }
|
|
49
|
+
.sileo-toast-close{ position: absolute; right: 8px; top: 6px; background: transparent; border: none; color: inherit; font-size: 16px; cursor: pointer; }
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
import { toasts, initSileoStore } from './store';
|
|
4
|
+
import type { ToastItem } from '../core/index';
|
|
5
|
+
import { sileo } from '../core/index';
|
|
6
|
+
import { animate } from 'motion';
|
|
7
|
+
|
|
8
|
+
let unsubscribe: () => void;
|
|
9
|
+
let items: ToastItem[] = [];
|
|
10
|
+
|
|
11
|
+
onMount(() => {
|
|
12
|
+
// subscribe local items to avoid $store auto-subscription issues
|
|
13
|
+
unsubscribe = toasts.subscribe((v) => (items = v));
|
|
14
|
+
// also initialize sileo store subscription helper if present
|
|
15
|
+
try {
|
|
16
|
+
initSileoStore();
|
|
17
|
+
} catch (e) {
|
|
18
|
+
// no-op
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
onDestroy(() => { unsubscribe && unsubscribe(); });
|
|
23
|
+
|
|
24
|
+
export function animateIn(node: HTMLElement) {
|
|
25
|
+
try {
|
|
26
|
+
const a = animate(node, { opacity: [0, 1], transform: ['translateY(-8px)', 'translateY(0px)'] }, { duration: 0.2 });
|
|
27
|
+
return {
|
|
28
|
+
destroy() {
|
|
29
|
+
// no-op
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return { destroy() {} };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function handleDismiss(id: string, ev: Event) {
|
|
38
|
+
const btn = ev.currentTarget as HTMLElement | null;
|
|
39
|
+
const el = btn ? btn.closest('.sileo-toast') as HTMLElement | null : null;
|
|
40
|
+
if (el) {
|
|
41
|
+
try {
|
|
42
|
+
const a = animate(el, { opacity: [1, 0], transform: ['translateY(0px)', 'translateY(-8px)'] }, { duration: 0.15 });
|
|
43
|
+
await (a as any).finished;
|
|
44
|
+
} catch (e) { /* ignore */ }
|
|
45
|
+
}
|
|
46
|
+
sileo.dismiss(id);
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<style>
|
|
51
|
+
/* rely on global sileo styles */
|
|
52
|
+
</style>
|
|
53
|
+
|
|
54
|
+
{#if items && items.length}
|
|
55
|
+
{#each items as t (t.id)}
|
|
56
|
+
<div class="sileo-toast" data-type={t.options.type} data-id={t.id} use:animateIn>
|
|
57
|
+
<div style="flex:1">
|
|
58
|
+
<div class="sileo-toast-header">{t.options.title}</div>
|
|
59
|
+
{#if t.options.description}
|
|
60
|
+
<div class="sileo-toast-desc">{t.options.description}</div>
|
|
61
|
+
{/if}
|
|
62
|
+
</div>
|
|
63
|
+
{#if t.options.button}
|
|
64
|
+
<button class="sileo-toast-btn" on:click|stopPropagation={(e) => { t.options.button && typeof t.options.button.onClick === 'function' ? t.options.button.onClick() : undefined; handleDismiss(t.id, e); }}>{t.options.button.title}</button>
|
|
65
|
+
{/if}
|
|
66
|
+
<button class="sileo-toast-close" on:click={(e) => handleDismiss(t.id, e)}>×</button>
|
|
67
|
+
</div>
|
|
68
|
+
{/each}
|
|
69
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
import { sileo, ToastItem } from '../core/index';
|
|
3
|
+
|
|
4
|
+
export const toasts = writable<ToastItem[]>([]);
|
|
5
|
+
|
|
6
|
+
export function initSileoStore() {
|
|
7
|
+
const unsub = sileo.subscribe((items: ToastItem[]) => toasts.set(items));
|
|
8
|
+
return unsub;
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { notify as coreNotify, sileo as coreSileo } from '../core/index';
|
|
2
|
+
import { initToasters, POSITIONS } from './toasterManager';
|
|
3
|
+
|
|
4
|
+
export { initToasters, POSITIONS };
|
|
5
|
+
|
|
6
|
+
// convenience: keep the previous shape where `notify` was the quick-show function
|
|
7
|
+
export const notify = coreNotify.show.bind(coreNotify);
|
|
8
|
+
|
|
9
|
+
type VanillaAPI = {
|
|
10
|
+
sileo: typeof coreSileo;
|
|
11
|
+
initToasters: typeof initToasters;
|
|
12
|
+
notify: typeof notify;
|
|
13
|
+
// keep full controller available under `controller` for advanced use
|
|
14
|
+
controller?: typeof coreNotify;
|
|
15
|
+
notifications?: any;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const defaultExport: VanillaAPI = { sileo: coreSileo, initToasters, notify, controller: coreNotify };
|
|
19
|
+
// backward compatibility: expose `notifications` key pointing to the same API object
|
|
20
|
+
(defaultExport as any).notifications = defaultExport;
|
|
21
|
+
|
|
22
|
+
export default defaultExport;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { sileo, ToastItem } from '../core/index';
|
|
2
|
+
import { animate } from 'motion';
|
|
3
|
+
|
|
4
|
+
const POSITIONS = [
|
|
5
|
+
'top-left',
|
|
6
|
+
'top-center',
|
|
7
|
+
'top-right',
|
|
8
|
+
'bottom-left',
|
|
9
|
+
'bottom-center',
|
|
10
|
+
'bottom-right'
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export type Position = typeof POSITIONS[number];
|
|
14
|
+
|
|
15
|
+
function createContainer(position: string, root: HTMLElement) {
|
|
16
|
+
const c = document.createElement('div');
|
|
17
|
+
c.className = 'sileo-toaster';
|
|
18
|
+
c.setAttribute('data-position', position);
|
|
19
|
+
c.setAttribute('role', 'status');
|
|
20
|
+
c.setAttribute('aria-live', 'polite');
|
|
21
|
+
return c;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderToast(item: ToastItem) {
|
|
25
|
+
const el = document.createElement('div');
|
|
26
|
+
el.className = 'sileo-toast';
|
|
27
|
+
el.dataset.id = item.id;
|
|
28
|
+
el.setAttribute('data-type', item.options.type || 'info');
|
|
29
|
+
el.style.opacity = '0';
|
|
30
|
+
el.style.transform = 'translateY(-6px)';
|
|
31
|
+
|
|
32
|
+
const body = document.createElement('div');
|
|
33
|
+
body.style.display = 'flex';
|
|
34
|
+
body.style.flexDirection = 'column';
|
|
35
|
+
body.style.flex = '1';
|
|
36
|
+
|
|
37
|
+
const header = document.createElement('div');
|
|
38
|
+
header.className = 'sileo-toast-header';
|
|
39
|
+
header.textContent = item.options.title || '';
|
|
40
|
+
|
|
41
|
+
body.appendChild(header);
|
|
42
|
+
|
|
43
|
+
if (item.options.description) {
|
|
44
|
+
const desc = document.createElement('div');
|
|
45
|
+
desc.className = 'sileo-toast-desc';
|
|
46
|
+
desc.textContent = item.options.description;
|
|
47
|
+
body.appendChild(desc);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
el.appendChild(body);
|
|
51
|
+
|
|
52
|
+
if (item.options.button) {
|
|
53
|
+
const btn = document.createElement('button');
|
|
54
|
+
btn.className = 'sileo-toast-btn';
|
|
55
|
+
btn.textContent = item.options.button.title;
|
|
56
|
+
btn.addEventListener('click', (e) => {
|
|
57
|
+
e.stopPropagation();
|
|
58
|
+
try {
|
|
59
|
+
if (item.options.button && typeof item.options.button.onClick === 'function') {
|
|
60
|
+
item.options.button.onClick();
|
|
61
|
+
}
|
|
62
|
+
} catch (err) { console.error(err); }
|
|
63
|
+
});
|
|
64
|
+
el.appendChild(btn);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const close = document.createElement('button');
|
|
68
|
+
close.className = 'sileo-toast-close';
|
|
69
|
+
close.innerHTML = '×';
|
|
70
|
+
close.addEventListener('click', () => sileo.dismiss(item.id));
|
|
71
|
+
el.appendChild(close);
|
|
72
|
+
|
|
73
|
+
if (typeof item.options.duration === 'number') {
|
|
74
|
+
if (item.options.duration > 0) {
|
|
75
|
+
setTimeout(() => sileo.dismiss(item.id), item.options.duration);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return el;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function initToasters(root: HTMLElement = document.body, positions: Position[] = ['top-right']) {
|
|
83
|
+
const containers: Record<string, HTMLElement> = {};
|
|
84
|
+
|
|
85
|
+
positions.forEach((pos) => {
|
|
86
|
+
const c = createContainer(pos, root);
|
|
87
|
+
root.appendChild(c);
|
|
88
|
+
containers[pos] = c;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
function rerender(items: ToastItem[]) {
|
|
92
|
+
positions.forEach((pos) => {
|
|
93
|
+
const container = containers[pos];
|
|
94
|
+
const visible = items.filter((t) => (t.options.position || 'top-right') === pos);
|
|
95
|
+
|
|
96
|
+
// Diff existing children and animate out removed toasts
|
|
97
|
+
const visibleIds = new Set(visible.map((v) => v.id));
|
|
98
|
+
const existing = Array.from(container.children) as HTMLElement[];
|
|
99
|
+
|
|
100
|
+
existing.forEach((child) => {
|
|
101
|
+
const id = child.dataset.id;
|
|
102
|
+
if (!id || !visibleIds.has(id)) {
|
|
103
|
+
// animate out then remove
|
|
104
|
+
animate(child, { opacity: 0, y: -8 }, { duration: 0.18 }).finished.then(() => child.remove());
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Add new items
|
|
109
|
+
visible.forEach((t) => {
|
|
110
|
+
if (!container.querySelector(`[data-id="${t.id}"]`)) {
|
|
111
|
+
const node = renderToast(t);
|
|
112
|
+
container.appendChild(node);
|
|
113
|
+
// animate in
|
|
114
|
+
requestAnimationFrame(() => {
|
|
115
|
+
animate(node, { opacity: [0, 1], y: [-6, 0] }, { duration: 0.24 });
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const unsub = sileo.subscribe(rerender);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
destroy() {
|
|
126
|
+
unsub();
|
|
127
|
+
Object.values(containers).forEach((c) => c.remove());
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { POSITIONS };
|
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { App, defineComponent, h, onMounted, ref } from 'vue';
|
|
2
|
+
import { notify, sileo } from '../core/index';
|
|
3
|
+
import type { ToastItem } from '../core/index';
|
|
4
|
+
import { animate } from 'motion';
|
|
5
|
+
|
|
6
|
+
export const Toaster = defineComponent({
|
|
7
|
+
props: { position: { type: String, default: 'top-right' } },
|
|
8
|
+
setup(props) {
|
|
9
|
+
const toasts = ref<ToastItem[]>([]);
|
|
10
|
+
let unsub: (() => void) | null = null;
|
|
11
|
+
onMounted(() => {
|
|
12
|
+
unsub = sileo.subscribe((items) => { toasts.value = items.filter((t) => (t.options.position || 'top-right') === props.position); });
|
|
13
|
+
});
|
|
14
|
+
const handleDismiss = async (id: string) => {
|
|
15
|
+
const el = document.querySelector(`[data-id="${id}"]`) as HTMLElement | null;
|
|
16
|
+
if (el) {
|
|
17
|
+
try {
|
|
18
|
+
const a = animate(el, { opacity: [1, 0], transform: ['translateY(0px)', 'translateY(-8px)'] }, { duration: 0.15 });
|
|
19
|
+
await (a as any).finished;
|
|
20
|
+
} catch (e) { /* ignore */ }
|
|
21
|
+
}
|
|
22
|
+
sileo.dismiss(id);
|
|
23
|
+
};
|
|
24
|
+
// animate in newly added toasts after render
|
|
25
|
+
const lastIds = ref<string[]>([]);
|
|
26
|
+
import('vue').then(({ nextTick }) => {
|
|
27
|
+
// watch toasts and animate new ones
|
|
28
|
+
const stop = (require('vue') as any).watch(toasts, async () => {
|
|
29
|
+
await nextTick();
|
|
30
|
+
const ids = toasts.value.map(t => t.id);
|
|
31
|
+
const added = ids.filter(id => !lastIds.value.includes(id));
|
|
32
|
+
added.forEach(id => {
|
|
33
|
+
const el = document.querySelector(`[data-id="${id}"]`) as HTMLElement | null;
|
|
34
|
+
if (el) {
|
|
35
|
+
el.style.opacity = '0';
|
|
36
|
+
el.style.transform = 'translateY(-6px)';
|
|
37
|
+
try { animate(el, { opacity: [0,1], transform: ['translateY(-6px)','translateY(0px)'] }, { duration: 0.22 }); } catch (e) {}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
lastIds.value = ids;
|
|
41
|
+
}, { deep: true });
|
|
42
|
+
}).catch(() => {});
|
|
43
|
+
return () => h('div', { class: 'sileo-toaster', 'data-position': props.position }, toasts.value.map((t) => h('div', { key: t.id, class: 'sileo-toast', 'data-type': t.options.type }, [
|
|
44
|
+
h('div', { style: { flex: '1' } }, [
|
|
45
|
+
h('div', { class: 'sileo-toast-header' }, t.options.title),
|
|
46
|
+
t.options.description ? h('div', { class: 'sileo-toast-desc' }, t.options.description) : null
|
|
47
|
+
]),
|
|
48
|
+
t.options.button ? h('button', { class: 'sileo-toast-btn', onClick: (e: Event) => { e.stopPropagation(); t.options.button!.onClick(); handleDismiss(t.id); } }, t.options.button.title) : null,
|
|
49
|
+
h('button', { class: 'sileo-toast-close', onClick: () => handleDismiss(t.id) }, '×')
|
|
50
|
+
])));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export default {
|
|
55
|
+
install(app: App) {
|
|
56
|
+
app.config.globalProperties.$sileo = sileo;
|
|
57
|
+
// expose notify as well on the app for convenience
|
|
58
|
+
(app.config.globalProperties as any).$notify = notify;
|
|
59
|
+
app.component('SileoToaster', Toaster as any);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
describe('no-bundler UMD bundle', () => {
|
|
6
|
+
it('dist/notify.umd.js should exist', () => {
|
|
7
|
+
const p = resolve(__dirname, '../../dist/notify.umd.js');
|
|
8
|
+
expect(existsSync(p)).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
describe('svelte toaster smoke', () => {
|
|
6
|
+
it('src/svelte/Toaster.svelte contains use:animateIn or motion import', () => {
|
|
7
|
+
const p = resolve(__dirname, '../../src/svelte/Toaster.svelte');
|
|
8
|
+
expect(existsSync(p)).toBe(true);
|
|
9
|
+
const txt = readFileSync(p, 'utf-8');
|
|
10
|
+
expect(/use:animateIn|from\s+['"]motion['"]/.test(txt)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2019",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"moduleResolution": "Node",
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"jsxImportSource": "react",
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"]
|
|
16
|
+
}
|