gbs-add-block 0.0.41 → 0.0.43
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/formrenderer/SelectHandles.tsx +5 -3
- 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/uploader/index.tsx +5 -0
- 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.43)
|
|
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.43)
|
|
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
|
@@ -92,7 +92,7 @@ export default function SelectHandles({
|
|
|
92
92
|
}
|
|
93
93
|
}, [item?.name, dependencyMap, formRef]);
|
|
94
94
|
|
|
95
|
-
const handleSelect = (value: string, key: string) => {
|
|
95
|
+
const handleSelect = (value: string | undefined, key: string) => {
|
|
96
96
|
if (!formRef?.current) return;
|
|
97
97
|
|
|
98
98
|
let input = formRef.current.querySelector(
|
|
@@ -106,7 +106,9 @@ export default function SelectHandles({
|
|
|
106
106
|
formRef.current.appendChild(input);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
if (value) {
|
|
110
|
+
input.value = value;
|
|
111
|
+
}
|
|
110
112
|
const event = new Event("change", { bubbles: true });
|
|
111
113
|
input.dispatchEvent(event);
|
|
112
114
|
};
|
|
@@ -123,7 +125,7 @@ export default function SelectHandles({
|
|
|
123
125
|
name={item?.name}
|
|
124
126
|
items={options}
|
|
125
127
|
selectedItem={item?.value ?? ""}
|
|
126
|
-
onSelect={(value: string) => {
|
|
128
|
+
onSelect={(value: string | undefined) => {
|
|
127
129
|
item?.name && handleSelect(value, item.name);
|
|
128
130
|
setRequirementError &&
|
|
129
131
|
setRequirementError((prevErrors) =>
|
|
@@ -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
|
+
}
|
|
@@ -182,6 +182,11 @@ export const FileUploader = ({
|
|
|
182
182
|
return rest;
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
+
// Reset file input to allow re-selecting the same file
|
|
186
|
+
if (fileInputRef.current) {
|
|
187
|
+
fileInputRef.current.value = "";
|
|
188
|
+
}
|
|
189
|
+
|
|
185
190
|
if (onChange) onChange(updatedFiles);
|
|
186
191
|
};
|
|
187
192
|
|
|
@@ -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
|
-
};
|