mirror of
https://github.com/hsjobeki/noogle.git
synced 2024-12-20 20:31:32 +03:00
261 lines
6.4 KiB
TypeScript
261 lines
6.4 KiB
TypeScript
import React, { useState, useMemo, useEffect } from "react";
|
|
import Paper from "@mui/material/Paper";
|
|
import InputBase from "@mui/material/InputBase";
|
|
import IconButton from "@mui/material/IconButton";
|
|
import ClearIcon from "@mui/icons-material/Clear";
|
|
import {
|
|
Box,
|
|
debounce,
|
|
FormControl,
|
|
FormControlLabel,
|
|
FormLabel,
|
|
Grid,
|
|
Radio,
|
|
RadioGroup,
|
|
Tooltip,
|
|
Typography,
|
|
} from "@mui/material";
|
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
import { NixType, nixTypes } from "../../types/nix";
|
|
import { useRouter } from "next/router";
|
|
import ShareIcon from "@mui/icons-material/Share";
|
|
import { useSnackbar } from "notistack";
|
|
|
|
interface SelectOptionProps {
|
|
label: string;
|
|
handleChange: (value: string) => void;
|
|
value: string;
|
|
|
|
options: {
|
|
value: string;
|
|
label: string;
|
|
}[];
|
|
}
|
|
|
|
const SelectOption = (props: SelectOptionProps) => {
|
|
const { label, handleChange, options, value } = props;
|
|
|
|
const _handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const newVal = (event.target as HTMLInputElement).value as NixType;
|
|
handleChange(newVal);
|
|
};
|
|
|
|
return (
|
|
<FormControl
|
|
sx={{
|
|
flexDirection: "row",
|
|
}}
|
|
>
|
|
<FormLabel
|
|
sx={{
|
|
width: "7rem",
|
|
wordWrap: "unset",
|
|
margin: "auto",
|
|
padding: 1,
|
|
}}
|
|
>
|
|
<Typography sx={{ minWidth: "max-content" }}>{label}</Typography>
|
|
</FormLabel>
|
|
|
|
<RadioGroup
|
|
sx={{
|
|
width: "100%",
|
|
"&.MuiFormGroup-root": {
|
|
flexDirection: "row",
|
|
},
|
|
}}
|
|
value={value}
|
|
onChange={_handleChange}
|
|
>
|
|
{options.map(({ value, label }) => (
|
|
<FormControlLabel
|
|
key={value}
|
|
value={value}
|
|
control={<Radio />}
|
|
label={label}
|
|
/>
|
|
))}
|
|
</RadioGroup>
|
|
</FormControl>
|
|
);
|
|
};
|
|
|
|
export type Filter = { from: NixType; to: NixType };
|
|
|
|
export interface SearchInputProps {
|
|
handleSearch: (term: string) => void;
|
|
handleFilter: (filter: Filter | ((curr: Filter) => Filter)) => void;
|
|
clearSearch: () => void;
|
|
placeholder: string;
|
|
page: number;
|
|
}
|
|
|
|
export function SearchInput(props: SearchInputProps) {
|
|
const { handleSearch, clearSearch, placeholder, handleFilter, page } = props;
|
|
const { enqueueSnackbar } = useSnackbar();
|
|
const [term, setTerm] = useState("");
|
|
const [to, setTo] = useState<NixType>("any");
|
|
const [from, setFrom] = useState<NixType>("any");
|
|
|
|
const router = useRouter();
|
|
useEffect(() => {
|
|
const { query } = router;
|
|
if (query?.search) {
|
|
if (typeof query.search === "string") {
|
|
const search = query.search;
|
|
setTerm(search);
|
|
handleSearch(search);
|
|
}
|
|
}
|
|
if (query?.to) {
|
|
if (typeof query.to === "string") {
|
|
const to = query.to as NixType;
|
|
setTo(to);
|
|
handleFilter((curr) => ({ ...curr, to }));
|
|
}
|
|
}
|
|
if (query?.from) {
|
|
if (typeof query.from === "string") {
|
|
const from = query.from as NixType;
|
|
setFrom(from);
|
|
handleFilter((curr) => ({ ...curr, from }));
|
|
}
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [router]);
|
|
|
|
const handleShare = () => {
|
|
const queries = [];
|
|
if (term) {
|
|
queries.push(`search=${term}`);
|
|
}
|
|
|
|
queries.push(`page=${page}`);
|
|
|
|
queries.push(`to=${to}&from=${from}`);
|
|
const handle = `https://noogle.dev?${queries.join("&")}`;
|
|
navigator.clipboard.writeText(handle);
|
|
enqueueSnackbar("link copied to clipboard", { variant: "default" });
|
|
};
|
|
const handleSubmit = React.useRef((input: string) => {
|
|
handleSearch(input);
|
|
router.query[term] = term;
|
|
}).current;
|
|
const debouncedSubmit = useMemo(
|
|
() => debounce(handleSubmit, 300),
|
|
[handleSubmit]
|
|
);
|
|
|
|
const _handleFilter = (t: NixType, mode: "from" | "to") => {
|
|
if (mode === "to") {
|
|
setTo(t);
|
|
handleFilter({ to: t, from });
|
|
} else {
|
|
setFrom(t);
|
|
handleFilter({ to, from: t });
|
|
}
|
|
};
|
|
|
|
const handleClear = () => {
|
|
setTerm("");
|
|
setFrom("any");
|
|
setTo("any");
|
|
clearSearch();
|
|
};
|
|
const handleChange = (
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
) => {
|
|
setTerm(e.target.value);
|
|
debouncedSubmit(e.target.value);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Paper
|
|
component="form"
|
|
elevation={0}
|
|
sx={{
|
|
width: "100%",
|
|
p: 1,
|
|
my: 2,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
}}
|
|
onSubmit={(e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
handleSubmit(term);
|
|
}}
|
|
>
|
|
<IconButton aria-label="clear-button" onClick={handleClear}>
|
|
<ClearIcon />
|
|
</IconButton>
|
|
<InputBase
|
|
autoFocus
|
|
sx={{
|
|
ml: 1,
|
|
flex: 1,
|
|
backgroundColor: "paper.main",
|
|
p: 1,
|
|
}}
|
|
placeholder={placeholder}
|
|
inputProps={{ "aria-label": "search-input" }}
|
|
value={term}
|
|
onChange={(e) => handleChange(e)}
|
|
/>
|
|
<Tooltip title="share search result">
|
|
<IconButton
|
|
sx={{
|
|
p: 1,
|
|
color: "common.black",
|
|
}}
|
|
onClick={handleShare}
|
|
>
|
|
<ShareIcon fontSize="inherit" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</Paper>
|
|
|
|
<Box>
|
|
<Grid container>
|
|
<Grid item xs={12} md={5}>
|
|
<SelectOption
|
|
value={from}
|
|
label="from type"
|
|
handleChange={(value) => {
|
|
_handleFilter(value as NixType, "from");
|
|
}}
|
|
options={nixTypes.map((v) => ({ value: v, label: v }))}
|
|
/>
|
|
</Grid>
|
|
<Grid
|
|
item
|
|
md={2}
|
|
sx={{
|
|
display: {
|
|
md: "flex",
|
|
xs: "none",
|
|
},
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
}}
|
|
>
|
|
<Typography>
|
|
<ChevronRightIcon />
|
|
</Typography>
|
|
</Grid>
|
|
<Grid item xs={12} md={5}>
|
|
<SelectOption
|
|
value={to}
|
|
label="to type"
|
|
handleChange={(value) => {
|
|
_handleFilter(value as NixType, "to");
|
|
}}
|
|
options={nixTypes.map((v) => ({ value: v, label: v }))}
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
</Box>
|
|
</>
|
|
);
|
|
}
|