@applica-software-guru/react-admin 1.5.301 → 1.5.303

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 (77) hide show
  1. package/.eslintrc +1 -1
  2. package/dist/components/Layout/Sidebar/Content.d.ts +8 -0
  3. package/dist/components/Layout/Sidebar/Content.d.ts.map +1 -0
  4. package/dist/components/Layout/Sidebar/Drawer.d.ts +11 -0
  5. package/dist/components/Layout/Sidebar/Drawer.d.ts.map +1 -0
  6. package/dist/components/Layout/Sidebar/Footer.d.ts +7 -0
  7. package/dist/components/Layout/Sidebar/Footer.d.ts.map +1 -0
  8. package/dist/components/Layout/Sidebar/Header.d.ts +16 -0
  9. package/dist/components/Layout/Sidebar/Header.d.ts.map +1 -0
  10. package/dist/components/Layout/Sidebar/index.d.ts +14 -0
  11. package/dist/components/Layout/Sidebar/index.d.ts.map +1 -0
  12. package/dist/components/Layout/TopToolbar.d.ts +11 -0
  13. package/dist/components/Layout/TopToolbar.d.ts.map +1 -0
  14. package/dist/components/Layout/index.d.ts +2 -0
  15. package/dist/components/Layout/index.d.ts.map +1 -1
  16. package/dist/components/Pagination/Pagination.d.ts.map +1 -1
  17. package/dist/components/ra-lists/FilterSidebar/ActiveFiltersChips.d.ts +4 -0
  18. package/dist/components/ra-lists/FilterSidebar/ActiveFiltersChips.d.ts.map +1 -0
  19. package/dist/components/ra-lists/FilterSidebar/FilterSidebar.d.ts +7 -0
  20. package/dist/components/ra-lists/FilterSidebar/FilterSidebar.d.ts.map +1 -0
  21. package/dist/components/ra-lists/FilterSidebar/FilterSidebarButton.d.ts +3 -0
  22. package/dist/components/ra-lists/FilterSidebar/FilterSidebarButton.d.ts.map +1 -0
  23. package/dist/components/ra-lists/FilterSidebar/FilterSidebarContext.d.ts +13 -0
  24. package/dist/components/ra-lists/FilterSidebar/FilterSidebarContext.d.ts.map +1 -0
  25. package/dist/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.d.ts +11 -0
  26. package/dist/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.d.ts.map +1 -0
  27. package/dist/components/ra-lists/FilterSidebar/SaveFiltersButton.d.ts +3 -0
  28. package/dist/components/ra-lists/FilterSidebar/SaveFiltersButton.d.ts.map +1 -0
  29. package/dist/components/ra-lists/FilterSidebar/SaveFiltersDialog.d.ts +7 -0
  30. package/dist/components/ra-lists/FilterSidebar/SaveFiltersDialog.d.ts.map +1 -0
  31. package/dist/components/ra-lists/FilterSidebar/SavedFiltersList.d.ts +6 -0
  32. package/dist/components/ra-lists/FilterSidebar/SavedFiltersList.d.ts.map +1 -0
  33. package/dist/components/ra-lists/FilterSidebar/index.d.ts +9 -0
  34. package/dist/components/ra-lists/FilterSidebar/index.d.ts.map +1 -0
  35. package/dist/components/ra-lists/List.d.ts.map +1 -1
  36. package/dist/components/ra-lists/ListToolbar.d.ts +25 -0
  37. package/dist/components/ra-lists/ListToolbar.d.ts.map +1 -0
  38. package/dist/components/ra-lists/ListView.d.ts.map +1 -1
  39. package/dist/components/ra-lists/ListViewProvider.d.ts +11 -0
  40. package/dist/components/ra-lists/ListViewProvider.d.ts.map +1 -0
  41. package/dist/components/ra-lists/index.d.ts +3 -0
  42. package/dist/components/ra-lists/index.d.ts.map +1 -1
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/react-admin.cjs.js +58 -58
  46. package/dist/react-admin.cjs.js.gz +0 -0
  47. package/dist/react-admin.cjs.js.map +1 -1
  48. package/dist/react-admin.es.js +12072 -11478
  49. package/dist/react-admin.es.js.gz +0 -0
  50. package/dist/react-admin.es.js.map +1 -1
  51. package/dist/react-admin.umd.js +58 -58
  52. package/dist/react-admin.umd.js.gz +0 -0
  53. package/dist/react-admin.umd.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/components/Layout/Sidebar/Content.tsx +13 -0
  56. package/src/components/Layout/Sidebar/Drawer.tsx +50 -0
  57. package/src/components/Layout/Sidebar/Footer.tsx +20 -0
  58. package/src/components/Layout/Sidebar/Header.tsx +54 -0
  59. package/src/components/Layout/Sidebar/index.ts +22 -0
  60. package/src/components/Layout/TopToolbar.tsx +43 -0
  61. package/src/components/Layout/index.ts +2 -0
  62. package/src/components/Pagination/Pagination.tsx +3 -0
  63. package/src/components/ra-lists/FilterSidebar/ActiveFiltersChips.tsx +108 -0
  64. package/src/components/ra-lists/FilterSidebar/FilterSidebar.tsx +164 -0
  65. package/src/components/ra-lists/FilterSidebar/FilterSidebarButton.tsx +44 -0
  66. package/src/components/ra-lists/FilterSidebar/FilterSidebarContext.tsx +53 -0
  67. package/src/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.tsx +72 -0
  68. package/src/components/ra-lists/FilterSidebar/SaveFiltersButton.tsx +28 -0
  69. package/src/components/ra-lists/FilterSidebar/SaveFiltersDialog.tsx +138 -0
  70. package/src/components/ra-lists/FilterSidebar/SavedFiltersList.tsx +118 -0
  71. package/src/components/ra-lists/FilterSidebar/index.ts +8 -0
  72. package/src/components/ra-lists/List.tsx +23 -6
  73. package/src/components/ra-lists/ListToolbar.tsx +101 -0
  74. package/src/components/ra-lists/ListView.tsx +18 -8
  75. package/src/components/ra-lists/ListViewProvider.tsx +23 -0
  76. package/src/components/ra-lists/index.ts +3 -0
  77. package/src/index.ts +3 -2
