gbs-add-block 0.0.44 → 0.0.46

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.44)
1
+ # GBS Building Blocks 2.0 (v0.0.46)
2
2
 
3
3
  Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
4
4
 
@@ -6,9 +6,10 @@ 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.44)
9
+ ## What's New 🎉 (Ver 0.0.46)
10
10
 
11
- - Refactored Code for Better Readability and removed dependency for Toast
11
+ - Refactored Code for Better Readability
12
+ - Code Reusablity and hook update
12
13
 
13
14
  ## Authors
14
15
 
package/index.js CHANGED
@@ -70,20 +70,6 @@ const copyCommonFiles = async (destPath) => {
70
70
  }
71
71
  };
72
72
 
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));
78
-
79
- if (!fs.existsSync(hooksSrc)) {
80
- throw new Error(`SelectionHooks not found at ${hooksSrc}`);
81
- }
82
-
83
- await fs.copy(hooksSrc, hooksDest, { overwrite: true });
84
- console.log(`SelectionHooks copied successfully to ${hooksDest}`);
85
- };
86
-
87
73
  const copyComponent = async (component, destPath) => {
88
74
  try {
89
75
  const componentSrc = path.join(SOURCE_PATH, component.toLowerCase());
@@ -103,10 +89,6 @@ const copyComponent = async (component, destPath) => {
103
89
  isFirstCopy = false;
104
90
  }
105
91
 
106
- if (["Select", "MultiSelect"].includes(component)) {
107
- await copySelectionHooks(destPath);
108
- }
109
-
110
92
  console.log(`\nFor Props and Usage Guides Visit: ${CONFIG.docs}\n`);
111
93
  } catch (error) {
112
94
  console.error(`Error copying component ${component}:`, error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "description": "React Component Library",
5
5
  "files": [
6
6
  "index.js",
@@ -5,8 +5,15 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import React, { useState, useEffect, useRef } from "react";
9
- import { generateCalendarHelper, months } from "./DatePickerHelper";
8
+ import React, { useRef } from "react";
9
+ import {
10
+ useDatePickerState,
11
+ useYearMonthNavigation,
12
+ useClickOutside,
13
+ isAtYearLimit,
14
+ months,
15
+ applyScrollbarStyles,
16
+ } from "@grampro/headless-helpers";
10
17
  import Icon from "../icon/Icon";
11
18
  import { calender, down, leftArrows, rightArrows } from "../icon/iconPaths";
12
19
  import type { DatePickerProps } from "./types";
@@ -23,59 +30,51 @@ export const DatePicker = ({
23
30
  error,
24
31
  placeholder = "Select a date",
25
32
  }: DatePickerProps) => {
26
- const [showDatepicker, setShowDatepicker] = useState(false);
27
- const [showYearMonthPicker, setShowYearMonthPicker] = useState(false);
28
- const [currentMonth, setCurrentMonth] = useState(new Date().getMonth());
29
- const [currentYear, setCurrentYear] = useState(new Date().getFullYear());
30
- const [days, setDays] = useState<(Date | null)[][]>([]);
31
- const [selectedDate, setSelectedDate] = useState<Date | null>(
32
- selectedDateValue || null
33
- );
34
- const [tempSelectedDate, setTempSelectedDate] = useState<Date | null>(
35
- selectedDateValue || new Date()
36
- );
37
- const [hasMounted, setHasMounted] = useState(false);
33
+ applyScrollbarStyles();
38
34
 
39
35
  const dateRef = useRef<HTMLDivElement>(null);
40
36
  const today = new Date();
41
-
42
37
  const actualCurrentYear = today.getFullYear();
38
+
39
+ const {
40
+ showDatepicker,
41
+ setShowDatepicker,
42
+ showYearMonthPicker,
43
+ setShowYearMonthPicker,
44
+ currentMonth,
45
+ setCurrentMonth,
46
+ currentYear,
47
+ setCurrentYear,
48
+ days,
49
+ selectedDate,
50
+ setSelectedDate,
51
+ tempSelectedDate,
52
+ setTempSelectedDate,
53
+ hasMounted,
54
+ } = useDatePickerState(selectedDateValue || null);
55
+
56
+ const { prevMonth, nextMonth } = useYearMonthNavigation(
57
+ currentMonth,
58
+ currentYear,
59
+ yearLimitStart,
60
+ yearLimitEnd,
61
+ setCurrentMonth,
62
+ setCurrentYear
63
+ );
64
+ useClickOutside(
65
+ dateRef as React.RefObject<HTMLElement>,
66
+ () => {
67
+ setShowDatepicker(false);
68
+ setTempSelectedDate(selectedDate || new Date());
69
+ },
70
+ [selectedDate]
71
+ );
72
+
43
73
  const years = Array.from(
44
74
  { length: yearLimitStart + yearLimitEnd + 1 },
45
75
  (_, i) => actualCurrentYear - yearLimitStart + i
46
76
  ).sort((a, b) => b - a);
47
77
 
48
- useEffect(() => {
49
- if (selectedDateValue) {
50
- setSelectedDate(selectedDateValue);
51
- setTempSelectedDate(selectedDateValue);
52
- setCurrentMonth(selectedDateValue.getMonth());
53
- setCurrentYear(selectedDateValue.getFullYear());
54
- }
55
- }, [selectedDateValue]);
56
-
57
- useEffect(() => {
58
- setHasMounted(true);
59
- }, []);
60
-
61
- useEffect(() => {
62
- function handleClickOutside(event: MouseEvent) {
63
- if (dateRef.current && !dateRef.current.contains(event.target as Node)) {
64
- setShowDatepicker(false);
65
- setTempSelectedDate(selectedDate || new Date());
66
- }
67
- }
68
-
69
- document.addEventListener("mousedown", handleClickOutside);
70
- return () => {
71
- document.removeEventListener("mousedown", handleClickOutside);
72
- };
73
- }, [selectedDate]);
74
-
75
- useEffect(() => {
76
- setDays(generateCalendarHelper(currentYear, currentMonth));
77
- }, [currentYear, currentMonth]);
78
-
79
78
  const toggleDatepicker = () => {
80
79
  setShowDatepicker(!showDatepicker);
81
80
  setShowYearMonthPicker(false);
@@ -102,25 +101,6 @@ export const DatePicker = ({
102
101
  setCurrentYear(year);
103
102
  setCurrentMonth(month);
104
103
  setShowYearMonthPicker(false);
105
- setDays(generateCalendarHelper(year, month));
106
- };
107
-
108
- const prevMonth = () => {
109
- if (currentMonth === 0) {
110
- setCurrentMonth(11);
111
- setCurrentYear(currentYear - 1);
112
- } else {
113
- setCurrentMonth(currentMonth - 1);
114
- }
115
- };
116
-
117
- const nextMonth = () => {
118
- if (currentMonth === 11) {
119
- setCurrentMonth(0);
120
- setCurrentYear(currentYear + 1);
121
- } else {
122
- setCurrentMonth(currentMonth + 1);
123
- }
124
104
  };
125
105
 
126
106
  const goToToday = () => {
@@ -157,7 +137,6 @@ export const DatePicker = ({
157
137
  </button>
158
138
  {error && <p className={primary["error-primary"]}>{error}</p>}
159
139
 
160
- {/* Hidden input to integrate with the form */}
161
140
  <input
162
141
  type="hidden"
163
142
  name={name}
@@ -171,11 +150,28 @@ export const DatePicker = ({
171
150
  <button
172
151
  type="button"
173
152
  onClick={prevMonth}
174
- className="text-gray-500 hover:text-gray-700 w-8 h-8 rounded-full flex items-center justify-center hover:bg-gray-100"
153
+ disabled={isAtYearLimit(
154
+ "prev",
155
+ currentMonth,
156
+ currentYear,
157
+ yearLimitStart,
158
+ yearLimitEnd
159
+ )}
160
+ className={`text-gray-500 hover:text-gray-700 w-8 h-8 rounded-full flex items-center justify-center hover:bg-gray-100 ${
161
+ isAtYearLimit(
162
+ "prev",
163
+ currentMonth,
164
+ currentYear,
165
+ yearLimitStart,
166
+ yearLimitEnd
167
+ )
168
+ ? "opacity-50"
169
+ : ""
170
+ }`}
175
171
  >
176
172
  <Icon
177
173
  elements={leftArrows}
178
- svgClass={"stroke-gray-500 fill-none dark:stroke-white"}
174
+ svgClass={"stroke-black fill-none dark:stroke-white"}
179
175
  />
180
176
  </button>
181
177
  <div className="flex items-center space-x-2">
@@ -199,7 +195,24 @@ export const DatePicker = ({
199
195
  <button
200
196
  type="button"
201
197
  onClick={nextMonth}
202
- className="text-gray-500 hover:text-gray-700 w-8 h-8 rounded-full flex items-center justify-center hover:bg-gray-100"
198
+ disabled={isAtYearLimit(
199
+ "next",
200
+ currentMonth,
201
+ currentYear,
202
+ yearLimitStart,
203
+ yearLimitEnd
204
+ )}
205
+ className={`text-gray-500 hover:text-gray-700 w-8 h-8 rounded-full flex items-center justify-center hover:bg-gray-100 ${
206
+ isAtYearLimit(
207
+ "next",
208
+ currentMonth,
209
+ currentYear,
210
+ yearLimitStart,
211
+ yearLimitEnd
212
+ )
213
+ ? "opacity-50"
214
+ : ""
215
+ }`}
203
216
  >
204
217
  <Icon
205
218
  elements={rightArrows}
@@ -219,7 +232,7 @@ export const DatePicker = ({
219
232
  onClick={() => selectYearMonth(currentYear, index)}
220
233
  className={`w-full text-left p-2 cursor-pointer rounded hover:bg-gray-200 transition duration-150 ease-in-out ${
221
234
  index === currentMonth
222
- ? "bg-blue-500 text-white hover:bg-blue-600"
235
+ ? "bg-black text-white hover:bg-blue-600"
223
236
  : ""
224
237
  }`}
225
238
  >
@@ -238,7 +251,7 @@ export const DatePicker = ({
238
251
  onClick={() => selectYearMonth(year, currentMonth)}
239
252
  className={`w-full text-left p-2 cursor-pointer rounded hover:bg-gray-200 transition duration-150 ease-in-out ${
240
253
  year === currentYear
241
- ? "bg-blue-500 text-white hover:bg-blue-600"
254
+ ? "bg-black text-white hover:bg-blue-600"
242
255
  : ""
243
256
  }`}
244
257
  >
@@ -277,7 +290,9 @@ export const DatePicker = ({
277
290
  onClick={() => !isDisabled && selectDate(day)}
278
291
  disabled={isDisabled}
279
292
  className={`text-center p-1 w-8 h-8 cursor-pointer rounded-md hover:bg-gray-200 transition duration-150 ease-in-out ${
280
- isSelected ? "bg-black text-white hover:bg-gray-800 " : ""
293
+ isSelected
294
+ ? "bg-black text-white hover:bg-gray-800 dark:bg-white"
295
+ : ""
281
296
  } ${
282
297
  isDisabled
283
298
  ? "bg-gray-100 text-gray-400 cursor-not-allowed"
@@ -1,11 +1,13 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import { CheckboxHandlesProps, FormItem } from "./types";
2
+ import { CheckboxHandlesProps } from "../types";
3
3
 
4
4
  const CheckboxHandles = ({
5
5
  item,
6
6
  formRef,
7
7
  requirementError,
8
8
  setRequirementError,
9
+ onChangeEvent,
10
+ updateContext,
9
11
  }: CheckboxHandlesProps) => {
10
12
  const [checked, setChecked] = useState<boolean>(!!item?.value);
11
13
 
@@ -47,6 +49,9 @@ const CheckboxHandles = ({
47
49
  prevErrors.filter((error) => error !== item.name)
48
50
  );
49
51
  }
52
+ onChangeEvent?.(e);
53
+
54
+ item?.name && updateContext("checkbox", item.name, newValue);
50
55
  }}
51
56
  className={`w-5 h-5 cursor-pointer rounded-md border-2 transition-colors duration-200 ${
52
57
  item?.name && requirementError.includes(item?.name)
@@ -1,12 +1,13 @@
1
1
  import React from "react";
2
- import { DatePicker } from "../datepicker";
3
- import { FormItem } from "./types";
2
+ import { DatePicker } from "../../datepicker";
3
+ import { FormItem } from "../types";
4
4
 
5
5
  interface DatePickerHandlesProps {
6
6
  item?: FormItem;
7
7
  requirementError: string[];
8
8
  setRequirementError?: React.Dispatch<React.SetStateAction<string[]>>;
9
9
  formRef?: React.RefObject<HTMLFormElement | null>;
10
+ onChangeEvent?: (event: any) => void;
10
11
  }
11
12
 
12
13
  export default function DatePickerHandles({
@@ -14,6 +15,7 @@ export default function DatePickerHandles({
14
15
  requirementError,
15
16
  setRequirementError,
16
17
  formRef,
18
+ onChangeEvent,
17
19
  }: DatePickerHandlesProps) {
18
20
  const handleSelectDate = (value: string[], key: string) => {
19
21
  if (formRef && formRef.current) {
@@ -52,6 +54,7 @@ export default function DatePickerHandles({
52
54
  prevErrors.filter((errorName) => errorName !== item.name)
53
55
  );
54
56
  }
57
+ onChangeEvent?.(value);
55
58
  }}
56
59
  error={
57
60
  item?.name && requirementError.includes(item.name)
@@ -0,0 +1,113 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { validateEmail, validatePhoneNumber } from "../helperFunctions";
3
+ import { Input } from "../../input";
4
+ import { FieldValue, FormContext, FormItem } from "../types";
5
+ import { evaluateExpression } from "@grampro/expression-evaluator";
6
+ import { twMerge } from "tailwind-merge";
7
+
8
+ interface InputHandlesProps {
9
+ item?: FormItem;
10
+ requirementError: string[];
11
+ setRequirementError?: React.Dispatch<React.SetStateAction<string[]>>;
12
+ onChangeEvent?: (event: any) => void;
13
+ formRef: React.RefObject<HTMLFormElement | null>;
14
+ context: FormContext;
15
+ updateContext: (
16
+ componentName: string,
17
+ fieldName: string,
18
+ value: FieldValue
19
+ ) => void;
20
+ }
21
+
22
+ const InputHandles = ({
23
+ item,
24
+ requirementError,
25
+ setRequirementError,
26
+ onChangeEvent,
27
+ context,
28
+ updateContext,
29
+ }: InputHandlesProps) => {
30
+ const [inputError, setInputError] = useState<string | null>(null);
31
+ const [isDisabled, setIsDisabled] = useState<boolean>(false);
32
+ const [isRequired, setIsRequired] = useState<boolean>(false);
33
+
34
+ // Initialize context with default values
35
+ useEffect(() => {
36
+ if (item?.name && item?.value !== undefined) {
37
+ updateContext("input", item.name, item.value);
38
+ }
39
+ }, [item?.name, item?.value, updateContext]);
40
+
41
+ // Handle dynamic disabled/required states expressions
42
+ useEffect(() => {
43
+ if (typeof item?.disabled === "string") {
44
+ setIsDisabled(evaluateExpression(item.disabled, context));
45
+ } else if (typeof item?.disabled === "boolean") {
46
+ setIsDisabled(item.disabled);
47
+ }
48
+
49
+ if (typeof item?.required === "string") {
50
+ setIsRequired(evaluateExpression(item.required, context));
51
+ } else if (typeof item?.required === "boolean") {
52
+ setIsRequired(item.required);
53
+ }
54
+ }, [item, context]);
55
+
56
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
57
+ const { value } = event.target;
58
+
59
+ if (item?.name) {
60
+ updateContext("input", item.name, value);
61
+
62
+ setRequirementError?.((prevErrors) =>
63
+ prevErrors.filter((error) => error !== item.name)
64
+ );
65
+ }
66
+
67
+ // Validation based on input type
68
+ if (item?.type === "email" && !validateEmail(value)) {
69
+ setInputError("Invalid email format");
70
+ } else if (item?.type === "tel" && !validatePhoneNumber(value)) {
71
+ setInputError("Invalid phone number. Must be 10 digits.");
72
+ } else {
73
+ setInputError(null);
74
+ }
75
+
76
+ // Trigger onChange event
77
+ onChangeEvent?.(event);
78
+ };
79
+
80
+ return (
81
+ <div className="w-full">
82
+ {item?.label && (
83
+ <label htmlFor={item.name} className="font-medium text-sm">
84
+ {item.label}
85
+ {isRequired && <span className="text-red-500">*</span>}
86
+ </label>
87
+ )}
88
+ <Input
89
+ type={item?.type}
90
+ name={item?.name}
91
+ placeholder={item?.placeholder || ""}
92
+ onChange={handleChange}
93
+ className={twMerge(
94
+ `border rounded-md px-4 py-[6px] w-full text-black ${
95
+ inputError || (item?.name && requirementError.includes(item?.name))
96
+ ? "border-red-500"
97
+ : "border-gray-300"
98
+ }`,
99
+ item?.stepProperty?.customClass?.inputClass
100
+ )}
101
+ defaultValue={item?.value ?? ""}
102
+ disabled={isDisabled}
103
+ required={isRequired}
104
+ />
105
+ {item?.name && requirementError.includes(item.name) && (
106
+ <p className="text-red-500 text-xs">{`${item?.name} is required`}</p>
107
+ )}
108
+ {inputError && <p className="text-red-500 text-xs">{inputError}</p>}
109
+ </div>
110
+ );
111
+ };
112
+
113
+ export default InputHandles;
@@ -1,12 +1,13 @@
1
1
  import React from "react";
2
- import { FormItem } from "./types";
3
- import { MultiSelect } from "../multiselect";
2
+ import { FormItem } from "../types";
3
+ import { MultiSelect } from "../../multiselect";
4
4
 
5
5
  interface MultiSelectHandlesProps {
6
6
  item?: FormItem;
7
7
  requirementError: string[];
8
8
  setRequirementError?: React.Dispatch<React.SetStateAction<string[]>>;
9
9
  formRef?: React.RefObject<HTMLFormElement | null>;
10
+ onChangeEvent?: (event: any) => void;
10
11
  }
11
12
 
12
13
  export default function MultiHandles({
@@ -14,6 +15,7 @@ export default function MultiHandles({
14
15
  requirementError,
15
16
  setRequirementError,
16
17
  formRef,
18
+ onChangeEvent,
17
19
  }: MultiSelectHandlesProps) {
18
20
  const handleSelect = (value: string[], key: string) => {
19
21
  if (formRef && formRef.current) {
@@ -53,6 +55,7 @@ export default function MultiHandles({
53
55
  prevErrors.filter((errorName) => errorName !== item.name)
54
56
  );
55
57
  }
58
+ onChangeEvent?.(value);
56
59
  }}
57
60
  error={
58
61
  item?.name && requirementError.includes(item.name)
@@ -0,0 +1,80 @@
1
+ import React, { useState } from "react";
2
+ import { Select } from "../../select";
3
+
4
+ interface Option {
5
+ value: string;
6
+ label: string;
7
+ }
8
+
9
+ interface SelectHandlesProps {
10
+ item?: {
11
+ name?: string;
12
+ label?: string;
13
+ options?: Option[];
14
+ value?: string;
15
+ required?: boolean;
16
+ };
17
+ requirementError: string[];
18
+ setRequirementError: React.Dispatch<React.SetStateAction<string[]>>;
19
+ formRef: React.RefObject<HTMLFormElement | null>;
20
+ onChangeEvent?: (event: any) => void;
21
+ }
22
+
23
+ export default function SelectHandles({
24
+ item,
25
+ requirementError,
26
+ setRequirementError,
27
+ formRef,
28
+ onChangeEvent,
29
+ }: SelectHandlesProps) {
30
+ const [options] = useState<Option[]>(item?.options || []);
31
+
32
+ const handleSelect = (value: string | undefined, key: string) => {
33
+ if (!formRef?.current) return;
34
+
35
+ let input = formRef.current.querySelector(
36
+ `input[name="${key}"]`
37
+ ) as HTMLInputElement | null;
38
+
39
+ if (!input) {
40
+ input = document.createElement("input");
41
+ input.type = "hidden";
42
+ input.name = key;
43
+ formRef.current.appendChild(input);
44
+ }
45
+
46
+ if (value) {
47
+ input.value = value;
48
+ }
49
+ const event = new Event("change", { bubbles: true });
50
+ input.dispatchEvent(event);
51
+ };
52
+
53
+ return (
54
+ <div className="w-full">
55
+ {item?.label && (
56
+ <label htmlFor={item.name} className="font-medium text-sm">
57
+ {item.label}
58
+ {item.required && <span className="text-red-500">*</span>}
59
+ </label>
60
+ )}
61
+ <Select
62
+ name={item?.name}
63
+ items={options}
64
+ selectedItem={item?.value ?? ""}
65
+ onSelect={(value: string | undefined) => {
66
+ item?.name && handleSelect(value, item.name);
67
+ setRequirementError((prevErrors) =>
68
+ prevErrors.filter((errorName) => errorName !== item?.name)
69
+ );
70
+ onChangeEvent?.(value);
71
+ }}
72
+ error={
73
+ item?.name && requirementError.includes(item.name)
74
+ ? `${item.name} is required`
75
+ : undefined
76
+ }
77
+ />
78
+ </div>
79
+ );
80
+ }
@@ -6,23 +6,25 @@
6
6
  */
7
7
 
8
8
  import React, { useRef, useState } from "react";
9
- import InputHandles from "./InputHandles";
10
- import SelectHandles from "./SelectHandles";
9
+ import { useFormContext } from "@grampro/expression-evaluator";
11
10
  import { FormElement, FormItem, FormRendererProps } from "./types";
12
- import MultiHandles from "./MultiHandles";
13
- import DatePickerHandles from "./DatePickerHandles";
14
- import CheckboxHandles from "./CheckBoxHandles";
11
+ import InputHandles from "./componentHandles/InputHandles";
12
+ import SelectHandles from "./componentHandles/SelectHandles";
13
+ import MultiHandles from "./componentHandles/MultiHandles";
14
+ import DatePickerHandles from "./componentHandles/DatePickerHandles";
15
+ import CheckboxHandles from "./componentHandles/CheckBoxHandles";
15
16
 
16
17
  const FormRenderer = ({
17
18
  onSubmit,
18
19
  sourceData,
19
20
  formFormationClass = "grid grid-cols-1 text-left gap-4",
20
21
  formParentClass = "w-96",
21
- dependencyConfig,
22
22
  }: FormRendererProps) => {
23
23
  const formRef = useRef<FormElement>(null);
24
24
  const [requirementError, setRequirementError] = useState<string[]>([]);
25
25
 
26
+ const { context, updateContext } = useFormContext();
27
+
26
28
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
27
29
  event.preventDefault();
28
30
  const requirementErrorItems: string[] = [];
@@ -60,6 +62,10 @@ const FormRenderer = ({
60
62
  item={item}
61
63
  requirementError={requirementError}
62
64
  setRequirementError={setRequirementError}
65
+ onChangeEvent={item.onChangeEvent}
66
+ formRef={formRef}
67
+ context={context}
68
+ updateContext={updateContext}
63
69
  />
64
70
  );
65
71
 
@@ -67,11 +73,17 @@ const FormRenderer = ({
67
73
  return (
68
74
  <SelectHandles
69
75
  key={index}
70
- item={item}
76
+ item={{
77
+ name: item.name,
78
+ label: item.label,
79
+ options: item.options,
80
+ value: item.value,
81
+ required: Boolean(item.required),
82
+ }}
71
83
  requirementError={requirementError}
72
84
  setRequirementError={setRequirementError}
73
85
  formRef={formRef}
74
- dependencyMap={dependencyConfig}
86
+ onChangeEvent={item.onChangeEvent}
75
87
  />
76
88
  );
77
89
 
@@ -83,6 +95,7 @@ const FormRenderer = ({
83
95
  requirementError={requirementError}
84
96
  setRequirementError={setRequirementError}
85
97
  formRef={formRef}
98
+ onChangeEvent={item.onChangeEvent}
86
99
  />
87
100
  );
88
101
 
@@ -94,6 +107,7 @@ const FormRenderer = ({
94
107
  requirementError={requirementError}
95
108
  setRequirementError={setRequirementError}
96
109
  formRef={formRef}
110
+ onChangeEvent={item.onChangeEvent}
97
111
  />
98
112
  );
99
113
 
@@ -105,6 +119,10 @@ const FormRenderer = ({
105
119
  requirementError={requirementError}
106
120
  setRequirementError={setRequirementError}
107
121
  formRef={formRef}
122
+ onChangeEvent={item.onChangeEvent}
123
+ sourceData={sourceData}
124
+ context={context}
125
+ updateContext={updateContext}
108
126
  />
109
127
  );
110
128
 
@@ -7,7 +7,7 @@ export type FormItem = {
7
7
  name?: string;
8
8
  component?: string;
9
9
  type?: string;
10
- required?: boolean;
10
+ required?: boolean | string;
11
11
  placeholder?: string;
12
12
  options?: { value: string; label: string }[];
13
13
  value?: string;
@@ -17,6 +17,13 @@ export type FormItem = {
17
17
  dependency?: string;
18
18
  hasDependents?: boolean;
19
19
  isReady?: boolean;
20
+ disabled?: string | boolean;
21
+ onChangeEvent?: (event: any) => void;
22
+ stepProperty?: {
23
+ customClass?: {
24
+ inputClass?: string;
25
+ };
26
+ };
20
27
  };
21
28
 
22
29
  interface DependencyConfig {
@@ -58,4 +65,22 @@ export type CheckboxHandlesProps = {
58
65
  requirementError: string[];
59
66
  setRequirementError?: React.Dispatch<React.SetStateAction<string[]>>;
60
67
  formRef?: React.RefObject<HTMLFormElement | null>;
68
+ onChangeEvent?: (event: any) => void;
69
+ changeTrigger?: number;
70
+ setChangeTrigger?: React.Dispatch<React.SetStateAction<number>>;
71
+ sourceData?: FormItem[];
72
+ context: FormContext;
73
+ updateContext: (
74
+ componentName: string,
75
+ fieldName: string,
76
+ value: FieldValue
77
+ ) => void;
61
78
  };
79
+
80
+ export type FieldValue = string | boolean | number | null;
81
+
82
+ export interface FormContext {
83
+ [componentName: string]: {
84
+ [fieldName: string]: FieldValue;
85
+ };
86
+ }
@@ -13,9 +13,11 @@ import {
13
13
  memo,
14
14
  useImperativeHandle,
15
15
  } from "react";
16
- import { useClickOutside } from "../hooks/SelectionHooks/useClickOutside";
17
16
  import type { MultiSelectHandle, MultiSelectProps } from "./types";
18
- import { useMultiSelectState } from "../hooks/SelectionHooks/useMultiSelectState";
17
+ import {
18
+ useMultiSelectState,
19
+ useClickOutside,
20
+ } from "@grampro/headless-helpers";
19
21
  import Icon from "../icon/Icon";
20
22
  import { check, search, upDown, x } from "../icon/iconPaths";
21
23
  import { iconClass, popUp, primary } from "../globalStyle";
@@ -51,7 +53,9 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
51
53
  getSelectItems,
52
54
  } = useMultiSelectState(items, selectedItems, lazy, onSelect);
53
55
 
54
- useClickOutside(selectRef, () => setShowPopover(false));
56
+ useClickOutside(selectRef as React.RefObject<HTMLElement>, () =>
57
+ setShowPopover(false)
58
+ );
55
59
 
56
60
  useEffect(() => {
57
61
  if (showPopover) {
@@ -18,9 +18,11 @@ import { check, search, upDown, x } from "../icon/iconPaths";
18
18
  import type { SelectHandle, SelectProps } from "./types";
19
19
  import { selectStyle } from "./style";
20
20
  import { iconClass, popUp, primary } from "../globalStyle";
21
- import { useSelectState } from "../hooks/SelectionHooks/useSelectState";
22
- import { useSelectData } from "../hooks/SelectionHooks/useSelectData";
23
- import { useClickOutside } from "../hooks/SelectionHooks/useClickOutside";
21
+ import {
22
+ useSelectState,
23
+ useSelectData,
24
+ useClickOutside,
25
+ } from "@grampro/headless-helpers";
24
26
 
25
27
  const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
26
28
  const {
@@ -61,7 +63,9 @@ const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
61
63
  const inputRef = useRef<HTMLInputElement>(null);
62
64
  const selectRef = useRef<HTMLDivElement>(null);
63
65
 
64
- useClickOutside(selectRef, () => setShowPopover(false));
66
+ useClickOutside(selectRef as React.RefObject<HTMLElement>, () =>
67
+ setShowPopover(false)
68
+ );
65
69
 
66
70
  useEffect(() => {
67
71
  if (showPopover) {
@@ -70,7 +74,9 @@ const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
70
74
  }, [showPopover]);
71
75
 
72
76
  useEffect(() => {
73
- setSelectedItem(initialSelectedItem);
77
+ if (initialSelectedItem) {
78
+ setSelectedItem(initialSelectedItem);
79
+ }
74
80
  }, [initialSelectedItem, setSelectedItem]);
75
81
 
76
82
  const handleSelect = useCallback(
@@ -1,42 +0,0 @@
1
- const getDaysInMonth = (year: number, month: number) => {
2
- return new Date(year, month + 1, 0).getDate();
3
- };
4
-
5
- export const generateCalendarHelper = (year: number, month: number) => {
6
- const daysInMonth = getDaysInMonth(year, month);
7
- const firstDay = new Date(year, month, 1).getDay();
8
- const weeks = [];
9
- let day = 1;
10
-
11
- for (let i = 0; i < 6; i++) {
12
- const week = [];
13
- for (let j = 0; j < 7; j++) {
14
- if (i === 0 && j < firstDay) {
15
- week.push(null);
16
- } else if (day > daysInMonth) {
17
- week.push(null);
18
- } else {
19
- const date = new Date(year, month, day);
20
- week.push(date);
21
- day++;
22
- }
23
- }
24
- weeks.push(week);
25
- }
26
- return weeks;
27
- };
28
-
29
- export const months = [
30
- 'January',
31
- 'February',
32
- 'March',
33
- 'April',
34
- 'May',
35
- 'June',
36
- 'July',
37
- 'August',
38
- 'September',
39
- 'October',
40
- 'November',
41
- 'December'
42
- ];
@@ -1,71 +0,0 @@
1
- import React, { useState } from "react";
2
- import { validateEmail, validatePhoneNumber } from "./helperFunctions";
3
- import { Input } from "../input";
4
- import { FormItem } from "./types";
5
-
6
- interface InputHandlesProps {
7
- item?: FormItem;
8
- requirementError: string[];
9
- setRequirementError?: React.Dispatch<React.SetStateAction<string[]>>;
10
- }
11
-
12
- const InputHandles = ({
13
- item,
14
- requirementError,
15
- setRequirementError,
16
- }: InputHandlesProps) => {
17
- const [inputError, setInputError] = useState<string | null>(null);
18
-
19
- const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
20
- const { value } = event.target;
21
-
22
- setRequirementError &&
23
- setRequirementError((prevErrors: string[]) =>
24
- prevErrors.filter((error) => error !== item?.name)
25
- );
26
-
27
- // Validation based on input type
28
- if (item?.type === "email") {
29
- if (!validateEmail(value)) {
30
- setInputError("Invalid email format");
31
- } else {
32
- setInputError(null);
33
- }
34
- } else if (item?.type === "tel") {
35
- if (!validatePhoneNumber(value)) {
36
- setInputError("Invalid phone number. Must be 10 digits.");
37
- } else {
38
- setInputError(null);
39
- }
40
- }
41
- };
42
-
43
- return (
44
- <div className="w-full">
45
- {item?.label && (
46
- <label htmlFor={item.name} className="font-medium text-sm">
47
- {item.label}
48
- {item.required && <span className="text-red-500">*</span>}
49
- </label>
50
- )}
51
- <Input
52
- type={item?.type}
53
- name={item?.name}
54
- placeholder={item?.placeholder || ""}
55
- onChange={handleChange}
56
- className={`border rounded-md px-4 py-[6px] w-full text-black ${
57
- inputError || (item?.name && requirementError.includes(item?.name))
58
- ? "border-red-500"
59
- : "border-gray-300"
60
- }`}
61
- defaultValue={item?.value ?? ""}
62
- />
63
- {item?.name && requirementError.includes(item.name) && (
64
- <p className="text-red-500 text-xs">{`${item?.name} is required`}</p>
65
- )}
66
- {inputError && <p className="text-red-500 text-xs">{inputError}</p>}
67
- </div>
68
- );
69
- };
70
-
71
- export default InputHandles;
@@ -1,143 +0,0 @@
1
- import React, { useEffect, useState } from "react";
2
- import { Select } from "../select";
3
- import { Option, SelectHandlesProps } from "./types";
4
-
5
- export default function SelectHandles({
6
- item,
7
- requirementError,
8
- setRequirementError,
9
- formRef,
10
- dependencyMap = {},
11
- }: SelectHandlesProps) {
12
- const [options, setOptions] = useState<Option[]>(item?.options || []);
13
-
14
- const getFieldValue = (fieldName: string): string => {
15
- if (!formRef?.current) return "";
16
- const field = formRef.current.querySelector(
17
- `input[name="${fieldName}"]`
18
- ) as HTMLInputElement;
19
- return field?.value || "";
20
- };
21
-
22
- const getDependentOptions = (fieldName: string): Option[] => {
23
- const fieldConfig = dependencyMap[fieldName];
24
- if (!fieldConfig) return [];
25
-
26
- let currentData = fieldConfig.dataStructure;
27
-
28
- if (fieldConfig.parent) {
29
- const parentValue = getFieldValue(fieldConfig.parent);
30
- if (!parentValue) return [];
31
-
32
- const getNestedValue = (data: any, path: string[]): any => {
33
- return path.reduce((acc, key) => acc?.[key], data);
34
- };
35
-
36
- const parentChain: string[] = [];
37
- let currentField = fieldName;
38
- while (dependencyMap[currentField]?.parent) {
39
- const parent = dependencyMap[currentField].parent!;
40
- parentChain.unshift(getFieldValue(parent));
41
- currentField = parent;
42
- }
43
-
44
- currentData = getNestedValue(currentData, parentChain);
45
- }
46
-
47
- if (Array.isArray(currentData)) {
48
- return currentData.map((value) => ({ value, label: value }));
49
- } else if (typeof currentData === "object" && currentData !== null) {
50
- return Object.keys(currentData).map((key) => ({
51
- value: key,
52
- label: key,
53
- }));
54
- }
55
-
56
- return [];
57
- };
58
-
59
- useEffect(() => {
60
- if (!item?.name || !dependencyMap[item.name]) {
61
- return;
62
- }
63
-
64
- const updateOptions = () => {
65
- const newOptions = getDependentOptions(item.name!);
66
- setOptions(newOptions);
67
- };
68
-
69
- updateOptions();
70
-
71
- const parentField = dependencyMap[item.name]?.parent;
72
- if (parentField && formRef?.current) {
73
- const parentInput = formRef.current.querySelector(
74
- `input[name="${parentField}"]`
75
- );
76
-
77
- const handleParentChange = () => {
78
- if (formRef.current) {
79
- const currentField = formRef.current.querySelector(
80
- `input[name="${item.name}"]`
81
- ) as HTMLInputElement;
82
- if (currentField) {
83
- currentField.value = "";
84
- }
85
- }
86
- updateOptions();
87
- };
88
-
89
- parentInput?.addEventListener("change", handleParentChange);
90
- return () =>
91
- parentInput?.removeEventListener("change", handleParentChange);
92
- }
93
- }, [item?.name, dependencyMap, formRef]);
94
-
95
- const handleSelect = (value: string | undefined, key: string) => {
96
- if (!formRef?.current) return;
97
-
98
- let input = formRef.current.querySelector(
99
- `input[name="${key}"]`
100
- ) as HTMLInputElement;
101
-
102
- if (!input) {
103
- input = document.createElement("input");
104
- input.type = "hidden";
105
- input.name = key;
106
- formRef.current.appendChild(input);
107
- }
108
-
109
- if (value) {
110
- input.value = value;
111
- }
112
- const event = new Event("change", { bubbles: true });
113
- input.dispatchEvent(event);
114
- };
115
-
116
- return (
117
- <div className="w-full">
118
- {item?.label && (
119
- <label htmlFor={item.name} className="font-medium text-sm">
120
- {item.label}
121
- {item.required && <span className="text-red-500">*</span>}
122
- </label>
123
- )}
124
- <Select
125
- name={item?.name}
126
- items={options}
127
- selectedItem={item?.value ?? ""}
128
- onSelect={(value: string | undefined) => {
129
- item?.name && handleSelect(value, item.name);
130
- setRequirementError &&
131
- setRequirementError((prevErrors) =>
132
- prevErrors.filter((errorName) => errorName !== item?.name)
133
- );
134
- }}
135
- error={
136
- item?.name && requirementError.includes(item.name)
137
- ? `${item.name} is required`
138
- : undefined
139
- }
140
- />
141
- </div>
142
- );
143
- }