UNPKG

@elacity-js/uikit

Version:

React / Material UI Design kit for Elacity project

208 lines (205 loc) 11.1 kB
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