whisp-toast 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/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # whisp-toast
2
+
3
+ Toasts ligeros para React / Next.js. Cero dependencias, API global — llamá `whisp.success('mensaje')` desde cualquier parte de tu código, sin hooks ni refs ni providers.
4
+
5
+ - 🪶 Sin dependencias externas
6
+ - 🌐 API global — funciona fuera de componentes React (handlers, fetch, utils)
7
+ - 🎨 Colores personalizables por CSS variables
8
+ - 🖼️ Íconos personalizables, por tipo o por toast individual
9
+ - 📍 6 posiciones en pantalla
10
+ - 🔒 Tipado con TypeScript
11
+
12
+ ## Instalación
13
+
14
+ ```bash
15
+ npm install whisp-toast
16
+ ```
17
+
18
+ ## Uso básico
19
+
20
+ **1. Montá el componente `Whisper`** una sola vez, en tu layout raíz:
21
+
22
+ ```tsx
23
+ // app/layout.tsx
24
+ import { Whisper } from 'whisp-toast'
25
+
26
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
27
+ return (
28
+ <html lang="es">
29
+ <body>
30
+ {children}
31
+ <Whisper position="bottom-right" />
32
+ </body>
33
+ </html>
34
+ )
35
+ }
36
+ ```
37
+
38
+ **2. Llamá `whisp` desde cualquier componente cliente, handler, o función async:**
39
+
40
+ ```tsx
41
+ 'use client'
42
+ import { whisp } from 'whisp-toast'
43
+
44
+ export default function Formulario() {
45
+ const handleSubmit = async () => {
46
+ try {
47
+ await fetch('/api/guardar', { method: 'POST' })
48
+ whisp.success('¡Guardado con éxito!')
49
+ } catch {
50
+ whisp.error('Algo salió mal')
51
+ }
52
+ }
53
+
54
+ return <button onClick={handleSubmit}>Guardar</button>
55
+ }
56
+ ```
57
+
58
+ No hace falta ningún import de estilos por separado — los estilos ya vienen incluidos al importar el componente.
59
+
60
+ ## API
61
+
62
+ ### `whisp(message, options?)`
63
+
64
+ Dispara un toast neutro (sin ícono por defecto).
65
+
66
+ ```tsx
67
+ whisp('Mensaje simple')
68
+ whisp('Con duración custom', { duration: 5000 })
69
+ ```
70
+
71
+ ### Variantes por tipo
72
+
73
+ ```tsx
74
+ whisp.success('¡Guardado con éxito!')
75
+ whisp.error('Algo salió mal')
76
+ whisp.info('Tenés una notificación nueva')
77
+ whisp.alert('¡Atención urgente!')
78
+ ```
79
+
80
+ ### Opciones
81
+
82
+ Cada función acepta un segundo argumento, que puede ser un número (duración en ms, para mantener compatibilidad) o un objeto de opciones:
83
+
84
+ ```tsx
85
+ whisp.success('Listo', 5000) // duración directa
86
+
87
+ whisp.success('Listo', {
88
+ duration: 5000, // ms antes de cerrarse. 0 = no se cierra solo. Default: 3000
89
+ icon: '🎉', // ícono custom para este toast puntual
90
+ })
91
+ ```
92
+
93
+ Todos los toasts se pueden cerrar antes de tiempo haciendo click sobre ellos.
94
+
95
+ ## Componente `<Whisper />`
96
+
97
+ Se monta una sola vez en el layout raíz de tu app.
98
+
99
+ | Prop | Valores | Default |
100
+ |---|---|---|
101
+ | `position` | `'top-center'` \| `'top-left'` \| `'top-right'` \|`'bottom-right'` \| `'bottom-left'`\| `'bottom-center'` | `'top-center'` |
102
+
103
+ ```tsx
104
+ <Whisper position="top-left" />
105
+ ```
106
+
107
+ ## Personalizar íconos
108
+
109
+ ### Ícono por defecto de un tipo
110
+
111
+ Cada tipo (`success`, `error`, `info`, `alert`) trae un ícono SVG incluido, sin dependencias externas.
112
+
113
+ ### Ícono en un toast puntual
114
+
115
+ Sobrescribe el ícono por defecto solo para esa llamada. Acepta cualquier `ReactNode`: emoji, string, SVG propio, o un componente de ícono de tu librería favorita.
116
+
117
+ ```tsx
118
+ whisp('Subiendo archivo...', { icon: '⏳' })
119
+
120
+ whisp.success('¡Lanzado!', {
121
+ icon: (
122
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
123
+ <path d="M18 6 7 17l-5-5" />
124
+ </svg>
125
+ ),
126
+ })
127
+
128
+ // Sin ícono, aunque el tipo tenga uno por defecto
129
+ whisp.success('Sin ícono', { icon: null })
130
+ ```
131
+
132
+ ## Personalizar colores
133
+
134
+ whisp-toast expone CSS variables con valores por defecto (dark, estilo Sonner). Sobrescribilas donde quieras: `globals.css`, un `<style>` inline, o dentro de un selector específico.
135
+
136
+ ```css
137
+ :root {
138
+ --whisp-bg: #ffffff;
139
+ --whisp-text: #111111;
140
+ --whisp-border: rgba(0, 0, 0, 0.1);
141
+ --whisp-radius: 12px;
142
+
143
+ --whisp-success-bg: #10b981;
144
+ --whisp-success-color: #052e16;
145
+
146
+ --whisp-error-bg: #dc2626;
147
+ --whisp-error-color: #450a0a;
148
+
149
+ --whisp-info-bg: #6366f1;
150
+ --whisp-info-color: #1e1b4b;
151
+
152
+ --whisp-alert-bg: #eab308;
153
+ --whisp-alert-color: #451a03;
154
+ }
155
+ ```
156
+
157
+ Si no definís nada, se usan los valores por defecto — cero configuración obligatoria.
158
+
159
+ ### Tabla de variables
160
+
161
+ | Variable | Controla | Default |
162
+ |---|---|---|
163
+ | `--whisp-bg` | Fondo de la card | `#1a1a1a` |
164
+ | `--whisp-text` | Color del texto | `#f5f5f5` |
165
+ | `--whisp-border` | Borde de la card | `rgba(255,255,255,0.08)` |
166
+ | `--whisp-radius` | Radio de esquinas | `10px` |
167
+ | `--whisp-success-bg` / `--whisp-success-color` | Círculo del ícono de éxito | `#22c55e` / `#052e16` |
168
+ | `--whisp-error-bg` / `--whisp-error-color` | Círculo del ícono de error | `#ef4444` / `#450a0a` |
169
+ | `--whisp-info-bg` / `--whisp-info-color` | Círculo del ícono de info | `#3b82f6` / `#172554` |
170
+ | `--whisp-alert-bg` / `--whisp-alert-color` | Círculo del ícono de alerta | `#f59e0b` / `#451a03` |
171
+
172
+ ### Ejemplo: dark/light mode automático
173
+
174
+ ```css
175
+ @media (prefers-color-scheme: light) {
176
+ :root {
177
+ --whisp-bg: #ffffff;
178
+ --whisp-text: #111111;
179
+ --whisp-border: rgba(0, 0, 0, 0.08);
180
+ }
181
+ }
182
+ ```
183
+
184
+ ## TypeScript
185
+
186
+ Todos los tipos están exportados:
187
+
188
+ ```tsx
189
+ import type { WhispType, WhispItem, WhispOptions, WhisperProps } from 'whisp-toast'
190
+ ```
191
+
192
+ ## Licencia
193
+
194
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,225 @@
1
+ "use client"
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
11
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __spreadValues = (a, b) => {
13
+ for (var prop in b || (b = {}))
14
+ if (__hasOwnProp.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ if (__getOwnPropSymbols)
17
+ for (var prop of __getOwnPropSymbols(b)) {
18
+ if (__propIsEnum.call(b, prop))
19
+ __defNormalProp(a, prop, b[prop]);
20
+ }
21
+ return a;
22
+ };
23
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
24
+ var __export = (target, all) => {
25
+ for (var name in all)
26
+ __defProp(target, name, { get: all[name], enumerable: true });
27
+ };
28
+ var __copyProps = (to, from, except, desc) => {
29
+ if (from && typeof from === "object" || typeof from === "function") {
30
+ for (let key of __getOwnPropNames(from))
31
+ if (!__hasOwnProp.call(to, key) && key !== except)
32
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
33
+ }
34
+ return to;
35
+ };
36
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
+
38
+ // src/index.ts
39
+ var index_exports = {};
40
+ __export(index_exports, {
41
+ Whisper: () => Whisper,
42
+ whisp: () => whisp,
43
+ whispStore: () => whispStore
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+
47
+ // src/store.ts
48
+ var whisps = [];
49
+ var listeners = [];
50
+ var idCounter = 0;
51
+ function emit() {
52
+ listeners.forEach((listener) => listener(whisps));
53
+ }
54
+ function normalizeOptions(options) {
55
+ if (typeof options === "number") return { duration: options };
56
+ return options != null ? options : {};
57
+ }
58
+ function addWhisp(message, type = "default", options) {
59
+ const { duration = 3e3, icon: icon2 } = normalizeOptions(options);
60
+ const id = idCounter++;
61
+ whisps = [{ id, message, type, icon: icon2 }];
62
+ emit();
63
+ if (duration > 0) {
64
+ setTimeout(() => removeWhisp(id), duration);
65
+ }
66
+ return id;
67
+ }
68
+ function removeWhisp(id) {
69
+ const whisp2 = whisps.find((w) => w.id === id);
70
+ if (!whisp2 || whisp2.closing) return;
71
+ whisps = whisps.map(
72
+ (w) => w.id === id ? __spreadProps(__spreadValues({}, w), { closing: true }) : w
73
+ );
74
+ emit();
75
+ setTimeout(() => {
76
+ whisps = whisps.filter((w) => w.id !== id);
77
+ emit();
78
+ }, 250);
79
+ }
80
+ function subscribe(listener) {
81
+ listeners.push(listener);
82
+ listener(whisps);
83
+ return () => {
84
+ listeners = listeners.filter((l) => l !== listener);
85
+ };
86
+ }
87
+ var whisp = Object.assign(
88
+ (message, options) => addWhisp(message, "default", options),
89
+ {
90
+ success: (message, options) => addWhisp(message, "success", options),
91
+ error: (message, options) => addWhisp(message, "error", options),
92
+ info: (message, options) => addWhisp(message, "info", options),
93
+ alert: (message, options) => addWhisp(message, "alert", options)
94
+ }
95
+ );
96
+ var whispStore = { subscribe, removeWhisp };
97
+
98
+ // src/Whisper.tsx
99
+ var import_react = require("react");
100
+
101
+ // #style-inject:#style-inject
102
+ function styleInject(css, { insertAt } = {}) {
103
+ if (!css || typeof document === "undefined") return;
104
+ const head = document.head || document.getElementsByTagName("head")[0];
105
+ const style = document.createElement("style");
106
+ style.type = "text/css";
107
+ if (insertAt === "top") {
108
+ if (head.firstChild) {
109
+ head.insertBefore(style, head.firstChild);
110
+ } else {
111
+ head.appendChild(style);
112
+ }
113
+ } else {
114
+ head.appendChild(style);
115
+ }
116
+ if (style.styleSheet) {
117
+ style.styleSheet.cssText = css;
118
+ } else {
119
+ style.appendChild(document.createTextNode(css));
120
+ }
121
+ }
122
+
123
+ // src/styles.css
124
+ styleInject(".whisp-container {\n position: fixed;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n z-index: 9999;\n pointer-events: none;\n}\n.whisp-container[data-vertical=top] {\n top: 1.5rem;\n}\n.whisp-container[data-vertical=bottom] {\n bottom: 1.5rem;\n flex-direction: column-reverse;\n}\n.whisp-container[data-horizontal=right] {\n right: 1.5rem;\n align-items: flex-end;\n}\n.whisp-container[data-horizontal=left] {\n left: 1.5rem;\n align-items: flex-start;\n}\n.whisp-container[data-horizontal=center] {\n left: 50%;\n transform: translateX(-50%);\n align-items: center;\n}\n.whisp-card {\n pointer-events: auto;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n min-width: 280px;\n max-width: 356px;\n padding: 0.85rem 1rem;\n background: var(--whisp-bg, #1a1a1a);\n color: var(--whisp-text, #f5f5f5);\n border: 1px solid var(--whisp-border, rgba(255, 255, 255, 0.08));\n border-radius: var(--whisp-radius, 10px);\n font-size: 0.85rem;\n line-height: 1.3;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);\n cursor: pointer;\n animation: whisp-in 0.3s cubic-bezier(0.21, 1.02, 0.73, 1) forwards;\n}\n.whisp-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n font-size: 0.7rem;\n flex-shrink: 0;\n}\n.whisp-success .whisp-icon {\n background: var(--whisp-success-bg, #22c55e);\n color: var(--whisp-success-color, #052e16);\n}\n.whisp-error .whisp-icon {\n background: var(--whisp-error-bg, #ef4444);\n color: var(--whisp-error-color, #450a0a);\n}\n.whisp-info .whisp-icon {\n background: var(--whisp-info-bg, #3b82f6);\n color: var(--whisp-info-color, #172554);\n}\n.whisp-alert .whisp-icon {\n background: var(--whisp-alert-bg, #f59e0b);\n color: var(--whisp-alert-color, #451a03);\n}\n.whisp-card.whisp-out {\n animation: whisp-out 0.25s ease forwards;\n}\n.whisp-container[data-vertical=bottom] .whisp-card.whisp-out {\n animation-name: whisp-out-bottom;\n}\n@keyframes whisp-in {\n from {\n transform: translateY(16px) scale(0.95);\n opacity: 0;\n }\n to {\n transform: translateY(0) scale(1);\n opacity: 1;\n }\n}\n@keyframes whisp-out {\n from {\n transform: translateY(0) scale(1);\n opacity: 1;\n }\n to {\n transform: translateY(-16px) scale(0.95);\n opacity: 0;\n }\n}\n.whisp-container[data-vertical=bottom] .whisp-card.whisp-out {\n animation-name: whisp-out-bottom;\n}\n@keyframes whisp-out-bottom {\n from {\n transform: translateY(0) scale(1);\n opacity: 1;\n }\n to {\n transform: translateY(16px) scale(0.95);\n opacity: 0;\n }\n}\n");
125
+
126
+ // src/icons/icons.tsx
127
+ var import_jsx_runtime = require("react/jsx-runtime");
128
+ var icon = {
129
+ success: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
130
+ "svg",
131
+ {
132
+ xmlns: "http://www.w3.org/2000/svg",
133
+ width: "16",
134
+ height: "16",
135
+ viewBox: "0 0 24 24",
136
+ fill: "none",
137
+ stroke: "currentColor",
138
+ strokeWidth: "2",
139
+ strokeLinecap: "round",
140
+ strokeLinejoin: "round",
141
+ children: [
142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m9 12 2 2 4-4" })
144
+ ]
145
+ }
146
+ ),
147
+ error: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m15 9-6 6" }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m9 9 6 6" })
151
+ ] }),
152
+ info: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 16v-4" }),
155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 8h.01" })
156
+ ] }),
157
+ alert: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
158
+ "svg",
159
+ {
160
+ xmlns: "http://www.w3.org/2000/svg",
161
+ width: "16",
162
+ height: "16",
163
+ viewBox: "0 0 24 24",
164
+ fill: "none",
165
+ stroke: "currentColor",
166
+ strokeWidth: "2",
167
+ strokeLinecap: "round",
168
+ strokeLinejoin: "round",
169
+ children: [
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12", y1: "8", y2: "12" }),
172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12.01", y1: "16", y2: "16" })
173
+ ]
174
+ }
175
+ )
176
+ };
177
+
178
+ // src/Whisper.tsx
179
+ var import_jsx_runtime2 = require("react/jsx-runtime");
180
+ var defaultIcons = {
181
+ success: icon.success,
182
+ error: icon.error,
183
+ info: icon.info,
184
+ alert: icon.alert,
185
+ default: null
186
+ };
187
+ function Whisper({ position = "top-center" }) {
188
+ const [whisps2, setWhisps] = (0, import_react.useState)([]);
189
+ (0, import_react.useEffect)(() => {
190
+ const unsubscribe = whispStore.subscribe(setWhisps);
191
+ return unsubscribe;
192
+ }, []);
193
+ const [vertical, horizontal] = position.split("-");
194
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
195
+ "div",
196
+ {
197
+ className: "whisp-container",
198
+ "data-vertical": vertical,
199
+ "data-horizontal": horizontal,
200
+ children: whisps2.map((w) => {
201
+ var _a;
202
+ const iconToShow = (_a = w.icon) != null ? _a : defaultIcons[w.type];
203
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
204
+ "div",
205
+ {
206
+ className: `whisp-card whisp-${w.type} ${w.closing ? "whisp-out" : ""}`,
207
+ onClick: () => whispStore.removeWhisp(w.id),
208
+ children: [
209
+ iconToShow && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "whisp-icon", children: iconToShow }),
210
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "whisp-message", children: w.message })
211
+ ]
212
+ },
213
+ w.id
214
+ );
215
+ })
216
+ }
217
+ );
218
+ }
219
+ // Annotate the CommonJS export names for ESM import in node:
220
+ 0 && (module.exports = {
221
+ Whisper,
222
+ whisp,
223
+ whispStore
224
+ });
225
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/store.ts","../src/Whisper.tsx","#style-inject:#style-inject","../src/styles.css","../src/icons/icons.tsx"],"sourcesContent":["export { whisp, whispStore } from './store'\nexport type { WhispItem, WhispType, WhispOptions } from './store'\nexport { Whisper } from './Whisper'\nexport type { WhisperProps } from './Whisper'\n","import type { ReactNode } from 'react'\n\nexport type WhispType = 'success' | 'error' | 'info' | 'alert' | 'default'\n\nexport interface WhispItem {\n id: number\n message: string\n type: WhispType\n icon?: ReactNode\n closing?: boolean\n}\n\nexport interface WhispOptions {\n duration?: number\n icon?: ReactNode\n}\n\ntype Listener = (whisps: WhispItem[]) => void\n\nlet whisps: WhispItem[] = []\nlet listeners: Listener[] = []\nlet idCounter = 0\n\nfunction emit() {\n listeners.forEach((listener) => listener(whisps))\n}\n\nfunction normalizeOptions(options?: number | WhispOptions): WhispOptions {\n if (typeof options === 'number') return { duration: options }\n return options ?? {}\n}\n\nfunction addWhisp(message: string, type: WhispType = 'default', options?: number | WhispOptions) {\n const { duration = 3000, icon } = normalizeOptions(options)\n const id = idCounter++\n whisps = [{ id, message, type, icon }]\n emit()\n\n if (duration > 0) {\n setTimeout(() => removeWhisp(id), duration)\n }\n\n return id\n}\n\nfunction removeWhisp(id: number) {\n const whisp = whisps.find(w => w.id === id)\n\n if (!whisp || whisp.closing) return\n\n whisps = whisps.map(w =>\n w.id === id ? { ...w, closing: true } : w\n )\n\n emit()\n\n setTimeout(() => {\n whisps = whisps.filter(w => w.id !== id)\n emit()\n }, 250)\n}\n\nfunction subscribe(listener: Listener) {\n listeners.push(listener)\n listener(whisps)\n return () => {\n listeners = listeners.filter((l) => l !== listener)\n }\n}\n\nexport const whisp = Object.assign(\n (message: string, options?: number | WhispOptions) => addWhisp(message, 'default', options),\n {\n success: (message: string, options?: number | WhispOptions) => addWhisp(message, 'success', options),\n error: (message: string, options?: number | WhispOptions) => addWhisp(message, 'error', options),\n info: (message: string, options?: number | WhispOptions) => addWhisp(message, 'info', options),\n alert: (message: string, options?: number | WhispOptions) => addWhisp(message, 'alert', options),\n }\n)\n\nexport const whispStore = { subscribe, removeWhisp }","'use client'\n\nimport { ReactNode, useEffect, useState } from 'react'\nimport { whispStore, WhispItem, WhispType } from './store'\nimport './styles.css'\nimport { icon } from './icons/icons'\n\nconst defaultIcons: Record<WhispType, ReactNode> = {\n success: icon.success,\n error: icon.error,\n info: icon.info,\n alert: icon.alert,\n default: null,\n}\n\nexport interface WhisperProps {\n position?: 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center'\n}\n\nexport function Whisper({ position = 'top-center' }: WhisperProps) {\n const [whisps, setWhisps] = useState<WhispItem[]>([])\n\n useEffect(() => {\n const unsubscribe = whispStore.subscribe(setWhisps)\n return unsubscribe\n }, [])\n\n const [vertical, horizontal] = position.split('-')\n\n return (\n <div\n className=\"whisp-container\"\n data-vertical={vertical}\n data-horizontal={horizontal}\n >\n {whisps.map((w) => {\n const iconToShow = w.icon ?? defaultIcons[w.type]\n\n return (\n <div\n key={w.id}\n className={`whisp-card whisp-${w.type} ${w.closing ? 'whisp-out' : ''}`}\n onClick={() => whispStore.removeWhisp(w.id)}\n >\n {iconToShow && <span className=\"whisp-icon\">{iconToShow}</span>}\n <span className=\"whisp-message\">{w.message}</span>\n </div>\n )\n })}\n </div>\n )\n}","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".whisp-container {\\n position: fixed;\\n display: flex;\\n flex-direction: column;\\n gap: 0.5rem;\\n z-index: 9999;\\n pointer-events: none;\\n}\\n.whisp-container[data-vertical=top] {\\n top: 1.5rem;\\n}\\n.whisp-container[data-vertical=bottom] {\\n bottom: 1.5rem;\\n flex-direction: column-reverse;\\n}\\n.whisp-container[data-horizontal=right] {\\n right: 1.5rem;\\n align-items: flex-end;\\n}\\n.whisp-container[data-horizontal=left] {\\n left: 1.5rem;\\n align-items: flex-start;\\n}\\n.whisp-container[data-horizontal=center] {\\n left: 50%;\\n transform: translateX(-50%);\\n align-items: center;\\n}\\n.whisp-card {\\n pointer-events: auto;\\n display: flex;\\n align-items: center;\\n gap: 0.6rem;\\n min-width: 280px;\\n max-width: 356px;\\n padding: 0.85rem 1rem;\\n background: var(--whisp-bg, #1a1a1a);\\n color: var(--whisp-text, #f5f5f5);\\n border: 1px solid var(--whisp-border, rgba(255, 255, 255, 0.08));\\n border-radius: var(--whisp-radius, 10px);\\n font-size: 0.85rem;\\n line-height: 1.3;\\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);\\n cursor: pointer;\\n animation: whisp-in 0.3s cubic-bezier(0.21, 1.02, 0.73, 1) forwards;\\n}\\n.whisp-icon {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 18px;\\n height: 18px;\\n border-radius: 50%;\\n font-size: 0.7rem;\\n flex-shrink: 0;\\n}\\n.whisp-success .whisp-icon {\\n background: var(--whisp-success-bg, #22c55e);\\n color: var(--whisp-success-color, #052e16);\\n}\\n.whisp-error .whisp-icon {\\n background: var(--whisp-error-bg, #ef4444);\\n color: var(--whisp-error-color, #450a0a);\\n}\\n.whisp-info .whisp-icon {\\n background: var(--whisp-info-bg, #3b82f6);\\n color: var(--whisp-info-color, #172554);\\n}\\n.whisp-alert .whisp-icon {\\n background: var(--whisp-alert-bg, #f59e0b);\\n color: var(--whisp-alert-color, #451a03);\\n}\\n.whisp-card.whisp-out {\\n animation: whisp-out 0.25s ease forwards;\\n}\\n.whisp-container[data-vertical=bottom] .whisp-card.whisp-out {\\n animation-name: whisp-out-bottom;\\n}\\n@keyframes whisp-in {\\n from {\\n transform: translateY(16px) scale(0.95);\\n opacity: 0;\\n }\\n to {\\n transform: translateY(0) scale(1);\\n opacity: 1;\\n }\\n}\\n@keyframes whisp-out {\\n from {\\n transform: translateY(0) scale(1);\\n opacity: 1;\\n }\\n to {\\n transform: translateY(-16px) scale(0.95);\\n opacity: 0;\\n }\\n}\\n.whisp-container[data-vertical=bottom] .whisp-card.whisp-out {\\n animation-name: whisp-out-bottom;\\n}\\n@keyframes whisp-out-bottom {\\n from {\\n transform: translateY(0) scale(1);\\n opacity: 1;\\n }\\n to {\\n transform: translateY(16px) scale(0.95);\\n opacity: 0;\\n }\\n}\\n\")","export const icon = {\n success: (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m9 12 2 2 4-4\" />\n </svg>\n ),\n error: (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m15 9-6 6\" />\n <path d=\"m9 9 6 6\" />\n </svg>\n ),\n info: (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n </svg>\n ),\n alert: (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" />\n <line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />\n </svg>\n ),\n}"],"mappings":"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","names":["icon","whisp","import_jsx_runtime","whisps"]}
@@ -0,0 +1,35 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ type WhispType = 'success' | 'error' | 'info' | 'alert' | 'default';
5
+ interface WhispItem {
6
+ id: number;
7
+ message: string;
8
+ type: WhispType;
9
+ icon?: ReactNode;
10
+ closing?: boolean;
11
+ }
12
+ interface WhispOptions {
13
+ duration?: number;
14
+ icon?: ReactNode;
15
+ }
16
+ type Listener = (whisps: WhispItem[]) => void;
17
+ declare function removeWhisp(id: number): void;
18
+ declare function subscribe(listener: Listener): () => void;
19
+ declare const whisp: ((message: string, options?: number | WhispOptions) => number) & {
20
+ success: (message: string, options?: number | WhispOptions) => number;
21
+ error: (message: string, options?: number | WhispOptions) => number;
22
+ info: (message: string, options?: number | WhispOptions) => number;
23
+ alert: (message: string, options?: number | WhispOptions) => number;
24
+ };
25
+ declare const whispStore: {
26
+ subscribe: typeof subscribe;
27
+ removeWhisp: typeof removeWhisp;
28
+ };
29
+
30
+ interface WhisperProps {
31
+ position?: 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center';
32
+ }
33
+ declare function Whisper({ position }: WhisperProps): react.JSX.Element;
34
+
35
+ export { type WhispItem, type WhispOptions, type WhispType, Whisper, type WhisperProps, whisp, whispStore };
@@ -0,0 +1,35 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ type WhispType = 'success' | 'error' | 'info' | 'alert' | 'default';
5
+ interface WhispItem {
6
+ id: number;
7
+ message: string;
8
+ type: WhispType;
9
+ icon?: ReactNode;
10
+ closing?: boolean;
11
+ }
12
+ interface WhispOptions {
13
+ duration?: number;
14
+ icon?: ReactNode;
15
+ }
16
+ type Listener = (whisps: WhispItem[]) => void;
17
+ declare function removeWhisp(id: number): void;
18
+ declare function subscribe(listener: Listener): () => void;
19
+ declare const whisp: ((message: string, options?: number | WhispOptions) => number) & {
20
+ success: (message: string, options?: number | WhispOptions) => number;
21
+ error: (message: string, options?: number | WhispOptions) => number;
22
+ info: (message: string, options?: number | WhispOptions) => number;
23
+ alert: (message: string, options?: number | WhispOptions) => number;
24
+ };
25
+ declare const whispStore: {
26
+ subscribe: typeof subscribe;
27
+ removeWhisp: typeof removeWhisp;
28
+ };
29
+
30
+ interface WhisperProps {
31
+ position?: 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center';
32
+ }
33
+ declare function Whisper({ position }: WhisperProps): react.JSX.Element;
34
+
35
+ export { type WhispItem, type WhispOptions, type WhispType, Whisper, type WhisperProps, whisp, whispStore };
package/dist/index.js ADDED
@@ -0,0 +1,199 @@
1
+ "use client"
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+
22
+ // src/store.ts
23
+ var whisps = [];
24
+ var listeners = [];
25
+ var idCounter = 0;
26
+ function emit() {
27
+ listeners.forEach((listener) => listener(whisps));
28
+ }
29
+ function normalizeOptions(options) {
30
+ if (typeof options === "number") return { duration: options };
31
+ return options != null ? options : {};
32
+ }
33
+ function addWhisp(message, type = "default", options) {
34
+ const { duration = 3e3, icon: icon2 } = normalizeOptions(options);
35
+ const id = idCounter++;
36
+ whisps = [{ id, message, type, icon: icon2 }];
37
+ emit();
38
+ if (duration > 0) {
39
+ setTimeout(() => removeWhisp(id), duration);
40
+ }
41
+ return id;
42
+ }
43
+ function removeWhisp(id) {
44
+ const whisp2 = whisps.find((w) => w.id === id);
45
+ if (!whisp2 || whisp2.closing) return;
46
+ whisps = whisps.map(
47
+ (w) => w.id === id ? __spreadProps(__spreadValues({}, w), { closing: true }) : w
48
+ );
49
+ emit();
50
+ setTimeout(() => {
51
+ whisps = whisps.filter((w) => w.id !== id);
52
+ emit();
53
+ }, 250);
54
+ }
55
+ function subscribe(listener) {
56
+ listeners.push(listener);
57
+ listener(whisps);
58
+ return () => {
59
+ listeners = listeners.filter((l) => l !== listener);
60
+ };
61
+ }
62
+ var whisp = Object.assign(
63
+ (message, options) => addWhisp(message, "default", options),
64
+ {
65
+ success: (message, options) => addWhisp(message, "success", options),
66
+ error: (message, options) => addWhisp(message, "error", options),
67
+ info: (message, options) => addWhisp(message, "info", options),
68
+ alert: (message, options) => addWhisp(message, "alert", options)
69
+ }
70
+ );
71
+ var whispStore = { subscribe, removeWhisp };
72
+
73
+ // src/Whisper.tsx
74
+ import { useEffect, useState } from "react";
75
+
76
+ // #style-inject:#style-inject
77
+ function styleInject(css, { insertAt } = {}) {
78
+ if (!css || typeof document === "undefined") return;
79
+ const head = document.head || document.getElementsByTagName("head")[0];
80
+ const style = document.createElement("style");
81
+ style.type = "text/css";
82
+ if (insertAt === "top") {
83
+ if (head.firstChild) {
84
+ head.insertBefore(style, head.firstChild);
85
+ } else {
86
+ head.appendChild(style);
87
+ }
88
+ } else {
89
+ head.appendChild(style);
90
+ }
91
+ if (style.styleSheet) {
92
+ style.styleSheet.cssText = css;
93
+ } else {
94
+ style.appendChild(document.createTextNode(css));
95
+ }
96
+ }
97
+
98
+ // src/styles.css
99
+ styleInject(".whisp-container {\n position: fixed;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n z-index: 9999;\n pointer-events: none;\n}\n.whisp-container[data-vertical=top] {\n top: 1.5rem;\n}\n.whisp-container[data-vertical=bottom] {\n bottom: 1.5rem;\n flex-direction: column-reverse;\n}\n.whisp-container[data-horizontal=right] {\n right: 1.5rem;\n align-items: flex-end;\n}\n.whisp-container[data-horizontal=left] {\n left: 1.5rem;\n align-items: flex-start;\n}\n.whisp-container[data-horizontal=center] {\n left: 50%;\n transform: translateX(-50%);\n align-items: center;\n}\n.whisp-card {\n pointer-events: auto;\n display: flex;\n align-items: center;\n gap: 0.6rem;\n min-width: 280px;\n max-width: 356px;\n padding: 0.85rem 1rem;\n background: var(--whisp-bg, #1a1a1a);\n color: var(--whisp-text, #f5f5f5);\n border: 1px solid var(--whisp-border, rgba(255, 255, 255, 0.08));\n border-radius: var(--whisp-radius, 10px);\n font-size: 0.85rem;\n line-height: 1.3;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);\n cursor: pointer;\n animation: whisp-in 0.3s cubic-bezier(0.21, 1.02, 0.73, 1) forwards;\n}\n.whisp-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n font-size: 0.7rem;\n flex-shrink: 0;\n}\n.whisp-success .whisp-icon {\n background: var(--whisp-success-bg, #22c55e);\n color: var(--whisp-success-color, #052e16);\n}\n.whisp-error .whisp-icon {\n background: var(--whisp-error-bg, #ef4444);\n color: var(--whisp-error-color, #450a0a);\n}\n.whisp-info .whisp-icon {\n background: var(--whisp-info-bg, #3b82f6);\n color: var(--whisp-info-color, #172554);\n}\n.whisp-alert .whisp-icon {\n background: var(--whisp-alert-bg, #f59e0b);\n color: var(--whisp-alert-color, #451a03);\n}\n.whisp-card.whisp-out {\n animation: whisp-out 0.25s ease forwards;\n}\n.whisp-container[data-vertical=bottom] .whisp-card.whisp-out {\n animation-name: whisp-out-bottom;\n}\n@keyframes whisp-in {\n from {\n transform: translateY(16px) scale(0.95);\n opacity: 0;\n }\n to {\n transform: translateY(0) scale(1);\n opacity: 1;\n }\n}\n@keyframes whisp-out {\n from {\n transform: translateY(0) scale(1);\n opacity: 1;\n }\n to {\n transform: translateY(-16px) scale(0.95);\n opacity: 0;\n }\n}\n.whisp-container[data-vertical=bottom] .whisp-card.whisp-out {\n animation-name: whisp-out-bottom;\n}\n@keyframes whisp-out-bottom {\n from {\n transform: translateY(0) scale(1);\n opacity: 1;\n }\n to {\n transform: translateY(16px) scale(0.95);\n opacity: 0;\n }\n}\n");
100
+
101
+ // src/icons/icons.tsx
102
+ import { jsx, jsxs } from "react/jsx-runtime";
103
+ var icon = {
104
+ success: /* @__PURE__ */ jsxs(
105
+ "svg",
106
+ {
107
+ xmlns: "http://www.w3.org/2000/svg",
108
+ width: "16",
109
+ height: "16",
110
+ viewBox: "0 0 24 24",
111
+ fill: "none",
112
+ stroke: "currentColor",
113
+ strokeWidth: "2",
114
+ strokeLinecap: "round",
115
+ strokeLinejoin: "round",
116
+ children: [
117
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
118
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
119
+ ]
120
+ }
121
+ ),
122
+ error: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
123
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
124
+ /* @__PURE__ */ jsx("path", { d: "m15 9-6 6" }),
125
+ /* @__PURE__ */ jsx("path", { d: "m9 9 6 6" })
126
+ ] }),
127
+ info: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
128
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
129
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
130
+ /* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
131
+ ] }),
132
+ alert: /* @__PURE__ */ jsxs(
133
+ "svg",
134
+ {
135
+ xmlns: "http://www.w3.org/2000/svg",
136
+ width: "16",
137
+ height: "16",
138
+ viewBox: "0 0 24 24",
139
+ fill: "none",
140
+ stroke: "currentColor",
141
+ strokeWidth: "2",
142
+ strokeLinecap: "round",
143
+ strokeLinejoin: "round",
144
+ children: [
145
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
146
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "8", y2: "12" }),
147
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "12.01", y1: "16", y2: "16" })
148
+ ]
149
+ }
150
+ )
151
+ };
152
+
153
+ // src/Whisper.tsx
154
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
155
+ var defaultIcons = {
156
+ success: icon.success,
157
+ error: icon.error,
158
+ info: icon.info,
159
+ alert: icon.alert,
160
+ default: null
161
+ };
162
+ function Whisper({ position = "top-center" }) {
163
+ const [whisps2, setWhisps] = useState([]);
164
+ useEffect(() => {
165
+ const unsubscribe = whispStore.subscribe(setWhisps);
166
+ return unsubscribe;
167
+ }, []);
168
+ const [vertical, horizontal] = position.split("-");
169
+ return /* @__PURE__ */ jsx2(
170
+ "div",
171
+ {
172
+ className: "whisp-container",
173
+ "data-vertical": vertical,
174
+ "data-horizontal": horizontal,
175
+ children: whisps2.map((w) => {
176
+ var _a;
177
+ const iconToShow = (_a = w.icon) != null ? _a : defaultIcons[w.type];
178
+ return /* @__PURE__ */ jsxs2(
179
+ "div",
180
+ {
181
+ className: `whisp-card whisp-${w.type} ${w.closing ? "whisp-out" : ""}`,
182
+ onClick: () => whispStore.removeWhisp(w.id),
183
+ children: [
184
+ iconToShow && /* @__PURE__ */ jsx2("span", { className: "whisp-icon", children: iconToShow }),
185
+ /* @__PURE__ */ jsx2("span", { className: "whisp-message", children: w.message })
186
+ ]
187
+ },
188
+ w.id
189
+ );
190
+ })
191
+ }
192
+ );
193
+ }
194
+ export {
195
+ Whisper,
196
+ whisp,
197
+ whispStore
198
+ };
199
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store.ts","../src/Whisper.tsx","#style-inject:#style-inject","../src/styles.css","../src/icons/icons.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\n\nexport type WhispType = 'success' | 'error' | 'info' | 'alert' | 'default'\n\nexport interface WhispItem {\n id: number\n message: string\n type: WhispType\n icon?: ReactNode\n closing?: boolean\n}\n\nexport interface WhispOptions {\n duration?: number\n icon?: ReactNode\n}\n\ntype Listener = (whisps: WhispItem[]) => void\n\nlet whisps: WhispItem[] = []\nlet listeners: Listener[] = []\nlet idCounter = 0\n\nfunction emit() {\n listeners.forEach((listener) => listener(whisps))\n}\n\nfunction normalizeOptions(options?: number | WhispOptions): WhispOptions {\n if (typeof options === 'number') return { duration: options }\n return options ?? {}\n}\n\nfunction addWhisp(message: string, type: WhispType = 'default', options?: number | WhispOptions) {\n const { duration = 3000, icon } = normalizeOptions(options)\n const id = idCounter++\n whisps = [{ id, message, type, icon }]\n emit()\n\n if (duration > 0) {\n setTimeout(() => removeWhisp(id), duration)\n }\n\n return id\n}\n\nfunction removeWhisp(id: number) {\n const whisp = whisps.find(w => w.id === id)\n\n if (!whisp || whisp.closing) return\n\n whisps = whisps.map(w =>\n w.id === id ? { ...w, closing: true } : w\n )\n\n emit()\n\n setTimeout(() => {\n whisps = whisps.filter(w => w.id !== id)\n emit()\n }, 250)\n}\n\nfunction subscribe(listener: Listener) {\n listeners.push(listener)\n listener(whisps)\n return () => {\n listeners = listeners.filter((l) => l !== listener)\n }\n}\n\nexport const whisp = Object.assign(\n (message: string, options?: number | WhispOptions) => addWhisp(message, 'default', options),\n {\n success: (message: string, options?: number | WhispOptions) => addWhisp(message, 'success', options),\n error: (message: string, options?: number | WhispOptions) => addWhisp(message, 'error', options),\n info: (message: string, options?: number | WhispOptions) => addWhisp(message, 'info', options),\n alert: (message: string, options?: number | WhispOptions) => addWhisp(message, 'alert', options),\n }\n)\n\nexport const whispStore = { subscribe, removeWhisp }","'use client'\n\nimport { ReactNode, useEffect, useState } from 'react'\nimport { whispStore, WhispItem, WhispType } from './store'\nimport './styles.css'\nimport { icon } from './icons/icons'\n\nconst defaultIcons: Record<WhispType, ReactNode> = {\n success: icon.success,\n error: icon.error,\n info: icon.info,\n alert: icon.alert,\n default: null,\n}\n\nexport interface WhisperProps {\n position?: 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center'\n}\n\nexport function Whisper({ position = 'top-center' }: WhisperProps) {\n const [whisps, setWhisps] = useState<WhispItem[]>([])\n\n useEffect(() => {\n const unsubscribe = whispStore.subscribe(setWhisps)\n return unsubscribe\n }, [])\n\n const [vertical, horizontal] = position.split('-')\n\n return (\n <div\n className=\"whisp-container\"\n data-vertical={vertical}\n data-horizontal={horizontal}\n >\n {whisps.map((w) => {\n const iconToShow = w.icon ?? defaultIcons[w.type]\n\n return (\n <div\n key={w.id}\n className={`whisp-card whisp-${w.type} ${w.closing ? 'whisp-out' : ''}`}\n onClick={() => whispStore.removeWhisp(w.id)}\n >\n {iconToShow && <span className=\"whisp-icon\">{iconToShow}</span>}\n <span className=\"whisp-message\">{w.message}</span>\n </div>\n )\n })}\n </div>\n )\n}","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".whisp-container {\\n position: fixed;\\n display: flex;\\n flex-direction: column;\\n gap: 0.5rem;\\n z-index: 9999;\\n pointer-events: none;\\n}\\n.whisp-container[data-vertical=top] {\\n top: 1.5rem;\\n}\\n.whisp-container[data-vertical=bottom] {\\n bottom: 1.5rem;\\n flex-direction: column-reverse;\\n}\\n.whisp-container[data-horizontal=right] {\\n right: 1.5rem;\\n align-items: flex-end;\\n}\\n.whisp-container[data-horizontal=left] {\\n left: 1.5rem;\\n align-items: flex-start;\\n}\\n.whisp-container[data-horizontal=center] {\\n left: 50%;\\n transform: translateX(-50%);\\n align-items: center;\\n}\\n.whisp-card {\\n pointer-events: auto;\\n display: flex;\\n align-items: center;\\n gap: 0.6rem;\\n min-width: 280px;\\n max-width: 356px;\\n padding: 0.85rem 1rem;\\n background: var(--whisp-bg, #1a1a1a);\\n color: var(--whisp-text, #f5f5f5);\\n border: 1px solid var(--whisp-border, rgba(255, 255, 255, 0.08));\\n border-radius: var(--whisp-radius, 10px);\\n font-size: 0.85rem;\\n line-height: 1.3;\\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);\\n cursor: pointer;\\n animation: whisp-in 0.3s cubic-bezier(0.21, 1.02, 0.73, 1) forwards;\\n}\\n.whisp-icon {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 18px;\\n height: 18px;\\n border-radius: 50%;\\n font-size: 0.7rem;\\n flex-shrink: 0;\\n}\\n.whisp-success .whisp-icon {\\n background: var(--whisp-success-bg, #22c55e);\\n color: var(--whisp-success-color, #052e16);\\n}\\n.whisp-error .whisp-icon {\\n background: var(--whisp-error-bg, #ef4444);\\n color: var(--whisp-error-color, #450a0a);\\n}\\n.whisp-info .whisp-icon {\\n background: var(--whisp-info-bg, #3b82f6);\\n color: var(--whisp-info-color, #172554);\\n}\\n.whisp-alert .whisp-icon {\\n background: var(--whisp-alert-bg, #f59e0b);\\n color: var(--whisp-alert-color, #451a03);\\n}\\n.whisp-card.whisp-out {\\n animation: whisp-out 0.25s ease forwards;\\n}\\n.whisp-container[data-vertical=bottom] .whisp-card.whisp-out {\\n animation-name: whisp-out-bottom;\\n}\\n@keyframes whisp-in {\\n from {\\n transform: translateY(16px) scale(0.95);\\n opacity: 0;\\n }\\n to {\\n transform: translateY(0) scale(1);\\n opacity: 1;\\n }\\n}\\n@keyframes whisp-out {\\n from {\\n transform: translateY(0) scale(1);\\n opacity: 1;\\n }\\n to {\\n transform: translateY(-16px) scale(0.95);\\n opacity: 0;\\n }\\n}\\n.whisp-container[data-vertical=bottom] .whisp-card.whisp-out {\\n animation-name: whisp-out-bottom;\\n}\\n@keyframes whisp-out-bottom {\\n from {\\n transform: translateY(0) scale(1);\\n opacity: 1;\\n }\\n to {\\n transform: translateY(16px) scale(0.95);\\n opacity: 0;\\n }\\n}\\n\")","export const icon = {\n success: (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m9 12 2 2 4-4\" />\n </svg>\n ),\n error: (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m15 9-6 6\" />\n <path d=\"m9 9 6 6\" />\n </svg>\n ),\n info: (\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n </svg>\n ),\n alert: (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" />\n <line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />\n </svg>\n ),\n}"],"mappings":"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","names":["icon","whisp","jsx","jsxs","whisps"]}
@@ -0,0 +1,128 @@
1
+ .whisp-container {
2
+ position: fixed;
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 0.5rem;
6
+ z-index: 9999;
7
+ pointer-events: none;
8
+ }
9
+
10
+ .whisp-container[data-vertical='top'] {
11
+ top: 1.5rem;
12
+ }
13
+
14
+ .whisp-container[data-vertical='bottom'] {
15
+ bottom: 1.5rem;
16
+ flex-direction: column-reverse;
17
+ }
18
+
19
+ .whisp-container[data-horizontal='right'] {
20
+ right: 1.5rem;
21
+ align-items: flex-end;
22
+ }
23
+
24
+ .whisp-container[data-horizontal='left'] {
25
+ left: 1.5rem;
26
+ align-items: flex-start;
27
+ }
28
+
29
+ .whisp-container[data-horizontal='center'] {
30
+ left: 50%;
31
+ transform: translateX(-50%);
32
+ align-items: center;
33
+ }
34
+
35
+ .whisp-card {
36
+ pointer-events: auto;
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 0.6rem;
40
+ min-width: 280px;
41
+ max-width: 356px;
42
+ padding: 0.85rem 1rem;
43
+ background: var(--whisp-bg, #1a1a1a);
44
+ color: var(--whisp-text, #f5f5f5);
45
+ border: 1px solid var(--whisp-border, rgba(255, 255, 255, 0.08));
46
+ border-radius: var(--whisp-radius, 10px);
47
+ font-size: 0.85rem;
48
+ line-height: 1.3;
49
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
50
+ cursor: pointer;
51
+ animation: whisp-in 0.3s cubic-bezier(0.21, 1.02, 0.73, 1) forwards;
52
+ }
53
+
54
+ .whisp-icon {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ width: 18px;
59
+ height: 18px;
60
+ border-radius: 50%;
61
+ font-size: 0.7rem;
62
+ flex-shrink: 0;
63
+ }
64
+
65
+ .whisp-success .whisp-icon {
66
+ background: var(--whisp-success-bg, #22c55e);
67
+ color: var(--whisp-success-color, #052e16);
68
+ }
69
+
70
+ .whisp-error .whisp-icon {
71
+ background: var(--whisp-error-bg, #ef4444);
72
+ color: var(--whisp-error-color, #450a0a);
73
+ }
74
+
75
+ .whisp-info .whisp-icon {
76
+ background: var(--whisp-info-bg, #3b82f6);
77
+ color: var(--whisp-info-color, #172554);
78
+ }
79
+
80
+ .whisp-alert .whisp-icon {
81
+ background: var(--whisp-alert-bg, #f59e0b);
82
+ color: var(--whisp-alert-color, #451a03);
83
+ }
84
+
85
+ .whisp-card.whisp-out {
86
+ animation: whisp-out 0.25s ease forwards;
87
+ }
88
+
89
+ .whisp-container[data-vertical='bottom'] .whisp-card.whisp-out {
90
+ animation-name: whisp-out-bottom;
91
+ }
92
+
93
+ @keyframes whisp-in {
94
+ from {
95
+ transform: translateY(16px) scale(0.95);
96
+ opacity: 0;
97
+ }
98
+ to {
99
+ transform: translateY(0) scale(1);
100
+ opacity: 1;
101
+ }
102
+ }
103
+
104
+ @keyframes whisp-out {
105
+ from {
106
+ transform: translateY(0) scale(1);
107
+ opacity: 1;
108
+ }
109
+ to {
110
+ transform: translateY(-16px) scale(0.95);
111
+ opacity: 0;
112
+ }
113
+ }
114
+
115
+ .whisp-container[data-vertical='bottom'] .whisp-card.whisp-out {
116
+ animation-name: whisp-out-bottom;
117
+ }
118
+
119
+ @keyframes whisp-out-bottom {
120
+ from {
121
+ transform: translateY(0) scale(1);
122
+ opacity: 1;
123
+ }
124
+ to {
125
+ transform: translateY(16px) scale(0.95);
126
+ opacity: 0;
127
+ }
128
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "whisp-toast",
3
+ "version": "0.1.0",
4
+ "description": "Librería ligera de toasts para React/Next.js, sin dependencias, con API global estilo toast.success().",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "react",
26
+ "nextjs",
27
+ "toast",
28
+ "notifications",
29
+ "notification",
30
+ "ui"
31
+ ],
32
+ "author": "Alex Aperador Salcedo",
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "react": ">=18.0.0",
36
+ "react-dom": ">=18.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "^18.3.0",
40
+ "@types/react-dom": "^18.3.0",
41
+ "react": "^18.3.0",
42
+ "react-dom": "^18.3.0",
43
+ "tsup": "^8.0.0",
44
+ "typescript": "^5.5.0"
45
+ },
46
+ "homepage": "https://github.com/alexaperador/whisp-toast#readme",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/alexaperador/whisp-toast.git"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/alexaperador/whisp-toast/issues"
53
+ }
54
+ }