gbs-add-block 0.0.32 → 0.0.33

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.31)
1
+ # GBS Building Blocks 2.0 (v0.0.33)
2
2
 
3
3
  Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
4
4
 
@@ -8,9 +8,7 @@ For detailed documentation on usage and props, Please visit: [Building Block Doc
8
8
 
9
9
  ## What's New 🎉 (Ver 0.0.23)
10
10
 
11
- - Updated Grid
12
- - Updated Modal
13
- - New Components (Material Input (Material Design Inspired Input), Form Renderer(Dynamic Form Rendering))
11
+ - Updated Form Renderer (Added Multi Select)
14
12
 
15
13
  ## Authors
16
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "description": "React Component Library",
5
5
  "files": [
6
6
  "index.js",
@@ -52,7 +52,7 @@ const InputHandles = ({
52
52
  name={item?.name}
53
53
  placeholder={item?.placeholder || ""}
54
54
  onChange={handleChange}
55
- className={`border rounded p-2 w-full text-black ${
55
+ className={`border rounded-md px-4 py-[6px] w-full text-black ${
56
56
  inputError || (item?.name && requirementError.includes(item?.name))
57
57
  ? "border-red-500"
58
58
  : "border-gray-300"
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import { FormItem } from "./types";
3
+ import { MultiSelect } from "../multiselect";
4
+
5
+ interface MultiSelectHandlesProps {
6
+ item?: FormItem;
7
+ requirementError: string[];
8
+ setRequirementError?: React.Dispatch<React.SetStateAction<string[]>>;
9
+ formRef?: React.RefObject<HTMLFormElement | null>;
10
+ }
11
+
12
+ export default function MultiHandles({
13
+ item,
14
+ requirementError,
15
+ setRequirementError,
16
+ formRef,
17
+ }: MultiSelectHandlesProps) {
18
+ const handleSelect = (value: string[], key: string) => {
19
+ if (formRef && formRef.current) {
20
+ const hiddenInput = formRef.current.querySelector(
21
+ `input[name="${key}"]`
22
+ ) as HTMLInputElement;
23
+
24
+ if (hiddenInput) {
25
+ hiddenInput.value = Array.isArray(value)
26
+ ? JSON.stringify(value)
27
+ : value;
28
+ } else {
29
+ const input = document.createElement("input");
30
+ input.type = "hidden";
31
+ input.name = key;
32
+ input.value = Array.isArray(value) ? JSON.stringify(value) : value;
33
+ formRef.current.appendChild(input);
34
+ }
35
+ }
36
+ };
37
+
38
+ return (
39
+ <div className="w-full">
40
+ {item?.label && (
41
+ <label htmlFor={item.name} className="font-medium text-sm">
42
+ {item.label}
43
+ </label>
44
+ )}
45
+ <MultiSelect
46
+ name={item?.name}
47
+ items={item?.options}
48
+ onSelect={(value: string[]) => {
49
+ if (item?.name) {
50
+ handleSelect(value, item.name);
51
+ setRequirementError?.((prevErrors) =>
52
+ prevErrors.filter((errorName) => errorName !== item.name)
53
+ );
54
+ }
55
+ }}
56
+ error={
57
+ item?.name && requirementError.includes(item.name)
58
+ ? `${item.name} is required`
59
+ : undefined
60
+ }
61
+ />
62
+ </div>
63
+ );
64
+ }
@@ -45,7 +45,7 @@ export default function SelectHandles({
45
45
  items={item?.options}
46
46
  selectedItem={item?.value ?? ""}
47
47
  onSelect={(value: string) => {
48
- item?.key && handleSelect(value, item.key);
48
+ item?.name && handleSelect(value, item.name);
49
49
  setRequirementError &&
50
50
  setRequirementError((prevErrors: any) =>
51
51
  prevErrors.filter((errorName: any) => errorName !== item?.name)
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  /**
2
3
  * Copyright (c) Grampro Business Services and affiliates.
3
4
  *
@@ -9,6 +10,7 @@ import React, { useRef, useState } from "react";
9
10
  import InputHandles from "./InputHandles";
10
11
  import SelectHandles from "./SelectHandles";
11
12
  import { FormItem, FormRendererProps } from "./types";
13
+ import MultiHandles from "./MultiHandles";
12
14
 
13
15
  const FormRenderer = ({
14
16
  onSubmit,
@@ -71,6 +73,17 @@ const FormRenderer = ({
71
73
  />
72
74
  );
73
75
 
76
+ case "multi-select":
77
+ return (
78
+ <MultiHandles
79
+ key={index}
80
+ item={item}
81
+ requirementError={requirementError}
82
+ setRequirementError={setRequirementError}
83
+ formRef={formRef}
84
+ />
85
+ );
86
+
74
87
  case "button":
75
88
  return (
76
89
  <div key={index} className="w-full mt-1">
@@ -17,207 +17,223 @@ import React, {
17
17
  import Icon from "../icon/Icon";
18
18
  import { check, search, upDown, x } from "../icon/iconPaths";
19
19
  import { getSourceData } from "../utils";
20
- import type { MultiSelectProps } from "./types";
21
-
22
- interface ItemsProps {
23
- value: string;
24
- label: string;
25
- }
26
-
27
- const MultiSelect = forwardRef<any, MultiSelectProps>((props, ref) => {
28
- const {
29
- placeholder = "Select an Item...",
30
- items,
31
- lazy = false,
32
- showSearch = true,
33
- onSelect,
34
- truncate = true,
35
- selectedItems = [],
36
- } = props;
37
-
38
- const [showPopover, setShowPopover] = useState(false);
39
- const [workingDataSource, setWorkingDataSource] = useState<ItemsProps[]>([]);
40
- const [filteredItems, setFilteredItems] = useState<ItemsProps[]>([]);
41
- const [selected, setSelected] = useState<string[]>(selectedItems);
42
- const [searchTerm, setSearchTerm] = useState("");
43
- const inputRef = useRef<HTMLInputElement>(null);
44
- const selectRef = useRef<HTMLDivElement>(null);
45
-
46
- useEffect(() => {
47
- inputRef.current?.focus();
48
- }, [showPopover]);
49
-
50
- useEffect(() => {
51
- handleDataSource();
52
- }, [items]);
53
-
54
- useEffect(() => {
55
- const handleClickOutside = (event: MouseEvent) => {
56
- if (
57
- selectRef.current &&
58
- !selectRef.current.contains(event.target as Node)
59
- ) {
60
- setShowPopover(false);
20
+ import type { ItemsProps, MultiSelectHandle, MultiSelectProps } from "./types";
21
+ import { primary } from "../globalStyle";
22
+
23
+ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
24
+ (props, ref) => {
25
+ const {
26
+ placeholder = "Select Items...",
27
+ items,
28
+ lazy = false,
29
+ showSearch = true,
30
+ onSelect,
31
+ truncate = true,
32
+ selectedItems = [],
33
+ name,
34
+ error,
35
+ } = props;
36
+
37
+ const [showPopover, setShowPopover] = useState(false);
38
+ const [workingDataSource, setWorkingDataSource] = useState<ItemsProps[]>(
39
+ []
40
+ );
41
+ const [filteredItems, setFilteredItems] = useState<ItemsProps[]>([]);
42
+ const [selected, setSelected] = useState<string[]>(selectedItems);
43
+ const [searchTerm, setSearchTerm] = useState("");
44
+ const inputRef = useRef<HTMLInputElement>(null);
45
+ const selectRef = useRef<HTMLDivElement>(null);
46
+
47
+ useEffect(() => {
48
+ inputRef.current?.focus();
49
+ }, [showPopover]);
50
+
51
+ useEffect(() => {
52
+ handleDataSource();
53
+ }, [items]);
54
+
55
+ useEffect(() => {
56
+ const handleClickOutside = (event: MouseEvent) => {
57
+ if (
58
+ selectRef.current &&
59
+ !selectRef.current.contains(event.target as Node)
60
+ ) {
61
+ setShowPopover(false);
62
+ }
63
+ };
64
+ document.addEventListener("mousedown", handleClickOutside);
65
+ return () =>
66
+ document.removeEventListener("mousedown", handleClickOutside);
67
+ }, []);
68
+
69
+ const getSelectItems = async (itemsApi: string) => {
70
+ const itemsData = await getSourceData(itemsApi);
71
+ setWorkingDataSource(itemsData.sourcedata);
72
+ setFilteredItems(itemsData.sourcedata);
73
+ };
74
+
75
+ const handleDataSource = async () => {
76
+ if (Array.isArray(items)) {
77
+ setWorkingDataSource(items);
78
+ setFilteredItems(items);
79
+ } else if (typeof items === "string") {
80
+ await getSelectItems(items);
81
+ }
82
+ };
83
+
84
+ const togglePopover = (e: React.MouseEvent) => {
85
+ e.stopPropagation();
86
+ setShowPopover(!showPopover);
87
+ };
88
+
89
+ const handleSelect = useCallback(
90
+ (value: string) => {
91
+ const newSelected = selected.includes(value)
92
+ ? selected.filter((item) => item !== value)
93
+ : [...selected, value];
94
+ setSelected(newSelected);
95
+ onSelect?.(newSelected);
96
+ },
97
+ [selected, onSelect]
98
+ );
99
+
100
+ const getSelectedDisplay = () => {
101
+ if (selected.length === 0) return placeholder;
102
+ const displayedItems = selected
103
+ .slice(0, 3)
104
+ .map(
105
+ (value) =>
106
+ workingDataSource.find((item) => item.value === value)?.label || ""
107
+ );
108
+ let display = displayedItems.join(", ");
109
+ if (selected.length > 3 && truncate) {
110
+ display += `, +${selected.length - 3} more`;
61
111
  }
112
+ return display;
62
113
  };
63
- document.addEventListener("mousedown", handleClickOutside);
64
- return () => document.removeEventListener("mousedown", handleClickOutside);
65
- }, []);
66
-
67
- const getSelectItems = async (itemsApi: string) => {
68
- const itemsData = await getSourceData(itemsApi);
69
- setWorkingDataSource(itemsData.sourcedata);
70
- setFilteredItems(itemsData.sourcedata);
71
- };
72
-
73
- const handleDataSource = async () => {
74
- if (Array.isArray(items)) {
75
- setWorkingDataSource(items);
76
- setFilteredItems(items);
77
- } else if (typeof items === "string") {
78
- await getSelectItems(items);
79
- }
80
- };
81
-
82
- const togglePopover = (e: React.MouseEvent) => {
83
- e.stopPropagation();
84
- setShowPopover(!showPopover);
85
- };
86
-
87
- const handleSelect = useCallback(
88
- (value: string) => {
89
- const newSelected = selected.includes(value)
90
- ? selected.filter((item) => item !== value)
91
- : [...selected, value];
92
- setSelected(newSelected);
93
- onSelect?.(newSelected);
94
- },
95
- [selected, onSelect]
96
- );
97
-
98
- const getSelectedDisplay = () => {
99
- if (selected.length === 0) return placeholder;
100
- const displayedItems = selected
101
- .slice(0, 3)
102
- .map(
103
- (value) =>
104
- workingDataSource.find((item) => item.value === value)?.label || ""
105
- );
106
- let display = displayedItems.join(", ");
107
- if (selected.length > 3 && truncate) {
108
- display += `, +${selected.length - 3} more`;
109
- }
110
- return display;
111
- };
112
-
113
- const inputSearchHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
114
- const value = e.target.value.toLowerCase();
115
- setSearchTerm(value);
116
- if (!lazy) {
117
- setFilteredItems(
118
- workingDataSource.filter((item) =>
119
- Object.values(item).some((val) =>
120
- val.toString().toLowerCase().includes(value)
114
+
115
+ const inputSearchHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
116
+ const value = e.target.value.toLowerCase();
117
+ setSearchTerm(value);
118
+ if (!lazy) {
119
+ setFilteredItems(
120
+ workingDataSource.filter((item) =>
121
+ Object.values(item).some((val) =>
122
+ val.toString().toLowerCase().includes(value)
123
+ )
121
124
  )
122
- )
123
- );
124
- }
125
- };
126
-
127
- const clearSelected = () => {
128
- setSelected([]);
129
- setShowPopover(false);
130
- if (onSelect) onSelect([]);
131
- };
132
-
133
- useImperativeHandle(ref, () => ({
134
- workingDataSource,
135
- items,
136
- clearSelected,
137
- togglePopover,
138
- getSelectItems,
139
- selectedDisplay: getSelectedDisplay(),
140
- selected,
141
- }));
142
-
143
- return (
144
- <div className="relative w-[200px]" ref={selectRef}>
145
- <div className="relative border w-[200px] flex items-center px-4 py-2 rounded-lg">
146
- <button
147
- onClick={togglePopover}
148
- className="flex-grow text-sm text-left"
149
- type="button"
125
+ );
126
+ }
127
+ };
128
+
129
+ const clearSelected = () => {
130
+ setSelected([]);
131
+ setShowPopover(false);
132
+ if (onSelect) onSelect([]);
133
+ };
134
+
135
+ useImperativeHandle(ref, () => ({
136
+ workingDataSource,
137
+ items,
138
+ clearSelected,
139
+ togglePopover,
140
+ getSelectItems,
141
+ selectedDisplay: getSelectedDisplay(),
142
+ selected,
143
+ }));
144
+
145
+ return (
146
+ <div className="relative w-full" ref={selectRef}>
147
+ <div
148
+ className={`relative border w-full flex items-center px-4 py-2 rounded-lg ${
149
+ error && "border-red-500"
150
+ }`}
150
151
  >
151
- {getSelectedDisplay()}
152
- </button>
153
- <div className="flex items-center space-x-2">
154
- {selected.length > 0 && (
155
- <button
156
- className="flex items-center px-2"
157
- onClick={clearSelected}
158
- type="button"
159
- >
160
- <Icon
161
- elements={x}
162
- svgClass="h-4 w-4 stroke-gray-500 fill-none dark:stroke-white"
163
- />
164
- </button>
165
- )}
166
- <button onClick={togglePopover} type="button">
167
- <Icon
168
- elements={upDown}
169
- svgClass="h-4 w-4 stroke-gray-500 fill-none dark:stroke-white"
170
- />
152
+ <button
153
+ onClick={togglePopover}
154
+ className="flex-grow text-sm text-left font-medium"
155
+ type="button"
156
+ >
157
+ {getSelectedDisplay()}
171
158
  </button>
172
- </div>
173
- </div>
174
-
175
- {showPopover && (
176
- <div className="w-[200px] absolute overflow-y-auto border px-2 rounded-lg mt-[1px] scrollbar bg-white z-50 scrollbar h-auto dark:bg-black dark:text-white">
177
- {showSearch && (
178
- <div className="flex p-2 gap-1 items-center sticky top-0 bg-white border-b dark:bg-black">
179
- <Icon
180
- elements={search}
181
- svgClass="stroke-gray-500 fill-none dark:stroke-white"
182
- />
183
- <input
184
- autoComplete="off"
185
- type="text"
186
- name="search"
187
- id="search"
188
- placeholder="Search a value"
189
- className="w-full outline-none dark:bg-black"
190
- ref={inputRef}
191
- value={searchTerm}
192
- onChange={inputSearchHandler}
193
- />
194
- </div>
195
- )}
196
- {filteredItems.length > 0 ? (
197
- filteredItems.map(({ value, label }) => (
159
+ <div className="flex items-center space-x-2">
160
+ {selected.length > 0 && (
198
161
  <button
162
+ className="flex items-center px-2"
163
+ onClick={clearSelected}
199
164
  type="button"
200
- key={value}
201
- className="flex items-center w-full px-2 py-1 text-left hover:bg-blue-100 gap-2 rounded-lg mt-1 text-sm dark:hover:bg-blue-600"
202
- onClick={() => handleSelect(value)}
203
165
  >
204
166
  <Icon
205
- elements={check}
206
- svgClass={`h-4 w-4 fill-none ${
207
- selected.includes(value) ? "stroke-gray-500" : ""
208
- }`}
167
+ elements={x}
168
+ svgClass="h-4 w-4 stroke-gray-500 fill-none dark:stroke-white"
209
169
  />
210
- {label}
211
170
  </button>
212
- ))
213
- ) : (
214
- <div className="text-sm text-center">No Data Found</div>
215
- )}
171
+ )}
172
+ <button onClick={togglePopover} type="button">
173
+ <Icon
174
+ elements={upDown}
175
+ svgClass="h-4 w-4 stroke-gray-500 fill-none dark:stroke-white"
176
+ />
177
+ </button>
178
+ </div>
216
179
  </div>
217
- )}
218
- </div>
219
- );
220
- });
180
+ <p className={primary["error-primary"]}>{error && error}</p>
181
+
182
+ {/* Hidden input to integrate with the form */}
183
+ <input
184
+ type="hidden"
185
+ name={name}
186
+ value={JSON.stringify(selected) || ""}
187
+ readOnly
188
+ />
189
+
190
+ {showPopover && (
191
+ <div className="w-full absolute overflow-y-auto border px-2 rounded-lg mt-[1px] scrollbar bg-white z-50 scrollbar h-auto dark:bg-black dark:text-white">
192
+ {showSearch && (
193
+ <div className="flex p-2 gap-1 items-center sticky top-0 bg-white border-b dark:bg-black">
194
+ <Icon
195
+ elements={search}
196
+ svgClass="stroke-gray-500 fill-none dark:stroke-white"
197
+ />
198
+ <input
199
+ autoComplete="off"
200
+ type="text"
201
+ name="search"
202
+ id="search"
203
+ placeholder="Search a value"
204
+ className="w-full outline-none dark:bg-black"
205
+ ref={inputRef}
206
+ value={searchTerm}
207
+ onChange={inputSearchHandler}
208
+ />
209
+ </div>
210
+ )}
211
+ {filteredItems.length > 0 ? (
212
+ filteredItems.map(({ value, label }) => (
213
+ <button
214
+ type="button"
215
+ key={value}
216
+ className="flex items-center w-full px-2 py-1 text-left hover:bg-blue-100 gap-2 rounded-lg mt-1 text-sm dark:hover:bg-blue-600"
217
+ onClick={() => handleSelect(value)}
218
+ >
219
+ <Icon
220
+ elements={check}
221
+ svgClass={`h-4 w-4 fill-none ${
222
+ selected.includes(value) ? "stroke-gray-500" : ""
223
+ }`}
224
+ />
225
+ {label}
226
+ </button>
227
+ ))
228
+ ) : (
229
+ <div className="text-sm text-center">No Data Found</div>
230
+ )}
231
+ </div>
232
+ )}
233
+ </div>
234
+ );
235
+ }
236
+ );
221
237
 
222
238
  const MemoizedMultiSelect = memo(MultiSelect);
223
239
  export { MemoizedMultiSelect as MultiSelect };
@@ -1,14 +1,25 @@
1
- interface ItemsProps {
1
+ export type ItemsProps = {
2
2
  value: string;
3
3
  label: string;
4
- }
4
+ };
5
5
 
6
6
  export type MultiSelectProps = {
7
7
  placeholder?: string;
8
8
  items?: ItemsProps[] | string;
9
9
  lazy?: boolean;
10
10
  showSearch?: boolean;
11
- onSelect?: any;
11
+ onSelect?: (selectedItem: string[]) => void;
12
12
  truncate?: boolean;
13
13
  selectedItems?: string[];
14
+ name?: string;
15
+ error?: string;
16
+ };
17
+
18
+ export type MultiSelectHandle = {
19
+ workingDataSource: ItemsProps[];
20
+ clearSelected: () => void;
21
+ togglePopover: (e: React.MouseEvent) => void;
22
+ getSelectItems: (itemsApi: string) => Promise<void>;
23
+ selectedDisplay: string;
24
+ selected: string[] | undefined;
14
25
  };