@janrankenhohn/react-thumbnail-list 0.5.0 → 0.5.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/dist/index.cjs.js +226 -211
- package/dist/index.esm.js +214 -198
- package/package.json +15 -4
package/dist/index.cjs.js
CHANGED
|
@@ -1,65 +1,34 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var system = require('@mui/system');
|
|
9
|
-
var SearchIcon = require('@mui/icons-material/Search');
|
|
10
|
-
var ClearIcon = require('@mui/icons-material/Clear');
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
var
|
|
18
|
-
var
|
|
19
|
-
var
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("h1", null, "Hello World"), /*#__PURE__*/React__default["default"].createElement(material.Button, {
|
|
24
|
-
variant: "contained"
|
|
25
|
-
}, "Contained"));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var material = require('@mui/material');
|
|
7
|
+
var React = require('react');
|
|
8
|
+
var system = require('@mui/system');
|
|
9
|
+
var SearchIcon = require('@mui/icons-material/Search');
|
|
10
|
+
var ClearIcon = require('@mui/icons-material/Clear');
|
|
11
|
+
var lodash = require('lodash');
|
|
12
|
+
var SwapVertIcon = require('@mui/icons-material/SwapVert');
|
|
13
|
+
var SortIcon = require('@mui/icons-material/Sort');
|
|
14
|
+
|
|
15
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
16
|
+
|
|
17
|
+
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
18
|
+
var SearchIcon__default = /*#__PURE__*/_interopDefaultLegacy(SearchIcon);
|
|
19
|
+
var ClearIcon__default = /*#__PURE__*/_interopDefaultLegacy(ClearIcon);
|
|
20
|
+
var SwapVertIcon__default = /*#__PURE__*/_interopDefaultLegacy(SwapVertIcon);
|
|
21
|
+
var SortIcon__default = /*#__PURE__*/_interopDefaultLegacy(SortIcon);
|
|
22
|
+
|
|
29
23
|
const ThumbnailListItemContext = React.createContext(undefined);
|
|
30
|
-
// Create a custom hook to consume the context
|
|
31
24
|
const useThumbnailListItemContext = () => {
|
|
32
25
|
const context = React.useContext(ThumbnailListItemContext);
|
|
33
26
|
if (!context) {
|
|
34
|
-
throw new Error('
|
|
27
|
+
throw new Error('no context provider available');
|
|
35
28
|
}
|
|
36
29
|
return context;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Can be used as parent component to crop a wrapped image
|
|
41
|
-
* @param props width: width for cropping
|
|
42
|
-
* height: height for cropping
|
|
43
|
-
* seperate xs and sm values for mui breakpoints
|
|
44
|
-
* @returns component
|
|
45
|
-
*/
|
|
46
|
-
function ImageCropper(props) {
|
|
47
|
-
const ThumbnailImageCrop = material.styled('div')((p) => ({
|
|
48
|
-
[p.theme.breakpoints.up('xs')]: {
|
|
49
|
-
minWidth: props.width.xs,
|
|
50
|
-
maxWidth: props.width.xs,
|
|
51
|
-
height: props.height.xs,
|
|
52
|
-
overflow: 'hidden'
|
|
53
|
-
},
|
|
54
|
-
[p.theme.breakpoints.up('sm')]: {
|
|
55
|
-
minWidth: props.width.sm,
|
|
56
|
-
maxwWidth: props.width.sm,
|
|
57
|
-
height: props.height.sm,
|
|
58
|
-
},
|
|
59
|
-
}));
|
|
60
|
-
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(ThumbnailImageCrop, { children: props.children }) }));
|
|
61
|
-
}
|
|
62
|
-
|
|
30
|
+
};
|
|
31
|
+
|
|
63
32
|
/**
|
|
64
33
|
* Creates a ellipies text with webkit css styles
|
|
65
34
|
* @param props lineClamp: lines till ellipses
|
|
@@ -70,128 +39,64 @@ function EllipsisContainer(props) {
|
|
|
70
39
|
[p.theme.breakpoints.up('xs')]: {
|
|
71
40
|
overflow: 'hidden',
|
|
72
41
|
display: '-webkit-box',
|
|
73
|
-
WebkitLineClamp: props.lineClamp.xs.toString()
|
|
42
|
+
WebkitLineClamp: props.lineClamp.xs.toString() /* number of lines to show */,
|
|
74
43
|
WebkitBoxOrient: 'vertical',
|
|
75
44
|
},
|
|
76
45
|
[p.theme.breakpoints.up('sm')]: {
|
|
77
46
|
overflow: 'hidden',
|
|
78
47
|
display: '-webkit-box',
|
|
79
|
-
WebkitLineClamp: props.lineClamp.sm.toString()
|
|
80
|
-
WebkitBoxOrient: 'vertical'
|
|
48
|
+
WebkitLineClamp: props.lineClamp.sm.toString() /* number of lines to show */,
|
|
49
|
+
WebkitBoxOrient: 'vertical' /* number of lines to show */,
|
|
81
50
|
},
|
|
82
51
|
}));
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
|
|
52
|
+
return jsxRuntime.jsx(EllipsisContainer, { children: props.children });
|
|
53
|
+
}
|
|
54
|
+
|
|
86
55
|
function ThumbnailListItemTitle(props) {
|
|
87
|
-
const StyledCardContent = material.styled('div')((
|
|
88
|
-
[
|
|
89
|
-
|
|
90
|
-
|
|
56
|
+
const StyledCardContent = material.styled('div')((p) => ({
|
|
57
|
+
[p.theme.breakpoints.up('xs')]: {
|
|
58
|
+
padding: p.theme.spacing(1),
|
|
59
|
+
flex: '1 0 auto',
|
|
91
60
|
'&:last-child': { paddingBottom: 0 },
|
|
92
|
-
|
|
61
|
+
overflow: 'hidden',
|
|
93
62
|
},
|
|
94
63
|
}));
|
|
95
|
-
|
|
96
|
-
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.Box, { children: jsxRuntime.jsxs(StyledCardContent, { children: [jsxRuntime.jsx(EllipsisContainer, { lineClamp: { xs: 1, sm: 2 }, children: jsxRuntime.jsx(material.Typography, { variant: '
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function ThumbnailListItemInfoLabel(props) {
|
|
100
|
-
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs(material.Stack, { textAlign: "right", justifyContent: "space-between", children: [jsxRuntime.jsx(material.Box, { textAlign: "right", padding: 1, children: props.topContent }), jsxRuntime.jsx(material.Box, { textAlign: "right", padding: 1, children: props.bottomContent })] }) });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// import {Link} from 'react-router-dom';
|
|
64
|
+
console.log('item title rerenders');
|
|
65
|
+
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.Box, { children: jsxRuntime.jsxs(StyledCardContent, { children: [jsxRuntime.jsx(EllipsisContainer, { lineClamp: { xs: 1, sm: 2 }, children: jsxRuntime.jsx(material.Typography, { variant: "subtitle2", sx: { fontWeight: 'bold' }, children: props.title }) }), jsxRuntime.jsx(system.Stack, { direction: "row", gap: 1, children: jsxRuntime.jsx(EllipsisContainer, { lineClamp: { xs: 1, sm: 2 }, children: jsxRuntime.jsx(material.Typography, { variant: "subtitle2", sx: { fontSize: '0.84rem' }, color: "text.secondary", children: props.subTitle }) }) })] }) }) }));
|
|
66
|
+
}
|
|
67
|
+
|
|
104
68
|
const ThumbnailListItem = (props) => {
|
|
105
|
-
|
|
69
|
+
console.log('ThumbnailListItems renders');
|
|
70
|
+
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.Card, { sx: { display: 'flex' }, children: jsxRuntime.jsx(material.CardActionArea, { disabled: !props.onClick, onClick: () => props.onClick(props.id), children: jsxRuntime.jsxs(material.Stack, { direction: "row", width: "100%", children: [jsxRuntime.jsx("img", { src: props.thumbnailUrl, width: '45%' }), jsxRuntime.jsxs(material.Stack, { direction: "row", justifyContent: "space-between", width: "100%", gap: 1, children: [jsxRuntime.jsx(ThumbnailListItemTitle, { title: props.title, subTitle: props.subTitle }), props.infoLabel] })] }) }) }) }));
|
|
106
71
|
};
|
|
107
|
-
ThumbnailListItem
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
};
|
|
129
|
-
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.FormControl, { children: jsxRuntime.jsx(material.TextField, { sx: { input: { color: 'white' } }, fullWidth: true, value: input, size: "small", variant: "outlined", onChange: (event) => handleChange(event.target.value), InputProps: {
|
|
130
|
-
startAdornment: (jsxRuntime.jsx(material.InputAdornment, { position: "start", children: jsxRuntime.jsx(SearchIcon__default["default"], {}) })),
|
|
131
|
-
endAdornment: (jsxRuntime.jsx(material.InputAdornment, { position: "end", children: jsxRuntime.jsx(material.IconButton, { onClick: () => handleChange(''), sx: { visibility: showClearIcon, padding: 0 }, children: jsxRuntime.jsx(ClearIcon__default["default"], {}) }) })),
|
|
132
|
-
} }) }) }));
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
function ThumbnailListFilterTag(props) {
|
|
136
|
-
const theme = material.useTheme();
|
|
137
|
-
const handleOnClick = (value) => {
|
|
138
|
-
if (props.onClickCallback) {
|
|
139
|
-
props.onClickCallback(value);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: material.useMediaQuery(theme.breakpoints.up(props.collapseBreakpoint ?? 0)) || !props.icon ?
|
|
143
|
-
jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.Chip, { label: props.label, variant: props.variant, onClick: props.onClickCallback ? () => handleOnClick(props.value) : undefined }) }) :
|
|
144
|
-
jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.Tooltip, { title: props.label, children: jsxRuntime.jsx(material.IconButton, { onClick: props.onClickCallback ? () => handleOnClick(props.value) : undefined, children: props.icon }) }) }) }));
|
|
72
|
+
var ThumbnailListItem$1 = React__default["default"].memo(ThumbnailListItem);
|
|
73
|
+
|
|
74
|
+
const RatioWrapper = material.styled('div')(() => ({
|
|
75
|
+
// Assuming a 16:9 aspect ratio
|
|
76
|
+
paddingTop: '27.75%',
|
|
77
|
+
position: 'relative',
|
|
78
|
+
width: '100%',
|
|
79
|
+
'& > *': {
|
|
80
|
+
position: 'absolute',
|
|
81
|
+
top: 0,
|
|
82
|
+
left: 0,
|
|
83
|
+
right: 0,
|
|
84
|
+
bottom: 0,
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
function ThumbnailListMainContent(props) {
|
|
88
|
+
const { items, isLoading } = useThumbnailListItemContext();
|
|
89
|
+
console.log('main content rerenders');
|
|
90
|
+
const memoizedItems = React.useMemo(() => {
|
|
91
|
+
return items.map((item) => (jsxRuntime.jsx(material.Grid, { item: true, xs: props.muiBreakpoints.xs, sm: props.muiBreakpoints.sm, md: props.muiBreakpoints.md, lg: props.muiBreakpoints.lg, xl: props.muiBreakpoints.xl, children: jsxRuntime.jsx(RatioWrapper, { children: jsxRuntime.jsx(ThumbnailListItem$1, { id: item.id, thumbnailUrl: item.thumbnailUrl, title: item.title, subTitle: item.subTitle, infoLabel: item.label, onClick: item.onClick }) }) }, item.id)));
|
|
92
|
+
}, [items, props.muiBreakpoints]);
|
|
93
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(material.Box, { sx: { mt: 0.75, mb: 0.75 }, children: jsxRuntime.jsx(material.LinearProgress, { sx: { opacity: isLoading ? 1 : 0 } }) }), jsxRuntime.jsx(material.Grid, { container: true, spacing: props.spacing, children: memoizedItems })] }));
|
|
145
94
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}) }));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Displays a generic MUI select dropdown.
|
|
156
|
-
* Optinal collapses to a sort icon at a certain breakpoint
|
|
157
|
-
* @param props.label Select Label
|
|
158
|
-
* @param props.width * Width of the input field
|
|
159
|
-
* @param props.collapseBreakPoint * MUI breakpoint after that the select will collapse to the sort icon
|
|
160
|
-
* @param props.onChangeCallback * Callback function that gets triggered once a item is selected
|
|
161
|
-
* @param props.items * Array of items (name-value-pairs) that will be the select options
|
|
162
|
-
* @returns Drowpdown Input Component
|
|
163
|
-
*/
|
|
164
|
-
function DropdownInput(props) {
|
|
165
|
-
const [value, setValue] = React.useState(props.defaultValue ?? '');
|
|
166
|
-
const theme = material.useTheme();
|
|
167
|
-
const [anchorEl, setAnchorEl] = React.useState(null);
|
|
168
|
-
const handleChange = (value, name) => {
|
|
169
|
-
setValue(value);
|
|
170
|
-
setAnchorEl(null);
|
|
171
|
-
props.onChangeCallback(value, name);
|
|
172
|
-
};
|
|
173
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [material.useMediaQuery(theme.breakpoints.up(props.collapseBreakpoint ?? 0)) ?
|
|
174
|
-
jsxRuntime.jsxs(material.FormControl, { sx: { width: props.width, textAlign: 'start' }, children: [jsxRuntime.jsx(material.InputLabel, { size: 'small', id: "demo-simple-select-label", children: props.label }), jsxRuntime.jsx(material.Select, { value: value, size: 'small', label: props.label, onChange: (event) => handleChange(event.target.value, event.target.name), children: props.items.map((item) => {
|
|
175
|
-
return jsxRuntime.jsx(material.MenuItem, { value: item.value, children: item.name }, item.value);
|
|
176
|
-
}) })] }) : (jsxRuntime.jsx(material.IconButton
|
|
177
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
178
|
-
, {
|
|
179
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
-
onClick: (event) => setAnchorEl(event.currentTarget), children: props.icon })), jsxRuntime.jsx(material.Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => setAnchorEl(null), children: props.items.map((item) => (jsxRuntime.jsx(material.MenuItem, { onClick: () => handleChange(item.value), children: item.name }, item.value))) })] }));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function ThumbnailListHeaderSort(props) {
|
|
184
|
-
const { setSortAscending, sortAscending, setSortBy } = useThumbnailListItemContext();
|
|
185
|
-
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs(material.Box, { sx: { width: '-webkit-fill-available', textAlign: 'end' }, children: [jsxRuntime.jsx(material.Tooltip, { title: "asc/desc", children: jsxRuntime.jsx(material.IconButton, { onClick: () => setSortAscending(!sortAscending), children: jsxRuntime.jsx(SwapVertIcon__default["default"], {}) }) }), jsxRuntime.jsx(DropdownInput, { width: "130px", collapseBreakpoint: 'md', label: 'sort', defaultValue: "creationTimeStamp", icon: jsxRuntime.jsx(material.Tooltip, { title: 'sort', children: jsxRuntime.jsx(SortIcon__default["default"], {}) }), items: props.items, onChangeCallback: (value) => setSortBy(value) })] }) }));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const ThumbnailListHeader = function (props) {
|
|
189
|
-
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.Grid, { item: true, xs: 12, children: jsxRuntime.jsx(material.Stack, { direction: "row", alignItems: "center", justifyContent: props.justifyContent ?? 'space-between', gap: 2, children: props.children }) }) }));
|
|
190
|
-
};
|
|
191
|
-
ThumbnailListHeader.SearchField = ThumbnailListSearchField;
|
|
192
|
-
ThumbnailListHeader.FilterTags = ThumbnailListFilterTags;
|
|
193
|
-
ThumbnailListHeader.Sort = ThumbnailListHeaderSort;
|
|
194
|
-
|
|
95
|
+
ThumbnailListMainContent.defaultProps = {
|
|
96
|
+
spacing: 2,
|
|
97
|
+
muiBreakpoints: { xs: 12, sm: 6, md: 6, lg: 4, xl: 3 },
|
|
98
|
+
};
|
|
99
|
+
|
|
195
100
|
/**
|
|
196
101
|
* Generic method that sorts an array of items based on an item key
|
|
197
102
|
* @param values The array that should be sorted
|
|
@@ -229,20 +134,18 @@ function filterByTag(array, tagType, condition) {
|
|
|
229
134
|
console.log('filter array');
|
|
230
135
|
console.log(filteredArray);
|
|
231
136
|
return [...filteredArray];
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const useTagFilteredThumbnailListItems = ({ allItems, initialTag, initialCondition }) => {
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const useTagFilteredThumbnailListItems = ({ allItems, initialTag, initialCondition, }) => {
|
|
236
140
|
const [tagAndCondition, setTagAndCondition] = React.useState({ tag: initialTag, condition: initialCondition });
|
|
237
|
-
const [tagFilteredItems, setTagFilteredItems] = React.useState(allItems);
|
|
238
141
|
const setTagWithCondition = (t, c) => {
|
|
239
142
|
setTagAndCondition({ tag: t, condition: c });
|
|
240
143
|
};
|
|
241
|
-
React.
|
|
242
|
-
const tagFiltered = tagAndCondition.tag === 'id'
|
|
243
|
-
allItems
|
|
244
|
-
filterByTag(allItems, tagAndCondition.tag, tagAndCondition.condition);
|
|
245
|
-
|
|
144
|
+
const tagFilteredItems = React.useMemo(() => {
|
|
145
|
+
const tagFiltered = tagAndCondition.tag === 'id'
|
|
146
|
+
? allItems
|
|
147
|
+
: filterByTag(allItems, tagAndCondition.tag, tagAndCondition.condition);
|
|
148
|
+
return tagFiltered;
|
|
246
149
|
}, [allItems, tagAndCondition]);
|
|
247
150
|
return {
|
|
248
151
|
tagAndCondition,
|
|
@@ -250,8 +153,8 @@ const useTagFilteredThumbnailListItems = ({ allItems, initialTag, initialConditi
|
|
|
250
153
|
tagFilteredItems,
|
|
251
154
|
setTagWithCondition,
|
|
252
155
|
};
|
|
253
|
-
};
|
|
254
|
-
|
|
156
|
+
};
|
|
157
|
+
|
|
255
158
|
/**
|
|
256
159
|
* Filters a list of event by a search term
|
|
257
160
|
* @param allEvents event list that will be formatted
|
|
@@ -260,48 +163,159 @@ const useTagFilteredThumbnailListItems = ({ allItems, initialTag, initialConditi
|
|
|
260
163
|
*/
|
|
261
164
|
const useFilteredThumbnailListItems = (allItems, initialSearchTerm = '') => {
|
|
262
165
|
const [searchTerm, setSearchTerm] = React.useState(initialSearchTerm);
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const filtered = [...allItems].filter((item) => item.title.toLowerCase().includes(searchTerm.toLowerCase()));
|
|
267
|
-
setFilteredItems(filtered);
|
|
268
|
-
};
|
|
269
|
-
filterEvents();
|
|
166
|
+
const filteredItems = React.useMemo(() => {
|
|
167
|
+
const filtered = [...allItems].filter((item) => item.title.toLowerCase().includes(searchTerm.toLowerCase()));
|
|
168
|
+
return filtered;
|
|
270
169
|
}, [allItems, searchTerm]);
|
|
271
170
|
return { searchTerm, setSearchTerm, filteredItems };
|
|
272
|
-
};
|
|
273
|
-
|
|
171
|
+
};
|
|
172
|
+
|
|
274
173
|
const useSortedThumbnailListItems = (allItems, initialSortBy, initialSortAscending) => {
|
|
275
174
|
const [sortBy, setSortBy] = React.useState(initialSortBy);
|
|
276
175
|
const [sortAscending, setSortAscending] = React.useState(initialSortAscending);
|
|
277
|
-
const
|
|
278
|
-
React.useEffect(() => {
|
|
176
|
+
const sortedItems = React.useMemo(() => {
|
|
279
177
|
let sorted = orderByArray(allItems, sortBy);
|
|
280
178
|
if (!sortAscending) {
|
|
281
179
|
sorted = sorted.reverse();
|
|
282
180
|
}
|
|
283
|
-
|
|
284
|
-
}, [allItems, sortBy, sortAscending
|
|
181
|
+
return sorted;
|
|
182
|
+
}, [allItems, sortBy, sortAscending]);
|
|
285
183
|
return { sortBy, sortAscending, setSortBy, setSortAscending, sortedItems };
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const defaultConfiguration = {
|
|
187
|
+
sortBy: 'id',
|
|
188
|
+
sortAscending: true,
|
|
189
|
+
tag: 'id',
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const ThumbnailListSearchField = () => {
|
|
193
|
+
const [input, setInput] = React.useState('');
|
|
194
|
+
const [showClearIcon, setShowClearIcon] = React.useState('hidden');
|
|
195
|
+
const { setSearchTerm } = useThumbnailListItemContext();
|
|
196
|
+
console.log('Searchfield rerenders');
|
|
197
|
+
const handleChange = (value) => {
|
|
198
|
+
setInput(value);
|
|
199
|
+
setShowClearIcon(value === '' ? 'hidden' : '');
|
|
200
|
+
};
|
|
201
|
+
const debouncedSetSearchTerm = React.useCallback(lodash.debounce(setSearchTerm, 50), []);
|
|
297
202
|
React.useEffect(() => {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
203
|
+
debouncedSetSearchTerm(input);
|
|
204
|
+
return () => {
|
|
205
|
+
debouncedSetSearchTerm.cancel();
|
|
206
|
+
};
|
|
207
|
+
}, [input, debouncedSetSearchTerm]);
|
|
208
|
+
return (jsxRuntime.jsx(material.Box, { sx: { marginLeft: 'auto' }, children: jsxRuntime.jsx(material.FormControl, { children: jsxRuntime.jsx(material.TextField, { fullWidth: true, value: input, size: "small", variant: "outlined", onChange: (event) => handleChange(event.target.value), InputProps: {
|
|
209
|
+
startAdornment: (jsxRuntime.jsx(material.InputAdornment, { position: "start", children: jsxRuntime.jsx(SearchIcon__default["default"], {}) })),
|
|
210
|
+
endAdornment: (jsxRuntime.jsx(material.InputAdornment, { position: "end", children: jsxRuntime.jsx(material.IconButton, { onClick: () => handleChange(''), sx: { visibility: showClearIcon, padding: 0 }, children: jsxRuntime.jsx(ClearIcon__default["default"], {}) }) })),
|
|
211
|
+
} }) }) }));
|
|
212
|
+
};
|
|
213
|
+
ThumbnailListSearchField.defaultProps = {
|
|
214
|
+
align: 'start',
|
|
215
|
+
};
|
|
216
|
+
var ThumbnailListSearchField$1 = React__default["default"].memo(ThumbnailListSearchField);
|
|
217
|
+
|
|
218
|
+
function ThumbnailListFilterTag(props) {
|
|
219
|
+
const theme = material.useTheme();
|
|
220
|
+
const handleOnClick = (value) => {
|
|
221
|
+
if (props.onClickCallback) {
|
|
222
|
+
props.onClickCallback(value);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: material.useMediaQuery(theme.breakpoints.up(props.collapseBreakpoint ?? 0)) || !props.icon ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.Chip, { label: props.label, variant: props.variant, onClick: props.onClickCallback ? () => handleOnClick(props.value) : undefined }) })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(material.Tooltip, { title: props.label, children: jsxRuntime.jsx(material.IconButton, { onClick: props.onClickCallback ? () => handleOnClick(props.value) : undefined, children: props.icon }) }) })) }));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function ThumbnailListFilterTags(props) {
|
|
229
|
+
const { tagFilterCallback, tagAndCondition } = useThumbnailListItemContext();
|
|
230
|
+
console.log('filter tags rerenders');
|
|
231
|
+
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: props.tags.map((tag, index) => {
|
|
232
|
+
return (jsxRuntime.jsx(ThumbnailListFilterTag, { label: tag.label, value: tag.key.toString(), variant: tagAndCondition.tag === tag.key ? 'filled' : 'outlined', collapseBreakpoint: props.muiCollapseBreakpoint, onClickCallback: (value) => tagFilterCallback({ tag: value, condition: tag.condition }), icon: tag.icon }, `${index}_${tag.key.toString()}`));
|
|
233
|
+
}) }));
|
|
234
|
+
}
|
|
235
|
+
ThumbnailListFilterTags.defaultProps = {
|
|
236
|
+
align: 'start',
|
|
237
|
+
muiCollapseBreakpoint: 'md',
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Displays a generic MUI select dropdown.
|
|
242
|
+
* Optinal collapses to a sort icon at a certain breakpoint
|
|
243
|
+
* @param props.label Select Label
|
|
244
|
+
* @param props.width * Width of the input field
|
|
245
|
+
* @param props.collapseBreakPoint * MUI breakpoint after that the select will collapse to the sort icon
|
|
246
|
+
* @param props.onChangeCallback * Callback function that gets triggered once a item is selected
|
|
247
|
+
* @param props.items * Array of items (name-value-pairs) that will be the select options
|
|
248
|
+
* @returns Drowpdown Input Component
|
|
249
|
+
*/
|
|
250
|
+
function DropdownInput(props) {
|
|
251
|
+
const [value, setValue] = React.useState(props.defaultValue ?? '');
|
|
252
|
+
const theme = material.useTheme();
|
|
253
|
+
const [anchorEl, setAnchorEl] = React.useState(null);
|
|
254
|
+
const handleChange = (value, name) => {
|
|
255
|
+
setValue(value);
|
|
256
|
+
setAnchorEl(null);
|
|
257
|
+
props.onChangeCallback(value, name);
|
|
258
|
+
};
|
|
259
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [material.useMediaQuery(theme.breakpoints.up(props.collapseBreakpoint ?? 0)) ? (jsxRuntime.jsxs(material.FormControl, { sx: { width: props.width, textAlign: 'start' }, children: [jsxRuntime.jsx(material.InputLabel, { size: "small", children: props.label }), jsxRuntime.jsx(material.Select, { value: value, size: "small", label: props.label, onChange: (event) => handleChange(event.target.value, event.target.name), children: props.items.map((item) => {
|
|
260
|
+
return (jsxRuntime.jsx(material.MenuItem, { value: item.value, children: item.name }, item.value));
|
|
261
|
+
}) })] })) : (jsxRuntime.jsx(material.IconButton
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
263
|
+
, {
|
|
264
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
265
|
+
onClick: (event) => setAnchorEl(event.currentTarget), children: props.icon })), jsxRuntime.jsx(material.Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => setAnchorEl(null), children: props.items.map((item) => (jsxRuntime.jsx(material.MenuItem, { onClick: () => handleChange(item.value), children: item.name }, item.value))) })] }));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function ThumbnailListHeaderSort(props) {
|
|
269
|
+
const { setSortAscending, sortAscending, setSortBy, sortBy } = useThumbnailListItemContext();
|
|
270
|
+
console.log('Header sort rerenders');
|
|
271
|
+
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs(material.Box, { sx: { minWidth: '80px' }, children: [jsxRuntime.jsx(material.Tooltip, { title: "asc/desc", children: jsxRuntime.jsx(material.IconButton, { onClick: () => setSortAscending(!sortAscending), children: jsxRuntime.jsx(SwapVertIcon__default["default"], {}) }) }), jsxRuntime.jsx(DropdownInput, { width: "130px", collapseBreakpoint: props.muiBreakpoint, label: 'sort', defaultValue: sortBy, icon: jsxRuntime.jsx(material.Tooltip, { title: 'sort', children: jsxRuntime.jsx(SortIcon__default["default"], {}) }), items: props.items.map((i) => {
|
|
272
|
+
return { name: i.label, value: i.key.toString() };
|
|
273
|
+
}), onChangeCallback: (value) => setSortBy(value) })] }) }));
|
|
274
|
+
}
|
|
275
|
+
ThumbnailListHeaderSort.defaultProps = {
|
|
276
|
+
align: 'start',
|
|
277
|
+
muiBreakpoint: 'md',
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const ThumbnailListHeader = function (props) {
|
|
281
|
+
const startAlignedItems = [];
|
|
282
|
+
const endAlignedItems = [];
|
|
283
|
+
// Iterate through each child to categorize them based on their 'align' prop
|
|
284
|
+
React.Children.forEach(props.children, (child) => {
|
|
285
|
+
if (React__default["default"].isValidElement(child)) {
|
|
286
|
+
const alignment = child.props.align || 'start';
|
|
287
|
+
if (alignment === 'end') {
|
|
288
|
+
endAlignedItems.push(child);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
startAlignedItems.push(child);
|
|
292
|
+
}
|
|
301
293
|
}
|
|
302
|
-
}
|
|
294
|
+
});
|
|
295
|
+
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs(material.Stack, { direction: "row", alignItems: "center", justifyContent: props.justifyContent ?? 'space-between', gap: 2, children: [jsxRuntime.jsx(material.Stack, { direction: "row", alignItems: "center", gap: 2, justifyContent: "start", children: startAlignedItems }), endAlignedItems] }) }));
|
|
296
|
+
};
|
|
297
|
+
ThumbnailListHeader.SearchField = ThumbnailListSearchField$1;
|
|
298
|
+
ThumbnailListHeader.FilterTags = ThumbnailListFilterTags;
|
|
299
|
+
ThumbnailListHeader.Sort = ThumbnailListHeaderSort;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Main Component: Renders all sub components
|
|
303
|
+
* Includes ThumbnailList Provider for context data
|
|
304
|
+
* @param props react children, items
|
|
305
|
+
* @returns component
|
|
306
|
+
*/
|
|
307
|
+
function ThumbnailList(props) {
|
|
308
|
+
const combinedConfig = {
|
|
309
|
+
...defaultConfiguration,
|
|
310
|
+
...props.config, // This will override the defaults with any props that are not undefined
|
|
311
|
+
};
|
|
312
|
+
const [listItems, setListItems] = React.useState(props.items);
|
|
313
|
+
const { sortedItems, setSortBy, setSortAscending, sortAscending } = useSortedThumbnailListItems(listItems, combinedConfig.sortBy.toString(), combinedConfig.sortAscending);
|
|
314
|
+
const { tagFilteredItems, setTagAndCondition, tagAndCondition } = useTagFilteredThumbnailListItems({ allItems: sortedItems, initialTag: combinedConfig.tag.toString() });
|
|
315
|
+
const { setSearchTerm, filteredItems } = useFilteredThumbnailListItems(tagFilteredItems);
|
|
316
|
+
console.log('Thumbnaillist renders');
|
|
303
317
|
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(ThumbnailListItemContext.Provider, { value: {
|
|
304
|
-
items:
|
|
318
|
+
items: filteredItems,
|
|
305
319
|
setItems: setListItems,
|
|
306
320
|
originalItems: listItems,
|
|
307
321
|
setOriginalItems: setListItems,
|
|
@@ -311,10 +325,11 @@ const ThumbnailList = function (props) {
|
|
|
311
325
|
setSortAscending: setSortAscending,
|
|
312
326
|
sortAscending: sortAscending,
|
|
313
327
|
setSortBy: setSortBy,
|
|
314
|
-
|
|
315
|
-
|
|
328
|
+
sortBy: combinedConfig.sortBy.toString(),
|
|
329
|
+
isLoading: false,
|
|
330
|
+
}, children: jsxRuntime.jsx(material.Stack, { direction: "column", sx: { width: '100%', minWidth: '425px' }, children: props.children }) }) }));
|
|
331
|
+
}
|
|
316
332
|
ThumbnailList.MainContent = ThumbnailListMainContent;
|
|
317
|
-
ThumbnailList.Header = ThumbnailListHeader;
|
|
318
|
-
|
|
319
|
-
exports.
|
|
320
|
-
exports.ThumbnailList = ThumbnailList;
|
|
333
|
+
ThumbnailList.Header = ThumbnailListHeader;
|
|
334
|
+
|
|
335
|
+
exports.ThumbnailList = ThumbnailList;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,53 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { Stack } from '@mui/system';
|
|
5
|
-
import SearchIcon from '@mui/icons-material/Search';
|
|
6
|
-
import ClearIcon from '@mui/icons-material/Clear';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Hello World"), /*#__PURE__*/React.createElement(Button, {
|
|
12
|
-
variant: "contained"
|
|
13
|
-
}, "Contained"));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { styled, Box, Typography, Card, CardActionArea, Stack as Stack$1, Grid, LinearProgress, FormControl, TextField, InputAdornment, IconButton, useTheme, useMediaQuery, Chip, Tooltip, InputLabel, Select, MenuItem, Menu } from '@mui/material';
|
|
3
|
+
import React, { createContext, useContext, useMemo, useState, useCallback, useEffect, Children } from 'react';
|
|
4
|
+
import { Stack } from '@mui/system';
|
|
5
|
+
import SearchIcon from '@mui/icons-material/Search';
|
|
6
|
+
import ClearIcon from '@mui/icons-material/Clear';
|
|
7
|
+
import { debounce } from 'lodash';
|
|
8
|
+
import SwapVertIcon from '@mui/icons-material/SwapVert';
|
|
9
|
+
import SortIcon from '@mui/icons-material/Sort';
|
|
10
|
+
|
|
17
11
|
const ThumbnailListItemContext = createContext(undefined);
|
|
18
|
-
// Create a custom hook to consume the context
|
|
19
12
|
const useThumbnailListItemContext = () => {
|
|
20
13
|
const context = useContext(ThumbnailListItemContext);
|
|
21
14
|
if (!context) {
|
|
22
|
-
throw new Error('
|
|
15
|
+
throw new Error('no context provider available');
|
|
23
16
|
}
|
|
24
17
|
return context;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Can be used as parent component to crop a wrapped image
|
|
29
|
-
* @param props width: width for cropping
|
|
30
|
-
* height: height for cropping
|
|
31
|
-
* seperate xs and sm values for mui breakpoints
|
|
32
|
-
* @returns component
|
|
33
|
-
*/
|
|
34
|
-
function ImageCropper(props) {
|
|
35
|
-
const ThumbnailImageCrop = styled('div')((p) => ({
|
|
36
|
-
[p.theme.breakpoints.up('xs')]: {
|
|
37
|
-
minWidth: props.width.xs,
|
|
38
|
-
maxWidth: props.width.xs,
|
|
39
|
-
height: props.height.xs,
|
|
40
|
-
overflow: 'hidden'
|
|
41
|
-
},
|
|
42
|
-
[p.theme.breakpoints.up('sm')]: {
|
|
43
|
-
minWidth: props.width.sm,
|
|
44
|
-
maxwWidth: props.width.sm,
|
|
45
|
-
height: props.height.sm,
|
|
46
|
-
},
|
|
47
|
-
}));
|
|
48
|
-
return (jsx(Fragment, { children: jsx(ThumbnailImageCrop, { children: props.children }) }));
|
|
49
|
-
}
|
|
50
|
-
|
|
18
|
+
};
|
|
19
|
+
|
|
51
20
|
/**
|
|
52
21
|
* Creates a ellipies text with webkit css styles
|
|
53
22
|
* @param props lineClamp: lines till ellipses
|
|
@@ -58,128 +27,64 @@ function EllipsisContainer(props) {
|
|
|
58
27
|
[p.theme.breakpoints.up('xs')]: {
|
|
59
28
|
overflow: 'hidden',
|
|
60
29
|
display: '-webkit-box',
|
|
61
|
-
WebkitLineClamp: props.lineClamp.xs.toString()
|
|
30
|
+
WebkitLineClamp: props.lineClamp.xs.toString() /* number of lines to show */,
|
|
62
31
|
WebkitBoxOrient: 'vertical',
|
|
63
32
|
},
|
|
64
33
|
[p.theme.breakpoints.up('sm')]: {
|
|
65
34
|
overflow: 'hidden',
|
|
66
35
|
display: '-webkit-box',
|
|
67
|
-
WebkitLineClamp: props.lineClamp.sm.toString()
|
|
68
|
-
WebkitBoxOrient: 'vertical'
|
|
36
|
+
WebkitLineClamp: props.lineClamp.sm.toString() /* number of lines to show */,
|
|
37
|
+
WebkitBoxOrient: 'vertical' /* number of lines to show */,
|
|
69
38
|
},
|
|
70
39
|
}));
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
40
|
+
return jsx(EllipsisContainer, { children: props.children });
|
|
41
|
+
}
|
|
42
|
+
|
|
74
43
|
function ThumbnailListItemTitle(props) {
|
|
75
|
-
const StyledCardContent = styled('div')((
|
|
76
|
-
[
|
|
77
|
-
|
|
78
|
-
|
|
44
|
+
const StyledCardContent = styled('div')((p) => ({
|
|
45
|
+
[p.theme.breakpoints.up('xs')]: {
|
|
46
|
+
padding: p.theme.spacing(1),
|
|
47
|
+
flex: '1 0 auto',
|
|
79
48
|
'&:last-child': { paddingBottom: 0 },
|
|
80
|
-
|
|
49
|
+
overflow: 'hidden',
|
|
81
50
|
},
|
|
82
51
|
}));
|
|
83
|
-
|
|
84
|
-
return jsx(Fragment, { children: jsx(Box, { children: jsxs(StyledCardContent, { children: [jsx(EllipsisContainer, { lineClamp: { xs: 1, sm: 2 }, children: jsx(Typography, { variant: '
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function ThumbnailListItemInfoLabel(props) {
|
|
88
|
-
return jsx(Fragment, { children: jsxs(Stack$1, { textAlign: "right", justifyContent: "space-between", children: [jsx(Box, { textAlign: "right", padding: 1, children: props.topContent }), jsx(Box, { textAlign: "right", padding: 1, children: props.bottomContent })] }) });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// import {Link} from 'react-router-dom';
|
|
52
|
+
console.log('item title rerenders');
|
|
53
|
+
return (jsx(Fragment, { children: jsx(Box, { children: jsxs(StyledCardContent, { children: [jsx(EllipsisContainer, { lineClamp: { xs: 1, sm: 2 }, children: jsx(Typography, { variant: "subtitle2", sx: { fontWeight: 'bold' }, children: props.title }) }), jsx(Stack, { direction: "row", gap: 1, children: jsx(EllipsisContainer, { lineClamp: { xs: 1, sm: 2 }, children: jsx(Typography, { variant: "subtitle2", sx: { fontSize: '0.84rem' }, color: "text.secondary", children: props.subTitle }) }) })] }) }) }));
|
|
54
|
+
}
|
|
55
|
+
|
|
92
56
|
const ThumbnailListItem = (props) => {
|
|
93
|
-
|
|
57
|
+
console.log('ThumbnailListItems renders');
|
|
58
|
+
return (jsx(Fragment, { children: jsx(Card, { sx: { display: 'flex' }, children: jsx(CardActionArea, { disabled: !props.onClick, onClick: () => props.onClick(props.id), children: jsxs(Stack$1, { direction: "row", width: "100%", children: [jsx("img", { src: props.thumbnailUrl, width: '45%' }), jsxs(Stack$1, { direction: "row", justifyContent: "space-between", width: "100%", gap: 1, children: [jsx(ThumbnailListItemTitle, { title: props.title, subTitle: props.subTitle }), props.infoLabel] })] }) }) }) }));
|
|
94
59
|
};
|
|
95
|
-
ThumbnailListItem
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
117
|
-
return (jsx(Fragment, { children: jsx(FormControl, { children: jsx(TextField, { sx: { input: { color: 'white' } }, fullWidth: true, value: input, size: "small", variant: "outlined", onChange: (event) => handleChange(event.target.value), InputProps: {
|
|
118
|
-
startAdornment: (jsx(InputAdornment, { position: "start", children: jsx(SearchIcon, {}) })),
|
|
119
|
-
endAdornment: (jsx(InputAdornment, { position: "end", children: jsx(IconButton, { onClick: () => handleChange(''), sx: { visibility: showClearIcon, padding: 0 }, children: jsx(ClearIcon, {}) }) })),
|
|
120
|
-
} }) }) }));
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
function ThumbnailListFilterTag(props) {
|
|
124
|
-
const theme = useTheme();
|
|
125
|
-
const handleOnClick = (value) => {
|
|
126
|
-
if (props.onClickCallback) {
|
|
127
|
-
props.onClickCallback(value);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
return (jsx(Fragment, { children: useMediaQuery(theme.breakpoints.up(props.collapseBreakpoint ?? 0)) || !props.icon ?
|
|
131
|
-
jsx(Fragment, { children: jsx(Chip, { label: props.label, variant: props.variant, onClick: props.onClickCallback ? () => handleOnClick(props.value) : undefined }) }) :
|
|
132
|
-
jsx(Fragment, { children: jsx(Tooltip, { title: props.label, children: jsx(IconButton, { onClick: props.onClickCallback ? () => handleOnClick(props.value) : undefined, children: props.icon }) }) }) }));
|
|
60
|
+
var ThumbnailListItem$1 = React.memo(ThumbnailListItem);
|
|
61
|
+
|
|
62
|
+
const RatioWrapper = styled('div')(() => ({
|
|
63
|
+
// Assuming a 16:9 aspect ratio
|
|
64
|
+
paddingTop: '27.75%',
|
|
65
|
+
position: 'relative',
|
|
66
|
+
width: '100%',
|
|
67
|
+
'& > *': {
|
|
68
|
+
position: 'absolute',
|
|
69
|
+
top: 0,
|
|
70
|
+
left: 0,
|
|
71
|
+
right: 0,
|
|
72
|
+
bottom: 0,
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
75
|
+
function ThumbnailListMainContent(props) {
|
|
76
|
+
const { items, isLoading } = useThumbnailListItemContext();
|
|
77
|
+
console.log('main content rerenders');
|
|
78
|
+
const memoizedItems = useMemo(() => {
|
|
79
|
+
return items.map((item) => (jsx(Grid, { item: true, xs: props.muiBreakpoints.xs, sm: props.muiBreakpoints.sm, md: props.muiBreakpoints.md, lg: props.muiBreakpoints.lg, xl: props.muiBreakpoints.xl, children: jsx(RatioWrapper, { children: jsx(ThumbnailListItem$1, { id: item.id, thumbnailUrl: item.thumbnailUrl, title: item.title, subTitle: item.subTitle, infoLabel: item.label, onClick: item.onClick }) }) }, item.id)));
|
|
80
|
+
}, [items, props.muiBreakpoints]);
|
|
81
|
+
return (jsxs(Fragment, { children: [jsx(Box, { sx: { mt: 0.75, mb: 0.75 }, children: jsx(LinearProgress, { sx: { opacity: isLoading ? 1 : 0 } }) }), jsx(Grid, { container: true, spacing: props.spacing, children: memoizedItems })] }));
|
|
133
82
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}) }));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Displays a generic MUI select dropdown.
|
|
144
|
-
* Optinal collapses to a sort icon at a certain breakpoint
|
|
145
|
-
* @param props.label Select Label
|
|
146
|
-
* @param props.width * Width of the input field
|
|
147
|
-
* @param props.collapseBreakPoint * MUI breakpoint after that the select will collapse to the sort icon
|
|
148
|
-
* @param props.onChangeCallback * Callback function that gets triggered once a item is selected
|
|
149
|
-
* @param props.items * Array of items (name-value-pairs) that will be the select options
|
|
150
|
-
* @returns Drowpdown Input Component
|
|
151
|
-
*/
|
|
152
|
-
function DropdownInput(props) {
|
|
153
|
-
const [value, setValue] = useState(props.defaultValue ?? '');
|
|
154
|
-
const theme = useTheme();
|
|
155
|
-
const [anchorEl, setAnchorEl] = useState(null);
|
|
156
|
-
const handleChange = (value, name) => {
|
|
157
|
-
setValue(value);
|
|
158
|
-
setAnchorEl(null);
|
|
159
|
-
props.onChangeCallback(value, name);
|
|
160
|
-
};
|
|
161
|
-
return (jsxs(Fragment, { children: [useMediaQuery(theme.breakpoints.up(props.collapseBreakpoint ?? 0)) ?
|
|
162
|
-
jsxs(FormControl, { sx: { width: props.width, textAlign: 'start' }, children: [jsx(InputLabel, { size: 'small', id: "demo-simple-select-label", children: props.label }), jsx(Select, { value: value, size: 'small', label: props.label, onChange: (event) => handleChange(event.target.value, event.target.name), children: props.items.map((item) => {
|
|
163
|
-
return jsx(MenuItem, { value: item.value, children: item.name }, item.value);
|
|
164
|
-
}) })] }) : (jsx(IconButton
|
|
165
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
-
, {
|
|
167
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
168
|
-
onClick: (event) => setAnchorEl(event.currentTarget), children: props.icon })), jsx(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => setAnchorEl(null), children: props.items.map((item) => (jsx(MenuItem, { onClick: () => handleChange(item.value), children: item.name }, item.value))) })] }));
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function ThumbnailListHeaderSort(props) {
|
|
172
|
-
const { setSortAscending, sortAscending, setSortBy } = useThumbnailListItemContext();
|
|
173
|
-
return (jsx(Fragment, { children: jsxs(Box, { sx: { width: '-webkit-fill-available', textAlign: 'end' }, children: [jsx(Tooltip, { title: "asc/desc", children: jsx(IconButton, { onClick: () => setSortAscending(!sortAscending), children: jsx(SwapVertIcon, {}) }) }), jsx(DropdownInput, { width: "130px", collapseBreakpoint: 'md', label: 'sort', defaultValue: "creationTimeStamp", icon: jsx(Tooltip, { title: 'sort', children: jsx(SortIcon, {}) }), items: props.items, onChangeCallback: (value) => setSortBy(value) })] }) }));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const ThumbnailListHeader = function (props) {
|
|
177
|
-
return (jsx(Fragment, { children: jsx(Grid, { item: true, xs: 12, children: jsx(Stack$1, { direction: "row", alignItems: "center", justifyContent: props.justifyContent ?? 'space-between', gap: 2, children: props.children }) }) }));
|
|
178
|
-
};
|
|
179
|
-
ThumbnailListHeader.SearchField = ThumbnailListSearchField;
|
|
180
|
-
ThumbnailListHeader.FilterTags = ThumbnailListFilterTags;
|
|
181
|
-
ThumbnailListHeader.Sort = ThumbnailListHeaderSort;
|
|
182
|
-
|
|
83
|
+
ThumbnailListMainContent.defaultProps = {
|
|
84
|
+
spacing: 2,
|
|
85
|
+
muiBreakpoints: { xs: 12, sm: 6, md: 6, lg: 4, xl: 3 },
|
|
86
|
+
};
|
|
87
|
+
|
|
183
88
|
/**
|
|
184
89
|
* Generic method that sorts an array of items based on an item key
|
|
185
90
|
* @param values The array that should be sorted
|
|
@@ -217,20 +122,18 @@ function filterByTag(array, tagType, condition) {
|
|
|
217
122
|
console.log('filter array');
|
|
218
123
|
console.log(filteredArray);
|
|
219
124
|
return [...filteredArray];
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const useTagFilteredThumbnailListItems = ({ allItems, initialTag, initialCondition }) => {
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const useTagFilteredThumbnailListItems = ({ allItems, initialTag, initialCondition, }) => {
|
|
224
128
|
const [tagAndCondition, setTagAndCondition] = useState({ tag: initialTag, condition: initialCondition });
|
|
225
|
-
const [tagFilteredItems, setTagFilteredItems] = useState(allItems);
|
|
226
129
|
const setTagWithCondition = (t, c) => {
|
|
227
130
|
setTagAndCondition({ tag: t, condition: c });
|
|
228
131
|
};
|
|
229
|
-
|
|
230
|
-
const tagFiltered = tagAndCondition.tag === 'id'
|
|
231
|
-
allItems
|
|
232
|
-
filterByTag(allItems, tagAndCondition.tag, tagAndCondition.condition);
|
|
233
|
-
|
|
132
|
+
const tagFilteredItems = useMemo(() => {
|
|
133
|
+
const tagFiltered = tagAndCondition.tag === 'id'
|
|
134
|
+
? allItems
|
|
135
|
+
: filterByTag(allItems, tagAndCondition.tag, tagAndCondition.condition);
|
|
136
|
+
return tagFiltered;
|
|
234
137
|
}, [allItems, tagAndCondition]);
|
|
235
138
|
return {
|
|
236
139
|
tagAndCondition,
|
|
@@ -238,8 +141,8 @@ const useTagFilteredThumbnailListItems = ({ allItems, initialTag, initialConditi
|
|
|
238
141
|
tagFilteredItems,
|
|
239
142
|
setTagWithCondition,
|
|
240
143
|
};
|
|
241
|
-
};
|
|
242
|
-
|
|
144
|
+
};
|
|
145
|
+
|
|
243
146
|
/**
|
|
244
147
|
* Filters a list of event by a search term
|
|
245
148
|
* @param allEvents event list that will be formatted
|
|
@@ -248,48 +151,159 @@ const useTagFilteredThumbnailListItems = ({ allItems, initialTag, initialConditi
|
|
|
248
151
|
*/
|
|
249
152
|
const useFilteredThumbnailListItems = (allItems, initialSearchTerm = '') => {
|
|
250
153
|
const [searchTerm, setSearchTerm] = useState(initialSearchTerm);
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const filtered = [...allItems].filter((item) => item.title.toLowerCase().includes(searchTerm.toLowerCase()));
|
|
255
|
-
setFilteredItems(filtered);
|
|
256
|
-
};
|
|
257
|
-
filterEvents();
|
|
154
|
+
const filteredItems = useMemo(() => {
|
|
155
|
+
const filtered = [...allItems].filter((item) => item.title.toLowerCase().includes(searchTerm.toLowerCase()));
|
|
156
|
+
return filtered;
|
|
258
157
|
}, [allItems, searchTerm]);
|
|
259
158
|
return { searchTerm, setSearchTerm, filteredItems };
|
|
260
|
-
};
|
|
261
|
-
|
|
159
|
+
};
|
|
160
|
+
|
|
262
161
|
const useSortedThumbnailListItems = (allItems, initialSortBy, initialSortAscending) => {
|
|
263
162
|
const [sortBy, setSortBy] = useState(initialSortBy);
|
|
264
163
|
const [sortAscending, setSortAscending] = useState(initialSortAscending);
|
|
265
|
-
const
|
|
266
|
-
useEffect(() => {
|
|
164
|
+
const sortedItems = useMemo(() => {
|
|
267
165
|
let sorted = orderByArray(allItems, sortBy);
|
|
268
166
|
if (!sortAscending) {
|
|
269
167
|
sorted = sorted.reverse();
|
|
270
168
|
}
|
|
271
|
-
|
|
272
|
-
}, [allItems, sortBy, sortAscending
|
|
169
|
+
return sorted;
|
|
170
|
+
}, [allItems, sortBy, sortAscending]);
|
|
273
171
|
return { sortBy, sortAscending, setSortBy, setSortAscending, sortedItems };
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const defaultConfiguration = {
|
|
175
|
+
sortBy: 'id',
|
|
176
|
+
sortAscending: true,
|
|
177
|
+
tag: 'id',
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const ThumbnailListSearchField = () => {
|
|
181
|
+
const [input, setInput] = useState('');
|
|
182
|
+
const [showClearIcon, setShowClearIcon] = useState('hidden');
|
|
183
|
+
const { setSearchTerm } = useThumbnailListItemContext();
|
|
184
|
+
console.log('Searchfield rerenders');
|
|
185
|
+
const handleChange = (value) => {
|
|
186
|
+
setInput(value);
|
|
187
|
+
setShowClearIcon(value === '' ? 'hidden' : '');
|
|
188
|
+
};
|
|
189
|
+
const debouncedSetSearchTerm = useCallback(debounce(setSearchTerm, 50), []);
|
|
285
190
|
useEffect(() => {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
191
|
+
debouncedSetSearchTerm(input);
|
|
192
|
+
return () => {
|
|
193
|
+
debouncedSetSearchTerm.cancel();
|
|
194
|
+
};
|
|
195
|
+
}, [input, debouncedSetSearchTerm]);
|
|
196
|
+
return (jsx(Box, { sx: { marginLeft: 'auto' }, children: jsx(FormControl, { children: jsx(TextField, { fullWidth: true, value: input, size: "small", variant: "outlined", onChange: (event) => handleChange(event.target.value), InputProps: {
|
|
197
|
+
startAdornment: (jsx(InputAdornment, { position: "start", children: jsx(SearchIcon, {}) })),
|
|
198
|
+
endAdornment: (jsx(InputAdornment, { position: "end", children: jsx(IconButton, { onClick: () => handleChange(''), sx: { visibility: showClearIcon, padding: 0 }, children: jsx(ClearIcon, {}) }) })),
|
|
199
|
+
} }) }) }));
|
|
200
|
+
};
|
|
201
|
+
ThumbnailListSearchField.defaultProps = {
|
|
202
|
+
align: 'start',
|
|
203
|
+
};
|
|
204
|
+
var ThumbnailListSearchField$1 = React.memo(ThumbnailListSearchField);
|
|
205
|
+
|
|
206
|
+
function ThumbnailListFilterTag(props) {
|
|
207
|
+
const theme = useTheme();
|
|
208
|
+
const handleOnClick = (value) => {
|
|
209
|
+
if (props.onClickCallback) {
|
|
210
|
+
props.onClickCallback(value);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
return (jsx(Fragment, { children: useMediaQuery(theme.breakpoints.up(props.collapseBreakpoint ?? 0)) || !props.icon ? (jsx(Fragment, { children: jsx(Chip, { label: props.label, variant: props.variant, onClick: props.onClickCallback ? () => handleOnClick(props.value) : undefined }) })) : (jsx(Fragment, { children: jsx(Tooltip, { title: props.label, children: jsx(IconButton, { onClick: props.onClickCallback ? () => handleOnClick(props.value) : undefined, children: props.icon }) }) })) }));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function ThumbnailListFilterTags(props) {
|
|
217
|
+
const { tagFilterCallback, tagAndCondition } = useThumbnailListItemContext();
|
|
218
|
+
console.log('filter tags rerenders');
|
|
219
|
+
return (jsx(Fragment, { children: props.tags.map((tag, index) => {
|
|
220
|
+
return (jsx(ThumbnailListFilterTag, { label: tag.label, value: tag.key.toString(), variant: tagAndCondition.tag === tag.key ? 'filled' : 'outlined', collapseBreakpoint: props.muiCollapseBreakpoint, onClickCallback: (value) => tagFilterCallback({ tag: value, condition: tag.condition }), icon: tag.icon }, `${index}_${tag.key.toString()}`));
|
|
221
|
+
}) }));
|
|
222
|
+
}
|
|
223
|
+
ThumbnailListFilterTags.defaultProps = {
|
|
224
|
+
align: 'start',
|
|
225
|
+
muiCollapseBreakpoint: 'md',
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Displays a generic MUI select dropdown.
|
|
230
|
+
* Optinal collapses to a sort icon at a certain breakpoint
|
|
231
|
+
* @param props.label Select Label
|
|
232
|
+
* @param props.width * Width of the input field
|
|
233
|
+
* @param props.collapseBreakPoint * MUI breakpoint after that the select will collapse to the sort icon
|
|
234
|
+
* @param props.onChangeCallback * Callback function that gets triggered once a item is selected
|
|
235
|
+
* @param props.items * Array of items (name-value-pairs) that will be the select options
|
|
236
|
+
* @returns Drowpdown Input Component
|
|
237
|
+
*/
|
|
238
|
+
function DropdownInput(props) {
|
|
239
|
+
const [value, setValue] = useState(props.defaultValue ?? '');
|
|
240
|
+
const theme = useTheme();
|
|
241
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
242
|
+
const handleChange = (value, name) => {
|
|
243
|
+
setValue(value);
|
|
244
|
+
setAnchorEl(null);
|
|
245
|
+
props.onChangeCallback(value, name);
|
|
246
|
+
};
|
|
247
|
+
return (jsxs(Fragment, { children: [useMediaQuery(theme.breakpoints.up(props.collapseBreakpoint ?? 0)) ? (jsxs(FormControl, { sx: { width: props.width, textAlign: 'start' }, children: [jsx(InputLabel, { size: "small", children: props.label }), jsx(Select, { value: value, size: "small", label: props.label, onChange: (event) => handleChange(event.target.value, event.target.name), children: props.items.map((item) => {
|
|
248
|
+
return (jsx(MenuItem, { value: item.value, children: item.name }, item.value));
|
|
249
|
+
}) })] })) : (jsx(IconButton
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
251
|
+
, {
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
|
+
onClick: (event) => setAnchorEl(event.currentTarget), children: props.icon })), jsx(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => setAnchorEl(null), children: props.items.map((item) => (jsx(MenuItem, { onClick: () => handleChange(item.value), children: item.name }, item.value))) })] }));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function ThumbnailListHeaderSort(props) {
|
|
257
|
+
const { setSortAscending, sortAscending, setSortBy, sortBy } = useThumbnailListItemContext();
|
|
258
|
+
console.log('Header sort rerenders');
|
|
259
|
+
return (jsx(Fragment, { children: jsxs(Box, { sx: { minWidth: '80px' }, children: [jsx(Tooltip, { title: "asc/desc", children: jsx(IconButton, { onClick: () => setSortAscending(!sortAscending), children: jsx(SwapVertIcon, {}) }) }), jsx(DropdownInput, { width: "130px", collapseBreakpoint: props.muiBreakpoint, label: 'sort', defaultValue: sortBy, icon: jsx(Tooltip, { title: 'sort', children: jsx(SortIcon, {}) }), items: props.items.map((i) => {
|
|
260
|
+
return { name: i.label, value: i.key.toString() };
|
|
261
|
+
}), onChangeCallback: (value) => setSortBy(value) })] }) }));
|
|
262
|
+
}
|
|
263
|
+
ThumbnailListHeaderSort.defaultProps = {
|
|
264
|
+
align: 'start',
|
|
265
|
+
muiBreakpoint: 'md',
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const ThumbnailListHeader = function (props) {
|
|
269
|
+
const startAlignedItems = [];
|
|
270
|
+
const endAlignedItems = [];
|
|
271
|
+
// Iterate through each child to categorize them based on their 'align' prop
|
|
272
|
+
Children.forEach(props.children, (child) => {
|
|
273
|
+
if (React.isValidElement(child)) {
|
|
274
|
+
const alignment = child.props.align || 'start';
|
|
275
|
+
if (alignment === 'end') {
|
|
276
|
+
endAlignedItems.push(child);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
startAlignedItems.push(child);
|
|
280
|
+
}
|
|
289
281
|
}
|
|
290
|
-
}
|
|
282
|
+
});
|
|
283
|
+
return (jsx(Fragment, { children: jsxs(Stack$1, { direction: "row", alignItems: "center", justifyContent: props.justifyContent ?? 'space-between', gap: 2, children: [jsx(Stack$1, { direction: "row", alignItems: "center", gap: 2, justifyContent: "start", children: startAlignedItems }), endAlignedItems] }) }));
|
|
284
|
+
};
|
|
285
|
+
ThumbnailListHeader.SearchField = ThumbnailListSearchField$1;
|
|
286
|
+
ThumbnailListHeader.FilterTags = ThumbnailListFilterTags;
|
|
287
|
+
ThumbnailListHeader.Sort = ThumbnailListHeaderSort;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Main Component: Renders all sub components
|
|
291
|
+
* Includes ThumbnailList Provider for context data
|
|
292
|
+
* @param props react children, items
|
|
293
|
+
* @returns component
|
|
294
|
+
*/
|
|
295
|
+
function ThumbnailList(props) {
|
|
296
|
+
const combinedConfig = {
|
|
297
|
+
...defaultConfiguration,
|
|
298
|
+
...props.config, // This will override the defaults with any props that are not undefined
|
|
299
|
+
};
|
|
300
|
+
const [listItems, setListItems] = useState(props.items);
|
|
301
|
+
const { sortedItems, setSortBy, setSortAscending, sortAscending } = useSortedThumbnailListItems(listItems, combinedConfig.sortBy.toString(), combinedConfig.sortAscending);
|
|
302
|
+
const { tagFilteredItems, setTagAndCondition, tagAndCondition } = useTagFilteredThumbnailListItems({ allItems: sortedItems, initialTag: combinedConfig.tag.toString() });
|
|
303
|
+
const { setSearchTerm, filteredItems } = useFilteredThumbnailListItems(tagFilteredItems);
|
|
304
|
+
console.log('Thumbnaillist renders');
|
|
291
305
|
return (jsx(Fragment, { children: jsx(ThumbnailListItemContext.Provider, { value: {
|
|
292
|
-
items:
|
|
306
|
+
items: filteredItems,
|
|
293
307
|
setItems: setListItems,
|
|
294
308
|
originalItems: listItems,
|
|
295
309
|
setOriginalItems: setListItems,
|
|
@@ -299,9 +313,11 @@ const ThumbnailList = function (props) {
|
|
|
299
313
|
setSortAscending: setSortAscending,
|
|
300
314
|
sortAscending: sortAscending,
|
|
301
315
|
setSortBy: setSortBy,
|
|
302
|
-
|
|
303
|
-
|
|
316
|
+
sortBy: combinedConfig.sortBy.toString(),
|
|
317
|
+
isLoading: false,
|
|
318
|
+
}, children: jsx(Stack$1, { direction: "column", sx: { width: '100%', minWidth: '425px' }, children: props.children }) }) }));
|
|
319
|
+
}
|
|
304
320
|
ThumbnailList.MainContent = ThumbnailListMainContent;
|
|
305
|
-
ThumbnailList.Header = ThumbnailListHeader;
|
|
306
|
-
|
|
307
|
-
export {
|
|
321
|
+
ThumbnailList.Header = ThumbnailListHeader;
|
|
322
|
+
|
|
323
|
+
export { ThumbnailList };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@janrankenhohn/react-thumbnail-list",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"module": "dist/index.esm.js",
|
|
6
6
|
"files": [
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@babel/core": "^7.23.9",
|
|
44
44
|
"@babel/preset-env": "^7.23.9",
|
|
45
45
|
"@babel/preset-react": "^7.23.3",
|
|
46
|
+
"@eslint/js": "^9.1.1",
|
|
46
47
|
"@rollup/plugin-babel": "^6.0.4",
|
|
47
48
|
"@rollup/plugin-commonjs": "^25.0.7",
|
|
48
49
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
@@ -51,22 +52,32 @@
|
|
|
51
52
|
"@testing-library/react": "^13.4.0",
|
|
52
53
|
"@testing-library/user-event": "^13.5.0",
|
|
53
54
|
"@types/lodash": "^4.17.0",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
|
56
|
+
"@typescript-eslint/parser": "^7.8.0",
|
|
57
|
+
"eslint": "^8.57.0",
|
|
58
|
+
"eslint-config-prettier": "^9.1.0",
|
|
59
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
54
60
|
"eslint-plugin-react": "^7.34.1",
|
|
61
|
+
"globals": "^15.1.0",
|
|
62
|
+
"jest": "^27.5.1",
|
|
55
63
|
"npm-run-all": "^4.1.5",
|
|
64
|
+
"prettier": "^3.2.5",
|
|
65
|
+
"react-router": "^6.26.1",
|
|
56
66
|
"rollup": "^2.79.1",
|
|
57
67
|
"rollup-plugin-delete": "^2.0.0",
|
|
58
68
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
59
|
-
"rollup-plugin-ts": "^3.4.5"
|
|
69
|
+
"rollup-plugin-ts": "^3.4.5",
|
|
70
|
+
"typescript-eslint": "^7.8.0"
|
|
60
71
|
},
|
|
61
72
|
"peerDependencies": {
|
|
62
73
|
"@emotion/react": "^11.11.4",
|
|
63
74
|
"@emotion/styled": "^11.11.0",
|
|
64
75
|
"@mui/icons-material": "^5.15.11",
|
|
65
76
|
"@mui/material": "^5.15.11",
|
|
77
|
+
"lodash.debounce": "^4.0.8",
|
|
66
78
|
"react": "^18.0.0",
|
|
67
79
|
"react-dom": "^18.0.0",
|
|
68
80
|
"react-scripts": "5.0.1",
|
|
69
|
-
"web-vitals": "^2.1.4"
|
|
70
|
-
"lodash.debounce": "^4.0.8"
|
|
81
|
+
"web-vitals": "^2.1.4"
|
|
71
82
|
}
|
|
72
83
|
}
|