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 +3 -3
- package/package.json +1 -1
- package/source/components/multiselect/PortalDropDown.tsx +42 -0
- package/source/components/multiselect/index.tsx +117 -72
- package/source/components/select/PortalDropDown.tsx +42 -0
- package/source/components/select/index.tsx +6 -47
- package/source/globalStyle.ts +12 -1
- package/source/components/select/style.ts +0 -9
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# GBS Building Blocks 2.0 (v1.0.
|
|
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.
|
|
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
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
{
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 "
|
|
22
|
-
import { iconClass,
|
|
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
|
|
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}
|
|
191
|
+
ref={buttonRef}
|
|
233
192
|
className={`${error ? primary["error-border"] : "border"} ${
|
|
234
193
|
selectStyle["select-button"]
|
|
235
194
|
} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
|
package/source/globalStyle.ts
CHANGED
|
@@ -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
|
|
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
|
-
};
|