gbs-add-block 0.0.39 → 0.0.41

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.39)
1
+ # GBS Building Blocks 2.0 (v0.0.41)
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.39)
9
+ ## What's New 🎉 (Ver 0.0.41)
10
10
 
11
- - Form Renderer Now Supports Date Picker and CheckBox
11
+ - Refactored Code Better Readability
12
12
 
13
13
  ## Authors
14
14
 
package/index.js CHANGED
@@ -2,164 +2,176 @@
2
2
 
3
3
  const fs = require("fs-extra");
4
4
  const path = require("path");
5
- const readline = require("readline");
6
-
7
- const COMPONENTS = [
8
- "Select",
9
- "Grid",
10
- "MultiSelect",
11
- "Button",
12
- "DatePicker",
13
- "Checkbox",
14
- "DarkMode",
15
- "Dialog",
16
- "Input",
17
- "Modal",
18
- "Spinner",
19
- "Toast",
20
- "Uploader",
21
- "FormRenderer",
22
- "materialInput",
23
- ];
24
-
25
- const FRAMEWORKS = {
26
- next: "Next.js",
27
- vite: "Vite",
5
+ const { createInterface } = require("readline");
6
+
7
+ // Configuration
8
+ const CONFIG = {
9
+ components: [
10
+ "Select",
11
+ "Grid",
12
+ "MultiSelect",
13
+ "Button",
14
+ "DatePicker",
15
+ "Checkbox",
16
+ "DarkMode",
17
+ "Dialog",
18
+ "Input",
19
+ "Modal",
20
+ "Spinner",
21
+ "Toast",
22
+ "Uploader",
23
+ "FormRenderer",
24
+ "materialInput",
25
+ ],
26
+ frameworks: {
27
+ next: {
28
+ name: "Next.js",
29
+ path: ["app", "component-lib"],
30
+ },
31
+ vite: {
32
+ name: "Vite",
33
+ path: ["src", "component-lib"],
34
+ },
35
+ },
36
+ docs: "https://blackmax-designs.gitbook.io/building-block-v2.0",
28
37
  };
29
38
 
30
39
  const SOURCE_PATH = path.join(__dirname, "source", "components");
31
- let DEST_PATH;
40
+ let isFirstCopy = true;
32
41
 
33
- const rl = readline.createInterface({
42
+ const rl = createInterface({
34
43
  input: process.stdin,
35
44
  output: process.stdout,
36
45
  });
37
46
 
38
- let isFirstCopy = true;
47
+ const prompt = (question) =>
48
+ new Promise((resolve) => {
49
+ rl.question(question, resolve);
50
+ });
39
51
 
40
- function listFrameworks() {
41
- console.log("Available frameworks:");
52
+ const displayOptions = (options, title) => {
53
+ console.log(`\n${title}:\n`);
54
+ options.forEach((option, index) => console.log(`${index + 1}. ${option}`));
42
55
  console.log("");
43
- Object.entries(FRAMEWORKS).forEach(([key, name], index) => {
44
- console.log(`${index + 1}. ${name}`);
45
- });
46
- }
47
-
48
- function listComponents() {
49
- console.log(" ");
50
- console.log("Available components:");
51
- console.log(" ");
52
- COMPONENTS.forEach((component, index) => {
53
- console.log(`${index + 1}. ${component}`);
54
- });
55
- }
56
-
57
- function copyCommonFiles() {
58
- // Copy utils.ts
59
- const utilsSrc = path.join(SOURCE_PATH, "..", "utils.ts");
60
- const utilsDest = path.join(DEST_PATH, "utils.ts");
61
- fs.copySync(utilsSrc, utilsDest, { overwrite: true });
62
- console.log(`utils.ts copied successfully to ${utilsDest}`);
63
-
64
- // Copy GlobalStyles
65
- const globalStyleSrc = path.join(SOURCE_PATH, "..", "globalStyle.ts");
66
- const globalStyleDest = path.join(DEST_PATH, "globalStyle.ts");
67
- fs.copySync(globalStyleSrc, globalStyleDest, { overwrite: true });
68
- console.log(`globalStyle.ts copied successfully to ${globalStyleDest}`);
69
-
70
- // Copy icon folder
71
- const iconSrc = path.join(SOURCE_PATH, "..", "icon");
72
- const iconDest = path.join(DEST_PATH, "icon");
73
- fs.copySync(iconSrc, iconDest, { overwrite: true });
74
- console.log(`icon folder copied successfully to ${iconDest}`);
75
- }
76
-
77
- function copyComponent(component) {
78
- const componentSrc = path.join(SOURCE_PATH, component.toLowerCase());
79
- const componentDest = path.join(DEST_PATH, component.toLowerCase());
80
-
81
- if (!fs.existsSync(componentSrc)) {
82
- console.error(`Component ${component} not found in source directory.`);
83
- return;
56
+ };
57
+
58
+ const copyCommonFiles = async (destPath) => {
59
+ const commonFiles = [
60
+ { src: ["..", "utils.ts"], dest: "utils.ts" },
61
+ { src: ["..", "globalStyle.ts"], dest: "globalStyle.ts" },
62
+ { src: ["..", "icon"], dest: "icon" },
63
+ ];
64
+
65
+ for (const file of commonFiles) {
66
+ const src = path.join(SOURCE_PATH, ...file.src);
67
+ const dest = path.join(destPath, file.dest);
68
+ await fs.copy(src, dest, { overwrite: true });
69
+ console.log(`${file.dest} copied successfully to ${dest}`);
84
70
  }
71
+ };
85
72
 
86
- fs.copySync(componentSrc, componentDest, { overwrite: true });
87
- console.log("");
88
- console.log(`Component ${component} copied successfully to ${componentDest}`);
89
- console.log("");
90
- console.log(
91
- "For Props and Usage Guides Visit : https://blackmax-designs.gitbook.io/building-block-v2.0"
92
- );
93
- console.log("");
73
+ const copySelectionHooks = async (destPath) => {
74
+ const hooksSrc = path.join(SOURCE_PATH, "..", "hooks", "SelectionHooks");
75
+ const hooksDest = path.join(destPath, "hooks", "SelectionHooks");
76
+
77
+ await fs.ensureDir(path.dirname(hooksDest));
94
78
 
95
- if (isFirstCopy) {
96
- copyCommonFiles();
97
- isFirstCopy = false;
79
+ if (!fs.existsSync(hooksSrc)) {
80
+ throw new Error(`SelectionHooks not found at ${hooksSrc}`);
98
81
  }
99
- }
100
82
 
101
- function promptForFramework() {
102
- listFrameworks();
103
- rl.question("Select your framework (enter the number): ", (answer) => {
104
- const index = parseInt(answer) - 1;
105
- const frameworks = Object.keys(FRAMEWORKS);
83
+ await fs.copy(hooksSrc, hooksDest, { overwrite: true });
84
+ console.log(`SelectionHooks copied successfully to ${hooksDest}`);
85
+ };
106
86
 
107
- if (isNaN(index) || index < 0 || index >= frameworks.length) {
108
- console.log("Invalid selection. Please try again.");
109
- promptForFramework();
110
- return;
87
+ const copyComponent = async (component, destPath) => {
88
+ try {
89
+ const componentSrc = path.join(SOURCE_PATH, component.toLowerCase());
90
+ const componentDest = path.join(destPath, component.toLowerCase());
91
+
92
+ if (!fs.existsSync(componentSrc)) {
93
+ throw new Error(`Component ${component} not found in source directory.`);
111
94
  }
112
95
 
113
- const selectedFramework = frameworks[index];
96
+ await fs.copy(componentSrc, componentDest, { overwrite: true });
97
+ console.log(
98
+ `\nComponent ${component} copied successfully to ${componentDest}`
99
+ );
114
100
 
115
- // Set destination path based on framework
116
- if (selectedFramework === "next") {
117
- DEST_PATH = path.join(process.cwd(), "app", "component-lib");
118
- } else {
119
- DEST_PATH = path.join(process.cwd(), "src", "component-lib");
101
+ if (isFirstCopy) {
102
+ await copyCommonFiles(destPath);
103
+ isFirstCopy = false;
120
104
  }
121
105
 
122
- // Ensure the destination directory exists
123
- fs.ensureDirSync(DEST_PATH);
124
-
125
- // Continue with component selection
126
- promptForComponent();
127
- });
128
- }
129
-
130
- function promptForComponent() {
131
- listComponents();
132
- rl.question(
133
- 'Enter the number of the component you want to copy (or "q" to quit): ',
134
- (answer) => {
135
- if (answer.toLowerCase() === "q") {
136
- rl.close();
137
- return;
138
- }
139
-
140
- const index = parseInt(answer) - 1;
141
- if (isNaN(index) || index < 0 || index >= COMPONENTS.length) {
142
- console.log("Invalid selection. Please try again.");
143
- promptForComponent();
144
- return;
145
- }
146
-
147
- const selectedComponent = COMPONENTS[index];
148
- copyComponent(selectedComponent);
149
-
150
- rl.question(
151
- "Do you want to copy another component? (y/n): ",
152
- (answer) => {
153
- if (answer.toLowerCase() === "y") {
154
- promptForComponent();
155
- } else {
156
- rl.close();
157
- }
158
- }
159
- );
106
+ if (["Select", "MultiSelect"].includes(component)) {
107
+ await copySelectionHooks(destPath);
160
108
  }
109
+
110
+ console.log(`\nFor Props and Usage Guides Visit: ${CONFIG.docs}\n`);
111
+ } catch (error) {
112
+ console.error(`Error copying component ${component}:`, error.message);
113
+ }
114
+ };
115
+
116
+ const selectFramework = async () => {
117
+ displayOptions(
118
+ Object.values(CONFIG.frameworks).map((f) => f.name),
119
+ "Available frameworks"
161
120
  );
162
- }
163
121
 
164
- // Start with framework selection
165
- promptForFramework();
122
+ const answer = await prompt("Select your framework (enter the number): ");
123
+ const index = parseInt(answer) - 1;
124
+ const frameworks = Object.keys(CONFIG.frameworks);
125
+
126
+ if (isNaN(index) || index < 0 || index >= frameworks.length) {
127
+ throw new Error("Invalid framework selection");
128
+ }
129
+
130
+ return frameworks[index];
131
+ };
132
+
133
+ const handleComponentSelection = async (destPath) => {
134
+ while (true) {
135
+ displayOptions(CONFIG.components, "Available components");
136
+
137
+ const answer = await prompt(
138
+ 'Enter the number of the component to copy (or "q" to quit): '
139
+ );
140
+
141
+ if (answer.toLowerCase() === "q") break;
142
+
143
+ const index = parseInt(answer) - 1;
144
+ if (isNaN(index) || index < 0 || index >= CONFIG.components.length) {
145
+ console.log("Invalid selection. Please try again.");
146
+ continue;
147
+ }
148
+
149
+ await copyComponent(CONFIG.components[index], destPath);
150
+
151
+ const continueAnswer = await prompt(
152
+ "Do you want to copy another component? (y/n): "
153
+ );
154
+ if (continueAnswer.toLowerCase() !== "y") break;
155
+ }
156
+ };
157
+
158
+ const main = async () => {
159
+ try {
160
+ const framework = await selectFramework();
161
+ const destPath = path.join(
162
+ process.cwd(),
163
+ ...CONFIG.frameworks[framework].path
164
+ );
165
+
166
+ await fs.ensureDir(destPath);
167
+ await handleComponentSelection(destPath);
168
+ } catch (error) {
169
+ console.error("Error:", error.message);
170
+ process.exit(1);
171
+ } finally {
172
+ rl.close();
173
+ }
174
+ };
175
+
176
+ // Run the script
177
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
4
4
  "description": "React Component Library",
5
5
  "files": [
6
6
  "index.js",
@@ -155,7 +155,7 @@ export const DatePicker = ({
155
155
  {selectedDate ? selectedDate.toLocaleDateString() : placeholder}
156
156
  </span>
157
157
  </button>
158
- <p className={primary["error-primary"]}>{error && error}</p>
158
+ {error && <p className={primary["error-primary"]}>{error}</p>}
159
159
 
160
160
  {/* Hidden input to integrate with the form */}
161
161
  <input
@@ -93,7 +93,7 @@ export const Input = ({
93
93
  />
94
94
  </button>
95
95
  )}
96
- <div className="text-xs text-red-500">{error && error}</div>
96
+ {error && <div className="text-xs text-red-500">{error}</div>}
97
97
  </div>
98
98
  ) : (
99
99
  <div className="otp-container">
@@ -62,7 +62,7 @@ const MaterialInput = ({
62
62
  ${
63
63
  error
64
64
  ? "border-red-500 focus:border-red-500"
65
- : "border-gray-300 focus:border-blue-500"
65
+ : "border-gray-300 focus:border-black"
66
66
  }
67
67
  ${disabled ? "text-gray-400 cursor-not-allowed" : "text-gray-900"}
68
68
  `}
@@ -91,7 +91,7 @@ const MaterialInput = ({
91
91
  ${
92
92
  error
93
93
  ? "border-red-500 focus:border-red-500"
94
- : "border-gray-300 focus:border-blue-500"
94
+ : "border-gray-300 focus:border-black"
95
95
  }
96
96
  ${disabled ? "text-gray-400 cursor-not-allowed" : "text-gray-900"}
97
97
  `}
@@ -109,7 +109,7 @@ const MaterialInput = ({
109
109
  error
110
110
  ? "text-red-500"
111
111
  : isFocused
112
- ? "text-blue-500"
112
+ ? "text-black"
113
113
  : "text-gray-500"
114
114
  }
115
115
  ${disabled ? "text-gray-400 cursor-not-allowed" : ""}
@@ -5,88 +5,132 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import React, { useEffect } from "react";
8
+ import React, { useEffect, useCallback, memo, useRef } from "react";
9
9
  import { twMerge } from "tailwind-merge";
10
- import type { ModalProps } from "./types";
11
10
  import Icon from "../icon/Icon";
12
11
  import { x } from "../icon/iconPaths";
12
+ import { ModalProps } from "./types";
13
13
 
14
- export const Modal = ({
15
- showModal = false,
16
- modalTitle = "Modal Title",
17
- modalClass = "fixed z-10 overflow-y-auto inset-0 flex items-center justify-center bg-gray-500 bg-opacity-75 transition-opacity",
18
- modalContentClass = "bg-white m-10 md:w-[80vh] rounded-xl relative",
19
- classModalContent = "",
20
- modalTitleClass = "p-4 text-lg leading-6 font-medium text-gray-900 flex justify-between items-center",
21
- classModalTitle = "",
22
- children,
23
- showCloseButton = false,
24
- onClose,
25
- dissmissible = false,
26
- }: ModalProps) => {
27
- // Handle ESC key press to close modal
28
- useEffect(() => {
29
- const handleEscKey = (event: KeyboardEvent) => {
30
- if (event.key === "Escape" && showModal && onClose) {
31
- onClose();
14
+ const defaultClasses = {
15
+ modal:
16
+ "fixed z-10 overflow-y-auto inset-0 flex items-center justify-center bg-gray-500 bg-opacity-75 transition-opacity",
17
+ modalContent: "bg-white m-10 md:w-[80vh] rounded-xl relative",
18
+ modalTitle:
19
+ "p-4 text-lg leading-6 font-medium text-gray-900 flex justify-between items-center",
20
+ closeButton:
21
+ "p-2 hover:bg-gray-100 rounded-full transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-300",
22
+ closeIcon: "h-5 w-5 stroke-gray-500 fill-none dark:stroke-white",
23
+ };
24
+
25
+ export const Modal = memo(
26
+ ({
27
+ showModal = false,
28
+ modalTitle = "Modal Title",
29
+ modalClass = defaultClasses.modal,
30
+ modalContentClass = defaultClasses.modalContent,
31
+ classModalContent = "",
32
+ modalTitleClass = defaultClasses.modalTitle,
33
+ classModalTitle = "",
34
+ children,
35
+ showCloseButton = false,
36
+ onClose,
37
+ dismissible = false,
38
+ titleId = "modal-title",
39
+ closeButtonContent,
40
+ animationDuration = 200,
41
+ }: ModalProps) => {
42
+ const modalRef = useRef<HTMLDivElement>(null);
43
+ const previousActiveElement = useRef<HTMLElement | null>(null);
44
+
45
+ // Handle ESC key press to close modal
46
+ const handleEscKey = useCallback(
47
+ (event: KeyboardEvent) => {
48
+ if (event.key === "Escape" && showModal && onClose) {
49
+ onClose();
50
+ }
51
+ },
52
+ [showModal, onClose]
53
+ );
54
+
55
+ // Handle click outside modal to close
56
+ const handleOutsideClick = useCallback(
57
+ (e: React.MouseEvent<HTMLDivElement>) => {
58
+ if (onClose && e.target === e.currentTarget && dismissible) {
59
+ onClose();
60
+ }
61
+ },
62
+ [onClose, dismissible]
63
+ );
64
+
65
+ // Manage focus trap and keyboard events
66
+ useEffect(() => {
67
+ if (showModal) {
68
+ previousActiveElement.current = document.activeElement as HTMLElement;
69
+ document.addEventListener("keydown", handleEscKey);
70
+ document.body.style.overflow = "hidden";
71
+ modalRef.current?.focus();
72
+ } else {
73
+ document.removeEventListener("keydown", handleEscKey);
74
+ document.body.style.overflow = "";
75
+ previousActiveElement.current?.focus();
32
76
  }
33
- };
34
77
 
35
- if (showModal) {
36
- document.addEventListener("keydown", handleEscKey);
37
- }
78
+ return () => {
79
+ document.removeEventListener("keydown", handleEscKey);
80
+ document.body.style.overflow = "";
81
+ };
82
+ }, [showModal, handleEscKey]);
38
83
 
39
- return () => {
40
- document.removeEventListener("keydown", handleEscKey);
84
+ // Animation styles
85
+ const modalStyles: React.CSSProperties = {
86
+ transition: `opacity ${animationDuration}ms ease-in-out`,
87
+ opacity: showModal ? 1 : 0,
88
+ visibility: showModal ? "visible" : "hidden",
41
89
  };
42
- }, [showModal, onClose]);
43
90
 
44
- // Handle click outside modal to close
45
- const handleOutsideClick = (e: React.MouseEvent) => {
46
- if (onClose && e.target === e.currentTarget && dissmissible) {
47
- onClose();
91
+ if (!showModal) {
92
+ return null;
48
93
  }
49
- };
50
94
 
51
- return (
52
- <>
53
- {showModal && (
95
+ return (
96
+ <div
97
+ className={modalClass}
98
+ aria-labelledby={titleId}
99
+ role="dialog"
100
+ aria-modal="true"
101
+ onClick={handleOutsideClick}
102
+ style={modalStyles}
103
+ >
54
104
  <div
55
- className={modalClass}
56
- aria-labelledby="modal-title"
57
- role="dialog"
58
- aria-modal="true"
59
- onClick={handleOutsideClick}
105
+ ref={modalRef}
106
+ className={twMerge(modalContentClass, classModalContent)}
107
+ onClick={(e: React.MouseEvent) => e.stopPropagation()}
108
+ role="document"
109
+ tabIndex={-1}
60
110
  >
61
- <div
62
- className={twMerge(modalContentClass, classModalContent)}
63
- onClick={(e: React.MouseEvent) => {
64
- e.stopPropagation();
65
- }}
66
- role="dialog"
67
- >
68
- <div className={twMerge(modalTitleClass, classModalTitle)}>
69
- <span>{modalTitle}</span>
70
- {showCloseButton && onClose && (
71
- <button
72
- onClick={onClose}
73
- className="p-2 hover:bg-gray-100 rounded-full transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-300"
74
- aria-label="Close modal"
75
- >
76
- <Icon
77
- elements={x}
78
- svgClass={
79
- "h-5 w-5 stroke-gray-500 fill-none dark:stroke-white"
80
- }
81
- />
82
- </button>
83
- )}
84
- </div>
85
- <hr className="border-gray-200" />
86
- <div className="p-4">{children}</div>
111
+ <div className={twMerge(modalTitleClass, classModalTitle)}>
112
+ <h2 id={titleId}>{modalTitle}</h2>
113
+ {showCloseButton && onClose && (
114
+ <button
115
+ onClick={onClose}
116
+ className={defaultClasses.closeButton}
117
+ aria-label="Close modal"
118
+ type="button"
119
+ >
120
+ {closeButtonContent || (
121
+ <Icon elements={x} svgClass={defaultClasses.closeIcon} />
122
+ )}
123
+ </button>
124
+ )}
87
125
  </div>
126
+ <hr className="border-gray-200" />
127
+ <div className="p-4">{children}</div>
88
128
  </div>
89
- )}
90
- </>
91
- );
92
- };
129
+ </div>
130
+ );
131
+ }
132
+ );
133
+
134
+ Modal.displayName = "Modal";
135
+
136
+ export default Modal;
@@ -1,14 +1,16 @@
1
1
  export type ModalProps = {
2
2
  showModal?: boolean;
3
3
  modalTitle?: string;
4
- autoclose?: boolean;
5
4
  modalClass?: string;
6
5
  modalContentClass?: string;
7
6
  classModalContent?: string;
8
7
  modalTitleClass?: string;
9
8
  classModalTitle?: string;
10
- children?: React.ReactNode;
9
+ children: React.ReactNode;
11
10
  showCloseButton?: boolean;
12
11
  onClose?: () => void;
13
- dissmissible?: boolean;
12
+ dismissible?: boolean;
13
+ titleId?: string;
14
+ closeButtonContent?: React.ReactNode;
15
+ animationDuration?: number;
14
16
  };