gbs-add-block 1.0.10 → 1.0.12

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 (v1.0.10)
1
+ # GBS Building Blocks 2.0 (v1.0.12)
2
2
 
3
3
  Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
4
4
 
@@ -6,10 +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 1.0.10)
9
+ ## What's New 🎉 (Ver 1.0.12)
10
10
 
11
11
  - Uploader Enhancement
12
- - Select Bug Fix
12
+ - Select & MultiSelect Bug Fix
13
13
 
14
14
  ## Authors
15
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "React Component Library",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,42 @@
1
+ import { useEffect, useState } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { popUp } from "../globalStyle";
4
+
5
+ export const PortalDropdown = ({ targetRef, children, isVisible }: any) => {
6
+ const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });
7
+
8
+ useEffect(() => {
9
+ if (isVisible && targetRef.current) {
10
+ const rect = targetRef.current.getBoundingClientRect();
11
+ const scrollTop =
12
+ window.pageYOffset || document.documentElement.scrollTop;
13
+ const scrollLeft =
14
+ window.pageXOffset || document.documentElement.scrollLeft;
15
+
16
+ setPosition({
17
+ top: rect.bottom + scrollTop + 4,
18
+ left: rect.left + scrollLeft,
19
+ width: rect.width,
20
+ });
21
+ }
22
+ }, [isVisible, targetRef]);
23
+
24
+ if (!isVisible) return null;
25
+
26
+ return createPortal(
27
+ <div
28
+ style={{
29
+ position: "absolute",
30
+ top: position.top,
31
+ left: position.left,
32
+ width: position.width,
33
+ zIndex: 1000,
34
+ }}
35
+ className={popUp["pop-up-style"]}
36
+ data-select-portal="true"
37
+ >
38
+ {children}
39
+ </div>,
40
+ document.body
41
+ );
42
+ };
@@ -21,7 +21,9 @@ import {
21
21
  } from "@grampro/headless-helpers";
22
22
  import Icon from "../icon/Icon";
23
23
  import { check, search, upDown, x } from "../icon/iconPaths";
24
- import { iconClass, popUp, primary } from "../globalStyle";
24
+ import { iconClass, primary } from "../globalStyle";
25
+ import { selectStyle } from "../globalStyle";
26
+ import { PortalDropdown } from "./PortalDropDown";
25
27
 
26
28
  const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
