kitzo 1.1.7 → 2.0.1

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 CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/kitzo)](https://www.npmjs.com/package/kitzo)
4
4
 
5
- ### A lightweight tool
5
+ ### A lightweight library of Vanilla js and React js
6
6
 
7
- Current features
7
+ ##### [Vanilla js](#quick-usage-overview-vanilla-js)
8
8
 
9
9
  - Copy on click
10
10
  - Tooltip on mouseover
@@ -12,6 +12,10 @@ Current features
12
12
  - Debounce function
13
13
  - Hover clip-path effect
14
14
 
15
+ ##### [React js](#react)
16
+
17
+ - React Toast notifications
18
+
15
19
  #### Install
16
20
 
17
21
  ```bash
@@ -21,17 +25,16 @@ npm i kitzo
21
25
  or
22
26
 
23
27
  ```javascript
24
- <script src="https://cdn.jsdelivr.net/npm/kitzo@1.1.7/dist/kitzo.umd.min.js"></script>
28
+ <script src="https://cdn.jsdelivr.net/npm/kitzo@2.0.1/dist/kitzo.umd.min.js"></script>
25
29
  ```
26
30
 
27
31
  > Attach this script tag in the html head tag and you are good to go.
28
32
 
29
33
  ---
30
34
 
31
- #### Quick usage overview
32
-
35
+ #### Quick usage overview: Vanilla js
33
36
 
34
- | [API](#apis) |
37
+ | [API](#vanilla-apis) |
35
38
  | ----------------------------------- |
36
39
  | [`kitzo.copy()`](#copy-api) |
37
40
  | [`kitzo.tooltip()`](#tooltip-api) |
@@ -39,7 +42,8 @@ or
39
42
  | [`kitzo.debounce()`](#debounce-api) |
40
43
  | [`kitzo.clippath()`](#clippath-api) |
41
44
 
42
- #### APIs
45
+ #### Vanilla APIs
46
+
43
47
  ```javascript
44
48
  // NPM usage
45
49
  import kitzo from 'kitzo';
@@ -122,3 +126,78 @@ kitzo.clippath(selectors | element | NodeList, {
122
126
  style: object,
123
127
  });
124
128
  ```
129
+
130
+ ---
131
+
132
+ ## React
133
+
134
+ #### Install
135
+
136
+ ```bash
137
+ npm i kitzo
138
+ ```
139
+
140
+ #### React APIs
141
+
142
+ ```jsx
143
+ import { ToastContainer, toast, ... } from 'kitzo/react';
144
+ ```
145
+
146
+ ##### Toast API:
147
+
148
+ ```jsx
149
+ import { toast } from 'kitzo/react';
150
+
151
+ toast.success('toast message', {});
152
+ toast.error('toast message', {});
153
+ toast.promise(
154
+ promise(),
155
+ {
156
+ loading: 'Saving...',
157
+ success: 'Saved',
158
+ error: 'Error occured',
159
+ },
160
+ { duration: 2000 }
161
+ );
162
+ ```
163
+
164
+ ##### Toast API Usage
165
+
166
+ ```jsx
167
+ import { ToastContainer, toast } from 'kitzo/react';
168
+
169
+ function App() {
170
+ function fakePromise() {
171
+ return new Promise((resolved, rejected) => {
172
+ setTimeout(() => {
173
+ Math.random() > 0.5 ? resolved('resolved') : rejected('rejected');
174
+ }, 2000)
175
+ })
176
+ }
177
+
178
+ return (
179
+ <div>
180
+ <button onClick={() => toast.success('✅ Success toast message')}>Succes</button>
181
+ <button onClick={() => toast.error('❌ Error toast message')}>Error</button>
182
+ <button
183
+ onClick={() => {
184
+ toast.promise(
185
+ fakePromise(),
186
+ {
187
+ loading: 'Saving data...',
188
+ success: 'Data saved',
189
+ error: 'Failed saving data',
190
+ },
191
+ { duration: 2500 }
192
+ );
193
+ }}
194
+ >
195
+ Promise
196
+ </button>
197
+
198
+ {/* Toast container must needed */}
199
+ <ToastContainer position="top-center" />
200
+ </div>
201
+ );
202
+ }
203
+ ```
@@ -0,0 +1,20 @@
1
+ export interface ToastOptions {
2
+ duration?: number;
3
+ }
4
+
5
+ export interface ToastAPI {
6
+ success(text?: string, options?: ToastOptions): void;
7
+ error(text?: string, options?: ToastOptions): void;
8
+ promise<T>(
9
+ callback: Promise<T>,
10
+ msgs?: {
11
+ loading?: string;
12
+ success?: string | ((res: T) => string);
13
+ error?: string | ((err: Error) => string);
14
+ },
15
+ options?: ToastOptions
16
+ ): Promise<T>;
17
+ }
18
+
19
+ export declare const toast: ToastAPI;
20
+ export declare function ToastContainer(props: { position?: string }): JSX.Element;
@@ -0,0 +1,529 @@
1
+ import React, { useRef, useLayoutEffect, useState, useEffect } from 'react';
2
+
3
+ const listeners = new Set();
4
+ let isStyleAdded = false;
5
+ function toastStyles() {
6
+ return `@layer base {
7
+ .toast-content,
8
+ .toast-content-bottom {
9
+ font-size: 0.925rem;
10
+ }
11
+ }
12
+
13
+ .toast-content,
14
+ .toast-content-bottom {
15
+ pointer-events: all;
16
+ padding-inline: 0.625rem 0.825rem;
17
+ padding-block: 0.625rem;
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 0.5rem;
21
+ background-color: white;
22
+ border-radius: 0.5525rem;
23
+ box-shadow: 0 2px 8px -3px rgba(0, 0, 0, 0.3);
24
+ }
25
+
26
+ .toast-content {
27
+ animation: slide-in 230ms forwards;
28
+ }
29
+ .toast-content.exit {
30
+ animation: slide-out 110ms forwards;
31
+ }
32
+
33
+ .toast-content-bottom {
34
+ animation: bottom-slide-in 230ms forwards;
35
+ }
36
+ .toast-content-bottom.exit {
37
+ animation: bottom-slide-out 110ms forwards;
38
+ }
39
+
40
+ @keyframes slide-in {
41
+ from {
42
+ opacity: 0;
43
+ translate: 0 -100%;
44
+ scale: 0.7;
45
+ }
46
+ to {
47
+ opacity: 1;
48
+ translate: 0 0;
49
+ scale: 1;
50
+ }
51
+ }
52
+
53
+ @keyframes slide-out {
54
+ from {
55
+ opacity: 1;
56
+ translate: 0 0;
57
+ scale: 1;
58
+ }
59
+ to {
60
+ opacity: 0;
61
+ translate: 0 -100%;
62
+ scale: 0.7;
63
+ }
64
+ }
65
+
66
+ @keyframes bottom-slide-in {
67
+ from {
68
+ opacity: 0;
69
+ translate: 0 100%;
70
+ scale: 0.7;
71
+ }
72
+ to {
73
+ opacity: 1;
74
+ translate: 0 0;
75
+ scale: 1;
76
+ }
77
+ }
78
+
79
+ @keyframes bottom-slide-out {
80
+ from {
81
+ opacity: 1;
82
+ translate: 0 0;
83
+ scale: 1;
84
+ }
85
+ to {
86
+ opacity: 0;
87
+ translate: 0 100%;
88
+ scale: 0.7;
89
+ }
90
+ }
91
+
92
+ .svg-container {
93
+ display: grid;
94
+ place-items: center;
95
+ border-radius: 10rem;
96
+ background-color: #61d345;
97
+ color: white;
98
+ height: 20px;
99
+ width: 20px;
100
+ position: relative;
101
+ overflow: hidden;
102
+
103
+ scale: 1.1;
104
+ animation: svg-container-animation 400ms ease-in-out forwards;
105
+
106
+ svg {
107
+ width: 14px;
108
+ height: 14px;
109
+ stroke-width: 4px;
110
+ display: inline-block;
111
+ }
112
+ }
113
+
114
+ .svg-container.success {
115
+ background-color: #61d345;
116
+ }
117
+ .svg-container.error {
118
+ background-color: #ff4b4b;
119
+
120
+ svg {
121
+ scale: 0;
122
+ animation: error-svg-zoom-in 170ms 130ms forwards;
123
+ }
124
+ }
125
+
126
+ .svg-container.success::before {
127
+ content: '';
128
+ position: absolute;
129
+ inset: 0;
130
+ z-index: 5;
131
+ border-radius: 10rem;
132
+ background-color: #61d345;
133
+ animation: success-container-before-animation 250ms 150ms forwards;
134
+ }
135
+
136
+ @keyframes svg-container-animation {
137
+ 0% {
138
+ scale: 1;
139
+ }
140
+ 50% {
141
+ scale: 1.15;
142
+ }
143
+ 100% {
144
+ scale: 1;
145
+ }
146
+ }
147
+
148
+ @keyframes success-container-before-animation {
149
+ from {
150
+ translate: 0 0;
151
+ }
152
+ to {
153
+ translate: 100% -50%;
154
+ }
155
+ }
156
+
157
+ @keyframes error-svg-zoom-in {
158
+ from {
159
+ scale: 0;
160
+ }
161
+ to {
162
+ scale: 1;
163
+ }
164
+ }
165
+
166
+ .promise-svg-container {
167
+ width: 20px;
168
+ height: 20px;
169
+ display: grid;
170
+ place-items: center;
171
+ color: #474747;
172
+
173
+ svg {
174
+ width: 14px;
175
+ height: 14px;
176
+ stroke-width: 3px;
177
+ display: inline-block;
178
+ animation: rotate-infinity 1000ms infinite linear;
179
+ }
180
+ }
181
+
182
+ @keyframes rotate-infinity {
183
+ to {
184
+ rotate: 360deg;
185
+ }
186
+ }
187
+ `;
188
+ }
189
+ function addToast(toast) {
190
+ listeners.forEach((callback) => {
191
+ callback(toast);
192
+ });
193
+ }
194
+ function subscribe(callback) {
195
+ if (!isStyleAdded) {
196
+ const styleTag = document.createElement('style');
197
+ styleTag.textContent = toastStyles();
198
+ document.head.appendChild(styleTag);
199
+ isStyleAdded = true;
200
+ }
201
+ listeners.add(callback);
202
+ return () => listeners.delete(callback);
203
+ }
204
+ function success(text = 'Toast success', options = {}) {
205
+ const id = Date.now();
206
+ options = Object.assign(
207
+ {
208
+ duration: 2000,
209
+ id,
210
+ },
211
+ options
212
+ );
213
+ addToast({
214
+ type: 'success',
215
+ text,
216
+ options,
217
+ });
218
+ }
219
+ function error(text = 'Toast denied', options = {}) {
220
+ const id = Date.now();
221
+ options = Object.assign(
222
+ {
223
+ duration: 2000,
224
+ id,
225
+ },
226
+ options
227
+ );
228
+ addToast({
229
+ type: 'error',
230
+ text,
231
+ options,
232
+ });
233
+ }
234
+ function promise(callback, msgs = {}, options = {}) {
235
+ const id = Date.now();
236
+ options = Object.assign(
237
+ {
238
+ duration: 2000,
239
+ id,
240
+ },
241
+ options
242
+ );
243
+ msgs = Object.assign(
244
+ {
245
+ loading: 'Saving...',
246
+ success: 'Success',
247
+ error: 'Something went wrong',
248
+ },
249
+ msgs
250
+ );
251
+ addToast({
252
+ type: 'loading',
253
+ text: msgs.loading,
254
+ options: {
255
+ ...options,
256
+ duration: Infinity,
257
+ id,
258
+ },
259
+ });
260
+ callback
261
+ .then((res) => {
262
+ const successMsg = typeof msgs.success === 'function' ? msgs.success(res) : msgs.success;
263
+ addToast({
264
+ type: 'success',
265
+ text: successMsg,
266
+ options,
267
+ });
268
+ return res;
269
+ })
270
+ .catch((err) => {
271
+ const normalizedError = err instanceof Error ? err : new Error(String(err));
272
+ const errorMsg = typeof msgs.error === 'function' ? msgs.error(normalizedError) : msgs.error;
273
+ addToast({
274
+ type: 'error',
275
+ text: errorMsg,
276
+ options,
277
+ });
278
+ return Promise.reject(normalizedError);
279
+ });
280
+ }
281
+
282
+ function SuccessSvg() {
283
+ return /*#__PURE__*/ React.createElement(
284
+ 'div',
285
+ {
286
+ className: 'svg-container success',
287
+ },
288
+ /*#__PURE__*/ React.createElement(
289
+ 'svg',
290
+ {
291
+ xmlns: 'http://www.w3.org/2000/svg',
292
+ width: '24',
293
+ height: '24',
294
+ viewBox: '0 0 24 24',
295
+ fill: 'none',
296
+ stroke: 'currentColor',
297
+ strokeWidth: '2',
298
+ strokeLinecap: 'round',
299
+ strokeLinejoin: 'round',
300
+ },
301
+ /*#__PURE__*/ React.createElement('path', {
302
+ d: 'M20 6 9 17l-5-5',
303
+ })
304
+ )
305
+ );
306
+ }
307
+ function ErrorSvg() {
308
+ return /*#__PURE__*/ React.createElement(
309
+ 'div',
310
+ {
311
+ className: 'svg-container error',
312
+ },
313
+ /*#__PURE__*/ React.createElement(
314
+ 'svg',
315
+ {
316
+ xmlns: 'http://www.w3.org/2000/svg',
317
+ width: '24',
318
+ height: '24',
319
+ viewBox: '0 0 24 24',
320
+ fill: 'none',
321
+ stroke: 'currentColor',
322
+ strokeWidth: '2',
323
+ strokeLinecap: 'round',
324
+ strokeLinejoin: 'round',
325
+ },
326
+ /*#__PURE__*/ React.createElement('path', {
327
+ d: 'M18 6 6 18',
328
+ }),
329
+ /*#__PURE__*/ React.createElement('path', {
330
+ d: 'm6 6 12 12',
331
+ })
332
+ )
333
+ );
334
+ }
335
+ function LoadingSvg() {
336
+ return /*#__PURE__*/ React.createElement(
337
+ 'div',
338
+ {
339
+ className: 'promise-svg-container',
340
+ },
341
+ /*#__PURE__*/ React.createElement(
342
+ 'svg',
343
+ {
344
+ xmlns: 'http://www.w3.org/2000/svg',
345
+ width: '24',
346
+ height: '24',
347
+ viewBox: '0 0 24 24',
348
+ fill: 'none',
349
+ stroke: 'currentColor',
350
+ strokeWidth: '2',
351
+ strokeLinecap: 'round',
352
+ strokeLinejoin: 'round',
353
+ },
354
+ /*#__PURE__*/ React.createElement('path', {
355
+ d: 'M21 12a9 9 0 1 1-6.219-8.56',
356
+ })
357
+ )
358
+ );
359
+ }
360
+
361
+ const GAP = 10;
362
+ function Toast({ toast, setToasts, position }) {
363
+ const ref = useRef(null);
364
+ useLayoutEffect(() => {
365
+ if (!ref.current) return;
366
+ const h = ref.current.getBoundingClientRect().height;
367
+ setToasts((prev) =>
368
+ prev.map((t) =>
369
+ t.options.id === toast.id
370
+ ? t
371
+ : {
372
+ ...t,
373
+ offset: t.offset + h + GAP,
374
+ }
375
+ )
376
+ );
377
+ }, [setToasts, toast.id]);
378
+ if (position.includes('bottom')) {
379
+ return /*#__PURE__*/ React.createElement(
380
+ 'div',
381
+ {
382
+ ref: ref,
383
+ className: 'toast',
384
+ style: {
385
+ transform: `translateY(-${toast.offset}px)`,
386
+ ...getToastPosition(position),
387
+ },
388
+ },
389
+ /*#__PURE__*/ React.createElement(
390
+ 'div',
391
+ {
392
+ className: `toast-content-bottom ${toast.leaving ? 'exit' : ''}`,
393
+ },
394
+ toast.type === 'loading' && /*#__PURE__*/ React.createElement(LoadingSvg, null),
395
+ toast.type === 'success' && /*#__PURE__*/ React.createElement(SuccessSvg, null),
396
+ toast.type === 'error' && /*#__PURE__*/ React.createElement(ErrorSvg, null),
397
+ /*#__PURE__*/ React.createElement('span', null, toast.text)
398
+ )
399
+ );
400
+ }
401
+ return /*#__PURE__*/ React.createElement(
402
+ 'div',
403
+ {
404
+ ref: ref,
405
+ className: 'toast',
406
+ style: {
407
+ transform: `translateY(${toast.offset}px)`,
408
+ ...getToastPosition(position),
409
+ },
410
+ },
411
+ /*#__PURE__*/ React.createElement(
412
+ 'div',
413
+ {
414
+ className: `toast-content ${toast.leaving ? 'exit' : ''}`,
415
+ },
416
+ toast.type === 'loading' && /*#__PURE__*/ React.createElement(LoadingSvg, null),
417
+ toast.type === 'success' && /*#__PURE__*/ React.createElement(SuccessSvg, null),
418
+ toast.type === 'error' && /*#__PURE__*/ React.createElement(ErrorSvg, null),
419
+ /*#__PURE__*/ React.createElement('span', null, toast.text)
420
+ )
421
+ );
422
+ }
423
+ function getToastPosition(position) {
424
+ const isLeft = position.includes('left');
425
+ const isRight = position.includes('right');
426
+ const styles = {
427
+ position: 'absolute',
428
+ width: 'calc(100% - 2rem)',
429
+ pointerEvents: 'none',
430
+ transition: 'transform 230ms',
431
+ display: 'flex',
432
+ };
433
+ if (position.includes('top')) {
434
+ styles.top = '1rem';
435
+ styles.justifyContent = isLeft ? 'flex-start' : isRight ? 'flex-end' : 'center';
436
+ }
437
+ if (position.includes('bottom')) {
438
+ styles.bottom = '1rem';
439
+ styles.justifyContent = isLeft ? 'flex-start' : isRight ? 'flex-end' : 'center';
440
+ }
441
+ return styles;
442
+ }
443
+
444
+ const EXIT_ANIM_MS = 300;
445
+ function ToastContainer(props) {
446
+ props = Object.assign(
447
+ {
448
+ position: 'top-center',
449
+ },
450
+ props
451
+ );
452
+ const { position } = props;
453
+ const [toasts, setToasts] = useState([]);
454
+ useEffect(() => {
455
+ const unsub = subscribe((toast) => {
456
+ const duration = toast.options.duration;
457
+ const id = toast.options.id;
458
+ setToasts((prev) => {
459
+ const exists = prev.some((t) => t.options.id === id);
460
+ if (exists) {
461
+ return prev.map((t) =>
462
+ t.options.id === id
463
+ ? {
464
+ ...t,
465
+ ...toast,
466
+ }
467
+ : t
468
+ );
469
+ }
470
+ return [
471
+ {
472
+ id,
473
+ ...toast,
474
+ offset: 0,
475
+ leaving: false,
476
+ },
477
+ ...prev,
478
+ ];
479
+ });
480
+ if (toast.type !== 'loading') {
481
+ setTimeout(() => {
482
+ setToasts((prev) =>
483
+ prev.map((t) =>
484
+ t.options.id === id
485
+ ? {
486
+ ...t,
487
+ leaving: true,
488
+ }
489
+ : t
490
+ )
491
+ );
492
+ }, Math.max(0, duration - EXIT_ANIM_MS));
493
+ setTimeout(() => {
494
+ setToasts((prev) => prev.filter((t) => t.options.id !== id));
495
+ }, duration);
496
+ }
497
+ });
498
+ return () => unsub();
499
+ }, []);
500
+ return /*#__PURE__*/ React.createElement(
501
+ 'div',
502
+ {
503
+ style: {
504
+ position: 'fixed',
505
+ inset: 0,
506
+ zIndex: 100000000,
507
+ pointerEvents: 'none',
508
+ fontFamily: 'inherit',
509
+ padding: '1rem',
510
+ },
511
+ },
512
+ toasts.map((t) =>
513
+ /*#__PURE__*/ React.createElement(Toast, {
514
+ key: t.options.id,
515
+ toast: t,
516
+ setToasts: setToasts,
517
+ position: position,
518
+ })
519
+ )
520
+ );
521
+ }
522
+
523
+ const toast = {
524
+ success,
525
+ error,
526
+ promise,
527
+ };
528
+
529
+ export { ToastContainer, toast };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitzo",
3
- "version": "1.1.7",
3
+ "version": "2.0.1",
4
4
  "description": "A lightweight JavaScript UI micro-library.",
5
5
  "type": "module",
6
6
  "main": "./dist/kitzo.umd.js",
@@ -9,6 +9,10 @@
9
9
  "import": "./dist/kitzo.esm.js",
10
10
  "require": "./dist/kitzo.umd.js",
11
11
  "types": "./dist/kitzo.d.ts"
12
+ },
13
+ "./react": {
14
+ "import": "./dist/react.esm.js",
15
+ "types": "./dist/react.d.ts"
12
16
  }
13
17
  },
14
18
  "files": [
@@ -27,6 +31,7 @@
27
31
  "modular",
28
32
  "ui",
29
33
  "javascript",
34
+ "react",
30
35
  "kitzo"
31
36
  ],
32
37
  "author": "Riyad",
@@ -37,7 +42,19 @@
37
42
  },
38
43
  "homepage": "https://github.com/riyad-96/kitzo#readme",
39
44
  "devDependencies": {
45
+ "@rollup/plugin-commonjs": "^28.0.6",
46
+ "@rollup/plugin-node-resolve": "^16.0.1",
47
+ "@babel/core": "^7.28.4",
48
+ "@babel/preset-react": "^7.27.1",
49
+ "@rollup/plugin-babel": "^6.0.4",
50
+ "@vitejs/plugin-react": "^5.0.4",
51
+ "react": "^19.1.1",
52
+ "react-dom": "^19.1.1",
40
53
  "rollup": "^4.46.2",
41
54
  "vite": "^7.0.4"
55
+ },
56
+ "peerDependencies": {
57
+ "react": ">=17",
58
+ "react-dom": ">=17"
42
59
  }
43
60
  }