mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 07:25:48 +01:00
✨ Add autocomplete to Search Module
Suggestions when searching with the search bar Fixes #12
This commit is contained in:
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
Kbd,
|
||||
createStyles,
|
||||
Text,
|
||||
Popover,
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { Kbd, createStyles, Text, Popover, Autocomplete } from '@mantine/core';
|
||||
import { useDebouncedValue, useForm, useHotkeys } from '@mantine/hooks';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
@@ -36,13 +30,10 @@ export const SearchModule: IModule = {
|
||||
export default function SearchBar(props: any) {
|
||||
const { config, setConfig } = useConfig();
|
||||
const [opened, setOpened] = useState(false);
|
||||
const [results, setOpenedResults] = useState(false);
|
||||
const [icon, setIcon] = useState(<Search />);
|
||||
const queryUrl = config.settings.searchUrl ?? 'https://www.google.com/search?q=';
|
||||
const textInput = useRef<HTMLInputElement>();
|
||||
// Find a service with the type of 'Overseerr'
|
||||
const service = config.services.find((s) => s.type === 'Overseerr');
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
query: '',
|
||||
@@ -50,19 +41,12 @@ export default function SearchBar(props: any) {
|
||||
});
|
||||
|
||||
const [debounced, cancel] = useDebouncedValue(form.values.query, 250);
|
||||
const [data, setData] = useState([]);
|
||||
const [results, setResults] = useState<any[]>([]);
|
||||
useEffect(() => {
|
||||
if (form.values.query !== debounced || form.values.query === '') return;
|
||||
setOpened(false);
|
||||
setOpenedResults(true);
|
||||
if (service) {
|
||||
const serviceUrl = new URL(service.url);
|
||||
axios
|
||||
.post(`/api/modules/overseerr?query=${form.values.query}`, {
|
||||
service,
|
||||
})
|
||||
.then((res) => setData(res.data.results ?? []));
|
||||
}
|
||||
.get(`/api/modules/search?q=${form.values.query}`)
|
||||
.then((res) => setResults(res.data ?? []));
|
||||
}, [debounced]);
|
||||
useHotkeys([['ctrl+K', () => textInput.current && textInput.current.focus()]]);
|
||||
const { classes, cx } = useStyles();
|
||||
@@ -82,7 +66,10 @@ export default function SearchBar(props: any) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Data with label as item.name
|
||||
const autocompleteData = results.map((result) => ({
|
||||
label: result.phrase,
|
||||
value: result.phrase,
|
||||
}));
|
||||
return (
|
||||
<form
|
||||
onChange={() => {
|
||||
@@ -114,9 +101,6 @@ export default function SearchBar(props: any) {
|
||||
}, 20);
|
||||
})}
|
||||
>
|
||||
<Popover
|
||||
opened={results}
|
||||
target={
|
||||
<Popover
|
||||
opened={opened}
|
||||
position="bottom"
|
||||
@@ -129,8 +113,9 @@ export default function SearchBar(props: any) {
|
||||
onFocusCapture={() => setOpened(true)}
|
||||
onBlurCapture={() => setOpened(false)}
|
||||
target={
|
||||
<TextInput
|
||||
<Autocomplete
|
||||
variant="filled"
|
||||
data={autocompleteData}
|
||||
icon={icon}
|
||||
ref={textInput}
|
||||
rightSectionWidth={90}
|
||||
@@ -143,13 +128,6 @@ export default function SearchBar(props: any) {
|
||||
{...form.getInputProps('query')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
tip: Use the prefixes <b>!yt</b> and <b>!t</b> in front of your query to search on
|
||||
YouTube or for a Torrent respectively.
|
||||
</Text>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Text>
|
||||
Tip: Use the prefixes <b>!yt</b> and <b>!t</b> in front of your query to search on YouTube
|
||||
|
||||
19
src/pages/api/modules/search.ts
Normal file
19
src/pages/api/modules/search.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import axios from 'axios';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { q } = req.query;
|
||||
const response = await axios.get(`https://duckduckgo.com/ac/?q=${q}`);
|
||||
res.status(200).json(response.data);
|
||||
}
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
// Filter out if the reuqest is a POST or a GET
|
||||
if (req.method === 'GET') {
|
||||
return Get(req, res);
|
||||
}
|
||||
return res.status(405).json({
|
||||
statusCode: 405,
|
||||
message: 'Method not allowed',
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user