gbs-add-block 0.0.50 → 0.0.52-beta

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.50)
1
+ # GBS Building Blocks 2.0 (v0.0.51)
2
2
 
3
3
  Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
4
4
 
@@ -6,7 +6,7 @@ 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.50)
9
+ ## What's New 🎉 (Ver 0.0.51)
10
10
 
11
11
  - Update on form renderer
12
12
  - Refactored Code for Better Readability
package/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  const fs = require("fs-extra");
4
4
  const path = require("path");
5
- const { createInterface } = require("readline");
5
+ const yargs = require("yargs/yargs");
6
+ const { hideBin } = require("yargs/helpers");
6
7
 
7
8
  // Configuration
8
9
  const CONFIG = {
@@ -21,7 +22,8 @@ const CONFIG = {
21
22
  "Toast",
22
23
  "Uploader",
23
24
  "FormRenderer",
24
- "materialInput",
25
+ "MaterialInput",
26
+ "ContextMenu",
25
27
  ],
26
28
  frameworks: {
27
29
  next: {
@@ -37,23 +39,6 @@ const CONFIG = {
37
39
  };
38
40
 
39
41
  const SOURCE_PATH = path.join(__dirname, "source", "components");
40
- let isFirstCopy = true;
41
-
42
- const rl = createInterface({
43
- input: process.stdin,
44
- output: process.stdout,
45
- });
46
-
47
- const prompt = (question) =>
48
- new Promise((resolve) => {
49
- rl.question(question, resolve);
50
- });
51
-
52
- const displayOptions = (options, title) => {
53
- console.log(`\n${title}:\n`);
54
- options.forEach((option, index) => console.log(`${index + 1}. ${option}`));
55
- console.log("");
56
- };
57
42
 
58
43
  const copyCommonFiles = async (destPath) => {
59
44
  const commonFiles = [
@@ -66,7 +51,7 @@ const copyCommonFiles = async (destPath) => {
66
51
  const src = path.join(SOURCE_PATH, ...file.src);
67
52
  const dest = path.join(destPath, file.dest);
68
53
  await fs.copy(src, dest, { overwrite: true });
69
- console.log(`${file.dest} copied successfully to ${dest}`);
54
+ console.log(`✓ ${file.dest} copied successfully`);
70
55
  }
71
56
  };
72
57
 
@@ -80,80 +65,103 @@ const copyComponent = async (component, destPath) => {
80
65
  }
81
66
 
82
67
  await fs.copy(componentSrc, componentDest, { overwrite: true });
83
- console.log(
84
- `\nComponent ${component} copied successfully to ${componentDest}`
85
- );
86
-
87
- if (isFirstCopy) {
88
- await copyCommonFiles(destPath);
89
- isFirstCopy = false;
90
- }
91
-
92
- console.log(`\nFor Props and Usage Guides Visit: ${CONFIG.docs}\n`);
68
+ console.log(`✓ Component ${component} installed successfully`);
69
+ console.log(`\nFor documentation visit: ${CONFIG.docs}`);
93
70
  } catch (error) {
94
- console.error(`Error copying component ${component}:`, error.message);
71
+ console.error(`Error installing component ${component}:`, error.message);
72
+ process.exit(1);
95
73
  }
96
74
  };
97
75
 
98
- const selectFramework = async () => {
99
- displayOptions(
100
- Object.values(CONFIG.frameworks).map((f) => f.name),
101
- "Available frameworks"
102
- );
103
-
104
- const answer = await prompt("Select your framework (enter the number): ");
105
- const index = parseInt(answer) - 1;
106
- const frameworks = Object.keys(CONFIG.frameworks);
107
-
108
- if (isNaN(index) || index < 0 || index >= frameworks.length) {
109
- throw new Error("Invalid framework selection");
76
+ const detectFramework = () => {
77
+ // Check for Next.js
78
+ if (fs.existsSync(path.join(process.cwd(), "next.config.js"))) {
79
+ return "next";
110
80
  }
111
-
112
- return frameworks[index];
81
+ // Check for Vite
82
+ if (
83
+ fs.existsSync(path.join(process.cwd(), "vite.config.js")) ||
84
+ fs.existsSync(path.join(process.cwd(), "vite.config.ts"))
85
+ ) {
86
+ return "vite";
87
+ }
88
+ return null;
113
89
  };
114
90
 
115
- const handleComponentSelection = async (destPath) => {
116
- while (true) {
117
- displayOptions(CONFIG.components, "Available components");
91
+ const main = async () => {
92
+ const argv = yargs(hideBin(process.argv))
93
+ .option("add", {
94
+ alias: "a",
95
+ describe: "Component to install",
96
+ type: "string",
97
+ })
98
+ .option("framework", {
99
+ alias: "f",
100
+ describe: "Framework to use (next or vite)",
101
+ type: "string",
102
+ })
103
+ .option("list", {
104
+ alias: "l",
105
+ describe: "List available components",
106
+ type: "boolean",
107
+ })
108
+ .help().argv;
109
+
110
+ // List components if requested
111
+ if (argv.list) {
112
+ console.log("\nAvailable components:");
113
+ CONFIG.components.forEach((comp) => console.log(`- ${comp}`));
114
+ return;
115
+ }
118
116
 
119
- const answer = await prompt(
120
- 'Enter the number of the component to copy (or "q" to quit): '
121
- );
117
+ if (!argv.add) {
118
+ console.error("Please specify a component to install using -a or --add");
119
+ process.exit(1);
120
+ }
122
121
 
123
- if (answer.toLowerCase() === "q") break;
122
+ // Validate component name
123
+ const component = argv.add;
124
+ if (!CONFIG.components.includes(component)) {
125
+ console.error(`Invalid component: ${component}`);
126
+ console.log("\nAvailable components:");
127
+ CONFIG.components.forEach((comp) => console.log(`- ${comp}`));
128
+ process.exit(1);
129
+ }
124
130
 
125
- const index = parseInt(answer) - 1;
126
- if (isNaN(index) || index < 0 || index >= CONFIG.components.length) {
127
- console.log("Invalid selection. Please try again.");
128
- continue;
131
+ // Detect or get framework
132
+ let framework = argv.framework;
133
+ if (!framework) {
134
+ framework = detectFramework();
135
+ if (!framework) {
136
+ console.error(
137
+ "Could not detect framework. Please specify using -f or --framework"
138
+ );
139
+ process.exit(1);
129
140
  }
130
-
131
- await copyComponent(CONFIG.components[index], destPath);
132
-
133
- const continueAnswer = await prompt(
134
- "Do you want to copy another component? (y/n): "
135
- );
136
- if (continueAnswer.toLowerCase() !== "y") break;
137
141
  }
138
- };
139
142
 
140
- const main = async () => {
141
- try {
142
- const framework = await selectFramework();
143
- const destPath = path.join(
144
- process.cwd(),
145
- ...CONFIG.frameworks[framework].path
146
- );
147
-
148
- await fs.ensureDir(destPath);
149
- await handleComponentSelection(destPath);
150
- } catch (error) {
151
- console.error("Error:", error.message);
143
+ if (!CONFIG.frameworks[framework]) {
144
+ console.error(`Unsupported framework: ${framework}`);
152
145
  process.exit(1);
153
- } finally {
154
- rl.close();
155
146
  }
147
+
148
+ // Create destination directory
149
+ const destPath = path.join(
150
+ process.cwd(),
151
+ ...CONFIG.frameworks[framework].path
152
+ );
153
+ await fs.ensureDir(destPath);
154
+
155
+ // Copy common files if they don't exist
156
+ if (!fs.existsSync(path.join(destPath, "utils.ts"))) {
157
+ await copyCommonFiles(destPath);
158
+ }
159
+
160
+ // Copy the requested component
161
+ await copyComponent(component, destPath);
156
162
  };
157
163
 
158
- // Run the script
159
- main();
164
+ main().catch((error) => {
165
+ console.error("Error:", error.message);
166
+ process.exit(1);
167
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "0.0.50",
3
+ "version": "0.0.52-beta",
4
4
  "description": "React Component Library",
5
5
  "files": [
6
6
  "index.js",
@@ -20,7 +20,8 @@
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
22
  "fs-extra": "^11.2.0",
23
- "path": "^0.12.7"
23
+ "path": "^0.12.7",
24
+ "yargs": "^17.7.2"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@types/fs-extra": "^11.0.4"
@@ -0,0 +1,30 @@
1
+ import { ContextMenuItemProps } from "./types";
2
+
3
+ export const ContextMenuItem: React.FC<ContextMenuItemProps> = ({
4
+ onClick,
5
+ icon,
6
+ children,
7
+ disabled = false,
8
+ }) => (
9
+ <button
10
+ onClick={(e: React.MouseEvent) => {
11
+ e.stopPropagation();
12
+ if (!disabled) {
13
+ onClick?.();
14
+ }
15
+ }}
16
+ disabled={disabled}
17
+ className={`w-full px-4 py-2 text-left flex items-center gap-2 ${
18
+ disabled
19
+ ? "text-gray-400 cursor-not-allowed"
20
+ : "hover:bg-gray-100 cursor-pointer"
21
+ }`}
22
+ >
23
+ {icon && <span className="w-4 h-4">{icon}</span>}
24
+ {children}
25
+ </button>
26
+ );
27
+
28
+ export const ContextMenuDivider: React.FC = () => (
29
+ <div className="my-1 border-t border-gray-200" />
30
+ );
@@ -0,0 +1,63 @@
1
+ import React, { useState, useEffect, useCallback, MouseEvent } from "react";
2
+ import { ContextMenuProps, Position } from "./types";
3
+
4
+ export const ContextMenu: React.FC<ContextMenuProps> = ({ children }) => {
5
+ const [isVisible, setIsVisible] = useState<boolean>(false);
6
+ const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
7
+
8
+ const handleContextMenu = useCallback(
9
+ (event: MouseEvent | globalThis.MouseEvent) => {
10
+ event.preventDefault();
11
+ setIsVisible(true);
12
+ setPosition({
13
+ x: event.pageX,
14
+ y: event.pageY,
15
+ });
16
+ },
17
+ []
18
+ );
19
+
20
+ const handleClick = useCallback(() => {
21
+ if (isVisible) setIsVisible(false);
22
+ }, [isVisible]);
23
+
24
+ useEffect(() => {
25
+ document.addEventListener("click", handleClick);
26
+ document.addEventListener("contextmenu", handleContextMenu);
27
+
28
+ return () => {
29
+ document.removeEventListener("click", handleClick);
30
+ document.removeEventListener("contextmenu", handleContextMenu);
31
+ };
32
+ }, [handleClick, handleContextMenu]);
33
+
34
+ // Ensure menu stays within viewport bounds
35
+ const adjustedPosition = useCallback((position: Position): Position => {
36
+ const menuWidth = 160; // min-width from CSS
37
+ const menuHeight = 200; // approximate max height
38
+
39
+ return {
40
+ x: Math.min(position.x, window.innerWidth - menuWidth),
41
+ y: Math.min(position.y, window.innerHeight - menuHeight),
42
+ };
43
+ }, []);
44
+
45
+ const finalPosition = adjustedPosition(position);
46
+
47
+ return (
48
+ <>
49
+ {isVisible && (
50
+ <div
51
+ className="fixed bg-white rounded-lg shadow-lg border border-gray-200 py-2 min-w-[160px]"
52
+ style={{
53
+ top: finalPosition.y,
54
+ left: finalPosition.x,
55
+ zIndex: 1000,
56
+ }}
57
+ >
58
+ {children}
59
+ </div>
60
+ )}
61
+ </>
62
+ );
63
+ };
@@ -0,0 +1,19 @@
1
+ import { ReactNode } from "react";
2
+
3
+ interface Position {
4
+ x: number;
5
+ y: number;
6
+ }
7
+
8
+ interface ContextMenuProps {
9
+ children: ReactNode;
10
+ }
11
+
12
+ interface ContextMenuItemProps {
13
+ onClick?: () => void;
14
+ icon?: ReactNode;
15
+ children: ReactNode;
16
+ disabled?: boolean;
17
+ }
18
+
19
+ export type { Position, ContextMenuProps, ContextMenuItemProps };
@@ -29,6 +29,7 @@ export const DatePicker = ({
29
29
  name,
30
30
  error,
31
31
  placeholder = "Select a date",
32
+ disabled = false,
32
33
  }: DatePickerProps) => {
33
34
  applyScrollbarStyles();
34
35
 
@@ -61,6 +62,7 @@ export const DatePicker = ({
61
62
  setCurrentMonth,
62
63
  setCurrentYear
63
64
  );
65
+
64
66
  useClickOutside(
65
67
  dateRef as React.RefObject<HTMLElement>,
66
68
  () => {
@@ -124,7 +126,10 @@ export const DatePicker = ({
124
126
  onClick={toggleDatepicker}
125
127
  className={`${
126
128
  error ? primary["error-border"] : "border"
127
- } p-2 rounded-lg w-full flex items-center gap-2 dark:text-white`}
129
+ } p-2 rounded-lg w-full flex items-center gap-2 dark:text-white ${
130
+ disabled ? "opacity-50 cursor-not-allowed" : ""
131
+ }`}
132
+ disabled={disabled}
128
133
  >
129
134
  <Icon
130
135
  dimensions={{ width: "20", height: "20" }}
@@ -144,7 +149,7 @@ export const DatePicker = ({
144
149
  readOnly
145
150
  />
146
151
 
147
- {showDatepicker && (
152
+ {showDatepicker && !disabled && (
148
153
  <div className="absolute z-10 bg-white border border-gray-300 shadow-lg mt-1 w-full rounded dark:bg-black dark:text-white px-2">
149
154
  <div className="flex justify-between items-center p-2">
150
155
  <button
@@ -8,4 +8,5 @@ export type DatePickerProps = {
8
8
  onDateChange?: (date: Date) => void;
9
9
  name?: string;
10
10
  error?: string;
11
+ disabled?: boolean;
11
12
  };
@@ -1,6 +1,7 @@
1
- import React from "react";
1
+ import React, { useCallback, useEffect, useState } from "react";
2
2
  import { DatePicker } from "../../datepicker";
3
3
  import { DatePickerHandlesProps } from "../types";
4
+ import { evaluateExpression } from "@grampro/headless-helpers";
4
5
 
5
6
  export default function DatePickerHandles({
6
7
  item,
@@ -8,7 +9,38 @@ export default function DatePickerHandles({
8
9
  setRequirementError,
9
10
  formRef,
10
11
  onChangeEvent,
12
+ context,
13
+ updateContext,
11
14
  }: DatePickerHandlesProps) {
15
+ const [isDisabled, setIsDisabled] = useState<boolean>(false);
16
+ const [isRequired, setIsRequired] = useState<boolean>(false);
17
+
18
+ // Evaluate expression with memoization
19
+ const evaluateCondition = useCallback(
20
+ (expression: string | boolean | undefined): boolean => {
21
+ if (typeof expression === "string") {
22
+ return evaluateExpression(expression, context);
23
+ }
24
+ return !!expression;
25
+ },
26
+ [context]
27
+ );
28
+
29
+ // Initialize context and handle dynamic states
30
+ useEffect(() => {
31
+ if (item?.name && item?.value !== undefined) {
32
+ updateContext("datepicker", item.name, item.value);
33
+ }
34
+
35
+ const disabled = evaluateCondition(item?.disabled);
36
+
37
+ setIsDisabled(disabled);
38
+
39
+ // Only set required if not disabled
40
+ const required = disabled ? false : evaluateCondition(item?.required);
41
+ setIsRequired(required);
42
+ }, [item, context, evaluateCondition, updateContext]);
43
+
12
44
  const handleSelectDate = (value: string[], key: string) => {
13
45
  if (formRef && formRef.current) {
14
46
  const hiddenInput = formRef.current.querySelector(
@@ -34,7 +66,7 @@ export default function DatePickerHandles({
34
66
  {item?.label && (
35
67
  <label htmlFor={item.name} className="font-medium text-sm">
36
68
  {item.label}
37
- {item.required && <span className="text-red-500">*</span>}
69
+ {isRequired && <span className="text-red-500">*</span>}
38
70
  </label>
39
71
  )}
40
72
  <DatePicker
@@ -29,11 +29,10 @@ export default function MultiHandles({
29
29
  // Initialize context and handle dynamic states
30
30
  useEffect(() => {
31
31
  if (item?.name && item?.value !== undefined) {
32
- updateContext("select", item.name, item.value);
32
+ updateContext("multi-select", item.name, item.value);
33
33
  }
34
34
 
35
35
  const disabled = evaluateCondition(item?.disabled);
36
- console.log("disabled", disabled);
37
36
 
38
37
  setIsDisabled(disabled);
39
38
 
@@ -34,7 +34,6 @@ export default function SelectHandles({
34
34
  }
35
35
 
36
36
  const disabled = evaluateCondition(item?.disabled);
37
- console.log("disabled", disabled);
38
37
 
39
38
  setIsDisabled(disabled);
40
39
 
@@ -1,3 +1,37 @@
1
+ import InputHandles from "./componentHandles/InputHandles";
2
+ import SelectHandles from "./componentHandles/SelectHandles";
3
+ import MultiHandles from "./componentHandles/MultiHandles";
4
+ import DatePickerHandles from "./componentHandles/DatePickerHandles";
5
+ import CheckboxHandles from "./componentHandles/CheckBoxHandles";
6
+ import { FormItem } from "./types";
7
+
8
+ // Component Map
9
+ export const COMPONENT_MAP = {
10
+ input: InputHandles,
11
+ select: SelectHandles,
12
+ "multi-select": MultiHandles,
13
+ datepicker: DatePickerHandles,
14
+ checkbox: CheckboxHandles,
15
+ } as const;
16
+
17
+ // Field Validation Helper
18
+ export const validateField = (
19
+ field: HTMLInputElement | null,
20
+ item: FormItem
21
+ ): boolean => {
22
+ if (!item.required || !item.name || field?.disabled) {
23
+ return true;
24
+ }
25
+ // Explicitly check for null and empty value
26
+ if (!field) {
27
+ return false;
28
+ }
29
+ return field.value.trim() !== "";
30
+ };
31
+
32
+ // Input Validation functions starts here
33
+ // You can add more validation functions here or
34
+ // use zod for more complex validation
1
35
  export const validateEmail = (email: string) => {
2
36
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3
37
  return emailRegex.test(email);
@@ -8,19 +42,6 @@ export const validatePhoneNumber = (phone: string) => {
8
42
  return phoneRegex.test(phone);
9
43
  };
10
44
 
11
- // In case of return as objects
12
- export const formDataToObject = (formData: FormData) => {
13
- const obj: Record<string, any> = {};
14
- formData.forEach((value, key) => {
15
- try {
16
- obj[key] = JSON.parse(value as string);
17
- } catch {
18
- obj[key] = value;
19
- }
20
- });
21
- return obj;
22
- };
23
-
24
45
  export const validateInput = (value: string, type?: string): string | null => {
25
46
  if (!value) return null;
26
47
 
@@ -35,3 +56,17 @@ export const validateInput = (value: string, type?: string): string | null => {
35
56
  return null;
36
57
  }
37
58
  };
59
+ // Input Validation functions ends here
60
+
61
+ // In case of return as objects
62
+ export const formDataToObject = (formData: FormData) => {
63
+ const obj: Record<string, any> = {};
64
+ formData.forEach((value, key) => {
65
+ try {
66
+ obj[key] = JSON.parse(value as string);
67
+ } catch {
68
+ obj[key] = value;
69
+ }
70
+ });
71
+ return obj;
72
+ };
@@ -1,39 +1,19 @@
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
+ */
7
+
1
8
  import React, { useRef, useState, FormEvent } from "react";
2
9
  import { useFormContext } from "@grampro/headless-helpers";
3
- import InputHandles from "./componentHandles/InputHandles";
4
- import SelectHandles from "./componentHandles/SelectHandles";
5
- import MultiHandles from "./componentHandles/MultiHandles";
6
- import DatePickerHandles from "./componentHandles/DatePickerHandles";
7
- import CheckboxHandles from "./componentHandles/CheckBoxHandles";
8
10
  import { FormItem, FormRendererProps } from "./types";
9
-
10
- // Component mapping
11
- const COMPONENT_MAP = {
12
- input: InputHandles,
13
- select: SelectHandles,
14
- "multi-select": MultiHandles,
15
- datepicker: DatePickerHandles,
16
- checkbox: CheckboxHandles,
17
- } as const;
18
-
19
- const validateField = (
20
- field: HTMLInputElement | null,
21
- item: FormItem
22
- ): boolean => {
23
- if (!item.required || !item.name || field?.disabled) {
24
- return true;
25
- }
26
- // Explicitly check for null and empty value
27
- if (!field) {
28
- return false;
29
- }
30
- return field.value.trim() !== "";
31
- };
11
+ import { COMPONENT_MAP, validateField } from "./helperFunctions";
32
12
 
33
13
  const FormRenderer: React.FC<FormRendererProps> = ({
34
14
  onSubmit,
35
15
  sourceData = [],
36
- formFormationClass = "grid grid-cols-1 text-left gap-4",
16
+ formFormationClass = "grid grid-cols-1 gap-2",
37
17
  formParentClass = "w-96",
38
18
  }) => {
39
19
  const formRef = useRef<HTMLFormElement>(null);
@@ -89,6 +89,12 @@ export type DatePickerHandlesProps = {
89
89
  setRequirementError?: React.Dispatch<React.SetStateAction<string[]>>;
90
90
  formRef?: React.RefObject<HTMLFormElement | null>;
91
91
  onChangeEvent?: (event: any) => void;
92
+ context: FormContext;
93
+ updateContext: (
94
+ componentName: string,
95
+ fieldName: string,
96
+ value: FieldValue
97
+ ) => void;
92
98
  };
93
99
 
94
100
  export type SelectHandlesProps = {
@@ -154,6 +154,7 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
154
154
  name={name}
155
155
  value={JSON.stringify(selected) || ""}
156
156
  readOnly
157
+ disabled={disabled}
157
158
  />
158
159
 
159
160
  {showPopover && !disabled && (
@@ -139,7 +139,13 @@ const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
139
139
  {error && <p className={primary["error-primary"]}>{error}</p>}
140
140
  </div>
141
141
 
142
- <input type="hidden" name={name} value={selectedItem || ""} readOnly />
142
+ <input
143
+ type="hidden"
144
+ name={name}
145
+ value={selectedItem || ""}
146
+ readOnly
147
+ disabled={disabled}
148
+ />
143
149
 
144
150
  {showPopover && !disabled && (
145
151
  <div className={popUp["pop-up-style"]}>