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 +2 -2
- package/index.js +88 -80
- package/package.json +3 -2
- package/source/components/contextmenu/ContextMenuItem.tsx +30 -0
- package/source/components/contextmenu/index.tsx +63 -0
- package/source/components/contextmenu/types.ts +19 -0
- package/source/components/datepicker/index.tsx +7 -2
- package/source/components/datepicker/types.ts +1 -0
- package/source/components/formrenderer/componentHandles/DatePickerHandles.tsx +34 -2
- package/source/components/formrenderer/componentHandles/MultiHandles.tsx +1 -2
- package/source/components/formrenderer/componentHandles/SelectHandles.tsx +0 -1
- package/source/components/formrenderer/helperFunctions.ts +48 -13
- package/source/components/formrenderer/index.tsx +9 -29
- package/source/components/formrenderer/types.ts +6 -0
- package/source/components/multiselect/index.tsx +1 -0
- package/source/components/select/index.tsx +7 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GBS Building Blocks 2.0 (v0.0.
|
|
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.
|
|
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
|
|
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
|
-
"
|
|
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(
|
|
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
|
-
|
|
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
|
|
71
|
+
console.error(`Error installing component ${component}:`, error.message);
|
|
72
|
+
process.exit(1);
|
|
95
73
|
}
|
|
96
74
|
};
|
|
97
75
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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.
|
|
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
|
|
@@ -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
|
-
{
|
|
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
|
|
|
@@ -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
|
|
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 = {
|
|
@@ -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
|
|
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"]}>
|