@bitrise/bitkit 13.318.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/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
package/package.json
CHANGED
|
@@ -19,13 +19,13 @@ import { DatePickerDayViewProps } from './DatePickerDay';
|
|
|
19
19
|
export { useDateRange, DateRange };
|
|
20
20
|
|
|
21
21
|
export type DatePickerProps = {
|
|
22
|
-
children
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
onClose?: () => void;
|
|
23
24
|
dayTooltip?: DatePickerDayViewProps['tooltip'];
|
|
24
25
|
selectable?: DateRange;
|
|
25
|
-
onClose: () => void;
|
|
26
26
|
onClear?: () => void;
|
|
27
27
|
visible: boolean;
|
|
28
|
-
variant?: 'default' | 'filter';
|
|
28
|
+
variant?: 'default' | 'filter' | 'inline';
|
|
29
29
|
} & (
|
|
30
30
|
| {
|
|
31
31
|
selected?: DateRange;
|
|
@@ -88,7 +88,7 @@ const DatePicker = (props: DatePickerProps) => {
|
|
|
88
88
|
}, [initialRange]);
|
|
89
89
|
|
|
90
90
|
const handleClose = () => {
|
|
91
|
-
onClose();
|
|
91
|
+
onClose?.();
|
|
92
92
|
setDateTo(undefined);
|
|
93
93
|
setDateFrom(undefined);
|
|
94
94
|
};
|
|
@@ -158,7 +158,7 @@ const DatePicker = (props: DatePickerProps) => {
|
|
|
158
158
|
setDateFrom(dateFromNew);
|
|
159
159
|
setDateTo(dateToNew);
|
|
160
160
|
|
|
161
|
-
if (variant === 'default') {
|
|
161
|
+
if (variant === 'default' || variant === 'inline') {
|
|
162
162
|
if (!dateFromNew || previewNew) {
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
@@ -200,58 +200,63 @@ const DatePicker = (props: DatePickerProps) => {
|
|
|
200
200
|
[isMonthSelector],
|
|
201
201
|
);
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
<
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
onMonthClick={onMonthClickLeft}
|
|
221
|
-
onViewDateChange={updateLeftViewDate}
|
|
222
|
-
viewDate={leftViewDate}
|
|
223
|
-
/>
|
|
203
|
+
const datePicker = (
|
|
204
|
+
<DatePickerContext value={ctx}>
|
|
205
|
+
<Box padding={variant === 'inline' ? '0' : '24'}>
|
|
206
|
+
{isMonthSelector ? (
|
|
207
|
+
<DatePickerMonthSelector
|
|
208
|
+
onMonthSelected={onMonthSelected}
|
|
209
|
+
viewDate={isMonthSelector === 'left' ? leftViewDate : rightViewDate}
|
|
210
|
+
/>
|
|
211
|
+
) : (
|
|
212
|
+
<>
|
|
213
|
+
<Box display="flex" gap="32" marginBottom="24">
|
|
214
|
+
<DatePickerMonth
|
|
215
|
+
controls={isSingleMonthView ? 'both' : 'left'}
|
|
216
|
+
onMonthClick={onMonthClickLeft}
|
|
217
|
+
onViewDateChange={updateLeftViewDate}
|
|
218
|
+
viewDate={leftViewDate}
|
|
219
|
+
/>
|
|
224
220
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
)}
|
|
233
|
-
</Box>
|
|
234
|
-
{variant === 'filter' && (
|
|
235
|
-
<DatePickerFooter
|
|
236
|
-
mode={mode || 'range'}
|
|
237
|
-
onApply={() => handleApply(dateFrom, dateTo)}
|
|
238
|
-
onClear={onClear}
|
|
239
|
-
onClose={handleClose}
|
|
240
|
-
selected={currentSelected}
|
|
241
|
-
/>
|
|
242
|
-
)}
|
|
243
|
-
{variant === 'default' && mode === 'day' && (
|
|
244
|
-
<Box display="flex" justifyContent="space-around">
|
|
245
|
-
<Button onClick={() => handleSelect(DateTime.now())} variant="tertiary">
|
|
246
|
-
Today
|
|
247
|
-
</Button>
|
|
248
|
-
</Box>
|
|
249
|
-
)}
|
|
250
|
-
</>
|
|
221
|
+
{!isSingleMonthView && (
|
|
222
|
+
<DatePickerMonth
|
|
223
|
+
controls="right"
|
|
224
|
+
onMonthClick={onMonthClickRight}
|
|
225
|
+
onViewDateChange={updateRightViewDate}
|
|
226
|
+
viewDate={rightViewDate}
|
|
227
|
+
/>
|
|
251
228
|
)}
|
|
252
229
|
</Box>
|
|
253
|
-
|
|
254
|
-
|
|
230
|
+
{variant === 'filter' && (
|
|
231
|
+
<DatePickerFooter
|
|
232
|
+
mode={mode || 'range'}
|
|
233
|
+
onApply={() => handleApply(dateFrom, dateTo)}
|
|
234
|
+
onClear={onClear}
|
|
235
|
+
onClose={handleClose}
|
|
236
|
+
selected={currentSelected}
|
|
237
|
+
/>
|
|
238
|
+
)}
|
|
239
|
+
{variant === 'default' && mode === 'day' && (
|
|
240
|
+
<Box display="flex" justifyContent="space-around">
|
|
241
|
+
<Button onClick={() => handleSelect(DateTime.now())} variant="tertiary">
|
|
242
|
+
Today
|
|
243
|
+
</Button>
|
|
244
|
+
</Box>
|
|
245
|
+
)}
|
|
246
|
+
</>
|
|
247
|
+
)}
|
|
248
|
+
</Box>
|
|
249
|
+
</DatePickerContext>
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return variant === 'inline' ? (
|
|
253
|
+
datePicker
|
|
254
|
+
) : (
|
|
255
|
+
<Popover isLazy isOpen={visible} lazyBehavior="unmount" modifiers={[]} onClose={onClose}>
|
|
256
|
+
<PopoverAnchor>{children}</PopoverAnchor>
|
|
257
|
+
<PopoverContent aria-label="select date range">
|
|
258
|
+
<FocusLock returnFocus />
|
|
259
|
+
{datePicker}
|
|
255
260
|
</PopoverContent>
|
|
256
261
|
</Popover>
|
|
257
262
|
);
|
|
@@ -19,6 +19,8 @@ import Icon from '../Icon/Icon';
|
|
|
19
19
|
|
|
20
20
|
export interface DrawerProps
|
|
21
21
|
extends Pick<ChakraDrawerProps, 'finalFocusRef' | 'initialFocusRef' | 'isOpen' | 'onClose' | 'onCloseComplete'> {
|
|
22
|
+
blockScrollOnMount?: boolean;
|
|
23
|
+
bodyRef?: React.RefObject<HTMLDivElement>;
|
|
22
24
|
children: DrawerContentProps['children'];
|
|
23
25
|
footer?: ReactNode;
|
|
24
26
|
maxWidth?: BoxProps['maxWidth'];
|
|
@@ -26,6 +28,7 @@ export interface DrawerProps
|
|
|
26
28
|
padding?: BoxProps['padding'];
|
|
27
29
|
margin?: BoxProps['margin'];
|
|
28
30
|
headerPadding?: BoxProps['padding'];
|
|
31
|
+
hideCloseButton?: boolean;
|
|
29
32
|
bodyPadding?: BoxProps['padding'];
|
|
30
33
|
overlayProps?: ModalOverlayProps;
|
|
31
34
|
contentProps?: DrawerContentProps;
|
|
@@ -34,12 +37,21 @@ export interface DrawerProps
|
|
|
34
37
|
footerProps?: ModalFooterProps;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
const Drawer = ({
|
|
40
|
+
const Drawer = ({
|
|
41
|
+
bodyRef,
|
|
42
|
+
overlayProps,
|
|
43
|
+
contentProps,
|
|
44
|
+
headerProps,
|
|
45
|
+
bodyProps,
|
|
46
|
+
footerProps,
|
|
47
|
+
...props
|
|
48
|
+
}: DrawerProps) => {
|
|
38
49
|
const {
|
|
39
50
|
bodyPadding,
|
|
40
51
|
children,
|
|
41
52
|
footer,
|
|
42
53
|
headerPadding,
|
|
54
|
+
hideCloseButton,
|
|
43
55
|
maxWidth = '20rem',
|
|
44
56
|
padding,
|
|
45
57
|
margin,
|
|
@@ -51,13 +63,15 @@ const Drawer = ({ overlayProps, contentProps, headerProps, bodyProps, footerProp
|
|
|
51
63
|
<ChakraDrawer {...drawerProps}>
|
|
52
64
|
<DrawerOverlay {...overlayProps} />
|
|
53
65
|
<DrawerContent maxWidth={maxWidth} padding={padding} margin={margin} {...contentProps}>
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
66
|
+
{!hideCloseButton && (
|
|
67
|
+
<DrawerCloseButton size="md">
|
|
68
|
+
<Icon name="Cross" />
|
|
69
|
+
</DrawerCloseButton>
|
|
70
|
+
)}
|
|
57
71
|
<DrawerHeader as="h3" padding={headerPadding} {...headerProps}>
|
|
58
72
|
{title}
|
|
59
73
|
</DrawerHeader>
|
|
60
|
-
<DrawerBody padding={bodyPadding} {...bodyProps}>
|
|
74
|
+
<DrawerBody padding={bodyPadding} {...bodyProps} ref={bodyRef}>
|
|
61
75
|
{children}
|
|
62
76
|
</DrawerBody>
|
|
63
77
|
{footer && <DrawerFooter {...footerProps}>{footer}</DrawerFooter>}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Modal, ModalOverlay, useMultiStyleConfig } from 'chakra-ui-2--react';
|
|
2
|
+
import Box from '../../Box/Box';
|
|
3
|
+
import Icon from '../../Icon/Icon';
|
|
4
|
+
import Button from '../../Button/Button';
|
|
5
|
+
import Divider from '../../Divider/Divider';
|
|
6
|
+
import { FilterProps } from '../Filter.types';
|
|
7
|
+
import { useFilterContext } from '../Filter.context';
|
|
8
|
+
import FilterAdd from './FilterAdd/FilterAdd';
|
|
9
|
+
import FilterDate from './FilterDate/FilterDate';
|
|
10
|
+
import FilterItem from './FilterItem';
|
|
11
|
+
import FilterSearch from './FilterSearch';
|
|
12
|
+
import FilterSwitchAdapter from './FilterSwitch/FilterSwitchAdapter';
|
|
13
|
+
|
|
14
|
+
const Filter = (props: FilterProps) => {
|
|
15
|
+
const {
|
|
16
|
+
data,
|
|
17
|
+
defaultState,
|
|
18
|
+
filtersDependOn,
|
|
19
|
+
isLoading,
|
|
20
|
+
isMobile,
|
|
21
|
+
onChange,
|
|
22
|
+
searchTooltip,
|
|
23
|
+
showAdd = true,
|
|
24
|
+
showFilterIcon = true,
|
|
25
|
+
showClearFilters = true,
|
|
26
|
+
showSearch,
|
|
27
|
+
...rest
|
|
28
|
+
} = props;
|
|
29
|
+
|
|
30
|
+
const filterStyle = useMultiStyleConfig('Filter');
|
|
31
|
+
|
|
32
|
+
const { filters, isPopoverOpen, onClearFilters, onFilterChange, showClearButton, state } = useFilterContext();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Box sx={filterStyle.container} {...rest}>
|
|
36
|
+
<Modal isOpen={isPopoverOpen} onClose={() => {}}>
|
|
37
|
+
{isPopoverOpen && <ModalOverlay />}
|
|
38
|
+
</Modal>
|
|
39
|
+
<Box sx={filterStyle.content}>
|
|
40
|
+
{showFilterIcon && <Icon name="Filter" sx={filterStyle.icon} />}
|
|
41
|
+
|
|
42
|
+
{Object.keys(filters.switch).map((category) => (
|
|
43
|
+
<FilterSwitchAdapter key={category} category={category} />
|
|
44
|
+
))}
|
|
45
|
+
|
|
46
|
+
{Object.keys(filters.dateRange).map((category) => (
|
|
47
|
+
<FilterDate category={category} isMobile={Boolean(isMobile)} key={category} />
|
|
48
|
+
))}
|
|
49
|
+
|
|
50
|
+
{Object.keys(filters.select).map((category) => (
|
|
51
|
+
<FilterItem key={category} category={category} />
|
|
52
|
+
))}
|
|
53
|
+
{Object.keys(filters.tag).map((category) => {
|
|
54
|
+
if (!state[category]) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
return <FilterItem key={category} category={category} />;
|
|
58
|
+
})}
|
|
59
|
+
|
|
60
|
+
{showAdd && <FilterAdd />}
|
|
61
|
+
</Box>
|
|
62
|
+
{(showClearButton || showSearch) && (
|
|
63
|
+
<Box sx={filterStyle.rightContent}>
|
|
64
|
+
{showClearButton && (
|
|
65
|
+
<Button
|
|
66
|
+
isDisabled={isLoading}
|
|
67
|
+
leftIconName="Cross"
|
|
68
|
+
minWidth="7.5rem"
|
|
69
|
+
onClick={onClearFilters}
|
|
70
|
+
size="sm"
|
|
71
|
+
variant="tertiary"
|
|
72
|
+
>
|
|
73
|
+
Clear filters
|
|
74
|
+
</Button>
|
|
75
|
+
)}
|
|
76
|
+
{showSearch && (
|
|
77
|
+
<>
|
|
78
|
+
<Divider flexShrink="0" orientation="vertical" size="1" variant="solid" />
|
|
79
|
+
<FilterSearch
|
|
80
|
+
onChange={onFilterChange}
|
|
81
|
+
value={state.search?.length ? state.search[0] : ''}
|
|
82
|
+
searchTooltip={searchTooltip}
|
|
83
|
+
/>
|
|
84
|
+
</>
|
|
85
|
+
)}
|
|
86
|
+
</Box>
|
|
87
|
+
)}
|
|
88
|
+
</Box>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default Filter;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Menu, MenuButton, MenuList, Portal } from 'chakra-ui-2--react';
|
|
2
|
+
import Button from '../../../Button/Button';
|
|
3
|
+
import MenuItem from '../../../Menu/MenuItem';
|
|
4
|
+
import Tooltip from '../../../Tooltip/Tooltip';
|
|
5
|
+
import { useFilterContext } from '../../Filter.context';
|
|
6
|
+
import FilterForm from '../FilterForm';
|
|
7
|
+
import { FilterValue } from '../../Filter.types';
|
|
8
|
+
import { getMissingDependencies } from '../../Filter.utils';
|
|
9
|
+
import useFilterAdd from '../../hooks/useFilterAdd';
|
|
10
|
+
|
|
11
|
+
export interface FilterAddProps {
|
|
12
|
+
onChange: (category: string, selected: FilterValue) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const FilterAdd = () => {
|
|
16
|
+
const { data, isLoading } = useFilterContext();
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
categoryList,
|
|
20
|
+
isDisabled,
|
|
21
|
+
isMenuOpen,
|
|
22
|
+
onCategorySelect,
|
|
23
|
+
onChange,
|
|
24
|
+
onClose,
|
|
25
|
+
onOpen,
|
|
26
|
+
selectedCategory,
|
|
27
|
+
stateKeys,
|
|
28
|
+
} = useFilterAdd({ isMobile: false });
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Menu closeOnSelect={false} isOpen={isMenuOpen} onClose={onClose} onOpen={onOpen}>
|
|
32
|
+
<MenuButton
|
|
33
|
+
as={Button}
|
|
34
|
+
isDisabled={isDisabled}
|
|
35
|
+
isLoading={isLoading}
|
|
36
|
+
leftIconName="Plus"
|
|
37
|
+
position={isMenuOpen ? 'relative' : undefined}
|
|
38
|
+
size="sm"
|
|
39
|
+
variant="tertiary"
|
|
40
|
+
zIndex={isMenuOpen ? 'dialog' : undefined}
|
|
41
|
+
>
|
|
42
|
+
Add filter {isMenuOpen}
|
|
43
|
+
</MenuButton>
|
|
44
|
+
<Portal>
|
|
45
|
+
<MenuList
|
|
46
|
+
paddingY={selectedCategory ? 0 : '12'}
|
|
47
|
+
position={isMenuOpen ? 'relative' : undefined}
|
|
48
|
+
zIndex={isMenuOpen ? 'dialog' : undefined}
|
|
49
|
+
>
|
|
50
|
+
{selectedCategory ? (
|
|
51
|
+
<FilterForm
|
|
52
|
+
category={selectedCategory}
|
|
53
|
+
categoryName={data[selectedCategory].categoryName}
|
|
54
|
+
categoryNamePlural={data[selectedCategory].categoryNamePlural}
|
|
55
|
+
loadingText={data[selectedCategory].loadingText}
|
|
56
|
+
onCancel={onClose}
|
|
57
|
+
onChange={onChange}
|
|
58
|
+
/>
|
|
59
|
+
) : (
|
|
60
|
+
categoryList.map((category) => {
|
|
61
|
+
const { categoryName, dependsOn } = data[category];
|
|
62
|
+
const missingDependencies = getMissingDependencies(stateKeys, Object.keys(dependsOn || []));
|
|
63
|
+
const isCategoryDisabled = missingDependencies.length > 0 || !data[category]?.options?.length;
|
|
64
|
+
return (
|
|
65
|
+
<Tooltip
|
|
66
|
+
key={category}
|
|
67
|
+
isDisabled={!isCategoryDisabled}
|
|
68
|
+
label={dependsOn?.[missingDependencies[0]] || 'There is no options for this category.'}
|
|
69
|
+
placement="right"
|
|
70
|
+
>
|
|
71
|
+
<MenuItem
|
|
72
|
+
isDisabled={isCategoryDisabled}
|
|
73
|
+
onClick={() => onCategorySelect(category)}
|
|
74
|
+
pointerEvents="all"
|
|
75
|
+
rightIconName="ChevronRight"
|
|
76
|
+
>
|
|
77
|
+
{categoryName || category}
|
|
78
|
+
</MenuItem>
|
|
79
|
+
</Tooltip>
|
|
80
|
+
);
|
|
81
|
+
})
|
|
82
|
+
)}
|
|
83
|
+
</MenuList>
|
|
84
|
+
</Portal>
|
|
85
|
+
</Menu>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default FilterAdd;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
// import { useEffect } from 'react';
|
|
2
1
|
import { useEffect } from 'react';
|
|
3
2
|
import { useDisclosure, useMultiStyleConfig } from 'chakra-ui-2--react';
|
|
4
3
|
import { DateTime } from 'luxon';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
4
|
+
import DatePicker, { DatePickerProps, DateRange, useDateRange } from '../../../DatePicker/DatePicker';
|
|
5
|
+
import { useFilterContext } from '../../Filter.context';
|
|
6
|
+
import { FilterStyle } from '../../Filter.theme';
|
|
7
|
+
import { FilterCategoryProps } from '../../Filter.types';
|
|
8
|
+
import Box from '../../../Box/Box';
|
|
9
|
+
import Text from '../../../Text/Text';
|
|
10
|
+
import Icon from '../../../Icon/Icon';
|
|
12
11
|
|
|
13
12
|
export type FilterDateProps = {
|
|
14
13
|
category: string;
|
|
14
|
+
isMobile: boolean;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const FilterDate = (props: FilterDateProps) => {
|
|
18
|
-
const { category } = props;
|
|
18
|
+
const { category, isMobile } = props;
|
|
19
19
|
const filterStyle = useMultiStyleConfig('Filter') as FilterStyle;
|
|
20
20
|
|
|
21
21
|
const { isLoading, onFilterChange, onFilterClear, setPopoverOpen, state, data } = useFilterContext();
|
|
@@ -58,7 +58,18 @@ const FilterDate = (props: FilterDateProps) => {
|
|
|
58
58
|
? `${selectedRange.from?.toFormat('LLL dd')} - ${selectedRange.to?.toFormat('LLL dd')}`
|
|
59
59
|
: 'All dates';
|
|
60
60
|
|
|
61
|
-
return (
|
|
61
|
+
return isMobile ? (
|
|
62
|
+
<DatePicker
|
|
63
|
+
selectable={dateRangeData.selectable || selectable}
|
|
64
|
+
onApply={onDateRangeApply}
|
|
65
|
+
onClear={value?.length ? onClearClick : undefined}
|
|
66
|
+
dayTooltip={dateRangeData.dayTooltip}
|
|
67
|
+
selected={selectedRange}
|
|
68
|
+
visible={isOpen}
|
|
69
|
+
variant="inline"
|
|
70
|
+
mode="range"
|
|
71
|
+
/>
|
|
72
|
+
) : (
|
|
62
73
|
<DatePicker
|
|
63
74
|
selectable={dateRangeData.selectable || selectable}
|
|
64
75
|
onApply={onDateRangeApply}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { FormEvent, useEffect, useMemo, useState } from 'react';
|
|
2
1
|
import { useMultiStyleConfig } from 'chakra-ui-2--react';
|
|
3
2
|
import FocusLock from 'react-focus-lock';
|
|
4
3
|
import Tooltip from '../../Tooltip/Tooltip';
|
|
@@ -18,12 +17,10 @@ import RadioGroup from '../../Form/Radio/RadioGroup';
|
|
|
18
17
|
import SearchInput from '../../SearchInput/SearchInput';
|
|
19
18
|
import Text from '../../Text/Text';
|
|
20
19
|
import { FilterStyle } from '../Filter.theme';
|
|
21
|
-
import {
|
|
22
|
-
import { isEqual, useDebounce } from '../../../utils/utils';
|
|
20
|
+
import { FilterFormProps, FilterOptions, FilterValue } from '../Filter.types';
|
|
23
21
|
import { getOptionLabel } from '../Filter.utils';
|
|
24
22
|
import { useFilterContext } from '../Filter.context';
|
|
25
|
-
|
|
26
|
-
const MAX_ITEMS = 100;
|
|
23
|
+
import useFilterForm, { MAX_ITEMS } from '../hooks/useFilterForm';
|
|
27
24
|
|
|
28
25
|
/**
|
|
29
26
|
* https://gist.github.com/donmccurdy/6d073ce2c6f3951312dfa45da14a420f
|
|
@@ -88,128 +85,40 @@ const MatchingResults = (props: MatchingResultsProps) => {
|
|
|
88
85
|
);
|
|
89
86
|
};
|
|
90
87
|
|
|
91
|
-
export type FilterFormProps = {
|
|
92
|
-
category: string;
|
|
93
|
-
categoryName?: string;
|
|
94
|
-
categoryNamePlural?: string;
|
|
95
|
-
loadingText?: string;
|
|
96
|
-
onChange: (category: string, selected: FilterValue, previousValue: FilterValue) => void;
|
|
97
|
-
onCancel: () => void;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
88
|
const FilterForm = (props: FilterFormProps) => {
|
|
101
|
-
const { category, categoryName, categoryNamePlural, loadingText, onCancel
|
|
89
|
+
const { category, categoryName, categoryNamePlural, loadingText, onCancel } = props;
|
|
102
90
|
|
|
103
91
|
const filterStyle = useMultiStyleConfig('Filter') as FilterStyle;
|
|
104
92
|
|
|
105
|
-
const { data
|
|
106
|
-
|
|
107
|
-
const {
|
|
108
|
-
isPatternEnabled,
|
|
109
|
-
iconsMap,
|
|
110
|
-
hasNotFilteredOption = true,
|
|
111
|
-
preserveOptionOrder,
|
|
112
|
-
isInitialLoading,
|
|
113
|
-
isMultiple,
|
|
114
|
-
onAsyncSearch,
|
|
115
|
-
options,
|
|
116
|
-
optionsMap,
|
|
117
|
-
} = data[category];
|
|
118
|
-
const value = state[category] || [];
|
|
119
|
-
|
|
120
|
-
const [selected, setSelected] = useState<FilterValue>(value);
|
|
121
|
-
|
|
122
|
-
const [mode, setMode] = useState<'manually' | 'pattern'>(
|
|
123
|
-
isPatternEnabled && value?.length && value[0].includes('*') ? 'pattern' : 'manually',
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const [searchValue, setSearchValue] = useState<string>('');
|
|
127
|
-
const debouncedSearchValue = useDebounce<string>(searchValue, 1000);
|
|
128
|
-
|
|
129
|
-
const [isLoading, setLoading] = useState(Boolean(isInitialLoading));
|
|
130
|
-
const [foundOptions, setFoundOptions] = useState<FilterOptions>([]);
|
|
131
|
-
const [foundOptionsMap, setFoundOptionsMap] = useState<FilterOptionsMap>();
|
|
132
|
-
|
|
133
|
-
const isAsync = !!onAsyncSearch;
|
|
134
|
-
const withSearch = (options && options.length > 5) || isAsync;
|
|
135
|
-
|
|
136
|
-
const isSubmitDisabled =
|
|
137
|
-
isEqual(selected, value) || (mode === 'pattern' && !!selected[0] && !selected[0].includes('*'));
|
|
138
|
-
const filteredOptions = useMemo(() => {
|
|
139
|
-
if (options?.length) {
|
|
140
|
-
return options.filter((opt) => {
|
|
141
|
-
const optLabel = (optionsMap && optionsMap[opt]) || opt;
|
|
142
|
-
return optLabel.toLowerCase().includes(searchValue.toLowerCase());
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return [];
|
|
146
|
-
}, [searchValue, options]);
|
|
147
|
-
|
|
148
|
-
const onSearchChange = (q: string) => {
|
|
149
|
-
setSearchValue(q);
|
|
150
|
-
if (isAsync) {
|
|
151
|
-
if (q.length > 0) {
|
|
152
|
-
setLoading(true);
|
|
153
|
-
} else {
|
|
154
|
-
setFoundOptions([]);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
};
|
|
93
|
+
const { data } = useFilterContext();
|
|
158
94
|
|
|
159
|
-
const
|
|
160
|
-
e.preventDefault();
|
|
161
|
-
const newSelected = selected[0] === '' ? [] : selected;
|
|
162
|
-
onChange(category, newSelected, value);
|
|
163
|
-
};
|
|
95
|
+
const { isPatternEnabled, iconsMap, hasNotFilteredOption = true, isInitialLoading, isMultiple } = data[category];
|
|
164
96
|
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
97
|
+
const {
|
|
98
|
+
currentOptionMap,
|
|
99
|
+
getEmptyText,
|
|
100
|
+
isAsync,
|
|
101
|
+
isLoading,
|
|
102
|
+
isSubmitDisabled,
|
|
103
|
+
mode,
|
|
104
|
+
onClearClick,
|
|
105
|
+
onSearchChange,
|
|
106
|
+
onSubmit,
|
|
107
|
+
isEditMode,
|
|
108
|
+
items,
|
|
109
|
+
searchValue,
|
|
110
|
+
selected,
|
|
111
|
+
setMode,
|
|
112
|
+
setSelected,
|
|
113
|
+
value,
|
|
114
|
+
withSearch,
|
|
115
|
+
} = useFilterForm(props);
|
|
169
116
|
|
|
170
117
|
const onCancelClick = () => {
|
|
171
118
|
setSelected(value);
|
|
172
119
|
onCancel();
|
|
173
120
|
};
|
|
174
121
|
|
|
175
|
-
const getEmptyText = () => {
|
|
176
|
-
if (searchValue.length) {
|
|
177
|
-
return 'No result. Refine your search term.';
|
|
178
|
-
}
|
|
179
|
-
return '';
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const getAsyncList = async () => {
|
|
183
|
-
if (onAsyncSearch) {
|
|
184
|
-
const response = await onAsyncSearch(category, searchValue);
|
|
185
|
-
setLoading(false);
|
|
186
|
-
setFoundOptions(response.options);
|
|
187
|
-
setFoundOptionsMap(response.optionsMap || optionsMap);
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
setLoading(Boolean(isInitialLoading));
|
|
193
|
-
}, [isInitialLoading]);
|
|
194
|
-
|
|
195
|
-
useEffect(() => {
|
|
196
|
-
if (debouncedSearchValue.length > 0) {
|
|
197
|
-
getAsyncList();
|
|
198
|
-
} else {
|
|
199
|
-
setLoading(Boolean(isInitialLoading));
|
|
200
|
-
}
|
|
201
|
-
}, [debouncedSearchValue]);
|
|
202
|
-
|
|
203
|
-
const isEditMode = value.length !== 0;
|
|
204
|
-
|
|
205
|
-
const isAsyncSearch = isAsync && !!searchValue;
|
|
206
|
-
|
|
207
|
-
const items = isAsyncSearch
|
|
208
|
-
? Array.from(new Set(preserveOptionOrder ? [...foundOptions] : [...value, ...foundOptions]))
|
|
209
|
-
: Array.from(new Set(preserveOptionOrder ? [...filteredOptions] : [...value, ...filteredOptions]));
|
|
210
|
-
|
|
211
|
-
const currentOptionMap = isAsyncSearch ? foundOptionsMap : optionsMap;
|
|
212
|
-
|
|
213
122
|
return (
|
|
214
123
|
<FocusLock>
|
|
215
124
|
<Box as="form" data-testid={`${category}-form`} onSubmit={onSubmit} sx={filterStyle.form}>
|
|
@@ -10,9 +10,9 @@ import Text from '../../Text/Text';
|
|
|
10
10
|
import Tooltip from '../../Tooltip/Tooltip';
|
|
11
11
|
import { FilterStyle } from '../Filter.theme';
|
|
12
12
|
import { FilterValue } from '../Filter.types';
|
|
13
|
-
import FilterForm from '../FilterForm/FilterForm';
|
|
14
13
|
import { useFilterContext } from '../Filter.context';
|
|
15
14
|
import { getOptionLabel } from '../Filter.utils';
|
|
15
|
+
import FilterForm from './FilterForm';
|
|
16
16
|
|
|
17
17
|
export type FilterItemProps = {
|
|
18
18
|
category: string;
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
} from 'chakra-ui-2--react';
|
|
10
10
|
import { omitThemingProps } from 'chakra-ui-2--styled-system';
|
|
11
11
|
import { createContext } from 'chakra-ui-2--react-utils';
|
|
12
|
-
import Divider from '
|
|
13
|
-
import Text from '
|
|
14
|
-
import Box from '
|
|
15
|
-
import Icon, { TypeIconName } from '
|
|
12
|
+
import Divider from '../../../Divider/Divider';
|
|
13
|
+
import Text from '../../../Text/Text';
|
|
14
|
+
import Box from '../../../Box/Box';
|
|
15
|
+
import Icon, { TypeIconName } from '../../../Icon/Icon';
|
|
16
16
|
|
|
17
17
|
type RadioInputProps = ChakraRadioProps['inputProps'] & {
|
|
18
18
|
'data-testid'?: string;
|
package/src/Components/Filter/{FilterSwitchAdapter → Desktop/FilterSwitch}/FilterSwitchAdapter.tsx
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
-
import { useFilterContext } from '
|
|
3
|
-
import { getOptionLabel } from '
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
2
|
+
import { useFilterContext } from '../../Filter.context';
|
|
3
|
+
import { getOptionLabel } from '../../Filter.utils';
|
|
4
|
+
import { FilterCategoryProps } from '../../Filter.types';
|
|
5
|
+
import FilterSwitch from './FilterSwitch';
|
|
6
|
+
import FilterSwitchGroup from './FilterSwitchGroup';
|
|
7
7
|
|
|
8
8
|
type FilterSwitchAdapterProps = {
|
|
9
9
|
category: string;
|