@@ -0,0 +1,72 @@
1
+ import { ReactElement } from 'react';
2
+ import isEqual from 'lodash/isEqual';
3
+ import { useListContext, useTranslate } from 'ra-core';
4
+ import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material';
5
+
6
+ import { SavedQuery, extractValidSavedQueries, useSavedQueries } from 'react-admin';
7
+
8
+ interface RemoveSavedQueryDialogProps {
9
+ open: boolean;
10
+ onClose: () => void;
11
+ filterToRemove?: SavedQuery;
12
+ }
13
+
14
+ function RemoveSavedQueryDialog({ open, onClose, filterToRemove }: RemoveSavedQueryDialogProps): ReactElement {
15
+ const translate = useTranslate();
16
+ const { resource, filterValues, sort, perPage, displayedFilters } = useListContext();
17
+ const filterValuesToUse = filterToRemove?.value?.filter || filterValues;
18
+ const label = filterToRemove?.label ?? '';
19
+
20
+ const [savedQueries, setSavedQueries] = useSavedQueries(resource);
21
+
22
+ function removeQuery() {
23
+ const savedQueryToRemove = {
24
+ filter: filterValuesToUse,
25
+ sort,
26
+ perPage,
27
+ displayedFilters
28
+ };
29
+
30
+ const newSavedQueries = extractValidSavedQueries(savedQueries);
31
+ const index = newSavedQueries.findIndex((savedFilter) => isEqual(savedFilter.value, savedQueryToRemove));
32
+ setSavedQueries([...newSavedQueries.slice(0, index), ...newSavedQueries.slice(index + 1)]);
33
+ onClose();
34
+ }
35
+
36
+ return (
37
+ <Dialog
38
+ open={open}
39
+ onClose={onClose}
40
+ aria-labelledby="alert-dialog-title"
41
+ aria-describedby="alert-dialog-description"
42
+ >
43
+ <DialogTitle id="alert-dialog-title">
44
+ {translate('ra.saved_queries.remove_dialog_title', {
45
+ label: label,
46
+ _: 'Remove saved query?'
47
+ })}
48
+ </DialogTitle>
49
+ <DialogContent>
50
+ <DialogContentText>
51
+ {translate('ra.saved_queries.remove_message', {
52
+ _: 'Are you sure you want to remove that item from your list of saved queries?'
53
+ })}
54
+ </DialogContentText>
55
+ </DialogContent>
56
+ <DialogActions>
57
+ <Button onClick={onClose}>{translate('ra.action.cancel')}</Button>
58
+ <Button
59
+ onClick={removeQuery}
60
+ color="primary"
61
+ // eslint-disable-next-line jsx-a11y/no-autofocus
62
+ autoFocus
63
+ >
64
+ {translate('ra.action.confirm')}
65
+ </Button>
66
+ </DialogActions>
67
+ </Dialog>
68
+ );
69
+ }
70
+
71
+ export { RemoveSavedQueryDialog };
72
+ export type { RemoveSavedQueryDialogProps };
@@ -0,0 +1,28 @@
1
+ import { Button } from '@mui/material';
2
+ import { useCallback, useState } from 'react';
3
+ import { SaveFiltersDialog } from './SaveFiltersDialog';
4
+ import { useTranslate } from 'ra-core';
5
+
6
+ function SaveFiltersButton() {
7
+ const [open, setOpen] = useState(false);
8
+ const translate = useTranslate();
9
+
10
+ const onClose = useCallback(() => {
11
+ setOpen(false);
12
+ }, []);
13
+
14
+ const openDialog = useCallback(() => {
15
+ setOpen(true);
16
+ }, []);
17
+
18
+ return (
19
+ <>
20
+ <Button sx={{ height: '36px' }} variant="contained" onClick={openDialog}>
21
+ {translate('ra.action.save_filter')}
22
+ </Button>
23
+ <SaveFiltersDialog open={open} onClose={onClose} />
24
+ </>
25
+ );
26
+ }
27
+
28
+ export { SaveFiltersButton };
@@ -0,0 +1,138 @@
1
+ import { SimpleForm } from '@/components/ra-forms';
2
+ import { TextInput } from '@/components/ra-inputs';
3
+ import { Button, Dialog, Divider, List, ListItem, ListItemText, Stack, Toolbar, Typography } from '@mui/material';
4
+ import { required, useListContext, useTranslate } from 'ra-core';
5
+ import { extractValidSavedQueries, useSavedQueries } from 'ra-ui-materialui';
6
+ import { useCallback } from 'react';
7
+ import { useFormContext } from 'react-hook-form';
8
+ import { useGetChipValue } from './FilterSidebarContext';
9
+
10
+ interface SelectedFiltersItemProps {
11
+ value: unknown;
12
+ source: string;
13
+ lastItem: string;
14
+ }
15
+
16
+ function SelectedFiltersItem(props: SelectedFiltersItemProps): JSX.Element {
17
+ const { value, source, lastItem } = props;
18
+ const label = useGetChipValue({ source, value });
19
+
20
+ return (
21
+ <Stack>
22
+ <ListItem alignItems="flex-start" sx={{ pl: 0 }}>
23
+ <ListItemText primary={label} sx={{ my: 0 }} />
24
+ </ListItem>
25
+ {lastItem === source ? null : <Divider />}
26
+ </Stack>
27
+ );
28
+ }
29
+
30
+ function SelectedFiltersList(): JSX.Element {
31
+ const { filterValues } = useListContext();
32
+ const translate = useTranslate();
33
+
34
+ return (
35
+ <Stack mt={4} width={'100%'}>
36
+ <Typography variant="h6" fontWeight={'bold'} color={'secondary'}>
37
+ {translate('ra.title.selected_filters')}
38
+ </Typography>
39
+ <List sx={{ bgcolor: 'background.paper', pt: 0 }}>
40
+ {filterValues
41
+ ? Object.entries(filterValues).map(([key, value]) => {
42
+ const lastEntry = Object.entries(filterValues).pop();
43
+ const lastItem = lastEntry ? lastEntry[0] : '';
44
+ return <SelectedFiltersItem key={key} lastItem={lastItem} value={value} source={key} />;
45
+ })
46
+ : null}
47
+ </List>
48
+ </Stack>
49
+ );
50
+ }
51
+
52
+ interface CustomToolbarProps {
53
+ onClose: () => void;
54
+ addQuery: (queryName: string) => void;
55
+ }
56
+
57
+ function CustomToolbar(props: CustomToolbarProps): JSX.Element {
58
+ const { onClose, addQuery } = props;
59
+ const translate = useTranslate();
60
+ const { handleSubmit } = useFormContext();
61
+
62
+ const onFormSubmit = useCallback(
63
+ (data: Record<string, any>) => {
64
+ addQuery(data.queryName);
65
+ },
66
+ [addQuery]
67
+ );
68
+
69
+ return (
70
+ <Toolbar sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
71
+ <Button onClick={onClose}>{translate('ra.action.cancel')}</Button>
72
+ <Button variant="contained" onClick={handleSubmit(onFormSubmit)}>
73
+ {translate('ra.action.save')}
74
+ </Button>
75
+ </Toolbar>
76
+ );
77
+ }
78
+
79
+ interface SaveFiltersFormProps {
80
+ open: boolean;
81
+ onClose: () => void;
82
+ }
83
+
84
+ function SaveFiltersDialog(props: SaveFiltersFormProps) {
85
+ const { open, onClose } = props;
86
+ const { filterValues, sort, perPage, displayedFilters, resource } = useListContext();
87
+ const [savedQueries, setSavedQueries] = useSavedQueries(resource);
88
+ const translate = useTranslate();
89
+
90
+ const addQuery = useCallback(
91
+ (queryName: string): void => {
92
+ const newSavedQuery = {
93
+ label: queryName.trim(),
94
+ value: {
95
+ filter: filterValues,
96
+ sort,
97
+ perPage,
98
+ displayedFilters
99
+ }
100
+ };
101
+ const newSavedQueries = extractValidSavedQueries(savedQueries);
102
+ setSavedQueries(newSavedQueries.concat(newSavedQuery));
103
+ onClose();
104
+ },
105
+ [filterValues, sort, perPage, displayedFilters, savedQueries, setSavedQueries, onClose]
106
+ );
107
+
108
+ const checkAlreadyExists = useCallback(
109
+ () => (value: string) => {
110
+ if (savedQueries.some((query) => query.label === value?.trim())) {
111
+ return 'ra.validation.already_exists';
112
+ }
113
+ return undefined;
114
+ },
115
+ [savedQueries]
116
+ );
117
+
118
+ return (
119
+ <Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
120
+ <SimpleForm
121
+ toolbar={<CustomToolbar addQuery={addQuery} onClose={onClose} />}
122
+ title={translate('ra.title.save_favorite_filter')}
123
+ modal={true}
124
+ >
125
+ <TextInput
126
+ fullWidth
127
+ source={'queryName'}
128
+ label={translate('ra.input.query_name')}
129
+ placeholder={translate('ra.input.query_name_placeholder')}
130
+ validate={[required(), checkAlreadyExists()]}
131
+ />
132
+ <SelectedFiltersList />
133
+ </SimpleForm>
134
+ </Dialog>
135
+ );
136
+ }
137
+
138
+ export { SaveFiltersDialog };
@@ -0,0 +1,118 @@
1
+ import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Stack, Typography } from '@mui/material';
2
+ import { useListContext, useTranslate } from 'ra-core';
3
+ import { SavedQuery, extractValidSavedQueries, useSavedQueries } from 'ra-ui-materialui';
4
+ import { useCallback, useState } from 'react';
5
+ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
6
+ import { useTheme } from '@mui/material/styles';
7
+ import { RemoveSavedQueryDialog } from './RemoveSavedQueryDialog';
8
+
9
+ interface RemoveSavedQueryButtonProps {
10
+ query?: SavedQuery;
11
+ }
12
+
13
+ function RemoveSavedQueryButton(props: RemoveSavedQueryButtonProps) {
14
+ const { query } = props;
15
+ const [open, setOpen] = useState(false);
16
+ const toggleDialog = useCallback(() => {
17
+ setOpen((prev) => !prev);
18
+ }, []);
19
+ const translate = useTranslate();
20
+
21
+ return (
22
+ <>
23
+ <Button size="small" color="primary" onClick={toggleDialog}>
24
+ {translate('ra.action.delete')}
25
+ </Button>
26
+ <RemoveSavedQueryDialog open={open} onClose={toggleDialog} filterToRemove={query} />
27
+ </>
28
+ );
29
+ }
30
+
31
+ interface SavedFiltersListProps {
32
+ closeSidebar: () => void;
33
+ }
34
+
35
+ function SavedFiltersList(props: SavedFiltersListProps) {
36
+ const { closeSidebar } = props;
37
+ const { resource, setFilters, setSort, setPage, setPerPage } = useListContext();
38
+ const [expanded, setExpanded] = useState(false);
39
+ const theme = useTheme();
40
+ const [savedQueries] = useSavedQueries(resource);
41
+ const validSavedQueries = extractValidSavedQueries(savedQueries);
42
+ const translate = useTranslate();
43
+
44
+ const toggleExpanded = useCallback(() => {
45
+ setExpanded((prev) => !prev);
46
+ }, []);
47
+
48
+ const handleApplyFilter = useCallback(
49
+ (props: any) => {
50
+ const { value } = props;
51
+ // @ts-ignore
52
+ setFilters(value.filter);
53
+ setSort({ field: value.sort.field, order: value.sort.order });
54
+ setPage(1);
55
+ setPerPage(value.perPage);
56
+ closeSidebar();
57
+ },
58
+ [setFilters, setSort, setPage, setPerPage, closeSidebar]
59
+ );
60
+
61
+ return savedQueries && savedQueries.length > 0 ? (
62
+ <Box
63
+ sx={{
64
+ mt: '-1px',
65
+ mb: 2,
66
+ '& .MuiAccordion-root': {
67
+ borderColor: theme.palette.divider,
68
+ '& .Mui-expanded:last-of-type': {
69
+ marginBottom: 'inherit'
70
+ },
71
+ '& .MuiAccordionSummary-root': {
72
+ bgcolor: 'transparent',
73
+ flexDirection: 'row'
74
+ },
75
+ '& .MuiAccordionSummary-content': {
76
+ justifyContent: 'space-between',
77
+ marginLeft: 0
78
+ },
79
+ '& .MuiAccordionDetails-root': {
80
+ borderColor: theme.palette.divider
81
+ },
82
+ '& .MuiAccordionSummary-expandIconWrapper': {
83
+ color: theme.palette.text.primary,
84
+ '&.Mui-expanded': {
85
+ transform: 'rotate(180deg)'
86
+ }
87
+ }
88
+ }
89
+ }}
90
+ >
91
+ <Accordion expanded={expanded} onChange={toggleExpanded}>
92
+ <AccordionSummary sx={{ px: '20px' }} expandIcon={<ExpandMoreIcon fontSize="small" />}>
93
+ <Typography variant="h5" fontWeight="bold">
94
+ {translate('ra.action.saved_filters')} ({validSavedQueries.length})
95
+ </Typography>
96
+ </AccordionSummary>
97
+ {validSavedQueries?.map((query, i) => (
98
+ <AccordionDetails
99
+ key={i}
100
+ sx={{ display: 'flex', justifyContent: 'space-between', px: '20px', alignItems: 'center' }}
101
+ >
102
+ <Typography variant="body1">{query.label}</Typography>
103
+ <Stack flexDirection="row" alignItems={'center'}>
104
+ <RemoveSavedQueryButton query={query} />
105
+ <Button size="small" color="primary" onClick={() => handleApplyFilter({ value: query.value })}>
106
+ {translate('ra.action.apply')}
107
+ </Button>
108
+ </Stack>
109
+ </AccordionDetails>
110
+ ))}
111
+ </Accordion>
112
+ </Box>
113
+ ) : (
114
+ <Box height={16} />
115
+ );
116
+ }
117
+
118
+ export { SavedFiltersList };
@@ -0,0 +1,8 @@
1
+ export * from './ActiveFiltersChips';
2
+ export * from './FilterSidebar';
3
+ export * from './FilterSidebarButton';
4
+ export * from './FilterSidebarContext';
5
+ export * from './RemoveSavedQueryDialog';
6
+ export * from './SaveFiltersButton';
7
+ export * from './SaveFiltersDialog';
8
+ export * from './SavedFiltersList';
@@ -2,9 +2,11 @@ import { MainCard } from '@/components/MainCard';
2
2
  import { ListView } from '@/components/ra-lists/ListView';
