@elacity-js/uikit
Version:
React / Material UI Design kit for Elacity project
208 lines (205 loc) • 11.1 kB
JavaScript
import { __rest } from '../../node_modules/tslib/tslib.es6.js';
import { jsxs, jsx } from 'react/jsx-runtime';
import React__default, { useState } from 'react';
import '@elacity-js/lib';
import { Icon } from '@iconify/react';
import searchFill from '@iconify/icons-eva/search-fill';
import closeFill from '@iconify/icons-eva/close-fill';
import { styled, alpha } from '@mui/material/styles';
import { ListItem, ListSubheader, TextField, ClickAwayListener, IconButton, Slide, Input, InputAdornment, Box, Popper, Paper, List } from '@mui/material';
import useDebounce from '../../hooks/useDebounce.js';
import Spinner from '../Spinner.js';
import Scrollbar from '../Scrollbar.js';
// @TODO: use searchContext for a better separation of UI/UX and flows
// in same way, make sure to review SearchContext to have a better implementation
styled(ListItem)(({ theme }) => ({
color: theme.palette.text.primary,
}));
styled(ListSubheader)(({ theme }) => ({
marginTop: 8,
// produce glass effect on groupd header
background: alpha(theme.palette.background.default, 0.85),
backdropFilter: 'blur(6px)',
WebkitBackdropFilter: 'blur(6px)',
}));
// @todo: find the appropriate type for WithTheme usage
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const SearchbarStyle = styled('div')(({ theme }) => {
var _a, _b;
return ({
top: 0,
left: 0,
zIndex: 999,
width: '100%',
display: 'flex',
position: 'absolute',
alignItems: 'center',
height: (_a = theme.layoutSettings) === null || _a === void 0 ? void 0 : _a.appBarMobile,
padding: theme.spacing(0, 3),
boxShadow: theme.shadows[4],
backdropFilter: 'blur(10px)',
WebkitBackdropFilter: 'blur(10px)',
backgroundColor: `${alpha(theme.palette.background.default, 0.8)}`,
[theme.breakpoints.up('md')]: {
height: (_b = theme.layoutSettings) === null || _b === void 0 ? void 0 : _b.appBarDesktop,
padding: theme.spacing(0, 5),
},
});
});
const SearchResultPopover = (_a) => {
var { searchTerm, anchorEl, hasWindow, onClose, fullScreen, renderResult, useQuery } = _a, props = __rest(_a, ["searchTerm", "anchorEl", "hasWindow", "onClose", "fullScreen", "renderResult", "useQuery"]);
const [open, setOpen] = useState(false);
React__default.useEffect(() => {
if (searchTerm.length >= 3 && Boolean(anchorEl) && hasWindow) {
setOpen(true);
}
else {
setOpen(false);
}
}, [searchTerm, anchorEl, hasWindow]);
const { data: result, isFetching } = useQuery(searchTerm, {
skip: !open,
});
return (jsx(Popper, Object.assign({ style: { borderRadius: 12 }, open: open, anchorEl: anchorEl, placement: "bottom-start", modifiers: [
{
// This part is aimed to set the popover
// to cover full width (if `fullScreen=true`)
name: 'applyFullWidth',
phase: 'beforeWrite',
enabled: Boolean(fullScreen),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fn: ({ state }) => {
state.styles.popper.width = '100%';
},
},
{
name: 'applyWidth',
phase: 'beforeWrite',
enabled: !fullScreen,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fn: ({ state }) => {
const anchorWidth = (anchorEl === null || anchorEl === void 0 ? void 0 : anchorEl.clientWidth) || 0;
state.styles.popper.width = anchorWidth > 0 ? `${anchorWidth}px` : 'calc(100vw / 3)';
state.styles.popper['margin-top'] = '10px';
},
},
] }, props, { children: jsx(Paper, Object.assign({ sx: {
mt: 0,
borderRadius: {
xs: 0,
sm: 0,
md: 0,
lg: 1,
},
height: {
xs: 'calc(100vh - 56px - 64px)',
sm: 'calc(100vh - 64px)',
md: 'calc(100vh - 56px)',
lg: 520,
},
bgcolor: (t) => `${alpha(t.palette.background.default, 0.7)}`,
backdropFilter: 'blur(6px)',
WebkitBackdropFilter: 'blur(6px)',
} }, { children: isFetching ? (jsx(Spinner.Dots, { sx: { py: { xs: 4, md: 6, lg: 8 } } })) : (jsx(Scrollbar, Object.assign({ sx: {
maxHeight: '100vh',
'& .simplebar-content': {
height: '100%',
display: 'flex',
flexDirection: 'column',
},
overflowX: 'hidden',
} }, { children: jsx(List, Object.assign({ sx: {
position: 'relative',
overflow: 'auto',
height: {
xs: 'calc(100vh - 56px - 64px)',
sm: 'calc(100vh - 64px)',
md: 'calc(100vh - 56px)',
lg: 520,
},
padding: 0,
'& ul': { padding: 0 },
}, subheader: jsx("li", {}) }, { children: renderResult(result) })) }))) })) })));
};
const SearchbarWrapper = styled('div')(({ theme }) => ({
'& .UiPopper-Wrapper [role="tooltip"]': Object.assign({ zIndex: 999 }, theme.glassy(theme.palette.background.default, 0.82, 4)),
}));
var Searchbar = ({ renderResult, useQuery }) => {
const [isOpen, setOpen] = useState(false);
const anchorEl = React__default.useRef();
const containerEl = React__default.useRef();
const [searchTerm, setSearchTerm] = React__default.useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
const handleOpen = () => {
setOpen((prev) => !prev);
};
const handleClose = () => {
setOpen(false);
setSearchTerm('');
};
return (jsxs(SearchbarWrapper, { children: [jsx("div", { className: "UiPopper-Wrapper", ref: containerEl }), jsx(ClickAwayListener, Object.assign({ onClickAway: handleClose }, { children: jsxs("div", { children: [!isOpen && (jsx(IconButton, Object.assign({ onClick: handleOpen }, { children: jsx(Icon, { icon: searchFill, width: 20, height: 20 }) }))), jsx(Slide, Object.assign({ direction: "down", in: isOpen, mountOnEnter: true, unmountOnExit: true, ref: anchorEl }, { children: jsx(SearchbarStyle, { children: jsx(Input, { autoFocus: true, fullWidth: true, disableUnderline: true, placeholder: "Search...", startAdornment: (jsx(InputAdornment, Object.assign({ position: "start" }, { children: jsx(Box, { component: Icon, icon: searchFill, sx: { color: 'text.disabled', width: 20, height: 20 } }) }))), endAdornment: (jsx(InputAdornment, Object.assign({ position: "end" }, { children: jsx(IconButton, Object.assign({ onClick: handleClose }, { children: jsx(Box, { component: Icon, icon: closeFill, sx: { color: 'text.disabled', width: 20, height: 20 } }) })) }))), sx: { mr: 1, fontWeight: 'fontWeightBold' }, onChange: (e) => setSearchTerm(e.target.value), value: searchTerm }) }) })), jsx(SearchResultPopover, { anchorEl: anchorEl.current, searchTerm: debouncedSearchTerm, hasWindow: isOpen, container: containerEl.current, onClose: handleClose, fullScreen: true, renderResult: renderResult, useQuery: useQuery })] }) }))] }));
};
const SearchInput = styled(TextField)(({ theme }) => ({
'& .MuiInputBase-root': {
borderRadius: theme.spacing(4),
backgroundColor: theme.palette.mode === 'light' ? 'white' : 'black',
'&:hover, &.Mui-focused': {
borderColor: 'transparent',
borderWidth: 2,
'&:after': {
content: '""',
position: 'absolute',
top: 'calc(-1 * 3px)',
left: 'calc(-1 * 3px)',
height: 'calc(100% + 3px * 2)',
width: 'calc(100% + 3px * 2)',
background: `linear-gradient(120deg, ${theme.palette.primary.main}, 35%, ${theme.palette.vivid.main})`,
borderRadius: theme.spacing(4),
zIndex: -1,
backgroundSize: '300% 300%',
},
'& .MuiOutlinedInput-notchedOutline': {
border: 'none',
},
},
},
'& .MuiOutlinedInput-notchedOutline': {
borderColor: 'transparent',
},
'& .MuiInputAdornment-positionEnd': {
position: 'absolute',
right: 2,
'& .MuiIconButton-root': {
background: alpha(theme.palette.grey[300], 0.5),
'&:hover': {
background: alpha(theme.palette.grey[400], 0.5),
},
},
},
}));
const SearchbarVisible = ({ renderResult, useQuery }) => {
const anchorEl = React__default.useRef();
const containerEl = React__default.useRef();
const [isOpen, setOpen] = useState(false);
const [searchTerm, setSearchTerm] = React__default.useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
React__default.useEffect(() => {
if (debouncedSearchTerm.length >= 3) {
if (!isOpen) {
setOpen(true);
}
}
else {
setOpen(false);
}
}, [debouncedSearchTerm]);
const handleClose = () => {
setOpen(false);
setSearchTerm('');
};
return (jsx(Box, Object.assign({ sx: { width: 'calc(100vw / 3)', maxWidth: 480, ml: 1 } }, { children: jsxs(SearchbarWrapper, { children: [jsx("div", { className: "UiPopper-Wrapper", ref: containerEl }), jsx(SearchInput, { placeholder: "Search...", size: "small", fullWidth: true, ref: anchorEl, onChange: (e) => setSearchTerm(e.target.value), value: searchTerm, InputProps: Object.assign({ autoComplete: 'off', startAdornment: (jsx(InputAdornment, Object.assign({ position: "start" }, { children: jsx(Box, { component: Icon, icon: searchFill, sx: { color: 'text.disabled', width: 20, height: 20 } }) }))) }, (searchTerm.length > 0 && {
endAdornment: (jsx(InputAdornment, Object.assign({ position: "end" }, { children: jsx(IconButton, Object.assign({ onClick: handleClose, color: "default" }, { children: jsx(Box, { component: Icon, icon: closeFill, sx: { color: 'text.disabled', width: 20, height: 20 } }) })) }))),
})) }), jsx(ClickAwayListener, Object.assign({ onClickAway: handleClose }, { children: jsx("div", { children: jsx(SearchResultPopover, { anchorEl: anchorEl.current, searchTerm: debouncedSearchTerm, hasWindow: isOpen, container: containerEl.current, onClose: handleClose, renderResult: renderResult, useQuery: useQuery }) }) }))] }) })));
};
export { SearchbarVisible, Searchbar as default };
//# sourceMappingURL=Searchbar.js.map