Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search bar new design #302

Merged
merged 10 commits into from
Oct 23, 2023
210 changes: 88 additions & 122 deletions frontend/src/components/Home/Autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import React, { useEffect, useState, useRef } from 'react';
import {
Chip,
CircularProgress,
ClickAwayListener,
Grid,
MenuItem,
MenuList,
TextField,
Typography,
Link,
} from '@material-ui/core';
import React, { ReactElement, useEffect, useState, useRef } from 'react';
import { useLocation } from 'react-router-dom';
import { CircularProgress, TextField } from '@material-ui/core';
import { get } from '../../utils/call';
import { LandlordOrApartmentWithLabel } from '../../../../common/types/db-types';
import SearchIcon from '@material-ui/icons/Search';
import { makeStyles } from '@material-ui/core/styles';
import { Link as RouterLink } from 'react-router-dom';
import { colors } from '../../colors';
import { useHistory } from 'react-router-dom';

export default function Autocomplete() {
type Props = {
drawerOpen: boolean;
};

const Autocomplete = ({ drawerOpen }: Props): ReactElement => {
const [isMobile, setIsMobile] = useState<boolean>(false);
const useStyles = makeStyles((theme) => ({
menuList: {
Expand All @@ -38,26 +32,43 @@ export default function Autocomplete() {
buildingText: {
color: colors.black,
},
searchIcon: { paddingRight: '10px' },
homeSearchIcon: {
paddingRight: '10px',
},
searchIcon: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
// fontSize: isMobile ? 17 : 22
height: '62%',
width: '62%',
},
searchIconBackground: {
backgroundColor: colors.red1,
width: '50px',
height: isMobile ? '35px' : '45px',
position: 'absolute',
right: '0',
borderRadius: '0px 10px 10px 0px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
resultChip: { cursor: 'pointer' },
field: {
'&.Mui-focused': {
border: '20px black ',
'& .MuiOutlinedInput-notchedOutline': {
border: 'none',
border: `1px solid ${colors.red1}`,
},
},
height: isMobile ? '35px' : '50px',
height: isMobile ? '35px' : '45px',
},
}));
const { menuList, text, searchIcon, resultChip, field, addressText, buildingText } = useStyles();
const [focus, setFocus] = useState(false);
const { text, searchIcon, homeSearchIcon, searchIconBackground, field } = useStyles();
const inputRef = useRef<HTMLDivElement>(document.createElement('div'));
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const [options, setOptions] = useState<LandlordOrApartmentWithLabel[]>([]);
const [query, setQuery] = useState('');
const [selected, setSelected] = useState<LandlordOrApartmentWithLabel | null>(null);
const [width, setWidth] = useState(inputRef.current?.offsetWidth);

const history = useHistory();
Expand All @@ -69,102 +80,28 @@ export default function Autocomplete() {
return () => window.removeEventListener('resize', handleResize);
}, []);

function handleListKeyDown(event: React.KeyboardEvent) {
event.preventDefault();
if (event.key === 'Tab') {
setOpen(false);
}
}

function textFieldHandleListKeyDown(event: React.KeyboardEvent) {
if (event.key === 'ArrowDown') {
setFocus(true);
} else if (event.key === 'Enter') {
setFocus(true);
history.push(`/search?q=${query}`);
setOpen(false);
setQuery('');
}
}

const handleSearchIconClick = () => {
history.push(`/search?q=${query}`);
setQuery('');
};

const handleOnChange = (query: string) => {
setQuery(query);
setSelected(null);
if (query !== '') {
setLoading(true);
} else {
setLoading(false);
}
};

const Menu = () => {
return (
<div>
<ClickAwayListener
onClickAway={() => {
setOpen(false);
}}
>
<div>
{open ? (
<MenuList
style={{ width: `${inputRef.current?.offsetWidth}px`, zIndex: 1 }}
className={menuList}
autoFocusItem={focus}
onKeyDown={handleListKeyDown}
>
{options.length === 0 ? (
<MenuItem disabled>No search results.</MenuItem>
) : (
options.map(({ id, name, address, label }, index) => {
return (
<Link
key={index}
{...{
to: `/${label.toLowerCase()}/${id}`,
style: { textDecoration: 'none' },
component: RouterLink,
}}
>
<MenuItem button={true} key={index} onClick={() => setOpen(false)}>
<Grid container justifyContent="space-between">
<Grid item xl={8}>
<Typography className={buildingText}>{name}</Typography>

<Typography className={addressText}>
{address !== name && address}
</Typography>
</Grid>
<Grid item xl={4}>
<Chip
color="primary"
label={label.toLowerCase()}
className={resultChip}
/>
</Grid>
</Grid>
</MenuItem>
</Link>
);
})
)}
</MenuList>
) : null}
</div>
</ClickAwayListener>
</div>
);
};

useEffect(() => {
if (query === '') {
setOpen(false);
} else if (selected === null) {
setOpen(true);
} else {
setOpen(false);
}
}, [query, selected]);

useEffect(() => {
const handleResize = () => {
setWidth(inputRef.current?.offsetWidth);
Expand All @@ -184,25 +121,64 @@ export default function Autocomplete() {
if (loading && query.trim() !== '') {
get<LandlordOrApartmentWithLabel[]>(`/api/search?q=${query}`, {
callback: (data) => {
setOptions(data);
setLoading(false);
},
});
}
}, [loading, query]);

const location = useLocation();
let placeholderText =
location.pathname === '/' && !drawerOpen
? 'Search by any location e.g. “301 College Ave”'
: 'Search';

/**
* @returns The the InputProps for the search bar depending on user's location.
* If: home and NavBar drawer is closed –> returns search bar style without red input adornment on right.
* If: home/NavBar drawer is open –> returns search bar style with red input adornment on right.
*/
const getInputProps = () => {
if (location.pathname === '/' && !drawerOpen) {
return {
style: { fontSize: isMobile ? 16 : 20 },
endAdornment: <>{loading ? <CircularProgress color="inherit" size={20} /> : null}</>,
startAdornment: (
<SearchIcon
style={{ fontSize: isMobile ? 17 : 22, marginLeft: isMobile ? -3 : 0 }}
className={homeSearchIcon}
/>
),
className: field,
};
} else {
return {
style: { height: isMobile ? '35px' : '45px', borderRadius: '10px' },
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
<div className={searchIconBackground} onClick={handleSearchIconClick}>
<SearchIcon className={searchIcon} />
</div>
</React.Fragment>
),
className: field,
};
}
};

return (
<div>
<div style={{ position: 'relative', width: drawerOpen ? '100%' : '65%' }}>
<TextField
fullWidth
ref={inputRef}
value={query}
placeholder="Search by any location e.g. “301 College Ave”"
placeholder={placeholderText}
className={text}
variant="outlined"
style={{
borderRadius: '6px',
width: !isMobile ? '70%' : '98%',
borderRadius: '10px',
width: !isMobile ? '100%' : '98%',
}}
onKeyDown={textFieldHandleListKeyDown}
onChange={(event) => {
Expand All @@ -211,20 +187,10 @@ export default function Autocomplete() {
handleOnChange(value);
}
}}
InputProps={{
style: { fontSize: isMobile ? 16 : 20 },
endAdornment: <>{loading ? <CircularProgress color="inherit" size={20} /> : null}</>,
startAdornment: (
<SearchIcon
style={{ fontSize: isMobile ? 17 : 22, marginLeft: isMobile ? -3 : 0 }}
className={searchIcon}
/>
),
className: field,
}}
InputProps={getInputProps()}
/>

<Menu />
</div>
);
}
};

export default Autocomplete;
37 changes: 24 additions & 13 deletions frontend/src/components/utils/NavBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { Link as RouterLink } from 'react-router-dom';
import LogoIcon from '../../../assets/navbar-logo.svg';
import { useLocation } from 'react-router-dom';
import { colors } from '../../../colors';
import auto from '../../Home/Autocomplete';
import Autocomplete from '../../Home/Autocomplete';
import { isAdmin } from '../../../utils/adminTool';

export type NavbarButton = {
Expand Down Expand Up @@ -112,8 +112,9 @@ const useStyles = makeStyles(() => ({
},
search: {
width: '50%',
marginRight: '25%',
marginLeft: '30px',
marginBottom: '-15px',
borderRadius: '10px',
},
searchHidden: {
width: '50%',
Expand All @@ -126,7 +127,8 @@ const useStyles = makeStyles(() => ({
marginLeft: '70%',
},
searchDrawer: {
fontSize: 5,
// fontSize: 5,
width: '100%',
marginBottom: '5%',
},
}));
Expand Down Expand Up @@ -187,16 +189,23 @@ const NavBar = ({ headersData, user, setUser }: Props): ReactElement => {
}, [user]);

useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth <= 600);
const handleResize = () => {
setIsMobile(window.innerWidth <= 600);
if (drawerOpen && window.innerWidth >= 960) {
setDrawerOpen(false);
}
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
}, [drawerOpen]);

const getDrawerChoices = () => {
return (
<ThemeProvider theme={muiTheme}>
<Grid className={searchDrawer}>{auto()}</Grid>
<Grid className={searchDrawer}>
<Autocomplete drawerOpen={drawerOpen} />
</Grid>
{headersData.map(({ label, href }, index) => {
return (
<Link component={RouterLink} to={href} color={GetButtonColor(label)} key={index}>
Expand Down Expand Up @@ -331,16 +340,16 @@ const NavBar = ({ headersData, user, setUser }: Props): ReactElement => {
);

const searchBar = location.pathname !== '/';

const displayDesktop = () => {
return (
<Grid container className={toolbar} alignItems="center" justifyContent="space-between">
<Grid item md={3}>
{homeLogo}
</Grid>
<Grid item md={6} className={searchBar ? search : searchHidden}>
{auto()}
<Grid item md={9} container alignItems="center">
<Grid item>{homeLogo}</Grid>
<Grid item className={searchBar ? search : searchHidden}>
<Autocomplete drawerOpen={drawerOpen} />
</Grid>
</Grid>

{isAdmin(user) && (
<Grid item md={1} className={menu} container justifyContent="flex-end">
{getAdminButton()}
Expand Down Expand Up @@ -381,7 +390,9 @@ const NavBar = ({ headersData, user, setUser }: Props): ReactElement => {
onClose: () => setDrawerOpen(false),
}}
>
<div className={drawerContainer}>{getDrawerChoices()}</div>
<div className={drawerContainer} style={{ width: '80%' }}>
{getDrawerChoices()}
</div>
</Drawer>
</Toolbar>
);
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const HomePage = (): ReactElement => {
const classes = useStyles();
const [data, setData] = useState<returnData>({ buildingData: [], isEnded: false });
const [isMobile, setIsMobile] = useState<boolean>(false);
const [drawerOpen] = useState<boolean>(false);

useEffect(() => {
get<returnData>(`/api/page-data/home/${loadingLength}`, {
Expand Down Expand Up @@ -80,7 +81,7 @@ const HomePage = (): ReactElement => {
</Box>

<Box pb={5} mx={0} mt={-4} paddingBottom={isMobile ? 4 : 8}>
<Autocomplete />
<Autocomplete drawerOpen={drawerOpen} />
</Box>
</Container>
</Box>
Expand Down
Loading