diff --git a/src/web/app/src/components/SearchInput/AuthorInput.tsx b/src/web/app/src/components/SearchInput/AuthorInput.tsx new file mode 100644 index 0000000000..d2ef5e542d --- /dev/null +++ b/src/web/app/src/components/SearchInput/AuthorInput.tsx @@ -0,0 +1,119 @@ +import { TextField } from '@material-ui/core'; +import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; +import { Autocomplete } from '@mui/material'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { searchServiceUrl } from '../../config'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + input: { + fontSize: '1.2rem', + color: theme.palette.text.primary, + borderRadius: `4rem`, + borderColor: theme.palette.info.main, + borderWidth: `2px`, + }, + listbox: { + fontSize: '1.2rem', + color: theme.palette.text.primary, + backgroundColor: theme.palette.background.paper, + '& :hover': { + color: theme.palette.text.secondary, + backgroundColor: theme.palette.background.default, + border: '2px solid', + }, + }, + }) +); + +const fetchResults = async (url: string | null) => { + if (url) { + try { + const res = await fetch(url); + if (res.ok) { + const results = await res.json(); + // return up to first 10 results + return results.res.slice(0, 10); + } + } catch (error) { + console.error(error); + return []; + } + } + return []; +}; + +interface AuthorInputInterface { + text: string; + setText: Dispatch>; + labelFor: string; +} + +const AuthorInput = ({ text, setText, labelFor }: AuthorInputInterface) => { + const cs = useStyles(); + const [options, setOptions] = useState<[{ author: string; highlight: string }]>(); + + useEffect(() => { + // debounce so it searches every 0.5 seconds, instead of on every stroke + const debounce = setTimeout(() => { + (async () => { + const prepareUrl = () => `${searchServiceUrl}/authors/autocomplete/?author=${text}`; + // Do the request if there is something to search for + const shouldFetch = () => text.length > 0; + const data = await fetchResults(shouldFetch() ? prepareUrl() : null); + if (data) { + setOptions(data); + } + })(); + }, 500); + + return () => clearTimeout(debounce); + }, [text]); + + // mui Autocomplete component https://mui.com/components/autocomplete/ + return ( + (typeof option === 'string' ? option : option.author)} + options={options || []} + // disable built-in filtering for search as you type + // https://mui.com/components/autocomplete/#search-as-you-type + filterOptions={(x) => x} + value={text} + onInputChange={(_event, newInputValue) => { + setText(newInputValue); + }} + fullWidth + classes={{ + listbox: cs.listbox, + }} + renderInput={(params) => ( + + )} + /> + ); +}; + +export default AuthorInput; diff --git a/src/web/app/src/components/SearchInput/SearchInput.tsx b/src/web/app/src/components/SearchInput/SearchInput.tsx index a2f222cda8..69cdb1969f 100644 --- a/src/web/app/src/components/SearchInput/SearchInput.tsx +++ b/src/web/app/src/components/SearchInput/SearchInput.tsx @@ -2,6 +2,7 @@ import { Dispatch, SetStateAction } from 'react'; import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; import { IconButton, TextField } from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; +import AuthorInput from './AuthorInput'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -12,13 +13,13 @@ const useStyles = makeStyles((theme: Theme) => marginTop: '1rem', }, customInput: { - borderRadius: `4rem 4rem 4rem 4rem`, + borderRadius: `4rem`, borderColor: theme.palette.info.main, borderWidth: `2px`, transition: theme.transitions.create(['background-color', 'border-color'], { duration: '.5s', }), - fontSize: '14px', + fontSize: '1.2rem', display: 'block', color: theme.palette.text.primary, }, @@ -61,27 +62,31 @@ const SearchInput = ({ text, setText, labelFor, clickEvent }: SearchInputInterfa return (
- setText(event.target.value)} - value={text} - InputProps={{ - classes: { - root: classes.customInput, - focused: classes.customInput, - notchedOutline: classes.customInput, - }, - }} - InputLabelProps={{ - classes: { - root: classes.customInputText, - focused: classes.customInputText, - }, - }} - /> + {labelFor === 'Look for an Author' ? ( + + ) : ( + setText(event.target.value)} + value={text} + InputProps={{ + classes: { + root: classes.customInput, + focused: classes.customInput, + notchedOutline: classes.customInput, + }, + }} + InputLabelProps={{ + classes: { + root: classes.customInputText, + focused: classes.customInputText, + }, + }} + /> + )} {clickEvent && (