@bitrise/bitkit 13.318.0 → 13.320.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.
Files changed (39) hide show
  1. package/package.json +19 -19
  2. package/src/Components/DatePicker/DatePicker.tsx +59 -54
  3. package/src/Components/Dialog/DialogProps.ts +1 -2
  4. package/src/Components/Drawer/Drawer.tsx +23 -7
  5. package/src/Components/ExpandableCard/ExpandableCard.tsx +23 -24
  6. package/src/Components/Filter/Desktop/Filter.tsx +92 -0
  7. package/src/Components/Filter/Desktop/FilterAdd/FilterAdd.tsx +89 -0
  8. package/src/Components/Filter/{FilterDate → Desktop/FilterDate}/FilterDate.tsx +21 -10
  9. package/src/Components/Filter/{FilterForm → Desktop}/FilterForm.tsx +24 -115
  10. package/src/Components/Filter/{FilterItem → Desktop}/FilterItem.tsx +1 -1
  11. package/src/Components/Filter/{FilterSwitch → Desktop/FilterSwitch}/FilterSwitch.theme.ts +1 -1
  12. package/src/Components/Filter/{FilterSwitch → Desktop/FilterSwitch}/FilterSwitch.tsx +4 -4
  13. package/src/Components/Filter/{FilterSwitchAdapter → Desktop/FilterSwitch}/FilterSwitchAdapter.tsx +5 -5
  14. package/src/Components/Filter/Filter.storyData.ts +14 -1
  15. package/src/Components/Filter/Filter.tsx +16 -106
  16. package/src/Components/Filter/Filter.types.ts +34 -1
  17. package/src/Components/Filter/Filter.utils.ts +13 -0
  18. package/src/Components/Filter/Mobile/DateSelectOption.tsx +53 -0
  19. package/src/Components/Filter/Mobile/Filter.tsx +57 -0
  20. package/src/Components/Filter/Mobile/FilterAdd.tsx +97 -0
  21. package/src/Components/Filter/Mobile/FilterDrawer.tsx +96 -0
  22. package/src/Components/Filter/Mobile/FilterForm.tsx +236 -0
  23. package/src/Components/Filter/Mobile/FilterItem.tsx +95 -0
  24. package/src/Components/Filter/Mobile/MultiSelectOptions.tsx +69 -0
  25. package/src/Components/Filter/Mobile/SingleSelectOptions.tsx +136 -0
  26. package/src/Components/Filter/hooks/useFilterAdd.ts +68 -0
  27. package/src/Components/Filter/hooks/useFilterForm.ts +131 -0
  28. package/src/Components/Filter/hooks/useIsScrollable.ts +35 -0
  29. package/src/Components/Filter/hooks/useListBox.ts +66 -0
  30. package/src/Components/Form/Checkbox/Checkbox.tsx +4 -2
  31. package/src/Components/Form/Input/Input.theme.ts +27 -11
  32. package/src/Components/Form/Input/Input.tsx +4 -1
  33. package/src/Components/Form/Radio/Radio.tsx +4 -2
  34. package/src/Components/SearchInput/SearchInput.tsx +3 -2
  35. package/src/Components/components.theme.ts +1 -1
  36. package/src/index.ts +3 -4
  37. package/src/Components/Filter/FilterAdd/FilterAdd.tsx +0 -111
  38. /package/src/Components/Filter/{FilterSearch → Desktop}/FilterSearch.tsx +0 -0
  39. /package/src/Components/Filter/{FilterSwitch → Desktop/FilterSwitch}/FilterSwitchGroup.tsx +0 -0
@@ -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 { FilterOptions, FilterOptionsMap, FilterValue } from '../Filter.types';
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, onChange } = props;
89
+ const { category, categoryName, categoryNamePlural, loadingText, onCancel } = props;
102
90
 
103
91
  const filterStyle = useMultiStyleConfig('Filter') as FilterStyle;
104
92
 
105
- const { data, state } = useFilterContext();
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 onSubmit = (e: FormEvent<HTMLDivElement>) => {
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 onClearClick = () => {
166
- setSelected([]);
167
- onChange(category, [], value);
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;
@@ -1,4 +1,4 @@
1
- import { rem } from '../../../utils/utils';
1
+ import { rem } from '../../../../utils/utils';
2
2
 
3
3
  const FilterSwitch = {
4
4
  baseStyle: ({ isChecked, iconColor }: { isChecked: boolean; iconColor?: 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 '../../Divider/Divider';
13
- import Text from '../../Text/Text';
14
- import Box from '../../Box/Box';
15
- import Icon, { TypeIconName } from '../../Icon/Icon';
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;
@@ -1,9 +1,9 @@
1
1
  import { useMemo } from 'react';
2
- import { useFilterContext } from '../Filter.context';
3
- import { getOptionLabel } from '../Filter.utils';
4
- import FilterSwitch from '../FilterSwitch/FilterSwitch';
5
- import FilterSwitchGroup from '../FilterSwitch/FilterSwitchGroup';
6
- import { FilterCategoryProps } from '../Filter.types';
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: 'dates',
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 { ReactNode, useMemo, useState } from 'react';
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 { FilterStyle } from './Filter.theme';
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 FilterAdd from './FilterAdd/FilterAdd';
20
- import FilterItem from './FilterItem/FilterItem';
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
- data,
42
- defaultState,
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
- } as Record<FilterType, Record<string, FilterCategoryProps>>;
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
- <Box sx={filterStyle.container} {...rest}>
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;