kitzo 1.1.6 → 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 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.6/dist/kitzo.umd.min.js"></script>
28
+ <script src="https://cdn.jsdelivr.net/npm/kitzo@2.0.0/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
+ ```
package/dist/kitzo.esm.js CHANGED
@@ -215,7 +215,6 @@ function ripple(element, config = {}) {
215
215
  btn.setAttribute('data-kitzo-ripple', true);
216
216
  rippleConfigMap.set(btn, config);
217
217
  const { position, overflow } = window.getComputedStyle(btn);
218
- console.log(position, overflow);
219
218
  if (position === 'static') {
220
219
  btn.style.position = 'relative';
221
220
  }
package/dist/kitzo.umd.js CHANGED
@@ -221,7 +221,6 @@
221
221
  btn.setAttribute('data-kitzo-ripple', true);
222
222
  rippleConfigMap.set(btn, config);
223
223
  const { position, overflow } = window.getComputedStyle(btn);
224
- console.log(position, overflow);
225
224
  if (position === 'static') {
226
225
  btn.style.position = 'relative';
227
226
  }
@@ -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,446 @@
1
+ import { 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
+ duration: 2000,
208
+ id
209
+ }, options);
210
+ addToast({
211
+ type: 'success',
212
+ text,
213
+ options
214
+ });
215
+ }
216
+ function error(text = 'Toast denied', options = {}) {
217
+ const id = Date.now();
218
+ options = Object.assign({
219
+ duration: 2000,
220
+ id
221
+ }, options);
222
+ addToast({
223
+ type: 'error',
224
+ text,
225
+ options
226
+ });
227
+ }
228
+ function promise(callback, msgs = {}, options = {}) {
229
+ const id = Date.now();
230
+ options = Object.assign({
231
+ duration: 2000,
232
+ id
233
+ }, options);
234
+ msgs = Object.assign({
235
+ loading: 'Saving...',
236
+ success: 'Success',
237
+ error: 'Something went wrong'
238
+ }, msgs);
239
+ addToast({
240
+ type: 'loading',
241
+ text: msgs.loading,
242
+ options: {
243
+ ...options,
244
+ duration: Infinity,
245
+ id
246
+ }
247
+ });
248
+ callback.then(res => {
249
+ const successMsg = typeof msgs.success === 'function' ? msgs.success(res) : msgs.success;
250
+ addToast({
251
+ type: 'success',
252
+ text: successMsg,
253
+ options
254
+ });
255
+ return res;
256
+ }).catch(err => {
257
+ const normalizedError = err instanceof Error ? err : new Error(String(err));
258
+ const errorMsg = typeof msgs.error === 'function' ? msgs.error(normalizedError) : msgs.error;
259
+ addToast({
260
+ type: 'error',
261
+ text: errorMsg,
262
+ options
263
+ });
264
+ return Promise.reject(normalizedError);
265
+ });
266
+ }
267
+
268
+ function SuccessSvg() {
269
+ return /*#__PURE__*/React.createElement("div", {
270
+ className: "svg-container success"
271
+ }, /*#__PURE__*/React.createElement("svg", {
272
+ xmlns: "http://www.w3.org/2000/svg",
273
+ width: "24",
274
+ height: "24",
275
+ viewBox: "0 0 24 24",
276
+ fill: "none",
277
+ stroke: "currentColor",
278
+ strokeWidth: "2",
279
+ strokeLinecap: "round",
280
+ strokeLinejoin: "round"
281
+ }, /*#__PURE__*/React.createElement("path", {
282
+ d: "M20 6 9 17l-5-5"
283
+ })));
284
+ }
285
+ function ErrorSvg() {
286
+ return /*#__PURE__*/React.createElement("div", {
287
+ className: "svg-container error"
288
+ }, /*#__PURE__*/React.createElement("svg", {
289
+ xmlns: "http://www.w3.org/2000/svg",
290
+ width: "24",
291
+ height: "24",
292
+ viewBox: "0 0 24 24",
293
+ fill: "none",
294
+ stroke: "currentColor",
295
+ strokeWidth: "2",
296
+ strokeLinecap: "round",
297
+ strokeLinejoin: "round"
298
+ }, /*#__PURE__*/React.createElement("path", {
299
+ d: "M18 6 6 18"
300
+ }), /*#__PURE__*/React.createElement("path", {
301
+ d: "m6 6 12 12"
302
+ })));
303
+ }
304
+ function LoadingSvg() {
305
+ return /*#__PURE__*/React.createElement("div", {
306
+ className: "promise-svg-container"
307
+ }, /*#__PURE__*/React.createElement("svg", {
308
+ xmlns: "http://www.w3.org/2000/svg",
309
+ width: "24",
310
+ height: "24",
311
+ viewBox: "0 0 24 24",
312
+ fill: "none",
313
+ stroke: "currentColor",
314
+ strokeWidth: "2",
315
+ strokeLinecap: "round",
316
+ strokeLinejoin: "round"
317
+ }, /*#__PURE__*/React.createElement("path", {
318
+ d: "M21 12a9 9 0 1 1-6.219-8.56"
319
+ })));
320
+ }
321
+
322
+ const GAP = 10;
323
+ function Toast({
324
+ toast,
325
+ setToasts,
326
+ position
327
+ }) {
328
+ const ref = useRef(null);
329
+ useLayoutEffect(() => {
330
+ if (!ref.current) return;
331
+ const h = ref.current.getBoundingClientRect().height;
332
+ setToasts(prev => prev.map(t => t.options.id === toast.id ? t : {
333
+ ...t,
334
+ offset: t.offset + h + GAP
335
+ }));
336
+ }, [setToasts, toast.id]);
337
+ if (position.includes('bottom')) {
338
+ return /*#__PURE__*/React.createElement("div", {
339
+ ref: ref,
340
+ className: "toast",
341
+ style: {
342
+ transform: `translateY(-${toast.offset}px)`,
343
+ ...getToastPosition(position)
344
+ }
345
+ }, /*#__PURE__*/React.createElement("div", {
346
+ className: `toast-content-bottom ${toast.leaving ? 'exit' : ''}`
347
+ }, toast.type === 'loading' && /*#__PURE__*/React.createElement(LoadingSvg, null), toast.type === 'success' && /*#__PURE__*/React.createElement(SuccessSvg, null), toast.type === 'error' && /*#__PURE__*/React.createElement(ErrorSvg, null), /*#__PURE__*/React.createElement("span", null, toast.text)));
348
+ }
349
+ return /*#__PURE__*/React.createElement("div", {
350
+ ref: ref,
351
+ className: "toast",
352
+ style: {
353
+ transform: `translateY(${toast.offset}px)`,
354
+ ...getToastPosition(position)
355
+ }
356
+ }, /*#__PURE__*/React.createElement("div", {
357
+ className: `toast-content ${toast.leaving ? 'exit' : ''}`
358
+ }, toast.type === 'loading' && /*#__PURE__*/React.createElement(LoadingSvg, null), toast.type === 'success' && /*#__PURE__*/React.createElement(SuccessSvg, null), toast.type === 'error' && /*#__PURE__*/React.createElement(ErrorSvg, null), /*#__PURE__*/React.createElement("span", null, toast.text)));
359
+ }
360
+ function getToastPosition(position) {
361
+ const isLeft = position.includes('left');
362
+ const isRight = position.includes('right');
363
+ const styles = {
364
+ position: 'absolute',
365
+ width: 'calc(100% - 2rem)',
366
+ pointerEvents: 'none',
367
+ transition: 'transform 230ms',
368
+ display: 'flex'
369
+ };
370
+ if (position.includes('top')) {
371
+ styles.top = '1rem';
372
+ styles.justifyContent = isLeft ? 'flex-start' : isRight ? 'flex-end' : 'center';
373
+ }
374
+ if (position.includes('bottom')) {
375
+ styles.bottom = '1rem';
376
+ styles.justifyContent = isLeft ? 'flex-start' : isRight ? 'flex-end' : 'center';
377
+ }
378
+ return styles;
379
+ }
380
+
381
+ const EXIT_ANIM_MS = 300;
382
+ function ToastContainer(props) {
383
+ props = Object.assign({
384
+ position: 'top-center'
385
+ }, props);
386
+ const {
387
+ position
388
+ } = props;
389
+ const [toasts, setToasts] = useState([]);
390
+ useEffect(() => {
391
+ const unsub = subscribe(toast => {
392
+ const duration = toast.options.duration;
393
+ const id = toast.options.id;
394
+ setToasts(prev => {
395
+ const exists = prev.some(t => t.options.id === id);
396
+ if (exists) {
397
+ return prev.map(t => t.options.id === id ? {
398
+ ...t,
399
+ ...toast
400
+ } : t);
401
+ }
402
+ return [{
403
+ id,
404
+ ...toast,
405
+ offset: 0,
406
+ leaving: false
407
+ }, ...prev];
408
+ });
409
+ if (toast.type !== 'loading') {
410
+ setTimeout(() => {
411
+ setToasts(prev => prev.map(t => t.options.id === id ? {
412
+ ...t,
413
+ leaving: true
414
+ } : t));
415
+ }, Math.max(0, duration - EXIT_ANIM_MS));
416
+ setTimeout(() => {
417
+ setToasts(prev => prev.filter(t => t.options.id !== id));
418
+ }, duration);
419
+ }
420
+ });
421
+ return () => unsub();
422
+ }, []);
423
+ return /*#__PURE__*/React.createElement("div", {
424
+ style: {
425
+ position: 'fixed',
426
+ inset: 0,
427
+ zIndex: 100000000,
428
+ pointerEvents: 'none',
429
+ fontFamily: 'inherit',
430
+ padding: '1rem'
431
+ }
432
+ }, toasts.map(t => /*#__PURE__*/React.createElement(Toast, {
433
+ key: t.options.id,
434
+ toast: t,
435
+ setToasts: setToasts,
436
+ position: position
437
+ })));
438
+ }
439
+
440
+ const toast = {
441
+ success,
442
+ error,
443
+ promise
444
+ };
445
+
446
+ export { ToastContainer, toast };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitzo",
3
- "version": "1.1.6",
3
+ "version": "2.0.0",
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
  }