gbs-add-block 0.0.39 → 0.0.41

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.
@@ -5,20 +5,20 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import React, {
9
- forwardRef,
10
- memo,
8
+ import {
11
9
  useCallback,
10
+ forwardRef,
11
+ useRef,
12
12
  useEffect,
13
+ memo,
13
14
  useImperativeHandle,
14
- useRef,
15
- useState,
16
15
  } from "react";
16
+ import { useClickOutside } from "../hooks/SelectionHooks/useClickOutside";
17
+ import type { MultiSelectHandle, MultiSelectProps } from "./types";
18
+ import { useMultiSelectState } from "../hooks/SelectionHooks/useMultiSelectState";
17
19
  import Icon from "../icon/Icon";
18
20
  import { check, search, upDown, x } from "../icon/iconPaths";
19
- import { getSourceData } from "../utils";
20
- import type { ItemsProps, MultiSelectHandle, MultiSelectProps } from "./types";
21
- import { primary } from "../globalStyle";
21
+ import { iconClass, popUp, primary } from "../globalStyle";
22
22
 
23
23
  const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
24
24
  (props, ref) => {
@@ -34,70 +34,32 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
34
34
  error,
35
35
  } = props;
36
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
37
  const inputRef = useRef<HTMLInputElement>(null);
45
38
  const selectRef = useRef<HTMLDivElement>(null);
46
39
 
47
- useEffect(() => {
48
- inputRef.current?.focus();
49
- }, [showPopover]);
40
+ const {
41
+ showPopover,
42
+ setShowPopover,
43
+ workingDataSource,
44
+ filteredItems,
45
+ selected,
46
+ searchTerm,
47
+ setSearchTerm,
48
+ handleSelect,
49
+ togglePopover,
50
+ clearSelected,
51
+ getSelectItems,
52
+ } = useMultiSelectState(items, selectedItems, lazy, onSelect);
50
53
 
51
- useEffect(() => {
52
- handleDataSource();
53
- }, [items]);
54
+ useClickOutside(selectRef, () => setShowPopover(false));
54
55
 
55
56
  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);
57
+ if (showPopover) {
58
+ inputRef.current?.focus();
81
59
  }
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
- );
60
+ }, [showPopover]);
99
61
 
100
- const getSelectedDisplay = () => {
62
+ const getSelectedDisplay = useCallback(() => {
101
63
  if (selected.length === 0) return placeholder;
102
64
  const displayedItems = selected
103
65
  .slice(0, 3)
@@ -110,37 +72,29 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
110
72
  display += `, +${selected.length - 3} more`;
111
73
  }
112
74
  return display;
113
- };
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
- )
124
- )
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
- }));
75
+ }, [selected, workingDataSource, placeholder, truncate]);
76
+
77
+ useImperativeHandle(
78
+ ref,
79
+ () => ({
80
+ workingDataSource,
81
+ items,
82
+ clearSelected,
83
+ togglePopover,
84
+ getSelectItems,
85
+ selectedDisplay: getSelectedDisplay(),
86
+ selected,
87
+ }),
88
+ [
89
+ workingDataSource,
90
+ items,
91
+ clearSelected,
92
+ togglePopover,
93
+ getSelectItems,
94
+ getSelectedDisplay,
95
+ selected,
96
+ ]
97
+ );
144
98
 
145
99
  return (
146
100
  <div className="relative w-full" ref={selectRef}>
@@ -163,23 +117,16 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
163
117
  onClick={clearSelected}
164
118
  type="button"
165
119
  >
166
- <Icon
167
- elements={x}
168
- svgClass="h-4 w-4 stroke-gray-500 fill-none dark:stroke-white"
169
- />
120
+ <Icon elements={x} svgClass={iconClass["grey-common"]} />
170
121
  </button>
171
122
  )}
172
123
  <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
- />
124
+ <Icon elements={upDown} svgClass={iconClass["grey-common"]} />
177
125
  </button>
178
126
  </div>
179
127
  </div>
180
- <p className={primary["error-primary"]}>{error && error}</p>
128
+ {error && <p className={primary["error-primary"]}>{error}</p>}
181
129
 