3
3
  import { styled } from '@mui/system';
4
4
  import { ListBase, RaRecord } from 'ra-core';
5
- import React, { ReactElement } from 'react';
5
+ import React, { ReactElement, isValidElement } from 'react';
6
6
  import { ListProps } from 'react-admin';
7
7
  import { Pagination } from '@/components/Pagination/Pagination';
8
+ import { FilterSidebar } from './FilterSidebar';
9
+ import { ListViewProvider } from './ListViewProvider';
8
10
 
9
11
  /**
10
12
  * List page component
@@ -56,6 +58,7 @@ import { Pagination } from '@/components/Pagination/Pagination';
56
58
  * </List>
57
59
  * );
58
60
  */
61
+
59
62
  function RaList<RecordType extends RaRecord = any>({
60
63
  debounce,
61
64
  disableAuthentication,
@@ -69,7 +72,7 @@ function RaList<RecordType extends RaRecord = any>({
69
72
  sort,
70
73
  storeKey,
71
74
  ...rest
72
- }: ListProps<RecordType>): ReactElement {
75
+ }: ListProps): ReactElement {
73
76
  return (
74
77
  <ListBase<RecordType>
75
78
  debounce={debounce}
@@ -84,7 +87,9 @@ function RaList<RecordType extends RaRecord = any>({
84
87
  sort={sort}
85
88
  storeKey={storeKey}
86
89
  >
87
- <ListView<RecordType> {...rest} />
90
+ <ListViewProvider>
91
+ <ListView<RecordType> {...rest} />
92
+ </ListViewProvider>
88
93
  </ListBase>
89
94
  );
90
95
  }
@@ -193,12 +198,24 @@ const StyledList = styled(RaList, { slot: 'root' })(({ theme }) => ({
193
198
  }));
194
199
 
195
200
  function List(props: ListProps): ReactElement {
196
- const Wrapper = props.aside ? React.Fragment : MainCard;
201
+ const { aside } = props;
202
+
203
+ // Check if 'aside' is a valid React element
204
+ const isAsideValid = isValidElement(aside);
205
+
206
+ // Determine if 'aside' is a FilterSidebar component
207
+ const isFilterSidebar = isAsideValid && aside?.type === FilterSidebar;
208
+
209
+ // Use React.Fragment if there's a generic 'aside', otherwise use MainCard
210
+ const ListWrapper = isAsideValid && !isFilterSidebar ? React.Fragment : MainCard;
211
+
212
+ // Define props for the ListWrapper based on the presence of 'aside'
213
+ const listWrapperProps = !aside || isFilterSidebar ? { content: false } : undefined;
197
214
 
198
215
  return (
199
- <Wrapper {...(!props.aside && { content: false })}>
216
+ <ListWrapper {...listWrapperProps}>
200
217
  <StyledList {...props} pagination={<Pagination />} />
201
- </Wrapper>
218
+ </ListWrapper>
202
219
  );
203
220
  }
204
221
 
@@ -0,0 +1,101 @@
1
+ import React, { ReactElement, memo } from 'react';
2
+ import { styled, useTheme } from '@mui/material/styles';
3
+ import PropTypes from 'prop-types';
4
+ import { Divider, ToolbarProps, useMediaQuery } from '@mui/material';
5
+ import { Exporter, useListContext } from 'ra-core';
6
+ import { FilterContext, FilterForm } from 'react-admin';
7
+ import { ActiveFiltersChips, FilterSidebarContext } from './FilterSidebar';
8
+ import { isEmpty } from 'lodash';
9
+
10
+ interface ListToolbarPropsExtended extends ListToolbarProps {
11
+ hasFilterSidebar?: boolean;
12
+ }
13
+
14
+ function ListToolbarComp(props: ListToolbarPropsExtended): ReactElement | null {
15
+ const { filters, actions, className, hasFilterSidebar, ...rest } = props;
16
+ const theme = useTheme();
17
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
18
+ const Actions = actions ? React.cloneElement(actions, { ...rest, ...actions.props }) : null;
19
+ const { filterValues } = useListContext();
20
+
21
+ return Array.isArray(filters) ? (
22
+ <FilterSidebarContext.Provider value={{ hasFilterSidebar }}>
23
+ <FilterContext.Provider value={filters}>
24
+ {isMobile && hasFilterSidebar ? (
25
+ <div>
26
+ {Actions}
27
+ {!isEmpty(filterValues) ? (
28
+ <div>
29
+ <Divider />
30
+ <ActiveFiltersChips />
31
+ </div>
32
+ ) : null}
33
+ </div>
34
+ ) : (
35
+ <Root className={className}>
36
+ {hasFilterSidebar ? <ActiveFiltersChips /> : <FilterForm />}
37
+ <span />
38
+ {Actions}
39
+ </Root>
40
+ )}
41
+ </FilterContext.Provider>
42
+ </FilterSidebarContext.Provider>
43
+ ) : (
44
+ <Root className={className}>
45
+ {filters
46
+ ? React.cloneElement(filters, {
47
+ ...rest,
48
+ context: 'form'
49
+ })
50
+ : null}
51
+ <span />
52
+ {actions
53
+ ? React.cloneElement(actions, {
54
+ ...rest,
55
+ filters,
56
+ ...actions.props
57
+ })
58
+ : null}
59
+ </Root>
60
+ );
61
+ }
62
+
63
+ ListToolbarComp.propTypes = {
64
+ filters: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
65
+ // @ts-ignore
66
+ actions: PropTypes.oneOfType([PropTypes.bool, PropTypes.element]),
67
+ // @ts-ignore
68
+ exporter: PropTypes.oneOfType([PropTypes.func, PropTypes.bool])
69
+ };
70
+
71
+ interface ListToolbarProps extends Omit<ToolbarProps, 'classes' | 'onSelect'> {
72
+ actions?: ReactElement | false;
73
+ exporter?: Exporter | false;
74
+ filters?: ReactElement | ReactElement[];
75
+ hasCreate?: boolean;
76
+ }
77
+
78
+ const PREFIX = 'RaListToolbar';
79
+
80
+ const Root = styled('div', {
81
+ name: PREFIX,
82
+ overridesResolver: (styles) => styles.root
83
+ })(({ theme }) => ({
84
+ display: 'flex',
85
+ position: 'relative',
86
+ justifyContent: 'space-between',
87
+ alignItems: 'flex-end',
88
+ width: '100%',
89
+ // [theme.breakpoints.down('md')]: {
90
+ // flexWrap: 'wrap'
91
+ // },
92
+ [theme.breakpoints.down('sm')]: {
93
+ backgroundColor: theme.palette.background.paper,
94
+ flexWrap: 'inherit',
95
+ flexDirection: 'column-reverse'
96
+ }
97
+ }));
98
+
99
+ const ListToolbar = memo(ListToolbarComp);
100
+ export { ListToolbar };
101
+ export type { ListToolbarProps };
@@ -11,10 +11,11 @@ import {
11
11
  ListActions as DefaultActions,
12
12
  Pagination as DefaultPagination,
13
13
  Empty,
14
- ListToolbar,
15
14
  SavedQueriesListClasses,
16
15
  Title
17
16
  } from 'react-admin';
17
+ import { ListToolbar } from './ListToolbar';
18
+ import { FilterSidebar } from './FilterSidebar';
18
19
  const defaultActions = <DefaultActions />;
19
20
  const defaultPagination = <DefaultPagination />;
20
21
  const defaultEmpty = <Empty />;
@@ -38,7 +39,8 @@ function ListView<RecordType extends RaRecord = any>(props: ListViewProps): Reac
38
39
  } = props;
39
40
  const { defaultTitle, data, error, isLoading, filterValues, resource } = useListContext<RecordType>(props);
40
41
  const theme = useTheme() as any;
41
- const isAsideValidElement = React.isValidElement(aside);
42
+ const hasAsideSidebar = React.isValidElement(aside) && aside?.type !== FilterSidebar;
43
+ const hasFilterSidebar = React.isValidElement(aside) && aside?.type === FilterSidebar;
42
44
 
43
45
  if (!children || (!data && isLoading && emptyWhileLoading)) {
44
46
  return null;
@@ -49,18 +51,24 @@ function ListView<RecordType extends RaRecord = any>(props: ListViewProps): Reac
49
51
  <div
50
52
  className={ListClasses.main}
51
53
  style={{
52
- border: isAsideValidElement ? '1px solid' : 'none',
53
- borderRadius: isAsideValidElement ? 4 : 0,
54
- borderColor: isAsideValidElement
54
+ border: hasAsideSidebar ? '1px solid' : 'none',
55
+ borderRadius: hasAsideSidebar ? 4 : 0,
56
+ borderColor: hasAsideSidebar
55
57
  ? theme.palette.mode === 'dark'
56
58
  ? theme.palette.divider
57
59
  : theme.palette.grey.A800
58
60
  : 'transparent',
59
- backgroundColor: isAsideValidElement ? theme.palette.background.paper : 'transparent'
61
+ backgroundColor: hasAsideSidebar ? theme.palette.background.paper : 'transparent'
60
62
  }}
61
63
  >
62
64
  {filters || actions ? (
63
- <ListToolbar className={ListClasses.actions} filters={filters} actions={actions} hasCreate={hasCreate} />
65
+ <ListToolbar
66
+ className={ListClasses.actions}
67
+ filters={filters}
68
+ actions={actions}
69
+ hasCreate={hasCreate}
70
+ hasFilterSidebar={hasFilterSidebar}
71
+ />
64
72
  ) : null}
65
73
  {!error && (
66
74
  <Content className={ListClasses.content}>
@@ -97,7 +105,9 @@ function ListView<RecordType extends RaRecord = any>(props: ListViewProps): Reac
97
105
  <Root className={clsx('list-page', className)} {...rest}>
98
106
  <Title title={title} defaultTitle={defaultTitle} preferenceKey={`${resource}.list.title`} />
99
107
  {shouldRenderEmptyPage ? renderEmpty() : renderList()}
100
- {isAsideValidElement ? <AsideCard aside={aside} /> : null}
108
+ {hasAsideSidebar ? <AsideCard aside={aside} /> : null}
109
+ {/* @ts-ignore */}
110
+ {hasFilterSidebar ? React.cloneElement(aside, { filters }) : null}
101
111
  </Root>
102
112
  );
103
113
  }
@@ -0,0 +1,23 @@
1
+ import React, { createContext, useContext } from 'react';
2
+
3
+ interface ListViewContextType {
4
+ sidebarOpen?: boolean;
5
+ setSidebarOpen?: (open: boolean) => void;
6
+ }
7
+
8
+ const ListViewContext = createContext<ListViewContextType>({
9
+ sidebarOpen: false,
10
+ setSidebarOpen: () => {}
11
+ });
12
+
13
+ function ListViewProvider(props: { children: React.ReactNode }) {
14
+ const [sidebarOpen, setSidebarOpen] = React.useState(false);
15
+
16
+ return <ListViewContext.Provider value={{ sidebarOpen, setSidebarOpen }}>{props.children}</ListViewContext.Provider>;
17
+ }
18
+
19
+ function useListViewContext() {
20
+ return useContext(ListViewContext);
21
+ }
22
+
23
+ export { ListViewProvider, useListViewContext };
@@ -2,6 +2,9 @@ export * from './BulkActionsToolbar';
2
2
  export * from './BulkFloatingActionsToolbar';
3
3
  export * from './Datagrid';
4
4
  export * from './Empty';
5
+ export * from './FilterSidebar';
5
6
  export * from './List';
7
+ export * from './ListToolbar';
8
+ export * from './ListViewProvider';
6
9
  export * from './NotificationList';
7
10
  export * from './SimpleList';
package/src/index.ts CHANGED
@@ -30,7 +30,6 @@ export {
30
30
  HttpError,
31
31
  I18nContextProvider,
32
32
  ListBase,
33
- ListToolbar,
34
33
  LoadingIndicator,
35
34
  SimpleFormIterator as RaSimpleFormIterator,
36
35
  RecordContextProvider,
@@ -44,9 +43,9 @@ export {
44
43
  SimpleShowLayout,
45
44
  SingleFieldList,
46
45
  TabbedFormTabs,
47
- TopToolbar,
48
46
  UrlField,
49
47
  ValidationError,
48
+ WithListContext,
50
49
  choices,
51
50
  email,
52
51
  maxLength,
@@ -80,9 +79,11 @@ export {
80
79
  useLocaleState,
81
80
  useLocales,
82
81
  useLogin,
82
+ useLogout,
83
83
  useNotify,
84
84
  usePermissions,
85
85
  useRecordContext,
86
+ useReferenceArrayFieldController,
86
87
  useRefresh,
87
88
  useRemoveFromStore,
88
89
  useResetStore,