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 CHANGED
@@ -1,4 +1,4 @@
1
- # GBS Building Blocks 2.0 (v0.0.41)
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.41)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "0.0.41",
3
+ "version": "0.0.42",
4
4
  "description": "React Component Library",
5
5
  "files": [
6
6
  "index.js",
@@ -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 { useToastContext } from "./ToastContext";
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
- interface ToastsProps {
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
- const positionClasses: Record<ToastPosition, string> = {
18
- "top-left": "top-0 left-0",
19
- "top-right": "top-0 right-0",
20
- "bottom-left": "bottom-0 left-0",
21
- "bottom-right": "bottom-0 right-0",
22
- };
26
+ useEffect(() => {
27
+ setMounted(true);
28
+ }, []);
23
29
 
24
- export const Toasts: React.FC<ToastsProps> = ({ position = "top-right" }) => {
25
- const { toasts } = useToastContext();
30
+ if (!mounted) return null;
26
31
 
27
- return (
28
- <section
29
- className={`fixed flex flex-col z-50 p-4 ${positionClasses[position]}`}
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
- <Toast key={toast.id} toast={toast} />
37
+ <ToastComponent key={toast.id} toast={toast} />
33
38
  ))}
34
- </section>
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
- import { ReactNode } from "react";
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-right";
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 { ToastOptions } from "./types";
2
- import { useToastContext } from "./ToastContext";
1
+ import { Toast } from "./types";
2
+ import { useToastStore } from "./toastStore";
3
3
 
4
- const useToast = () => {
5
- const { addToast, dismissToast } = useToastContext();
4
+ export function useToast() {
5
+ const store = useToastStore();
6
6
 
7
7
  return {
8
- addToast: ({
9
- title,
10
- message,
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
- };