@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
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;
|
|
@@ -49,6 +49,8 @@ export const FILTER_STORY_DATA: FilterData = {
|
|
|
49
49
|
type: 'tag',
|
|
50
50
|
},
|
|
51
51
|
cache_type: {
|
|
52
|
+
categoryName: 'Cache type',
|
|
53
|
+
categoryNamePlural: 'Cache types',
|
|
52
54
|
allOptionLabel: 'All items',
|
|
53
55
|
iconsMap: {
|
|
54
56
|
bazel: 'Bazel',
|
|
@@ -63,7 +65,7 @@ export const FILTER_STORY_DATA: FilterData = {
|
|
|
63
65
|
},
|
|
64
66
|
date_range: {
|
|
65
67
|
categoryName: 'Date',
|
|
66
|
-
categoryNamePlural: '
|
|
68
|
+
categoryNamePlural: 'Dates',
|
|
67
69
|
isMultiple: true,
|
|
68
70
|
dayTooltip: ({ date }) => (date > DateTime.now() ? 'Date must be in the past' : undefined),
|
|
69
71
|
selectable: new DateRange(DateTime.now().minus({ days: 15 }), DateTime.now()),
|
|
@@ -97,6 +99,7 @@ export const FILTER_STORY_DATA: FilterData = {
|
|
|
97
99
|
},
|
|
98
100
|
workflow: {
|
|
99
101
|
categoryName: 'Workflow',
|
|
102
|
+
categoryNamePlural: 'Workflows',
|
|
100
103
|
isMultiple: true,
|
|
101
104
|
options: FILTER_STORY_OPTIONS,
|
|
102
105
|
type: 'tag',
|
|
@@ -115,4 +118,14 @@ export const FILTER_STORY_CONTEXT: FilterContextType = {
|
|
|
115
118
|
onFilterClear: () => {},
|
|
116
119
|
setPopoverOpen: () => {},
|
|
117
120
|
state: FILTER_STORY_INIT_STATE,
|
|
121
|
+
filters: {
|
|
122
|
+
dateRange: {},
|
|
123
|
+
search: {},
|
|
124
|
+
select: {},
|
|
125
|
+
switch: {},
|
|
126
|
+
tag: {},
|
|
127
|
+
},
|
|
128
|
+
isPopoverOpen: false,
|
|
129
|
+
onClearFilters: () => {},
|
|
130
|
+
setSelectedCategory: () => {},
|
|
118
131
|
};
|
|
@@ -1,58 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Modal, ModalOverlay, useMultiStyleConfig } from 'chakra-ui-2--react';
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
3
2
|
import { isEqual } from '../../utils/utils';
|
|
4
|
-
import Box, { BoxProps } from '../Box/Box';
|
|
5
|
-
import Button from '../Button/Button';
|
|
6
|
-
import Divider from '../Divider/Divider';
|
|
7
|
-
import Icon from '../Icon/Icon';
|
|
8
3
|
import { FilterContext } from './Filter.context';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
FilterCategoryProps,
|
|
12
|
-
FilterContextType,
|
|
13
|
-
FilterData,
|
|
14
|
-
FilterState,
|
|
15
|
-
FilterType,
|
|
16
|
-
FilterValue,
|
|
17
|
-
} from './Filter.types';
|
|
4
|
+
import { FilterContextType, FilterProps, Filters, FilterState, FilterValue } from './Filter.types';
|
|
18
5
|
import { getDependents } from './Filter.utils';
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import FilterSearch from './FilterSearch/FilterSearch';
|
|
22
|
-
import FilterDate from './FilterDate/FilterDate';
|
|
23
|
-
import FilterSwitchAdapter from './FilterSwitchAdapter/FilterSwitchAdapter';
|
|
24
|
-
|
|
25
|
-
export interface FilterProps extends Omit<BoxProps, 'onChange'> {
|
|
26
|
-
data: FilterData;
|
|
27
|
-
defaultState?: FilterState;
|
|
28
|
-
filtersDependOn?: string[];
|
|
29
|
-
isLoading?: boolean;
|
|
30
|
-
onChange: (state: FilterState) => void;
|
|
31
|
-
searchTooltip?: ReactNode;
|
|
32
|
-
showAdd?: boolean;
|
|
33
|
-
showFilterIcon?: boolean;
|
|
34
|
-
showClearFilters?: boolean;
|
|
35
|
-
showSearch?: boolean;
|
|
36
|
-
state: FilterState;
|
|
37
|
-
}
|
|
6
|
+
import FilterMobile from './Mobile/Filter';
|
|
7
|
+
import FilterDesktop from './Desktop/Filter';
|
|
38
8
|
|
|
39
9
|
const Filter = (props: FilterProps) => {
|
|
40
|
-
const {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
filtersDependOn,
|
|
44
|
-
isLoading,
|
|
45
|
-
onChange,
|
|
46
|
-
searchTooltip,
|
|
47
|
-
showAdd = true,
|
|
48
|
-
showFilterIcon = true,
|
|
49
|
-
showClearFilters = true,
|
|
50
|
-
showSearch,
|
|
51
|
-
state,
|
|
52
|
-
...rest
|
|
53
|
-
} = props;
|
|
54
|
-
|
|
55
|
-
const filterStyle = useMultiStyleConfig('Filter') as FilterStyle;
|
|
10
|
+
const { data, defaultState, filtersDependOn, isLoading, isMobile, onChange, showClearFilters = true, state } = props;
|
|
11
|
+
|
|
12
|
+
const [selectedCategory, setSelectedCategory] = useState<string | undefined>();
|
|
56
13
|
|
|
57
14
|
const cleanState: FilterState = {};
|
|
58
15
|
Object.entries(state).forEach(([category, values]) => {
|
|
@@ -106,13 +63,13 @@ const Filter = (props: FilterProps) => {
|
|
|
106
63
|
onChange(stateToRestore || {});
|
|
107
64
|
};
|
|
108
65
|
|
|
109
|
-
const filters = {
|
|
66
|
+
const filters: Filters = {
|
|
110
67
|
dateRange: {},
|
|
111
68
|
search: {},
|
|
112
69
|
select: {},
|
|
113
70
|
switch: {},
|
|
114
71
|
tag: {},
|
|
115
|
-
}
|
|
72
|
+
};
|
|
116
73
|
|
|
117
74
|
Object.entries(data).forEach(([category, value]) => {
|
|
118
75
|
filters[value.type || 'tag'][category] = value;
|
|
@@ -123,11 +80,17 @@ const Filter = (props: FilterProps) => {
|
|
|
123
80
|
const contextValue: FilterContextType = useMemo(
|
|
124
81
|
() => ({
|
|
125
82
|
data,
|
|
83
|
+
filters,
|
|
126
84
|
filtersDependOn,
|
|
85
|
+
isPopoverOpen,
|
|
127
86
|
isLoading,
|
|
87
|
+
onClearFilters,
|
|
128
88
|
onFilterChange,
|
|
129
89
|
onFilterClear,
|
|
90
|
+
selectedCategory,
|
|
91
|
+
showClearButton,
|
|
130
92
|
setPopoverOpen,
|
|
93
|
+
setSelectedCategory,
|
|
131
94
|
state: cleanState,
|
|
132
95
|
}),
|
|
133
96
|
[data, filtersDependOn, isLoading, onFilterChange, onFilterClear, setPopoverOpen, cleanState],
|
|
@@ -135,60 +98,7 @@ const Filter = (props: FilterProps) => {
|
|
|
135
98
|
|
|
136
99
|
return (
|
|
137
100
|
<FilterContext value={contextValue}>
|
|
138
|
-
<
|
|
139
|
-
<Modal isOpen={isPopoverOpen} onClose={() => {}}>
|
|
140
|
-
{isPopoverOpen && <ModalOverlay />}
|
|
141
|
-
</Modal>
|
|
142
|
-
<Box sx={filterStyle.content}>
|
|
143
|
-
{showFilterIcon && <Icon name="Filter" sx={filterStyle.icon} />}
|
|
144
|
-
|
|
145
|
-
{Object.keys(filters.switch).map((category) => (
|
|
146
|
-
<FilterSwitchAdapter key={category} category={category} />
|
|
147
|
-
))}
|
|
148
|
-
|
|
149
|
-
{Object.keys(filters.dateRange).map((category) => (
|
|
150
|
-
<FilterDate key={category} category={category} />
|
|
151
|
-
))}
|
|
152
|
-
|
|
153
|
-
{Object.keys(filters.select).map((category) => (
|
|
154
|
-
<FilterItem key={category} category={category} />
|
|
155
|
-
))}
|
|
156
|
-
{Object.keys(filters.tag).map((category) => {
|
|
157
|
-
if (!cleanState[category]) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
return <FilterItem key={category} category={category} />;
|
|
161
|
-
})}
|
|
162
|
-
|
|
163
|
-
{showAdd && <FilterAdd onChange={onFilterChange} />}
|
|
164
|
-
</Box>
|
|
165
|
-
{(showClearButton || showSearch) && (
|
|
166
|
-
<Box sx={filterStyle.rightContent}>
|
|
167
|
-
{showClearButton && (
|
|
168
|
-
<Button
|
|
169
|
-
isDisabled={isLoading}
|
|
170
|
-
leftIconName="Cross"
|
|
171
|
-
minWidth="7.5rem"
|
|
172
|
-
onClick={onClearFilters}
|
|
173
|
-
size="sm"
|
|
174
|
-
variant="tertiary"
|
|
175
|
-
>
|
|
176
|
-
Clear filters
|
|
177
|
-
</Button>
|
|
178
|
-
)}
|
|
179
|
-
{showSearch && (
|
|
180
|
-
<>
|
|
181
|
-
<Divider flexShrink="0" orientation="vertical" size="1" variant="solid" />
|
|
182
|
-
<FilterSearch
|
|
183
|
-
onChange={onFilterChange}
|
|
184
|
-
value={cleanState.search?.length ? cleanState.search[0] : ''}
|
|
185
|
-
searchTooltip={searchTooltip}
|
|
186
|
-
/>
|
|
187
|
-
</>
|
|
188
|
-
)}
|
|
189
|
-
</Box>
|
|
190
|
-
)}
|
|
191
|
-
</Box>
|
|
101
|
+
{isMobile ? <FilterMobile {...props} /> : <FilterDesktop {...props} />}
|
|
192
102
|
</FilterContext>
|
|
193
103
|
);
|
|
194
104
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Dispatch, SetStateAction } from 'react';
|
|
1
|
+
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
|
2
2
|
import { TypeIconName } from '../Icon/Icon';
|
|
3
3
|
import { DatePickerProps, DateRange } from '../DatePicker/DatePicker';
|
|
4
|
+
import { BoxProps } from '../Box/Box';
|
|
4
5
|
|
|
5
6
|
export type FilterType = 'dateRange' | 'search' | 'select' | 'switch' | 'tag';
|
|
6
7
|
|
|
@@ -58,10 +59,42 @@ export type FilterState = Record<string, FilterValue>;
|
|
|
58
59
|
|
|
59
60
|
export interface FilterContextType {
|
|
60
61
|
data: FilterData;
|
|
62
|
+
filters: Filters;
|
|
61
63
|
filtersDependOn?: string[];
|
|
62
64
|
isLoading?: boolean;
|
|
65
|
+
isPopoverOpen: boolean;
|
|
66
|
+
onClearFilters: () => void;
|
|
63
67
|
onFilterClear: (category: string) => void;
|
|
64
68
|
onFilterChange: (category: string, value: FilterValue) => void;
|
|
69
|
+
selectedCategory?: string;
|
|
65
70
|
setPopoverOpen: Dispatch<SetStateAction<boolean>>;
|
|
71
|
+
setSelectedCategory: Dispatch<SetStateAction<string | undefined>>;
|
|
72
|
+
showClearButton?: boolean;
|
|
66
73
|
state: FilterState;
|
|
67
74
|
}
|
|
75
|
+
|
|
76
|
+
export type Filters = Record<FilterType, Record<string, FilterCategoryProps>>;
|
|
77
|
+
|
|
78
|
+
export interface FilterProps extends Omit<BoxProps, 'onChange'> {
|
|
79
|
+
data: FilterData;
|
|
80
|
+
defaultState?: FilterState;
|
|
81
|
+
filtersDependOn?: string[];
|
|
82
|
+
isLoading?: boolean;
|
|
83
|
+
isMobile?: boolean;
|
|
84
|
+
onChange: (state: FilterState) => void;
|
|
85
|
+
searchTooltip?: ReactNode;
|
|
86
|
+
showAdd?: boolean;
|
|
87
|
+
showFilterIcon?: boolean;
|
|
88
|
+
showClearFilters?: boolean;
|
|
89
|
+
showSearch?: boolean;
|
|
90
|
+
state: FilterState;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type FilterFormProps = {
|
|
94
|
+
category: string;
|
|
95
|
+
categoryName?: string;
|
|
96
|
+
categoryNamePlural?: string;
|
|
97
|
+
loadingText?: string;
|
|
98
|
+
onChange: (category: string, selected: FilterValue, previousValue: FilterValue) => void;
|
|
99
|
+
onCancel: () => void;
|
|
100
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
import { DateRange } from '../DatePicker/DatePicker';
|
|
1
3
|
import { FilterData, FilterOptionsMap } from './Filter.types';
|
|
2
4
|
|
|
3
5
|
export const hasAllDependencies = (stateKeys: string[], dependsOn?: string[]): boolean => {
|
|
@@ -38,3 +40,14 @@ export const getOptionLabel = (option: string, optionsMap?: FilterOptionsMap) =>
|
|
|
38
40
|
}
|
|
39
41
|
return optionsMap[option] || option;
|
|
40
42
|
};
|
|
43
|
+
|
|
44
|
+
export const getDateRangeLabel = (value: string[] | undefined): string => {
|
|
45
|
+
const selectedRange: DateRange | undefined =
|
|
46
|
+
value && value[0]
|
|
47
|
+
? new DateRange(DateTime.fromMillis(Number(value[0])), DateTime.fromMillis(Number(value[1])))
|
|
48
|
+
: undefined;
|
|
49
|
+
|
|
50
|
+
return selectedRange
|
|
51
|
+
? `${selectedRange.from?.toFormat('LLL dd')} - ${selectedRange.to?.toFormat('LLL dd')}`
|
|
52
|
+
: 'All dates';
|
|
53
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
import Box from '../../Box/Box';
|
|
3
|
+
import DatePicker, { DatePickerProps, DateRange, useDateRange } from '../../DatePicker/DatePicker';
|
|
4
|
+
import { useFilterContext } from '../Filter.context';
|
|
5
|
+
import { FilterCategoryProps } from '../Filter.types';
|
|
6
|
+
|
|
7
|
+
type DateSelectOptionProps = {
|
|
8
|
+
onClear: () => void;
|
|
9
|
+
onChange: (dateRange: string[]) => void;
|
|
10
|
+
selectedItems: string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DateSelectOption = ({ onClear, onChange, selectedItems }: DateSelectOptionProps) => {
|
|
14
|
+
const { data } = useFilterContext();
|
|
15
|
+
|
|
16
|
+
const dateRangeData = data.date_range as FilterCategoryProps & {
|
|
17
|
+
dayTooltip?: DatePickerProps['dayTooltip'];
|
|
18
|
+
selectable?: DateRange;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const handleChange = (range: DateRange) => {
|
|
22
|
+
if (range.from && range.to) {
|
|
23
|
+
onChange([String(range.from.toMillis()), String(range.to.endOf('day').toMillis())]);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const now = DateTime.local();
|
|
28
|
+
const twoYearsAgo = now.minus({ years: 2 });
|
|
29
|
+
|
|
30
|
+
const selectable = useDateRange(twoYearsAgo, now);
|
|
31
|
+
|
|
32
|
+
const selectedRange: DateRange | undefined =
|
|
33
|
+
selectedItems && selectedItems[0]
|
|
34
|
+
? new DateRange(DateTime.fromMillis(Number(selectedItems[0])), DateTime.fromMillis(Number(selectedItems[1])))
|
|
35
|
+
: undefined;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Box display="flex" justifyContent="center">
|
|
39
|
+
<DatePicker
|
|
40
|
+
dayTooltip={dateRangeData.dayTooltip}
|
|
41
|
+
mode="range"
|
|
42
|
+
onApply={handleChange}
|
|
43
|
+
onClear={selectedItems?.length ? onClear : undefined}
|
|
44
|
+
selectable={dateRangeData.selectable || selectable}
|
|
45
|
+
selected={selectedRange}
|
|
46
|
+
variant="inline"
|
|
47
|
+
visible
|
|
48
|
+
/>
|
|
49
|
+
</Box>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default DateSelectOption;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import Badge from '../../Badge/Badge';
|
|
2
|
+
import Text from '../../Text/Text';
|
|
3
|
+
import Button from '../../Button/Button';
|
|
4
|
+
import Box from '../../Box/Box';
|
|
5
|
+
import { useFilterContext } from '../Filter.context';
|
|
6
|
+
import { FilterProps } from '../Filter.types';
|
|
7
|
+
import FilterDrawer from './FilterDrawer';
|
|
8
|
+
import FilterForm from './FilterForm';
|
|
9
|
+
|
|
10
|
+
const Filter = ({ isLoading, showAdd = true }: FilterProps) => {
|
|
11
|
+
const {
|
|
12
|
+
isPopoverOpen,
|
|
13
|
+
onClearFilters,
|
|
14
|
+
selectedCategory,
|
|
15
|
+
setSelectedCategory,
|
|
16
|
+
setPopoverOpen,
|
|
17
|
+
showClearButton,
|
|
18
|
+
state,
|
|
19
|
+
} = useFilterContext();
|
|
20
|
+
|
|
21
|
+
const count = Object.values(state).filter((item) => item?.length > 0).length;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
<Box alignItems="center" display="flex" justifyContent="space-between">
|
|
26
|
+
<Button leftIconName="Filter" onClick={() => setPopoverOpen(true)} size="sm" variant="secondary">
|
|
27
|
+
<Text alignItems="center" display="flex" gap="8">
|
|
28
|
+
Filters
|
|
29
|
+
<Badge colorScheme={count > 0 ? 'progress' : 'neutral'} variant="subtle">
|
|
30
|
+
{String(count)}
|
|
31
|
+
</Badge>
|
|
32
|
+
</Text>
|
|
33
|
+
</Button>
|
|
34
|
+
{showClearButton && (
|
|
35
|
+
<Button
|
|
36
|
+
isDisabled={isLoading}
|
|
37
|
+
leftIconName="Cross"
|
|
38
|
+
minWidth="7.5rem"
|
|
39
|
+
onClick={onClearFilters}
|
|
40
|
+
size="sm"
|
|
41
|
+
variant="tertiary"
|
|
42
|
+
>
|
|
43
|
+
Clear filters
|
|
44
|
+
</Button>
|
|
45
|
+
)}
|
|
46
|
+
</Box>
|
|
47
|
+
<FilterDrawer isOpen={isPopoverOpen} onClose={() => setPopoverOpen(false)} showAdd={showAdd} />
|
|
48
|
+
<FilterForm
|
|
49
|
+
isOpen={Boolean(selectedCategory)}
|
|
50
|
+
onClose={() => setSelectedCategory(undefined)}
|
|
51
|
+
selectedCategory={selectedCategory || ''}
|
|
52
|
+
/>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default Filter;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Modal, ModalOverlay, Portal } from 'chakra-ui-2--react';
|
|
2
|
+
import Menu from '../../Menu/Menu';
|
|
3
|
+
import MenuItem from '../../Menu/MenuItem';
|
|
4
|
+
import Button from '../../Button/Button';
|
|
5
|
+
import Box from '../../Box/Box';
|
|
6
|
+
import Text from '../../Text/Text';
|
|
7
|
+
import { getMissingDependencies } from '../Filter.utils';
|
|
8
|
+
import useFilterAdd from '../hooks/useFilterAdd';
|
|
9
|
+
import { useFilterContext } from '../Filter.context';
|
|
10
|
+
import FilterForm from './FilterForm';
|
|
11
|
+
|
|
12
|
+
const FilterAdd = () => {
|
|
13
|
+
const { data } = useFilterContext();
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
categoryList,
|
|
17
|
+
closeMenu,
|
|
18
|
+
isDisabled,
|
|
19
|
+
isMenuOpen,
|
|
20
|
+
onCategorySelect,
|
|
21
|
+
onOpen,
|
|
22
|
+
selectedCategory,
|
|
23
|
+
setSelectedCategory,
|
|
24
|
+
stateKeys,
|
|
25
|
+
} = useFilterAdd({ isMobile: true });
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<Modal isOpen={isMenuOpen} onClose={() => closeMenu()} trapFocus={false} autoFocus={false}>
|
|
30
|
+
{isMenuOpen && <ModalOverlay onClick={() => closeMenu()} zIndex="popover" />}
|
|
31
|
+
</Modal>
|
|
32
|
+
<Button alignSelf="flex-start" isDisabled={isDisabled} variant="tertiary" leftIconName="Plus" onClick={onOpen}>
|
|
33
|
+
Add filter
|
|
34
|
+
</Button>
|
|
35
|
+
<FilterForm
|
|
36
|
+
isOpen={Boolean(selectedCategory)}
|
|
37
|
+
onClose={() => setSelectedCategory(undefined)}
|
|
38
|
+
selectedCategory={selectedCategory || ''}
|
|
39
|
+
/>
|
|
40
|
+
{isMenuOpen && (
|
|
41
|
+
<Portal>
|
|
42
|
+
<Box
|
|
43
|
+
bg="white"
|
|
44
|
+
borderRadius="8px"
|
|
45
|
+
bottom="12px"
|
|
46
|
+
boxShadow="0 4px 12px rgba(0, 0, 0, 0.15)"
|
|
47
|
+
left="12px"
|
|
48
|
+
maxHeight="75vh"
|
|
49
|
+
overflowY="auto"
|
|
50
|
+
paddingY="8"
|
|
51
|
+
position="fixed"
|
|
52
|
+
right="12px"
|
|
53
|
+
sx={{
|
|
54
|
+
'@keyframes slideUp': {
|
|
55
|
+
from: { transform: 'translateY(100%)', opacity: 0 },
|
|
56
|
+
to: { transform: 'translateY(0)', opacity: 1 },
|
|
57
|
+
},
|
|
58
|
+
animation: 'slideUp 0.3s ease-out',
|
|
59
|
+
}}
|
|
60
|
+
zIndex="tooltip"
|
|
61
|
+
>
|
|
62
|
+
<Menu>
|
|
63
|
+
<Text
|
|
64
|
+
color="text/tertiary"
|
|
65
|
+
paddingBlock="8"
|
|
66
|
+
paddingInline="16"
|
|
67
|
+
textStyle="heading/h6"
|
|
68
|
+
textTransform="uppercase"
|
|
69
|
+
>
|
|
70
|
+
Select filter
|
|
71
|
+
</Text>
|
|
72
|
+
{categoryList.map((category) => {
|
|
73
|
+
const { categoryName, dependsOn } = data[category];
|
|
74
|
+
const missingDependencies = getMissingDependencies(stateKeys, Object.keys(dependsOn || []));
|
|
75
|
+
const isCategoryDisabled = missingDependencies.length > 0 || !data[category]?.options?.length;
|
|
76
|
+
return (
|
|
77
|
+
<MenuItem
|
|
78
|
+
isDisabled={isCategoryDisabled}
|
|
79
|
+
key={category}
|
|
80
|
+
onClick={() => onCategorySelect(category)}
|
|
81
|
+
pointerEvents="all"
|
|
82
|
+
rightIconName="ChevronRight"
|
|
83
|
+
rightIconColor="neutral.60"
|
|
84
|
+
>
|
|
85
|
+
{categoryName || category}
|
|
86
|
+
</MenuItem>
|
|
87
|
+
);
|
|
88
|
+
})}
|
|
89
|
+
</Menu>
|
|
90
|
+
</Box>
|
|
91
|
+
</Portal>
|
|
92
|
+
)}
|
|
93
|
+
</>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default FilterAdd;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import ButtonGroup from '../../ButtonGroup/ButtonGroup';
|
|
3
|
+
import type { DialogProps } from '../../Dialog/DialogProps';
|
|
4
|
+
import Box from '../../Box/Box';
|
|
5
|
+
import Button from '../../Button/Button';
|
|
6
|
+
import Drawer from '../../Drawer/Drawer';
|
|
7
|
+
import Divider from '../../Divider/Divider';
|
|
8
|
+
import Text from '../../Text/Text';
|
|
9
|
+
import { useFilterContext } from '../Filter.context';
|
|
10
|
+
import FilterItem from './FilterItem';
|
|
11
|
+
import FilterAdd from './FilterAdd';
|
|
12
|
+
|
|
13
|
+
const FilterDrawer = ({
|
|
14
|
+
isOpen,
|
|
15
|
+
onClose,
|
|
16
|
+
showAdd,
|
|
17
|
+
}: Pick<DialogProps, 'isOpen' | 'onClose'> & { showAdd?: boolean }) => {
|
|
18
|
+
const { filters, state, onClearFilters, showClearButton } = useFilterContext();
|
|
19
|
+
|
|
20
|
+
const initialFocusRef = useRef(null);
|
|
21
|
+
|
|
22
|
+
const onClear = () => {
|
|
23
|
+
onClearFilters();
|
|
24
|
+
onClose();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Drawer
|
|
29
|
+
blockScrollOnMount={false}
|
|
30
|
+
bodyPadding={16}
|
|
31
|
+
bodyProps={{ overflowY: 'auto' }}
|
|
32
|
+
contentProps={{ zIndex: 'fullDialog', top: '0' }}
|
|
33
|
+
headerPadding={0}
|
|
34
|
+
initialFocusRef={initialFocusRef}
|
|
35
|
+
isOpen={isOpen}
|
|
36
|
+
maxWidth="100%"
|
|
37
|
+
onClose={onClose}
|
|
38
|
+
overlayProps={{ zIndex: 'fullDialogOverlay' }}
|
|
39
|
+
padding={0}
|
|
40
|
+
title={
|
|
41
|
+
<>
|
|
42
|
+
<Text color="text/primary" padding="16" textStyle="heading/mobile/h2" textTransform="none">
|
|
43
|
+
Filters
|
|
44
|
+
</Text>
|
|
45
|
+
<Divider />
|
|
46
|
+
</>
|
|
47
|
+
}
|
|
48
|
+
footer={
|
|
49
|
+
<Box width="100%">
|
|
50
|
+
<Divider />
|
|
51
|
+
<ButtonGroup
|
|
52
|
+
display="flex"
|
|
53
|
+
justifyContent="space-between"
|
|
54
|
+
gap="12"
|
|
55
|
+
paddingBlock="12"
|
|
56
|
+
paddingInline="16"
|
|
57
|
+
width="100%"
|
|
58
|
+
ref={initialFocusRef}
|
|
59
|
+
>
|
|
60
|
+
<Button isDisabled={!showClearButton} variant="secondary" onClick={onClear} flex="1" size="md">
|
|
61
|
+
Clear filters
|
|
62
|
+
</Button>
|
|
63
|
+
<Button type="submit" flex="1" size="md" onClick={onClose}>
|
|
64
|
+
Show results
|
|
65
|
+
</Button>
|
|
66
|
+
</ButtonGroup>
|
|
67
|
+
</Box>
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
<Box display="flex" flexDirection="column" gap="16">
|
|
71
|
+
{Object.keys(filters.dateRange).map((category) => (
|
|
72
|
+
<FilterItem key={category} category={category} />
|
|
73
|
+
))}
|
|
74
|
+
|
|
75
|
+
{Object.keys(filters.switch).map((category) => (
|
|
76
|
+
<FilterItem key={category} category={category} />
|
|
77
|
+
))}
|
|
78
|
+
|
|
79
|
+
{Object.keys(filters.select).map((category) => (
|
|
80
|
+
<FilterItem key={category} category={category} />
|
|
81
|
+
))}
|
|
82
|
+
|
|
83
|
+
{Object.keys(filters.tag).map((category) => {
|
|
84
|
+
if (!state[category]) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
return <FilterItem key={category} category={category} />;
|
|
88
|
+
})}
|
|
89
|
+
|
|
90
|
+
{showAdd && <FilterAdd />}
|
|
91
|
+
</Box>
|
|
92
|
+
</Drawer>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default FilterDrawer;
|