@xqmsg/ui-core 0.24.2 → 0.24.4
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 +8 -13
- package/dist/components/input/StackedMultiSelect/index.d.ts +1 -0
- package/dist/components/input/StackedSelect/index.d.ts +1 -0
- package/dist/components/input/components/dropdown/index.d.ts +2 -0
- package/dist/components/input/index.d.ts +2 -1
- package/dist/ui-core.cjs.development.js +197 -113
- package/dist/ui-core.cjs.development.js.map +1 -1
- package/dist/ui-core.cjs.production.min.js +1 -1
- package/dist/ui-core.cjs.production.min.js.map +1 -1
- package/dist/ui-core.esm.js +198 -114
- package/dist/ui-core.esm.js.map +1 -1
- package/package.json +6 -2
- package/src/components/icons/checkmark/index.tsx +1 -1
- package/src/components/icons/chevron/down/index.tsx +7 -1
- package/src/components/icons/chevron/right/index.tsx +1 -1
- package/src/components/icons/clock/index.tsx +1 -1
- package/src/components/icons/dropdown/index.tsx +5 -1
- package/src/components/icons/error/index.tsx +1 -1
- package/src/components/icons/file/fill/index.tsx +1 -1
- package/src/components/icons/file/outline/index.tsx +1 -1
- package/src/components/icons/folder/add/fill/index.tsx +1 -1
- package/src/components/icons/folder/add/outline/index.tsx +1 -1
- package/src/components/icons/folder/outline/index.tsx +1 -1
- package/src/components/icons/group/index.tsx +1 -1
- package/src/components/icons/home/index.tsx +1 -1
- package/src/components/icons/image/index.tsx +1 -1
- package/src/components/icons/link/index.tsx +1 -1
- package/src/components/icons/menu/index.tsx +1 -1
- package/src/components/icons/microsoft/index.tsx +1 -1
- package/src/components/icons/neutral/index.tsx +3 -1
- package/src/components/icons/page/index.tsx +1 -1
- package/src/components/icons/positive/index.tsx +1 -1
- package/src/components/icons/question/index.tsx +1 -1
- package/src/components/icons/search/index.tsx +1 -1
- package/src/components/icons/services/index.tsx +1 -1
- package/src/components/icons/settings/index.tsx +3 -1
- package/src/components/icons/table/fill/index.tsx +1 -1
- package/src/components/icons/table/outline/index.tsx +1 -1
- package/src/components/icons/task/index.tsx +1 -1
- package/src/components/icons/trash/index.tsx +1 -1
- package/src/components/icons/video/index.tsx +1 -1
- package/src/components/icons/warning/index.tsx +1 -1
- package/src/components/input/Input.stories.tsx +9 -0
- package/src/components/input/StackedMultiSelect/index.tsx +296 -277
- package/src/components/input/StackedSelect/index.tsx +46 -33
- package/src/components/input/components/dropdown/index.tsx +61 -12
- package/src/components/input/index.tsx +4 -0
|
@@ -2,18 +2,15 @@ import React, {
|
|
|
2
2
|
KeyboardEventHandler,
|
|
3
3
|
useEffect,
|
|
4
4
|
useRef,
|
|
5
|
-
useMemo,
|
|
6
5
|
useState,
|
|
7
6
|
} from 'react';
|
|
8
7
|
import { Box, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
|
|
9
8
|
import { FieldOptions } from '../InputTypes';
|
|
10
9
|
import { StackedInputProps } from '../StackedInput/StackedInput';
|
|
11
|
-
import colors from '../../../theme/foundations/colors';
|
|
12
10
|
import { UseFormSetValue, FieldValues, Control } from 'react-hook-form';
|
|
13
11
|
import { Dropdown } from '../components/dropdown';
|
|
14
12
|
import { useOnClickOutside } from '../../../hooks/useOnOutsideClick';
|
|
15
13
|
import { Dropdown as DropdownIcon } from '../../icons/dropdown';
|
|
16
|
-
import { debounce } from 'lodash';
|
|
17
14
|
|
|
18
15
|
export interface StackedSelectProps extends StackedInputProps {
|
|
19
16
|
options: FieldOptions;
|
|
@@ -21,6 +18,7 @@ export interface StackedSelectProps extends StackedInputProps {
|
|
|
21
18
|
setValue: UseFormSetValue<FieldValues>;
|
|
22
19
|
control: Control<FieldValues, any>;
|
|
23
20
|
handleOnChange: (value?: string) => void;
|
|
21
|
+
loadingOptions?: boolean;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
/**
|
|
@@ -37,6 +35,7 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
37
35
|
disabled,
|
|
38
36
|
value,
|
|
39
37
|
fullOptions,
|
|
38
|
+
loadingOptions,
|
|
40
39
|
...props
|
|
41
40
|
},
|
|
42
41
|
_ref
|
|
@@ -51,7 +50,9 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
51
50
|
const [optionIndex, setOptionIndex] = useState<number | null>(null);
|
|
52
51
|
const [position, setPosition] = useState<'top' | 'bottom'>('top');
|
|
53
52
|
const [searchValue, setSearchValue] = useState('');
|
|
54
|
-
const [
|
|
53
|
+
const [filteredOptions, setFilteredOptions] = useState<FieldOptions>(
|
|
54
|
+
options
|
|
55
|
+
);
|
|
55
56
|
|
|
56
57
|
const boundingClientRect = dropdownRef.current?.getBoundingClientRect() as DOMRect;
|
|
57
58
|
|
|
@@ -70,9 +71,12 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
70
71
|
(fullOptions || options).find(option => option.value === value)
|
|
71
72
|
?.label ?? ''
|
|
72
73
|
);
|
|
73
|
-
}, [fullOptions, value]);
|
|
74
|
+
}, [fullOptions, options, value]);
|
|
74
75
|
|
|
75
|
-
useOnClickOutside(dropdownRef, () =>
|
|
76
|
+
useOnClickOutside(dropdownRef, () => {
|
|
77
|
+
setIsFocussed(false);
|
|
78
|
+
setSearchValue('');
|
|
79
|
+
});
|
|
76
80
|
|
|
77
81
|
const handleOnSelectItem = (option: {
|
|
78
82
|
label: string;
|
|
@@ -85,10 +89,14 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
85
89
|
setValue(name as string, option.value);
|
|
86
90
|
setSelectedOption(option.label);
|
|
87
91
|
setIsFocussed(false);
|
|
92
|
+
setSearchValue('');
|
|
88
93
|
};
|
|
89
94
|
|
|
90
95
|
const handleOnKeyDown: KeyboardEventHandler<HTMLInputElement> = e => {
|
|
91
|
-
const initialOptionIndex =
|
|
96
|
+
const initialOptionIndex =
|
|
97
|
+
filteredOptions.length && filteredOptions[0].value === 'section_header'
|
|
98
|
+
? 1
|
|
99
|
+
: 0;
|
|
92
100
|
|
|
93
101
|
if (
|
|
94
102
|
!isFocussed &&
|
|
@@ -98,7 +106,7 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
98
106
|
return setOptionIndex(initialOptionIndex);
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
if (isFocussed) {
|
|
109
|
+
if (isFocussed && filteredOptions.length > 0) {
|
|
102
110
|
if (
|
|
103
111
|
optionIndex === null &&
|
|
104
112
|
(e.key === 'Enter' || e.key === 'ArrowUp' || e.key === 'ArrowDown')
|
|
@@ -108,8 +116,8 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
108
116
|
|
|
109
117
|
if (e.key === 'ArrowUp' && optionIndex !== null && optionIndex > 0) {
|
|
110
118
|
const incrementValue =
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
filteredOptions[optionIndex - 1] &&
|
|
120
|
+
filteredOptions[optionIndex - 1].value === 'section_header'
|
|
113
121
|
? 2
|
|
114
122
|
: 1;
|
|
115
123
|
setOptionIndex(optionIndex - incrementValue);
|
|
@@ -123,11 +131,11 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
123
131
|
if (
|
|
124
132
|
e.key === 'ArrowDown' &&
|
|
125
133
|
optionIndex !== null &&
|
|
126
|
-
optionIndex <
|
|
134
|
+
optionIndex < filteredOptions.length
|
|
127
135
|
) {
|
|
128
136
|
const incrementValue =
|
|
129
|
-
|
|
130
|
-
|
|
137
|
+
filteredOptions[optionIndex + 1] &&
|
|
138
|
+
filteredOptions[optionIndex + 1].value === 'section_header'
|
|
131
139
|
? 2
|
|
132
140
|
: 1;
|
|
133
141
|
setOptionIndex(optionIndex + incrementValue);
|
|
@@ -139,7 +147,7 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
if (e.key === 'Enter' && optionIndex !== null) {
|
|
142
|
-
const option =
|
|
150
|
+
const option = filteredOptions.find((_, idx) => optionIndex === idx);
|
|
143
151
|
if (!option) return;
|
|
144
152
|
|
|
145
153
|
if (handleOnChange) {
|
|
@@ -173,21 +181,27 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
173
181
|
top: idx * 24,
|
|
174
182
|
behavior: 'smooth',
|
|
175
183
|
});
|
|
176
|
-
|
|
177
|
-
setSearchValue('');
|
|
178
|
-
setDebouncedSearchValue('');
|
|
179
184
|
}
|
|
180
185
|
}, [options, searchValue]);
|
|
181
186
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
setFilteredOptions(
|
|
189
|
+
options.filter(element => {
|
|
190
|
+
return element.label
|
|
191
|
+
.toLowerCase()
|
|
192
|
+
.includes(searchValue.toLowerCase());
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
}, [options, searchValue]);
|
|
187
196
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
198
|
+
const initialOptionIndex =
|
|
199
|
+
filteredOptions.length && filteredOptions[0]?.value === 'section_header'
|
|
200
|
+
? 1
|
|
201
|
+
: 0;
|
|
202
|
+
setOptionIndex(initialOptionIndex);
|
|
203
|
+
const { value } = e.target;
|
|
204
|
+
setSearchValue(value);
|
|
191
205
|
};
|
|
192
206
|
|
|
193
207
|
return (
|
|
@@ -198,21 +212,19 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
198
212
|
{...props}
|
|
199
213
|
ref={_ref}
|
|
200
214
|
onClick={() => setIsFocussed(!isFocussed)}
|
|
201
|
-
cursor=
|
|
202
|
-
color=
|
|
215
|
+
cursor={isFocussed ? 'default' : 'pointer'}
|
|
216
|
+
color={loadingOptions ? 'transparent' : 'inital'}
|
|
203
217
|
fontSize="13px"
|
|
204
|
-
|
|
205
|
-
value={selectedOption}
|
|
206
|
-
disabled={disabled}
|
|
218
|
+
value={isFocussed ? searchValue : selectedOption}
|
|
207
219
|
autoComplete="off"
|
|
208
|
-
onChange={
|
|
220
|
+
onChange={handleInput}
|
|
209
221
|
onKeyDown={handleOnKeyDown}
|
|
210
222
|
/>
|
|
211
223
|
<InputRightElement
|
|
212
224
|
cursor={disabled ? 'not-allowed' : 'pointer'}
|
|
213
225
|
onClick={() => !disabled && setIsFocussed(!isFocussed)}
|
|
214
226
|
>
|
|
215
|
-
<DropdownIcon boxSize="
|
|
227
|
+
<DropdownIcon boxSize="12px" disabled={disabled} />
|
|
216
228
|
</InputRightElement>
|
|
217
229
|
</InputGroup>
|
|
218
230
|
{isFocussed && (
|
|
@@ -220,8 +232,9 @@ const StackedSelect = React.forwardRef<HTMLInputElement, StackedSelectProps>(
|
|
|
220
232
|
position={position}
|
|
221
233
|
dropdownRef={dropdownMenuRef}
|
|
222
234
|
onSelectItem={handleOnSelectItem}
|
|
223
|
-
options={
|
|
235
|
+
options={filteredOptions}
|
|
224
236
|
optionIndex={optionIndex}
|
|
237
|
+
loading={loadingOptions}
|
|
225
238
|
/>
|
|
226
239
|
)}
|
|
227
240
|
</Box>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { RefObject, useMemo } from 'react';
|
|
2
|
-
import { Box, Flex } from '@chakra-ui/react';
|
|
2
|
+
import { Box, Flex, Spinner } from '@chakra-ui/react';
|
|
3
3
|
import colors from '../../../../../src/theme/foundations/colors';
|
|
4
4
|
import { FieldOption, FieldOptions } from '../../InputTypes';
|
|
5
5
|
|
|
@@ -9,6 +9,8 @@ export interface DropdownProps {
|
|
|
9
9
|
dropdownRef: RefObject<HTMLDivElement>;
|
|
10
10
|
position: 'top' | 'bottom';
|
|
11
11
|
optionIndex?: number | null;
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
loading?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -20,8 +22,45 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|
|
20
22
|
dropdownRef,
|
|
21
23
|
position,
|
|
22
24
|
optionIndex,
|
|
25
|
+
children,
|
|
26
|
+
loading = false,
|
|
23
27
|
}) => {
|
|
24
28
|
const DropdownContent = useMemo(() => {
|
|
29
|
+
if (loading) {
|
|
30
|
+
return (
|
|
31
|
+
<Box
|
|
32
|
+
borderRadius="inherit"
|
|
33
|
+
fontSize="13px"
|
|
34
|
+
px="8px"
|
|
35
|
+
py="4px"
|
|
36
|
+
width="100%"
|
|
37
|
+
color={colors.label.primary.light}
|
|
38
|
+
bg="inherit"
|
|
39
|
+
whiteSpace="nowrap"
|
|
40
|
+
>
|
|
41
|
+
<Flex alignItems="center">
|
|
42
|
+
Loading
|
|
43
|
+
<Spinner size="xs" opacity={0.5} ml={2} />
|
|
44
|
+
</Flex>
|
|
45
|
+
</Box>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (!loading && (!options || options.length === 0)) {
|
|
49
|
+
return (
|
|
50
|
+
<Box
|
|
51
|
+
borderRadius="inherit"
|
|
52
|
+
fontSize="13px"
|
|
53
|
+
px="8px"
|
|
54
|
+
py="4px"
|
|
55
|
+
width="100%"
|
|
56
|
+
color={colors.label.primary.light}
|
|
57
|
+
bg="inherit"
|
|
58
|
+
whiteSpace="nowrap"
|
|
59
|
+
>
|
|
60
|
+
No options
|
|
61
|
+
</Box>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
25
64
|
return options.map((option, idx) => (
|
|
26
65
|
<Box key={idx} width="100%" role="combobox">
|
|
27
66
|
{option.value === 'section_header' &&
|
|
@@ -79,13 +118,8 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|
|
79
118
|
));
|
|
80
119
|
}, [onSelectItem, optionIndex, options]);
|
|
81
120
|
|
|
82
|
-
if (!options) return null;
|
|
83
|
-
|
|
84
121
|
return (
|
|
85
122
|
<Flex
|
|
86
|
-
flexDirection="column"
|
|
87
|
-
ref={dropdownRef}
|
|
88
|
-
scrollMargin="15px"
|
|
89
123
|
bg={colors.fill.light.quaternary}
|
|
90
124
|
backdropFilter="auto"
|
|
91
125
|
backdropBlur="64px"
|
|
@@ -94,18 +128,33 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|
|
94
128
|
borderColor={colors.fill.light.tertiary}
|
|
95
129
|
mt="3px"
|
|
96
130
|
maxH="240px"
|
|
97
|
-
|
|
131
|
+
position="absolute"
|
|
98
132
|
px="8px"
|
|
99
133
|
py="4px"
|
|
100
|
-
|
|
101
|
-
top={position === 'top' ? 26 : undefined}
|
|
102
|
-
bottom={position === 'bottom' ? 30 : undefined}
|
|
103
|
-
width="fit-content"
|
|
134
|
+
overflow="hidden"
|
|
104
135
|
minWidth="100%"
|
|
105
136
|
zIndex={100}
|
|
106
137
|
tabIndex={-2000}
|
|
138
|
+
alignItems="flex-start"
|
|
139
|
+
flexDirection="column"
|
|
140
|
+
top={position === 'top' ? 26 : undefined}
|
|
141
|
+
bottom={position === 'bottom' ? 30 : undefined}
|
|
107
142
|
>
|
|
108
|
-
{
|
|
143
|
+
{children && (
|
|
144
|
+
<Box width="100%" mb={2} mt={1}>
|
|
145
|
+
{children}
|
|
146
|
+
</Box>
|
|
147
|
+
)}
|
|
148
|
+
<Flex
|
|
149
|
+
width="fit-content"
|
|
150
|
+
overflowY="auto"
|
|
151
|
+
flexDirection="column"
|
|
152
|
+
ref={dropdownRef}
|
|
153
|
+
minWidth="100%"
|
|
154
|
+
scrollMargin="15px"
|
|
155
|
+
>
|
|
156
|
+
{DropdownContent}
|
|
157
|
+
</Flex>
|
|
109
158
|
</Flex>
|
|
110
159
|
);
|
|
111
160
|
};
|
|
@@ -51,6 +51,7 @@ export interface InputProps<T extends FieldValues = FieldValues>
|
|
|
51
51
|
rightElement?: React.ReactNode;
|
|
52
52
|
variant?: string;
|
|
53
53
|
separators?: string[];
|
|
54
|
+
loadingOptions?: boolean;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -84,6 +85,7 @@ export function Input<T extends FieldValues>({
|
|
|
84
85
|
setError,
|
|
85
86
|
clearErrors,
|
|
86
87
|
separators,
|
|
88
|
+
loadingOptions = false,
|
|
87
89
|
}: InputProps<T>) {
|
|
88
90
|
function selectedInputField<T extends Element = Element>(
|
|
89
91
|
onChange: ((e: ChangeEvent<T>) => void) | ((v?: string) => void),
|
|
@@ -150,6 +152,7 @@ export function Input<T extends FieldValues>({
|
|
|
150
152
|
defaultValue={defaultValue}
|
|
151
153
|
placeholder={placeholder}
|
|
152
154
|
fullOptions={fullOptions}
|
|
155
|
+
loadingOptions={loadingOptions}
|
|
153
156
|
/>
|
|
154
157
|
);
|
|
155
158
|
case 'textarea':
|
|
@@ -207,6 +210,7 @@ export function Input<T extends FieldValues>({
|
|
|
207
210
|
setError={setError as UseFormSetError<FieldValues>}
|
|
208
211
|
clearErrors={clearErrors as UseFormClearErrors<FieldValues>}
|
|
209
212
|
placeholder={placeholder}
|
|
213
|
+
loadingOptions={loadingOptions}
|
|
210
214
|
/>
|
|
211
215
|
);
|
|
212
216
|
case 'pilled-text':
|