gbs-add-block 0.0.1

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.
Files changed (37) hide show
  1. package/index.js +108 -0
  2. package/package.json +28 -0
  3. package/source/components/button/index.tsx +25 -0
  4. package/source/components/checkbox/index.tsx +9 -0
  5. package/source/components/darkmode/index.tsx +48 -0
  6. package/source/components/datepicker/DatePickerHelper.ts +42 -0
  7. package/source/components/datepicker/index.tsx +258 -0
  8. package/source/components/datepicker/types.ts +9 -0
  9. package/source/components/dialog/index.tsx +44 -0
  10. package/source/components/dialog/types.ts +12 -0
  11. package/source/components/grid/FilterPopup.tsx +85 -0
  12. package/source/components/grid/Grid.tsx +615 -0
  13. package/source/components/grid/GridHelperFunctions.ts +218 -0
  14. package/source/components/grid/index.tsx +26 -0
  15. package/source/components/grid/type.ts +28 -0
  16. package/source/components/input/index.tsx +105 -0
  17. package/source/components/input/types.ts +9 -0
  18. package/source/components/modal/index.tsx +58 -0
  19. package/source/components/modal/types.ts +11 -0
  20. package/source/components/multiselect/index.tsx +213 -0
  21. package/source/components/multiselect/types.ts +14 -0
  22. package/source/components/select/index.tsx +220 -0
  23. package/source/components/select/types.ts +13 -0
  24. package/source/components/spinner/Stroke.tsx +89 -0
  25. package/source/components/spinner/index.tsx +54 -0
  26. package/source/components/spinner/types.ts +9 -0
  27. package/source/components/toast/Toast.tsx +61 -0
  28. package/source/components/toast/index.tsx +36 -0
  29. package/source/components/toast/toastAtom.ts +23 -0
  30. package/source/components/toast/types.ts +24 -0
  31. package/source/components/toast/useToast.ts +24 -0
  32. package/source/fallback/index.tsx +11 -0
  33. package/source/icon/Icon.tsx +55 -0
  34. package/source/icon/iconPaths.ts +92 -0
  35. package/source/index.ts +15 -0
  36. package/source/types.ts +108 -0
  37. package/source/utils.ts +10 -0
