@bitrise/bitkit 13.317.0 → 13.319.0
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/package.json +1 -1
- package/src/Components/DatePicker/DatePicker.tsx +59 -54
- package/src/Components/Drawer/Drawer.tsx +19 -5
- package/src/Components/Dropdown/DropdownOption.tsx +1 -1
- package/src/Components/Filter/Desktop/Filter.tsx +92 -0
- package/src/Components/Filter/Desktop/FilterAdd/FilterAdd.tsx +89 -0
- package/src/Components/Filter/{FilterDate → Desktop/FilterDate}/FilterDate.tsx +21 -10
- package/src/Components/Filter/{FilterForm → Desktop}/FilterForm.tsx +24 -115
- package/src/Components/Filter/{FilterItem → Desktop}/FilterItem.tsx +1 -1
- package/src/Components/Filter/{FilterSwitch → Desktop/FilterSwitch}/FilterSwitch.theme.ts +1 -1
- package/src/Components/Filter/{FilterSwitch → Desktop/FilterSwitch}/FilterSwitch.tsx +4 -4
- package/src/Components/Filter/{FilterSwitchAdapter → Desktop/FilterSwitch}/FilterSwitchAdapter.tsx +5 -5
- package/src/Components/Filter/Filter.storyData.ts +14 -1
- package/src/Components/Filter/Filter.tsx +16 -106
- package/src/Components/Filter/Filter.types.ts +34 -1
- package/src/Components/Filter/Filter.utils.ts +13 -0
- package/src/Components/Filter/Mobile/DateSelectOption.tsx +53 -0
- package/src/Components/Filter/Mobile/Filter.tsx +57 -0
- package/src/Components/Filter/Mobile/FilterAdd.tsx +97 -0
- package/src/Components/Filter/Mobile/FilterDrawer.tsx +96 -0
- package/src/Components/Filter/Mobile/FilterForm.tsx +236 -0
- package/src/Components/Filter/Mobile/FilterItem.tsx +95 -0
- package/src/Components/Filter/Mobile/MultiSelectOptions.tsx +69 -0
- package/src/Components/Filter/Mobile/SingleSelectOptions.tsx +136 -0
- package/src/Components/Filter/hooks/useFilterAdd.ts +68 -0
- package/src/Components/Filter/hooks/useFilterForm.ts +131 -0
- package/src/Components/Filter/hooks/useIsScrollable.ts +35 -0
- package/src/Components/Filter/hooks/useListBox.ts +66 -0
- package/src/Components/Form/Input/Input.theme.ts +27 -11
- package/src/Components/Form/Input/Input.tsx +4 -1
- package/src/Components/SearchInput/SearchInput.tsx +3 -2
- package/src/Components/components.theme.ts +1 -1
- package/src/index.ts +3 -4
- package/src/Components/Filter/FilterAdd/FilterAdd.tsx +0 -111
- /package/src/Components/Filter/{FilterSearch → Desktop}/FilterSearch.tsx +0 -0
- /package/src/Components/Filter/{FilterSwitch → Desktop/FilterSwitch}/FilterSwitchGroup.tsx +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { FormEvent, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { isEqual, useDebounce } from '../../../utils/utils';
|
|
3
|
+
import { useFilterContext } from '../Filter.context';
|
|
4
|
+
import { FilterFormProps, FilterOptions, FilterOptionsMap, FilterValue } from '../Filter.types';
|
|
5
|
+
|
|
6
|
+
export const MAX_ITEMS = 100;
|
|
7
|
+
|
|
8
|
+
const useFilterForm = (props: Omit<FilterFormProps, 'onCancel'>) => {
|
|
9
|
+
const { category, onChange } = props;
|
|
10
|
+
|
|
11
|
+
const { data, state } = useFilterContext();
|
|
12
|
+
|
|
13
|
+
const categoryData = data[category] || {};
|
|
14
|
+
const { isPatternEnabled, preserveOptionOrder, isInitialLoading, onAsyncSearch, options, optionsMap } = categoryData;
|
|
15
|
+
|
|
16
|
+
const value = useMemo(() => state[category] || [], [category, state]);
|
|
17
|
+
|
|
18
|
+
const [selected, setSelected] = useState<FilterValue>(value);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
setSelected(value);
|
|
22
|
+
}, [value]);
|
|
23
|
+
|
|
24
|
+
const [mode, setMode] = useState<'manually' | 'pattern'>(
|
|
25
|
+
isPatternEnabled && value?.length && value[0].includes('*') ? 'pattern' : 'manually',
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const [searchValue, setSearchValue] = useState<string>('');
|
|
29
|
+
const debouncedSearchValue = useDebounce<string>(searchValue, 1000);
|
|
30
|
+
|
|
31
|
+
const [isLoading, setLoading] = useState(Boolean(isInitialLoading));
|
|
32
|
+
const [foundOptions, setFoundOptions] = useState<FilterOptions>([]);
|
|
33
|
+
const [foundOptionsMap, setFoundOptionsMap] = useState<FilterOptionsMap>();
|
|
34
|
+
|
|
35
|
+
const isAsync = !!onAsyncSearch;
|
|
36
|
+
const withSearch = (options && options.length > 5) || isAsync;
|
|
37
|
+
|
|
38
|
+
const isSubmitDisabled =
|
|
39
|
+
isEqual(selected, value) || (mode === 'pattern' && !!selected[0] && !selected[0].includes('*'));
|
|
40
|
+
const filteredOptions = useMemo(() => {
|
|
41
|
+
if (options?.length) {
|
|
42
|
+
return options.filter((opt) => {
|
|
43
|
+
const optLabel = (optionsMap && optionsMap[opt]) || opt;
|
|
44
|
+
return optLabel.toLowerCase().includes(searchValue.toLowerCase());
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return [];
|
|
48
|
+
}, [searchValue, options]);
|
|
49
|
+
|
|
50
|
+
const onSearchChange = (q: string) => {
|
|
51
|
+
setSearchValue(q);
|
|
52
|
+
if (isAsync) {
|
|
53
|
+
if (q.length > 0) {
|
|
54
|
+
setLoading(true);
|
|
55
|
+
} else {
|
|
56
|
+
setFoundOptions([]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const onSubmit = (e?: FormEvent<HTMLDivElement>) => {
|
|
62
|
+
e?.preventDefault();
|
|
63
|
+
const newSelected = selected[0] === '' ? [] : selected;
|
|
64
|
+
onChange(category, newSelected, value);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const onClearClick = () => {
|
|
68
|
+
setSelected([]);
|
|
69
|
+
onChange(category, [], value);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const getEmptyText = () => {
|
|
73
|
+
if (searchValue.length) {
|
|
74
|
+
return 'No result. Refine your search term.';
|
|
75
|
+
}
|
|
76
|
+
return '';
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const getAsyncList = async () => {
|
|
80
|
+
if (onAsyncSearch) {
|
|
81
|
+
const response = await onAsyncSearch(category, searchValue);
|
|
82
|
+
setLoading(false);
|
|
83
|
+
setFoundOptions(response.options);
|
|
84
|
+
setFoundOptionsMap(response.optionsMap || optionsMap);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
setLoading(Boolean(isInitialLoading));
|
|
90
|
+
}, [isInitialLoading]);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (debouncedSearchValue.length > 0) {
|
|
94
|
+
getAsyncList();
|
|
95
|
+
} else {
|
|
96
|
+
setLoading(Boolean(isInitialLoading));
|
|
97
|
+
}
|
|
98
|
+
}, [debouncedSearchValue]);
|
|
99
|
+
|
|
100
|
+
const isEditMode = value.length !== 0;
|
|
101
|
+
|
|
102
|
+
const isAsyncSearch = isAsync && !!searchValue;
|
|
103
|
+
|
|
104
|
+
const items = isAsyncSearch
|
|
105
|
+
? Array.from(new Set(preserveOptionOrder ? [...foundOptions] : [...value, ...foundOptions]))
|
|
106
|
+
: Array.from(new Set(preserveOptionOrder ? [...filteredOptions] : [...value, ...filteredOptions]));
|
|
107
|
+
|
|
108
|
+
const currentOptionMap = isAsyncSearch ? foundOptionsMap : optionsMap;
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
currentOptionMap,
|
|
112
|
+
getEmptyText,
|
|
113
|
+
isAsync,
|
|
114
|
+
isLoading,
|
|
115
|
+
isSubmitDisabled,
|
|
116
|
+
mode,
|
|
117
|
+
onClearClick,
|
|
118
|
+
onSearchChange,
|
|
119
|
+
onSubmit,
|
|
120
|
+
isEditMode,
|
|
121
|
+
items,
|
|
122
|
+
selected,
|
|
123
|
+
searchValue,
|
|
124
|
+
setMode,
|
|
125
|
+
setSelected,
|
|
126
|
+
value,
|
|
127
|
+
withSearch,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default useFilterForm;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
type UseIsScrollableProps = {
|
|
4
|
+
items: string[];
|
|
5
|
+
hasNotFilteredOption: boolean;
|
|
6
|
+
ref: React.RefObject<HTMLDivElement>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const useIsScrollable = ({ items, hasNotFilteredOption, ref }: UseIsScrollableProps) => {
|
|
10
|
+
const [isScrollable, setIsScrollable] = useState(false);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const checkScrollable = () => {
|
|
14
|
+
if (ref.current) {
|
|
15
|
+
const { scrollHeight, clientHeight } = ref.current;
|
|
16
|
+
setIsScrollable(scrollHeight > clientHeight);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
checkScrollable();
|
|
21
|
+
|
|
22
|
+
const timer = setTimeout(checkScrollable, 100);
|
|
23
|
+
|
|
24
|
+
window.addEventListener('resize', checkScrollable);
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
window.removeEventListener('resize', checkScrollable);
|
|
29
|
+
};
|
|
30
|
+
}, [items, hasNotFilteredOption]);
|
|
31
|
+
|
|
32
|
+
return { isScrollable };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default useIsScrollable;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useState, useRef } from 'react';
|
|
2
|
+
import { MAX_ITEMS } from './useFilterForm';
|
|
3
|
+
|
|
4
|
+
type UseListBoxProps = {
|
|
5
|
+
items: string[];
|
|
6
|
+
hasNotFilteredOption: boolean;
|
|
7
|
+
onChange: (option: string) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const useListBox = ({ items, hasNotFilteredOption, onChange }: UseListBoxProps) => {
|
|
11
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
12
|
+
const allOptions = hasNotFilteredOption ? ['', ...items.slice(0, MAX_ITEMS)] : items.slice(0, MAX_ITEMS);
|
|
13
|
+
const mouseInteractionRef = useRef(false);
|
|
14
|
+
|
|
15
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
16
|
+
mouseInteractionRef.current = false;
|
|
17
|
+
switch (e.key) {
|
|
18
|
+
case 'ArrowDown':
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
setActiveIndex((prev) => (prev < allOptions.length - 1 ? prev + 1 : 0));
|
|
21
|
+
break;
|
|
22
|
+
case 'ArrowUp':
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
setActiveIndex((prev) => (prev > 0 ? prev - 1 : allOptions.length - 1));
|
|
25
|
+
break;
|
|
26
|
+
case 'Enter':
|
|
27
|
+
case ' ':
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
if (activeIndex >= 0) {
|
|
30
|
+
onChange(allOptions[activeIndex]);
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleFocus = () => {
|
|
37
|
+
if (activeIndex === -1 && !mouseInteractionRef.current) {
|
|
38
|
+
setActiveIndex(0);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleBlur = () => {
|
|
43
|
+
setActiveIndex(-1);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleMouseDown = () => {
|
|
47
|
+
mouseInteractionRef.current = true;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleMouseMove = () => {
|
|
51
|
+
if (activeIndex !== -1) {
|
|
52
|
+
setActiveIndex(-1);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
activeIndex,
|
|
58
|
+
handleKeyDown,
|
|
59
|
+
handleFocus,
|
|
60
|
+
handleBlur,
|
|
61
|
+
handleMouseDown,
|
|
62
|
+
handleMouseMove,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default useListBox;
|
|
@@ -14,12 +14,6 @@ const InputTheme = defineStyle({
|
|
|
14
14
|
background: 'background.disabled',
|
|
15
15
|
cursor: 'not-allowed',
|
|
16
16
|
},
|
|
17
|
-
_focus: {
|
|
18
|
-
boxShadow: 'formFocus',
|
|
19
|
-
},
|
|
20
|
-
_focusVisible: {
|
|
21
|
-
boxShadow: 'formFocus',
|
|
22
|
-
},
|
|
23
17
|
_hover: {
|
|
24
18
|
borderColor: 'border.hover',
|
|
25
19
|
},
|
|
@@ -41,12 +35,9 @@ const InputTheme = defineStyle({
|
|
|
41
35
|
},
|
|
42
36
|
appearance: 'none',
|
|
43
37
|
background: 'background.primary',
|
|
44
|
-
|
|
45
|
-
borderColor: 'border.regular',
|
|
38
|
+
|
|
46
39
|
borderRadius: '4',
|
|
47
|
-
|
|
48
|
-
outline: 0,
|
|
49
|
-
transition: '200ms',
|
|
40
|
+
|
|
50
41
|
width: '100%',
|
|
51
42
|
},
|
|
52
43
|
icon: {
|
|
@@ -75,6 +66,28 @@ const InputTheme = defineStyle({
|
|
|
75
66
|
position: 'relative',
|
|
76
67
|
},
|
|
77
68
|
},
|
|
69
|
+
variants: {
|
|
70
|
+
desktop: {
|
|
71
|
+
field: {
|
|
72
|
+
border: '1px solid',
|
|
73
|
+
borderColor: 'border.regular',
|
|
74
|
+
boxShadow: 'inner',
|
|
75
|
+
outline: 0,
|
|
76
|
+
transition: '200ms',
|
|
77
|
+
_focus: {
|
|
78
|
+
boxShadow: 'formFocus',
|
|
79
|
+
},
|
|
80
|
+
_focusVisible: {
|
|
81
|
+
boxShadow: 'formFocus',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
mobile: {
|
|
86
|
+
field: {
|
|
87
|
+
boxShadow: 'none',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
78
91
|
sizes: {
|
|
79
92
|
lg: {
|
|
80
93
|
field: {
|
|
@@ -89,6 +102,9 @@ const InputTheme = defineStyle({
|
|
|
89
102
|
},
|
|
90
103
|
},
|
|
91
104
|
},
|
|
105
|
+
defaultProps: {
|
|
106
|
+
variant: 'desktop',
|
|
107
|
+
},
|
|
92
108
|
});
|
|
93
109
|
|
|
94
110
|
export default InputTheme;
|
|
@@ -64,6 +64,7 @@ export interface InputProps extends UsedFormControlProps, UsedChakraInputProps {
|
|
|
64
64
|
rightIconName?: TypeIconName;
|
|
65
65
|
rightIconColor?: IconProps['color'];
|
|
66
66
|
size?: 'lg' | 'md';
|
|
67
|
+
variant?: 'desktop' | 'mobile';
|
|
67
68
|
warningText?: ReactNode;
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -111,6 +112,7 @@ const Input = forwardRef<InputProps, 'div'>((props, ref) => {
|
|
|
111
112
|
step,
|
|
112
113
|
type,
|
|
113
114
|
value,
|
|
115
|
+
variant,
|
|
114
116
|
warningText,
|
|
115
117
|
...rest
|
|
116
118
|
} = props;
|
|
@@ -167,7 +169,7 @@ const Input = forwardRef<InputProps, 'div'>((props, ref) => {
|
|
|
167
169
|
};
|
|
168
170
|
|
|
169
171
|
const iconSize = size === 'lg' ? '24' : '16';
|
|
170
|
-
const style = useMultiStyleConfig('Input');
|
|
172
|
+
const style = useMultiStyleConfig('Input', { variant });
|
|
171
173
|
|
|
172
174
|
const [leftIconPos, setLeftIconPos] = useState(rem(12));
|
|
173
175
|
|
|
@@ -220,6 +222,7 @@ const Input = forwardRef<InputProps, 'div'>((props, ref) => {
|
|
|
220
222
|
{...inputProps}
|
|
221
223
|
{...dataAttributes}
|
|
222
224
|
onChange={onInputChange}
|
|
225
|
+
variant={variant}
|
|
223
226
|
/>
|
|
224
227
|
{rightAddon && (
|
|
225
228
|
<RightContentWrapper ref={rightAddonRef} bottom="0">
|
|
@@ -7,7 +7,7 @@ export interface SearchInputProps extends Omit<InputProps, 'onChange'> {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const SearchInput = (props: SearchInputProps) => {
|
|
10
|
-
const { onChange, value, ...rest } = props;
|
|
10
|
+
const { onChange, value, variant, ...rest } = props;
|
|
11
11
|
|
|
12
12
|
const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
13
13
|
const q = event.target.value;
|
|
@@ -29,6 +29,7 @@ const SearchInput = (props: SearchInputProps) => {
|
|
|
29
29
|
/>
|
|
30
30
|
);
|
|
31
31
|
const inputProps: InputProps = {
|
|
32
|
+
boxShadow: variant === 'mobile' ? 'none' : undefined,
|
|
32
33
|
leftIconColor: 'neutral.60',
|
|
33
34
|
leftIconName: 'Magnifier',
|
|
34
35
|
onChange: onInputChange,
|
|
@@ -38,7 +39,7 @@ const SearchInput = (props: SearchInputProps) => {
|
|
|
38
39
|
value,
|
|
39
40
|
...rest,
|
|
40
41
|
};
|
|
41
|
-
return <Input {...inputProps} />;
|
|
42
|
+
return <Input {...inputProps} variant={variant} />;
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
export default SearchInput;
|
|
@@ -47,7 +47,7 @@ import ExpandableCard from './ExpandableCard/ExpandableCard.theme';
|
|
|
47
47
|
import FileInput from './Form/FileInput/FileInput.theme';
|
|
48
48
|
import Filter from './Filter/Filter.theme';
|
|
49
49
|
import Toggletip from './Toggletip/Toggletip.theme';
|
|
50
|
-
import FilterSwitch from './Filter/FilterSwitch/FilterSwitch.theme';
|
|
50
|
+
import FilterSwitch from './Filter/Desktop/FilterSwitch/FilterSwitch.theme';
|
|
51
51
|
import TagsInput from './Form/TagsInput/TagsInput.theme';
|
|
52
52
|
import DraggableCard from './DraggableCard/DraggableCard.theme';
|
|
53
53
|
import SelectableTag from './SelectableTag/SelectableTag.theme';
|
package/src/index.ts
CHANGED
|
@@ -323,10 +323,10 @@ export { default as FileInput } from './Components/Form/FileInput/FileInput';
|
|
|
323
323
|
export type { ToggletipProps } from './Components/Toggletip/Toggletip';
|
|
324
324
|
export { default as Toggletip } from './Components/Toggletip/Toggletip';
|
|
325
325
|
|
|
326
|
-
export type { FilterSwitchProps } from './Components/Filter/FilterSwitch/FilterSwitch';
|
|
327
|
-
export { default as FilterSwitch } from './Components/Filter/FilterSwitch/FilterSwitch';
|
|
326
|
+
export type { FilterSwitchProps } from './Components/Filter/Desktop/FilterSwitch/FilterSwitch';
|
|
327
|
+
export { default as FilterSwitch } from './Components/Filter/Desktop/FilterSwitch/FilterSwitch';
|
|
328
328
|
|
|
329
|
-
export { default as FilterSwitchGroup } from './Components/Filter/FilterSwitch/FilterSwitchGroup';
|
|
329
|
+
export { default as FilterSwitchGroup } from './Components/Filter/Desktop/FilterSwitch/FilterSwitchGroup';
|
|
330
330
|
|
|
331
331
|
export type { TablePaginationProps } from './Components/Table/TablePagination';
|
|
332
332
|
export { default as TablePagination } from './Components/Table/TablePagination';
|
|
@@ -337,7 +337,6 @@ export { default as Pagination } from './Components/Pagination/Pagination';
|
|
|
337
337
|
export type { ProgressIndicatorProps } from './Components/ProgressIndicator/ProgressIndicator';
|
|
338
338
|
export { default as ProgressIndicator } from './Components/ProgressIndicator/ProgressIndicator';
|
|
339
339
|
|
|
340
|
-
export type { FilterProps } from './Components/Filter/Filter';
|
|
341
340
|
export { default as Filter } from './Components/Filter/Filter';
|
|
342
341
|
export * from './Components/Filter/Filter.types';
|
|
343
342
|
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { Menu, MenuButton, MenuList, Portal, useDisclosure } from 'chakra-ui-2--react';
|
|
3
|
-
import Button from '../../Button/Button';
|
|
4
|
-
import MenuItem from '../../Menu/MenuItem';
|
|
5
|
-
import Tooltip from '../../Tooltip/Tooltip';
|
|
6
|
-
import { useFilterContext } from '../Filter.context';
|
|
7
|
-
import FilterForm from '../FilterForm/FilterForm';
|
|
8
|
-
import { FilterValue } from '../Filter.types';
|
|
9
|
-
import { getMissingDependencies, hasAllDependencies } from '../Filter.utils';
|
|
10
|
-
|
|
11
|
-
export interface FilterAddProps {
|
|
12
|
-
onChange: (category: string, selected: FilterValue) => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const FilterAdd = (props: FilterAddProps) => {
|
|
16
|
-
const { onChange } = props;
|
|
17
|
-
const [selectedCategory, setSelectedCategory] = useState<string | undefined>();
|
|
18
|
-
|
|
19
|
-
const { isOpen, onClose: closeMenu, onOpen: openMenu } = useDisclosure();
|
|
20
|
-
|
|
21
|
-
const { data, filtersDependOn, isLoading, setPopoverOpen, state } = useFilterContext();
|
|
22
|
-
|
|
23
|
-
const onCategorySelect = (category: string) => {
|
|
24
|
-
setSelectedCategory(category);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const onOpen = () => {
|
|
28
|
-
openMenu();
|
|
29
|
-
setPopoverOpen(true);
|
|
30
|
-
setSelectedCategory(undefined);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const onClose = () => {
|
|
34
|
-
setPopoverOpen(false);
|
|
35
|
-
closeMenu();
|
|
36
|
-
setSelectedCategory(undefined);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const onFilterChange = (category: string, value: FilterValue) => {
|
|
40
|
-
onClose();
|
|
41
|
-
onChange(category, value);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const stateKeys = Object.keys(state);
|
|
45
|
-
|
|
46
|
-
const categoryList = Object.keys(data).filter((category) => {
|
|
47
|
-
return !stateKeys.includes(category) && data[category].type === 'tag';
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const isDisabled = !hasAllDependencies(stateKeys, filtersDependOn) || categoryList.length === 0;
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<Menu closeOnSelect={false} isOpen={isOpen} onClose={onClose} onOpen={onOpen}>
|
|
54
|
-
<MenuButton
|
|
55
|
-
as={Button}
|
|
56
|
-
isDisabled={isDisabled}
|
|
57
|
-
isLoading={isLoading}
|
|
58
|
-
leftIconName="Plus"
|
|
59
|
-
position={isOpen ? 'relative' : undefined}
|
|
60
|
-
size="sm"
|
|
61
|
-
variant="tertiary"
|
|
62
|
-
zIndex={isOpen ? 'dialog' : undefined}
|
|
63
|
-
>
|
|
64
|
-
Add filter {isOpen}
|
|
65
|
-
</MenuButton>
|
|
66
|
-
<Portal>
|
|
67
|
-
<MenuList
|
|
68
|
-
paddingY={selectedCategory ? 0 : '12'}
|
|
69
|
-
position={isOpen ? 'relative' : undefined}
|
|
70
|
-
zIndex={isOpen ? 'dialog' : undefined}
|
|
71
|
-
>
|
|
72
|
-
{selectedCategory ? (
|
|
73
|
-
<FilterForm
|
|
74
|
-
category={selectedCategory}
|
|
75
|
-
categoryName={data[selectedCategory].categoryName}
|
|
76
|
-
categoryNamePlural={data[selectedCategory].categoryNamePlural}
|
|
77
|
-
loadingText={data[selectedCategory].loadingText}
|
|
78
|
-
onCancel={onClose}
|
|
79
|
-
onChange={onFilterChange}
|
|
80
|
-
/>
|
|
81
|
-
) : (
|
|
82
|
-
categoryList.map((category) => {
|
|
83
|
-
const { categoryName, dependsOn } = data[category];
|
|
84
|
-
const missingDependencies = getMissingDependencies(stateKeys, Object.keys(dependsOn || []));
|
|
85
|
-
const isCategoryDisabled = missingDependencies.length > 0 || !data[category]?.options?.length;
|
|
86
|
-
return (
|
|
87
|
-
<Tooltip
|
|
88
|
-
key={category}
|
|
89
|
-
isDisabled={!isCategoryDisabled}
|
|
90
|
-
label={dependsOn?.[missingDependencies[0]] || 'There is no options for this category.'}
|
|
91
|
-
placement="right"
|
|
92
|
-
>
|
|
93
|
-
<MenuItem
|
|
94
|
-
isDisabled={isCategoryDisabled}
|
|
95
|
-
onClick={() => onCategorySelect(category)}
|
|
96
|
-
pointerEvents="all"
|
|
97
|
-
rightIconName="ChevronRight"
|
|
98
|
-
>
|
|
99
|
-
{categoryName || category}
|
|
100
|
-
</MenuItem>
|
|
101
|
-
</Tooltip>
|
|
102
|
-
);
|
|
103
|
-
})
|
|
104
|
-
)}
|
|
105
|
-
</MenuList>
|
|
106
|
-
</Portal>
|
|
107
|
-
</Menu>
|
|
108
|
-
);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
export default FilterAdd;
|
|
File without changes
|
|
File without changes
|