182
- {/* Hidden input to integrate with the form */}
183
130
  <input
184
131
  type="hidden"
185
132
  name={name}
@@ -188,13 +135,10 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
188
135
  />
189
136
 
190
137
  {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">
138
+ <div className={popUp["pop-up-style"]}>
192
139
  {showSearch && (
193
140
  <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
- />
141
+ <Icon elements={search} svgClass={iconClass["grey-common"]} />
198
142
  <input
199
143
  autoComplete="off"
200
144
  type="text"
@@ -204,7 +148,7 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
204
148
  className="w-full outline-none dark:bg-black"
205
149
  ref={inputRef}
206
150
  value={searchTerm}
207
- onChange={inputSearchHandler}
151
+ onChange={(e) => setSearchTerm(e.target.value)}
208
152
  />
209
153
  </div>
210
154
  )}
@@ -0,0 +1,176 @@
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
+ useCallback,
12
+ useEffect,
13
+ useImperativeHandle,
14
+ useRef,
15
+ } from "react";
16
+ import Icon from "../icon/Icon";
17
+ import { check, search, upDown, x } from "../icon/iconPaths";
18
+ import type { SelectHandle, SelectProps } from "./types";
19
+ import { selectStyle } from "./style";
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";
24
+
25
+ const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
26
+ const {
27
+ id = "",
28
+ name = "",
29
+ placeholder = "Select an Item...",
30
+ items,
31
+ lazy = false,
32
+ showSearch = true,
33
+ onSelect,
34
+ selectedItem: initialSelectedItem,
35
+ error = undefined,
36
+ onFiltering,
37
+ } = props;
38
+
39
+ const {
40
+ showPopover,
41
+ setShowPopover,
42
+ workingDataSource,
43
+ setWorkingDataSource,
44
+ searchTerm,
45
+ setSearchTerm,
46
+ selectedItem,
47
+ setSelectedItem,
48
+ togglePopover,
49
+ clearSelected,
50
+ } = useSelectState(initialSelectedItem);
51
+
52
+ const { getSelectItems, selectedDisplay, filteredItems } = useSelectData(
53
+ items,
54
+ setWorkingDataSource,
55
+ workingDataSource,
56
+ searchTerm,
57
+ lazy,
58
+ selectedItem
59
+ );
60
+
61
+ const inputRef = useRef<HTMLInputElement>(null);
62
+ const selectRef = useRef<HTMLDivElement>(null);
63
+
64
+ useClickOutside(selectRef, () => setShowPopover(false));
65
+
66
+ useEffect(() => {
67
+ if (showPopover) {
68
+ inputRef.current?.focus();
69
+ }
70
+ }, [showPopover]);
71
+
72
+ useEffect(() => {
73
+ setSelectedItem(initialSelectedItem);
74
+ }, [initialSelectedItem, setSelectedItem]);
75
+
76
+ const handleSelect = useCallback(
77
+ (value: string) => {
78
+ setSelectedItem(value);
79
+ setShowPopover(false);
80
+ setSearchTerm("");
81
+ if (onSelect) onSelect(value);
82
+ },
83
+ [onSelect, setSelectedItem, setShowPopover, setSearchTerm]
84
+ );
85
+
86
+ const handleClear = () => {
87
+ clearSelected();
88
+ if (onSelect) onSelect(undefined);
89
+ };
90
+
91
+ useImperativeHandle(ref, () => ({
92
+ workingDataSource,
93
+ items,
94
+ clearSelected,
95
+ togglePopover,
96
+ getSelectItems,
97
+ selectedDisplay,
98
+ selected: selectedItem,
99
+ }));
100
+
101
+ return (
102
+ <div className="relative w-full" ref={selectRef} id={id}>
103
+ <div className="w-full relative">
104
+ <button
105
+ className={`${error ? primary["error-border"] : "border"} ${
106
+ selectStyle["select-button"]
107
+ }`}
108
+ onClick={togglePopover}
109
+ type="button"
110
+ >
111
+ {selectedDisplay || placeholder}
112
+ <Icon elements={upDown} svgClass={iconClass["grey-common"]} />
113
+ </button>
114
+ {selectedDisplay && (
115
+ <button
116
+ className={selectStyle["selectedDisplay-Button"]}
117
+ onClick={handleClear}
118
+ >
119
+ <Icon elements={x} svgClass={iconClass["grey-common"]} />
120
+ </button>
121
+ )}
122
+ {error && <p className={primary["error-primary"]}>{error}</p>}
123
+ </div>
124
+
125
+ <input type="hidden" name={name} value={selectedItem || ""} readOnly />
126
+
127
+ {showPopover && (
128
+ <div className={popUp["pop-up-style"]}>
129
+ {showSearch && (
130
+ <div className={selectStyle["input-parent"]}>
131
+ <Icon elements={search} svgClass={iconClass["grey-common"]} />
132
+ <input
133
+ autoComplete="off"
134
+ type="text"
135
+ name="search"
136
+ id="search"
137
+ placeholder="Search a value"
138
+ className="w-full outline-none dark:bg-black"
139
+ ref={inputRef}
140
+ value={searchTerm}
141
+ onChange={(e) => {
142
+ setSearchTerm(e.target.value);
143
+ if (onFiltering) onFiltering(e.target.value);
144
+ }}
145
+ />
146
+ </div>
147
+ )}
148
+ {filteredItems.length > 0 ? (
149
+ filteredItems.map(({ value, label }) => (
150
+ <button
151
+ key={value}
152
+ className={selectStyle["filter-button"]}
153
+ onClick={() => handleSelect(value)}
154
+ >
155
+ <Icon
156
+ elements={check}
157
+ svgClass={`h-4 w-4 fill-none ${
158
+ selectedItem === value ? "stroke-gray-500" : ""
159
+ }`}
160
+ />
161
+ {label}
162
+ </button>
163
+ ))
164
+ ) : (
165
+ <div className="text-sm text-center">No Data Found</div>
166
+ )}
167
+ </div>
168
+ )}
169
+ </div>
170
+ );
171
+ });
172
+
173
+ Select.displayName = "Select";
174
+
175
+ const MemoizedSelect = memo(Select);
176
+ export { MemoizedSelect as Select };
@@ -11,7 +11,7 @@ export type SelectProps = {
11
11
  items?: ItemsProps[] | string;
12
12
  lazy?: boolean;
13
13
  showSearch?: boolean;
14
- onSelect?: (selectedItem: string) => void;
14
+ onSelect?: (selectedItem: string | undefined) => void;
15
15
  selectedItem?: string;
16
16
  onFiltering?: (searchTerm: string) => void;
17
17
  };