27
29
  (props, ref) => {
@@ -42,6 +44,7 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
42
44
 
43
45
  const inputRef = useRef<HTMLInputElement>(null);
44
46
  const selectRef = useRef<HTMLDivElement>(null);
47
+ const buttonRef = useRef<HTMLButtonElement>(null);
45
48
 
46
49
  const {
47
50
  showPopover,
@@ -57,9 +60,15 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
57
60
  getSelectItems,
58
61
  } = useMultiSelectState(items, selectedItems, lazy, onSelect);
59
62
 
60
- useClickOutside(selectRef as React.RefObject<HTMLElement>, () =>
61
- setShowPopover(false)
62
- );
63
+ useClickOutside(selectRef as React.RefObject<HTMLElement>, (event) => {
64
+ const portalElement = document.querySelector(
65
+ '[data-select-portal="true"]'
66
+ );
67
+ if (portalElement && portalElement.contains(event.target as Node)) {
68
+ return;
69
+ }
70
+ setShowPopover(false);
71
+ });
63
72
 
64
73
  useEffect(() => {
65
74
  if (showPopover) {
@@ -68,7 +77,7 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
68
77
  }, [showPopover]);
69
78
 
70
79
  const getSelectedDisplay = useCallback(() => {
71
- if (selected.length === 0) return placeholder;
80
+ // if (selected.length === 0) return placeholder;
72
81
  const displayedItems = selected
73
82
  .slice(0, 3)
74
83
  .map(
@@ -110,42 +119,112 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
110
119
  ]
111
120
  );
112
121
 
122
+ // Portal dropdown content
123
+ const dropdownContent = (
124
+ <>
125
+ {showSearch && (
126
+ <div className={selectStyle["input-parent"]}>
127
+ <Icon elements={search} svgClass={iconClass["grey-common"]} />
128
+ <input
129
+ autoComplete="off"
130
+ type="text"
131
+ name="search"
132
+ id="search"
133
+ placeholder="Search a value"
134
+ className="w-full outline-none dark:bg-transparent"
135
+ ref={inputRef}
136
+ value={searchTerm}
137
+ onChange={(e) => setSearchTerm(e.target.value)}
138
+ />
139
+ </div>
140
+ )}
141
+
142
+ {filteredItems.length > 0 ? (
143
+ filteredItems.map(({ value, label }) => (
144
+ <button
145
+ type="button"
146
+ key={value}
147
+ className={selectStyle["filter-button"]}
148
+ onClick={() => handleSelect(value)}
149
+ >
150
+ <Icon
151
+ elements={check}
152
+ svgClass={`h-4 w-4 fill-none ${
153
+ selected.includes(value) ? "stroke-gray-500" : ""
154
+ }`}
155
+ />
156
+ {label.length > 20 ? `${label.substring(0, 20)}...` : label}
157
+ </button>
158
+ ))
159
+ ) : (
160
+ <div className="text-sm text-center">No Data Found</div>
161
+ )}
162
+ </>
163
+ );
164
+
165
+ // Selected display Content
166
+ const selectedDisplay = (
167
+ <span className="flex items-center gap-1">
168
+ {selected
169
+ .slice(0, truncate && selected.length > 3 ? 3 : selected.length)
170
+ .map((item: string, index: number) => {
171
+ return (
172
+ <span
173
+ key={index}
174
+ className="bg-slate-800 text-white px-2 py-1 rounded flex items-center gap-1 text-xs"
175
+ >
176
+ {item}{" "}
177
+ <span
178
+ onClick={() => handleSelect(item)}
179
+ className="cursor-pointer"
180
+ >
181
+ <Icon elements={x} svgClass={iconClass["grey-common"]} />
182
+ </span>
183
+ </span>
184
+ );
185
+ })}
186
+ {truncate && selected.length > 3 && (
187
+ <span className="bg-slate-800 text-white px-2 py-1 rounded text-xs">
188
+ +{selected.length - 3} more
189
+ </span>
190
+ )}
191
+ </span>
192
+ );
193
+
113
194
  return (
114
195
  <div className="relative w-full" ref={selectRef}>
115
- <div
116
- className={`relative border border-gary-300 w-full flex items-center px-4 py-2 rounded-lg ${
117
- error && "border-red-500"
118
- }`}
119
- >
196
+ <div className="w-full relative">
120
197
  <button
198
+ ref={buttonRef}
199
+ className={`${error ? primary["error-border"] : "border"} ${
200
+ selectStyle["select-button"]
201
+ } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
121
202
  onClick={togglePopover}
122
- className={`flex-grow text-sm text-left font-medium ${
123
- disabled ? "opacity-50 cursor-not-allowed" : ""
124
- }`}
125
203
  type="button"
126
204
  disabled={disabled}
127
205
  >
128
- {getSelectedDisplay()}
129
- </button>
130
- <div className="flex items-center space-x-2">
131
- {selected.length > 0 && (
132
- <button
133
- className="flex items-center px-2"
134
- onClick={handleClear}
135
- type="button"
136
- >
137
- <Icon elements={x} svgClass={iconClass["grey-common"]} />
138
- </button>
206
+ {selected.length == 0 ? (
207
+ <span>{placeholder}</span>
208
+ ) : (
209
+ <span>{selectedDisplay}</span>
139
210
  )}
140
- <button onClick={togglePopover} type="button">
141
- <Icon
142
- elements={upDown}
143
- svgClass={`${iconClass["grey-common"]} ${
144
- disabled ? "opacity-50" : ""
145
- }`}
146
- />
211
+
212
+ <Icon
213
+ elements={upDown}
214
+ svgClass={`${iconClass["grey-common"]} ${
215
+ disabled ? "opacity-50" : ""
216
+ }`}
217
+ />
218
+ </button>
219
+ {selected.length > 0 && (
220
+ <button
221
+ className={selectStyle["selectedDisplay-Button"]}
222
+ onClick={handleClear}
223
+ type="button"
224
+ >
225
+ <Icon elements={x} svgClass={iconClass["grey-common"]} />
147
226
  </button>
148
- </div>
227
+ )}
149
228
  </div>
150
229
  {error && <p className={primary["error-primary"]}>{error}</p>}
151
230
 
@@ -157,46 +236,12 @@ const MultiSelect = forwardRef<MultiSelectHandle, MultiSelectProps>(
157
236
  disabled={disabled}
158
237
  />
159
238
 
160
- {showPopover && !disabled && (
161
- <div className={popUp["pop-up-style"]}>
162
- {showSearch && (
163
- <div className="flex p-2 gap-1 items-center sticky top-0 bg-white border-b dark:bg-black">
164
- <Icon elements={search} svgClass={iconClass["grey-common"]} />
165
- <input
166
- autoComplete="off"
167
- type="text"
168
- name="search"
169
- id="search"
170
- placeholder="Search a value"
171
- className="w-full outline-none dark:bg-black"
172
- ref={inputRef}
173
- value={searchTerm}
174
- onChange={(e) => setSearchTerm(e.target.value)}
175
- />
176
- </div>
177
- )}
178
- {filteredItems.length > 0 ? (
179
- filteredItems.map(({ value, label }) => (
180
- <button
181
- type="button"
182
- key={value}
183
- 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"
184
- onClick={() => handleSelect(value)}
185
- >
186
- <Icon
187
- elements={check}
188
- svgClass={`h-4 w-4 fill-none ${
189
- selected.includes(value) ? "stroke-gray-500" : ""
190
- }`}
191
- />
192
- {label}
193
- </button>
194
- ))
195
- ) : (
196
- <div className="text-sm text-center">No Data Found</div>
197
- )}
198
- </div>
199
- )}
239
+ <PortalDropdown
240
+ targetRef={buttonRef}
241
+ isVisible={showPopover && !disabled}
242
+ >
243
+ {dropdownContent}
244
+ </PortalDropdown>
200
245
  </div>
201
246
  );
202
247
  }
@@ -0,0 +1,42 @@
1
+ import { useEffect, useState } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { popUp } from "../globalStyle";
4
+
5
+ export const PortalDropdown = ({ targetRef, children, isVisible }: any) => {
6
+ const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });
7
+
8
+ useEffect(() => {
9
+ if (isVisible && targetRef.current) {
10
+ const rect = targetRef.current.getBoundingClientRect();
11
+ const scrollTop =
12
+ window.pageYOffset || document.documentElement.scrollTop;
13
+ const scrollLeft =
14
+ window.pageXOffset || document.documentElement.scrollLeft;
15
+
16
+ setPosition({
17
+ top: rect.bottom + scrollTop + 4,
18
+ left: rect.left + scrollLeft,
19
+ width: rect.width,
20
+ });
21
+ }
22
+ }, [isVisible, targetRef]);
23
+
24
+ if (!isVisible) return null;
25
+
26
+ return createPortal(
27
+ <div
28
+ style={{
29
+ position: "absolute",
30
+ top: position.top,
31
+ left: position.left,
32
+ width: position.width,
33
+ zIndex: 1000,
34
+ }}
35
+ className={popUp["pop-up-style"]}
36
+ data-select-portal="true"
37
+ >
38
+ {children}
39
+ </div>,
40
+ document.body
41
+ );
42
+ };
@@ -12,14 +12,12 @@ import React, {
12
12
  useEffect,
13
13
  useImperativeHandle,
14
14
  useRef,
15
- useState,
16
15
  } from "react";
17
- import { createPortal } from "react-dom";
18
16
  import Icon from "../icon/Icon";
19
17
  import { check, search, upDown, x } from "../icon/iconPaths";
20
18
  import type { ItemsProps, SelectHandle, SelectProps } from "./types";
21
- import { selectStyle } from "./style";
22
- import { iconClass, popUp, primary } from "../globalStyle";
19
+ import { selectStyle } from "../globalStyle";
20
+ import { iconClass, primary } from "../globalStyle";
23
21
  import {
24
22
  useSelectState,
25
23
  useSelectData,
@@ -27,46 +25,7 @@ import {
27
25
  useKeyboardNavigation,
28
26
  useClickOutside,
29
27
  } from "@grampro/headless-helpers";
30
-
31
- // Portal Dropdown Component
32
- const PortalDropdown = ({ targetRef, children, isVisible }: any) => {
33
- const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });
34
-
35
- useEffect(() => {
36
- if (isVisible && targetRef.current) {
37
- const rect = targetRef.current.getBoundingClientRect();
38
- const scrollTop =
39
- window.pageYOffset || document.documentElement.scrollTop;
40
- const scrollLeft =
41
- window.pageXOffset || document.documentElement.scrollLeft;
42
-
43
- setPosition({
44
- top: rect.bottom + scrollTop + 4,
45
- left: rect.left + scrollLeft,
46
- width: rect.width,
47
- });
48
- }
49
- }, [isVisible, targetRef]);
50
-
51
- if (!isVisible) return null;
52
-
53
- return createPortal(
54
- <div
55
- style={{
56
- position: "absolute",
57
- top: position.top,
58
- left: position.left,
59
- width: position.width,
60
- zIndex: 1000,
61
- }}
62
- className={popUp["pop-up-style"]}
63
- data-select-portal="true"
64
- >
65
- {children}
66
- </div>,
67
- document.body
68
- );
69
- };
28
+ import { PortalDropdown } from "./PortalDropDown";
70
29
 
71
30
  const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
72
31
  const {
@@ -211,7 +170,7 @@ const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
211
170
  selectedItem === value ? "stroke-gray-500" : ""
212
171
  }`}
213
172
  />
214
- {label}
173
+ {label.length > 20 ? `${label.substring(0, 20)}...` : label}
215
174
  </button>
216
175
  ))
217
176
  ) : (
@@ -222,14 +181,14 @@ const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
222
181
 
223
182
  return (
224
183
  <div
225
- className="relative w-full mt-2"
184
+ className="relative w-full"
226
185
  ref={selectRef}
227
186
  id={id}
228
187
  onKeyDown={handleKeyDown}
229
188
  >
230
189
  <div className="w-full relative">
231
190
  <button
232
- ref={buttonRef} // Add ref for portal positioning
191
+ ref={buttonRef}
233
192
  className={`${error ? primary["error-border"] : "border"} ${
234
193
  selectStyle["select-button"]
235
194
  } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
@@ -5,7 +5,7 @@ export const primary = {
5
5
 
6
6
  export const popUp = {
7
7
  "pop-up-style":
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",
8
+ "w-full absolute overflow-y-auto px-2 border border-slate-200 rounded-lg mt-[2px] scrollbar bg-white z-90 scrollbar h-auto dark:bg-black dark:text-white animate-fade-down animate-once animate-duration-200",
9
9
  };
10
10
 
11
11
  export const iconClass = {
@@ -20,3 +20,14 @@ export const inputStyles = {
20
20
  passwordToggle: "absolute inset-y-0 right-0 flex items-center pr-2",
21
21
  otpContainer: "otp-container",
22
22
  };
23
+
24
+ export const selectStyle = {
25
+ "select-button":
26
+ "flex items-center px-4 py-2 w-full justify-between rounded-lg font-medium text-sm",
27
+ "filter-button":
28
+ "flex items-center w-full px-2 py-1 text-left hover:bg-blue-100 gap-2 rounded-lg mt-1 text-xs dark:hover:bg-blue-600",
29
+ "input-parent":
30
+ "text-sm flex p-1 mt-1 gap-1 items-center sticky top-0 bg-white border rounded-lg border-slate-200 z-10",
31
+ "selectedDisplay-Button":
32
+ "absolute right-8 top-0 h-full flex items-center px-2 z-20",
33
+ };
@@ -1,9 +0,0 @@
1
- export const selectStyle = {
2
- "select-button":
3
- "flex items-center px-4 py-2 w-full justify-between rounded-lg font-medium text-sm",
4
- "filter-button":
5
- "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",
6
- "input-parent": "flex p-2 gap-1 items-center sticky top-0 bg-white border-b",
7
- "selectedDisplay-Button":
8
- "absolute right-8 top-0 h-full flex items-center px-2 z-20",
9
- };