@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
package/dist/styles.css
ADDED
|
@@ -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,13 @@
|
|
|
1
|
+
import { notify as coreNotify, sileo as coreSileo } from '../core/index';
|
|
2
|
+
import { initToasters, POSITIONS } from './toasterManager';
|
|
3
|
+
export { initToasters, POSITIONS };
|
|
4
|
+
export declare const notify: (opts: import("../core/index").ToastOptions) => string;
|
|
5
|
+
type VanillaAPI = {
|
|
6
|
+
sileo: typeof coreSileo;
|
|
7
|
+
initToasters: typeof initToasters;
|
|
8
|
+
notify: typeof notify;
|
|
9
|
+
controller?: typeof coreNotify;
|
|
10
|
+
notifications?: any;
|
|
11
|
+
};
|
|
12
|
+
declare const defaultExport: VanillaAPI;
|
|
13
|
+
export default defaultExport;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
declare const POSITIONS: readonly ["top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right"];
|
|
2
|
+
export type Position = typeof POSITIONS[number];
|
|
3
|
+
export declare function initToasters(root?: HTMLElement, positions?: Position[]): {
|
|
4
|
+
destroy(): void;
|
|
5
|
+
};
|
|
6
|
+
export { POSITIONS };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
export declare const Toaster: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
3
|
+
position: {
|
|
4
|
+
type: StringConstructor;
|
|
5
|
+
default: string;
|
|
6
|
+
};
|
|
7
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
10
|
+
position: {
|
|
11
|
+
type: StringConstructor;
|
|
12
|
+
default: string;
|
|
13
|
+
};
|
|
14
|
+
}>> & Readonly<{}>, {
|
|
15
|
+
position: string;
|
|
16
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
17
|
+
declare const _default: {
|
|
18
|
+
install(app: App): void;
|
|
19
|
+
};
|
|
20
|
+
export default _default;
|
package/docs/browser.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Browser (UMD / no-bundler)
|
|
2
|
+
|
|
3
|
+
Quick start
|
|
4
|
+
|
|
5
|
+
Include the UMD bundle and stylesheet in a static page:
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<link rel="stylesheet" href="/path/to/dist/styles.css">
|
|
9
|
+
<script src="/path/to/dist/notify.umd.js"></script>
|
|
10
|
+
<script>
|
|
11
|
+
const api = window.notify || window.notifications;
|
|
12
|
+
api.initToasters(document.body, ['top-right']);
|
|
13
|
+
// convenience: api.notify
|
|
14
|
+
api.notify({ title: 'Hello', description: 'No-bundler usage', type: 'info' });
|
|
15
|
+
</script>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Notes
|
|
19
|
+
|
|
20
|
+
- The UMD bundle exposes `window.notify` (preferred). For compatibility it also exposes `window.notifications` with the previous API shape.
|
|
21
|
+
- Make sure to load `dist/styles.css` for styles and animations.
|
|
22
|
+
|
|
23
|
+
## CDN / Browser
|
|
24
|
+
|
|
25
|
+
Use the browser build when your project loads scripts directly in the page and cannot compile npm modules (Shopify, WordPress, plain HTML templates).
|
|
26
|
+
|
|
27
|
+
Example using the UMD build (replace path/version as needed):
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<link rel="stylesheet" href="/path/to/dist/styles.css">
|
|
31
|
+
<script src="/path/to/dist/notify.umd.js"></script>
|
|
32
|
+
<script>
|
|
33
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
34
|
+
const api = window.notify || window.notifications;
|
|
35
|
+
api.initToasters(document.body, ['top-right']);
|
|
36
|
+
api.notify({ title: 'Hello', description: 'No-bundler usage', type: 'info' });
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Notes
|
|
42
|
+
|
|
43
|
+
The browser bundle exposes `window.notify` (preferred) and for compatibility also exposes `window.notifications`; if these globals conflict with other scripts use the module builds instead.
|
|
44
|
+
Include `dist/styles.css` for styles and animations when using the UMD/browser bundle.
|
package/docs/react.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# React
|
|
2
|
+
|
|
3
|
+
Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @samline/notify react react-dom
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Basic usage
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { notify, sileo } from '@samline/notify';
|
|
14
|
+
import { Toaster } from '@samline/notify/react';
|
|
15
|
+
|
|
16
|
+
function App(){
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<Toaster />
|
|
20
|
+
<button onClick={() => notify.show({ title: 'Done', type: 'success' })}>Show</button>
|
|
21
|
+
</>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Notes
|
|
27
|
+
|
|
28
|
+
- The `Toaster` component subscribes to the core controller and renders toasts.
|
|
29
|
+
- You can customize positions and styles by importing `dist/styles.css`.
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
Install:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @samline/notify react react-dom
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Import and render the React `Toaster` in your app:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import React from 'react';
|
|
43
|
+
import { Toaster } from '@samline/notify/react';
|
|
44
|
+
import { notify, sileo } from '@samline/notify';
|
|
45
|
+
|
|
46
|
+
export function App(){
|
|
47
|
+
return (
|
|
48
|
+
<div>
|
|
49
|
+
<Toaster />
|
|
50
|
+
<button onClick={() => notify.show({ title: 'Done', type: 'success' })}>Show</button>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
- `Toaster` component: mounts a toast container and subscribes to the core controller.
|
|
59
|
+
- Primary API: use `notify` (e.g. `notify.show(...)`). A backward-compatible `sileo` alias is also available: `sileo.show(...)`.
|
|
60
|
+
|
|
61
|
+
## Notes
|
|
62
|
+
|
|
63
|
+
- The React adapter renders toasts in a React-friendly way and is SSR-safe when used with client-only mounting.
|
|
64
|
+
- To customize styles, import `dist/styles.css` or override the CSS variables defined in the stylesheet.
|
package/docs/svelte.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Svelte
|
|
2
|
+
|
|
3
|
+
Quick start
|
|
4
|
+
# Svelte
|
|
5
|
+
|
|
6
|
+
Quick start
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @samline/notify svelte
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Usage
|
|
13
|
+
|
|
14
|
+
```svelte
|
|
15
|
+
<script>
|
|
16
|
+
import Toaster, { initSileoStore } from '@samline/notify/svelte';
|
|
17
|
+
initSileoStore();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Toaster />
|
|
21
|
+
|
|
22
|
+
<button on:click={() => import('@samline/notify').then(m => m.notify.show({ title: 'Svelte' }))}>Show</button>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
TypeScript
|
|
26
|
+
|
|
27
|
+
If you use TypeScript, run `npm run typecheck` and `npx svelte-check` during development.
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
- `Toaster.svelte` — component that renders toasts and subscribes to the core controller.
|
|
32
|
+
- `initSileoStore()` — helper to wire the core `notify`/`sileo` controller to a Svelte store.
|
|
33
|
+
|
|
34
|
+
## Examples
|
|
35
|
+
|
|
36
|
+
```svelte
|
|
37
|
+
<script>
|
|
38
|
+
import Toaster, { initSileoStore } from '@samline/notify/svelte';
|
|
39
|
+
initSileoStore();
|
|
40
|
+
function show(){ import('@samline/notify').then(m => m.notify.show({ title: 'From Svelte' })); }
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<Toaster />
|
|
44
|
+
<button on:click={show}>Show</button>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- Use `npx svelte-check` and `npm run typecheck` when developing with TypeScript.
|
|
50
|
+
- The Svelte adapter is lightweight and subscribes to the shared core controller; use `initSileoStore()` in your app root to wire the store.
|
package/docs/vanilla.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Vanilla
|
|
2
|
+
|
|
3
|
+
## Quick Start
|
|
4
|
+
|
|
5
|
+
Import and initialize the vanilla (DOM) renderer:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { notify, sileo, initToasters } from '@samline/notify/vanilla';
|
|
9
|
+
|
|
10
|
+
// Mount the containers (defaults to document.body)
|
|
11
|
+
initToasters(document.body, ['top-right']);
|
|
12
|
+
|
|
13
|
+
// Show a notification (use `notify(...)` as the recommended API)
|
|
14
|
+
notify({ title: 'Saved', description: 'Your changes have been saved', type: 'success' });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// Core controller (primary: `notify`, compatibility alias: `sileo`)
|
|
21
|
+
notify.show(options)
|
|
22
|
+
notify.success(options)
|
|
23
|
+
notify.error(options)
|
|
24
|
+
notify.info(options)
|
|
25
|
+
notify.warning(options)
|
|
26
|
+
notify.action(options)
|
|
27
|
+
notify.promise(promise, { loading, success, error })
|
|
28
|
+
notify.dismiss(id)
|
|
29
|
+
notify.clear()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Parameters
|
|
33
|
+
|
|
34
|
+
| Parameter | Type | Description |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `options` | object | Toast creation options (see Options below) |
|
|
37
|
+
|
|
38
|
+
### Returns
|
|
39
|
+
|
|
40
|
+
`string | void` — `show` returns a toast id when created.
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
| Property | Type | Default | Description |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| `title` | `string` | — | Toast title |
|
|
47
|
+
| `description` | `string` | — | Optional body text |
|
|
48
|
+
| `type` | `info\|success\|error\|warning` | `info` | Visual intent |
|
|
49
|
+
| `position` | `string` | `top-right` | Container position |
|
|
50
|
+
| `duration` | `number` | `4000` | Auto-dismiss timeout in ms (`null` = sticky) |
|
|
51
|
+
| `button` | `{ title, onClick }` | — | Optional action button |
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
### Basic
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { notify, initToasters } from '@samline/notify/vanilla';
|
|
59
|
+
initToasters();
|
|
60
|
+
notify.success({ title: 'Saved' });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Promise flow
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
notify.promise(fetch('/api/save'), {
|
|
67
|
+
loading: { title: 'Saving...' },
|
|
68
|
+
success: { title: 'Done', type: 'success' },
|
|
69
|
+
error: { title: 'Failed', type: 'error' }
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## When to use
|
|
74
|
+
|
|
75
|
+
Use the vanilla entry when you interact directly with DOM nodes or need a framework-agnostic API.
|
|
76
|
+
|
|
77
|
+
## Accessibility
|
|
78
|
+
|
|
79
|
+
Toaster containers use `role="status"` and `aria-live="polite"` by default. Respect `prefers-reduced-motion` in your UI.
|
package/docs/vue.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Vue 3
|
|
2
|
+
|
|
3
|
+
Quick start
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { createApp } from 'vue';
|
|
7
|
+
import App from './App.vue';
|
|
8
|
+
import Notifications from '@samline/notify/vue';
|
|
9
|
+
|
|
10
|
+
const app = createApp(App);
|
|
11
|
+
app.use(Notifications);
|
|
12
|
+
app.mount('#app');
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Usage
|
|
16
|
+
|
|
17
|
+
- The plugin registers a `Toaster` component and provides a `useSileo()` composable to access the controller.
|
|
18
|
+
|
|
19
|
+
Example in a single-file component:
|
|
20
|
+
|
|
21
|
+
```vue
|
|
22
|
+
<template>
|
|
23
|
+
<Toaster />
|
|
24
|
+
<button @click="show">Show</button>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
import { notify, sileo } from '@samline/notify';
|
|
29
|
+
function show(){ notify.show({ title: 'Hello from Vue' }); }
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
- `Notifications` plugin: registers a global component `SileoToaster` and exposes `app.config.globalProperties.$sileo`.
|
|
35
|
+
- `useSileo()` (composable): returns the `sileo` controller instance for programmatic use.
|
|
36
|
+
|
|
37
|
+
## Notes
|
|
38
|
+
|
|
39
|
+
- The Vue adapter integrates with the Vue lifecycle and works with Vue 3's Composition API.
|
|
40
|
+
- To customize appearance, import `dist/styles.css` or override the CSS variables.
|
|
41
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<title>Samline Notifications - No Bundler Example</title>
|
|
7
|
+
<link rel="stylesheet" href="../../dist/styles.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>Samline Notifications - No Bundler</h1>
|
|
11
|
+
<button id="ok">Show toast</button>
|
|
12
|
+
|
|
13
|
+
<script src="../../dist/notify.umd.js"></script>
|
|
14
|
+
<script>
|
|
15
|
+
// UMD global: window.notify (preferred). For backward compatibility the API
|
|
16
|
+
// object also exposes a `notifications` key when necessary.
|
|
17
|
+
const api = window.notify || window.notifications;
|
|
18
|
+
const manager = api.initToasters(document.body, ['top-right','bottom-left']);
|
|
19
|
+
|
|
20
|
+
document.getElementById('ok').addEventListener('click', () => {
|
|
21
|
+
api.notify({ title: 'Hola', description: 'Toast desde UMD', duration: 4000 });
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@samline/notify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Notifications engine inspired by Sileo, with adapters for vanilla, React, Vue and Svelte.",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"unpkg": "dist/notify.umd.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
"./styles.css": "./dist/styles.css",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "rollup -c",
|
|
15
|
+
"test": "vitest",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"prepare": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"notifications",
|
|
21
|
+
"toasts",
|
|
22
|
+
"sileo",
|
|
23
|
+
"samline"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": ">=18",
|
|
28
|
+
"react-dom": ">=18",
|
|
29
|
+
"vue": "^3.5.31",
|
|
30
|
+
"svelte": "^5.55.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@rollup/plugin-commonjs": "^25.0.0",
|
|
34
|
+
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
35
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
36
|
+
"rollup": "^3.0.0",
|
|
37
|
+
"tslib": "^2.8.1",
|
|
38
|
+
"rollup-plugin-copy": "^3.4.0",
|
|
39
|
+
"svelte-check": "^3.3.0",
|
|
40
|
+
"typescript": "^5.0.0",
|
|
41
|
+
"vitest": "^1.0.0",
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"@types/react": "^18.2.0",
|
|
44
|
+
"@types/react-dom": "^18.2.0"
|
|
45
|
+
}
|
|
46
|
+
,
|
|
47
|
+
"devDependenciesMeta": {
|
|
48
|
+
"@types/react": { "optional": true },
|
|
49
|
+
"@types/react-dom": { "optional": true }
|
|
50
|
+
}
|
|
51
|
+
,
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"motion": "^10.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
2
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
3
|
+
import typescript from '@rollup/plugin-typescript';
|
|
4
|
+
import copy from 'rollup-plugin-copy';
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
// ESM + CJS
|
|
8
|
+
{
|
|
9
|
+
input: 'src/vanilla/index.ts',
|
|
10
|
+
output: [
|
|
11
|
+
{ file: 'dist/index.esm.js', format: 'es', exports: 'named' },
|
|
12
|
+
{ file: 'dist/index.cjs.js', format: 'cjs', exports: 'named' }
|
|
13
|
+
],
|
|
14
|
+
plugins: [resolve(), commonjs(), typescript({ tsconfig: './tsconfig.json' }),
|
|
15
|
+
copy({
|
|
16
|
+
targets: [
|
|
17
|
+
{ src: 'src/styles/sileo.css', dest: 'dist', rename: 'styles.css' }
|
|
18
|
+
],
|
|
19
|
+
verbose: true
|
|
20
|
+
})]
|
|
21
|
+
},
|
|
22
|
+
// UMD build for browser (vanilla DOM use)
|
|
23
|
+
{
|
|
24
|
+
input: 'src/vanilla/index.ts',
|
|
25
|
+
output: {
|
|
26
|
+
file: 'dist/notify.umd.js',
|
|
27
|
+
format: 'umd',
|
|
28
|
+
exports: 'named',
|
|
29
|
+
name: 'notify'
|
|
30
|
+
},
|
|
31
|
+
plugins: [resolve(), commonjs(), typescript({ tsconfig: './tsconfig.json' }),
|
|
32
|
+
copy({
|
|
33
|
+
targets: [
|
|
34
|
+
{ src: 'src/styles/sileo.css', dest: 'dist', rename: 'styles.css' }
|
|
35
|
+
],
|
|
36
|
+
verbose: true
|
|
37
|
+
})]
|
|
38
|
+
}
|
|
39
|
+
];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { sileo } from './index';
|
|
3
|
+
|
|
4
|
+
describe('sileo core', () => {
|
|
5
|
+
it('shows and dismisses a toast', () => {
|
|
6
|
+
const id = sileo.success({ title: 't1' });
|
|
7
|
+
const items = sileo.getToasts();
|
|
8
|
+
expect(items.find((i) => i.id === id)).toBeDefined();
|
|
9
|
+
sileo.dismiss(id);
|
|
10
|
+
const after = sileo.getToasts();
|
|
11
|
+
expect(after.find((i) => i.id === id)).toBeUndefined();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('promise flow shows loading then result', async () => {
|
|
15
|
+
const p = new Promise((resolve) => setTimeout(() => resolve('ok'), 50));
|
|
16
|
+
const promise = sileo.promise(p as any, {
|
|
17
|
+
loading: { title: 'loading' },
|
|
18
|
+
success: { title: 'done' },
|
|
19
|
+
error: { title: 'err' }
|
|
20
|
+
});
|
|
21
|
+
await promise;
|
|
22
|
+
const items = sileo.getToasts();
|
|
23
|
+
// success toast should be present
|
|
24
|
+
expect(items.some((t) => t.options.title === 'done')).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export type Position =
|
|
2
|
+
| 'top-left'
|
|
3
|
+
| 'top-center'
|
|
4
|
+
| 'top-right'
|
|
5
|
+
| 'bottom-left'
|
|
6
|
+
| 'bottom-center'
|
|
7
|
+
| 'bottom-right';
|
|
8
|
+
|
|
9
|
+
export type ToastType = 'success' | 'error' | 'info' | 'warning' | 'loading' | 'action';
|
|
10
|
+
|
|
11
|
+
export interface ToastButton {
|
|
12
|
+
title: string;
|
|
13
|
+
onClick: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ToastOptions {
|
|
17
|
+
title?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
position?: Position;
|
|
20
|
+
duration?: number | null; // ms, null = sticky
|
|
21
|
+
type?: ToastType;
|
|
22
|
+
button?: ToastButton;
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ToastItem {
|
|
27
|
+
id: string;
|
|
28
|
+
options: ToastOptions;
|
|
29
|
+
createdAt: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type Listener = (items: ToastItem[]) => void;
|
|
33
|
+
|
|
34
|
+
class NotifyController {
|
|
35
|
+
private toasts: ToastItem[] = [];
|
|
36
|
+
private listeners = new Set<Listener>();
|
|
37
|
+
private idCounter = 1;
|
|
38
|
+
|
|
39
|
+
private nextId() {
|
|
40
|
+
return `notify_${Date.now()}_${this.idCounter++}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
subscribe(fn: Listener) {
|
|
44
|
+
this.listeners.add(fn);
|
|
45
|
+
fn(this.toasts.slice());
|
|
46
|
+
return () => this.listeners.delete(fn);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private notify() {
|
|
50
|
+
const snapshot = this.toasts.slice();
|
|
51
|
+
this.listeners.forEach((l) => l(snapshot));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getToasts() {
|
|
55
|
+
return this.toasts.slice();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
show(opts: ToastOptions): string {
|
|
59
|
+
const id = this.nextId();
|
|
60
|
+
const item: ToastItem = { id, options: { ...opts }, createdAt: Date.now() };
|
|
61
|
+
this.toasts.push(item);
|
|
62
|
+
this.notify();
|
|
63
|
+
return id;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
success(opts: ToastOptions) {
|
|
67
|
+
return this.show({ ...opts, type: 'success' });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
error(opts: ToastOptions) {
|
|
71
|
+
return this.show({ ...opts, type: 'error' });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
info(opts: ToastOptions) {
|
|
75
|
+
return this.show({ ...opts, type: 'info' });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
warning(opts: ToastOptions) {
|
|
79
|
+
return this.show({ ...opts, type: 'warning' });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
action(opts: ToastOptions) {
|
|
83
|
+
return this.show({ ...opts, type: 'action' });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
dismiss(id: string) {
|
|
87
|
+
const idx = this.toasts.findIndex((t) => t.id === id);
|
|
88
|
+
if (idx >= 0) {
|
|
89
|
+
this.toasts.splice(idx, 1);
|
|
90
|
+
this.notify();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
clear(position?: Position) {
|
|
95
|
+
if (position) {
|
|
96
|
+
this.toasts = this.toasts.filter((t) => t.options.position !== position);
|
|
97
|
+
} else {
|
|
98
|
+
this.toasts = [];
|
|
99
|
+
}
|
|
100
|
+
this.notify();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
promise<T = any>(p: Promise<T>, opts: { loading: ToastOptions; success?: ToastOptions | ((r: T) => ToastOptions); error?: ToastOptions | ((e: any) => ToastOptions); position?: Position; }) {
|
|
104
|
+
const loadingId = this.show({ ...(opts.loading || {}), type: 'loading', position: opts.position });
|
|
105
|
+
p.then((res) => {
|
|
106
|
+
this.dismiss(loadingId);
|
|
107
|
+
const successOpt = typeof opts.success === 'function' ? (opts.success as any)(res) : opts.success;
|
|
108
|
+
if (successOpt) this.show({ ...(successOpt || {}), type: 'success', position: opts.position });
|
|
109
|
+
}).catch((err) => {
|
|
110
|
+
this.dismiss(loadingId);
|
|
111
|
+
const errorOpt = typeof opts.error === 'function' ? (opts.error as any)(err) : opts.error;
|
|
112
|
+
if (errorOpt) this.show({ ...(errorOpt || {}), type: 'error', position: opts.position });
|
|
113
|
+
});
|
|
114
|
+
return p;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const notify = new NotifyController();
|
|
119
|
+
// backward compatibility alias
|
|
120
|
+
export const sileo = notify as unknown as NotifyController;
|
|
121
|
+
|
|
122
|
+
export default notify;
|