gbs-add-block 0.0.41 → 0.0.42
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 +3 -3
- package/package.json +1 -1
- package/source/components/toast/ToastComponent.tsx +149 -0
- package/source/components/toast/index.tsx +31 -25
- package/source/components/toast/toastStore.ts +42 -0
- package/source/components/toast/types.ts +15 -25
- package/source/components/toast/useToast.ts +8 -24
- package/source/components/toast/Toast.tsx +0 -61
- package/source/components/toast/ToastContext.tsx +0 -43
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GBS Building Blocks 2.0 (v0.0.
|
|
1
|
+
# GBS Building Blocks 2.0 (v0.0.42)
|
|
2
2
|
|
|
3
3
|
Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
|
|
4
4
|
|
|
@@ -6,9 +6,9 @@ Latest and upgraded version of GBS building blocks with headless UI and removed
|
|
|
6
6
|
|
|
7
7
|
For detailed documentation on usage and props, Please visit: [Building Block Documentation v2.0](https://blackmax-designs.gitbook.io/building-block-v2.0)
|
|
8
8
|
|
|
9
|
-
## What's New 🎉 (Ver 0.0.
|
|
9
|
+
## What's New 🎉 (Ver 0.0.42)
|
|
10
10
|
|
|
11
|
-
- Refactored Code Better Readability
|
|
11
|
+
- Refactored Code for Better Readability and removed dependency for Toast
|
|
12
12
|
|
|
13
13
|
## Authors
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { Toast } from "./types";
|
|
3
|
+
import { useToastStore } from "./toastStore";
|
|
4
|
+
|
|
5
|
+
interface ToastProps {
|
|
6
|
+
toast: Toast;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const bgColors = {
|
|
10
|
+
default: "bg-white hover:bg-gray-50",
|
|
11
|
+
success: "bg-green-50 hover:bg-green-100",
|
|
12
|
+
error: "bg-red-50 hover:bg-red-100",
|
|
13
|
+
warning: "bg-yellow-50 hover:bg-yellow-100",
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
const iconColors = {
|
|
17
|
+
default: "text-gray-800",
|
|
18
|
+
success: "text-green-600",
|
|
19
|
+
error: "text-red-600",
|
|
20
|
+
warning: "text-yellow-600",
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
const CloseButton = ({ onDismiss }: { onDismiss: () => void }) => (
|
|
24
|
+
<button
|
|
25
|
+
onClick={onDismiss}
|
|
26
|
+
className="shrink-0 rounded-lg p-1 transition-colors hover:bg-black/5 focus:outline-none focus:ring-2 focus:ring-black/10"
|
|
27
|
+
>
|
|
28
|
+
<svg
|
|
29
|
+
className="h-4 w-4"
|
|
30
|
+
fill="none"
|
|
31
|
+
viewBox="0 0 24 24"
|
|
32
|
+
stroke="currentColor"
|
|
33
|
+
>
|
|
34
|
+
<path
|
|
35
|
+
strokeLinecap="round"
|
|
36
|
+
strokeLinejoin="round"
|
|
37
|
+
strokeWidth={2}
|
|
38
|
+
d="M6 18L18 6M6 6l12 12"
|
|
39
|
+
/>
|
|
40
|
+
</svg>
|
|
41
|
+
</button>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const ToastIcon = ({ type }: { type: Toast["type"] }) => {
|
|
45
|
+
if (!type) return null;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className={`h-5 w-5 ${iconColors[type]}`}>
|
|
49
|
+
{type === "success" && (
|
|
50
|
+
<svg
|
|
51
|
+
viewBox="0 0 24 24"
|
|
52
|
+
fill="none"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
strokeWidth="2"
|
|
55
|
+
>
|
|
56
|
+
<path
|
|
57
|
+
strokeLinecap="round"
|
|
58
|
+
strokeLinejoin="round"
|
|
59
|
+
d="M5 13l4 4L19 7"
|
|
60
|
+
/>
|
|
61
|
+
</svg>
|
|
62
|
+
)}
|
|
63
|
+
{type === "error" && (
|
|
64
|
+
<svg
|
|
65
|
+
viewBox="0 0 24 24"
|
|
66
|
+
fill="none"
|
|
67
|
+
stroke="currentColor"
|
|
68
|
+
strokeWidth="2"
|
|
69
|
+
>
|
|
70
|
+
<path
|
|
71
|
+
strokeLinecap="round"
|
|
72
|
+
strokeLinejoin="round"
|
|
73
|
+
d="M6 18L18 6M6 6l12 12"
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
76
|
+
)}
|
|
77
|
+
{type === "warning" && (
|
|
78
|
+
<svg
|
|
79
|
+
viewBox="0 0 24 24"
|
|
80
|
+
fill="none"
|
|
81
|
+
stroke="currentColor"
|
|
82
|
+
strokeWidth="2"
|
|
83
|
+
>
|
|
84
|
+
<path
|
|
85
|
+
strokeLinecap="round"
|
|
86
|
+
strokeLinejoin="round"
|
|
87
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
88
|
+
/>
|
|
89
|
+
</svg>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export function ToastComponent({ toast }: ToastProps) {
|
|
96
|
+
const { dismiss } = useToastStore();
|
|
97
|
+
const handleDismiss = () => dismiss(toast.id);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (toast.duration !== 0) {
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
handleDismiss();
|
|
103
|
+
}, toast.duration || 5000);
|
|
104
|
+
|
|
105
|
+
return () => clearTimeout(timer);
|
|
106
|
+
}
|
|
107
|
+
}, [toast.id, toast.duration]);
|
|
108
|
+
|
|
109
|
+
if (toast.content) {
|
|
110
|
+
return (
|
|
111
|
+
<div className="relative">
|
|
112
|
+
{toast.content}
|
|
113
|
+
<div className="absolute top-2 right-2">
|
|
114
|
+
<CloseButton onDismiss={handleDismiss} />
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
className={`
|
|
123
|
+
${bgColors[toast.type || "default"]}
|
|
124
|
+
transform transition-all duration-300 ease-in-out
|
|
125
|
+
pointer-events-auto relative flex w-full items-center justify-between
|
|
126
|
+
overflow-hidden rounded-lg border p-4 shadow-lg
|
|
127
|
+
`}
|
|
128
|
+
role="alert"
|
|
129
|
+
>
|
|
130
|
+
<div className="flex items-start gap-3 w-full">
|
|
131
|
+
<ToastIcon type={toast.type} />
|
|
132
|
+
<div className="flex-1">
|
|
133
|
+
{toast.title && (
|
|
134
|
+
<div className="font-medium text-sm text-gray-900">
|
|
135
|
+
{toast.title}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
{toast.description && (
|
|
139
|
+
<div className="text-sm text-gray-500 mt-1">
|
|
140
|
+
{toast.description}
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
{toast.action}
|
|
145
|
+
<CloseButton onDismiss={handleDismiss} />
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -1,36 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
* Copyright (c) Grampro Business Services and affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/
|
|
1
|
+
"use client";
|
|
7
2
|
|
|
8
|
-
import React from "react";
|
|
9
|
-
import {
|
|
10
|
-
import Toast from "./Toast";
|
|
3
|
+
import React, { useEffect } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
11
5
|
import { ToastPosition } from "./types";
|
|
6
|
+
import { useToastStore } from "./toastStore";
|
|
7
|
+
import { ToastComponent } from "./ToastComponent";
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
const positions: Record<ToastPosition, string> = {
|
|
10
|
+
"top-right": "top-4 right-4",
|
|
11
|
+
"top-left": "top-4 left-4",
|
|
12
|
+
"top-center": "top-4 left-1/2 -translate-x-1/2",
|
|
13
|
+
"bottom-right": "bottom-4 right-4",
|
|
14
|
+
"bottom-left": "bottom-4 left-4",
|
|
15
|
+
"bottom-center": "bottom-4 left-1/2 -translate-x-1/2",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function Toaster({
|
|
19
|
+
position = "top-right",
|
|
20
|
+
}: {
|
|
14
21
|
position?: ToastPosition;
|
|
15
|
-
}
|
|
22
|
+
}) {
|
|
23
|
+
const [mounted, setMounted] = React.useState(false);
|
|
24
|
+
const { toasts } = useToastStore();
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"bottom-left": "bottom-0 left-0",
|
|
21
|
-
"bottom-right": "bottom-0 right-0",
|
|
22
|
-
};
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setMounted(true);
|
|
28
|
+
}, []);
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
const { toasts } = useToastContext();
|
|
30
|
+
if (!mounted) return null;
|
|
26
31
|
|
|
27
|
-
return (
|
|
28
|
-
<
|
|
29
|
-
className={`fixed flex flex-col
|
|
32
|
+
return createPortal(
|
|
33
|
+
<div
|
|
34
|
+
className={`fixed z-50 flex flex-col gap-2 w-full max-w-[420px] ${positions[position]}`}
|
|
30
35
|
>
|
|
31
36
|
{toasts.map((toast) => (
|
|
32
|
-
<
|
|
37
|
+
<ToastComponent key={toast.id} toast={toast} />
|
|
33
38
|
))}
|
|
34
|
-
</
|
|
39
|
+
</div>,
|
|
40
|
+
document.body
|
|
35
41
|
);
|
|
36
|
-
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Toast } from "./types";
|
|
3
|
+
|
|
4
|
+
type ToastState = {
|
|
5
|
+
toasts: Toast[];
|
|
6
|
+
add: (toast: Omit<Toast, "id">) => void;
|
|
7
|
+
dismiss: (id: string) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let listeners: Array<(state: ToastState) => void> = [];
|
|
11
|
+
let state: ToastState = {
|
|
12
|
+
toasts: [],
|
|
13
|
+
add: (toast) => {
|
|
14
|
+
const id = Math.random().toString(36).slice(2);
|
|
15
|
+
state.toasts = [{ ...toast, id }, ...state.toasts];
|
|
16
|
+
emitChange();
|
|
17
|
+
},
|
|
18
|
+
dismiss: (id) => {
|
|
19
|
+
state.toasts = state.toasts.filter((t) => t.id !== id);
|
|
20
|
+
emitChange();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function emitChange() {
|
|
25
|
+
for (let listener of listeners) {
|
|
26
|
+
listener(state);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useToastStore(): ToastState {
|
|
31
|
+
const [, setUpdate] = React.useState({});
|
|
32
|
+
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
const listener = () => setUpdate({});
|
|
35
|
+
listeners.push(listener);
|
|
36
|
+
return () => {
|
|
37
|
+
listeners = listeners.filter((l) => l !== listener);
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
return state;
|
|
42
|
+
}
|
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export type ToastType = "success" | "error" | "info";
|
|
4
|
-
|
|
5
|
-
export type Toast = {
|
|
6
|
-
id: number;
|
|
7
|
-
type: ToastType;
|
|
8
|
-
message: string | ReactNode;
|
|
9
|
-
title: string | ReactNode;
|
|
10
|
-
dismissible: boolean;
|
|
11
|
-
timeout: number;
|
|
12
|
-
action?: any;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type ToastOptions = {
|
|
16
|
-
message: string | ReactNode;
|
|
17
|
-
title?: string | ReactNode;
|
|
18
|
-
type?: ToastType;
|
|
19
|
-
dismissible?: boolean;
|
|
20
|
-
timeout?: number;
|
|
21
|
-
action?: any;
|
|
22
|
-
};
|
|
23
|
-
|
|
1
|
+
export type ToastType = "default" | "success" | "error" | "warning";
|
|
24
2
|
export type ToastPosition =
|
|
25
|
-
| "top-left"
|
|
26
3
|
| "top-right"
|
|
4
|
+
| "top-left"
|
|
5
|
+
| "top-center"
|
|
6
|
+
| "bottom-right"
|
|
27
7
|
| "bottom-left"
|
|
28
|
-
| "bottom-
|
|
8
|
+
| "bottom-center";
|
|
9
|
+
|
|
10
|
+
export interface Toast {
|
|
11
|
+
id: string;
|
|
12
|
+
type?: ToastType;
|
|
13
|
+
duration?: number;
|
|
14
|
+
content?: React.ReactNode;
|
|
15
|
+
title?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
action?: React.ReactNode;
|
|
18
|
+
}
|
|
@@ -1,28 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Toast } from "./types";
|
|
2
|
+
import { useToastStore } from "./toastStore";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const
|
|
4
|
+
export function useToast() {
|
|
5
|
+
const store = useToastStore();
|
|
6
6
|
|
|
7
7
|
return {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type = "info",
|
|
12
|
-
dismissible = false,
|
|
13
|
-
timeout = 3000,
|
|
14
|
-
action,
|
|
15
|
-
}: ToastOptions) =>
|
|
16
|
-
addToast({
|
|
17
|
-
title,
|
|
18
|
-
message,
|
|
19
|
-
type,
|
|
20
|
-
dismissible,
|
|
21
|
-
timeout,
|
|
22
|
-
action,
|
|
23
|
-
}),
|
|
24
|
-
dismissToast,
|
|
8
|
+
toast: (props: Omit<Toast, "id">) => store.add(props),
|
|
9
|
+
dismiss: store.dismiss,
|
|
10
|
+
toasts: store.toasts,
|
|
25
11
|
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export default useToast;
|
|
12
|
+
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Toast as ToastType } from "./types";
|
|
3
|
-
import { useToastContext } from "./ToastContext";
|
|
4
|
-
import Icon from "../icon/Icon";
|
|
5
|
-
import { circleCheck, info, x, xCircle } from "../icon/iconPaths";
|
|
6
|
-
|
|
7
|
-
interface ToastProps {
|
|
8
|
-
toast: ToastType;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const Toast: React.FC<ToastProps> = ({ toast }) => {
|
|
12
|
-
const { dismissToast } = useToastContext();
|
|
13
|
-
const icons = {
|
|
14
|
-
success: (
|
|
15
|
-
<Icon
|
|
16
|
-
elements={circleCheck}
|
|
17
|
-
svgClass="h-4 w-4 stroke-green-500 fill-none dark:stroke-white"
|
|
18
|
-
/>
|
|
19
|
-
),
|
|
20
|
-
error: (
|
|
21
|
-
<Icon
|
|
22
|
-
elements={xCircle}
|
|
23
|
-
svgClass="h-4 w-4 stroke-red-500 fill-none dark:stroke-white"
|
|
24
|
-
/>
|
|
25
|
-
),
|
|
26
|
-
info: (
|
|
27
|
-
<Icon
|
|
28
|
-
elements={info}
|
|
29
|
-
svgClass="h-4 w-4 stroke-blue-500 fill-none dark:stroke-white"
|
|
30
|
-
/>
|
|
31
|
-
),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div
|
|
36
|
-
className={`bg-white px-2 py-3 rounded-lg text-sm flex justify-between w-full m-2 border shadow-lg ${
|
|
37
|
-
toast.type === "success"
|
|
38
|
-
? "text-green-500"
|
|
39
|
-
: toast.type === "error"
|
|
40
|
-
? "text-red-500"
|
|
41
|
-
: "text-blue-500"
|
|
42
|
-
}`}
|
|
43
|
-
role="alert"
|
|
44
|
-
>
|
|
45
|
-
<div className="flex gap-2 items-center">
|
|
46
|
-
{icons[toast.type]}
|
|
47
|
-
<div>{toast.message}</div>
|
|
48
|
-
</div>
|
|
49
|
-
{toast.dismissible && (
|
|
50
|
-
<button onClick={() => dismissToast(toast.id)}>
|
|
51
|
-
<Icon
|
|
52
|
-
elements={x}
|
|
53
|
-
svgClass="h-4 w-4 stroke-gray-500 fill-none dark:stroke-white"
|
|
54
|
-
/>
|
|
55
|
-
</button>
|
|
56
|
-
)}
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export default Toast;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext, useState } from "react";
|
|
2
|
-
import { Toast } from "./types";
|
|
3
|
-
|
|
4
|
-
interface ToastContextProps {
|
|
5
|
-
toasts: Toast[];
|
|
6
|
-
addToast: (toast: Omit<Toast, "id">) => void;
|
|
7
|
-
dismissToast: (id: number) => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const ToastContext = createContext<ToastContextProps | undefined>(undefined);
|
|
11
|
-
|
|
12
|
-
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
13
|
-
children,
|
|
14
|
-
}) => {
|
|
15
|
-
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
16
|
-
|
|
17
|
-
const addToast = (toast: Omit<Toast, "id">) => {
|
|
18
|
-
const id = Math.floor(Math.random() * 10000);
|
|
19
|
-
const newToast = { ...toast, id };
|
|
20
|
-
setToasts((prev) => [newToast, ...prev]);
|
|
21
|
-
if (toast.timeout) {
|
|
22
|
-
setTimeout(() => dismissToast(id), toast.timeout);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const dismissToast = (id: number) => {
|
|
27
|
-
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<ToastContext.Provider value={{ toasts, addToast, dismissToast }}>
|
|
32
|
-
{children}
|
|
33
|
-
</ToastContext.Provider>
|
|
34
|
-
);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const useToastContext = (): ToastContextProps => {
|
|
38
|
-
const context = useContext(ToastContext);
|
|
39
|
-
if (!context) {
|
|
40
|
-
throw new Error("useToastContext must be used within a ToastProvider");
|
|
41
|
-
}
|
|
42
|
-
return context;
|
|
43
|
-
};
|