@awaisdev5765/prism-ui 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +61 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/toast.d.ts +49 -0
- package/dist/toast.js +245 -0
- package/package.json +49 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Awais Rehman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# prism-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Zero-dependency web components for modern apps — the web sibling of the
|
|
4
|
+
[Prism Flutter family](https://pub.dev/packages/prism_markdown).
|
|
5
|
+
|
|
6
|
+
## Components
|
|
7
|
+
|
|
8
|
+
### `<prism-toast>` — adaptive toast notifications
|
|
9
|
+
|
|
10
|
+
Bottom-centered on mobile, stacked top-right cards on desktop — the pattern
|
|
11
|
+
each platform expects. Four icon-led variants, auto-dismiss, optional action
|
|
12
|
+
button, XSS-safe.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @awaisdev5765/prism-ui
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import '@awaisdev5765/prism-ui/toast';
|
|
24
|
+
|
|
25
|
+
// Shorthands
|
|
26
|
+
PrismToast.success('Settings saved.');
|
|
27
|
+
PrismToast.error('Upload failed. Retry?');
|
|
28
|
+
PrismToast.warning('Storage almost full.');
|
|
29
|
+
PrismToast.info('New features available.');
|
|
30
|
+
|
|
31
|
+
// Full control
|
|
32
|
+
PrismToast.show({
|
|
33
|
+
title: 'Sync complete',
|
|
34
|
+
message: '142 items updated.',
|
|
35
|
+
type: 'success',
|
|
36
|
+
duration: 4000,
|
|
37
|
+
actionLabel: 'View',
|
|
38
|
+
onAction: () => openChangelog(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
PrismToast.dismissAll();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Works with **any framework** — React, Vue, Svelte, plain HTML — because
|
|
45
|
+
they're standard Web Components with zero dependencies.
|
|
46
|
+
|
|
47
|
+
## How it adapts
|
|
48
|
+
|
|
49
|
+
| Viewport | Behavior |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| < 600px | Slides up from the bottom edge, nearly full width |
|
|
52
|
+
| ≥ 600px | Slides in from the right, stacked top-right cards (380px) |
|
|
53
|
+
|
|
54
|
+
## Why prism-ui?
|
|
55
|
+
|
|
56
|
+
- **Zero dependencies** — ~3KB of hand-written code per component
|
|
57
|
+
- **Web Components** — native custom elements, no framework lock-in
|
|
58
|
+
- **The Prism family** — the same design language as our 12 Flutter packages
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT — see [LICENSE](LICENSE).
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/toast.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive toast notifications as a web component — bottom-centered on
|
|
3
|
+
* mobile, stacked top-right on desktop. Zero dependencies.
|
|
4
|
+
*
|
|
5
|
+
* Quick start:
|
|
6
|
+
* ```js
|
|
7
|
+
* import 'prism-ui/toast';
|
|
8
|
+
* PrismToast.success('Settings saved.');
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export type PrismToastType = 'success' | 'error' | 'warning' | 'info';
|
|
12
|
+
export interface PrismToastOptions {
|
|
13
|
+
/** Main message text. */
|
|
14
|
+
message: string;
|
|
15
|
+
/** Visual/semantic type — drives icon and colors. Default: 'info'. */
|
|
16
|
+
type?: PrismToastType;
|
|
17
|
+
/** Optional bold lead-in above the message. */
|
|
18
|
+
title?: string;
|
|
19
|
+
/** Auto-dismiss delay in ms. Default: 3000. */
|
|
20
|
+
duration?: number;
|
|
21
|
+
/** Label for an optional action button. */
|
|
22
|
+
actionLabel?: string;
|
|
23
|
+
/** Invoked when the action button is clicked. */
|
|
24
|
+
onAction?: () => void;
|
|
25
|
+
}
|
|
26
|
+
/** Custom element rendering one toast card. */
|
|
27
|
+
export declare class PrismToastElement extends HTMLElement {
|
|
28
|
+
static observedAttributes: string[];
|
|
29
|
+
private _timer;
|
|
30
|
+
private _exiting;
|
|
31
|
+
connectedCallback(): void;
|
|
32
|
+
/** Animates the toast out and removes it from the DOM. */
|
|
33
|
+
dismiss(): void;
|
|
34
|
+
}
|
|
35
|
+
/** Imperative API for showing toasts. */
|
|
36
|
+
export declare const PrismToast: {
|
|
37
|
+
/** Shows a toast with full options. */
|
|
38
|
+
show(options: PrismToastOptions): PrismToastElement;
|
|
39
|
+
/** Success shorthand. */
|
|
40
|
+
success(message: string, options?: Partial<PrismToastOptions>): void;
|
|
41
|
+
/** Error shorthand. */
|
|
42
|
+
error(message: string, options?: Partial<PrismToastOptions>): void;
|
|
43
|
+
/** Warning shorthand. */
|
|
44
|
+
warning(message: string, options?: Partial<PrismToastOptions>): void;
|
|
45
|
+
/** Info shorthand. */
|
|
46
|
+
info(message: string, options?: Partial<PrismToastOptions>): void;
|
|
47
|
+
/** Removes every visible toast. */
|
|
48
|
+
dismissAll(): void;
|
|
49
|
+
};
|
package/dist/toast.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive toast notifications as a web component — bottom-centered on
|
|
3
|
+
* mobile, stacked top-right on desktop. Zero dependencies.
|
|
4
|
+
*
|
|
5
|
+
* Quick start:
|
|
6
|
+
* ```js
|
|
7
|
+
* import 'prism-ui/toast';
|
|
8
|
+
* PrismToast.success('Settings saved.');
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
const TYPES = {
|
|
12
|
+
success: { icon: '✓', label: 'Success' },
|
|
13
|
+
error: { icon: '✕', label: 'Error' },
|
|
14
|
+
warning: { icon: '!', label: 'Warning' },
|
|
15
|
+
info: { icon: 'i', label: 'Info' },
|
|
16
|
+
};
|
|
17
|
+
/** Container managing the toast stack (one per document). */
|
|
18
|
+
let container = null;
|
|
19
|
+
let styleInjected = false;
|
|
20
|
+
const STACK_CSS = `
|
|
21
|
+
.prism-toast-stack {
|
|
22
|
+
position: fixed;
|
|
23
|
+
z-index: 2147483000;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: 8px;
|
|
27
|
+
pointer-events: none;
|
|
28
|
+
}
|
|
29
|
+
.prism-toast-stack > * { pointer-events: auto; }
|
|
30
|
+
/* Mobile: bottom-centered, near full width */
|
|
31
|
+
.prism-toast-stack {
|
|
32
|
+
left: 16px;
|
|
33
|
+
right: 16px;
|
|
34
|
+
bottom: calc(16px + env(safe-area-inset-bottom, 0px));
|
|
35
|
+
}
|
|
36
|
+
/* Desktop/web: stacked top-right cards — the notification pattern */
|
|
37
|
+
@media (min-width: 600px) {
|
|
38
|
+
.prism-toast-stack {
|
|
39
|
+
top: 16px;
|
|
40
|
+
right: 16px;
|
|
41
|
+
bottom: auto;
|
|
42
|
+
left: auto;
|
|
43
|
+
width: 380px;
|
|
44
|
+
align-items: flex-end;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
function ensureContainer() {
|
|
49
|
+
if (container && document.body.contains(container))
|
|
50
|
+
return container;
|
|
51
|
+
if (!styleInjected) {
|
|
52
|
+
const style = document.createElement('style');
|
|
53
|
+
style.textContent = STACK_CSS;
|
|
54
|
+
document.head.appendChild(style);
|
|
55
|
+
styleInjected = true;
|
|
56
|
+
}
|
|
57
|
+
container = document.createElement('div');
|
|
58
|
+
container.className = 'prism-toast-stack';
|
|
59
|
+
document.body.appendChild(container);
|
|
60
|
+
return container;
|
|
61
|
+
}
|
|
62
|
+
/** Custom element rendering one toast card. */
|
|
63
|
+
export class PrismToastElement extends HTMLElement {
|
|
64
|
+
static observedAttributes = [];
|
|
65
|
+
_timer = null;
|
|
66
|
+
_exiting = false;
|
|
67
|
+
connectedCallback() {
|
|
68
|
+
if (this.shadowRoot)
|
|
69
|
+
return;
|
|
70
|
+
const shadow = this.attachShadow({ mode: 'open' });
|
|
71
|
+
const type = (this.dataset.type ?? 'info');
|
|
72
|
+
const meta = TYPES[type];
|
|
73
|
+
const title = this.dataset.title;
|
|
74
|
+
const actionLabel = this.dataset.actionLabel;
|
|
75
|
+
shadow.innerHTML = `
|
|
76
|
+
<style>
|
|
77
|
+
:host {
|
|
78
|
+
display: block;
|
|
79
|
+
width: 100%;
|
|
80
|
+
animation: prism-in 250ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
81
|
+
}
|
|
82
|
+
:host(.prism-exiting) {
|
|
83
|
+
animation: prism-out 200ms ease-in forwards;
|
|
84
|
+
}
|
|
85
|
+
@keyframes prism-in {
|
|
86
|
+
from { opacity: 0; transform: translateY(12px); }
|
|
87
|
+
to { opacity: 1; transform: none; }
|
|
88
|
+
}
|
|
89
|
+
@keyframes prism-out {
|
|
90
|
+
to { opacity: 0; transform: translateY(-8px); }
|
|
91
|
+
}
|
|
92
|
+
.card {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: 12px;
|
|
96
|
+
padding: 12px 8px 12px 16px;
|
|
97
|
+
border-radius: 14px;
|
|
98
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
|
99
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
line-height: 1.4;
|
|
102
|
+
}
|
|
103
|
+
.icon {
|
|
104
|
+
flex: none;
|
|
105
|
+
width: 26px;
|
|
106
|
+
height: 26px;
|
|
107
|
+
display: grid;
|
|
108
|
+
place-items: center;
|
|
109
|
+
border-radius: 50%;
|
|
110
|
+
font-weight: 700;
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
}
|
|
113
|
+
.body { flex: 1; min-width: 0; }
|
|
114
|
+
.title { font-weight: 700; margin-bottom: 2px; }
|
|
115
|
+
.action {
|
|
116
|
+
flex: none;
|
|
117
|
+
background: none;
|
|
118
|
+
border: none;
|
|
119
|
+
font: inherit;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
padding: 8px 10px;
|
|
123
|
+
border-radius: 8px;
|
|
124
|
+
}
|
|
125
|
+
.close {
|
|
126
|
+
flex: none;
|
|
127
|
+
background: none;
|
|
128
|
+
border: none;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
font-size: 18px;
|
|
131
|
+
line-height: 1;
|
|
132
|
+
padding: 8px;
|
|
133
|
+
border-radius: 8px;
|
|
134
|
+
opacity: 0.7;
|
|
135
|
+
}
|
|
136
|
+
.close:hover, .action:hover { opacity: 1; filter: brightness(1.15); }
|
|
137
|
+
</style>
|
|
138
|
+
<div class="card" part="card">
|
|
139
|
+
<span class="icon" part="icon">${meta.icon}</span>
|
|
140
|
+
<div class="body" part="body">
|
|
141
|
+
${title ? `<div class="title">${escapeHtml(title)}</div>` : ''}
|
|
142
|
+
<div class="message">${escapeHtml(this.dataset.message ?? '')}</div>
|
|
143
|
+
</div>
|
|
144
|
+
${actionLabel
|
|
145
|
+
? `<button class="action" part="action">${escapeHtml(actionLabel)}</button>`
|
|
146
|
+
: ''}
|
|
147
|
+
<button class="close" part="close" aria-label="Dismiss">✕</button>
|
|
148
|
+
</div>
|
|
149
|
+
`;
|
|
150
|
+
const card = shadow.querySelector('.card');
|
|
151
|
+
applySeverity(card, type);
|
|
152
|
+
const close = shadow.querySelector('.close');
|
|
153
|
+
close.addEventListener('click', () => this.dismiss());
|
|
154
|
+
if (actionLabel) {
|
|
155
|
+
const action = shadow.querySelector('.action');
|
|
156
|
+
action.addEventListener('click', () => {
|
|
157
|
+
this.dispatchEvent(new CustomEvent('prism-toast-action', { bubbles: true }));
|
|
158
|
+
this.dismiss();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const duration = Number(this.dataset.duration ?? 3000);
|
|
162
|
+
this._timer = setTimeout(() => this.dismiss(), duration);
|
|
163
|
+
}
|
|
164
|
+
/** Animates the toast out and removes it from the DOM. */
|
|
165
|
+
dismiss() {
|
|
166
|
+
if (this._exiting)
|
|
167
|
+
return;
|
|
168
|
+
this._exiting = true;
|
|
169
|
+
if (this._timer)
|
|
170
|
+
clearTimeout(this._timer);
|
|
171
|
+
this.classList.add('prism-exiting');
|
|
172
|
+
this.addEventListener('animationend', () => this.remove(), { once: true });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!customElements.get('prism-toast')) {
|
|
176
|
+
customElements.define('prism-toast', PrismToastElement);
|
|
177
|
+
}
|
|
178
|
+
/** Imperative API for showing toasts. */
|
|
179
|
+
export const PrismToast = {
|
|
180
|
+
/** Shows a toast with full options. */
|
|
181
|
+
show(options) {
|
|
182
|
+
ensureContainer();
|
|
183
|
+
const el = document.createElement('prism-toast');
|
|
184
|
+
el.dataset.type = options.type ?? 'info';
|
|
185
|
+
el.dataset.message = options.message;
|
|
186
|
+
if (options.title)
|
|
187
|
+
el.dataset.title = options.title;
|
|
188
|
+
if (options.duration !== undefined) {
|
|
189
|
+
el.dataset.duration = String(options.duration);
|
|
190
|
+
}
|
|
191
|
+
if (options.actionLabel)
|
|
192
|
+
el.dataset.actionLabel = options.actionLabel;
|
|
193
|
+
if (options.onAction) {
|
|
194
|
+
el.addEventListener('prism-toast-action', () => options.onAction());
|
|
195
|
+
}
|
|
196
|
+
container.appendChild(el);
|
|
197
|
+
return el;
|
|
198
|
+
},
|
|
199
|
+
/** Success shorthand. */
|
|
200
|
+
success(message, options) {
|
|
201
|
+
PrismToast.show({ ...options, message, type: 'success' });
|
|
202
|
+
},
|
|
203
|
+
/** Error shorthand. */
|
|
204
|
+
error(message, options) {
|
|
205
|
+
PrismToast.show({ ...options, message, type: 'error' });
|
|
206
|
+
},
|
|
207
|
+
/** Warning shorthand. */
|
|
208
|
+
warning(message, options) {
|
|
209
|
+
PrismToast.show({ ...options, message, type: 'warning' });
|
|
210
|
+
},
|
|
211
|
+
/** Info shorthand. */
|
|
212
|
+
info(message, options) {
|
|
213
|
+
PrismToast.show({ ...options, message, type: 'info' });
|
|
214
|
+
},
|
|
215
|
+
/** Removes every visible toast. */
|
|
216
|
+
dismissAll() {
|
|
217
|
+
if (!container)
|
|
218
|
+
return;
|
|
219
|
+
container
|
|
220
|
+
.querySelectorAll('prism-toast')
|
|
221
|
+
.forEach((el) => el.dismiss());
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
function applySeverity(card, type) {
|
|
225
|
+
// Material 3-inspired container colors.
|
|
226
|
+
const palettes = {
|
|
227
|
+
success: ['#DCEFE4', '#0F5132'],
|
|
228
|
+
error: ['#FADBD8', '#7A1C12'],
|
|
229
|
+
warning: ['#FDEBD0', '#7A4B0C'],
|
|
230
|
+
info: ['#E3E7F4', '#28325A'],
|
|
231
|
+
};
|
|
232
|
+
const [bg, fg] = palettes[type];
|
|
233
|
+
card.style.background = bg;
|
|
234
|
+
card.style.color = fg;
|
|
235
|
+
const icon = card.querySelector('.icon');
|
|
236
|
+
icon.style.background = fg;
|
|
237
|
+
icon.style.color = bg;
|
|
238
|
+
}
|
|
239
|
+
function escapeHtml(text) {
|
|
240
|
+
return text
|
|
241
|
+
.replace(/&/g, '&')
|
|
242
|
+
.replace(/</g, '<')
|
|
243
|
+
.replace(/>/g, '>')
|
|
244
|
+
.replace(/"/g, '"');
|
|
245
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awaisdev5765/prism-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency web components for modern apps — adaptive toasts, banners, and more. The web sibling of the Prism Flutter family.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./toast": {
|
|
14
|
+
"types": "./dist/toast.d.ts",
|
|
15
|
+
"import": "./dist/toast.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"prepublishOnly": "npm run build && npm test"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"web-components",
|
|
28
|
+
"custom-elements",
|
|
29
|
+
"toast",
|
|
30
|
+
"notification",
|
|
31
|
+
"ui",
|
|
32
|
+
"vanilla",
|
|
33
|
+
"zero-dependency",
|
|
34
|
+
"prism"
|
|
35
|
+
],
|
|
36
|
+
"author": "Awais Rehman",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/awaisdev5765/prism-ui.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/awaisdev5765/prism-ui#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/awaisdev5765/prism-ui/issues"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"happy-dom": "^15.0.0",
|
|
48
|
+
"typescript": "^5.6.0",
|
|
49
|
+
"vitest": "^2.1.0"
|
|
50
|
+
}
|
|
51
|
+
}
|