@@ -0,0 +1,218 @@
1
+ import * as XLSX from "xlsx";
2
+ import jsPDF from "jspdf";
3
+ import "jspdf-autotable";
4
+
5
+ let activeFilterArray: any[] = [];
6
+
7
+ interface PDFExportOptions {
8
+ layout: "portrait" | "landscape";
9
+ paperSize:
10
+ | "a3"
11
+ | "a4"
12
+ | "letter"
13
+ | "legal"
14
+ | "tabloid"
15
+ | "statement"
16
+ | "executive";
17
+ }
18
+
19
+ export function handleApplyFilterHelper(
20
+ event: any,
21
+ columns: any,
22
+ dataSource: any
23
+ ) {
24
+ let workingDataSource: any[] = [...dataSource];
25
+ let filterValue = event.filterValue.toLowerCase();
26
+ let filterCondition = event.filterType;
27
+ let filterColumn = event.columnHeader;
28
+
29
+ // Add the new filter to activeFilterArray
30
+ activeFilterArray.push({
31
+ filterValue: filterValue,
32
+ filterCondition: filterCondition,
33
+ filterColumn: filterColumn,
34
+ });
35
+
36
+ columns = columns.map((column: any) => {
37
+ if (column.field === filterColumn) {
38
+ return { ...column, isFilterActive: true, showFilterPopup: false };
39
+ }
40
+ return column;
41
+ });
42
+
43
+ // Apply all active filters
44
+ activeFilterArray.forEach((filter: any) => {
45
+ workingDataSource = dataSource.filter((item: any) => {
46
+ let columnValue = item[filter.filterColumn].toString().toLowerCase();
47
+ switch (filter.filterCondition) {
48
+ case "contains":
49
+ return columnValue.includes(filter.filterValue);
50
+ case "equals":
51
+ return columnValue === filter.filterValue;
52
+ case "starts_with":
53
+ return columnValue.startsWith(filter.filterValue);
54
+ case "ends_with":
55
+ return columnValue.endsWith(filter.filterValue);
56
+ default:
57
+ return true;
58
+ }
59
+ });
60
+ });
61
+
62
+ return { columns, workingDataSource, activeFilterArray };
63
+ }
64
+
65
+ // Function For Clearing the Filter
66
+ export function clearFilterHelper(
67
+ event: any,
68
+ columns: any[],
69
+ dataSource: any[]
70
+ ) {
71
+ let workingDataSource = [...dataSource];
72
+ let filterColumn = event.columnHeader;
73
+
74
+ // Remove the cleared filter from activeFilterArray
75
+ activeFilterArray = activeFilterArray.filter(
76
+ (filter: any) => filter.filterColumn !== filterColumn
77
+ );
78
+
79
+ let hasActiveFilters = activeFilterArray.length > 0;
80
+
81
+ // Reset the isFilterActive and showFilterPopup flags for the cleared filter
82
+ columns = columns.map((column: any) => {
83
+ if (column.field === filterColumn) {
84
+ return { ...column, isFilterActive: false, showFilterPopup: false };
85
+ }
86
+ return column;
87
+ });
88
+
89
+ // Reapply all remaining active filters
90
+ if (hasActiveFilters) {
91
+ activeFilterArray.forEach((filter: any) => {
92
+ workingDataSource = workingDataSource.filter((item: any) => {
93
+ let columnValue = item[filter.filterColumn].toString().toLowerCase();
94
+ switch (filter.filterCondition) {
95
+ case "contains":
96
+ return columnValue.includes(filter.filterValue);
97
+ case "equals":
98
+ return columnValue === filter.filterValue;
99
+ case "starts_with":
100
+ return columnValue.startsWith(filter.filterValue);
101
+ case "ends_with":
102
+ return columnValue.endsWith(filter.filterValue);
103
+ default:
104
+ return true;
105
+ }
106
+ });
107
+ });
108
+ }
109
+
110
+ return { columns, workingDataSource, hasActiveFilters, activeFilterArray };
111
+ }
112
+
113
+ export function exportToExcelHelper(
114
+ dataSource: any[],
115
+ columns: any[],
116
+ excelName: string
117
+ ) {
118
+ // This will remove the column template.
119
+ const templateRemovedColumn = columns.filter((column) => !column.template);
120
+ const dataToExport = dataSource.map((row) => {
121
+ const rowData: any = {};
122
+ templateRemovedColumn.forEach((column: any) => {
123
+ rowData[column.field] = row[column.field];
124
+ });
125
+ return rowData;
126
+ });
127
+ const ws = XLSX.utils.json_to_sheet(dataToExport);
128
+ const wb = XLSX.utils.book_new();
129
+ XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
130
+ XLSX.writeFile(wb, `${excelName}.xlsx`);
131
+ }
132
+
133
+ export function exportToPDFHelper(
134
+ dataSource: any[],
135
+ columns: any[],
136
+ pdfName: string,
137
+ pdfoptions?: PDFExportOptions
138
+ ) {
139
+ const { layout = "portrait", paperSize = "a4" }: any = pdfoptions;
140
+
141
+ const doc: any = new jsPDF({
142
+ orientation: layout,
143
+ unit: "mm",
144
+ format: paperSize,
145
+ });
146
+ // This will remove the columns with template.
147
+ const printableColumns = columns.filter(
148
+ (column) => !column.template || column.showInPdf
149
+ );
150
+
151
+ const dataToExport = dataSource.map((row) => {
152
+ const rowData: any = {};
153
+ printableColumns.forEach((column: any) => {
154
+ rowData[column.field] = row[column.field];
155
+ });
156
+ return Object.values(rowData);
157
+ });
158
+
159
+ const columnNames = printableColumns.map((column: any) => column.field);
160
+ doc.autoTable({
161
+ head: [columnNames],
162
+ body: dataToExport,
163
+ });
164
+
165
+ doc.save(`${pdfName}.pdf`);
166
+ }
167
+
168
+ export function handleEditActionHelper(
169
+ e: any,
170
+ isEditModeActive: boolean,
171
+ actionMode: string,
172
+ newEntry: any,
173
+ workingDataSource: any[],
174
+ goToFirstPage: () => void
175
+ ) {
176
+ const { mode } = e.detail;
177
+ let dataSourceUpdate = [...workingDataSource];
178
+ let isEditModeActiveUpdate: boolean = isEditModeActive;
179
+ let actionModeUpdate: string = actionMode;
180
+ let newEntryUpdate: any = { ...newEntry };
181
+
182
+ function resetEditMode() {
183
+ isEditModeActiveUpdate = false;
184
+ actionModeUpdate = "";
185
+ newEntryUpdate = {};
186
+ }
187
+
188
+ function addNewEntry() {
189
+ if (Object.keys(newEntryUpdate).length > 0) {
190
+ dataSourceUpdate = [newEntryUpdate, ...workingDataSource];
191
+ newEntryUpdate = {};
192
+ }
193
+ }
194
+
195
+ switch (mode) {
196
+ case "add":
197
+ isEditModeActiveUpdate = true;
198
+ actionModeUpdate = mode;
199
+ goToFirstPage();
200
+ break;
201
+ case "cancel":
202
+ resetEditMode();
203
+ break;
204
+ case "update":
205
+ addNewEntry();
206
+ resetEditMode();
207
+ break;
208
+ default:
209
+ break;
210
+ }
211
+
212
+ return {
213
+ dataSourceUpdate,
214
+ isEditModeActiveUpdate,
215
+ actionModeUpdate,
216
+ newEntryUpdate,
217
+ };
218
+ }
@@ -0,0 +1,26 @@
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
+ * Here We Will Re-Render Grid as a Memoized Component For Better Performance.
7
+ * Extended Documentation for Grid can be found at
8
+ * https://psychedelic-step-e70.notion.site/Data-GRID-by-GBS-R-D-20ff97c899d24bc590215a6196435fa3
9
+ */
10
+
11
+ import { Grid as GridComponent } from "./Grid";
12
+ import React, { memo } from "react";
13
+
14
+ const Grid = memo(GridComponent, (prevProps, nextProps) => {
15
+ // Custom comparison function
16
+ return (
17
+ prevProps.dataSource === nextProps.dataSource &&
18
+ prevProps.columns === nextProps.columns &&
19
+ prevProps.pageSettings.pageNumber === nextProps.pageSettings.pageNumber &&
20
+ prevProps.enableSearch === nextProps.enableSearch &&
21
+ prevProps.enablePdfExport === nextProps.enablePdfExport &&
22
+ prevProps.enableExcelExport === nextProps.enableExcelExport
23
+ );
24
+ });
25
+
26
+ export { Grid };
@@ -0,0 +1,28 @@
1
+ interface PageSettingsProps {
2
+ pageNumber: number;
3
+ }
4
+
5
+ export type GridProps = {
6
+ dataSource: any[] | string;
7
+ columns?: any[];
8
+ pageSettings: PageSettingsProps;
9
+ enableSearch?: boolean;
10
+ lazy?: boolean;
11
+ enableExcelExport?: boolean;
12
+ excelName?: string;
13
+ enablePdfExport?: boolean;
14
+ pdfName?: string;
15
+ gridContainerClass?: string;
16
+ gridButtonClass?: string;
17
+ gridHeaderClass?: string;
18
+ gridGlobalSearchButtonClass?: string;
19
+ gridPaginationButtonClass?: string;
20
+ pdfOptions?: any;
21
+ isFetching?: boolean;
22
+ showPagination?: boolean;
23
+ selectAll?: boolean;
24
+ onSelectRow?: (value: any) => void;
25
+ tableHeaderStyle?: string;
26
+ gridColumnStyleSelectAll?: string;
27
+ gridColumnStyle?: string;
28
+ };
@@ -0,0 +1,105 @@
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
+
8
+ import React, { useState, useRef } from "react";
9
+ import { twMerge } from "tailwind-merge";
10
+ import Icon from "../icon/Icon";
11
+ import { eye } from "../icon/iconPaths";
12
+ import type { InputProps } from "./types";
13
+
14
+ export const Input = ({
15
+ OTPField = false,
16
+ OTPValue = "",
17
+ OTPLength = 4,
18
+ OTPClass = "w-8 h-10 m-1 border border-gray-600 rounded-lg text-center",
19
+ onOTPValueChange,
20
+ ...props
21
+ }: InputProps) => {
22
+ const [otpValues, setOtpValues] = useState(new Array(OTPLength).fill(""));
23
+ const [showPassword, setShowPassword] = useState(false);
24
+ const inputRefs = useRef<any>([]);
25
+
26
+ const defaultClass = "bg-gray-100 p-2 rounded-lg";
27
+
28
+ // This will update the OTP Field Value
29
+ const updateOtpValue = (index: number, e: any) => {
30
+ e.preventDefault();
31
+ const input = e.target;
32
+ if (input) {
33
+ const newOtpValues = [...otpValues];
34
+ newOtpValues[index] = input.value;
35
+ setOtpValues(newOtpValues);
36
+ if (index < OTPLength - 1 && input.value) {
37
+ inputRefs.current[index + 1].focus();
38
+ }
39
+ OTPValue = newOtpValues.join("");
40
+ if (onOTPValueChange) onOTPValueChange(OTPValue);
41
+ }
42
+ };
43
+
44
+ // Function for handling keyboard navigation in OTP Field
45
+ const handleKeydown = (index: number, e: any) => {
46
+ if (e.key === "Backspace" && otpValues[index] === "" && index > 0) {
47
+ inputRefs.current[index - 1].focus();
48
+ } else if (e.key === "ArrowLeft" && index > 0) {
49
+ inputRefs.current[index - 1].focus();
50
+ } else if (e.key === "ArrowRight" && index < OTPLength - 1) {
51
+ inputRefs.current[index + 1].focus();
52
+ }
53
+ };
54
+
55
+ // Password Feild Toggler
56
+ const toggleTypeForPassword = () => {
57
+ setShowPassword(!showPassword);
58
+ };
59
+
60
+ return (
61
+ <div>
62
+ {!OTPField ? (
63
+ <div className="text-input-container w-60 relative">
64
+ <input
65
+ className={twMerge(defaultClass, "w-full focus:outline-blue-400")}
66
+ {...props}
67
+ type={
68
+ props.type === "password" && showPassword ? "text" : props.type
69
+ }
70
+ />
71
+ {/* If Type is password, this will show a password visible toggle button */}
72
+ {props.type && props.type === "password" && (
73
+ <button
74
+ className="absolute inset-y-0 right-0 flex items-center pr-2"
75
+ onClick={toggleTypeForPassword}
76
+ >
77
+ <Icon
78
+ elements={eye}
79
+ svgClass={"h-5 w-5 stroke-gray-500 fill-none dark:stroke-white"}
80
+ />
81
+ </button>
82
+ )}
83
+ </div>
84
+ ) : (
85
+ <div className="otp-container">
86
+ {Array(OTPLength)
87
+ .fill("")
88
+ .map((_, index) => (
89
+ <input
90
+ key={index}
91
+ type="text"
92
+ maxLength={1}
93
+ pattern="[0-9]*"
94
+ value={otpValues[index]}
95
+ onChange={(e) => updateOtpValue(index, e)}
96
+ onKeyDown={(e) => handleKeydown(index, e)}
97
+ ref={(ref) => (inputRefs.current[index] = ref)}
98
+ className={OTPClass}
99
+ />
100
+ ))}
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ };
@@ -0,0 +1,9 @@
1
+ import { InputHTMLAttributes } from "react";
2
+
3
+ export type InputProps = {
4
+ OTPField?: boolean;
5
+ OTPValue?: string;
6
+ OTPLength?: number;
7
+ OTPClass?: string;
8
+ onOTPValueChange?: (value: string) => void;
9
+ } & InputHTMLAttributes<HTMLInputElement>;
@@ -0,0 +1,58 @@
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
+
8
+ import React, { useState } from "react";
9
+ import { twMerge } from "tailwind-merge";
10
+ import type { ModalProps } from "./types";
11
+
12
+ export const Modal = ({
13
+ showModal = false,
14
+ modalTitle = "Modal Title",
15
+ autoclose = false,
16
+ modalClass = "fixed z-10 overflow-y-auto inset-0 flex items-center justify-center bg-gray-500 bg-opacity-75 transition-opacity",
17
+ modalContentClass = "bg-white m-10 md:w-[80vh] rounded-xl",
18
+ classModalContent = "",
19
+ modalTitleClass = "p-4 text-lg leading-6 font-medium text-gray-900 flex justify-between",
20
+ classModalTitle = "",
21
+ children,
22
+ }: ModalProps) => {
23
+ const [show, setShow] = useState(showModal);
24
+
25
+ const autoCloseHandler = () => {
26
+ if (autoclose) {
27
+ setShow(false);
28
+ }
29
+ };
30
+
31
+ return (
32
+ <>
33
+ {show && (
34
+ <div
35
+ className={modalClass}
36
+ aria-labelledby="modal-title"
37
+ role="dialog"
38
+ aria-modal="true"
39
+ onClick={autoCloseHandler}
40
+ >
41
+ <div
42
+ className={twMerge(modalContentClass, classModalContent)}
43
+ onClick={(e: any) => {
44
+ e.stopPropagation();
45
+ }}
46
+ role="dialog"
47
+ >
48
+ <div className={twMerge(modalTitleClass, classModalTitle)}>
49
+ {modalTitle}
50
+ </div>
51
+ <hr />
52
+ <div className="p-4">{children}</div>
53
+ </div>
54
+ </div>
55
+ )}
56
+ </>
57
+ );
58
+ };
@@ -0,0 +1,11 @@
1
+ export type ModalProps = {
2
+ showModal?: boolean;
3
+ modalTitle?: string;
4
+ autoclose?: boolean;
5
+ modalClass?: string;
6
+ modalContentClass?: string;
7
+ classModalContent?: string;
8
+ modalTitleClass?: string;
9
+ classModalTitle?: string;
10
+ children?: React.ReactNode;
11
+ };
@@ -0,0 +1,213 @@
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
+
8
+ import React, {
9
+ forwardRef,
10
+ memo,
11
+ useEffect,
12
+ useImperativeHandle,
13
+ useRef,
14
+ useState,
15
+ } from "react";
16
+ import Icon from "../icon/Icon";
17
+ import { check, search, upDown, x } from "../icon/iconPaths";
18
+ import { getSourceData } from "../utils";
19
+ import type { MultiSelectProps } from "./types";
20
+
21
+ interface ItemsProps {
22
+ value: string;
23
+ label: string;
24
+ }
25
+
26
+ const MultiSelect = forwardRef<any, MultiSelectProps>((props, ref) => {
27
+ const {
28
+ placeholder = "Select an Item...",
29
+ items,
30
+ lazy = false,
31
+ showSearch = true,
32
+ onSelect,
33
+ truncate = true,
34
+ selectedItems = [],
35
+ } = props;
36
+
37
+ const [showPopover, setShowPopover] = useState(false);
38
+ const [workingDataSource, setWorkingDataSource] = useState<ItemsProps[]>([]);
39
+ const [filteredItems, setFilteredItems] = useState<ItemsProps[]>([]);
40
+ const [selected, setSelected] = useState<string[]>(selectedItems);
41
+ const [searchTerm, setSearchTerm] = useState("");
42
+ const inputRef = useRef<HTMLInputElement>(null);
43
+ const selectRef = useRef<HTMLDivElement>(null);
44
+
45
+ useEffect(() => {
46
+ inputRef.current?.focus();
47
+ }, [showPopover]);
48
+
49
+ useEffect(() => {
50
+ handleDataSource();
51
+ }, [items]);
52
+
53
+ useEffect(() => {
54
+ if (onSelect) onSelect(selected);
55
+ }, [selected, onSelect]);
56
+
57
+ useEffect(() => {
58
+ const handleClickOutside = (event: MouseEvent) => {
59
+ if (
60
+ selectRef.current &&
61
+ !selectRef.current.contains(event.target as Node)
62
+ ) {
63
+ setShowPopover(false);
64
+ }
65
+ };
66
+ document.addEventListener("mousedown", handleClickOutside);
67
+ return () => document.removeEventListener("mousedown", handleClickOutside);
68
+ }, []);
69
+
70
+ const getSelectItems = async (itemsApi: string) => {
71
+ const itemsData = await getSourceData(itemsApi);
72
+ setWorkingDataSource(itemsData.sourcedata);
73
+ setFilteredItems(itemsData.sourcedata);
74
+ };
75
+
76
+ const handleDataSource = async () => {
77
+ if (Array.isArray(items)) {
78
+ setWorkingDataSource(items);
79
+ setFilteredItems(items);
80
+ } else if (typeof items === "string") {
81
+ await getSelectItems(items);
82
+ }
83
+ };
84
+
85
+ const togglePopover = (e: React.MouseEvent) => {
86
+ e.stopPropagation();
87
+ setShowPopover(!showPopover);
88
+ };
89
+
90
+ const handleSelect = (value: string) => {
91
+ setSelected((prev) =>
92
+ prev.includes(value)
93
+ ? prev.filter((item) => item !== value)
94
+ : [...prev, value]
95
+ );
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)
121
+ )
122
+ )
123
+ );
124
+ }
125
+ };
126
+
127
+ const clearSelected = () => {
128
+ setSelected([]);
129
+ setShowPopover(false);
130
+ };
131
+
132
+ useImperativeHandle(ref, () => ({
133
+ workingDataSource,
134
+ items,
135
+ clearSelected,
136
+ togglePopover,
137
+ getSelectItems,
138
+ selectedDisplay: getSelectedDisplay(),
139
+ selected,
140
+ }));
141
+
142
+ return (
143
+ <div className="relative w-[200px]" ref={selectRef}>
144
+ <div className="relative border w-[200px] flex items-center px-4 py-2 rounded-lg">
145
+ <button onClick={togglePopover} className="flex-grow text-sm text-left">
146
+ {getSelectedDisplay()}
147
+ </button>
148
+ <div className="flex items-center space-x-2">
149
+ {selected.length > 0 && (
150
+ <button className="flex items-center px-2" onClick={clearSelected}>
151
+ <Icon
152
+ elements={x}
153
+ svgClass="h-4 w-4 stroke-gray-500 fill-none dark:stroke-white"
154
+ />
155
+ </button>
156
+ )}
157
+ <button onClick={togglePopover}>
158
+ <Icon
159
+ elements={upDown}
160
+ svgClass="h-4 w-4 stroke-gray-500 fill-none dark:stroke-white"
161
+ />
162
+ </button>
163
+ </div>
164
+ </div>
165
+
166
+ {showPopover && (
167
+ <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">
168
+ {showSearch && (
169
+ <div className="flex p-2 gap-1 items-center sticky top-0 bg-white border-b dark:bg-black">
170
+ <Icon
171
+ elements={search}
172
+ svgClass="stroke-gray-500 fill-none dark:stroke-white"
173
+ />
174
+ <input
175
+ autoComplete="off"
176
+ type="text"
177
+ name="search"
178
+ id="search"
179
+ placeholder="Search a value"
180
+ className="w-full outline-none dark:bg-black"
181
+ ref={inputRef}
182
+ value={searchTerm}
183
+ onChange={inputSearchHandler}
184
+ />
185
+ </div>
186
+ )}
187
+ {filteredItems.length > 0 ? (
188
+ filteredItems.map(({ value, label }) => (
189
+ <button
190
+ key={value}
191
+ 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"
192
+ onClick={() => handleSelect(value)}
193
+ >
194
+ <Icon
195
+ elements={check}
196
+ svgClass={`h-4 w-4 fill-none ${
197
+ selected.includes(value) ? "stroke-gray-500" : ""
198
+ }`}
199
+ />
200
+ {label}
201
+ </button>
202
+ ))
203
+ ) : (
204
+ <div className="text-sm text-center">No Data Found</div>
205
+ )}
206
+ </div>
207
+ )}
208
+ </div>
209
+ );
210
+ });
211
+
212
+ const MemoizedMultiSelect = memo(MultiSelect);
213
+ export { MemoizedMultiSelect as MultiSelect };
@@ -0,0 +1,14 @@
1
+ interface ItemsProps {
2
+ value: string;
3
+ label: string;
4
+ }
5
+
6
+ export type MultiSelectProps = {
7
+ placeholder?: string;
8
+ items?: ItemsProps[] | string;
9
+ lazy?: boolean;
10
+ showSearch?: boolean;
11
+ onSelect?: any;
12
+ truncate?: boolean;
13
+ selectedItems?: string[];
14
+ };