@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.
Files changed (47) hide show
  1. package/README.md +8 -13
  2. package/dist/components/input/StackedMultiSelect/index.d.ts +1 -0
  3. package/dist/components/input/StackedSelect/index.d.ts +1 -0
  4. package/dist/components/input/components/dropdown/index.d.ts +2 -0
  5. package/dist/components/input/index.d.ts +2 -1
  6. package/dist/ui-core.cjs.development.js +197 -113
  7. package/dist/ui-core.cjs.development.js.map +1 -1
  8. package/dist/ui-core.cjs.production.min.js +1 -1
  9. package/dist/ui-core.cjs.production.min.js.map +1 -1
  10. package/dist/ui-core.esm.js +198 -114
  11. package/dist/ui-core.esm.js.map +1 -1
  12. package/package.json +6 -2
  13. package/src/components/icons/checkmark/index.tsx +1 -1
  14. package/src/components/icons/chevron/down/index.tsx +7 -1
  15. package/src/components/icons/chevron/right/index.tsx +1 -1
  16. package/src/components/icons/clock/index.tsx +1 -1
  17. package/src/components/icons/dropdown/index.tsx +5 -1
  18. package/src/components/icons/error/index.tsx +1 -1
  19. package/src/components/icons/file/fill/index.tsx +1 -1
  20. package/src/components/icons/file/outline/index.tsx +1 -1
  21. package/src/components/icons/folder/add/fill/index.tsx +1 -1
  22. package/src/components/icons/folder/add/outline/index.tsx +1 -1
  23. package/src/components/icons/folder/outline/index.tsx +1 -1
  24. package/src/components/icons/group/index.tsx +1 -1
  25. package/src/components/icons/home/index.tsx +1 -1
  26. package/src/components/icons/image/index.tsx +1 -1
  27. package/src/components/icons/link/index.tsx +1 -1
  28. package/src/components/icons/menu/index.tsx +1 -1
  29. package/src/components/icons/microsoft/index.tsx +1 -1
  30. package/src/components/icons/neutral/index.tsx +3 -1
  31. package/src/components/icons/page/index.tsx +1 -1
  32. package/src/components/icons/positive/index.tsx +1 -1
  33. package/src/components/icons/question/index.tsx +1 -1
  34. package/src/components/icons/search/index.tsx +1 -1
  35. package/src/components/icons/services/index.tsx +1 -1
  36. package/src/components/icons/settings/index.tsx +3 -1
  37. package/src/components/icons/table/fill/index.tsx +1 -1
  38. package/src/components/icons/table/outline/index.tsx +1 -1
  39. package/src/components/icons/task/index.tsx +1 -1
  40. package/src/components/icons/trash/index.tsx +1 -1
  41. package/src/components/icons/video/index.tsx +1 -1
  42. package/src/components/icons/warning/index.tsx +1 -1
  43. package/src/components/input/Input.stories.tsx +9 -0
  44. package/src/components/input/StackedMultiSelect/index.tsx +296 -277
  45. package/src/components/input/StackedSelect/index.tsx +46 -33
  46. package/src/components/input/components/dropdown/index.tsx +61 -12
  47. 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 [debouncedSearchValue, setDebouncedSearchValue] = useState('');
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, () => setIsFocussed(false));
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 = options[0].value === 'section_header' ? 1 : 0;
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
- options[optionIndex - 1] &&
112
- options[optionIndex - 1].value === 'section_header'
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 < options.length
134
+ optionIndex < filteredOptions.length
127
135
  ) {
128
136
  const incrementValue =
129
- options[optionIndex + 1] &&
130
- options[optionIndex + 1].value === 'section_header'
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 = options.find((_, idx) => optionIndex === idx);
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
- const updateSearchValue = useMemo(() => {
183
- return debounce(val => {
184
- setSearchValue(val);
185
- }, 1000);
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 update = (value: string) => {
189
- updateSearchValue(value);
190
- setDebouncedSearchValue(value);
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="pointer"
202
- color="transparent"
215
+ cursor={isFocussed ? 'default' : 'pointer'}
216
+ color={loadingOptions ? 'transparent' : 'inital'}
203
217
  fontSize="13px"
204
- textShadow={`0 0 0 ${colors.label.primary.light}`}
205
- value={selectedOption}
206
- disabled={disabled}
218
+ value={isFocussed ? searchValue : selectedOption}
207
219
  autoComplete="off"
208
- onChange={e => update(debouncedSearchValue.concat(e.target.value))}
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="16px" disabled={disabled} />
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={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
- overflowY="auto"
131
+ position="absolute"
98
132
  px="8px"
99
133
  py="4px"
100
- position="absolute"
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
- {DropdownContent}
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':