@@ -51,4 +51,4 @@ export const Spinner: React.FC<SpinnerProps> = ({
51
51
  } else {
52
52
  return null;
53
53
  }
54
- };
54
+ };
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { Toast as ToastType } from "./types";
3
- import { useToastStore } from "./toastAtom";
3
+ import { useToastContext } from "./ToastContext";
4
4
  import Icon from "../icon/Icon";
5
5
  import { circleCheck, info, x, xCircle } from "../icon/iconPaths";
6
6
 
@@ -9,7 +9,7 @@ interface ToastProps {
9
9
  }
10
10
 
11
11
  const Toast: React.FC<ToastProps> = ({ toast }) => {
12
- const { dismissToast } = useToastStore();
12
+ const { dismissToast } = useToastContext();
13
13
  const icons = {
14
14
  success: (
15
15
  <Icon
@@ -33,7 +33,7 @@ const Toast: React.FC<ToastProps> = ({ toast }) => {
33
33
 
34
34
  return (
35
35
  <div
36
- className={`bg-white px-2 py-3 rounded-lg text-sm flex justify-between w-80 m-2 border shadow-lg ${
36
+ className={`bg-white px-2 py-3 rounded-lg text-sm flex justify-between w-full m-2 border shadow-lg ${
37
37
  toast.type === "success"
38
38
  ? "text-green-500"
39
39
  : toast.type === "error"
@@ -0,0 +1,43 @@
1
+ import React, { createContext, useContext, useState } from "react";
2
+ import { Toast } from "./types";
3
+
4
+ interface ToastContextProps {
5
+ toasts: Toast[];
6
+ addToast: (toast: Omit<Toast, "id">) => void;
7
+ dismissToast: (id: number) => void;
8
+ }
9
+
10
+ const ToastContext = createContext<ToastContextProps | undefined>(undefined);
11
+
12
+ export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({
13
+ children,
14
+ }) => {
15
+ const [toasts, setToasts] = useState<Toast[]>([]);
16
+
17
+ const addToast = (toast: Omit<Toast, "id">) => {
18
+ const id = Math.floor(Math.random() * 10000);
19
+ const newToast = { ...toast, id };
20
+ setToasts((prev) => [newToast, ...prev]);
21
+ if (toast.timeout) {
22
+ setTimeout(() => dismissToast(id), toast.timeout);
23
+ }
24
+ };
25
+
26
+ const dismissToast = (id: number) => {
27
+ setToasts((prev) => prev.filter((t) => t.id !== id));
28
+ };
29
+
30
+ return (
31
+ <ToastContext.Provider value={{ toasts, addToast, dismissToast }}>
32
+ {children}
33
+ </ToastContext.Provider>
34
+ );
35
+ };
36
+
37
+ export const useToastContext = (): ToastContextProps => {
38
+ const context = useContext(ToastContext);
39
+ if (!context) {
40
+ throw new Error("useToastContext must be used within a ToastProvider");
41
+ }
42
+ return context;
43
+ };
@@ -5,9 +5,8 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- "use client";
9
8
  import React from "react";
10
- import { useToastStore } from "./toastAtom";
9
+ import { useToastContext } from "./ToastContext";
11
10
  import Toast from "./Toast";
12
11
  import { ToastPosition } from "./types";
13
12
 
@@ -23,7 +22,7 @@ const positionClasses: Record<ToastPosition, string> = {
23
22
  };
24
23
 
25
24
  export const Toasts: React.FC<ToastsProps> = ({ position = "top-right" }) => {
26
- const { toasts } = useToastStore();
25
+ const { toasts } = useToastContext();
27
26
 
28
27
  return (
29
28
  <section
@@ -1,8 +1,8 @@
1
1
  import { ToastOptions } from "./types";
2
- import { useToastStore } from "./toastAtom";
2
+ import { useToastContext } from "./ToastContext";
3
3
 
4
4
  const useToast = () => {
5
- const { addToast, dismissToast } = useToastStore();
5
+ const { addToast, dismissToast } = useToastContext();
6
6
 
7
7
  return {
8
8
  addToast: ({
@@ -7,3 +7,7 @@ export const popUp = {
7
7
  "pop-up-style":
8
8
  "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 animate-fade-down animate-once animate-duration-200",
9
9
  };
10
+
11
+ export const iconClass = {
12
+ "grey-common": "h-4 w-4 stroke-gray-500 fill-none dark:stroke-white",
13
+ };
@@ -0,0 +1,4 @@
1
+ export type ItemsProps = {
2
+ value: string;
3
+ label: string;
4
+ };
@@ -0,0 +1,22 @@
1
+ import { useCallback, useEffect } from "react";
2
+
3
+ export const useClickOutside = <T extends HTMLElement>(
4
+ ref: React.RefObject<T | null>,
5
+ callback: () => void
6
+ ) => {
7
+ const handleClickOutside = useCallback(
8
+ (event: MouseEvent) => {
9
+ if (ref.current && !ref.current.contains(event.target as Node)) {
10
+ callback();
11
+ }
12
+ },
13
+ [callback, ref]
14
+ );
15
+
16
+ useEffect(() => {
17
+ document.addEventListener("mousedown", handleClickOutside);
18
+ return () => {
19
+ document.removeEventListener("mousedown", handleClickOutside);
20
+ };
21
+ }, [handleClickOutside]);
22
+ };
@@ -0,0 +1,64 @@
1
+ import { useState, useCallback } from "react";
2
+ import { useSelectState } from "./useSelectState";
3
+ import { useSelectData } from "./useSelectData";
4
+ import type { ItemsProps } from "./type";
5
+
6
+ export const useMultiSelectState = (
7
+ items: ItemsProps[] | string | undefined,
8
+ selectedItems: string[] = [],
9
+ lazy = false,
10
+ onSelect?: (selectedItems: string[]) => void
11
+ ) => {
12
+ const [selected, setSelected] = useState<string[]>(selectedItems);
13
+
14
+ const {
15
+ showPopover,
16
+ setShowPopover,
17
+ workingDataSource,
18
+ setWorkingDataSource,
19
+ searchTerm,
20
+ setSearchTerm,
21
+ togglePopover,
22
+ } = useSelectState();
23
+
24
+ const { filteredItems, getSelectItems } = useSelectData(
25
+ items,
26
+ setWorkingDataSource,
27
+ workingDataSource,
28
+ searchTerm,
29
+ lazy,
30
+ undefined
31
+ );
32
+
33
+ const handleSelect = useCallback(
34
+ (value: string) => {
35
+ const newSelected = selected.includes(value)
36
+ ? selected.filter((item) => item !== value)
37
+ : [...selected, value];
38
+ setSelected(newSelected);
39
+ onSelect?.(newSelected);
40
+ },
41
+ [selected, onSelect]
42
+ );
43
+
44
+ const clearSelected = useCallback(() => {
45
+ setSelected([]);
46
+ setShowPopover(false);
47
+ setSearchTerm("");
48
+ onSelect?.([]);
49
+ }, [setShowPopover, setSearchTerm, onSelect]);
50
+
51
+ return {
52
+ showPopover,
53
+ setShowPopover,
54
+ workingDataSource,
55
+ filteredItems,
56
+ selected,
57
+ searchTerm,
58
+ setSearchTerm,
59
+ handleSelect,
60
+ togglePopover,
61
+ clearSelected,
62
+ getSelectItems,
63
+ };
64
+ };
@@ -0,0 +1,58 @@
1
+ import { useCallback, useEffect, useMemo } from "react";
2
+ import { getSourceData } from "../../utils";
3
+ import type { ItemsProps } from "./type";
4
+
5
+ export const useSelectData = (
6
+ items: ItemsProps[] | string | undefined,
7
+ setWorkingDataSource: (data: ItemsProps[]) => void,
8
+ workingDataSource: ItemsProps[],
9
+ searchTerm: string,
10
+ lazy: boolean,
11
+ selectedItem?: string
12
+ ) => {
13
+ const getSelectItems = useCallback(
14
+ async (itemsApi: string) => {
15
+ const itemsData = await getSourceData(itemsApi);
16
+ setWorkingDataSource(itemsData.sourcedata);
17
+ },
18
+ [setWorkingDataSource]
19
+ );
20
+
21
+ useEffect(() => {
22
+ if (!items) {
23
+ setWorkingDataSource([]);
24
+ return;
25
+ }
26
+
27
+ if (Array.isArray(items)) {
28
+ setWorkingDataSource(items);
29
+ } else if (typeof items === "string") {
30
+ getSelectItems(items);
31
+ }
32
+ }, [items, getSelectItems, setWorkingDataSource]);
33
+
34
+ const selectedDisplay = useMemo(() => {
35
+ if (selectedItem && workingDataSource.length > 0) {
36
+ const selected = workingDataSource.find(
37
+ (item) => item.value === selectedItem
38
+ );
39
+ return selected ? selected.label : "";
40
+ }
41
+ return "";
42
+ }, [selectedItem, workingDataSource]);
43
+
44
+ const filteredItems = useMemo(() => {
45
+ if (!searchTerm || lazy) return workingDataSource;
46
+ return workingDataSource.filter((item) =>
47
+ Object.values(item).some((val) =>
48
+ val.toString().toLowerCase().includes(searchTerm.toLowerCase())
49
+ )
50
+ );
51
+ }, [workingDataSource, searchTerm, lazy]);
52
+
53
+ return {
54
+ getSelectItems,
55
+ selectedDisplay,
56
+ filteredItems,
57
+ };
58
+ };