@campxdev/react-blueprint 1.6.9 → 1.7.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campxdev/react-blueprint",
3
- "version": "1.6.9",
3
+ "version": "1.7.1",
4
4
  "main": "./export.ts",
5
5
  "private": false,
6
6
  "dependencies": {
@@ -1,18 +1,10 @@
1
- import {
2
- AutocompleteCloseReason,
3
- AutocompleteProps,
4
- Box,
5
- Autocomplete as MuiAutocomplete,
6
- Paper,
7
- } from '@mui/material';
1
+ import { AutocompleteCloseReason, AutocompleteProps } from '@mui/material';
8
2
  import axios, { AxiosInstance } from 'axios';
9
- import _, { debounce, startCase } from 'lodash';
10
- import { SyntheticEvent, useEffect, useMemo, useReducer } from 'react';
3
+ import _ from 'lodash';
4
+ import { SyntheticEvent, useEffect, useReducer } from 'react';
11
5
  import { Typography } from '../../DataDisplay/Typography/Typography';
12
- import { Spinner } from '../../Feedback/Spinner/Spinner';
13
- import { TextField } from '../TextField/TextField';
14
- import { OptionsLoader } from '../components/OptionsLoader';
15
- import { OptionContainer } from '../styles';
6
+ import { SingleFilter } from './components/SingleFilter';
7
+ import { SingleInput } from './components/SingleInput';
16
8
  import {
17
9
  SingleSelectActionsTypes,
18
10
  singleSelectReducer,
@@ -58,6 +50,7 @@ export type SingleSelectProps = {
58
50
  onChange?: (value: any) => void;
59
51
  error?: any;
60
52
  helperText?: string;
53
+ type?: 'input' | 'filter';
61
54
  } & Omit<
62
55
  AutocompleteProps<
63
56
  { label: string; subLabel?: string; value: any } | any,
@@ -75,32 +68,14 @@ export type SingleSelectProps = {
75
68
  | 'renderInput'
76
69
  >;
77
70
 
78
- const CustomPaper = (props: any) => (
79
- <Paper
80
- {...props}
81
- sx={{
82
- boxShadow: 'none',
83
- borderRadius: '0px 0px 4px 4px',
84
- }}
85
- >
86
- {props.children}
87
- <OptionsLoader loading={props.loadingOptions} isSearch={props.isSearch} />
88
- </Paper>
89
- );
90
-
91
71
  export const SingleSelect = ({
92
72
  options,
93
73
  optionsApiEndPoint,
94
74
  optionsApiEndpointParams,
95
75
  externalAxios,
96
- required = false,
97
- label,
98
- name,
99
76
  getValue,
100
77
  value,
101
78
  onChange,
102
- error,
103
- helperText,
104
79
  dbValueProps = {
105
80
  valueKey: 'id',
106
81
  isObjectId: false,
@@ -114,6 +89,7 @@ export const SingleSelect = ({
114
89
  },
115
90
  onOpen,
116
91
  onClose,
92
+ type = 'input',
117
93
  ...restProps
118
94
  }: SingleSelectProps) => {
119
95
  const generateOptionsMap = (options: any[]) => {
@@ -133,16 +109,7 @@ export const SingleSelect = ({
133
109
  };
134
110
 
135
111
  const [state, dispatch] = useReducer(singleSelectReducer, initialState);
136
- const {
137
- open,
138
- loadingInternalOptions,
139
- loadingInitialInternalOptions,
140
- internalOptions,
141
- limit,
142
- offset,
143
- hasMore,
144
- search,
145
- } = state;
112
+ const { internalOptions, limit, offset, hasMore, search } = state;
146
113
 
147
114
  const internalAxios = externalAxios || axios;
148
115
 
@@ -358,19 +325,6 @@ export const SingleSelect = ({
358
325
  }
359
326
  };
360
327
 
361
- const debouncedSendRequest = useMemo(() => {
362
- return debounce((searchValue) => {
363
- if (optionsApiEndPoint) searchDb(searchValue);
364
- }, 300);
365
- }, [searchDb]);
366
-
367
- const handleSearch = async (e: any) => {
368
- if (e) {
369
- const searchValue = e.target.value;
370
- debouncedSendRequest(searchValue);
371
- }
372
- };
373
-
374
328
  const loadSelectedOptions = async () => {
375
329
  dispatch({
376
330
  actionType: SingleSelectActionsTypes.LOAD_SELECTED_OPTIONS_START,
@@ -418,87 +372,40 @@ export const SingleSelect = ({
418
372
  }
419
373
  }, []);
420
374
 
421
- if (loadingInitialInternalOptions) {
422
- return (
423
- <TextField
424
- label={label}
425
- name={name}
426
- required={required}
427
- InputProps={{
428
- endAdornment: <Spinner />,
429
- }}
430
- error={error}
431
- helperText={helperText}
432
- />
433
- );
434
- }
435
-
436
375
  if (!onChange) {
437
376
  return <Typography>onChange Missing</Typography>;
438
377
  }
439
378
 
379
+ if (type === 'filter')
380
+ return (
381
+ <SingleFilter
382
+ onChange={onChange}
383
+ searchDb={searchDb}
384
+ handleOpen={handleOpen}
385
+ handleClose={handleClose}
386
+ handleScroll={handleScroll}
387
+ state={state}
388
+ optionsApiEndPoint={optionsApiEndPoint}
389
+ getValue={getValue}
390
+ value={value}
391
+ dbLabelProps={dbLabelProps}
392
+ {...restProps}
393
+ />
394
+ );
395
+
440
396
  return (
441
- <MuiAutocomplete
397
+ <SingleInput
398
+ onChange={onChange}
399
+ searchDb={searchDb}
400
+ handleOpen={handleOpen}
401
+ handleClose={handleClose}
402
+ handleScroll={handleScroll}
403
+ state={state}
404
+ optionsApiEndPoint={optionsApiEndPoint}
405
+ getValue={getValue}
406
+ value={value}
407
+ dbLabelProps={dbLabelProps}
442
408
  {...restProps}
443
- onChange={(e, value) => {
444
- onChange(getValue ? getValue(value) : value?.value);
445
- }}
446
- onInputCapture={handleSearch}
447
- open={open}
448
- autoFocus={true}
449
- filterOptions={(options, state) => {
450
- if (optionsApiEndPoint) {
451
- return options;
452
- }
453
- return options.filter((option) =>
454
- option.label.toLowerCase().includes(state.inputValue.toLowerCase()),
455
- );
456
- }}
457
- value={state.internalOptionsMap[value]}
458
- PaperComponent={CustomPaper}
459
- renderOption={(props, option: any) => {
460
- return (
461
- <Box component="li" {...props} key={option.value}>
462
- <OptionContainer>
463
- <Typography variant="label1">
464
- {dbLabelProps.useLabelStartCase
465
- ? startCase(option.label)
466
- : option.label}
467
- </Typography>
468
- <Typography variant="caption">
469
- {dbLabelProps.useSubLabelStartCase
470
- ? startCase(option?.subLabel)
471
- : option?.subLabel}
472
- </Typography>
473
- </OptionContainer>
474
- </Box>
475
- );
476
- }}
477
- ListboxProps={{
478
- ...restProps.ListboxProps,
479
- onScroll: handleScroll,
480
- }}
481
- slotProps={{
482
- ...restProps.slotProps,
483
- paper: {
484
- ...restProps.slotProps?.paper,
485
- loadingOptions: loadingInternalOptions,
486
- isSearch: !!state.search,
487
- },
488
- }}
489
- onOpen={handleOpen}
490
- onClose={handleClose}
491
- options={internalOptions}
492
- renderInput={(params) => (
493
- <TextField
494
- {...params}
495
- label={label}
496
- required={required}
497
- name={name}
498
- error={error}
499
- helperText={helperText}
500
- />
501
- )}
502
409
  />
503
410
  );
504
411
  };
@@ -0,0 +1,228 @@
1
+ import { KeyboardArrowDown } from '@mui/icons-material';
2
+ import {
3
+ AutocompleteCloseReason,
4
+ FormControlLabel,
5
+ RadioGroup as MuiRadioGroup,
6
+ Paper,
7
+ Radio,
8
+ useTheme,
9
+ } from '@mui/material';
10
+ import { startCase } from 'lodash';
11
+ import { SyntheticEvent, useState } from 'react';
12
+ import {
13
+ Button,
14
+ DropdownMenu,
15
+ Icons,
16
+ SearchBar,
17
+ SingleSelectProps,
18
+ Typography,
19
+ } from '../../../export';
20
+ import { OptionsLoader } from '../../components/OptionsLoader';
21
+ import { OptionContainer } from '../../styles';
22
+ import { SingleSelectState } from '../singleSelectReducer';
23
+
24
+ declare module '@mui/material/Autocomplete' {
25
+ interface AutocompletePaperSlotPropsOverrides {
26
+ loadingOptions: boolean;
27
+ isSearch: boolean;
28
+ }
29
+ }
30
+
31
+ const CustomPaper = (props: any) => (
32
+ <Paper
33
+ {...props}
34
+ sx={{
35
+ boxShadow: 'none',
36
+ borderRadius: '0px 0px 4px 4px',
37
+ }}
38
+ >
39
+ {props.children}
40
+ <OptionsLoader loading={props.loadingOptions} isSearch={props.isSearch} />
41
+ </Paper>
42
+ );
43
+
44
+ export type SingleFilterProps = {
45
+ onChange: (value: any) => void;
46
+ searchDb: (searchValue: string) => Promise<void>;
47
+ handleOpen: (event: SyntheticEvent) => Promise<void>;
48
+ handleClose: (event: SyntheticEvent, reason: AutocompleteCloseReason) => void;
49
+ handleScroll: (event: any) => Promise<void>;
50
+ state: SingleSelectState;
51
+ } & SingleSelectProps;
52
+
53
+ export const SingleFilter = ({
54
+ options,
55
+ optionsApiEndPoint,
56
+ optionsApiEndpointParams,
57
+ externalAxios,
58
+ required = false,
59
+ label,
60
+ name,
61
+ getValue,
62
+ value,
63
+ onChange,
64
+ error,
65
+ helperText,
66
+ dbValueProps = {
67
+ valueKey: 'id',
68
+ isObjectId: false,
69
+ isInt: false,
70
+ isFloat: false,
71
+ },
72
+ dbLabelProps = {
73
+ labelKey: '',
74
+ useLabelStartCase: false,
75
+ useSubLabelStartCase: false,
76
+ },
77
+ onOpen,
78
+ onClose,
79
+ searchDb,
80
+ handleOpen,
81
+ handleClose,
82
+ handleScroll,
83
+ state,
84
+ ...restProps
85
+ }: SingleFilterProps) => {
86
+ const {
87
+ loadingInitialInternalOptions,
88
+ internalOptions,
89
+ internalOptionsMap,
90
+ loadingInternalOptions,
91
+ search,
92
+ } = state;
93
+
94
+ const [localSearch, setLocalSearch] = useState(search);
95
+
96
+ const searchLocal = (searchValue: string) => {
97
+ setLocalSearch(searchValue);
98
+ };
99
+
100
+ const theme = useTheme();
101
+
102
+ const validValue = !!internalOptionsMap[value];
103
+
104
+ return (
105
+ <>
106
+ <DropdownMenu
107
+ menuListProps={{
108
+ sx: {
109
+ padding: '12px',
110
+ width: '250px',
111
+ },
112
+ }}
113
+ anchor={({ open }: { open: (e: any) => void }) => (
114
+ <Button
115
+ onClick={(e) => {
116
+ open(e);
117
+ handleOpen(e);
118
+ }}
119
+ loading={loadingInitialInternalOptions}
120
+ sx={{
121
+ gap: '1px',
122
+ borderRadius: '20px',
123
+ }}
124
+ variant="contained"
125
+ background={
126
+ validValue
127
+ ? theme.palette.highlight.greenBackground
128
+ : theme.palette.surface.grey
129
+ }
130
+ hoverBackground={
131
+ validValue
132
+ ? theme.palette.highlight.greenBackground
133
+ : theme.palette.surface.grey
134
+ }
135
+ color={theme.palette.text.primary}
136
+ endIcon={<KeyboardArrowDown />}
137
+ >
138
+ {validValue
139
+ ? label + ' = ' + internalOptionsMap[value].label
140
+ : label}
141
+ </Button>
142
+ )}
143
+ menuProps={{
144
+ anchorOrigin: {
145
+ vertical: 'bottom',
146
+ horizontal: 'left',
147
+ },
148
+ transformOrigin: {
149
+ vertical: 'top',
150
+ horizontal: 'left',
151
+ },
152
+ }}
153
+ menuHeader={
154
+ <SearchBar
155
+ fullWidth
156
+ size="small"
157
+ placeholder="Search"
158
+ onSearch={optionsApiEndPoint ? searchDb : searchLocal}
159
+ />
160
+ }
161
+ menuListContainerSx={{
162
+ gap: '10px',
163
+ minHeight: '310px',
164
+ maxHeight: '310px',
165
+ overflow: 'scroll',
166
+ overflowY: 'auto',
167
+ marginTop: '10px',
168
+ }}
169
+ handleMenuScroll={handleScroll}
170
+ menuSlot={({ close }) => {
171
+ return (
172
+ <MuiRadioGroup
173
+ onChange={(e) => {
174
+ onChange(e.target.value);
175
+ }}
176
+ name={name}
177
+ value={value}
178
+ >
179
+ {internalOptions
180
+ ?.filter(
181
+ (option) =>
182
+ (localSearch && option.label.includes(localSearch)) ||
183
+ !localSearch,
184
+ )
185
+ ?.map((option, index) => (
186
+ <FormControlLabel
187
+ sx={{
188
+ margin: '0px',
189
+ width: '100%',
190
+ '.MuiFormControlLabel-label': {
191
+ width: '100%',
192
+ },
193
+ }}
194
+ key={index}
195
+ value={option.value}
196
+ control={
197
+ <Radio
198
+ checkedIcon={<Icons.CheckedRadioIcon />}
199
+ icon={<Icons.UnCheckedRadioIcon />}
200
+ />
201
+ }
202
+ label={
203
+ <OptionContainer>
204
+ <Typography variant="subtitle3">
205
+ {dbLabelProps.useLabelStartCase
206
+ ? startCase(option.label)
207
+ : option.label}
208
+ </Typography>
209
+ <Typography variant="caption">
210
+ {dbLabelProps.useSubLabelStartCase
211
+ ? startCase(option?.subLabel)
212
+ : option?.subLabel}
213
+ </Typography>
214
+ </OptionContainer>
215
+ }
216
+ />
217
+ ))}
218
+ </MuiRadioGroup>
219
+ );
220
+ }}
221
+ menuFooter={
222
+ <OptionsLoader loading={loadingInternalOptions} isSearch={!!search} />
223
+ }
224
+ useMenuSlot
225
+ />
226
+ </>
227
+ );
228
+ };
@@ -0,0 +1,165 @@
1
+ import {
2
+ AutocompleteCloseReason,
3
+ Box,
4
+ Autocomplete as MuiAutocomplete,
5
+ Paper,
6
+ } from '@mui/material';
7
+ import { debounce, startCase } from 'lodash';
8
+ import { SyntheticEvent, useMemo } from 'react';
9
+ import { Typography } from '../../../DataDisplay/Typography/Typography';
10
+ import { SingleSelectProps, Spinner } from '../../../export';
11
+ import { TextField } from '../../TextField/TextField';
12
+ import { OptionsLoader } from '../../components/OptionsLoader';
13
+ import { OptionContainer } from '../../styles';
14
+ import { SingleSelectState } from '../singleSelectReducer';
15
+
16
+ declare module '@mui/material/Autocomplete' {
17
+ interface AutocompletePaperSlotPropsOverrides {
18
+ loadingOptions: boolean;
19
+ isSearch: boolean;
20
+ }
21
+ }
22
+
23
+ const CustomPaper = (props: any) => (
24
+ <Paper
25
+ {...props}
26
+ sx={{
27
+ boxShadow: 'none',
28
+ borderRadius: '0px 0px 4px 4px',
29
+ }}
30
+ >
31
+ {props.children}
32
+ <OptionsLoader loading={props.loadingOptions} isSearch={props.isSearch} />
33
+ </Paper>
34
+ );
35
+
36
+ export type SingleInputProps = {
37
+ onChange: (value: any) => void;
38
+ searchDb: (searchValue: string) => Promise<void>;
39
+ handleOpen: (event: SyntheticEvent) => Promise<void>;
40
+ handleClose: (event: SyntheticEvent, reason: AutocompleteCloseReason) => void;
41
+ handleScroll: (event: any) => Promise<void>;
42
+ state: SingleSelectState;
43
+ } & SingleSelectProps;
44
+
45
+ export const SingleInput = ({
46
+ required = false,
47
+ optionsApiEndPoint,
48
+ label,
49
+ name,
50
+ getValue,
51
+ value,
52
+ onChange,
53
+ error,
54
+ helperText,
55
+ dbLabelProps = {
56
+ labelKey: '',
57
+ useLabelStartCase: false,
58
+ useSubLabelStartCase: false,
59
+ },
60
+ searchDb,
61
+ handleOpen,
62
+ handleClose,
63
+ handleScroll,
64
+ state,
65
+ ...restProps
66
+ }: SingleInputProps) => {
67
+ const {
68
+ open,
69
+ loadingInitialInternalOptions,
70
+ internalOptions,
71
+ loadingInternalOptions,
72
+ } = state;
73
+
74
+ const debouncedSendRequest = useMemo(() => {
75
+ return debounce((searchValue) => {
76
+ if (optionsApiEndPoint) searchDb(searchValue);
77
+ }, 300);
78
+ }, [searchDb]);
79
+
80
+ const handleSearch = async (e: any) => {
81
+ if (e) {
82
+ const searchValue = e.target.value;
83
+ debouncedSendRequest(searchValue);
84
+ }
85
+ };
86
+
87
+ if (loadingInitialInternalOptions) {
88
+ return (
89
+ <TextField
90
+ label={label}
91
+ name={name}
92
+ required={required}
93
+ InputProps={{
94
+ endAdornment: <Spinner />,
95
+ }}
96
+ error={error}
97
+ helperText={helperText}
98
+ />
99
+ );
100
+ }
101
+ return (
102
+ <MuiAutocomplete
103
+ {...restProps}
104
+ onChange={(e, value) => {
105
+ onChange(getValue ? getValue(value) : value?.value);
106
+ }}
107
+ onInputCapture={handleSearch}
108
+ open={open}
109
+ autoFocus={true}
110
+ filterOptions={(options, state) => {
111
+ if (optionsApiEndPoint) {
112
+ return options;
113
+ }
114
+ return options.filter((option) =>
115
+ option.label.toLowerCase().includes(state.inputValue.toLowerCase()),
116
+ );
117
+ }}
118
+ value={state.internalOptionsMap[value]}
119
+ PaperComponent={CustomPaper}
120
+ renderOption={(props, option: any) => {
121
+ return (
122
+ <Box component="li" {...props} key={option.value}>
123
+ <OptionContainer>
124
+ <Typography variant="label1">
125
+ {dbLabelProps.useLabelStartCase
126
+ ? startCase(option.label)
127
+ : option.label}
128
+ </Typography>
129
+ <Typography variant="caption">
130
+ {dbLabelProps.useSubLabelStartCase
131
+ ? startCase(option?.subLabel)
132
+ : option?.subLabel}
133
+ </Typography>
134
+ </OptionContainer>
135
+ </Box>
136
+ );
137
+ }}
138
+ ListboxProps={{
139
+ ...restProps.ListboxProps,
140
+ onScroll: handleScroll,
141
+ }}
142
+ slotProps={{
143
+ ...restProps.slotProps,
144
+ paper: {
145
+ ...restProps.slotProps?.paper,
146
+ loadingOptions: loadingInternalOptions,
147
+ isSearch: !!state.search,
148
+ },
149
+ }}
150
+ onOpen={handleOpen}
151
+ onClose={handleClose}
152
+ options={internalOptions}
153
+ renderInput={(params) => (
154
+ <TextField
155
+ {...params}
156
+ label={label}
157
+ required={required}
158
+ name={name}
159
+ error={error}
160
+ helperText={helperText}
161
+ />
162
+ )}
163
+ />
164
+ );
165
+ };
@@ -1,7 +1,15 @@
1
1
  import { Box, Divider, Stack, useTheme } from '@mui/material';
2
2
  import { GridColDef } from '@mui/x-data-grid';
3
3
  import { AnimatePresence, motion } from 'framer-motion';
4
- import { ReactNode, useState } from 'react';
4
+ import {
5
+ cloneElement,
6
+ Fragment,
7
+ ReactElement,
8
+ ReactNode,
9
+ useState,
10
+ } from 'react';
11
+ import { useDispatch } from 'react-redux';
12
+ import { setFilterByNameForUniqueId } from '../../../redux/slices/pageHeaderSlice';
5
13
  import {
6
14
  DensitySelector,
7
15
  Icons,
@@ -18,6 +26,7 @@ import { SearchBar } from './components/SearchBar';
18
26
  interface PageHeaderProps {
19
27
  uniqueId?: string;
20
28
  viewsSlot?: ReactNode;
29
+ filterComponents?: ReactElement[];
21
30
  actions?: ReactNode[];
22
31
  columns?: GridColDef[];
23
32
  searchText?: string;
@@ -50,13 +59,33 @@ const motionDivVariants = {
50
59
 
51
60
  export const PageHeader = ({
52
61
  uniqueId,
53
- actions,
62
+ actions = [],
63
+ filterComponents = [],
54
64
  columns,
55
65
  viewsSlot = <Box></Box>,
56
66
  searchText = 'Search',
57
67
  }: PageHeaderProps) => {
58
68
  const [expanded, setExpanded] = useState(false);
59
69
  const [expandedSearch, setExpandedSearch] = useState(false);
70
+ const dispatch = useDispatch();
71
+
72
+ const wrapProps = (element: ReactElement): ReactElement => {
73
+ const { name } = element.props;
74
+ if (name && uniqueId) {
75
+ return cloneElement(element, {
76
+ onChange: (value: any) => {
77
+ dispatch(
78
+ setFilterByNameForUniqueId({
79
+ uniqueId,
80
+ name,
81
+ value,
82
+ }),
83
+ );
84
+ },
85
+ });
86
+ }
87
+ return element;
88
+ };
60
89
 
61
90
  const theme = useTheme();
62
91
 
@@ -142,7 +171,7 @@ export const PageHeader = ({
142
171
  </AnimatePresence>
143
172
  )}
144
173
  {isTableMode && <DensitySelector uniqueId={uniqueId} />}
145
- {actions?.map((action, index) => action)}
174
+ {actions.map((action, index) => action)}
146
175
  </Stack>
147
176
  </Stack>
148
177
  {expanded && (
@@ -154,16 +183,32 @@ export const PageHeader = ({
154
183
  )}
155
184
  {expanded && (
156
185
  <Stack direction="row" width="100%" alignItems="center" gap={2}>
157
- {columns && uniqueId && (
158
- <TableColumnsSelector columns={columns} uniqueId={uniqueId} />
186
+ {isTableMode && (
187
+ <>
188
+ <TableColumnsSelector columns={columns} uniqueId={uniqueId} />
189
+ <Divider
190
+ orientation="vertical"
191
+ style={{
192
+ height: '20px',
193
+ background: theme.palette.border.primary,
194
+ }}
195
+ />
196
+ </>
159
197
  )}
160
- <Divider
161
- orientation="vertical"
162
- style={{
163
- height: '20px',
164
- background: theme.palette.border.primary,
165
- }}
166
- />
198
+ {filterComponents.map((filter, index) => (
199
+ <Fragment key={index}>
200
+ {wrapProps(filter)}
201
+ {index < filterComponents.length - 1 && (
202
+ <Divider
203
+ orientation="vertical"
204
+ style={{
205
+ height: '20px',
206
+ background: theme.palette.border.primary,
207
+ }}
208
+ />
209
+ )}
210
+ </Fragment>
211
+ ))}
167
212
  <Stack direction="row"></Stack>
168
213
  </Stack>
169
214
  )}
@@ -11,20 +11,28 @@ import { Fragment, ReactNode, useState } from 'react';
11
11
 
12
12
  export type DropdownMenuProps = {
13
13
  anchor: (props: { open: (e: any) => void }) => ReactNode;
14
- menu: ReactNode[] | ((props: { close: () => void }) => ReactNode[]);
14
+ menu?: ReactNode[] | ((props: { close: () => void }) => ReactNode[]);
15
+ menuSlot?: (props: { close: () => void }) => ReactNode;
16
+ useMenuSlot?: boolean;
15
17
  menuProps?: Omit<MenuProps, 'open'>;
16
18
  menuListProps?: MenuListProps;
17
19
  menuListContainerSx?: StackProps['sx'];
18
20
  menuHeader?: ReactNode;
21
+ menuFooter?: ReactNode;
22
+ handleMenuScroll?: (event: any) => Promise<void>;
19
23
  };
20
24
 
21
25
  export const DropdownMenu = ({
22
26
  menuProps,
23
27
  menu = [],
28
+ menuSlot,
29
+ useMenuSlot = false,
24
30
  menuListProps,
25
31
  menuHeader,
32
+ menuFooter,
26
33
  menuListContainerSx,
27
34
  anchor,
35
+ handleMenuScroll,
28
36
  }: DropdownMenuProps) => {
29
37
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
30
38
  const [open, setOpen] = useState(false);
@@ -70,6 +78,8 @@ export const DropdownMenu = ({
70
78
  >
71
79
  {menuHeader}
72
80
  <Stack
81
+ component="div"
82
+ onScroll={handleMenuScroll}
73
83
  direction={'column'}
74
84
  sx={{
75
85
  ...menuListContainerSx,
@@ -83,15 +93,18 @@ export const DropdownMenu = ({
83
93
  },
84
94
  }}
85
95
  >
86
- {menuItems.map((item, index) => (
87
- <Fragment key={index}>
88
- {item}
89
- {index < menuItems.length - 1 && (
90
- <Divider flexItem sx={{ margin: '0 !important' }} />
91
- )}
92
- </Fragment>
93
- ))}
96
+ {useMenuSlot && menuSlot
97
+ ? menuSlot({ close: handleClose })
98
+ : menuItems.map((item, index) => (
99
+ <Fragment key={index}>
100
+ {item}
101
+ {index < menuItems.length - 1 && (
102
+ <Divider flexItem sx={{ margin: '0 !important' }} />
103
+ )}
104
+ </Fragment>
105
+ ))}
94
106
  </Stack>
107
+ {menuFooter}
95
108
  </Menu>
96
109
  </>
97
110
  );
@@ -53,7 +53,17 @@ export const ManageFilters = ({
53
53
  {<Icons.ConfigureIcon />} Manage Filters
54
54
  </Button>
55
55
  )}
56
- menuProps={{ ...props.menuProps }}
56
+ menuProps={{
57
+ ...props.menuProps,
58
+ anchorOrigin: {
59
+ vertical: 'bottom',
60
+ horizontal: 'left',
61
+ },
62
+ transformOrigin: {
63
+ vertical: 'top',
64
+ horizontal: 'left',
65
+ },
66
+ }}
57
67
  menuHeader={
58
68
  <SearchBar
59
69
  fullWidth
@@ -65,14 +75,23 @@ export const ManageFilters = ({
65
75
  />
66
76
  }
67
77
  menuListContainerSx={{
68
- margin: '10px 2px 0px 2px',
78
+ margin: '10px 5px 0px 5px',
69
79
  gap: 1,
70
80
  maxHeight: '350px',
71
- overflow: 'scroll',
72
81
  }}
73
82
  menu={filteredOptions.map((item, index) => (
74
83
  <SingleCheckBox
75
84
  key={index}
85
+ formControlLabelProps={{
86
+ labelPlacement: 'start',
87
+ sx: {
88
+ display: 'flex',
89
+ justifyContent: 'space-between',
90
+ marginLeft: '0px',
91
+ },
92
+ }}
93
+ icon={<Icons.VisibilityIcon />}
94
+ checkedIcon={<Icons.VisibilityOffIcon />}
76
95
  checked={values.map((s) => s.label).includes(item.label)}
77
96
  label={<Typography variant="subtitle3">{item.label}</Typography>}
78
97
  onChange={(e) => {
@@ -81,6 +81,16 @@ export const pageHeaderSlice = createSlice({
81
81
  filters: defaultFilters,
82
82
  };
83
83
  },
84
+ setFilterByNameForUniqueId: (state, action) => {
85
+ const { uniqueId, name, value } = action.payload;
86
+ state[uniqueId] = {
87
+ ...state[uniqueId],
88
+ filters: {
89
+ ...state[uniqueId].filters,
90
+ [name]: value,
91
+ },
92
+ };
93
+ },
84
94
  },
85
95
  });
86
96
 
@@ -92,4 +102,5 @@ export const {
92
102
  setDefaultFiltersForUniqueId,
93
103
  setLimitForUniqueId,
94
104
  setOffsetForUniqueId,
105
+ setFilterByNameForUniqueId,
95
106
  } = pageHeaderSlice.actions;
@@ -1,17 +1,16 @@
1
1
  import _ from 'lodash';
2
2
 
3
3
  export const getBreadcrumbsCharacter = () => {
4
- const unicodeChar = String.fromCharCode(0x21e5);
4
+ const unicodeChar = String.fromCharCode(0x21e5),
5
+ fallbackChar = '~~';
6
+
5
7
  const testElement = document.createElement('span');
6
8
  testElement.innerHTML = unicodeChar;
7
9
  document.body.appendChild(testElement);
8
10
  const isRendered = testElement.offsetWidth > 0;
9
11
  document.body.removeChild(testElement);
10
12
 
11
- const fallbackChar = '~~';
12
- const s = isRendered ? unicodeChar : fallbackChar;
13
-
14
- return s;
13
+ return isRendered ? unicodeChar : fallbackChar;
15
14
  };
16
15
 
17
16
  type CreateUrlSlug = {
@@ -21,3 +20,8 @@ type CreateUrlSlug = {
21
20
  export const createBreadcrumbIdSlug = ({ name, id }: CreateUrlSlug): string => {
22
21
  return `${_.kebabCase(name)}${getBreadcrumbsCharacter()}${id}`;
23
22
  };
23
+
24
+ export const splitBreadcrumbIdSlug = (param: string) => {
25
+ const [name, id] = param.split(getBreadcrumbsCharacter());
26
+ return { name, id };
27
+ };