frontend/src/common/SearchBar.tsx
2024-12-18 14:00:21 -06:00

123 lines
3 KiB
TypeScript

import {
alpha,
Grid2,
IconButton,
InputAdornment,
InputBase,
Theme
} from "@mui/material";
import { makeStyles } from "@mui/styles";
import { Search as SearchIcon, Cancel as CancelIcon } from "@mui/icons-material";
import classNames from "classnames";
import { useDebounce, usePrevious } from "hooks";
import { useEffect, useState } from "react";
const useStyles = makeStyles((theme: Theme) => {
return ({
search: {
position: "relative",
width: "100%",
marginLeft: 0,
backgroundColor: alpha(theme.palette.common.white, 0.15),
"&:hover": {
backgroundColor: alpha(theme.palette.common.white, 0.25)
},
[theme.breakpoints.up("sm")]: {
width: "auto"
}
},
inputRoot: {
position: "relative",
color: "inherit",
width: "100%"
},
searchInput: {
width: "100%"
},
spacingLeft: {
marginLeft: theme.spacing(1)
},
spacingRight: {
marginRight: theme.spacing(1)
},
mutedIcon: {
color: theme.palette.text.secondary
},
roundBar: {
borderRadius: theme.shape.borderRadius
},
squareBar: {
borderRadius: 0
}
});
});
interface Props {
value: string;
onChange: (value: string) => void;
shape?: "round" | "square";
}
export default function SearchBar(props: Props) {
const classes = useStyles();
const { value, onChange, shape } = props;
const prevValue = usePrevious(value);
const [search, setSearch] = useState(value);
const debouncedSearch = useDebounce(search, 500);
const prevDebouncedSearch = usePrevious(debouncedSearch);
useEffect(() => {
if (value !== prevValue) {
setSearch(value);
}
}, [prevValue, value]);
useEffect(() => {
if (debouncedSearch !== prevDebouncedSearch) {
onChange(debouncedSearch);
}
}, [debouncedSearch, prevDebouncedSearch, onChange]);
const handleChange = (newSearch: string) => {
setSearch(newSearch);
};
return (
<Grid2
container
direction="row"
className={classNames(
classes.search,
shape === "square" ? classes.squareBar : classes.roundBar
)}>
<InputBase
autoFocus={false}
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.searchInput
}}
onChange={event => handleChange(event.target.value)}
value={search}
fullWidth
inputProps={{ "aria-label": "Search" }}
startAdornment={
<InputAdornment position="start">
<SearchIcon className={classes.spacingLeft} />
</InputAdornment>
}
endAdornment={
<InputAdornment position="end">
<IconButton
size="small"
aria-label="Cancel Search"
onClick={() => handleChange("")}
className={classes.spacingRight}>
<CancelIcon fontSize="small" className={classes.mutedIcon} />
</IconButton>
</InputAdornment>
}
/>
</Grid2>
);
}