@inceptionbg/iui 2.0.19 → 2.0.20
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/dist/index.d.ts +15 -7
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/iui.css +1 -1
- package/package.json +2 -1
- package/src/components/Accordions/Accordions.tsx +35 -27
- package/src/components/Dialog/Dialog.tsx +27 -25
- package/src/components/Header/Components/EnvBadge.tsx +17 -0
- package/src/components/Header/Header.tsx +7 -3
- package/src/components/Inputs/Selects/components/SelectWrapper.tsx +1 -1
- package/src/components/Table/components/items/TableItemActions.tsx +5 -3
- package/src/components/Table/contexts/TableContext.tsx +8 -5
- package/src/components/Table/hooks/localHooks/useLocalTableData.tsx +1 -1
- package/src/components/Table/hooks/localHooks/useLocalTableKeyboard.ts +3 -1
- package/src/components/Table/hooks/useTableEdit.tsx +9 -2
- package/src/components/Table/hooks/useTableSearch.ts +2 -1
- package/src/components/Tabs/Tabs.tsx +3 -3
- package/src/components/Tooltip/Tooltip.tsx +14 -81
- package/src/components/Wrappers/FormWrapper.tsx +1 -1
- package/src/index.ts +2 -0
- package/src/styles/common/_typography.scss +2 -2
- package/src/styles/components/_dialog.scss +25 -19
- package/src/styles/components/_header.scss +5 -0
- package/src/styles/components/_table.scss +1 -1
- package/src/styles/components/_tabs.scss +1 -1
- package/src/types/ITable.ts +1 -1
- package/src/utils/InputPatternValidation.ts +2 -2
- package/src/utils/objectUtils.ts +15 -5
- package/src/components/Inputs/Select2/Select.tsx +0 -258
- package/src/components/Inputs/Select2/select.scss +0 -42
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Dispatch, SetStateAction, useState } from 'react';
|
|
2
|
+
import { deletePropsThatEndsWith } from '../../../utils/objectUtils';
|
|
2
3
|
|
|
3
4
|
export interface ITableSearchProps<T> {
|
|
4
5
|
setOffset: Dispatch<SetStateAction<number>>;
|
|
@@ -17,7 +18,7 @@ export const useTableSearch = <T>({
|
|
|
17
18
|
const onSearch = (filters: T) => {
|
|
18
19
|
setOffset(0);
|
|
19
20
|
resetSelectedRows?.();
|
|
20
|
-
setSearch(filters);
|
|
21
|
+
setSearch(<T>deletePropsThatEndsWith(filters, 'Obj'));
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
return {
|
|
@@ -27,9 +27,9 @@ export const Tabs: FC<Props> = ({
|
|
|
27
27
|
}) => {
|
|
28
28
|
const filteredTabs = useMemo(() => tabs.filter(e => !e.hidden), [tabs]);
|
|
29
29
|
|
|
30
|
-
const [selected, setSelected] = useState(initialValue || filteredTabs[0]
|
|
30
|
+
const [selected, setSelected] = useState(initialValue || filteredTabs[0]?.value || '');
|
|
31
31
|
|
|
32
|
-
const activeTab = filteredTabs.find(tab => (control?.value
|
|
32
|
+
const activeTab = filteredTabs.find(tab => (control?.value ?? selected) === tab.value);
|
|
33
33
|
|
|
34
34
|
return filteredTabs.length > 0 ? (
|
|
35
35
|
<div className={className}>
|
|
@@ -39,7 +39,7 @@ export const Tabs: FC<Props> = ({
|
|
|
39
39
|
id={tab.value}
|
|
40
40
|
key={tab.value}
|
|
41
41
|
className={clsx('iui-tab clickable', {
|
|
42
|
-
selected: (control?.value
|
|
42
|
+
selected: (control?.value ?? selected) === tab.value,
|
|
43
43
|
disabled: tab.disabled,
|
|
44
44
|
'no-wrap': noWrap,
|
|
45
45
|
})}
|
|
@@ -103,98 +103,31 @@ const setAbsolutePosition = (
|
|
|
103
103
|
) => {
|
|
104
104
|
const containerRect = containerEl.getBoundingClientRect();
|
|
105
105
|
const tooltipRect = targetEl.getBoundingClientRect();
|
|
106
|
-
const margin = 16; // Distance from container
|
|
107
|
-
const screenPadding = 8; // Minimum distance from screen edges
|
|
108
106
|
|
|
109
107
|
if (position === 'bottom' || position === 'top') {
|
|
110
|
-
const bottom = Math.floor(containerRect.bottom +
|
|
111
|
-
const top = Math.floor(containerRect.top - tooltipRect.height -
|
|
112
|
-
|
|
108
|
+
const bottom = Math.floor(containerRect.bottom + 16);
|
|
109
|
+
const top = Math.floor(containerRect.top - tooltipRect.height - 16);
|
|
110
|
+
const leftCenter = Math.floor(
|
|
113
111
|
containerRect.left + containerRect.width / 2 - tooltipRect.width / 2
|
|
114
112
|
);
|
|
115
|
-
|
|
116
|
-
// Check if tooltip overflows horizontally and adjust
|
|
117
|
-
if (leftCenter < screenPadding) {
|
|
118
|
-
targetEl.style.width = `${tooltipRect.width + leftCenter}px`;
|
|
119
|
-
leftCenter = screenPadding;
|
|
120
|
-
} else if (leftCenter + tooltipRect.width > window.innerWidth - screenPadding) {
|
|
121
|
-
leftCenter = window.innerWidth - tooltipRect.width - screenPadding;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
113
|
const reverse =
|
|
125
114
|
position === 'top' ? top < 0 : bottom + tooltipRect.height > window.innerHeight;
|
|
126
115
|
|
|
127
116
|
setNewPosition(reverse ? (position === 'bottom' ? 'top' : 'bottom') : '');
|
|
128
117
|
|
|
129
|
-
|
|
130
|
-
position === 'bottom' ? (reverse ? top : bottom) : reverse ? bottom : top
|
|
131
|
-
|
|
132
|
-
// Ensure tooltip doesn't go above screen
|
|
133
|
-
if (finalTop < screenPadding) {
|
|
134
|
-
finalTop = screenPadding;
|
|
135
|
-
}
|
|
136
|
-
// Ensure tooltip doesn't go below screen
|
|
137
|
-
if (finalTop + tooltipRect.height > window.innerHeight - screenPadding) {
|
|
138
|
-
finalTop = window.innerHeight - tooltipRect.height - screenPadding;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
targetEl.style.top = `${finalTop}px`;
|
|
118
|
+
targetEl.style.top = `${
|
|
119
|
+
position === 'bottom' ? (reverse ? top : bottom) : reverse ? bottom : top
|
|
120
|
+
}px`;
|
|
142
121
|
targetEl.style.left = `${leftCenter}px`;
|
|
143
122
|
} else if (position === 'right') {
|
|
144
|
-
|
|
145
|
-
containerRect.top + containerRect.height / 2 - tooltipRect.height / 2
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Check if tooltip overflows vertically and adjust
|
|
150
|
-
if (finalTop < screenPadding) {
|
|
151
|
-
finalTop = screenPadding;
|
|
152
|
-
} else if (finalTop + tooltipRect.height > window.innerHeight - screenPadding) {
|
|
153
|
-
finalTop = window.innerHeight - tooltipRect.height - screenPadding;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Check if tooltip overflows to the right and flip to left
|
|
157
|
-
if (finalLeft + tooltipRect.width > window.innerWidth - screenPadding) {
|
|
158
|
-
finalLeft = Math.floor(containerRect.left - tooltipRect.width - margin);
|
|
159
|
-
setNewPosition('left');
|
|
160
|
-
|
|
161
|
-
// If it still overflows on the left, position it at the edge
|
|
162
|
-
if (finalLeft < screenPadding) {
|
|
163
|
-
finalLeft = screenPadding;
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
setNewPosition('');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
targetEl.style.top = `${finalTop}px`;
|
|
170
|
-
targetEl.style.left = `${finalLeft}px`;
|
|
123
|
+
targetEl.style.top =
|
|
124
|
+
Math.floor(containerRect.top + containerRect.height / 2 - tooltipRect.height / 2) +
|
|
125
|
+
'px';
|
|
126
|
+
targetEl.style.left = Math.floor(containerRect.right + 16) + 'px';
|
|
171
127
|
} else if (position === 'left') {
|
|
172
|
-
|
|
173
|
-
containerRect.top + containerRect.height / 2 - tooltipRect.height / 2
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// Check if tooltip overflows vertically and adjust
|
|
178
|
-
if (finalTop < screenPadding) {
|
|
179
|
-
finalTop = screenPadding;
|
|
180
|
-
} else if (finalTop + tooltipRect.height > window.innerHeight - screenPadding) {
|
|
181
|
-
finalTop = window.innerHeight - tooltipRect.height - screenPadding;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Check if tooltip overflows to the left and flip to right
|
|
185
|
-
if (finalLeft < screenPadding) {
|
|
186
|
-
finalLeft = Math.floor(containerRect.right + margin);
|
|
187
|
-
setNewPosition('right');
|
|
188
|
-
|
|
189
|
-
// If it still overflows on the right, position it at the edge
|
|
190
|
-
if (finalLeft + tooltipRect.width > window.innerWidth - screenPadding) {
|
|
191
|
-
finalLeft = window.innerWidth - tooltipRect.width - screenPadding;
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
setNewPosition('');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
targetEl.style.top = `${finalTop}px`;
|
|
198
|
-
targetEl.style.left = `${finalLeft}px`;
|
|
128
|
+
targetEl.style.top =
|
|
129
|
+
Math.floor(containerRect.top + containerRect.height / 2 - tooltipRect.height / 2) +
|
|
130
|
+
'px';
|
|
131
|
+
targetEl.style.left = Math.floor(containerRect.left - tooltipRect.width - 16) + 'px';
|
|
199
132
|
}
|
|
200
133
|
};
|
|
@@ -67,7 +67,7 @@ export const FormWrapper: FC<IFormWrapper> = ({
|
|
|
67
67
|
{!noAccess && (
|
|
68
68
|
<Button
|
|
69
69
|
label={submitButton.label ?? t('Save')}
|
|
70
|
-
|
|
70
|
+
icon={submitButton.icon}
|
|
71
71
|
disabled={isLoading || submitButton.disabled}
|
|
72
72
|
variant={submitButton.variant}
|
|
73
73
|
color={submitButton.color}
|
package/src/index.ts
CHANGED
|
@@ -173,6 +173,7 @@ import {
|
|
|
173
173
|
deleteEmptyPropsIncludingArray,
|
|
174
174
|
deleteProps,
|
|
175
175
|
deletePropsThatEndsWith,
|
|
176
|
+
flattenTreeForSelect,
|
|
176
177
|
intersectArrays,
|
|
177
178
|
} from './utils/objectUtils';
|
|
178
179
|
import {
|
|
@@ -345,6 +346,7 @@ export {
|
|
|
345
346
|
setTemplateData,
|
|
346
347
|
getVisibleColumnsIds,
|
|
347
348
|
inputPattern,
|
|
349
|
+
flattenTreeForSelect,
|
|
348
350
|
intersectArrays,
|
|
349
351
|
maxChar,
|
|
350
352
|
parseUrlSearch,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
@use '../variables/bp';
|
|
2
|
-
@import url('
|
|
2
|
+
@import url('@fontsource-variable/ibm-plex-sans');
|
|
3
3
|
|
|
4
4
|
* {
|
|
5
|
-
font-family: '
|
|
5
|
+
font-family: 'IBM Plex Sans Variable', sans-serif;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
body {
|
|
@@ -24,15 +24,22 @@ $dialog-sizes: (
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
.iui-dialog-container {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
gap: 24px;
|
|
27
30
|
background-color: var(--background);
|
|
28
31
|
border-radius: 16px;
|
|
29
32
|
min-width: 400px;
|
|
30
33
|
max-width: 98vw;
|
|
34
|
+
max-height: 95dvh;
|
|
31
35
|
animation: scale-in 0.2s forwards;
|
|
32
36
|
overflow: hidden;
|
|
37
|
+
padding: 24px;
|
|
38
|
+
|
|
33
39
|
.iui-dialog-header {
|
|
34
40
|
display: flex;
|
|
35
41
|
gap: 16px;
|
|
42
|
+
flex-shrink: 0;
|
|
36
43
|
.iui-dialog-desc {
|
|
37
44
|
color: var(--text);
|
|
38
45
|
word-break: break-word;
|
|
@@ -53,6 +60,18 @@ $dialog-sizes: (
|
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
}
|
|
63
|
+
.iui-dialog-content {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: 8px;
|
|
67
|
+
flex: 1;
|
|
68
|
+
margin-right: -24px;
|
|
69
|
+
padding-right: 24px;
|
|
70
|
+
&:not(.no-overflow) {
|
|
71
|
+
overflow: auto;
|
|
72
|
+
overscroll-behavior: none;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
56
75
|
|
|
57
76
|
@each $key, $value in $dialog-sizes {
|
|
58
77
|
&.#{$key} {
|
|
@@ -71,28 +90,15 @@ $dialog-sizes: (
|
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
92
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
max-height: 80vh;
|
|
81
|
-
padding: 24px;
|
|
82
|
-
&:not(.no-overflow) {
|
|
83
|
-
overflow: auto;
|
|
84
|
-
overscroll-behavior: none;
|
|
93
|
+
.iui-dialog-actions {
|
|
94
|
+
display: flex;
|
|
95
|
+
gap: 8px;
|
|
96
|
+
justify-content: right;
|
|
97
|
+
padding-top: 24px;
|
|
98
|
+
border-top: var(--border);
|
|
85
99
|
}
|
|
86
100
|
}
|
|
87
101
|
|
|
88
|
-
.iui-dialog-actions {
|
|
89
|
-
display: flex;
|
|
90
|
-
gap: 8px;
|
|
91
|
-
justify-content: right;
|
|
92
|
-
padding: 24px;
|
|
93
|
-
border-top: var(--border);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
102
|
@media #{bp.$mobile} {
|
|
97
103
|
.iui-dialog-container {
|
|
98
104
|
height: 100dvh;
|
package/src/types/ITable.ts
CHANGED
|
@@ -3,10 +3,10 @@ export const inputPattern = {
|
|
|
3
3
|
businessCode: '^\\d{8}$',
|
|
4
4
|
umcn: '^\\d{13}$',
|
|
5
5
|
idCardNumber: '^\\d{9}$',
|
|
6
|
-
phoneNumber: '
|
|
6
|
+
phoneNumber: '^\\+\\d{11,13}$',
|
|
7
7
|
port: '^\\d{4}$',
|
|
8
8
|
year: '^\\d{4}$',
|
|
9
9
|
number: '^\\d*$',
|
|
10
10
|
strongPassword: '^.*(?=.{8,})(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).*$',
|
|
11
|
-
|
|
11
|
+
email: '\\S+@\\S+\\.\\S+',
|
|
12
12
|
};
|
package/src/utils/objectUtils.ts
CHANGED
|
@@ -14,17 +14,14 @@ export const deleteProps = <T extends Record<string, any>>(
|
|
|
14
14
|
return newObj as Partial<T>;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export const deletePropsThatEndsWith = <T
|
|
18
|
-
obj: T,
|
|
19
|
-
endsWith: string
|
|
20
|
-
) => {
|
|
17
|
+
export const deletePropsThatEndsWith = <T>(obj: T, endsWith: string) => {
|
|
21
18
|
const newObj = { ...obj };
|
|
22
19
|
for (const prop in newObj) {
|
|
23
20
|
if (prop.endsWith(endsWith)) {
|
|
24
21
|
delete newObj[prop];
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
|
-
return newObj
|
|
24
|
+
return newObj as Partial<T>;
|
|
28
25
|
};
|
|
29
26
|
|
|
30
27
|
export const deleteEmptyProps = <T extends Record<string, any>>(obj: T): T =>
|
|
@@ -102,3 +99,16 @@ export const getNotificationValues = (
|
|
|
102
99
|
}
|
|
103
100
|
return { ...obj, [item.key]: processedValue };
|
|
104
101
|
}, {});
|
|
102
|
+
|
|
103
|
+
export const flattenTreeForSelect = (items: any[], depth = 0, maxDepth = 5): any[] => {
|
|
104
|
+
if (!Array.isArray(items)) return [];
|
|
105
|
+
const indent = '\u00A0'.repeat(Math.min(depth, maxDepth) * 3);
|
|
106
|
+
return items.flatMap(node => [
|
|
107
|
+
{
|
|
108
|
+
value: node.uuid,
|
|
109
|
+
label: `${indent}${node.code ? node.code + ' - ' : ''}${node.name}`,
|
|
110
|
+
disabled: node.active === false || node.hasUserAccess === false,
|
|
111
|
+
},
|
|
112
|
+
...(node.children ? flattenTreeForSelect(node.children, depth + 1, maxDepth) : []),
|
|
113
|
+
]);
|
|
114
|
+
};
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import './select.scss';
|
|
2
|
-
|
|
3
|
-
import { useState, useRef, useEffect, ChangeEvent, FC, UIEvent } from 'react';
|
|
4
|
-
import clsx from 'clsx';
|
|
5
|
-
import { InputWrapper } from '../InputWrapper';
|
|
6
|
-
import { createPortal } from 'react-dom';
|
|
7
|
-
import { rootDir } from '../../../utils/rootDir';
|
|
8
|
-
import { useMenuPosition } from '../../Menu/hooks/useMenuPosition';
|
|
9
|
-
import { IMenuPlacement } from '../../../types/IMenu';
|
|
10
|
-
|
|
11
|
-
export interface OptionType {
|
|
12
|
-
label: string;
|
|
13
|
-
value: any;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// interface LoadOptionsParams {
|
|
17
|
-
// inputValue: string;
|
|
18
|
-
// loadedOptions: OptionType[];
|
|
19
|
-
// page: number;
|
|
20
|
-
// }
|
|
21
|
-
|
|
22
|
-
interface LoadOptionsResponse {
|
|
23
|
-
options: OptionType[];
|
|
24
|
-
hasMore: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface CustomSelectProps {
|
|
28
|
-
label: string;
|
|
29
|
-
value?: any;
|
|
30
|
-
setValue: (option: OptionType | null) => void;
|
|
31
|
-
required?: boolean;
|
|
32
|
-
disabled?: boolean;
|
|
33
|
-
isMulti?: boolean;
|
|
34
|
-
isAsync?: boolean;
|
|
35
|
-
isCreatable?: boolean;
|
|
36
|
-
options?: OptionType[];
|
|
37
|
-
placeholder?: string;
|
|
38
|
-
loadOptions?: (
|
|
39
|
-
inputValue: string,
|
|
40
|
-
loadedOptions: OptionType[],
|
|
41
|
-
params: { page: number }
|
|
42
|
-
) => Promise<LoadOptionsResponse>;
|
|
43
|
-
onCreateOption?: (inputValue: string) => Promise<OptionType>;
|
|
44
|
-
isClearable: boolean;
|
|
45
|
-
onClearInput?: () => void;
|
|
46
|
-
helperText?: string;
|
|
47
|
-
errorText?: string;
|
|
48
|
-
error?: boolean;
|
|
49
|
-
menuPlacement?: IMenuPlacement;
|
|
50
|
-
className?: string;
|
|
51
|
-
// minWidth?: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const Select: FC<CustomSelectProps> = ({
|
|
55
|
-
label,
|
|
56
|
-
value,
|
|
57
|
-
setValue,
|
|
58
|
-
onCreateOption,
|
|
59
|
-
required,
|
|
60
|
-
disabled,
|
|
61
|
-
options = [],
|
|
62
|
-
loadOptions,
|
|
63
|
-
placeholder = 'Select...',
|
|
64
|
-
isMulti,
|
|
65
|
-
isAsync = false,
|
|
66
|
-
isCreatable = false,
|
|
67
|
-
isClearable = true,
|
|
68
|
-
onClearInput,
|
|
69
|
-
helperText,
|
|
70
|
-
errorText,
|
|
71
|
-
error,
|
|
72
|
-
menuPlacement = 'bottom-left',
|
|
73
|
-
className,
|
|
74
|
-
// minWidth,
|
|
75
|
-
}) => {
|
|
76
|
-
const [inputValue, setInputValue] = useState<string | null>(null);
|
|
77
|
-
const [dropdownVisible, setDropdownVisible] = useState(false);
|
|
78
|
-
const [filteredOptions, setFilteredOptions] = useState<OptionType[]>([]);
|
|
79
|
-
const [page, setPage] = useState(0);
|
|
80
|
-
const [index, setIndex] = useState<number | null>(null);
|
|
81
|
-
const [hasMore, setHasMore] = useState(true);
|
|
82
|
-
|
|
83
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
84
|
-
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
85
|
-
|
|
86
|
-
const menuStyle = useMenuPosition({
|
|
87
|
-
isOpen: dropdownVisible,
|
|
88
|
-
placement: menuPlacement,
|
|
89
|
-
containerRef,
|
|
90
|
-
menuRef: dropdownRef,
|
|
91
|
-
withMinWidth: true,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
if (!isAsync) {
|
|
96
|
-
const filtered = inputValue
|
|
97
|
-
? options.filter(opt =>
|
|
98
|
-
opt.label.toLowerCase().includes(inputValue.toLowerCase())
|
|
99
|
-
)
|
|
100
|
-
: options;
|
|
101
|
-
setFilteredOptions(filtered);
|
|
102
|
-
}
|
|
103
|
-
}, [inputValue, options, isAsync]);
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (isAsync && loadOptions) {
|
|
107
|
-
loadOptions(inputValue ?? '', filteredOptions, { page }).then(
|
|
108
|
-
({ options: newOptions, hasMore }) => {
|
|
109
|
-
setFilteredOptions(prev => [...prev, ...newOptions]);
|
|
110
|
-
setHasMore(hasMore);
|
|
111
|
-
}
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
-
}, [page]);
|
|
116
|
-
|
|
117
|
-
const handleOptionClick = (option: OptionType) => {
|
|
118
|
-
setValue(option);
|
|
119
|
-
setInputValue(null);
|
|
120
|
-
setTimeout(() => {
|
|
121
|
-
setIndex(null);
|
|
122
|
-
// setDropdownVisible(false);
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const handleInput = (e: ChangeEvent<HTMLInputElement>) => {
|
|
127
|
-
const inputValue = e.target.value;
|
|
128
|
-
setInputValue(
|
|
129
|
-
e.target.defaultValue === value?.label
|
|
130
|
-
? // @ts-ignore
|
|
131
|
-
(e.nativeEvent?.data ?? '')
|
|
132
|
-
: inputValue
|
|
133
|
-
);
|
|
134
|
-
// setFilteredOptions([]);
|
|
135
|
-
setPage(0);
|
|
136
|
-
if (isAsync && loadOptions) {
|
|
137
|
-
loadOptions(inputValue, [], { page: 0 }).then(({ options, hasMore }) => {
|
|
138
|
-
setFilteredOptions(options);
|
|
139
|
-
setHasMore(hasMore);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const handleScroll = (e: UIEvent<HTMLDivElement>) => {
|
|
145
|
-
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
|
146
|
-
if (scrollHeight - scrollTop === clientHeight && hasMore) {
|
|
147
|
-
setPage(prev => prev + 1);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const handleCreate = async () => {
|
|
152
|
-
if (onCreateOption && inputValue?.trim()) {
|
|
153
|
-
const newOption = await onCreateOption(inputValue);
|
|
154
|
-
setFilteredOptions(prev => [newOption, ...prev]);
|
|
155
|
-
setValue(newOption);
|
|
156
|
-
setInputValue(null);
|
|
157
|
-
setDropdownVisible(false);
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<div
|
|
163
|
-
className="customSelect"
|
|
164
|
-
onFocus={() => setDropdownVisible(true)}
|
|
165
|
-
onBlur={() => {
|
|
166
|
-
setDropdownVisible(false);
|
|
167
|
-
setInputValue(null);
|
|
168
|
-
}}
|
|
169
|
-
>
|
|
170
|
-
<InputWrapper
|
|
171
|
-
label={label}
|
|
172
|
-
required={required}
|
|
173
|
-
disabled={disabled}
|
|
174
|
-
// endText={endText}
|
|
175
|
-
// endButton={endButton}
|
|
176
|
-
onClearInput={
|
|
177
|
-
isClearable && value
|
|
178
|
-
? () => {
|
|
179
|
-
setValue(null);
|
|
180
|
-
setInputValue('');
|
|
181
|
-
// setSelectedOption(null);
|
|
182
|
-
}
|
|
183
|
-
: undefined
|
|
184
|
-
}
|
|
185
|
-
helperText={helperText}
|
|
186
|
-
errorText={errorText}
|
|
187
|
-
error={error}
|
|
188
|
-
inputFieldRef={containerRef}
|
|
189
|
-
className={className}
|
|
190
|
-
>
|
|
191
|
-
<input
|
|
192
|
-
value={inputValue ?? value?.label ?? ''}
|
|
193
|
-
onChange={handleInput}
|
|
194
|
-
required={required}
|
|
195
|
-
disabled={disabled}
|
|
196
|
-
// onFocus={e => e.currentTarget.select()}
|
|
197
|
-
// autoFocus={autoFocus}
|
|
198
|
-
placeholder={value?.label || placeholder}
|
|
199
|
-
onKeyDown={e => {
|
|
200
|
-
if (e.key === 'ArrowUp') {
|
|
201
|
-
e.preventDefault();
|
|
202
|
-
if (index === null || index === 0) {
|
|
203
|
-
setIndex(filteredOptions.length - 1);
|
|
204
|
-
} else {
|
|
205
|
-
setIndex(prev => prev! - 1);
|
|
206
|
-
}
|
|
207
|
-
} else if (e.key === 'ArrowDown') {
|
|
208
|
-
e.preventDefault();
|
|
209
|
-
if (index === null || index === filteredOptions.length - 1) {
|
|
210
|
-
setIndex(0);
|
|
211
|
-
} else {
|
|
212
|
-
setIndex(prev => prev! + 1);
|
|
213
|
-
dropdownRef.current?.scrollTo({ top: 20 * ((index ?? 0) + 1) });
|
|
214
|
-
}
|
|
215
|
-
} else if (e.key === 'Enter' && index !== null) {
|
|
216
|
-
e.preventDefault();
|
|
217
|
-
e.currentTarget.blur();
|
|
218
|
-
handleOptionClick(filteredOptions[index]);
|
|
219
|
-
} else if (e.key === 'Escape') {
|
|
220
|
-
e.preventDefault();
|
|
221
|
-
e.currentTarget.blur();
|
|
222
|
-
}
|
|
223
|
-
}}
|
|
224
|
-
/>
|
|
225
|
-
</InputWrapper>
|
|
226
|
-
|
|
227
|
-
{dropdownVisible
|
|
228
|
-
? createPortal(
|
|
229
|
-
<div
|
|
230
|
-
ref={dropdownRef}
|
|
231
|
-
className="select-dropdown"
|
|
232
|
-
onScroll={handleScroll}
|
|
233
|
-
style={menuStyle}
|
|
234
|
-
>
|
|
235
|
-
{filteredOptions.map((option, i) => (
|
|
236
|
-
<div
|
|
237
|
-
key={i}
|
|
238
|
-
className={clsx('option', { hover: index === i })}
|
|
239
|
-
onMouseDown={() => handleOptionClick(option)}
|
|
240
|
-
>
|
|
241
|
-
{option.label}
|
|
242
|
-
</div>
|
|
243
|
-
))}
|
|
244
|
-
{!filteredOptions.length && <div className="option">No options</div>}
|
|
245
|
-
{isCreatable &&
|
|
246
|
-
inputValue &&
|
|
247
|
-
!filteredOptions.some(opt => opt.label === inputValue) && (
|
|
248
|
-
<div className="option createOption" onClick={handleCreate}>
|
|
249
|
-
{`Create ${inputValue}`}
|
|
250
|
-
</div>
|
|
251
|
-
)}
|
|
252
|
-
</div>,
|
|
253
|
-
rootDir
|
|
254
|
-
)
|
|
255
|
-
: null}
|
|
256
|
-
</div>
|
|
257
|
-
);
|
|
258
|
-
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
.customSelect {
|
|
2
|
-
position: relative;
|
|
3
|
-
width: 300px;
|
|
4
|
-
font-family: sans-serif;
|
|
5
|
-
|
|
6
|
-
.inputWrapper {
|
|
7
|
-
input {
|
|
8
|
-
width: 100%;
|
|
9
|
-
padding: 8px 12px;
|
|
10
|
-
border: 1px solid #ccc;
|
|
11
|
-
border-radius: 4px;
|
|
12
|
-
font-size: 14px;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.select-dropdown {
|
|
18
|
-
position: absolute;
|
|
19
|
-
z-index: 1000;
|
|
20
|
-
// width: 100%;
|
|
21
|
-
min-width: 200px;
|
|
22
|
-
max-height: 200px;
|
|
23
|
-
overflow-y: auto;
|
|
24
|
-
background: #fff;
|
|
25
|
-
border: var(--border);
|
|
26
|
-
border-radius: 8px;
|
|
27
|
-
// border-radius: 0 0 8px 8px;
|
|
28
|
-
margin-top: 2px;
|
|
29
|
-
.option {
|
|
30
|
-
padding: 8px 12px;
|
|
31
|
-
cursor: pointer;
|
|
32
|
-
|
|
33
|
-
&.hover,
|
|
34
|
-
&:hover {
|
|
35
|
-
background-color: #f5f5f5;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
.createOption {
|
|
39
|
-
font-weight: bold;
|
|
40
|
-
color: #007bff;
|
|
41
|
-
}
|
|
42
|
-
}
|