@manufosela/slide-notification 2.0.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/README.md +159 -0
- package/package.json +44 -0
- package/src/slide-notification.js +208 -0
- package/src/slide-notification.styles.js +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# @manufosela/slide-notification
|
|
2
|
+
|
|
3
|
+
Slide-in notification web component from the right edge. Built with [Lit](https://lit.dev/).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @manufosela/slide-notification
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { showSlideNotification } from '@manufosela/slide-notification';
|
|
15
|
+
|
|
16
|
+
// Simple notification
|
|
17
|
+
showSlideNotification({
|
|
18
|
+
message: 'Hello, world!'
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// With type and title
|
|
22
|
+
showSlideNotification({
|
|
23
|
+
type: 'success',
|
|
24
|
+
title: 'Success!',
|
|
25
|
+
message: 'Your changes have been saved.'
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- Slides in from the right edge
|
|
32
|
+
- Four notification types (info, success, warning, error)
|
|
33
|
+
- Auto-hide with configurable duration
|
|
34
|
+
- HTML message support
|
|
35
|
+
- Custom background colors
|
|
36
|
+
- CSS custom properties for theming
|
|
37
|
+
|
|
38
|
+
## Attributes
|
|
39
|
+
|
|
40
|
+
| Attribute | Type | Default | Description |
|
|
41
|
+
| ------------------ | ------- | ------- | ------------------------------ |
|
|
42
|
+
| `title` | String | `''` | Notification title |
|
|
43
|
+
| `message` | String | `''` | Message (supports HTML) |
|
|
44
|
+
| `type` | String | `info` | Type: info, success, warning, error |
|
|
45
|
+
| `timetohide` | Number | `3000` | Auto-hide delay in ms (0 = never) |
|
|
46
|
+
| `background-color` | String | `''` | Custom background color |
|
|
47
|
+
| `persistent` | Boolean | `false` | Stay visible until clicked |
|
|
48
|
+
|
|
49
|
+
## Methods
|
|
50
|
+
|
|
51
|
+
| Method | Description |
|
|
52
|
+
| -------- | ---------------------------------------- |
|
|
53
|
+
| `show()` | Show the notification |
|
|
54
|
+
| `hide()` | Hide the notification |
|
|
55
|
+
|
|
56
|
+
## Events
|
|
57
|
+
|
|
58
|
+
| Event | Description |
|
|
59
|
+
| ---------------------------- | ------------------------------ |
|
|
60
|
+
| `slide-notification-shown` | Fired when notification appears |
|
|
61
|
+
| `slide-notification-hidden` | Fired when notification hides |
|
|
62
|
+
|
|
63
|
+
## Notification Types
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
// Info (default) - Blue
|
|
67
|
+
showSlideNotification({ type: 'info', message: 'Info message' });
|
|
68
|
+
|
|
69
|
+
// Success - Green/Blue
|
|
70
|
+
showSlideNotification({ type: 'success', message: 'Success!' });
|
|
71
|
+
|
|
72
|
+
// Warning - Yellow (dark text)
|
|
73
|
+
showSlideNotification({ type: 'warning', message: 'Warning!' });
|
|
74
|
+
|
|
75
|
+
// Error - Red
|
|
76
|
+
showSlideNotification({ type: 'error', message: 'Error occurred' });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Custom Duration
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
// Quick notification (1 second)
|
|
83
|
+
showSlideNotification({
|
|
84
|
+
message: 'Quick!',
|
|
85
|
+
timetohide: 1000
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Persistent (click to close)
|
|
89
|
+
showSlideNotification({
|
|
90
|
+
message: 'Click me to close',
|
|
91
|
+
persistent: true
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Declarative Usage
|
|
96
|
+
|
|
97
|
+
For manual control, use the element in HTML:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<slide-notification id="myNotification"
|
|
101
|
+
title="Hello"
|
|
102
|
+
message="Click buttons to control"
|
|
103
|
+
persistent>
|
|
104
|
+
</slide-notification>
|
|
105
|
+
|
|
106
|
+
<button onclick="myNotification.show()">Show</button>
|
|
107
|
+
<button onclick="myNotification.hide()">Hide</button>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Custom Colors
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
showSlideNotification({
|
|
114
|
+
message: 'Purple notification',
|
|
115
|
+
backgroundColor: '#8b5cf6'
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## CSS Custom Properties
|
|
120
|
+
|
|
121
|
+
| Property | Default | Description |
|
|
122
|
+
| --------------------------------- | ------------------------------ | ------------------ |
|
|
123
|
+
| `--slide-notification-width` | `300px` | Notification width |
|
|
124
|
+
| `--slide-notification-bg` | `#17a2b8` | Background color |
|
|
125
|
+
| `--slide-notification-color` | `white` | Text color |
|
|
126
|
+
| `--slide-notification-radius` | `8px` | Border radius |
|
|
127
|
+
| `--slide-notification-padding` | `1.5rem` | Padding |
|
|
128
|
+
| `--slide-notification-shadow` | `0 2px 8px rgba(0,0,0,0.2)` | Box shadow |
|
|
129
|
+
| `--slide-notification-bottom` | `20px` | Bottom position |
|
|
130
|
+
| `--slide-notification-z-index` | `10000` | Z-index |
|
|
131
|
+
| `--slide-notification-min-height` | `80px` | Minimum height |
|
|
132
|
+
|
|
133
|
+
## slide-notification vs toast-notification
|
|
134
|
+
|
|
135
|
+
This library includes two notification components. Choose based on your needs:
|
|
136
|
+
|
|
137
|
+
| Feature | slide-notification | toast-notification |
|
|
138
|
+
|---------|--------------------|-------------------|
|
|
139
|
+
| **Animation** | Slides in from right edge | Appears in corner |
|
|
140
|
+
| **Position** | Right side only | 6 positions (corners + centers) |
|
|
141
|
+
| **Stacking** | One at a time | Multiple stack vertically |
|
|
142
|
+
| **Progress bar** | No | Yes |
|
|
143
|
+
| **Close button** | No (click anywhere) | Yes |
|
|
144
|
+
| **Persistent mode** | `persistent` attribute | `duration="0"` |
|
|
145
|
+
| **Best for** | Single important messages | Multiple sequential notifications |
|
|
146
|
+
|
|
147
|
+
**Use `slide-notification` when:**
|
|
148
|
+
- You want a prominent single notification
|
|
149
|
+
- You prefer slide-in animation
|
|
150
|
+
- You want click-anywhere-to-close behavior
|
|
151
|
+
|
|
152
|
+
**Use `toast-notification` when:**
|
|
153
|
+
- You need to show multiple notifications
|
|
154
|
+
- You want position flexibility (6 options)
|
|
155
|
+
- You need a progress bar indicator
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@manufosela/slide-notification",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Slide-in notification web component from the right edge",
|
|
5
|
+
"main": "src/slide-notification.js",
|
|
6
|
+
"module": "src/slide-notification.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start": "web-dev-server --node-resolve --open demo/ --watch",
|
|
13
|
+
"test": "web-test-runner",
|
|
14
|
+
"test:watch": "web-test-runner --watch",
|
|
15
|
+
"test:coverage": "web-test-runner --coverage"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"web-components",
|
|
19
|
+
"lit",
|
|
20
|
+
"notification",
|
|
21
|
+
"slide",
|
|
22
|
+
"toast",
|
|
23
|
+
"alert"
|
|
24
|
+
],
|
|
25
|
+
"author": "manufosela",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/manufosela/ui-components",
|
|
30
|
+
"directory": "packages/slide-notification"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/manufosela/ui-components/tree/main/packages/slide-notification#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/manufosela/ui-components/issues"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"lit": "^3.2.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@open-wc/testing": "^4.0.0",
|
|
41
|
+
"@web/dev-server": "^0.4.6",
|
|
42
|
+
"@web/test-runner": "^0.19.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { LitElement, html } from 'lit';
|
|
2
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
3
|
+
import { slideNotificationStyles } from './slide-notification.styles.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_COLORS = {
|
|
6
|
+
error: '#dc3545',
|
|
7
|
+
success: '#22c55e',
|
|
8
|
+
warning: '#ffc107',
|
|
9
|
+
info: '#17a2b8'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const ICONS = {
|
|
13
|
+
error: '❌',
|
|
14
|
+
success: '✅',
|
|
15
|
+
warning: '⚠️',
|
|
16
|
+
info: 'ℹ️'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A slide-in notification component from the right edge.
|
|
21
|
+
*
|
|
22
|
+
* @element slide-notification
|
|
23
|
+
* @fires slide-notification-shown - Fired when notification appears
|
|
24
|
+
* @fires slide-notification-hidden - Fired when notification is hidden
|
|
25
|
+
*
|
|
26
|
+
* @attr {String} title - Notification title
|
|
27
|
+
* @attr {String} message - Notification message (supports HTML)
|
|
28
|
+
* @attr {Number} timetohide - Time in ms before auto-hide (default: 3000, 0 = persistent)
|
|
29
|
+
* @attr {String} type - Type: 'info', 'success', 'warning', 'error'
|
|
30
|
+
* @attr {String} background-color - Custom background color
|
|
31
|
+
* @attr {Boolean} persistent - If true, notification stays until clicked (same as timetohide=0)
|
|
32
|
+
*
|
|
33
|
+
* @cssprop --slide-notification-width - Width (default: 300px)
|
|
34
|
+
* @cssprop --slide-notification-bg - Background color (default: #17a2b8)
|
|
35
|
+
* @cssprop --slide-notification-color - Text color (default: white)
|
|
36
|
+
* @cssprop --slide-notification-radius - Border radius (default: 8px)
|
|
37
|
+
* @cssprop --slide-notification-z-index - Z-index (default: 10000)
|
|
38
|
+
*/
|
|
39
|
+
export class SlideNotification extends LitElement {
|
|
40
|
+
static styles = slideNotificationStyles;
|
|
41
|
+
|
|
42
|
+
static properties = {
|
|
43
|
+
title: { type: String },
|
|
44
|
+
message: { type: String },
|
|
45
|
+
timetohide: { type: Number },
|
|
46
|
+
backgroundColor: { type: String, attribute: 'background-color' },
|
|
47
|
+
type: { type: String, reflect: true },
|
|
48
|
+
persistent: { type: Boolean, reflect: true },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
constructor() {
|
|
52
|
+
super();
|
|
53
|
+
this.title = '';
|
|
54
|
+
this.message = '';
|
|
55
|
+
this.timetohide = 3000;
|
|
56
|
+
this.type = 'info';
|
|
57
|
+
this.backgroundColor = '';
|
|
58
|
+
this.persistent = false;
|
|
59
|
+
this._hideTimeout = null;
|
|
60
|
+
this._isVisible = false;
|
|
61
|
+
this._createdProgrammatically = false;
|
|
62
|
+
this._removeOnHide = false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
connectedCallback() {
|
|
66
|
+
super.connectedCallback();
|
|
67
|
+
|
|
68
|
+
// Click to close for persistent notifications
|
|
69
|
+
this.addEventListener('click', this._handleClick);
|
|
70
|
+
|
|
71
|
+
// Only auto-show if created programmatically (via helper function)
|
|
72
|
+
if (this._createdProgrammatically) {
|
|
73
|
+
this._showNotification();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_showNotification() {
|
|
78
|
+
if (this._isVisible) return;
|
|
79
|
+
this._isVisible = true;
|
|
80
|
+
|
|
81
|
+
requestAnimationFrame(() => {
|
|
82
|
+
requestAnimationFrame(() => {
|
|
83
|
+
this.classList.remove('hiding');
|
|
84
|
+
this.classList.add('visible');
|
|
85
|
+
|
|
86
|
+
this.dispatchEvent(new CustomEvent('slide-notification-shown', {
|
|
87
|
+
bubbles: true,
|
|
88
|
+
composed: true
|
|
89
|
+
}));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Auto-hide after timeout (unless persistent)
|
|
93
|
+
const shouldAutoHide = !this.persistent && this.timetohide > 0;
|
|
94
|
+
if (shouldAutoHide) {
|
|
95
|
+
this._hideTimeout = setTimeout(() => {
|
|
96
|
+
this.hide();
|
|
97
|
+
}, this.timetohide);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_handleClick = () => {
|
|
103
|
+
if (this.persistent || this.timetohide === 0) {
|
|
104
|
+
this.hide();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
disconnectedCallback() {
|
|
109
|
+
super.disconnectedCallback();
|
|
110
|
+
if (this._hideTimeout) {
|
|
111
|
+
clearTimeout(this._hideTimeout);
|
|
112
|
+
this._hideTimeout = null;
|
|
113
|
+
}
|
|
114
|
+
this.removeEventListener('click', this._handleClick);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
hide() {
|
|
118
|
+
if (!this._isVisible) return;
|
|
119
|
+
this._isVisible = false;
|
|
120
|
+
|
|
121
|
+
if (this._hideTimeout) {
|
|
122
|
+
clearTimeout(this._hideTimeout);
|
|
123
|
+
this._hideTimeout = null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.classList.remove('visible');
|
|
127
|
+
this.classList.add('hiding');
|
|
128
|
+
|
|
129
|
+
this.dispatchEvent(new CustomEvent('slide-notification-hidden', {
|
|
130
|
+
bubbles: true,
|
|
131
|
+
composed: true
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
// Remove from DOM if created programmatically
|
|
135
|
+
if (this._removeOnHide) {
|
|
136
|
+
setTimeout(() => this.remove(), 600);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
show() {
|
|
141
|
+
this._showNotification();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_getBackgroundColor() {
|
|
145
|
+
if (this.backgroundColor) return this.backgroundColor;
|
|
146
|
+
return DEFAULT_COLORS[this.type] || DEFAULT_COLORS.info;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_getTextColor() {
|
|
150
|
+
return this.type === 'warning' ? '#212529' : 'white';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
_getTextShadow() {
|
|
154
|
+
return this.type === 'warning'
|
|
155
|
+
? '1px 1px 2px rgba(0, 0, 0, 0.3)'
|
|
156
|
+
: '1px 1px 2px rgba(0, 0, 0, 0.5)';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
render() {
|
|
160
|
+
const icon = ICONS[this.type] || ICONS.info;
|
|
161
|
+
const bgColor = this._getBackgroundColor();
|
|
162
|
+
const textColor = this._getTextColor();
|
|
163
|
+
const textShadow = this._getTextShadow();
|
|
164
|
+
|
|
165
|
+
this.style.setProperty('--_bg', bgColor);
|
|
166
|
+
this.style.setProperty('--_color', textColor);
|
|
167
|
+
this.style.setProperty('--_text-shadow', textShadow);
|
|
168
|
+
|
|
169
|
+
return html`
|
|
170
|
+
${this.title ? html`<div class="title">${this.title}</div>` : ''}
|
|
171
|
+
<div class="notification-content">
|
|
172
|
+
<span class="icon">${icon}</span>
|
|
173
|
+
<div class="message">${unsafeHTML(this.message)}</div>
|
|
174
|
+
</div>
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
customElements.define('slide-notification', SlideNotification);
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Helper function to show a slide notification
|
|
183
|
+
* @param {Object} options - Notification options
|
|
184
|
+
* @param {string} options.title - Notification title
|
|
185
|
+
* @param {string} options.message - Notification message (supports HTML)
|
|
186
|
+
* @param {number} options.timetohide - Time in ms before auto-hide (default: 3000, 0 = persistent)
|
|
187
|
+
* @param {string} options.type - Type: 'info' | 'success' | 'warning' | 'error'
|
|
188
|
+
* @param {string} options.backgroundColor - Custom background color
|
|
189
|
+
* @param {boolean} options.persistent - If true, stays until clicked
|
|
190
|
+
* @returns {SlideNotification} The created notification element
|
|
191
|
+
*/
|
|
192
|
+
export function showSlideNotification(options = {}) {
|
|
193
|
+
const notification = document.createElement('slide-notification');
|
|
194
|
+
notification.title = options.title || '';
|
|
195
|
+
notification.message = options.message || 'Notification';
|
|
196
|
+
notification.timetohide = options.timetohide ?? 3000;
|
|
197
|
+
notification.type = options.type || 'info';
|
|
198
|
+
notification.persistent = options.persistent || false;
|
|
199
|
+
if (options.backgroundColor) {
|
|
200
|
+
notification.backgroundColor = options.backgroundColor;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Mark as programmatically created so it auto-shows and auto-removes
|
|
204
|
+
notification._createdProgrammatically = true;
|
|
205
|
+
notification._removeOnHide = true;
|
|
206
|
+
document.body.appendChild(notification);
|
|
207
|
+
return notification;
|
|
208
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
|
|
3
|
+
export const slideNotificationStyles = css`
|
|
4
|
+
:host {
|
|
5
|
+
--_width: var(--slide-notification-width, 300px);
|
|
6
|
+
--_bg: var(--slide-notification-bg, #17a2b8);
|
|
7
|
+
--_color: var(--slide-notification-color, white);
|
|
8
|
+
--_text-shadow: var(--slide-notification-text-shadow, 1px 1px 2px rgba(0, 0, 0, 0.5));
|
|
9
|
+
|
|
10
|
+
position: fixed;
|
|
11
|
+
bottom: var(--slide-notification-bottom, 20px);
|
|
12
|
+
right: calc(-20px - var(--_width));
|
|
13
|
+
width: var(--_width);
|
|
14
|
+
min-height: var(--slide-notification-min-height, 80px);
|
|
15
|
+
background: var(--_bg);
|
|
16
|
+
color: var(--_color);
|
|
17
|
+
border-radius: var(--slide-notification-radius, 8px);
|
|
18
|
+
border-left: 4px solid rgba(255, 255, 255, 0.3);
|
|
19
|
+
padding: var(--slide-notification-padding, 1.5rem);
|
|
20
|
+
box-shadow: var(--slide-notification-shadow, 0 2px 8px rgba(0, 0, 0, 0.2));
|
|
21
|
+
font-size: 1rem;
|
|
22
|
+
font-weight: 500;
|
|
23
|
+
opacity: 0;
|
|
24
|
+
transition: transform 0.5s ease-in-out, opacity 0.5s ease-in-out;
|
|
25
|
+
transform: translateX(0);
|
|
26
|
+
z-index: var(--slide-notification-z-index, 10000);
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
align-items: center;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
:host(.visible) {
|
|
34
|
+
opacity: 1;
|
|
35
|
+
transform: translateX(calc(-1 * var(--_width) - 40px));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:host(.hiding) {
|
|
39
|
+
transform: translateX(calc(var(--_width) + 40px));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:host([persistent]) {
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.title {
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
margin-bottom: 0.25rem;
|
|
49
|
+
text-shadow: var(--_text-shadow);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.notification-content {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 0.5rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.message {
|
|
59
|
+
font-weight: 500;
|
|
60
|
+
text-shadow: var(--_text-shadow);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.icon {
|
|
64
|
+
font-size: 1.2em;
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
}
|
|
67
|
+
`;
|